
- Github: PinkWink
5장 우리나라 인구 소멸 위기 지역 분석¶
- 5-1 목표 명확히하기
- 5-2 인구 데이터 확보하고 정리하기
- 5-3 인구 소멸 위기 지역 계산하고 데이터 정리하기
- 5-4 대한민국 지도 그리는 방법에 대한 소개
- 5-5 지도 시각화를 위해 지역별 고유 ID 만들기
- 5-6 Cartogram 으로 우리나라 지도 만들기
- 5-7 인구 현황 및 인구 소멸 지역 확인하기
- 5-8 인구 현황에서 여성 인구 비율 확인하기
- 5-9 Folium에서 인구 소멸 위기 지역 표현하기
대한민국 지도를 그리고 그 위에 인구 소명 위기 지역에 대해 매핑해서 시각화하는 것을 최종 목표로 한다.
5-1 목표 명확히 하기¶
65세 이상 노인 인구와 20~39세 여성 인구를 비교해서 젊은 여성 인구가 노인 인구의 절반에 미달할 경우 인구 소멸 위험 지역으로 분류하는 방법이다. 먼저 각 지역별 20~30대 여성 인구수를 파악해야하며, 또 65세 이상 노인 인구수를 파악해야 한다. 한국 지도에 시각화 하기위해 한국 지도를 그리는 법을 확보한다. Foilim을 이용한 방법과 또 다른 방법 모두 확인해 보겠다.
5-2 인구 데이터 확보하고 정리하기¶
import pandas as pd
import numpy as np
import platform
import matplotlib.pyplot as plt
%matplotlib inline
paht = "c:/Windows/Fonts/malgun.ttf"
import platform
from matplotlib import font_manager, rc
plt.rcParams['axes.unicode_minus'] = False
if platform.system() == 'Darwin':
rc('font', family='AppleGothic')
print('Mac version')
elif platform.system() == 'Windows':
path = "c:/Windows/Fonts/malgun.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
rc('font', family=font_name)
print('Windows version')
elif platform.system() == 'Linux':
path = "/usr/share/fonts/NanumFont/NanumGothicBold.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
plt.rc('font', family=font_name)
print('Linux version')
else:
print('Unknown system... sorry~~~~')
Linux version
#!pip3 install openpyxl
- csv 파일로 변경시,
population = pd.read_csv('./data/05. population_raw_data.csv', encoding='utf-8')
population.fillna(method='pad', inplace = True)
population.rename(columns = {'행정구역(동읍면)별(1)':'광역시도',
'행정구역(동읍면)별(2)':'시도',
'계':'인구수'}, inplace=True)
population = population[(population['시도'] != '소계')]
population
광역시도 | 시도 | 항목 | 2016 | Unnamed: 4 | Unnamed: 5 | Unnamed: 6 | Unnamed: 7 | Unnamed: 8 | Unnamed: 9 | Unnamed: 10 | Unnamed: 11 | Unnamed: 12 | Unnamed: 13 | Unnamed: 14 | Unnamed: 15 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | NaN | NaN | NaN | 계 | 20 - 24세 | 25 - 29세 | 30 - 34세 | 35 - 39세 | 65 - 69세 | 70 - 74세 | 75 - 79세 | 80 - 84세 | 85 - 89세 | 90 - 94세 | 95 - 99세 | 100+ |
7 | 서울특별시 | 종로구 | 총인구수 (명) | 152,737 | 11,379 | 11,891 | 10,684 | 10,379 | 7,411 | 6,636 | 5,263 | 3,104 | 1,480 | 602 | 234 | 220 |
8 | 서울특별시 | 종로구 | 남자인구수 (명) | 75,201 | 5,620 | 6,181 | 5,387 | 5,034 | 3,411 | 3,009 | 2,311 | 1,289 | 506 | 207 | 89 | 73 |
9 | 서울특별시 | 종로구 | 여자인구수 (명) | 77,536 | 5,759 | 5,710 | 5,297 | 5,345 | 4,000 | 3,627 | 2,952 | 1,815 | 974 | 395 | 145 | 147 |
10 | 서울특별시 | 중구 | 총인구수 (명) | 125,249 | 8,216 | 9,529 | 10,332 | 10,107 | 6,399 | 5,313 | 4,127 | 2,502 | 1,260 | 469 | 158 | 160 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
842 | 제주특별자치도 | 제주시 | 남자인구수 (명) | 235,977 | 17,377 | 13,118 | 15,084 | 18,350 | 8,474 | 6,782 | 4,941 | 2,737 | 854 | 226 | 53 | 17 |
843 | 제주특별자치도 | 제주시 | 여자인구수 (명) | 234,688 | 15,261 | 12,245 | 14,687 | 18,062 | 9,265 | 7,877 | 7,178 | 5,649 | 3,122 | 1,387 | 460 | 137 |
844 | 제주특별자치도 | 서귀포시 | 총인구수 (명) | 170,932 | 10,505 | 8,067 | 9,120 | 11,606 | 8,686 | 7,460 | 6,456 | 4,521 | 1,855 | 733 | 242 | 77 |
845 | 제주특별자치도 | 서귀포시 | 남자인구수 (명) | 86,568 | 5,600 | 4,247 | 4,693 | 6,082 | 4,237 | 3,441 | 2,611 | 1,494 | 370 | 103 | 29 | 9 |
846 | 제주특별자치도 | 서귀포시 | 여자인구수 (명) | 84,364 | 4,905 | 3,820 | 4,427 | 5,524 | 4,449 | 4,019 | 3,845 | 3,027 | 1,485 | 630 | 213 | 68 |
793 rows × 16 columns
population = pd.read_excel('./data/05. population_raw_data.xlsx',engine='openpyxl', header=1)
population.fillna(method='pad', inplace=True)
population.rename(columns = {'행정구역(동읍면)별(1)':'광역시도',
'행정구역(동읍면)별(2)':'시도',
'계':'인구수'}, inplace=True)
population = population[(population['시도'] != '소계')]
population
광역시도 | 시도 | 항목 | 인구수 | 20 - 24세 | 25 - 29세 | 30 - 34세 | 35 - 39세 | 65 - 69세 | 70 - 74세 | 75 - 79세 | 80 - 84세 | 85 - 89세 | 90 - 94세 | 95 - 99세 | 100+ | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
6 | 서울특별시 | 종로구 | 총인구수 (명) | 152737.0 | 11379.0 | 11891.0 | 10684 | 10379.0 | 7411.0 | 6636.0 | 5263 | 3104.0 | 1480.0 | 602.0 | 234 | 220.0 |
7 | 서울특별시 | 종로구 | 남자인구수 (명) | 75201.0 | 5620.0 | 6181.0 | 5387 | 5034.0 | 3411.0 | 3009.0 | 2311 | 1289.0 | 506.0 | 207.0 | 89 | 73.0 |
8 | 서울특별시 | 종로구 | 여자인구수 (명) | 77536.0 | 5759.0 | 5710.0 | 5297 | 5345.0 | 4000.0 | 3627.0 | 2952 | 1815.0 | 974.0 | 395.0 | 145 | 147.0 |
9 | 서울특별시 | 중구 | 총인구수 (명) | 125249.0 | 8216.0 | 9529.0 | 10332 | 10107.0 | 6399.0 | 5313.0 | 4127 | 2502.0 | 1260.0 | 469.0 | 158 | 160.0 |
10 | 서울특별시 | 중구 | 남자인구수 (명) | 62204.0 | 4142.0 | 4792.0 | 5192 | 5221.0 | 3113.0 | 2405.0 | 1752 | 929.0 | 414.0 | 132.0 | 56 | 51.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
841 | 제주특별자치도 | 제주시 | 남자인구수 (명) | 235977.0 | 17377.0 | 13118.0 | 15084 | 18350.0 | 8474.0 | 6782.0 | 4941 | 2737.0 | 854.0 | 226.0 | 53 | 17.0 |
842 | 제주특별자치도 | 제주시 | 여자인구수 (명) | 234688.0 | 15261.0 | 12245.0 | 14687 | 18062.0 | 9265.0 | 7877.0 | 7178 | 5649.0 | 3122.0 | 1387.0 | 460 | 137.0 |
843 | 제주특별자치도 | 서귀포시 | 총인구수 (명) | 170932.0 | 10505.0 | 8067.0 | 9120 | 11606.0 | 8686.0 | 7460.0 | 6456 | 4521.0 | 1855.0 | 733.0 | 242 | 77.0 |
844 | 제주특별자치도 | 서귀포시 | 남자인구수 (명) | 86568.0 | 5600.0 | 4247.0 | 4693 | 6082.0 | 4237.0 | 3441.0 | 2611 | 1494.0 | 370.0 | 103.0 | 29 | 9.0 |
845 | 제주특별자치도 | 서귀포시 | 여자인구수 (명) | 84364.0 | 4905.0 | 3820.0 | 4427 | 5524.0 | 4449.0 | 4019.0 | 3845 | 3027.0 | 1485.0 | 630.0 | 213 | 68.0 |
792 rows × 16 columns
복잡한 엑셀 형식을 우리가 필요한 데이터로만 정리 하였다.
population.is_copy = False
population.rename(columns = {'항목':'구분'}, inplace = True)
population.loc[population['구분'] == '총인구수 (명)', '구분'] ='합계'
population.loc[population['구분'] == '남자인구수 (명)', '구분'] = '남자'
population.loc[population['구분'] == '여자인구수 (명)', '구분'] = '여자'
population
광역시도 | 시도 | 구분 | 인구수 | 20 - 24세 | 25 - 29세 | 30 - 34세 | 35 - 39세 | 65 - 69세 | 70 - 74세 | 75 - 79세 | 80 - 84세 | 85 - 89세 | 90 - 94세 | 95 - 99세 | 100+ | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
6 | 서울특별시 | 종로구 | 합계 | 152737.0 | 11379.0 | 11891.0 | 10684 | 10379.0 | 7411.0 | 6636.0 | 5263 | 3104.0 | 1480.0 | 602.0 | 234 | 220.0 |
7 | 서울특별시 | 종로구 | 남자 | 75201.0 | 5620.0 | 6181.0 | 5387 | 5034.0 | 3411.0 | 3009.0 | 2311 | 1289.0 | 506.0 | 207.0 | 89 | 73.0 |
8 | 서울특별시 | 종로구 | 여자 | 77536.0 | 5759.0 | 5710.0 | 5297 | 5345.0 | 4000.0 | 3627.0 | 2952 | 1815.0 | 974.0 | 395.0 | 145 | 147.0 |
9 | 서울특별시 | 중구 | 합계 | 125249.0 | 8216.0 | 9529.0 | 10332 | 10107.0 | 6399.0 | 5313.0 | 4127 | 2502.0 | 1260.0 | 469.0 | 158 | 160.0 |
10 | 서울특별시 | 중구 | 남자 | 62204.0 | 4142.0 | 4792.0 | 5192 | 5221.0 | 3113.0 | 2405.0 | 1752 | 929.0 | 414.0 | 132.0 | 56 | 51.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
841 | 제주특별자치도 | 제주시 | 남자 | 235977.0 | 17377.0 | 13118.0 | 15084 | 18350.0 | 8474.0 | 6782.0 | 4941 | 2737.0 | 854.0 | 226.0 | 53 | 17.0 |
842 | 제주특별자치도 | 제주시 | 여자 | 234688.0 | 15261.0 | 12245.0 | 14687 | 18062.0 | 9265.0 | 7877.0 | 7178 | 5649.0 | 3122.0 | 1387.0 | 460 | 137.0 |
843 | 제주특별자치도 | 서귀포시 | 합계 | 170932.0 | 10505.0 | 8067.0 | 9120 | 11606.0 | 8686.0 | 7460.0 | 6456 | 4521.0 | 1855.0 | 733.0 | 242 | 77.0 |
844 | 제주특별자치도 | 서귀포시 | 남자 | 86568.0 | 5600.0 | 4247.0 | 4693 | 6082.0 | 4237.0 | 3441.0 | 2611 | 1494.0 | 370.0 | 103.0 | 29 | 9.0 |
845 | 제주특별자치도 | 서귀포시 | 여자 | 84364.0 | 4905.0 | 3820.0 | 4427 | 5524.0 | 4449.0 | 4019.0 | 3845 | 3027.0 | 1485.0 | 630.0 | 213 | 68.0 |
792 rows × 16 columns
그리고 원래 '항목'으로 되어 있던 컬럼을 '구분'으로 이름을 바꾸고, '총인구수(명)'와 같이 긴 이름을 간단하게 '합계','남자','여자'로 바꾼다.
컬럼 이름은 잘 정리가 되었다.
5-3 인구 소멸 위기 지역 계산하고 데이터 정리하기¶
먼저 20~30대의 인구를 알아보자. 그리고 65세 이상 인구수도 알아보자
population['20-39세'] = population['20 - 24세'] + population['25 - 29세'] + \
population['30 - 34세'] + population['35 - 39세']
population['65세이상'] = population['65 - 69세'] + population['70 - 74세'] + \
population['75 - 79세'] + population['80 - 84세'] + \
population['85 - 89세'] + population['90 - 94세'] + \
population['95 - 99세'] + population['100+']
population.head(10)
광역시도 | 시도 | 구분 | 인구수 | 20 - 24세 | 25 - 29세 | 30 - 34세 | 35 - 39세 | 65 - 69세 | 70 - 74세 | 75 - 79세 | 80 - 84세 | 85 - 89세 | 90 - 94세 | 95 - 99세 | 100+ | 20-39세 | 65세이상 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
6 | 서울특별시 | 종로구 | 합계 | 152737.0 | 11379.0 | 11891.0 | 10684 | 10379.0 | 7411.0 | 6636.0 | 5263 | 3104.0 | 1480.0 | 602.0 | 234 | 220.0 | 44333.0 | 24950.0 |
7 | 서울특별시 | 종로구 | 남자 | 75201.0 | 5620.0 | 6181.0 | 5387 | 5034.0 | 3411.0 | 3009.0 | 2311 | 1289.0 | 506.0 | 207.0 | 89 | 73.0 | 22222.0 | 10895.0 |
8 | 서울특별시 | 종로구 | 여자 | 77536.0 | 5759.0 | 5710.0 | 5297 | 5345.0 | 4000.0 | 3627.0 | 2952 | 1815.0 | 974.0 | 395.0 | 145 | 147.0 | 22111.0 | 14055.0 |
9 | 서울특별시 | 중구 | 합계 | 125249.0 | 8216.0 | 9529.0 | 10332 | 10107.0 | 6399.0 | 5313.0 | 4127 | 2502.0 | 1260.0 | 469.0 | 158 | 160.0 | 38184.0 | 20388.0 |
10 | 서울특별시 | 중구 | 남자 | 62204.0 | 4142.0 | 4792.0 | 5192 | 5221.0 | 3113.0 | 2405.0 | 1752 | 929.0 | 414.0 | 132.0 | 56 | 51.0 | 19347.0 | 8852.0 |
11 | 서울특별시 | 중구 | 여자 | 63045.0 | 4074.0 | 4737.0 | 5140 | 4886.0 | 3286.0 | 2908.0 | 2375 | 1573.0 | 846.0 | 337.0 | 102 | 109.0 | 18837.0 | 11536.0 |
12 | 서울특별시 | 용산구 | 합계 | 230241.0 | 14317.0 | 16972.0 | 19032 | 19127.0 | 10675.0 | 9093.0 | 7477 | 4553.0 | 2254.0 | 916.0 | 264 | 315.0 | 69448.0 | 35547.0 |
13 | 서울특별시 | 용산구 | 남자 | 111601.0 | 6937.0 | 8373.0 | 9455 | 9434.0 | 4834.0 | 3975.0 | 3094 | 1739.0 | 750.0 | 284.0 | 102 | 88.0 | 34199.0 | 14866.0 |
14 | 서울특별시 | 용산구 | 여자 | 118640.0 | 7380.0 | 8599.0 | 9577 | 9693.0 | 5841.0 | 5118.0 | 4383 | 2814.0 | 1504.0 | 632.0 | 162 | 227.0 | 35249.0 | 20681.0 |
15 | 서울특별시 | 성동구 | 합계 | 299259.0 | 20813.0 | 23383.0 | 25507 | 25979.0 | 12938.0 | 10734.0 | 7989 | 4450.0 | 1944.0 | 678.0 | 209 | 198.0 | 95682.0 | 39140.0 |
그 결과를 구하고, 이 많은 컬럼들 중에서 일부만 선택해야 한다. 그리고 결정적으로 '합계','남자','여자'로 되어 있는 구분도 정리해야한다. 이럴때 사용하는 마법 키워드 pivot_table를 사용하자.광역시도와 시도를 index로 잡고, 인구수 20-39세,65세 이상만 데이터로 가져온다.
pop = pd.pivot_table(population,
index = ['광역시도', '시도'],
columns = ['구분'],
values = ['인구수', '20-39세', '65세이상'])
pop
20-39세 | 65세이상 | 인구수 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
구분 | 남자 | 여자 | 합계 | 남자 | 여자 | 합계 | 남자 | 여자 | 합계 | |
광역시도 | 시도 | |||||||||
강원도 | 강릉시 | 26286.0 | 23098.0 | 49384.0 | 15767.0 | 21912.0 | 37679.0 | 106231.0 | 107615.0 | 213846.0 |
고성군 | 4494.0 | 2529.0 | 7023.0 | 2900.0 | 4251.0 | 7151.0 | 15899.0 | 14215.0 | 30114.0 | |
동해시 | 11511.0 | 9753.0 | 21264.0 | 6392.0 | 8732.0 | 15124.0 | 47166.0 | 46131.0 | 93297.0 | |
삼척시 | 8708.0 | 7115.0 | 15823.0 | 5892.0 | 8718.0 | 14610.0 | 35253.0 | 34346.0 | 69599.0 | |
속초시 | 9956.0 | 8752.0 | 18708.0 | 5139.0 | 7613.0 | 12752.0 | 40288.0 | 41505.0 | 81793.0 | |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
충청북도 | 진천군 | 9391.0 | 7622.0 | 17013.0 | 4731.0 | 6575.0 | 11306.0 | 36387.0 | 33563.0 | 69950.0 |
청원구 | 32216.0 | 27805.0 | 60021.0 | 8417.0 | 11914.0 | 20331.0 | 97006.0 | 93807.0 | 190813.0 | |
청주시 | 128318.0 | 115719.0 | 244037.0 | 37882.0 | 53671.0 | 91553.0 | 419323.0 | 415874.0 | 835197.0 | |
충주시 | 26600.0 | 22757.0 | 49357.0 | 14407.0 | 20383.0 | 34790.0 | 104877.0 | 103473.0 | 208350.0 | |
흥덕구 | 40933.0 | 37675.0 | 78608.0 | 9788.0 | 13671.0 | 23459.0 | 127647.0 | 125916.0 | 253563.0 |
264 rows × 9 columns
그 결과 중 일부입니다. 이제 대망의 인구 소멸 비율을 계산할 수 있다. 20-39세 여성 인구와 65세 이상 인구를 지역별로 알았기 때문이다.
pop['소멸비율'] = pop['20-39세','여자'] / (pop['65세이상','합계']/2)
pop.head()
20-39세 | 65세이상 | 인구수 | 소멸비율 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
구분 | 남자 | 여자 | 합계 | 남자 | 여자 | 합계 | 남자 | 여자 | 합계 | ||
광역시도 | 시도 | ||||||||||
강원도 | 강릉시 | 26286.0 | 23098.0 | 49384.0 | 15767.0 | 21912.0 | 37679.0 | 106231.0 | 107615.0 | 213846.0 | 1.226041 |
고성군 | 4494.0 | 2529.0 | 7023.0 | 2900.0 | 4251.0 | 7151.0 | 15899.0 | 14215.0 | 30114.0 | 0.707314 | |
동해시 | 11511.0 | 9753.0 | 21264.0 | 6392.0 | 8732.0 | 15124.0 | 47166.0 | 46131.0 | 93297.0 | 1.289738 | |
삼척시 | 8708.0 | 7115.0 | 15823.0 | 5892.0 | 8718.0 | 14610.0 | 35253.0 | 34346.0 | 69599.0 | 0.973990 | |
속초시 | 9956.0 | 8752.0 | 18708.0 | 5139.0 | 7613.0 | 12752.0 | 40288.0 | 41505.0 | 81793.0 | 1.372647 |
결과 확인.
pop['소멸위기지역'] = pop['소멸비율'] < 1.0
pop.head()
20-39세 | 65세이상 | 인구수 | 소멸비율 | 소멸위기지역 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
구분 | 남자 | 여자 | 합계 | 남자 | 여자 | 합계 | 남자 | 여자 | 합계 | |||
광역시도 | 시도 | |||||||||||
강원도 | 강릉시 | 26286.0 | 23098.0 | 49384.0 | 15767.0 | 21912.0 | 37679.0 | 106231.0 | 107615.0 | 213846.0 | 1.226041 | False |
고성군 | 4494.0 | 2529.0 | 7023.0 | 2900.0 | 4251.0 | 7151.0 | 15899.0 | 14215.0 | 30114.0 | 0.707314 | True | |
동해시 | 11511.0 | 9753.0 | 21264.0 | 6392.0 | 8732.0 | 15124.0 | 47166.0 | 46131.0 | 93297.0 | 1.289738 | False | |
삼척시 | 8708.0 | 7115.0 | 15823.0 | 5892.0 | 8718.0 | 14610.0 | 35253.0 | 34346.0 | 69599.0 | 0.973990 | True | |
속초시 | 9956.0 | 8752.0 | 18708.0 | 5139.0 | 7613.0 | 12752.0 | 40288.0 | 41505.0 | 81793.0 | 1.372647 | False |
소멸비율이 1 이하면 소멸위기지역이라고 기록하자
pop[pop['소멸위기지역']==True].index.get_level_values(1)
Index(['고성군', '삼척시', '양양군', '영월군', '정선군', '평창군', '홍천군', '횡성군', '가평군', '양평군', '연천군', '거창군', '고성군', '남해군', '밀양시', '산청군', '의령군', '창녕군', '하동군', '함안군', '함양군', '합천군', '고령군', '군위군', '문경시', '봉화군', '상주시', '성주군', '영덕군', '영양군', '영주시', '영천시', '예천군', '울릉군', '울진군', '의성군', '청도군', '청송군', '동구', '영도구', '강화군', '옹진군', '강진군', '고흥군', '곡성군', '구례군', '담양군', '보성군', '신안군', '영광군', '영암군', '완도군', '장성군', '장흥군', '진도군', '함평군', '해남군', '화순군', '고창군', '김제시', '남원시', '무주군', '부안군', '순창군', '임실군', '장수군', '정읍시', '진안군', '공주시', '금산군', '논산시', '보령시', '부여군', '서천군', '예산군', '청양군', '태안군', '홍성군', '괴산군', '단양군', '보은군', '영동군', '옥천군'], dtype='object', name='시도')
총 83개 지자체입니다. 이렇게 이름만 나열하는 것은 뭔가 부족하다. 지도로 시각화 하자.
pop.reset_index(inplace=True)
pop.head()
광역시도 | 시도 | 20-39세 | 65세이상 | 인구수 | 소멸비율 | 소멸위기지역 | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
구분 | 남자 | 여자 | 합계 | 남자 | 여자 | 합계 | 남자 | 여자 | 합계 | ||||
0 | 강원도 | 강릉시 | 26286.0 | 23098.0 | 49384.0 | 15767.0 | 21912.0 | 37679.0 | 106231.0 | 107615.0 | 213846.0 | 1.226041 | False |
1 | 강원도 | 고성군 | 4494.0 | 2529.0 | 7023.0 | 2900.0 | 4251.0 | 7151.0 | 15899.0 | 14215.0 | 30114.0 | 0.707314 | True |
2 | 강원도 | 동해시 | 11511.0 | 9753.0 | 21264.0 | 6392.0 | 8732.0 | 15124.0 | 47166.0 | 46131.0 | 93297.0 | 1.289738 | False |
3 | 강원도 | 삼척시 | 8708.0 | 7115.0 | 15823.0 | 5892.0 | 8718.0 | 14610.0 | 35253.0 | 34346.0 | 69599.0 | 0.973990 | True |
4 | 강원도 | 속초시 | 9956.0 | 8752.0 | 18708.0 | 5139.0 | 7613.0 | 12752.0 | 40288.0 | 41505.0 | 81793.0 | 1.372647 | False |
먼저 pivot_table에 의해 다단으로 구성된 index를 다시 초기화 하자
tmp_columns = [pop.columns.get_level_values(0)[n] + \
pop.columns.get_level_values(1)[n]
for n in range(0,len(pop.columns.get_level_values(0)))]
pop.columns = tmp_columns
pop.head()
광역시도 | 시도 | 20-39세남자 | 20-39세여자 | 20-39세합계 | 65세이상남자 | 65세이상여자 | 65세이상합계 | 인구수남자 | 인구수여자 | 인구수합계 | 소멸비율 | 소멸위기지역 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 강원도 | 강릉시 | 26286.0 | 23098.0 | 49384.0 | 15767.0 | 21912.0 | 37679.0 | 106231.0 | 107615.0 | 213846.0 | 1.226041 | False |
1 | 강원도 | 고성군 | 4494.0 | 2529.0 | 7023.0 | 2900.0 | 4251.0 | 7151.0 | 15899.0 | 14215.0 | 30114.0 | 0.707314 | True |
2 | 강원도 | 동해시 | 11511.0 | 9753.0 | 21264.0 | 6392.0 | 8732.0 | 15124.0 | 47166.0 | 46131.0 | 93297.0 | 1.289738 | False |
3 | 강원도 | 삼척시 | 8708.0 | 7115.0 | 15823.0 | 5892.0 | 8718.0 | 14610.0 | 35253.0 | 34346.0 | 69599.0 | 0.973990 | True |
4 | 강원도 | 속초시 | 9956.0 | 8752.0 | 18708.0 | 5139.0 | 7613.0 | 12752.0 | 40288.0 | 41505.0 | 81793.0 | 1.372647 | False |
역시 다단으로 표시된 컬럼(columns)을 하나로 합칩니다. 이제 세로축에 지역, 가로축에 연령대별 성별 인구수가 정리 되었다.
pop.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 264 entries, 0 to 263 Data columns (total 13 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 광역시도 264 non-null object 1 시도 264 non-null object 2 20-39세남자 264 non-null float64 3 20-39세여자 264 non-null float64 4 20-39세합계 264 non-null float64 5 65세이상남자 264 non-null float64 6 65세이상여자 264 non-null float64 7 65세이상합계 264 non-null float64 8 인구수남자 264 non-null float64 9 인구수여자 264 non-null float64 10 인구수합계 264 non-null float64 11 소멸비율 264 non-null float64 12 소멸위기지역 264 non-null bool dtypes: bool(1), float64(10), object(2) memory usage: 25.1+ KB
요약 정리에서도 큰 문제 없이 진행된 것으로 보인다. 총 264항목에 인구수는 숫자형으로 잡혔고, 소멸위기 지역은 bool형으로 잡혀 있다.
5-4 대한민국 지도 그리는 방법에 대한 소개¶
이미 Folium은 우리가 사용할 줄 안다. 그러나 Folium에도 접근하기 어려운 부분이 있다. 바로 경계선을 그려주는 json 파일을 구하는 것이다. 일다 Lucy Park이라는 이름으로 활동하는 깃헙 사이트에서 json파일을 얻어오자.
5-5 지도 시각화를 위해 지역별 고유 ID 만들기¶
일단 우리에게 필요한 것은 지역에 따른 고유 ID가 필요하다는 거입니다. 우선 pop['시도']에 대해 unique를 조사하도록 하겠습니다.
pop['시도'].unique()
array(['강릉시', '고성군', '동해시', '삼척시', '속초시', '양구군', '양양군', '영월군', '원주시', '인제군', '정선군', '철원군', '춘천시', '태백시', '평창군', '홍천군', '화천군', '횡성군', '가평군', '고양시', '과천시', '광명시', '광주시', '구리시', '군포시', '권선구', '기흥구', '김포시', '남양주시', '단원구', '덕양구', '동두천시', '동안구', '만안구', '부천시', '분당구', '상록구', '성남시', '소사구', '수원시', '수정구', '수지구', '시흥시', '안산시', '안성시', '안양시', '양주시', '양평군', '여주시', '연천군', '영통구', '오산시', '오정구', '용인시', '원미구', '의왕시', '의정부시', '이천시', '일산동구', '일산서구', '장안구', '중원구', '처인구', '파주시', '팔달구', '평택시', '포천시', '하남시', '화성시', '거제시', '거창군', '김해시', '남해군', '마산합포구', '마산회원구', '밀양시', '사천시', '산청군', '성산구', '양산시', '의령군', '의창구', '진주시', '진해구', '창녕군', '창원시', '통영시', '하동군', '함안군', '함양군', '합천군', '경산시', '경주시', '고령군', '구미시', '군위군', '김천시', '남구', '문경시', '봉화군', '북구', '상주시', '성주군', '안동시', '영덕군', '영양군', '영주시', '영천시', '예천군', '울릉군', '울진군', '의성군', '청도군', '청송군', '칠곡군', '포항시', '광산구', '동구', '서구', '달서구', '달성군', '수성구', '중구', '대덕구', '유성구', '강서구', '금정구', '기장군', '동래구', '부산진구', '사상구', '사하구', '수영구', '연제구', '영도구', '해운대구', '강남구', '강동구', '강북구', '관악구', '광진구', '구로구', '금천구', '노원구', '도봉구', '동대문구', '동작구', '마포구', '서대문구', '서초구', '성동구', '성북구', '송파구', '양천구', '영등포구', '용산구', '은평구', '종로구', '중랑구', '세종특별자치시', '울주군', '강화군', '계양구', '남동구', '부평구', '연수구', '옹진군', '강진군', '고흥군', '곡성군', '광양시', '구례군', '나주시', '담양군', '목포시', '무안군', '보성군', '순천시', '신안군', '여수시', '영광군', '영암군', '완도군', '장성군', '장흥군', '진도군', '함평군', '해남군', '화순군', '고창군', '군산시', '김제시', '남원시', '덕진구', '무주군', '부안군', '순창군', '완산구', '완주군', '익산시', '임실군', '장수군', '전주시', '정읍시', '진안군', '서귀포시', '제주시', '계룡시', '공주시', '금산군', '논산시', '당진시', '동남구', '보령시', '부여군', '서북구', '서산시', '서천군', '아산시', '예산군', '천안시', '청양군', '태안군', '홍성군', '괴산군', '단양군', '보은군', '상당구', '서원구', '영동군', '옥천군', '음성군', '제천시', '증평군', '진천군', '청원구', '청주시', '충주시', '흥덕구'], dtype=object)
pop['광역시도']와 pop['시도']를 합친다고 하면 광역시도의 자치구는 서울 중구와 같이 만들 수 있지만, 행정구의 경우는 안양시 동안구가 되지 않고 '경기도 동안구'가 됩니다. 그래서 이부분에 대한 고민이 필요하다.
si_name = [None] * len(pop)
tmp_gu_dict = {'수원':['장안구', '권선구', '팔달구', '영통구'],
'성남':['수정구', '중원구', '분당구'],
'안양':['만안구', '동안구'],
'고양':['덕양구', '일산동구', '일산서구'],
'용인':['처인구', '기흥구', '수지구'],
'청주':['상당구', '서원구', '흥덕구', '청원구'],
'천안':['동남구', '서북구'],
'전주':['완산구', '덕진구'],
'포항':['남구', '북구'],
'창원':['의창구','성산구','진해구','마산합포구','마산회원구'],
'부천':['오정구','원미구','소사구']}
먼저 광역시가 아니면서 구를 가지고 있는 시와 그 행정구를 파이썬의 dict형으로 선언하자. 그리고 광역시도에 있는 이름의 끝 세 글자가 '광역시','특별시','자치시'로 끝나지 않으면 일반 시 혹은 군으로 보자.그 속에서 강원도와 경상남도에는 동일한 이름을 가진 '고성군'이 있어서 그것을 처리하자. 그리고 방금 이야기한 일반 시인데 구를 가지는 경우에 대해 대응 하자. 그리고 세종특별자치시를 그냥 '세종'으로 처리하고, 나머지는 광역시도에서 앞 두글자(서울 특별시)와 시도에서 두 글자인 경우 모두, 아니면 앞 두 글자만 선택하면서 고유 ID를 만들자.
for n in pop.index:
if pop['광역시도'][n][-3:] not in ['광역시', '특별시', '자치시']:
if pop['시도'][n][:-1]=='고성' and pop['광역시도'][n]=='강원도':
si_name[n] = '고성(강원)'
elif pop['시도'][n][:-1]=='고성' and pop['광역시도'][n]=='경상남도':
si_name[n] = '고성(경남)'
else:
si_name[n] = pop['시도'][n][:-1]
for keys, values in tmp_gu_dict.items():
if pop['시도'][n] in values:
if len(pop['시도'][n])==2:
si_name[n] = keys + ' ' + pop['시도'][n]
elif pop['시도'][n] in ['마산합포구','마산회원구']:
si_name[n] = keys + ' ' + pop['시도'][n][2:-1]
else:
si_name[n] = keys + ' ' + pop['시도'][n][:-1]
elif pop['광역시도'][n] == '세종특별자치시':
si_name[n] = '세종'
else:
if len(pop['시도'][n])==2:
si_name[n] = pop['광역시도'][n][:2] + ' ' + pop['시도'][n]
else:
si_name[n] = pop['광역시도'][n][:2] + ' ' + pop['시도'][n][:-1]
si_name
['강릉', '고성(강원)', '동해', '삼척', '속초', '양구', '양양', '영월', '원주', '인제', '정선', '철원', '춘천', '태백', '평창', '홍천', '화천', '횡성', '가평', '고양', '과천', '광명', '광주', '구리', '군포', '수원 권선', '용인 기흥', '김포', '남양주', '단원', '고양 덕양', '동두천', '안양 동안', '안양 만안', '부천', '성남 분당', '상록', '성남', '부천 소사', '수원', '성남 수정', '용인 수지', '시흥', '안산', '안성', '안양', '양주', '양평', '여주', '연천', '수원 영통', '오산', '부천 오정', '용인', '부천 원미', '의왕', '의정부', '이천', '고양 일산동', '고양 일산서', '수원 장안', '성남 중원', '용인 처인', '파주', '수원 팔달', '평택', '포천', '하남', '화성', '거제', '거창', '고성(경남)', '김해', '남해', '창원 합포', '창원 회원', '밀양', '사천', '산청', '창원 성산', '양산', '의령', '창원 의창', '진주', '창원 진해', '창녕', '창원', '통영', '하동', '함안', '함양', '합천', '경산', '경주', '고령', '구미', '군위', '김천', '포항 남구', '문경', '봉화', '포항 북구', '상주', '성주', '안동', '영덕', '영양', '영주', '영천', '예천', '울릉', '울진', '의성', '청도', '청송', '칠곡', '포항', '광주 광산', '광주 남구', '광주 동구', '광주 북구', '광주 서구', '대구 남구', '대구 달서', '대구 달성', '대구 동구', '대구 북구', '대구 서구', '대구 수성', '대구 중구', '대전 대덕', '대전 동구', '대전 서구', '대전 유성', '대전 중구', '부산 강서', '부산 금정', '부산 기장', '부산 남구', '부산 동구', '부산 동래', '부산 부산진', '부산 북구', '부산 사상', '부산 사하', '부산 서구', '부산 수영', '부산 연제', '부산 영도', '부산 중구', '부산 해운대', '서울 강남', '서울 강동', '서울 강북', '서울 강서', '서울 관악', '서울 광진', '서울 구로', '서울 금천', '서울 노원', '서울 도봉', '서울 동대문', '서울 동작', '서울 마포', '서울 서대문', '서울 서초', '서울 성동', '서울 성북', '서울 송파', '서울 양천', '서울 영등포', '서울 용산', '서울 은평', '서울 종로', '서울 중구', '서울 중랑', '세종', '울산 남구', '울산 동구', '울산 북구', '울산 울주', '울산 중구', '인천 강화', '인천 계양', '인천 남구', '인천 남동', '인천 동구', '인천 부평', '인천 서구', '인천 연수', '인천 옹진', '인천 중구', '강진', '고흥', '곡성', '광양', '구례', '나주', '담양', '목포', '무안', '보성', '순천', '신안', '여수', '영광', '영암', '완도', '장성', '장흥', '진도', '함평', '해남', '화순', '고창', '군산', '김제', '남원', '전주 덕진', '무주', '부안', '순창', '전주 완산', '완주', '익산', '임실', '장수', '전주', '정읍', '진안', '서귀포', '제주', '계룡', '공주', '금산', '논산', '당진', '천안 동남', '보령', '부여', '천안 서북', '서산', '서천', '아산', '예산', '천안', '청양', '태안', '홍성', '괴산', '단양', '보은', '청주 상당', '청주 서원', '영동', '옥천', '음성', '제천', '증평', '진천', '청주 청원', '청주', '충주', '청주 흥덕']
결과는 이렇게 나타난다.
pop['ID'] = si_name
결과를 pop에 포함시키고 잊 큰 의미가 없는 몇몇 컬럼을 제거한다.
del pop['20-39세남자']
del pop['65세이상남자']
del pop['65세이상여자']
pop.head()
광역시도 | 시도 | 20-39세여자 | 20-39세합계 | 65세이상합계 | 인구수남자 | 인구수여자 | 인구수합계 | 소멸비율 | 소멸위기지역 | ID | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 강원도 | 강릉시 | 23098.0 | 49384.0 | 37679.0 | 106231.0 | 107615.0 | 213846.0 | 1.226041 | False | 강릉 |
1 | 강원도 | 고성군 | 2529.0 | 7023.0 | 7151.0 | 15899.0 | 14215.0 | 30114.0 | 0.707314 | True | 고성(강원) |
2 | 강원도 | 동해시 | 9753.0 | 21264.0 | 15124.0 | 47166.0 | 46131.0 | 93297.0 | 1.289738 | False | 동해 |
3 | 강원도 | 삼척시 | 7115.0 | 15823.0 | 14610.0 | 35253.0 | 34346.0 | 69599.0 | 0.973990 | True | 삼척 |
4 | 강원도 | 속초시 | 8752.0 | 18708.0 | 12752.0 | 40288.0 | 41505.0 | 81793.0 | 1.372647 | False | 속초 |
draw_korea_raw = pd.read_excel('./data/05. draw_korea_raw.xlsx',engine='openpyxl')
draw_korea_raw
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 철원 | 화천 | 양구 | 고성(강원) | NaN | NaN | NaN |
1 | NaN | NaN | NaN | 양주 | 동두천 | 연천 | 포천 | 의정부 | 인제 | 춘천 | 속초 | NaN | NaN | NaN |
2 | NaN | NaN | NaN | 고양 덕양 | 고양 일산동 | 서울 도봉 | 서울 노원 | 남양주 | 홍천 | 횡성 | 양양 | NaN | NaN | NaN |
3 | NaN | NaN | 파주 | 고양 일산서 | 김포 | 서울 강북 | 서울 성북 | 가평 | 구리 | 하남 | 정선 | 강릉 | NaN | NaN |
4 | NaN | NaN | 부천 소사 | 안양 만안 | 광명 | 서울 서대문 | 서울 종로 | 서울 동대문 | 서울 중랑 | 양평 | 태백 | 동해 | NaN | NaN |
5 | NaN | 인천 강화 | 부천 원미 | 안양 동안 | 서울 은평 | 서울 마포 | 서울 중구 | 서울 성동 | 서울 강동 | 여주 | 원주 | 삼척 | NaN | NaN |
6 | NaN | 인천 서구 | 부천 오정 | 시흥 | 서울 강서 | 서울 동작 | 서울 용산 | 서울 광진 | 서울 송파 | 이천 | 평창 | 울진 | NaN | NaN |
7 | NaN | 인천 동구 | 인천 계양 | 안산 상록 | 서울 양천 | 서울 관악 | 서울 서초 | 성남 중원 | 과천 | 광주 | 영월 | 영덕 | NaN | NaN |
8 | NaN | NaN | 인천 부평 | 안산 단원 | 서울 영등포 | 서울 금천 | 서울 강남 | 성남 분당 | 성남 수정 | 용인 수지 | 문경 | 봉화 | NaN | 울릉 |
9 | NaN | 인천 중구 | 인천 남구 | 화성 | 서울 구로 | 군포 | 의왕 | 수원 영통 | 용인 기흥 | 용인 처인 | 안동 | 영양 | NaN | NaN |
10 | 인천 옹진 | 인천 연수 | 인천 남동 | 오산 | 안성 | 수원 권선 | 수원 장안 | 제천 | 예천 | 영주 | 구미 | 청송 | 포항 북구 | NaN |
11 | 태안 | 아산 | 천안 동남 | 천안 서북 | 평택 | 음성 | 수원 팔달 | 단양 | 상주 | 김천 | 군위 | 의성 | 포항 남구 | NaN |
12 | NaN | 당진 | 홍성 | 예산 | 공주 | 진천 | 충주 | 청주 흥덕 | 괴산 | 칠곡 | 영천 | 경산 | 경주 | NaN |
13 | NaN | 서산 | 보령 | 청양 | 세종 | 대전 대덕 | 증평 | 청주 청원 | 보은 | 고령 | 청도 | 성주 | 울산 북구 | NaN |
14 | NaN | NaN | 부여 | 논산 | 계룡 | 대전 동구 | 청주 상당 | 청주 서원 | 대구 북구 | 대구 중구 | 대구 수성 | 울산 울주 | 울산 동구 | NaN |
15 | NaN | NaN | 서천 | 금산 | 대전 유성 | 대전 중구 | 옥천 | 영동 | 대구 서구 | 대구 남구 | 대구 동구 | 울산 중구 | 울산 남구 | NaN |
16 | NaN | NaN | 군산 | 익산 | 대전 서구 | 무주 | 거창 | 합천 | 대구 달서 | 대구 달성 | 부산 금정 | 부산 동래 | 부산 기장 | NaN |
17 | NaN | NaN | 부안 | 김제 | 완주 | 장수 | 함양 | 창녕 | 밀양 | 부산 북구 | 부산 부산진 | 부산 연제 | 부산 해운대 | NaN |
18 | NaN | 고창 | 정읍 | 전주 덕진 | 진안 | 남원 | 진주 | 의령 | 부산 강서 | 부산 사상 | 부산 동구 | 부산 중구 | NaN | NaN |
19 | NaN | 영광 | 장성 | 전주 완산 | 임실 | 산청 | 함안 | 양산 | 창원 합포 | 부산 서구 | 부산 사하 | 부산 남구 | NaN | NaN |
20 | NaN | 함평 | 담양 | 순창 | 구례 | 하동 | 창원 의창 | 창원 성산 | 창원 진해 | 김해 | 부산 영도 | 부산 수영 | NaN | NaN |
21 | 신안 | 무안 | 광주 광산 | 곡성 | 화순 | 광양 | 사천 | 창원 회원 | 통영 | NaN | NaN | NaN | NaN | NaN |
22 | 목포 | 나주 | 광주 서구 | 광주 북구 | 순천 | 고흥 | 남해 | 고성(경남) | 거제 | NaN | NaN | NaN | NaN | NaN |
23 | 해남 | 영암 | 광주 남구 | 광주 동구 | 여수 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
24 | 진도 | 강진 | 장흥 | 보성 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
25 | NaN | NaN | 완도 | NaN | NaN | 제주 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
26 | NaN | NaN | NaN | NaN | NaN | 서귀포 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
27 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
우리에게 필요한 것은 각 지역별 x,y 좌표입니다.
draw_korea_raw_stacked = pd.DataFrame(draw_korea_raw.stack())
draw_korea_raw_stacked.reset_index(inplace=True)
draw_korea_raw_stacked.rename(columns={'level_0':'y', 'level_1':'x', 0:'ID'},
inplace=True)
draw_korea_raw_stacked
y | x | ID | |
---|---|---|---|
0 | 0 | 7 | 철원 |
1 | 0 | 8 | 화천 |
2 | 0 | 9 | 양구 |
3 | 0 | 10 | 고성(강원) |
4 | 1 | 3 | 양주 |
... | ... | ... | ... |
247 | 24 | 2 | 장흥 |
248 | 24 | 3 | 보성 |
249 | 25 | 2 | 완도 |
250 | 25 | 5 | 제주 |
251 | 26 | 5 | 서귀포 |
252 rows × 3 columns
그래서 stack()으로 풀고 인덱스를 재설정(reset_index)했다. 그리고 다시 컬럼 이름을 바꾸었다.
이제 각 지역에 대한 좌표를 얻었으며 철원은 (7,0) 이다.
draw_korea = draw_korea_raw_stacked
변수 이름을 변경
BORDER_LINES = [
[(5, 1), (5,2), (7,2), (7,3), (11,3), (11,0)], # 인천
[(5,4), (5,5), (2,5), (2,7), (4,7), (4,9), (7,9),
(7,7), (9,7), (9,5), (10,5), (10,4), (5,4)], # 서울
[(1,7), (1,8), (3,8), (3,10), (10,10), (10,7),
(12,7), (12,6), (11,6), (11,5), (12, 5), (12,4),
(11,4), (11,3)], # 경기도
[(8,10), (8,11), (6,11), (6,12)], # 강원도
[(12,5), (13,5), (13,4), (14,4), (14,5), (15,5),
(15,4), (16,4), (16,2)], # 충청북도
[(16,4), (17,4), (17,5), (16,5), (16,6), (19,6),
(19,5), (20,5), (20,4), (21,4), (21,3), (19,3), (19,1)], # 전라북도
[(13,5), (13,6), (16,6)], # 대전시
[(13,5), (14,5)], #세종시
[(21,2), (21,3), (22,3), (22,4), (24,4), (24,2), (21,2)], #광주
[(20,5), (21,5), (21,6), (23,6)], #전라남도
[(10,8), (12,8), (12,9), (14,9), (14,8), (16,8), (16,6)], #충청북도
[(14,9), (14,11), (14,12), (13,12), (13,13)], #경상북도
[(15,8), (17,8), (17,10), (16,10), (16,11), (14,11)], #대구
[(17,9), (18,9), (18,8), (19,8), (19,9), (20,9), (20,10), (21,10)], #부산
[(16,11), (16,13)], #울산
# [(9,14), (9,15)],
[(27,5), (27,6), (25,6)],
]
그리고 광역시도를 구분하는 경계선도 직접 입력하였다.
plt.figure(figsize=(8, 11))
# 지역 이름 표시
for idx, row in draw_korea.iterrows():
# 광역시는 구 이름이 겹치는 경우가 많아서 시단위 이름도 같이 표시한다.
# (중구, 서구)
if len(row['ID'].split())==2:
dispname = '{}\n{}'.format(row['ID'].split()[0], row['ID'].split()[1])
elif row['ID'][:2]=='고성':
dispname = '고성'
else:
dispname = row['ID']
# 서대문구, 서귀포시 같이 이름이 3자 이상인 경우에 작은 글자로 표시한다.
if len(dispname.splitlines()[-1]) >= 3:
fontsize, linespacing = 9.5, 1.5
else:
fontsize, linespacing = 11, 1.2
plt.annotate(dispname, (row['x']+0.5, row['y']+0.5), weight='bold',
fontsize=fontsize, ha='center', va='center',
linespacing=linespacing)
# 시도 경계 그린다.
for path in BORDER_LINES:
ys, xs = zip(*path)
plt.plot(xs, ys, c='black', lw=1.5)
plt.gca().invert_yaxis()
#plt.gca().set_aspect(1)
plt.axis('off')
plt.tight_layout()
plt.show()
이 코드까지 실행하면 그림과 같이 경계선과 지역 이름만 나타난다.
tmp_list = list(set(pop['ID'].unique()) - set(draw_korea['ID'].unique()))
for tmp in tmp_list:
pop = pop.drop(pop[pop['ID'] == tmp].index)
print(set(pop['ID'].unique()) - set(draw_korea['ID'].unique()))
set()
set()
set()
원래 인구 현황을 가지고 있던 pop 변수와 엑셀에서 출발해서 지도를 그리기 위해 만든 draw_korea 변수에서 일반 행정구를 가진 시 (성남,수원 등등)의 합계 정보를 삭제한다.
pop = pd.merge(pop, draw_korea, how='left', on=['ID'])
pop.head()
광역시도 | 시도 | 20-39세여자 | 20-39세합계 | 65세이상합계 | 인구수남자 | 인구수여자 | 인구수합계 | 소멸비율 | 소멸위기지역 | ID | y | x | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 강원도 | 강릉시 | 23098.0 | 49384.0 | 37679.0 | 106231.0 | 107615.0 | 213846.0 | 1.226041 | False | 강릉 | 3 | 11 |
1 | 강원도 | 고성군 | 2529.0 | 7023.0 | 7151.0 | 15899.0 | 14215.0 | 30114.0 | 0.707314 | True | 고성(강원) | 0 | 10 |
2 | 강원도 | 동해시 | 9753.0 | 21264.0 | 15124.0 | 47166.0 | 46131.0 | 93297.0 | 1.289738 | False | 동해 | 4 | 11 |
3 | 강원도 | 삼척시 | 7115.0 | 15823.0 | 14610.0 | 35253.0 | 34346.0 | 69599.0 | 0.973990 | True | 삼척 | 5 | 11 |
4 | 강원도 | 속초시 | 8752.0 | 18708.0 | 12752.0 | 40288.0 | 41505.0 | 81793.0 | 1.372647 | False | 속초 | 1 | 10 |
그리고 두 변수 (pop,draw_korea)를 merge 한다.
이제 ID도 마련하고 각 지역별 좌표도 모두 확보했다. 이렇게 해서 지도 그릴 준비가 데이터 입장에서는 다 된듯 하다.
def drawKorea(targetData, blockedMap, cmapname):
gamma = 0.75
whitelabelmin = (max(blockedMap[targetData]) -
min(blockedMap[targetData]))*0.25 + \
min(blockedMap[targetData])
datalabel = targetData
vmin = min(blockedMap[targetData])
vmax = max(blockedMap[targetData])
mapdata = blockedMap.pivot_table(index='y', columns='x', values=targetData)
masked_mapdata = np.ma.masked_where(np.isnan(mapdata), mapdata)
plt.figure(figsize=(9, 11))
plt.pcolor(masked_mapdata, vmin=vmin, vmax=vmax, cmap=cmapname,
edgecolor='#aaaaaa', linewidth=0.5)
# 지역 이름 표시
for idx, row in blockedMap.iterrows():
# 광역시는 구 이름이 겹치는 경우가 많아서 시단위 이름도 같이 표시한다.
#(중구, 서구)
if len(row['ID'].split())==2:
dispname = '{}\n{}'.format(row['ID'].split()[0], row['ID'].split()[1])
elif row['ID'][:2]=='고성':
dispname = '고성'
else:
dispname = row['ID']
# 서대문구, 서귀포시 같이 이름이 3자 이상인 경우에 작은 글자로 표시한다.
if len(dispname.splitlines()[-1]) >= 3:
fontsize, linespacing = 10.0, 1.1
else:
fontsize, linespacing = 11, 1.
annocolor = 'white' if row[targetData] > whitelabelmin else 'black'
plt.annotate(dispname, (row['x']+0.5, row['y']+0.5), weight='bold',
fontsize=fontsize, ha='center', va='center', color=annocolor,
linespacing=linespacing)
# 시도 경계 그린다.
for path in BORDER_LINES:
ys, xs = zip(*path)
plt.plot(xs, ys, c='black', lw=2)
plt.gca().invert_yaxis()
plt.axis('off')
cb = plt.colorbar(shrink=.1, aspect=10)
cb.set_label(datalabel)
plt.tight_layout()
plt.show()
위 코드는 앞서 이야기 한 혜식 님의 메인 함수이다. 이것을 그대로 사용할 수 있도록 작업한 것과 같다고 생각해도 된다.
5-7 인구 현황 및 인구 소멸 지역 확인하기¶
drawKorea를 사용해서 인구수합계를 그려본다.
import numpy as np
drawKorea('인구수합계', pop, 'Blues')
확실히 서울 송파, 남양주, 화성은 인구가 많다. 사선으로 갈라지면서 인구가 거의 없는 곳이 보인다.
pop['소멸위기지역'] = [1 if con else 0 for con in pop['소멸위기지역']]
drawKorea('소멸위기지역', pop, 'Reds')
그리고 원래 우리의 목적 '인구소멸위기지역'을 확인해야 한다. 그림을 그리기 위해 bool형이었던 것을 1과 0으로 바꾸었다. 그러면 그림에서 색이 확연히 구분된다. 결과는 위와 같다. 두 지역 외에 인구 밀집 현상이 심하다.
5-8 인구 현황에서 여성 인구 비율 확인하기¶
drawKorea 함수의 일부 내용이 바뀌어야 한다. 이유는 표현하고자 하는 데이터에 음(-)이 값이 있는지 여부에 따라 일부 설정이 바뀌어야 하기 때문이다.
def drawKorea(targetData, blockedMap, cmapname):
gamma = 0.75
whitelabelmin = 20.
datalabel = targetData
tmp_max = max([ np.abs(min(blockedMap[targetData])),
np.abs(max(blockedMap[targetData]))])
vmin, vmax = -tmp_max, tmp_max
mapdata = blockedMap.pivot_table(index='y', columns='x', values=targetData)
masked_mapdata = np.ma.masked_where(np.isnan(mapdata), mapdata)
plt.figure(figsize=(9, 11))
plt.pcolor(masked_mapdata, vmin=vmin, vmax=vmax, cmap=cmapname,
edgecolor='#aaaaaa', linewidth=0.5)
# 지역 이름 표시
for idx, row in blockedMap.iterrows():
# 광역시는 구 이름이 겹치는 경우가 많아서 시단위 이름도 같이 표시한다.
#(중구, 서구)
if len(row['ID'].split())==2:
dispname = '{}\n{}'.format(row['ID'].split()[0], row['ID'].split()[1])
elif row['ID'][:2]=='고성':
dispname = '고성'
else:
dispname = row['ID']
# 서대문구, 서귀포시 같이 이름이 3자 이상인 경우에 작은 글자로 표시한다.
if len(dispname.splitlines()[-1]) >= 3:
fontsize, linespacing = 10.0, 1.1
else:
fontsize, linespacing = 11, 1.
annocolor = 'white' if np.abs(row[targetData]) > whitelabelmin else 'black'
plt.annotate(dispname, (row['x']+0.5, row['y']+0.5), weight='bold',
fontsize=fontsize, ha='center', va='center', color=annocolor,
linespacing=linespacing)
# 시도 경계 그린다.
for path in BORDER_LINES:
ys, xs = zip(*path)
plt.plot(xs, ys, c='black', lw=2)
plt.gca().invert_yaxis()
plt.axis('off')
cb = plt.colorbar(shrink=.1, aspect=10)
cb.set_label(datalabel)
plt.tight_layout()
plt.show()
그와 같은 설정은 당연히 평상시하면 한 함수에서 처리하도록 해야 하지만, 지금은 오히려 너무 함수 코드를 어렵게 만드는것 같아 그냥 두겠다.
pop['여성비'] = (pop['인구수여자']/pop['인구수합계'] - 0.5)*100
drawKorea('여성비',pop,'RdBu')
여성인구수와 합계를 알고 있어서 나눈 다음 0.5를 빼는 작업을 했습니다. 그래서 0이면 여성 인구수가 50%인 것입니다. 이렇게 한 이유는 drawKorea에 색상 팔레트는 지정할 때 RdBu 같은 설정을 사용하면 0을 기준으로 좌우가 다른 색상을 갖도록 할 수 있기 때문이다. 그 결과를 보면 파란색으로 갈수록 여성비가 높은 것이고, 빨간색으로 갈수록 여성비가 낮은것 입니다. 지역별로 여성비의 높낮이가 비슷해 보인다. 그러나 이것은 여성 인구 전체를 대상으로 한 것으로 20-30대 여성으로 데이터를 바꾸면 상황은 달라진다.
pop['2030여성비'] = (pop['20-39세여자']/pop['20-39세합계']-0.5)*100
drawKorea('2030여성비', pop, 'RdBu')
이번에는 20-30대 여성의 20-30대 전체 인구에 대한 비율을 확인해보겠다. 고령 여성 인구를 제외하고 나면 전국적으로 남성 인구 비율이 높다는 이야기가 되니 이것도 어쩌면 사회문제가 될 것 같다.
5-9 Folium에서 인구 소멸 위기 지역 표현하기¶
이미 몇 번 다뤄서 Folium이 친숙하게 느껴질 것이다.
pop_folium = pop.set_index('ID')
pop_folium.head()
광역시도 | 시도 | 20-39세여자 | 20-39세합계 | 65세이상합계 | 인구수남자 | 인구수여자 | 인구수합계 | 소멸비율 | 소멸위기지역 | y | x | 여성비 | 2030여성비 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ID | ||||||||||||||
강릉 | 강원도 | 강릉시 | 23098.0 | 49384.0 | 37679.0 | 106231.0 | 107615.0 | 213846.0 | 1.226041 | 0 | 3 | 11 | 0.323597 | -3.227766 |
고성(강원) | 강원도 | 고성군 | 2529.0 | 7023.0 | 7151.0 | 15899.0 | 14215.0 | 30114.0 | 0.707314 | 1 | 0 | 10 | -2.796042 | -13.989748 |
동해 | 강원도 | 동해시 | 9753.0 | 21264.0 | 15124.0 | 47166.0 | 46131.0 | 93297.0 | 1.289738 | 0 | 4 | 11 | -0.554680 | -4.133747 |
삼척 | 강원도 | 삼척시 | 7115.0 | 15823.0 | 14610.0 | 35253.0 | 34346.0 | 69599.0 | 0.973990 | 1 | 5 | 11 | -0.651590 | -5.033812 |
속초 | 강원도 | 속초시 | 8752.0 | 18708.0 | 12752.0 | 40288.0 | 41505.0 | 81793.0 | 1.372647 | 0 | 1 | 10 | 0.743951 | -3.217875 |
원래 pop 데이터에서 ID컬럼을 index로 설정한다. 그래야 Folium에서 쉽게 인식한다.
import folium
import json
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
Folium과 json을 import 한다.
geo_path = './data/05. skorea_municipalities_geo_simple.json'
geo_str = json.load(open(geo_path, encoding='utf-8'))
map = folium.Map(location=[36.2002, 127.054], zoom_start=7)
map.choropleth(geo_data = geo_str,
data = pop_folium['인구수합계'],
columns = [pop_folium.index, pop_folium['인구수합계']],
fill_color = 'YlGnBu', #PuRd, YlGnBu
key_on = 'feature.id')
map
앞 절에서 약간 편집을 한 json파일을 연결하고 '인구수합계'를 표현했다.
map = folium.Map(location=[36.2002, 127.054], zoom_start=7)
map.choropleth(geo_data = geo_str,
data = pop_folium['소멸위기지역'],
columns = [pop_folium.index, pop_folium['소멸위기지역']],
fill_color = 'PuRd', #PuRd, YlGnBu
key_on='feature.id')
map
인구 소멸 위기 지역도 확인했습니다.
draw_korea.to_csv("./data/05. draw_korea.csv", encoding='utf-8', sep=',')
우리가 이룬 성과를 저장하겠다.
- 출처:[파이썬으로 데이터 주무르기]
'파이썬으로 데이터 주무르기' 카테고리의 다른 글
Chapter 8. 자연어 처리 시작하기 (0) | 2021.05.23 |
---|---|
Chapter 7. 시계열 데이터를 다뤄보자 (0) | 2021.04.11 |
Chapter 3. 시카고 샌드위치 맛집 분석 + 네이버 영화 평점 & 평점 변화 확인하기 (0) | 2021.04.11 |
Chapter 2. 서울시 범죄 현황 분석 (0) | 2021.04.10 |
Chapter 1. 서울시 구별 CCTV 현황 분석 (0) | 2021.04.08 |