{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# supp-5: 기말고사 풀이\n", "\n", "최규빈 \n", "2023-12-19\n", "\n", "" ], "id": "913d0bb2-eeaf-4963-a178-7fb0e99b937b" }, { "cell_type": "code", "execution_count": 1, "metadata": { "tags": [] }, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import plotly.express as px\n", "import plotly.io as pio\n", "import json\n", "import requests\n", "import pickle" ], "id": "ed421a5a-42c4-40d9-b2b6-ff076d455a26" }, { "cell_type": "code", "execution_count": 2, "metadata": { "tags": [] }, "outputs": [], "source": [ "pd.options.plotting.backend = \"plotly\"\n", "pio.templates.default = \"plotly_white\"" ], "id": "71bd9243-7e0b-4e07-81dc-2083e8042be9" }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 1. NYCTaxi 자료 분석 (dashboard) – 100점\n", "\n", "아래는 NYCTaxi자료에서 기본적인 전처리를 수행한 데이터프레임이다." ], "id": "35d2b41f-8d81-41d8-8db1-c5f51574d7df" }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "df = pd.read_csv(\"https://raw.githubusercontent.com/guebin/DV2023/main/posts/NYCTaxi.csv\").assign(\n", " log_trip_duration = lambda df: np.log(df.trip_duration),\n", " pickup_datetime = lambda df: df.pickup_datetime.apply(pd.to_datetime),\n", " dropoff_datetime = lambda df: df.dropoff_datetime.apply(pd.to_datetime),\n", " dist = lambda df: np.sqrt((df.pickup_latitude-df.dropoff_latitude)**2 + (df.pickup_longitude-df.dropoff_longitude)**2),\n", " #---#\n", " vendor_id = lambda df: df.vendor_id.map({1:'A',2:'B'})\n", ").assign(\n", " speed = lambda df: df.dist / df.trip_duration,\n", " pickup_hour = lambda df: df.pickup_datetime.dt.hour,\n", " dropoff_hour = lambda df: df.dropoff_datetime.dt.hour,\n", " dayofweek = lambda df: df.pickup_datetime.dt.dayofweek\n", ")\n", "df_small = df[::100].reset_index(drop=True)" ], "id": "ad7ea102-8a81-40ec-afee-1e9087a8a442" }, { "cell_type": "markdown", "metadata": {}, "source": [ "주어진 자료를 이용하여 (1)-(3)에 해당하는 시각화를 대시보드로 구현하고,\n", "홈페이지를 남겨라. 답안 예시는 아래와 같다.\n", "\n", "`(답안예시)`\n", "\n", "- \n", "- \n", "\n", "**주의사항**\n", "\n", "- 제출시간 이후에 대시보드 생성을 시도할 경우 부정행위로 간주하여\n", " 기말고사 전체를 0점처리함. (git에 기록남아있음)\n", "- 대시보드 구현이 되어있지 않은 경우 0점 처리함\n", "- `xaxis`, `yaxis`, `lengend` 등을 조정하는 것이 문제의도임.\n", " (구현되어있지 않으면 0점처리함)\n", "\n", "**힌트**\n", "\n", "아래와 같은 양식으로 qmd를 만들면 그림이 중복되어 출력되는 문제[1]가\n", "발생하지 않음.\n", "\n", "```` bash\n", "---\n", "title: \"NYCTaxi\"\n", "author: \"최규빈\"\n", "format: dashboard\n", "execute: \n", " enabled: true\n", " cache: false\n", " freeze: false\n", "---\n", "\n", "```{python}\n", "#| output: false\n", "# 여기에 온갖코드를 넣음.\n", "# 1-(1),(2),(3) 에 대응하는 plotly figure를 아래와 같은 이름으로 저장\n", "# fig1 = ...\n", "# fig2 = ...\n", "# fig3 = ...\n", "```\n", "\n", "# 기말고사1-(1),(2)\n", "\n", "```{python}\n", "#| title: 요일,시간에 따른 평균속력 시각화 \n", "fig1.show()\n", "```\n", "\n", "```{python}\n", "#| title: 요일,시간에 따른 평균이동거리 시각화 \n", "fig2.show()\n", "```\n", "\n", "# 기말고사1-(3)\n", "\n", "```{python}\n", "#| title: 속력별 경로 시각화\n", "fig3.show()\n", "```\n", "````\n", "\n", "## `(1)` 요일,시간에 따른 평균속력 시각화 – 25점\n", "\n", "자료 `df`에서 시간에 따른 평균속력을 구하고 이를 대시보드에 시각화하라.\n", "\n", "**README**\n", "\n", "- 요일은 `{0:'월',1:'화',2:'수',3:'목',4:'금',5:'토',6:'일'}`의 규칙에\n", " 따라 변환할 것\n", "\n", "## `(2)` 요일,시간에 따른 평균이동거리 시각화 – 25점\n", "\n", "자료 `df`에서 시간에 따른 평균이동거리를 구하고 이를 대시보드에\n", "시각화하라.\n", "\n", "**README**\n", "\n", "- 요일은 `{0:'월',1:'화',2:'수',3:'목',4:'금',5:'토',6:'일'}`의 규칙에\n", " 따라 변환할 것\n", "\n", "## `(3)` 속력별 경로시각화 – 50점\n", "\n", "자료 `df_small`에서 속력을 quatile에 따라 4개의 구간으로 나누고, 구간별\n", "이동경로를 대시보드에 시각화하라.\n", "\n", "**README**\n", "\n", "- Zoom = 11 로 설정할것. Figure의 width, height는 설정하지 말것\n", "- 기타 설정값에 대해서는 궁금한것이 있다면 질문할 것\n", "\n", "**힌트**:\n", "\n", "*힌트1*: 아래의 코드를 관찰하세요.\n", "\n", "[1] 현재 quarto dashboard가 불완전(정식버전이 아니라 prereleased\n", "version임)하여 생기는 버그인듯합니다" ], "id": "a49f1cf9-2de5-42a0-a6bf-213f2b25111c" }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "0 (0.999, 1.75]\n", "1 (0.999, 1.75]\n", "2 (1.75, 2.5]\n", "3 (1.75, 2.5]\n", "4 (2.5, 3.25]\n", "5 (2.5, 3.25]\n", "6 (3.25, 4.0]\n", "7 (3.25, 4.0]\n", "dtype: category\n", "Categories (4, interval[float64, right]): [(0.999, 1.75] < (1.75, 2.5] < (2.5, 3.25] < (3.25, 4.0]]\n", "0 매우느림\n", "1 매우느림\n", "2 조금느림\n", "3 조금느림\n", "4 조금빠름\n", "5 조금빠름\n", "6 매우빠름\n", "7 매우빠름\n", "dtype: category\n", "Categories (4, object): ['매우느림' < '조금느림' < '조금빠름' < '매우빠름']" ] } ], "source": [ "speed = pd.Series([1,1,2,2,3,3,4,4])\n", "print(pd.qcut(speed,4))\n", "print(pd.qcut(speed,4,labels=['매우느림','조금느림','조금빠름','매우빠름']))" ], "id": "af7e5828-c6b9-4be2-af26-5ca27b4ded51" }, { "cell_type": "markdown", "metadata": {}, "source": [ "*힌트2*: 1-(3)에 해당하는 그림을 fig3로 저장한후 아래의 코드를\n", "관찰하세요\n", "\n", "``` python\n", "for i in range(150):\n", " print(fig3.data[i].mode)\n", "```\n", "\n", "이를 이용하여 legend를 수정하는 방법을 생각해보세요.\n", "\n", "`(풀이)`\n", "\n", "- \n", "- \n", "\n", "# 2. 에너지사용량 (지리정보시각화) – 50점\n", "\n", "아래는 대한민국의 행정구역을 나타내는 `json` 파일과 2018-2021 기간동안의\n", "에너지사용량이 저장된 url들이다.\n", "\n", " # Json\n", " https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-provinces-2018-geo.json\n", " https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-municipalities-2018-geo.json\n", "\n", " # DataFrame\n", " https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Seoul2018.csv\n", " https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Seoul2019.csv\n", " https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Seoul2020.csv\n", " https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Seoul2021.csv\n", " ...\n", " https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Busan2018.csv\n", " https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Busan2019.csv\n", " https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Busan2020.csv\n", " https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/Busan2021.csv\n", "\n", "주어진 자료를 활용하여 아래의 물음에 답하라.\n", "\n", "**주의사항**\n", "\n", "- ‘전주시완산구’,’완산구’와 같은 지역명은 ’전주시-완산구’와 같은\n", " 양식으로 정리하라.\n", "- 인천광역시 남구는 새로운 이름인 미추홀구로 변경하라.\n", "\n", "**힌트**\n", "\n", "문제가 되는 지역명을 정리하면 아래와 같다." ], "id": "70e112da-feef-4461-9678-cef178790e8e" }, { "cell_type": "code", "execution_count": 5, "metadata": { "tags": [] }, "outputs": [], "source": [ "s = pd.Series(['인천광역시-미추홀구',\n", " '경기도-고양시-덕양구','경기도-고양시-일산동구','경기도-고양시-일산서구',\n", " '경기도-성남시-분당구','경기도-성남시-수정구','경기도-성남시-중원구',\n", " '경기도-수원시-권선구','경기도-수원시-영통구', '경기도-수원시-장안구', '경기도-수원시-팔달구',\n", " '경기도-안산시-단원구', '경기도-안산시-상록구',\n", " '경기도-안양시-동안구', '경기도-안양시-만안구',\n", " '경기도-용인시-기흥구', '경기도-용인시-수지구', '경기도-용인시-처인구',\n", " '경상남도-창원시-마산합포구', '경상남도-창원시-마산회원구', '경상남도-창원시-성산구', '경상남도-창원시-의창구', '경상남도-창원시-진해구',\n", " '경상북도-포항시-남구', '경상북도-포항시-북구',\n", " '전라북도-전주시-덕진구', '전라북도-전주시-완산구',\n", " '충청남도-천안시-동남구', '충청남도-천안시-서북구',\n", " '충청북도-청주시-상당구', '충청북도-청주시-서원구', '충청북도-청주시-청원구', '충청북도-청주시-흥덕구'])" ], "id": "a8f26e14-199e-47cb-8ce3-8462baec7f87" }, { "cell_type": "markdown", "metadata": {}, "source": [ "`(사전풀이)`" ], "id": "14debbf4-1dc3-43bc-98d7-2b5cd39e2f03" }, { "cell_type": "code", "execution_count": 6, "metadata": { "tags": [] }, "outputs": [], "source": [ "dct1 = {f'{prov}-{district}':f'{prov}-{city}-{district}' for prov,city,district in s[1:].str.split('-')}\n", "dct2 = {f'{prov}-{city}{district}':f'{prov}-{city}-{district}' for prov,city,district in s[1:].str.split('-')}\n", "dct2['인천광역시-남구'] = '인천광역시-미추홀구'" ], "id": "d61db09c-299c-4b69-b1f3-4c80e046a432" }, { "cell_type": "code", "execution_count": 7, "metadata": { "tags": [] }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "{'경기도-덕양구': '경기도-고양시-덕양구', '경기도-일산동구': '경기도-고양시-일산동구', '경기도-일산서구': '경기도-고양시-일산서구', '경기도-분당구': '경기도-성남시-분당구', '경기도-수정구': '경기도-성남시-수정구', '경기도-중원구': '경기도-성남시-중원구', '경기도-권선구': '경기도-수원시-권선구', '경기도-영통구': '경기도-수원시-영통구', '경기도-장안구': '경기도-수원시-장안구', '경기도-팔달구': '경기도-수원시-팔달구', '경기도-단원구': '경기도-안산시-단원구', '경기도-상록구': '경기도-안산시-상록구', '경기도-동안구': '경기도-안양시-동안구', '경기도-만안구': '경기도-안양시-만안구', '경기도-기흥구': '경기도-용인시-기흥구', '경기도-수지구': '경기도-용인시-수지구', '경기도-처인구': '경기도-용인시-처인구', '경상남도-마산합포구': '경상남도-창원시-마산합포구', '경상남도-마산회원구': '경상남도-창원시-마산회원구', '경상남도-성산구': '경상남도-창원시-성산구', '경상남도-의창구': '경상남도-창원시-의창구', '경상남도-진해구': '경상남도-창원시-진해구', '경상북도-남구': '경상북도-포항시-남구', '경상북도-북구': '경상북도-포항시-북구', '전라북도-덕진구': '전라북도-전주시-덕진구', '전라북도-완산구': '전라북도-전주시-완산구', '충청남도-동남구': '충청남도-천안시-동남구', '충청남도-서북구': '충청남도-천안시-서북구', '충청북도-상당구': '충청북도-청주시-상당구', '충청북도-서원구': '충청북도-청주시-서원구', '충청북도-청원구': '충청북도-청주시-청원구', '충청북도-흥덕구': '충청북도-청주시-흥덕구'}" ] } ], "source": [ "print(dct1)" ], "id": "62f00bcf-0823-42e9-8a76-351010051781" }, { "cell_type": "code", "execution_count": 8, "metadata": { "tags": [] }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "{'경기도-고양시덕양구': '경기도-고양시-덕양구', '경기도-고양시일산동구': '경기도-고양시-일산동구', '경기도-고양시일산서구': '경기도-고양시-일산서구', '경기도-성남시분당구': '경기도-성남시-분당구', '경기도-성남시수정구': '경기도-성남시-수정구', '경기도-성남시중원구': '경기도-성남시-중원구', '경기도-수원시권선구': '경기도-수원시-권선구', '경기도-수원시영통구': '경기도-수원시-영통구', '경기도-수원시장안구': '경기도-수원시-장안구', '경기도-수원시팔달구': '경기도-수원시-팔달구', '경기도-안산시단원구': '경기도-안산시-단원구', '경기도-안산시상록구': '경기도-안산시-상록구', '경기도-안양시동안구': '경기도-안양시-동안구', '경기도-안양시만안구': '경기도-안양시-만안구', '경기도-용인시기흥구': '경기도-용인시-기흥구', '경기도-용인시수지구': '경기도-용인시-수지구', '경기도-용인시처인구': '경기도-용인시-처인구', '경상남도-창원시마산합포구': '경상남도-창원시-마산합포구', '경상남도-창원시마산회원구': '경상남도-창원시-마산회원구', '경상남도-창원시성산구': '경상남도-창원시-성산구', '경상남도-창원시의창구': '경상남도-창원시-의창구', '경상남도-창원시진해구': '경상남도-창원시-진해구', '경상북도-포항시남구': '경상북도-포항시-남구', '경상북도-포항시북구': '경상북도-포항시-북구', '전라북도-전주시덕진구': '전라북도-전주시-덕진구', '전라북도-전주시완산구': '전라북도-전주시-완산구', '충청남도-천안시동남구': '충청남도-천안시-동남구', '충청남도-천안시서북구': '충청남도-천안시-서북구', '충청북도-청주시상당구': '충청북도-청주시-상당구', '충청북도-청주시서원구': '충청북도-청주시-서원구', '충청북도-청주시청원구': '충청북도-청주시-청원구', '충청북도-청주시흥덕구': '충청북도-청주시-흥덕구', '인천광역시-남구': '인천광역시-미추홀구'}" ] } ], "source": [ "print(dct2)" ], "id": "59d852a6-d25a-4a14-abcd-7b0e42c87326" }, { "cell_type": "code", "execution_count": 9, "metadata": { "tags": [] }, "outputs": [], "source": [ "global_dict = json.loads(requests.get('https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-provinces-2018-geo.json').text)\n", "local_dict = json.loads(requests.get('https://raw.githubusercontent.com/southkorea/southkorea-maps/master/kostat/2018/json/skorea-municipalities-2018-geo.json').text)\n", "df_global = pd.DataFrame([l['properties'] for l in global_dict['features']]).drop(['base_year','name_eng'],axis=1)\n", "df_local = pd.DataFrame([l['properties'] for l in local_dict['features']]).drop(['base_year','name_eng'],axis=1)\\\n", ".rename({'name':'name_local','code':'code_local'},axis=1)\\\n", ".assign(code = lambda df: df.code_local.str[:2])\n", "df_json = pd.merge(df_local,df_global)\\\n", ".assign(on = lambda df: df['name'] + '-' + df['name_local'])\\\n", ".set_index('on').rename(dct2).reset_index()\\\n", ".drop(['name_local','name'],axis=1)\n", "df_json" ], "id": "af15b5bd-f61e-46a3-aac1-0aa5695afe07" }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "/tmp/ipykernel_1181569/17364776.py:9: FutureWarning:\n", "\n", "DataFrame.applymap has been deprecated. Use DataFrame.map instead.\n" ] } ], "source": [ "url = 'https://raw.githubusercontent.com/guebin/DV2022/main/posts/Energy/{}.csv'\n", "prov = ['Seoul', 'Busan', 'Daegu', 'Incheon', \n", " 'Gwangju', 'Daejeon', 'Ulsan', 'Sejongsi', \n", " 'Gyeonggi-do', 'Gangwon-do', 'Chungcheongbuk-do', \n", " 'Chungcheongnam-do', 'Jeollabuk-do', 'Jeollanam-do', \n", " 'Gyeongsangbuk-do', 'Gyeongsangnam-do', 'Jeju-do']\n", "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)\\\n", ".assign(년도 = lambda df: df.년도.astype(int))\\\n", ".set_index(['년도','시도','지역']).applymap(lambda x: int(str(x).replace(',','')))\\\n", ".reset_index()\\\n", ".assign(시도 = lambda df: df.시도.map({l['properties']['name_eng']:l['properties']['name'] for l in global_dict['features']}))\\\n", ".assign(on = lambda df: df.시도 + '-' + df.지역)\\\n", ".set_index('on').rename(dct1).reset_index()\\\n", ".drop(['지역','시도'],axis=1)\n", "display('df',df.head())\n", "df2 = pd.merge(df_json,df).assign(\n", " 시도 = lambda df: df['on'].str.split('-').str[0],\n", " 지역 = lambda df: df['on'].str.split('-').str[1:].str.join('-')\n", ").drop('on',axis=1).set_index(['시도','지역']).reset_index()\n", "display('df2[200:210]',df2[200:210])\n", "display('df2[370:380]',df2[370:380])" ], "id": "12b662d8-0898-4e3e-897e-d6f32f729156" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `(1)` 에너지사용량차이(전기-도시가스) 시각화 – 25점\n", "\n", "`에너지사용량(TOE)/전기`와 `에너지사용량(TOE)/도시가스`의 차이를\n", "계산하여 `에너지사용량차이(전기-도시가스)`라는 새로운 열로 추가하라.\n", "수도권지역에 한정하여 `에너지사용량차이(전기-도시가스)`를 시각화하라.\n", "시각화를 위해 `plotly`의 `choropleth_mapbox`를 사용하고, 다음 요구사항을\n", "충족시켜라.\n", "\n", "- 색상은 `에너지사용량차이(전기-도시가스)` 값에 따라 달라져야 하며,\n", " 색상 범위(`range_color`)는 해당 열의 최소값과 최대값으로 설정하라.\n", "- 애니메이션 프레임은 `년도`를 기준으로 하라.\n", "- 호버 데이터는 `시도`와 `지역`을 포함해야 한다.\n", "- 투명도는 0.5로 설정하라.\n", "- 지도 스타일은 ’carto-positron’을 사용하며, 중심 좌표는 위도\n", " 37.5642135, 경도 127.0016985로 설정하라.\n", "- 지도의 줌 레벨은 7.5로, 높이는 800, 너비는 750으로 설정하라.\n", "- 스크롤 줌 기능은 비활성화하라.\n", "\n", "**주의사항**\n", "\n", "- 수도권지역은 서울,경기,인천을 의미한다.\n", "- 호버데이터시 ‘고양시-덕양구’와 같은 양식이 아니라 ’고양시덕양구’\n", " 혹은 ’덕양구’와 같은 방식으로 호버될 경우 0점 처리함. (인천광역시의\n", " 미추홀구 역시 구지명(인천광역시 남구)로 호버될경우 0점처리함)\n", "\n", "`(풀이)`" ], "id": "545e9366-a4f1-48bb-8a0c-f7f5b97a475d" }, { "cell_type": "code", "execution_count": 11, "metadata": { "tags": [] }, "outputs": [], "source": [ "metro_dict = local_dict.copy() \n", "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')]\n", "#---#\n", "tidydata = df2.assign(diff = lambda df : df['에너지사용량(TOE)/전기']- df['에너지사용량(TOE)/도시가스'])\\\n", ".rename({'diff':'에너지사용량차이(전기-도시가스)'},axis=1)\\\n", ".query('시도 in [\"서울특별시\",\"경기도\",\"인천광역시\"]')\n", "tidydata\n", "#---# \n", "range_color = tidydata['에너지사용량차이(전기-도시가스)'].min(), tidydata['에너지사용량차이(전기-도시가스)'].max()\n", "fig = px.choropleth_mapbox(\n", " geojson = metro_dict,\n", " featureidkey = 'properties.code',\n", " data_frame = tidydata,\n", " locations = 'code_local',\n", " color = '에너지사용량차이(전기-도시가스)',\n", " animation_frame= '년도',\n", " hover_data = ['시도','지역'],\n", " opacity = 0.5,\n", " #---#\n", " mapbox_style=\"carto-positron\",\n", " range_color= range_color,\n", " center={\"lat\": 37.5642135, \"lon\": 127.0016985},\n", " zoom=7.5,\n", " height=800,\n", " width=750 \n", ")\n", "fig.show(config={'scrollZoom':False})" ], "id": "9ce3a601-9bdd-4677-bf27-923162b435a0" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `(2)` (서울+김포)특별시? – 25점\n", "\n", "김포시의 서울편입이슈에 대한 아래의 제시문을 읽으라.\n", "\n", "> **김포시의 서울편입 이슈**\n", ">\n", "> 김포시를 서울특별시로 편입하자는 주장에 대한 논의가 활발해졌다. 2023년\n", "> 10월 23일, 국민의힘 소속 김포 지역 정치인들이 2026년 신설 예정인\n", "> 경기북부특별자치도에 김포시가 포함되는 것에 반대하며 서울특별시 편입을\n", "> 제안했다. 이 제안은 국민의힘 지도부의 호응을 받았고, 이로 인해 10월\n", "> 30일에는 이 문제가 국민의힘 내에서 중요한 논의 주제로 부상했다. 이후,\n", "> 이 주제는 제22대 국회의원 선거의 중요한 정치적 이슈로 자리 잡으며,\n", "> 많은 논쟁과 대중의 관심을 불러일으켰다. 이러한 정치적 변화는 한국 정치\n", "> 무대에서 논쟁의 새로운 중심점이 되었으며, 김포시의 행정적 미래에 대한\n", "> 광범위한 토론을 촉발하고 있다.\n", "\n", "김포시가 서울시에 편입되었다고 가정하고, (서울+김포)특별시 내 각 구별\n", "`에너지사용비율`을 아래의 수식을 참고하여 연도별로 계산하라.\n", "\n", ":::{.callout-note}\n", "\n", "2018년 김포시의 `에너지사용비율`, 2018년 서울시 서초구의\n", "`에너지사용비율`은 각각 아래와 같이 계산한다.\n", "\n", "- $\\text{에너지사용비율}_{\\text{김포시,2018}} = \\frac{\\text{에너지사용량}_\\text{김포시,2018}}{\\text{에너지사용량}_\\text{김포시,2018}+\\text{에너지사용량}_\\text{서울시강남구,2018}+...+\\text{에너지사용량}_\\text{서울시중랑구,2018}}$\n", "- $\\text{에너지사용비율}_{\\text{서울시서초구,2018}} = \\frac{\\text{에너지사용량}_\\text{서울시서초구,2018}}{\\text{에너지사용량}_\\text{김포시,2018}+\\text{에너지사용량}_\\text{서울시강남구,2018}+...+\\text{에너지사용량}_\\text{서울시중랑구,2018}}$\n", "\n", "이때 에너지사용량은 전기,도시가스,지역난방의 합을 의미한다. 계산된\n", "데이터를 바탕으로, 구별 `에너시사용비율`을 시각화 하라. 시각화를 위해\n", "`plotly`의 `choropleth_mapbox`를 사용하고, 다음 요구사항을 충족시켜라.\n", "\n", "- 색상은 ’에너지사용비율’에 따라 달라져야 하며, `range_color`는\n", " `에너시사용비율` 열의 최소값과 최대값으로 설정하라.\n", "- 애니메이션 프레임은 ’년도’를 기준으로 설정하라.\n", "- 호버 데이터에는 ’시도’와 ’지역’을 포함시켜라.\n", "- 지도의 투명도는 0.5로 설정하라.\n", "- 지도 스타일은 ’carto-positron’을 사용하고, 중심 좌표는 위도 37.5612,\n", " 경도 126.8228로 설정하라.\n", "- 지도의 줌 레벨은 9로, 높이는 800, 너비는 750으로 설정하라.\n", "- 스크롤 줌 기능은 비활성화라.\n", "\n", "`(풀이)`" ], "id": "397d9a05-fa4a-41b9-b293-7d0abeef7fae" }, { "cell_type": "code", "execution_count": 12, "metadata": { "tags": [] }, "outputs": [], "source": [ "df_local[df_local['name_local'] == '김포시']" ], "id": "aef0dc58-fa6a-41fb-ad6b-567a25dc4358" }, { "cell_type": "code", "execution_count": 13, "metadata": { "tags": [] }, "outputs": [], "source": [ "new_seoul_dict = local_dict.copy() \n", "new_seoul_dict['features'] = [l for l in metro_dict['features'] if (l['properties']['code'] == '31230' or l['properties']['code'][:2] == '11')]\n", "#---#\n", "df_big = df2.query('시도 == \"서울특별시\" or 지역 ==\"김포시\"').assign(에너지사용량=lambda df: df['에너지사용량(TOE)/전기']+df['에너지사용량(TOE)/도시가스']+df['에너지사용량(TOE)/지역난방'])\n", "df_small = df_big.groupby('년도').agg({'에너지사용량':'sum'}).reset_index().rename({'에너지사용량':'합'},axis=1)\n", "tidydata = df_big.merge(df_small).assign(구별사용비율 = lambda df: df['에너지사용량']/df['합'])\n", "#---#\n", "range_color = tidydata['구별사용비율'].min(), tidydata['구별사용비율'].max()\n", "fig = px.choropleth_mapbox(\n", " geojson = new_seoul_dict,\n", " featureidkey = 'properties.code',\n", " data_frame = tidydata,\n", " locations = 'code_local',\n", " color = '구별사용비율',\n", " animation_frame= '년도',\n", " hover_data = ['시도','지역'],\n", " opacity = 0.5,\n", " #---#\n", " mapbox_style=\"carto-positron\",\n", " range_color= range_color,\n", " center={\"lat\": 37.5612, \"lon\": 126.8228},\n", " zoom=9,\n", " height=800,\n", " width=750 \n", ")\n", "fig.show(config={'scrollZoom':False})" ], "id": "2095a848-69a7-47f9-9f58-bfea29ac1b56" }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 3. FIFA – 50점\n", "\n", "아래는 FIFA 자료를 불러오는 코드이다." ], "id": "8ba2ca22-fbac-441d-8b94-4cbdcc3ca56c" }, { "cell_type": "code", "execution_count": 14, "metadata": { "tags": [] }, "outputs": [], "source": [ "df = pd.read_csv('https://raw.githubusercontent.com/guebin/DV2021/master/_notebooks/2021-10-25-FIFA22_official_data.csv').drop(['Loaned From','Marking'],axis=1).dropna()\n", "df.head()" ], "id": "d00b3760-27fc-4b1a-bfab-57c1de63100a" }, { "cell_type": "markdown", "metadata": {}, "source": [ "아래는 {소속대륙:국가} 및 {포지션:세부포지션}에 정보를 담은\n", "딕셔너리이다." ], "id": "21956600-4e35-4243-979b-5ab48884ee51" }, { "cell_type": "code", "execution_count": 15, "metadata": { "tags": [] }, "outputs": [], "source": [ "continent_dict = {\n", " 'Asia': ['Afghanistan', 'Japan', 'Macau', 'Chinese Taipei', 'Indonesia', 'Korea Republic', 'Kazakhstan', 'Kyrgyzstan', 'Bhutan', 'Philippines', 'Syria', 'China PR', 'Oman', 'Guam', 'Vietnam', 'Jordan', 'Palestine', 'Malaysia', 'Hong Kong', 'Korea DPR', 'Lebanon', 'Uzbekistan', 'India','Iraq', 'Iran', 'Saudi Arabia', 'United Arab Emirates','Australia'],\n", " 'Europe': ['Portugal', 'Germany', 'Belgium', 'Netherlands', 'Croatia', 'Spain', 'Austria', 'Italy', 'France', 'Serbia', 'England', 'Poland', 'Ukraine', 'Wales', 'Scotland', 'Czech Republic', 'Slovakia', 'Romania', 'Bosnia and Herzegovina', 'Republic of Ireland', 'Norway', 'Sweden', 'Bulgaria', 'Lithuania', 'Estonia', 'Latvia', 'Liechtenstein','Albania','Denmark','Finland','Greece','Hungary','Iceland','Luxembourg','Northern Ireland','Slovenia','Switzerland','Andorra','Azerbaijan','Belarus','Cyprus','Faroe Islands','Georgia','Kosovo','Malta','Moldova','Montenegro','North Macedonia','Armenia','Gibraltar','Russia','Turkey','Israel'],\n", " 'South America': ['Uruguay', 'Argentina', 'Brazil', 'Chile', 'Colombia', 'Ecuador', 'Paraguay', 'Venezuela', 'Suriname', 'Bolivia','Peru','Guyana'],\n", " 'Africa': ['Egypt', \"Côte d'Ivoire\", 'Senegal', 'Morocco', 'Ghana', 'Algeria', 'Guinea', 'Mali', 'Congo DR', 'Liberia', 'Cameroon', 'Tunisia', 'Comoros', 'Kenya', 'South Africa', 'Zimbabwe', 'Madagascar', 'Mozambique', 'Equatorial Guinea', 'Congo', 'Burundi', 'Grenada', 'Thailand', 'Togo', 'Sudan', 'Mauritania','Guinea Bissau','Libya','Nigeria','Zambia','Angola','Benin','Burkina Faso','Cape Verde Islands','Central African Republic','Chad','Eritrea','Gabon','Gambia','Mauritius','Namibia','Rwanda','Sierra Leone','South Sudan','São Tomé e Príncipe','Uganda','Niger'],\n", " 'North and Central America': ['Antigua and Barbuda', 'Barbados', 'Belize', 'Bermuda', 'Canada', 'Costa Rica', 'Cuba', 'Curacao', 'Dominican Republic', 'El Salvador', 'Guatemala', 'Haiti', 'Honduras', 'Jamaica', 'Mexico', 'Montserrat', 'Panama', 'Puerto Rico', 'Saint Kitts and Nevis', 'Saint Lucia', 'Trinidad and Tobago', 'United States'],\n", " 'Oceania': ['New Zealand', 'Fiji', 'Papua New Guinea','New Caledonia'],\n", "}\n", "position_dict = {\n", " 'GOALKEEPER':{'GK'},\n", " 'DEFENDER':{'CB','RCB','LCB','RB','LB','RWB','LWB'},\n", " 'MIDFIELDER':{'CM','RCM','LCM','CDM','RDM','LDM','CAM','RAM','LAM','RM','LM'},\n", " 'FORWARD':{'ST','CF','RF','LF','RW','LW','RS','LS'},\n", " 'SUB':{'SUB'},\n", " 'RES':{'RES'}\n", "}" ], "id": "59371ef5-4ab3-42d2-808a-fb10f9b46c25" }, { "cell_type": "markdown", "metadata": {}, "source": [ "주어진 자료를 활용하여 아래를 잘 읽고 물음에 답하라.\n", "\n", "`(데이터정리)`" ], "id": "b50b0d31-8092-43dc-af13-2ecdfcd4738a" }, { "cell_type": "code", "execution_count": 16, "metadata": { "tags": [] }, "outputs": [], "source": [ "df = pd.read_csv('https://raw.githubusercontent.com/guebin/DV2021/master/_notebooks/2021-10-25-FIFA22_official_data.csv').drop(['Loaned From','Marking'],axis=1).dropna()\\\n", ".assign(\n", " Position = lambda df: df.Position.str.split('>').str[-1],\n", ").assign(\n", " Continent = lambda df: [k for x in df['Nationality'] for k,v in continent_dict.items() if x in v],\n", " Position = lambda df: [k for x in df['Position'] for k,v in position_dict.items() if x in v],\n", " Wage = lambda df: df.Wage.str[1:].str.replace('M','*1000000').str.replace('K','*1000').apply(eval), \n", "\n", ")\n", "df.head()" ], "id": "cd88a2b1-f227-40db-bf34-03a38ded3bb0" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `(1)` 대륙별 인적자원 – 20점\n", "\n", "각 대륙 및 국가별 인적 자원 현황을 시각화하고자 한다. 이를 위해 두 가지\n", "형태의 시각화를 구현했다." ], "id": "c752af47-ce40-448a-a6d7-f26e872eb7a8" }, { "cell_type": "code", "execution_count": 17, "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = pio.from_json(requests.get('https://raw.githubusercontent.com/guebin/DV2023/main/posts/figure_31.json').text)\n", "pio.show(fig,config={'scrollZoom':False})" ], "id": "a87ffdab-9986-4607-bb26-06f632e15b9f" }, { "cell_type": "code", "execution_count": 18, "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = pio.from_json(requests.get('https://raw.githubusercontent.com/guebin/DV2023/main/posts/figure_32.json').text)\n", "pio.show(fig,config={'scrollZoom':False})" ], "id": "6df8aea9-38c1-4f49-8d69-b5cf1c445491" }, { "cell_type": "markdown", "metadata": {}, "source": [ "첫 번째 시각화(Figure 1)를 통해, 각 대륙별로 선수들의 평균 능력치, 평균\n", "급여, 그리고 선수층의 깊이를 비교할 수 있었다. 이를 바탕으로, 남미\n", "대륙이 평균 능력치에서, 아프리카가 평균 급여에서 높은 값을 보였으며,\n", "유럽은 가장 깊은 선수층을 가지고 있는 것으로 나타났다. 두 번째\n", "시각화(Figure 2)는 나라별로 세분화된 인적 자원을 조사하는 데 중점을\n", "두었다. 이 그림에서 브라질은 선수 수, 평균 능력치, 평균 급여 등 여러\n", "면에서 우수함을 확인할 수 있었다. 반면, 우크라이나는 선수들의 평균\n", "능력치는 높지만 선수 수와 평균 급여는 상대적으로 낮은 값을 보었다. 두\n", "번째 시각화를 이용해 국가별 특색을 좀 더 면밀히 파악할 수 있었다.\n", "\n", "Figure 1과 Figure 2를 구현하라.\n", "\n", "`(풀이)`" ], "id": "92ae8376-879b-4620-85c1-94ff4e06d80c" }, { "cell_type": "code", "execution_count": 19, "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = df.assign(\n", " logWage = lambda df: np.log(df.Wage)\n", ").groupby(['Continent']).agg({'Overall':['mean','size'],'logWage':'mean'})\\\n", ".set_axis(['Overall(Mean)','PlayerSize','logWage(Mean)'],axis=1).reset_index()\\\n", ".assign(logPlayerSize = lambda df: np.log(df['PlayerSize']))\\\n", ".plot.scatter(\n", " x='Overall(Mean)',\n", " y='logWage(Mean)',\n", " size='PlayerSize',\n", " text='Continent',\n", " hover_data = ['Continent'],\n", " color = 'Continent',\n", " title = 'Figure1: 인적자원 (대륙별)',\n", " #---#\n", " height=400,\n", " width=750\n", ")\n", "fig.show(config={'scrollZoom':False})" ], "id": "9a1f3615-caeb-447a-afca-a80c65b9d16e" }, { "cell_type": "code", "execution_count": 20, "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = df.assign(\n", " logWage = lambda df: np.log(df.Wage)\n", ").groupby(['Nationality','Continent']).agg({'Overall':['mean','size'],'logWage':'mean'})\\\n", ".set_axis(['Overall(Mean)','PlayerSize','logWage(Mean)'],axis=1).reset_index()\\\n", ".assign(logPlayerSize = lambda df: np.log(df['PlayerSize']))\\\n", ".plot.scatter(\n", " x='Overall(Mean)',\n", " y='logWage(Mean)',\n", " size='PlayerSize',\n", " hover_data = ['Continent','Nationality'],\n", " color = 'Continent',\n", " title = 'Figure2: 인적자원 (국가별)',\n", " #---#\n", " height=400,\n", " width=750\n", ")\n", "fig.show(config={'scrollZoom':False})\n", "with open('/home/cgb2/Dropbox/07_lectures/2023-09-DV2023/posts/figure_32.json', 'w') as file:\n", " file.write(fig.to_json())" ], "id": "97a9e092-cae9-433d-b2cf-82855f50b4e6" }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `(2)` 아시안컵은 누가 차지할까 – 30점(+$\\alpha$)\n", "\n", "곧 시작될 아시안컵의 우승 국가를 예측하기 위해, 아시아 국가들의 축구\n", "선수들을 기준으로 한 새로운 강함 지표를 개발했다. 이 지표는 선수들의\n", "평균 능력치와 선수 수를 결합한 것이다. 예를 들어, 한국과 일본의 Strength\n", "지표는 각각 다음과 같이 계산된다:\n", "\n", "- 한국의 Strength = 한국 선수들의 평균 능력치 + 한국 선수들의 수 / 5\n", "- 일본의 Strength = 일본 선수들의 평균 능력치 + 일본 선수들의 수 / 5\n", "\n", "이를 바탕으로 아래와 같은 두가지 형태의 시각화를 구현했다." ], "id": "9ea31f53-f7f4-4748-a4ba-39b8b2fbf3ba" }, { "cell_type": "code", "execution_count": 21, "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = pio.from_json(requests.get('https://raw.githubusercontent.com/guebin/DV2023/main/posts/figure_33.json').text)\n", "pio.show(fig,config={'scrollZoom':False})" ], "id": "df964c55-d5e6-43e7-b6df-e6a39c7482a8" }, { "cell_type": "code", "execution_count": 22, "metadata": { "tags": [] }, "outputs": [], "source": [ "fig = pio.from_json(requests.get('https://raw.githubusercontent.com/guebin/DV2023/main/posts/figure_34.json').text)\n", "pio.show(fig,config={'scrollZoom':False})" ], "id": "f4397516-4d93-4daa-895f-e99417d00cdb" }, { "cell_type": "markdown", "metadata": {}, "source": [ "이 첫 번째 시각화(Figure 1)를 통해, 각 국가의 Strength를 기반으로 8강\n", "진출 국가를 예상했다. 그 결과\n", "일본,한국,호주,사우디아라비아,이란,중국,시리아,우즈베키스탄 순으로\n", "8강진출국이 예상되었다. 또한 두 번째 시각화 (Figure22)에서는\n", "`Strength`가 높은 상위 6개 국가를 선별하여, 국가별 포지션에 따른 평균\n", "능력치(Overall)를 비교함으로써 각 국가의 강점과 약점을 분석했다. 이\n", "분석에 따르면, 일본은 모든 포지션에서 뛰어난 능력치를 보였고, 이란은\n", "특히 골키퍼와 공격수 포지션에서 매우 우수한 것으로 나타났다.\n", "\n", "Figure1,2를 재현하라.\n", "\n", "**주의사항**\n", "\n", "- Figure1에서 투명도는 구현하지 않아도 무방하며 투명도 구현시 10점의\n", " 가산점이 있음. (Figure1에서 8강 진출이 예상되는 국가에 대한 투명도는\n", " 0.9로, 그렇지 않는 국가에 대한 투명도는 0.5로 설정)\n", "- Figure2에서 카테고리들의 정렬은 구현하지 않아도 무방함. (즉 국가가\n", " 일본,한국,…,중국 순서로 정렬될 필요는 없으며 포지션도\n", " 골키퍼,수비수,미드필더,공격수 순으로 정렬될 필요없음) 구현시 10점\n", " 가산점 있음.\n", "- 이외의 사항은 모두 구현되어야 하며 올바르게 구현되지 않은 경우 0점\n", " 처리함.\n", "\n", "`(풀이)`" ], "id": "b4c23680-fdcc-40b9-8c2a-4ff251fb7379" }, { "cell_type": "code", "execution_count": 23, "metadata": { "tags": [] }, "outputs": [], "source": [ "tidydata = df.query('Continent == \"Asia\"')\\\n", ".groupby('Nationality').agg({'Overall':['size','mean']})\\\n", ".set_axis(['PlayerSize','Overall(mean)'],axis=1).reset_index()\\\n", ".assign(Strength = lambda df: df['Overall(mean)']+df['PlayerSize']/5)\\\n", ".sort_values('Strength',ascending=False).reset_index(drop=True)\\\n", ".assign(Top8 = lambda df: df.index<8)\\\n", ".rename({'Top8':'8강 가능성'},axis=1)\\\n", ".sort_values('Strength')\\\n", ".assign(Text = lambda df: [f'{x:.2f} + {y/5:.2f} = {z:.2f}' for x,y,z in zip(df['Overall(mean)'],df['PlayerSize'],df['Strength'])])\n", "#---#\n", "fig = px.bar(\n", " tidydata,\n", " x='Strength',y='Nationality',\n", " color='8강 가능성',\n", " text='Text',\n", " title='Figure1: Overall + PlayerSize/5 = Strength',\n", " opacity=0.9,\n", " #---#\n", " width=750,\n", " height=1500\n", ")\n", "fig.data[0]['marker']['opacity'] = 0.5\n", "fig" ], "id": "af58e295-3c4e-4dab-9642-b086865c9394" }, { "cell_type": "code", "execution_count": 24, "metadata": { "tags": [] }, "outputs": [], "source": [ "tidydata = df.query('Nationality in [\"Japan\",\"Korea Republic\",\"Australia\",\"Saudi Arabia\",\"Iran\",\"China PR\"]')\\\n", ".query('Position != \"RES\" and Position != \"SUB\"')\\\n", ".groupby(['Nationality','Position']).agg({'Overall':'mean'})\\\n", ".reset_index().rename({'Nationality':'국가','Position':'포지션','Overall':'평균능력치'},axis=1)\n", "#---#\n", "fig = px.density_heatmap(\n", " tidydata,\n", " x='국가',\n", " y='포지션',\n", " z='평균능력치',\n", " width=750,\n", " height=450,\n", " title='Figure2: 상위8개국에 대한 포지션별 히트맵',\n", " category_orders={\n", " '국가':[\"Japan\",\"Korea Republic\",\"Australia\",\"Saudi Arabia\",\"Iran\",\"China PR\"],\n", " '포지션':['FORWARD','MIDFIELDER','DEFENDER','GOALKEEPER']\n", " }\n", ")\n", "fig.layout['coloraxis']['colorbar']['title']['text'] = '평균능력치'\n", "fig.data[0]['hovertemplate'] = '국가=%{x}
포지션=%{y}
평균능력치=%{z}'\n", "fig" ], "id": "3a7493ea-6cd7-4645-8000-06e2e351c105" } ], "nbformat": 4, "nbformat_minor": 5, "metadata": { "kernelspec": { "name": "python3", "display_name": "Python 3 (ipykernel)", "language": "python" }, "language_info": { "name": "python", "codemirror_mode": { "name": "ipython", "version": "3" }, "file_extension": ".py", "mimetype": "text/x-python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.13" } } }