지리 데이터 시각화(4) - folium으로 명목형 choropleth 지도 그리기, tool tip 사용
목적
우편번호 별로 권역 코드가 매핑되어 있는 데이터를 지도 위에 표현하고자합니다.
권역코드 별로 색상을 다르게하여 권역을 구분하고 우편번호를 지도 위에서 확인하겠습니다.
1. folium을 이용한 시각화
데이터 준비
folium의 경우 utf-8로 인코딩된 csv파일을 이용할 경우 오류가 납니다. euc-kr로 인코딩을 변경해주세요.
저는 mac 환경을 사용하고 있고 아래 명령어로 인코딩을 바꾸었습니다.
iconv -c -f utf-8 -t euc-kr code-bas_id.csv > code-bas_id_euc-kr.csv
file -I code-bas_id.csv, file -I code-bas_id_euc-kr.csv 명령어로 인코딩이 잘 된 것을 확인하였습니다.
colab에서 작업을 하였습니다.
- 지리 데이터 시각화(2)에서 만든 서울시 기초 구역도 geo json파일(TL_KODIS_BAS_W.json)
- 경기도 기초 구역도 geo json파일(TL_KODIS_BAS_W_GG.json)
- 위에서 euc-kr로 인코딩한 권역코드-우편번호 매핑 파일(code-bas_id_euc-kr.csv)을 불러옵니다.
필요한 라이브러리를 import 합니다.
import folium
import pandas as pd
import json
euc-kr로 인코딩된 권역코드-우편번호 매핑 csv 파일의 우편번호를 전처리하고 컬럼명을 변경합니다. ('우편번호*'->'BAS_ID', '권역코드*'->'AREA_CD')
df = pd.read_csv('sample_data/code-bas_id_euc-kr.csv', encoding = 'euc-kr')
df['우편번호*'] = df['우편번호*'].astype('str').str.zfill(5) # 다섯자리 맞춰주기 위해.
df.rename(columns={'우편번호*':'BAS_ID'}, inplace=True)
df.rename(columns={'권역코드*':'AREA_CD'}, inplace=True)
df.head()
서울 geo json 파일과 경기도 geo json 파일을 합쳐 geo_json이라는 변수를 만듭니다.
# 서울 geo json 가져오기
with open('sample_data/TL_KODIS_BAS_W.json') as response:
geo_json = json.load(response)
# 경기도 geo json 가져오기
with open('sample_data/TL_KODIS_BAS_W_GG.json') as response:
geo_json2 = json.load(response)
# 서울 geo json과 경기도 geo json 합치기.
d = dict()
# geo_json.keys() # dict_keys(['type', 'features'])
d['type'] = geo_json['type']
d['features'] = geo_json['features']
d['features'].extend(geo_json2['features'])
# len(d['features'])
geo_json = d
geo_json의 구조는 다음과 같습니다.
2. folium.Choropleth
만약 권역코드(AREA_CD) 가 연속형 변수일 경우 아래와 같이 쉽게 Choropleth 지도를 만들 수 있지만
권역 코드가 명목형 변수이기 때문에 folium.Choropleth를 이용할 수 없고 folium.GeoJson을 이용해야합니다.
m = folium.Map(location=[37.541, 126.986], zoom_start=10)
# Choropleth 레이어를 만들고, 맵 m에 추가.
folium.Choropleth(
geo_data=geo_json,
data=df,
columns=['BAS_ID', 'AREA_CD'],
key_on='feature.properties.BAS_ID', # feature.properties.BAS_ID
fill_color='YlGn',
).add_to(m)
m.save("index.html")
만약 명목형 변수 'AREA_CD'를 그냥 사용했을 경우 아래와 같은 오류가 납니다.
TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
3. folium.GeoJson
geo_json의 properties에 특정 값 추가
(folium.GeoJson은 주어진 json파일로만 그래프를 그립니다. 그래서 df에만 있던 AREA_CD 정보를 geo_json에 추가해주어야합니다.)
geo_json 데이터의 ['properties']에 ['AREA_CD']라는 키를 추가해 해당 우편번호의 권역 코드 값을 채웁니다.
df['BAS_ID']에 해당 우편번호가 없다면 AREA_CD엔 'None'값을 채웁니다.
for i in range(len(geo_json['features'])):
try:
area_cd = df[df['BAS_ID'] == geo_json['features'][i]['properties']['BAS_ID']]['AREA_CD'].tolist()[0]
geo_json['features'][i]['properties']['AREA_CD'] = area_cd
except:
geo_json['features'][i]['properties']['AREA_CD'] = 'None'
AREA_CD 추가 | |
![]() |
![]() |
이 데이터를 이용해 서울과 경기도 전체 지도를 대상으로 AREA_CD가 None인 것과 None이 아닌 것에 대해 색상을 다르게 해서 지도를 만들어보겠습니다.
참고: https://nbviewer.org/gist/talbertc-usgs/18f8901fc98f109f2b71156cf3ac81cd
center = [37.541, 126.986]
m = folium.Map(location=center, zoom_start=10)
def style_function(feature):
AREA_CD = feature['properties']['AREA_CD']
return {
'fillOpacity': 0.5,
'weight': 0.5,
'fillColor': 'gray' if AREA_CD == 'None' \
else 'orange'
}
gjson = folium.GeoJson(geo_json,
style_function=style_function
).add_to(m)
m.save("index.html")
위 코드에서 중요한 부분은 gjson = folium.GeoJson() 부분입니다. 그 위에 있는 함수는 스타일을 지정해주는 함수에요.
AREA_CD가 None인 부분은 회색, AREA_CD가 None이 아닌 부분은 주황색, 서울+경기가 아닌 부분은 색이 칠해지지 않은 지도를 생성하였습니다.
다음은 geo_json에서 AREA_CD가 None인 데이터를 제외하고 지도를 생성해보겠습니다.
new_geo_json = dict()
new_geo_json['type'] = geo_json['type']
new_geo_json['features'] = list()
for i in range(len(geo_json['features'])):
if geo_json['features'][i]['properties']['AREA_CD'] == 'None':
continue
else:
new_geo_json['features'].append(geo_json['features'][i])
geo_json = new_geo_json
색상도 조금 다양하게 바꿔보았습니다.
(색상을 조금 더 쉽게 설정하는 방법: 지리 데이터 시각화(6) 의 color 참고)
center = [37.541, 126.986]
m = folium.Map(location=center, zoom_start=10)
def style_function(feature):
AREA_CD = feature['properties']['AREA_CD']
return {
'fillOpacity': 0.5,
'fillColor': 'aliceblue' if AREA_CD[1:] == '00' \
else 'cyan'if AREA_CD[1:] == '01'\
else 'greenyellow'if AREA_CD[1:] == '02'\
else 'dimgray'if AREA_CD == 'S03'\
else 'forestgreen'if AREA_CD[1:] == '03'\
else 'yellow'if AREA_CD == 'S04'\
else 'deeppink'if AREA_CD[1:] == '04'\
else 'deepskyblue'if AREA_CD[1:] == '05'\
else 'black'if AREA_CD[1:] == '06'\
else 'blanchedalmond'if AREA_CD[1:] == '07'\
else 'blue'if AREA_CD[1:] == '08'\
else 'blueviolet'if AREA_CD[1:] == '09'\
else 'brown'if AREA_CD[1:] == '10'\
else 'gray'
}
gjson = folium.GeoJson(geo_json,
style_function=style_function
).add_to(m)
m.save("index.html")
4. folium.features.GeoJsonTooltip
지도위에 권역 별로 표시는 되었지만 해당 구역의 우편번호, 권역코드를 확인할 수 없는 불편함이 있었습니다.
이를 해결하기 위해 tool tip 을 추가해 Interactive choropleth 를 그려보았습니다. (참고: https://vverde.github.io/blob/interactivechoropleth.html)
위에서 만든 코드에 m.save("index.html")줄을 제거하고 아래 내용을 추가해줍니다.
style_function = lambda x: {'fillColor': '#ffffff',
'color':'#000000',
'fillOpacity': 0.1,
'weight': 0.1}
highlight_function = lambda x: {'fillColor': '#000000',
'color':'#000000',
'fillOpacity': 0.50,
'weight': 0.1}
NIL=folium.features.GeoJson(
geo_json,
style_function=style_function,
control=False,
highlight_function=highlight_function,
tooltip=folium.features.GeoJsonTooltip(fields=['BAS_ID','AREA_CD'],
aliases=['ZIP_CD','AREA_CD'],
style=("background-color: white; color: #333333; font-family: arial; font-size: 12px; padding: 10px;"),
sticky=True
)
)
m.add_child(NIL)
m.keep_in_front(NIL)
folium.LayerControl().add_to(m)
# m
m.save("index.html")
이렇게 마우스를 올리면 우편번호와 권역코드를 확인할 수 있는 툴팁이 추가 되었습니다.