import numpy as np
import pandas as pd
import plotly.express as px
import plotly.io as pio
import json
import requests
import picklesupp-5: 기말고사 풀이
pd.options.plotting.backend = "plotly"
pio.templates.default = "plotly_white"1. NYCTaxi 자료 분석 (dashboard) – 100점
아래는 NYCTaxi자료에서 기본적인 전처리를 수행한 데이터프레임이다.
df = pd.read_csv("https://raw.githubusercontent.com/guebin/DV2023/main/posts/NYCTaxi.csv").assign(
log_trip_duration = lambda df: np.log(df.trip_duration),
pickup_datetime = lambda df: df.pickup_datetime.apply(pd.to_datetime),
dropoff_datetime = lambda df: df.dropoff_datetime.apply(pd.to_datetime),
dist = lambda df: np.sqrt((df.pickup_latitude-df.dropoff_latitude)**2 + (df.pickup_longitude-df.dropoff_longitude)**2),
#---#
vendor_id = lambda df: df.vendor_id.map({1:'A',2:'B'})
).assign(
speed = lambda df: df.dist / df.trip_duration,
pickup_hour = lambda df: df.pickup_datetime.dt.hour,
dropoff_hour = lambda df: df.dropoff_datetime.dt.hour,
dayofweek = lambda df: df.pickup_datetime.dt.dayofweek
)
df_small = df[::100].reset_index(drop=True)주어진 자료를 이용하여 (1)-(3)에 해당하는 시각화를 대시보드로 구현하고, 홈페이지를 남겨라. 답안 예시는 아래와 같다.
(답안예시)
주의사항
- 제출시간 이후에 대시보드 생성을 시도할 경우 부정행위로 간주하여 기말고사 전체를 0점처리함. (git에 기록남아있음)
- 대시보드 구현이 되어있지 않은 경우 0점 처리함
xaxis,yaxis,lengend등을 조정하는 것이 문제의도임. (구현되어있지 않으면 0점처리함)
힌트
아래와 같은 양식으로 qmd를 만들면 그림이 중복되어 출력되는 문제1가 발생하지 않음.
1 현재 quarto dashboard가 불완전(정식버전이 아니라 prereleased version임)하여 생기는 버그인듯합니다
NYCTaxi.qmd
---
title: "NYCTaxi"
author: "최규빈"
format: dashboard
execute:
enabled: true
cache: false
freeze: false
---
```{python}
#| output: false
# 여기에 온갖코드를 넣음.
# 1-(1),(2),(3) 에 대응하는 plotly figure를 아래와 같은 이름으로 저장
# fig1 = ...
# fig2 = ...
# fig3 = ...
```
# 기말고사1-(1),(2)
```{python}
#| title: 요일,시간에 따른 평균속력 시각화
fig1.show()
```
```{python}
#| title: 요일,시간에 따른 평균이동거리 시각화
fig2.show()
```
# 기말고사1-(3)
```{python}
#| title: 속력별 경로 시각화
fig3.show()
```(1) 요일,시간에 따른 평균속력 시각화 – 25점
자료 df에서 시간에 따른 평균속력을 구하고 이를 대시보드에 시각화하라.
README
- 요일은
{0:'월',1:'화',2:'수',3:'목',4:'금',5:'토',6:'일'}의 규칙에 따라 변환할 것
(2) 요일,시간에 따른 평균이동거리 시각화 – 25점
자료 df에서 시간에 따른 평균이동거리를 구하고 이를 대시보드에 시각화하라.
README
- 요일은
{0:'월',1:'화',2:'수',3:'목',4:'금',5:'토',6:'일'}의 규칙에 따라 변환할 것
(3) 속력별 경로시각화 – 50점
자료 df_small에서 속력을 quatile에 따라 4개의 구간으로 나누고, 구간별 이동경로를 대시보드에 시각화하라.
README
- Zoom = 11 로 설정할것. Figure의 width, height는 설정하지 말것
- 기타 설정값에 대해서는 궁금한것이 있다면 질문할 것
힌트:
힌트1: 아래의 코드를 관찰하세요.
speed = pd.Series([1,1,2,2,3,3,4,4])
print(pd.qcut(speed,4))
print(pd.qcut(speed,4,labels=['매우느림','조금느림','조금빠름','매우빠름']))0 (0.999, 1.75]
1 (0.999, 1.75]
2 (1.75, 2.5]
3 (1.75, 2.5]
4 (2.5, 3.25]
5 (2.5, 3.25]
6 (3.25, 4.0]
7 (3.25, 4.0]
dtype: category
Categories (4, interval[float64, right]): [(0.999, 1.75] < (1.75, 2.5] < (2.5, 3.25] < (3.25, 4.0]]
0 매우느림
1 매우느림
2 조금느림
3 조금느림
4 조금빠름
5 조금빠름
6 매우빠름
7 매우빠름
dtype: category
Categories (4, object): ['매우느림' < '조금느림' < '조금빠름' < '매우빠름']
힌트2: 1-(3)에 해당하는 그림을 fig3로 저장한후 아래의 코드를 관찰하세요
for i in range(150):
print(fig3.data[i].mode)이를 이용하여 legend를 수정하는 방법을 생각해보세요.
(풀이)
2. 에너지사용량 (지리정보시각화) – 50점
아래는 대한민국의 행정구역을 나타내는 json 파일과 2018-2021 기간동안의 에너지사용량이 저장된 url들이다.
# Json
https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-provinces-2018-geo.json
https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-municipalities-2018-geo.json
# DataFrame
https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Seoul2018.csv
https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Seoul2019.csv
https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Seoul2020.csv
https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Seoul2021.csv
...
https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Busan2018.csv
https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Busan2019.csv
https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Busan2020.csv
https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Busan2021.csv
주어진 자료를 활용하여 아래의 물음에 답하라.
주의사항
- ‘전주시완산구’,’완산구’와 같은 지역명은 ’전주시-완산구’와 같은 양식으로 정리하라.
- 인천광역시 남구는 새로운 이름인 미추홀구로 변경하라.
힌트
문제가 되는 지역명을 정리하면 아래와 같다.
s = pd.Series(['인천광역시-미추홀구',
'경기도-고양시-덕양구','경기도-고양시-일산동구','경기도-고양시-일산서구',
'경기도-성남시-분당구','경기도-성남시-수정구','경기도-성남시-중원구',
'경기도-수원시-권선구','경기도-수원시-영통구', '경기도-수원시-장안구', '경기도-수원시-팔달구',
'경기도-안산시-단원구', '경기도-안산시-상록구',
'경기도-안양시-동안구', '경기도-안양시-만안구',
'경기도-용인시-기흥구', '경기도-용인시-수지구', '경기도-용인시-처인구',
'경상남도-창원시-마산합포구', '경상남도-창원시-마산회원구', '경상남도-창원시-성산구', '경상남도-창원시-의창구', '경상남도-창원시-진해구',
'경상북도-포항시-남구', '경상북도-포항시-북구',
'전라북도-전주시-덕진구', '전라북도-전주시-완산구',
'충청남도-천안시-동남구', '충청남도-천안시-서북구',
'충청북도-청주시-상당구', '충청북도-청주시-서원구', '충청북도-청주시-청원구', '충청북도-청주시-흥덕구'])(사전풀이)
dct1 = {f'{prov}-{district}':f'{prov}-{city}-{district}' for prov,city,district in s[1:].str.split('-')}
dct2 = {f'{prov}-{city}{district}':f'{prov}-{city}-{district}' for prov,city,district in s[1:].str.split('-')}
dct2['인천광역시-남구'] = '인천광역시-미추홀구'print(dct1){'경기도-덕양구': '경기도-고양시-덕양구', '경기도-일산동구': '경기도-고양시-일산동구', '경기도-일산서구': '경기도-고양시-일산서구', '경기도-분당구': '경기도-성남시-분당구', '경기도-수정구': '경기도-성남시-수정구', '경기도-중원구': '경기도-성남시-중원구', '경기도-권선구': '경기도-수원시-권선구', '경기도-영통구': '경기도-수원시-영통구', '경기도-장안구': '경기도-수원시-장안구', '경기도-팔달구': '경기도-수원시-팔달구', '경기도-단원구': '경기도-안산시-단원구', '경기도-상록구': '경기도-안산시-상록구', '경기도-동안구': '경기도-안양시-동안구', '경기도-만안구': '경기도-안양시-만안구', '경기도-기흥구': '경기도-용인시-기흥구', '경기도-수지구': '경기도-용인시-수지구', '경기도-처인구': '경기도-용인시-처인구', '경상남도-마산합포구': '경상남도-창원시-마산합포구', '경상남도-마산회원구': '경상남도-창원시-마산회원구', '경상남도-성산구': '경상남도-창원시-성산구', '경상남도-의창구': '경상남도-창원시-의창구', '경상남도-진해구': '경상남도-창원시-진해구', '경상북도-남구': '경상북도-포항시-남구', '경상북도-북구': '경상북도-포항시-북구', '전라북도-덕진구': '전라북도-전주시-덕진구', '전라북도-완산구': '전라북도-전주시-완산구', '충청남도-동남구': '충청남도-천안시-동남구', '충청남도-서북구': '충청남도-천안시-서북구', '충청북도-상당구': '충청북도-청주시-상당구', '충청북도-서원구': '충청북도-청주시-서원구', '충청북도-청원구': '충청북도-청주시-청원구', '충청북도-흥덕구': '충청북도-청주시-흥덕구'}
print(dct2){'경기도-고양시덕양구': '경기도-고양시-덕양구', '경기도-고양시일산동구': '경기도-고양시-일산동구', '경기도-고양시일산서구': '경기도-고양시-일산서구', '경기도-성남시분당구': '경기도-성남시-분당구', '경기도-성남시수정구': '경기도-성남시-수정구', '경기도-성남시중원구': '경기도-성남시-중원구', '경기도-수원시권선구': '경기도-수원시-권선구', '경기도-수원시영통구': '경기도-수원시-영통구', '경기도-수원시장안구': '경기도-수원시-장안구', '경기도-수원시팔달구': '경기도-수원시-팔달구', '경기도-안산시단원구': '경기도-안산시-단원구', '경기도-안산시상록구': '경기도-안산시-상록구', '경기도-안양시동안구': '경기도-안양시-동안구', '경기도-안양시만안구': '경기도-안양시-만안구', '경기도-용인시기흥구': '경기도-용인시-기흥구', '경기도-용인시수지구': '경기도-용인시-수지구', '경기도-용인시처인구': '경기도-용인시-처인구', '경상남도-창원시마산합포구': '경상남도-창원시-마산합포구', '경상남도-창원시마산회원구': '경상남도-창원시-마산회원구', '경상남도-창원시성산구': '경상남도-창원시-성산구', '경상남도-창원시의창구': '경상남도-창원시-의창구', '경상남도-창원시진해구': '경상남도-창원시-진해구', '경상북도-포항시남구': '경상북도-포항시-남구', '경상북도-포항시북구': '경상북도-포항시-북구', '전라북도-전주시덕진구': '전라북도-전주시-덕진구', '전라북도-전주시완산구': '전라북도-전주시-완산구', '충청남도-천안시동남구': '충청남도-천안시-동남구', '충청남도-천안시서북구': '충청남도-천안시-서북구', '충청북도-청주시상당구': '충청북도-청주시-상당구', '충청북도-청주시서원구': '충청북도-청주시-서원구', '충청북도-청주시청원구': '충청북도-청주시-청원구', '충청북도-청주시흥덕구': '충청북도-청주시-흥덕구', '인천광역시-남구': '인천광역시-미추홀구'}
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)
df_global = pd.DataFrame([l['properties'] for l in global_dict['features']]).drop(['base_year','name_eng'],axis=1)
df_local = pd.DataFrame([l['properties'] for l in local_dict['features']]).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])
df_json = pd.merge(df_local,df_global)\
.assign(on = lambda df: df['name'] + '-' + df['name_local'])\
.set_index('on').rename(dct2).reset_index()\
.drop(['name_local','name'],axis=1)
df_json| 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
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()\
.assign(시도 = lambda df: df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']}))\
.assign(on = lambda df: df.시도 + '-' + df.지역)\
.set_index('on').rename(dct1).reset_index()\
.drop(['지역','시도'],axis=1)
display('df',df.head())
df2 = pd.merge(df_json,df).assign(
시도 = lambda df: df['on'].str.split('-').str[0],
지역 = lambda df: df['on'].str.split('-').str[1:].str.join('-')
).drop('on',axis=1).set_index(['시도','지역']).reset_index()
display('df2[200:210]',df2[200:210])
display('df2[370:380]',df2[370:380])/tmp/ipykernel_1181513/17364776.py:9: FutureWarning:
DataFrame.applymap has been deprecated. Use DataFrame.map instead.
'df'
| on | 년도 | 건물동수 | 연면적 | 에너지사용량(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 |
'df2[200:210]'
| 시도 | 지역 | code_local | code | 년도 | 건물동수 | 연면적 | 에너지사용량(TOE)/전기 | 에너지사용량(TOE)/도시가스 | 에너지사용량(TOE)/지역난방 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 200 | 인천광역시 | 동구 | 23020 | 23 | 2018 | 5414 | 2094127 | 10459 | 18695 | 0 |
| 201 | 인천광역시 | 동구 | 23020 | 23 | 2019 | 5157 | 2084655 | 10098 | 17304 | 0 |
| 202 | 인천광역시 | 동구 | 23020 | 23 | 2020 | 5096 | 2080453 | 10053 | 17113 | 0 |
| 203 | 인천광역시 | 동구 | 23020 | 23 | 2021 | 5015 | 2186129 | 10336 | 17704 | 0 |
| 204 | 인천광역시 | 미추홀구 | 23030 | 23 | 2018 | 28305 | 14479403 | 67154 | 125211 | 2282 |
| 205 | 인천광역시 | 미추홀구 | 23030 | 23 | 2019 | 27416 | 14515405 | 64888 | 115779 | 3161 |
| 206 | 인천광역시 | 미추홀구 | 23030 | 23 | 2020 | 26646 | 14476580 | 63312 | 114153 | 2314 |
| 207 | 인천광역시 | 미추홀구 | 23030 | 23 | 2021 | 25676 | 14830633 | 65328 | 116521 | 5337 |
| 208 | 인천광역시 | 연수구 | 23040 | 23 | 2018 | 5982 | 15321162 | 70421 | 55642 | 47047 |
| 209 | 인천광역시 | 연수구 | 23040 | 23 | 2019 | 6019 | 16083104 | 71719 | 53937 | 48334 |
'df2[370:380]'
| 시도 | 지역 | code_local | code | 년도 | 건물동수 | 연면적 | 에너지사용량(TOE)/전기 | 에너지사용량(TOE)/도시가스 | 에너지사용량(TOE)/지역난방 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 370 | 경기도 | 고양시-일산동구 | 31103 | 31 | 2020 | 11496 | 11754447 | 52447 | 33546 | 50595 |
| 371 | 경기도 | 고양시-일산동구 | 31103 | 31 | 2021 | 11530 | 11882593 | 53565 | 33855 | 51300 |
| 372 | 경기도 | 고양시-일산서구 | 31104 | 31 | 2018 | 6766 | 11412557 | 49505 | 28483 | 59278 |
| 373 | 경기도 | 고양시-일산서구 | 31104 | 31 | 2019 | 6823 | 11722973 | 49429 | 27013 | 56862 |
| 374 | 경기도 | 고양시-일산서구 | 31104 | 31 | 2020 | 6839 | 11738763 | 49913 | 27720 | 58026 |
| 375 | 경기도 | 고양시-일산서구 | 31104 | 31 | 2021 | 6861 | 11779013 | 51686 | 27710 | 58009 |
| 376 | 경기도 | 과천시 | 31110 | 31 | 2018 | 2290 | 2089132 | 11629 | 10145 | 8707 |
| 377 | 경기도 | 과천시 | 31110 | 31 | 2019 | 2311 | 2099538 | 10883 | 9811 | 8387 |
| 378 | 경기도 | 과천시 | 31110 | 31 | 2020 | 2264 | 2040791 | 9089 | 9695 | 8631 |
| 379 | 경기도 | 과천시 | 31110 | 31 | 2021 | 2339 | 2615206 | 9984 | 9818 | 10977 |
(1) 에너지사용량차이(전기-도시가스) 시각화 – 25점
에너지사용량(TOE)/전기와 에너지사용량(TOE)/도시가스의 차이를 계산하여 에너지사용량차이(전기-도시가스)라는 새로운 열로 추가하라. 수도권지역에 한정하여 에너지사용량차이(전기-도시가스)를 시각화하라. 시각화를 위해 plotly의 choropleth_mapbox를 사용하고, 다음 요구사항을 충족시켜라.
- 색상은
에너지사용량차이(전기-도시가스)값에 따라 달라져야 하며, 색상 범위(range_color)는 해당 열의 최소값과 최대값으로 설정하라. - 애니메이션 프레임은
년도를 기준으로 하라. - 호버 데이터는
시도와지역을 포함해야 한다. - 투명도는 0.5로 설정하라.
- 지도 스타일은 ’carto-positron’을 사용하며, 중심 좌표는 위도 37.5642135, 경도 127.0016985로 설정하라.
- 지도의 줌 레벨은 7.5로, 높이는 800, 너비는 750으로 설정하라.
- 스크롤 줌 기능은 비활성화하라.
주의사항
- 수도권지역은 서울,경기,인천을 의미한다.
- 호버데이터시 ‘고양시-덕양구’와 같은 양식이 아니라 ’고양시덕양구’ 혹은 ’덕양구’와 같은 방식으로 호버될 경우 0점 처리함. (인천광역시의 미추홀구 역시 구지명(인천광역시 남구)로 호버될경우 0점처리함)
(풀이)
metro_dict = local_dict.copy()
metro_dict['features'] = [l for l in metro_dict['features'] if (l['properties']['code'][:2] == '31' or l['properties']['code'][:2] == '23' or l['properties']['code'][:2] == '11')]
#---#
tidydata = df2.assign(diff = lambda df : df['에너지사용량(TOE)/전기']- df['에너지사용량(TOE)/도시가스'])\
.rename({'diff':'에너지사용량차이(전기-도시가스)'},axis=1)\
.query('시도 in ["서울특별시","경기도","인천광역시"]')
tidydata
#---#
range_color = tidydata['에너지사용량차이(전기-도시가스)'].min(), tidydata['에너지사용량차이(전기-도시가스)'].max()
fig = px.choropleth_mapbox(
geojson = metro_dict,
featureidkey = 'properties.code',
data_frame = tidydata,
locations = 'code_local',
color = '에너지사용량차이(전기-도시가스)',
animation_frame= '년도',
hover_data = ['시도','지역'],
opacity = 0.5,
#---#
mapbox_style="carto-positron",
range_color= range_color,
center={"lat": 37.5642135, "lon": 127.0016985},
zoom=7.5,
height=800,
width=750
)
fig.show(config={'scrollZoom':False})