chapter-5 우리나라 인구 소멸 위기 지역 분석
5장 우리나라 인구 소멸 위기 지역 분석¶
이번에는 우리나라의 인구 소멸 위기 지역에 대해 조사하겠습니다. 인구 소멸 위기 지역을 시각화해서 위기감을 일으키는 것이 목접입니다. 그러기 위해서는 이전에 했던 서울시 지도가 아니라 대한민국 지도가 그려져야 합니다. 이번 절에서는 대한민국 지도를 그리고 그 위에 인구 소멸 위기 지역에 대해 매핑해서 시각화하는 것을 최종 목표로 합니다.
5- 목표 명확히 하기¶
이상호 한국고용정보원 연구원의 <한국의 지방 소멸에 관한 7가지 분석>이라는 보고서에서 사용한 방법으로, 인구 소멸 지역의 정의를 65세 이상 노인 인구와 20 ~ 39세 여성 인구를 비교해서 젊은 여성인구가 노인 인구의 절반에 미달할 경우 인구 소멸 위험 지역으로 분류하는 방법입니다.
이 방식에 따라 먼저 각 지역별 20 ~ 30대 여성 인구수를 파악해야 하며, 또65세 이상 노인 인구수를 파악해야 합니다. 또한 인구 소멸 위기 지역인지 파악해야 합니다. 그리고 한 단계 더 나아가 한국 지도에 시각화하기 위해 한국 지도를 그리는 법을 확보해야 합니다. 한국 지도 그리는 법은 앞서 사용한 Folium을 이용한 방법과 또 다른 방법 모두 확인해보겠습니다.
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"
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
population = pd.read_excel('data/05. population_raw_data.xlsx', 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
복잡한 형태의 엑셀 형식이므로 설정을 해야 할 것이 몇 줄 됩니다. 먼저 두 번째 줄부터 읽어야하고, 빈 셀에 대해 NaN 처리를 하지 않고 그 앞 내용으로 채우도록 합니다. 그리고 적절히 컬럼의 이름을 바꾸고 중간중간에 있는'소계'라는 항목도 삭제했습니다.
그렇게 얻은 결과 중 일부입니다. 웹 브라우저에서는 스크롤해야 할 정도의 양이 될 것입니다.
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 인구 소멸 위기 지역 계산하고 데이터 정리하기¶
5-1절에서 이야기한 것처럼 인구 소멸 위기 지역을 알기 위해서는 먼저 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 |
아주 손쉽게 계산할 수 있습니다.
그 결과는 쉽게 얻을 수 있습니다. 이제 이 많은 컬럼들 중에서 일부만 선택해야 합니다. 그리고 결정적으로'합계','남자','여자'로 되어 있는 구분도 정리해야 합니다. 이럴 때 사용하는 마법 키워드를 우리는 하나 알고 있습니다.
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
바로 pivot_table입니다. 광역시도와 시도를 index로 잡고, 인구수와 20-39세, 65세 이상만 데이터로 가져오도록 합니다.
그 결과 중 일부입니다. 이제 대망의 인구 소멸 비율을 계산할 수 있습니다. 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 |
5-1절에서 확인한 인구 소멸 위기 지역에 대한 정의에 의해 코드[6]에서 계산한 소멸비율이 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_coloumns = [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_coloumns
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 |
역시 다단으로 표시된 컬럼(column)을 하나로 합칩니다.
이제 세로축에 지역, 가로축에 연령대별 혹은 성별 인구수가 정리되었습니다.
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이라는 이름으로 활동하는 github 사이트가 있습니다. Lucy Park님은 인터넷 상에서 참으로 좋아하고 존경하는 분입니다. Github에서는 e9t라는 아이디로 활동합니다. https://goo.gl/xi5pKD로 접속하면 대한민국 시군구 단위로 구분된 json파일을 얻을 수 있습니다.
5-5 지도 시각화를 위해 지역별 고유 ID 만들기¶
일단 우리에게 필요한 것은 두 방법 모두 지역에 따른 고유ID가 필요하다는 것입니다. 우선 pop['시도']에 대해 unique를 조사하도록 하겠습니다.
pop['시도'].unique()
array(['강릉시', '고성군', '동해시', '삼척시', '속초시', '양구군', '양양군', '영월군', '원주시',
'인제군', '정선군', '철원군', '춘천시', '태백시', '평창군', '홍천군', '화천군', '횡성군',
'가평군', '고양시', '과천시', '광명시', '광주시', '구리시', '군포시', '권선구', '기흥구',
'김포시', '남양주시', '단원구', '덕양구', '동두천시', '동안구', '만안구', '부천시', '분당구',
'상록구', '성남시', '소사구', '수원시', '수정구', '수지구', '시흥시', '안산시', '안성시',
'안양시', '양주시', '양평군', '여주시', '연천군', '영통구', '오산시', '오정구', '용인시',
'원미구', '의왕시', '의정부시', '이천시', '일산동구', '일산서구', '장안구', '중원구', '처인구',
'파주시', '팔달구', '평택시', '포천시', '하남시', '화성시', '거제시', '거창군', '김해시',
'남해군', '마산합포구', '마산회원구', '밀양시', '사천시', '산청군', '성산구', '양산시', '의령군',
'의창구', '진주시', '진해구', '창녕군', '창원시', '통영시', '하동군', '함안군', '함양군',
'합천군', '경산시', '경주시', '고령군', '구미시', '군위군', '김천시', '남구', '문경시',
'봉화군', '북구', '상주시', '성주군', '안동시', '영덕군', '영양군', '영주시', '영천시',
'예천군', '울릉군', '울진군', '의성군', '청도군', '청송군', '칠곡군', '포항시', '광산구',
'동구', '서구', '달서구', '달성군', '수성구', '중구', '대덕구', '유성구', '강서구', '금정구',
'기장군', '동래구', '부산진구', '사상구', '사하구', '수영구', '연제구', '영도구', '해운대구',
'강남구', '강동구', '강북구', '관악구', '광진구', '구로구', '금천구', '노원구', '도봉구',
'동대문구', '동작구', '마포구', '서대문구', '서초구', '성동구', '성북구', '송파구', '양천구',
'영등포구', '용산구', '은평구', '종로구', '중랑구', '세종특별자치시', '울주군', '강화군', '계양구',
'남동구', '부평구', '연수구', '옹진군', '강진군', '고흥군', '곡성군', '광양시', '구례군',
'나주시', '담양군', '목포시', '무안군', '보성군', '순천시', '신안군', '여수시', '영광군',
'영암군', '완도군', '장성군', '장흥군', '진도군', '함평군', '해남군', '화순군', '고창군',
'군산시', '김제시', '남원시', '덕진구', '무주군', '부안군', '순창군', '완산구', '완주군',
'익산시', '임실군', '장수군', '전주시', '정읍시', '진안군', '서귀포시', '제주시', '계룡시',
'공주시', '금산군', '논산시', '당진시', '동남구', '보령시', '부여군', '서북구', '서산시',
'서천군', '아산시', '예산군', '천안시', '청양군', '태안군', '홍성군', '괴산군', '단양군',
'보은군', '상당구', '서원구', '영동군', '옥천군', '음성군', '제천시', '증평군', '진천군',
'청원구', '청주시', '충주시', '흥덕구'], dtype=object)
조사된 결과를 유심히 보면 인천,부산 등 광역시의 구(이런 구를 자치구라고 부릅니다)도 있지만, 안양시나 수원시에 있는 구(이런 구를 행정구라고 합니다.)도 있습니다. 일단 우리는 고유 아이디를 '과역시도'의 값과'시도'의 값을 합치면 될 듯합니다. 물론 지도 표기 및 ID로서의 간결함을 유지하기 위해 서울 강남과 같은 형태를 원칙으로 합니다. 특별히 구 이름이 두 글자인 경우는'서울 중구'로 두면 됩니다. 그럼 일반 자치시의 경우는'충북 제천'과 같이 표기하면 좋겠습니다. 그런데 우리가 받은 엑셀 파일의 규칙을 다시 보면서 단순히 pop['광역시도']와 pop['시도']를 합친다고 하면 광역시도의 자치구는 서울 중구와 같이 만들 수 있지만, 행정구의 경우는 안양시 동안구가 되지 않고'경기도 동안구'가 됩니다. 그래서 이 부분에 대한 고민이 필요합니다.
si_name = [None] * len(pop)
tmp_gu_dict = {'수원':['장안구','권선구','팔달구','영통구'],
'성남':['수정구','중원구','분당구'],
'안양':['만안구','동안구'],
'안산':['상록구','단원구'],
'고양':['덕양구','일산동구','일산서구'],
'용인':['처인구','기흥구','수지구'],
'청주':['상당구','서원구','흥덕구','청원구'],
'천안':['동남구','서북구'],
'전주':['완산구','덕진구'],
'포항':['남구','북구'],
'창원':['의창구','성산구','진해구','마산합포구','마산회원구'],
'부천':['오정구','원미구','소사구']}
먼저 광역시가 아니면서 구를 가지고 있는 시와 그 행정구를 파이썬의 dict형으로 선언합니다. 그리고 코드[14]와 같이 작성합니다. 먼저 광역시도에 잇는 이름의 끝 세글자가'광역시','득별시','자치시'로 끝나지 않으면 일반 시 혹은 군으로 봅니다. 그 속에서 강원도와 경상남도에는 동일한 이름을 가진'고성군'이 있어서 그것을 처리합니다. 그리고 방금 이야기한 일반 시인데 구를 가지는 경우에 대해 대응합니다. 그리고 세종특별자치시를 그냥'세종'으로 처리하고, 나머지는 광역시도에서 앞 두 글자(서울특별시)와 시도에서 두 글자인 경우 모두, 아니면 앞 두 글자만 선택하면서 고유 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 | 속초 |
5-6 Cartogram으로 우리나라 지도 만들기¶
5-4절에서 이야기했지만 혜식 님의 방식은 그 시작을 고민해야 합니다. 혜식님의 방법에서는 안양동안이나 안양 만안을 합쳐서 그냥'안양'이라고만 되어 있기 때문에 제 의도와는 약간 다른 것이 문제입니다. 그래서 처음부터 고민해야하는 것인데, 저는 그 시작 원리로 엑셀로 잡았습니다.
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 |
우리에게 필요한 것은 각 지역별 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()
이 코드는 길어서 Jupyter Notebook 화면으로 담을 수 없었습니다. 이 코드까지 실행하면 위 그림과 같이 경계선과 지역 이름만 나타납니다. 반복문 안에서 대다수 코드는 이름을 표기하기 위한 코드입니다. 그리고 경계선을 그립니다. 마지막 부분에 invert_yaxis()가 되어 있는 이유는 y축이 엑셀에서 0번이 시작하는 것과 matplotlib가 0이라고 인식하는 좌표가 서로 반대이기 때문입니다. 일단 대한민국 지도 그리기는 잘 될 듯합니다.
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 |
이제 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 인구 현황 및 인구 소멸 지역 확인하기¶
5-6절에서 만든 함수 drawKorea를 사용해서 인구수합계를 그려봅니다.
drawKorea('인구수합계', pop, 'Blues')
확실히 서울 송파,남양주,화성은 인구가 많다는 것이 보입니다. 이렇게 확인했더니 사선으로 갈라지면서 인구가 거의 없는 곳이 보이네요.
pop['소멸위기지역'] = [1 if con else 0 for con in pop['소멸위기지역']]
drawKorea('소멸위기지역', pop, 'Reds')
그리고 원래 우리의 목적'인구소멸위기지역'을 확인해야 합니다. 그림을 그리기 위해 bool형이었던것을 1과 0으로 바꾸겠습니다. 그러면 그림에서 색이 확연히 구분됩니다.
결과는 위와 같습니다. 생각보다 심각합니다. 특히 광역시 중에서도 부산은 동구와 영도가 위험한 지역에 포함되어 있습니다. 바꿔 이야기하면 두 지역 외에 인구 밀집 현상이 심하다고 할 수도 있습니다.
5-8 인구 현황에서 여성 인구 비율 확인하기¶
이미 가지고 있는 데이터이니 여성 인구 비율도 확인해보겠습니다. 한때 인구 소멸 위기 지역 이야기가 나오기 전에는 여성 인구가 너무 줄어들어 남녀 비율의 불균형이 뉴스에 자주 등장했는데 지금은 어떤지 확인해보겠습니다.
그러기 위해서는 5-7절의 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에서 인구 소멸 위기 지역 표현하기¶
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 |
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
인구 소멸 위기 지역도 확인했습니다.
출처 : "파이썬으로 데이터 주무르기"