python의 plotly 패키지를 활용하여 아래와 같은 choropleth map을 그리는 방법을 소개하겠습니다.
[시군구별 인구 현황_'23년 8월]
1. 준비물
① 시군구별 인구 데이터
아래 링크에서 제공하는 csv 파일을 활용합니다.
https://jumin.mois.go.kr/index.jsp#
② GeoJSON 파일
아래 zip 파일에서 법정구역_시군구_simplified.geojson 파일을 사용합니다.
https://drive.google.com/file/d/1VkVQJcyEUq2KW9E9mOUeSDIoiZibVcaG/view?usp=sharing
대한민국 법정구역 정보를 담고있는 GeoJSON 파일을 직접 만드는 방법이 궁금한 분들은 아래 포스트를 참고해주세요.
2. 데이터 준비
pandas를 활용하여 다운받은 데이터를 전처리해줍니다.
import pandas as pd
df = pd.read_csv('202308_202308_주민등록인구및세대현황_월간.csv',encoding='cp949')
# csv파일 읽어오기 (공공데이터는 웬만하면 encoding이 cp949인 것 같습니다.)
df = df[df['2023년08월_총인구수']!= '0'].copy()
# 인구정보가 없는 행을 삭제합니다.
df['2023년08월_총인구수']= df['2023년08월_총인구수'].str.replace(",","").astype('int')
# 총인구수가 str으로 저장되어있기 때문에 int 형식으로 바꾸어줍니다.
# '140,032' → 140032
def split_code_name(x):
splited = x.split('(')
return pd.Series([splited[0].strip(" "), splited[1][:5]])
df[['구역명','구역코드']] = df['행정구역'].apply(lambda x: split_code_name(x))
행정구역 열에 행정구역명과 코드가 함께 str로 작성되어있으므로 이를 구역명과 구역코드로 나눠주는 함수를 작성하여 적용합니다.
df['시도명'] = df['구역명'].str.split(' ').apply(lambda x: x[0])
# 구역명에서 시도명을 구분해줍니다. (ex. 서울특별시 종로구 → 서울특별시)
def abbreviate_province(name):
if name.endswith('북도') or name.endswith('남도'):
return name[0] + name[2]
else:
return name[:2]
# 시도명을 축약해주는 함수입니다. (ex. 서울특별시 → 서울, 경상남도 → 경남)
df['시도명(축약)'] = df['시도명'].apply(lambda x: abbreviate_province(x))
# 위 함수를 적용해줍니다.
import json
file = open('법정구역_시군구_simplified.geojson', encoding='utf-8')
gjson = json.load(file)
# geojson 파일을 읽어옵니다.
geojson 파일은 여러개의 Feature 데이터로 이루어져있으며, 각각의 Feature는 행정구역코드, 행정구역명 등의 properties와 좌표정보를 담고 있는 geometry로 구성되어있습니다.
시도_in_gjson = {feat['properties']['SIG_CD'] : feat['properties']['SIG_KOR_NM'] for feat in gjson['features']}
# GeoJSON에 포함된 feature들의 SIG_CD(행정구역코드) 와 SIG_KOR_NM(행정구역명)을 dictionary로 가져옵니다.
df['구역명확인'] = df['구역코드'].map(시도_in_gjson)
# df.구역코드에 들어있는 코드들이 geojson에도 동일하게 포함되어있는지 확인합니다.
# geojson에서 코드가 확인되지 않는 행들은 nan값을 가지게 됩니다.
not_found = df[df['구역명확인'].isna()].copy()
GeoJSON은 시군구 데이터로 이루어져있기 때문에 서울특별시, 경기도 등 시도 데이터는 없는 것을 확인할 수 있습니다.
또한 수원시, 성남시 등은 수원시 장안구, 수원시 영통구, 성남시 분당구, 성남시 수정구 등 구단위로 나뉘어져있기 때문에 시로 합쳐진 구역데이터는 GeoJSON에 없습니다.
df = df[df['구역명확인'].notna()].copy().reset_index(drop=True)
# GeoJSON에 없는 구역들은 삭제해줍니다.
3. 지도로 시각화 하기 (Choropleth Map 그리기)
import plotly.graph_objects as go
fig = go.Figure(data=go.Choropleth(geojson=gjson,
featureidkey='properties.SIG_CD',#GeoJSON에서 활용할 id키
locationmode= 'geojson-id',
locations=df['구역코드'],
z = df['2023년08월_총인구수'],
colorscale = [[0, '#F1EFEF'], [0.2, '#CCC8AA'], [0.4, '#B0AA7C'], [0.6, '#7D7C7C'], [0.8,'#606060'], [1, '#191717']],
# 컬러스케일은 원하는 갯수의 색상으로 구성할 수 있습니다.
hovertext = df['구역명(최종)']+": " +df['2023년08월_총인구수'].apply(lambda x: f'{x:,} 명'),
hoverinfo = 'text',
marker_line_color='#555',
# marker_line : 행정구역의 경계선
marker_line_width=1,
)
)
fig.update_layout(geo = dict(fitbounds="locations", # 설정하지 않으면 세계지도가 표시됩니다
visible = False, # 설정하지 않으면 세계지도가 표시됩니다
showframe=False,
),
margin={"r":0,"t":0,"l":0,"b":0},
)
html = fig.to_html()
with open('시군구별 인구 현황_202308.html', "w", encoding="utf-8") as f:
f.write(html)
# html 파일로 export 해줍니다.
생성된 html파일은 아래와 같습니다. 마우스를 지도에 가져다대면 해당 구역의 인구수가 표시되며 휠로 지도 크기를 조정할 수 있습니다. 원래 크기로 돌아오려면 마우스로 더블클릭 해줍니다.
[시군구별 인구 현황_202308.html]
우측의 칼라바가 거슬리는 경우 아래와 같이 정리해줄 수 있습니다.
fig.update_traces(colorbar = dict(len=0.4,
thickness=10,
dtick = 300000,
orientation = 'h',
exponentformat= "none",
separatethousands= True,
)
)
[시군구별 인구 현황_v2_202308.html]
디자인 요소를 추가하고 싶은 경우 차트를 html div로 출력하여 html 문서 내에 포함시킬 수 있습니다.
import plotly.offline as pyo
chart_div = pyo.plot(fig, include_plotlyjs=True, output_type='div')
title = '시군구별 인구 현황_202308'
dashboard = open('시군구별 인구 현황_v3_202308.html', 'w', encoding='utf-8')
dashboard.write(f'''
<html>
<meta charset="UTF-8">
<style>
body {{
background-color: white;
margin : auto;
width : 80%;
max-width : 800px;
}}
.title {{
text-align: left;
margin: 10px 0;
padding: 0 20px;
font-family: '맑은 고딕';
font-weight: bold;
border-left: 3px solid #C00000;
}}
.chart {{
padding: 10px 0;
font-family: '맑은 고딕';
}}
</style>
<body>
<div class = 'title'>{title}
</div>
<hr>
<div class = 'chart'>{chart_div}
</div>
</body>
</html>
''')
[시군구별 인구 현황_v3_202308.html]
'코딩' 카테고리의 다른 글
Plotly 사용법 튜토리얼_1. 막대그래프 (Bar Chart) (1) | 2023.09.27 |
---|---|
대한민국 법정구역 SHP 파일을 GeoJSON으로 변환하기 (0) | 2023.09.23 |
파이썬으로 예쁜 PPT 차트 그리기 (0) | 2023.09.22 |
OPENDART API를 활용하여 파이썬으로 기업 정보 검색하기 (0) | 2023.08.19 |
파이썬 Chat GPT로 인사 평가 의견을 자동 요약하기 (1) | 2023.06.27 |