python의 plotly 패키지를 활용하여 아래와 같은 choropleth map을 그리는 방법을 소개하겠습니다.
[시군구별 인구 현황_'23년 8월]
1. 준비물
① 시군구별 인구 데이터
아래 링크에서 제공하는 csv 파일을 활용합니다.
https://jumin.mois.go.kr/index.jsp#
주민등록 인구통계 행정안전부
jumin.mois.go.kr
② GeoJSON 파일
아래 zip 파일에서 법정구역_시군구_simplified.geojson 파일을 사용합니다.
https://drive.google.com/file/d/1VkVQJcyEUq2KW9E9mOUeSDIoiZibVcaG/view?usp=sharing
법정구역 GeoJSON 데이터_23년8월.zip
drive.google.com
대한민국 법정구역 정보를 담고있는 GeoJSON 파일을 직접 만드는 방법이 궁금한 분들은 아래 포스트를 참고해주세요.
대한민국 법정구역 SHP 파일을 GeoJSON으로 변환하기
튜토리얼 없이 GeoJSON파일만 필요한 분들은 아래 링크로 다운받으시면 됩니다. https://drive.google.com/file/d/1VkVQJcyEUq2KW9E9mOUeSDIoiZibVcaG/view?usp=sharing 법정구역 GeoJSON 데이터_23년8월.zip drive.google.com 튜토
jgws.tistory.com
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 |