이전에 포스팅한 "단계구분를 그려보자 2"에서는 국토정보플랫폼에서 인구데이터와 격자데이터를 동시에 받았습니다. 이번에는 국토정보플랫폼의 성남시 100M기준 격자데이터와 전국 어린이보호구역 데이터를 합쳐보겠습니다.
#기본 패키지
import numpy as np
import pandas as pd
import json
import folium
import geopandas as gpd
from geopandas import GeoDataFrame #메인
from shapely.geometry import Polygon, LineString, Point #격자, 선, 점
from fiona.crs import from_string
# 좌표 바꾸는 패키지
from pyproj import Transformer
import pyproj
# 심신의 안정 찾기(경고창 안뜨게 하기)
import warnings
warnings.filterwarnings('ignore')
#성남시 100m2 격자 shape파일
seongnam = gpd.GeoDataFrame.from_file('./shape/seongnam_100.shp', encoding='utf-8')
seongnam.crs
기본 패키지를 불러오고, 성남시의 좌표정보를 확인해봅시다.
EPSG가 5179인 것을 확인할 수 있습니다.
seongnam = seongnam.to_crs(epsg = 4326)
print(seongnam.shape)
EPSG를 4326(경위도 형태) 로 변경하고, shape를 통해 index와 column을 확인합니다.
14513개와 gid, lbl, val, geometry 컬럼이 확인됩니다. 저번에 포스팅한 공간정보, 인구 수, geometry(GeoPandas 필수요소) 입니다.
이후, 전국의 어린이 보호구역 데이터를 확인합시다.
경위도가 따로 나뉘어져 있는 DataFrame입니다.
GeoPandas로 변경하기 위해서는 geometry가 필요하기 때문에, 경위도를 병합하는 과정을 진행하겠습니다.
data['geometry'] = data.apply(lambda row : Point([row['경도'], row['위도']]), axis = 1)
data.head(3)
이렇게 확인해보면
geometry columns 경위도가 들어가 있는 것을 확인할 수 있습니다.
이제 GeoPandas로 변경해줍시다.
data = gpd.GeoDataFrame(data, geometry = 'geometry', crs = 'ESPG:4326')
data.crs
ESPG 4326형태의 GeoDataFrame으로 변경한 뒤, 확인하면 변경이 잘 된 것을 확인할 수 있습니다.
우리가 단계구분도로 나타내고자 하는 데이터는 어린이보호구역이 100m2로 나눈 격자단위 구역에 몇개 들어가 있는가 입니다. data 변수로 지정한 GeoDataFrame은 전국 어린이보호구역의 좌표이니, '어린이보호구역'이라는 컬럼을 추가하고 1을 지정하겠습니다.
data['어린이보호구역'] == 1
data.head(3)
우리가 여기에서 필요한 데이터는 geometry컬럼과 어린이보호구역컬럼 두 개 뿐입니다. 해당 데이터들만 분리해 공간결합합시다.
tmp = data[['geometry','어린이보호구역']]
df = gpd.sjoin(seongnam, tmp, how = 'left', predicate = 'intersects')
print('공간결합 전 데이터프레임 형태 : ', seongnam.shape, sep = '')
print('=' * 80)
print('공간결합 전 데이터프레임 형태 : ', df.shape, sep = '')
이렇게 진행하면 다음과 같은 결과를 확인할 수 있습니다.
sjoin은 merge와 유사한 함수인데, 다음과 같이 이해하면 편하겠습니다.
그러면 여기에서 드는 의문점. 'column개수가 늘어난건 그렇다 쳐도, index는 왜 늘어났을까?
이는 value_counts()로 확인 가능합니다.
tmp2 = df.groupby(by = 'gid', as_index=False)['어린이보호구역'].count()
tmp2.shape
tmp2['어린이보호구역'].value_counts()
를 통해 임시 DataFrame인 tmp2의 데이터를 확인하면 다음과 같은 개수 확인이 가능합니다.
이때 0은 내부에 어린이 보호구역이 하나도 없는 격자 개수, 1은 1개, 2는 2개를 의미합니다. 그래서 공간결합 후 5개의 index가 추가된 것이죠.
그래서 primary key 역할을 하는 'gid' 컬럼을 기준으로 중복을 제거하면
df = pd.DataFrame(df).drop_duplicates(subset = 'gid')
# 필요없는 컬럼 제거
df.drop(columns=['index_right', '어린이보호구역'], axis = 1, inplace = True)
df
최초 index개수와 같아진 것을 확인할 수 있습니다. 이제 합쳐봅시다.
# tmp3 데이터프레임의 '금연구역'값을 df와 합치기
df = pd.merge(df, tmp2, how = 'left', on = 'gid')
# 확인
df['어린이보호구역'].value_counts()
맨 우측에 어린이 보호구역 column이 기존 df에 추가된 것을 확인할 수 있습니다. 이제 GeoDataFrame으로 변경한 뒤 folium을 그려봅시다.
df = gpd.GeoDataFrame(df)
# folium map 생성
m = folium.Map(location=['37.4439', '127.1389'], # 성남시청
zoom_start=11,
tiles='OpenStreetMap')
# Choropleth 맵 생성
folium.Choropleth(
geo_data = df, # Choropleth 맵에 사용될 지리 정보
data = df, # Choropleth 맵에 사용될 데이터
columns = ['gid', '어린이보호구역'], # 데이터에서 사용될 열 이름
key_on='feature.properties.gid', # GeoJson에서 고유 식별자로 사용할 (지리적 영역을 구분할) 열 이름
fill_color='YlOrRd', # 색상 팔레트 설정 예) 'YlGnBu', 'YlOrRd'
fill_opacity= 0.7, # 채우기 투명도 설정 / 0 (투명) ~ 1
bins= 3, # 색 구간
line_color = 'black', # 경계선 색
line_opacity = 0.05, # 경계선 투명도
nan_fill_color = 'white', # 결측치 색
nan_fill_opacity = 0, # 결측치 투명도 - 0 (완전투명)
name = '성남시 어린이보호구역', # 이름
legend_name = '성남시 어린이보호구역' # 범례 이름 설정
).add_to(m)
# 레이어 컨트롤 추가
folium.LayerControl().add_to(m)
# 지도 띄우기
m
드디어 우리는 성남시 100제곱미터단위 격자구역의 어린이보호구역을 띄우는데 성공했습니다!
하나하나씩 찾으면서 했으면 오래걸렸을 포스팅인데, 동기 에이블러 덕분에 빠르게 끝낼 수 있었습니다. 다시한번

장훈햄 감사합니다!
'AIVLE_School' 카테고리의 다른 글
에이블스쿨 DX커리큘럼 - 졸업 (6) | 2023.07.21 |
---|---|
에이블스쿨 DX커리큘럼 - 공공데이터포털을 사용해보자 (0) | 2023.06.03 |
에이블스쿨 DX커리큘럼 - GeoPandas(단계구분도를 그려보자 2) (0) | 2023.06.02 |
에이블스쿨 DX커리큘럼 - GeoPandas(단계구분도를 그려보자 1) (0) | 2023.06.02 |
에이블스쿨 DX커리큘럼 - 공모전에 나가보자(금융 데이터 활용 아이디어 경진대회) (2) | 2023.05.31 |