11wk-2: Choropleth (plotly)

plotly
Author

최규빈

Published

November 15, 2023

1. 강의영상

2. Imports

# !conda install -c conda-forge plotly
import numpy as np
import pandas as pd
#---#
import plotly.express as px
import json 
import requests 

3. 에너지사용량 시각화

A. 데이터 불러오기

global_dict = json.loads(requests.get('https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-provinces-2018-geo.json').text)
local_dict = json.loads(requests.get('https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-municipalities-2018-geo.json').text)
#--#
url = 'https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/{}.csv'
prov = ['Seoul', 'Busan', 'Daegu', 'Incheon', 
        'Gwangju', 'Daejeon', 'Ulsan', 'Sejongsi', 
        'Gyeonggi-do', 'Gangwon-do', 'Chungcheongbuk-do', 
        'Chungcheongnam-do', 'Jeollabuk-do', 'Jeollanam-do', 
        'Gyeongsangbuk-do', 'Gyeongsangnam-do', 'Jeju-do']
df = pd.concat([pd.read_csv(url.format(p+y)).assign(년도=y, 시도=p) for p in prov for y in ['2018', '2019', '2020', '2021']]).reset_index(drop=True)\
.assign(년도 = lambda df: df.년도.astype(int))\
.set_index(['년도','시도','지역']).applymap(lambda x: int(str(x).replace(',','')))\
.reset_index()
df.head()
/tmp/ipykernel_1184324/712135142.py:12: FutureWarning:

DataFrame.applymap has been deprecated. Use DataFrame.map instead.
년도 시도 지역 건물동수 연면적 에너지사용량(TOE)/전기 에너지사용량(TOE)/도시가스 에너지사용량(TOE)/지역난방
0 2018 Seoul 종로구 17929 9141777 64818 82015 111
1 2018 Seoul 중구 10598 10056233 81672 75260 563
2 2018 Seoul 용산구 17201 10639652 52659 85220 12043
3 2018 Seoul 성동구 14180 11631770 60559 107416 0
4 2018 Seoul 광진구 21520 12054796 70609 130308 0

B. 데이터정리

(1) global_dict 내의 영어이름과 df의 영어이름이 일치하는지 확인

set(df.시도) == {l['properties']['name_eng'] for l in global_dict['features']}
True

(2) global_dict내의 영어이름과 한글이름을 이용해 변환을 위한 dictionary 생성

{l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']}
{'Seoul': '서울특별시',
 'Busan': '부산광역시',
 'Daegu': '대구광역시',
 'Incheon': '인천광역시',
 'Gwangju': '광주광역시',
 'Daejeon': '대전광역시',
 'Ulsan': '울산광역시',
 'Sejongsi': '세종특별자치시',
 'Gyeonggi-do': '경기도',
 'Gangwon-do': '강원도',
 'Chungcheongbuk-do': '충청북도',
 'Chungcheongnam-do': '충청남도',
 'Jeollabuk-do': '전라북도',
 'Jeollanam-do': '전라남도',
 'Gyeongsangbuk-do': '경상북도',
 'Gyeongsangnam-do': '경상남도',
 'Jeju-do': '제주특별자치도'}

(3) df에 변환을 수행하여 영어지명을 한글지명으로 변환

df.assign(
    시도 = lambda df: df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']})
)
년도 시도 지역 건물동수 연면적 에너지사용량(TOE)/전기 에너지사용량(TOE)/도시가스 에너지사용량(TOE)/지역난방
0 2018 서울특별시 종로구 17929 9141777 64818 82015 111
1 2018 서울특별시 중구 10598 10056233 81672 75260 563
2 2018 서울특별시 용산구 17201 10639652 52659 85220 12043
3 2018 서울특별시 성동구 14180 11631770 60559 107416 0
4 2018 서울특별시 광진구 21520 12054796 70609 130308 0
... ... ... ... ... ... ... ... ...
995 2019 제주특별자치도 서귀포시 34729 7233931 34641 1306 0
996 2020 제주특별자치도 제주시 66504 19819923 99212 22179 0
997 2020 제주특별자치도 서귀포시 34880 7330040 35510 1639 0
998 2021 제주특별자치도 제주시 67053 20275738 103217 25689 0
999 2021 제주특별자치도 서귀포시 35230 7512206 37884 2641 0

1000 rows × 8 columns

(4) local_dictglobal_dict의 지명정보를 정리하여 데이터프레임으로 만듦

# 예비학습

pd.DataFrame(
    [{'X':100,'y':0},
     {'X':101,'y':1}]
)    
X y
0 100 0
1 101 1

#

df_global = pd.DataFrame([l['properties'] for l in global_dict['features']])
df_global
name base_year name_eng code
0 서울특별시 2018 Seoul 11
1 부산광역시 2018 Busan 21
2 대구광역시 2018 Daegu 22
3 인천광역시 2018 Incheon 23
4 광주광역시 2018 Gwangju 24
5 대전광역시 2018 Daejeon 25
6 울산광역시 2018 Ulsan 26
7 세종특별자치시 2018 Sejongsi 29
8 경기도 2018 Gyeonggi-do 31
9 강원도 2018 Gangwon-do 32
10 충청북도 2018 Chungcheongbuk-do 33
11 충청남도 2018 Chungcheongnam-do 34
12 전라북도 2018 Jeollabuk-do 35
13 전라남도 2018 Jeollanam-do 36
14 경상북도 2018 Gyeongsangbuk-do 37
15 경상남도 2018 Gyeongsangnam-do 38
16 제주특별자치도 2018 Jeju-do 39
df_local = pd.DataFrame([l['properties'] for l in local_dict['features']])
df_local
name base_year name_eng code
0 종로구 2018 Jongno-gu 11010
1 중구 2018 Jung-gu 11020
2 용산구 2018 Yongsan-gu 11030
3 성동구 2018 Seongdong-gu 11040
4 광진구 2018 Gwangjin-gu 11050
... ... ... ... ...
245 함양군 2018 Hamyang-gun 38380
246 거창군 2018 Geochang-gun 38390
247 합천군 2018 Hapcheon-gun 38400
248 제주시 2018 Jeju-si 39010
249 서귀포시 2018 Seogwipo-si 39020

250 rows × 4 columns

(5) df_local에서 “전주시완산구”와 같이 정리된 지명들을 “완산구”로 변환

df_local.set_index('name')\
.rename(
    {name:name.split('시')[-1] for name in df_local['name'] if ('시' in name) and ('구' in name) and (len(name)>3)}
).reset_index()
name base_year name_eng code
0 종로구 2018 Jongno-gu 11010
1 중구 2018 Jung-gu 11020
2 용산구 2018 Yongsan-gu 11030
3 성동구 2018 Seongdong-gu 11040
4 광진구 2018 Gwangjin-gu 11050
... ... ... ... ...
245 함양군 2018 Hamyang-gun 38380
246 거창군 2018 Geochang-gun 38390
247 합천군 2018 Hapcheon-gun 38400
248 제주시 2018 Jeju-si 39010
249 서귀포시 2018 Seogwipo-si 39020

250 rows × 4 columns

(6) df_localdf_global의 정보를 정리하여 merge, 합쳐진 정보를 df_json에 저장

df_json = df_local.set_index('name')\
.rename(
    {name:name.split('시')[-1] for name in df_local['name'] if ('시' in name) and ('구' in name) and (len(name)>3)}
).reset_index()\
.drop(['base_year','name_eng'],axis=1)\
.rename({'name':'name_local','code':'code_local'},axis=1)\
.assign(code = lambda df: df['code_local'].str[:2])\
.merge(df_global.drop(['base_year','name_eng'],axis=1))
df_json
name_local code_local code name
0 종로구 11010 11 서울특별시
1 중구 11020 11 서울특별시
2 용산구 11030 11 서울특별시
3 성동구 11040 11 서울특별시
4 광진구 11050 11 서울특별시
... ... ... ... ...
245 함양군 38380 38 경상남도
246 거창군 38390 38 경상남도
247 합천군 38400 38 경상남도
248 제주시 39010 39 제주특별자치도
249 서귀포시 39020 39 제주특별자치도

250 rows × 4 columns

(7) df_json과 df의 정보를 merge하기 위하여 ’서울특별시-종로구’와 같은 형식으로 공통열을 각각 생성. 생성된 공통열의 원소가 일치하는지 비교

df_left = df.assign(
    시도 = lambda df: df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']})
).assign(on = lambda df: df.시도 + '-' + df.지역)
df_left
년도 시도 지역 건물동수 연면적 에너지사용량(TOE)/전기 에너지사용량(TOE)/도시가스 에너지사용량(TOE)/지역난방 on
0 2018 서울특별시 종로구 17929 9141777 64818 82015 111 서울특별시-종로구
1 2018 서울특별시 중구 10598 10056233 81672 75260 563 서울특별시-중구
2 2018 서울특별시 용산구 17201 10639652 52659 85220 12043 서울특별시-용산구
3 2018 서울특별시 성동구 14180 11631770 60559 107416 0 서울특별시-성동구
4 2018 서울특별시 광진구 21520 12054796 70609 130308 0 서울특별시-광진구
... ... ... ... ... ... ... ... ... ...
995 2019 제주특별자치도 서귀포시 34729 7233931 34641 1306 0 제주특별자치도-서귀포시
996 2020 제주특별자치도 제주시 66504 19819923 99212 22179 0 제주특별자치도-제주시
997 2020 제주특별자치도 서귀포시 34880 7330040 35510 1639 0 제주특별자치도-서귀포시
998 2021 제주특별자치도 제주시 67053 20275738 103217 25689 0 제주특별자치도-제주시
999 2021 제주특별자치도 서귀포시 35230 7512206 37884 2641 0 제주특별자치도-서귀포시

1000 rows × 9 columns

df_right = df_json.assign(on = lambda df: df.name + '-' + df.name_local)\
.drop(['name_local','name'],axis=1)
df_right
code_local code on
0 11010 11 서울특별시-종로구
1 11020 11 서울특별시-중구
2 11030 11 서울특별시-용산구
3 11040 11 서울특별시-성동구
4 11050 11 서울특별시-광진구
... ... ... ...
245 38380 38 경상남도-함양군
246 38390 38 경상남도-거창군
247 38400 38 경상남도-합천군
248 39010 39 제주특별자치도-제주시
249 39020 39 제주특별자치도-서귀포시

250 rows × 3 columns

set(df_left.on) == set(df_right.on)
False
set(df_right.on) - set(df_left.on)
{'인천광역시-남구'}
set(df_left.on) - set(df_right.on)
{'인천광역시-미추홀구'}

(8) 아래의 기사를 살펴보고 지역명을 적절히 변환

df_right.set_index('on')\
.rename({'인천광역시-남구':'인천광역시-미추홀구'})\
.reset_index()
on code_local code
0 서울특별시-종로구 11010 11
1 서울특별시-중구 11020 11
2 서울특별시-용산구 11030 11
3 서울특별시-성동구 11040 11
4 서울특별시-광진구 11050 11
... ... ... ...
245 경상남도-함양군 38380 38
246 경상남도-거창군 38390 38
247 경상남도-합천군 38400 38
248 제주특별자치도-제주시 39010 39
249 제주특별자치도-서귀포시 39020 39

250 rows × 3 columns

(9) 데이터프레임을 결합

df2 = df_left.merge(
    df_right.set_index('on')\
    .rename({'인천광역시-남구':'인천광역시-미추홀구'})\
    .reset_index()
).drop(['on'],axis=1)

C. 시각화 (2018년도 전기에너지 사용량)

df2
년도 시도 지역 건물동수 연면적 에너지사용량(TOE)/전기 에너지사용량(TOE)/도시가스 에너지사용량(TOE)/지역난방 code_local code
0 2018 서울특별시 종로구 17929 9141777 64818 82015 111 11010 11
1 2019 서울특별시 종로구 17851 9204140 63492 76653 799 11010 11
2 2020 서울특별시 종로구 17638 9148895 60123 71263 912 11010 11
3 2021 서울특별시 종로구 22845 18551145 125179 117061 0 11010 11
4 2018 서울특별시 중구 10598 10056233 81672 75260 563 11020 11
... ... ... ... ... ... ... ... ... ... ...
995 2021 제주특별자치도 제주시 67053 20275738 103217 25689 0 39010 39
996 2018 제주특별자치도 서귀포시 34154 6914685 34470 1597 0 39020 39
997 2019 제주특별자치도 서귀포시 34729 7233931 34641 1306 0 39020 39
998 2020 제주특별자치도 서귀포시 34880 7330040 35510 1639 0 39020 39
999 2021 제주특별자치도 서귀포시 35230 7512206 37884 2641 0 39020 39

1000 rows × 10 columns

px.choropleth_mapbox(
    geojson = local_dict,
    featureidkey = 'properties.code',
    data_frame = df2.query('년도 == 2018'),
    locations = 'code_local',
    color = '에너지사용량(TOE)/전기',
    hover_data = ['시도','지역'],
    #---#
    mapbox_style="carto-positron",
    center={"lat": 36, "lon": 127.5}, 
    zoom=6,
    height=800,
    width=800    
)

D. 시각화 (2018~2021년도 전기에너지 사용량)

seoul_dict = local_dict.copy() 
seoul_dict['features'] = [l for l in seoul_dict['features'] if l['properties']['code'][:2] == '11']
px.choropleth_mapbox(
    geojson = seoul_dict,
    featureidkey = 'properties.code',
    data_frame = df2,
    locations = 'code_local',
    color = '에너지사용량(TOE)/전기',
    animation_frame= '년도',
    hover_data = ['시도','지역'],
    #---#
    mapbox_style="carto-positron",
    range_color = [0,400000],
    center={"lat": 37.5642135, "lon": 127.0016985},
    zoom=9,
    height=500,
    width=700    
)

4. HW

2018~2021년도 대전의 전기에너지 사용량을 애니메이션으로 시각화