20 분 소요

1. 배경

목표

  • 인구 소멸 위기 지역 파악
  • 인구 소멸 위기 지역 지도 표현
  • 지도 표현에 대한 카르토그램 표현

2. 데이터 읽고 인구 소멸 지역 계산

requirements

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import set_matplotlib_hangul
import warnings

matplotlib.rcParams['axes.unicode_minus'] = False
warnings.filterwarnings(action="ignore")
%matplotlib inline

fillna()

datas = {
    "A": np.random.randint(1, 45, 8),
    "B": np.random.randint(1, 45, 8),
    "C": np.random.randint(1, 45, 8)
}

fillna_df = pd.DataFrame(datas)
fillna_df
A B C
0 10 38 3
1 26 8 18
2 24 34 20
3 21 14 39
4 24 38 30
5 42 37 8
6 15 25 5
7 13 2 3

fillna_df.loc[2:4, ["A"]] = np.nan
fillna_df.loc[3:5, ["B"]] = np.nan
fillna_df.loc[4:7, ["C"]] = np.nan
fillna_df
A B C
0 10.0 38.0 3.0
1 26.0 8.0 18.0
2 NaN 34.0 20.0
3 NaN NaN 39.0
4 NaN NaN NaN
5 42.0 NaN NaN
6 15.0 25.0 NaN
7 13.0 2.0 NaN

fillna_df.fillna(method="ffill") # pad
A B C
0 10.0 38.0 3.0
1 26.0 8.0 18.0
2 26.0 34.0 20.0
3 26.0 34.0 39.0
4 26.0 34.0 39.0
5 42.0 34.0 39.0
6 15.0 25.0 39.0
7 13.0 2.0 39.0

데이터 불러오기

population = pd.read_excel("../data/07_population_raw_data.xlsx", header=1)
population.fillna(method="pad", inplace=True)
population
행정구역(동읍면)별(1) 행정구역(동읍면)별(2) 항목 20 - 24세 25 - 29세 30 - 34세 35 - 39세 65 - 69세 70 - 74세 75 - 79세 80 - 84세 85 - 89세 90 - 94세 95 - 99세 100+
0 전국 소계 총인구수 (명) 51696216.0 3541061.0 3217367.0 3517868 4016272.0 2237345.0 1781229.0 1457890 909130.0 416164.0 141488.0 34844 17562.0
1 전국 소계 남자인구수 (명) 25827594.0 1877127.0 1682988.0 1806754 2045265.0 1072395.0 806680.0 600607 319391.0 113221.0 32695.0 7658 4137.0
2 전국 소계 여자인구수 (명) 25868622.0 1663934.0 1534379.0 1711114 1971007.0 1164950.0 974549.0 857283 589739.0 302943.0 108793.0 27186 13425.0
3 서울특별시 소계 총인구수 (명) 9930616.0 690728.0 751973.0 803507 817467.0 448956.0 350580.0 251961 141649.0 66067.0 24153.0 7058 5475.0
4 서울특별시 소계 남자인구수 (명) 4876789.0 347534.0 372249.0 402358 410076.0 211568.0 163766.0 112076 54033.0 19595.0 6146.0 1900 1406.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

population.info()

=>

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 846 entries, 0 to 845
Data columns (total 16 columns):
 #   Column         Non-Null Count  Dtype  
---  ------         --------------  -----  
 0   행정구역(동읍면)별(1)  846 non-null    object 
 1   행정구역(동읍면)별(2)  846 non-null    object 
 2   항목             846 non-null    object 
 3   계              846 non-null    float64
 4   20 - 24세       846 non-null    float64
 5   25 - 29세       846 non-null    float64
 6   30 - 34세       846 non-null    int64  
 7   35 - 39세       846 non-null    float64
 8   65 - 69세       846 non-null    float64
 9   70 - 74세       846 non-null    float64
 10  75 - 79세       846 non-null    int64  
 11  80 - 84세       846 non-null    float64
 12  85 - 89세       846 non-null    float64
 13  90 - 94세       846 non-null    float64
 14  95 - 99세       846 non-null    int64  
 15  100+           846 non-null    float64
dtypes: float64(10), int64(3), object(3)
memory usage: 105.9+ KB

컬럼 이름 변경

population.rename(
    columns={
        "행정구역(동읍면)별(1)": "광역시도",
        "행정구역(동읍면)별(2)": "시도",
        "계": "인구수"
    }, inplace=True
)
population.head()
광역시도 시도 항목 인구수 20 - 24세 25 - 29세 30 - 34세 35 - 39세 65 - 69세 70 - 74세 75 - 79세 80 - 84세 85 - 89세 90 - 94세 95 - 99세 100+
0 전국 소계 총인구수 (명) 51696216.0 3541061.0 3217367.0 3517868 4016272.0 2237345.0 1781229.0 1457890 909130.0 416164.0 141488.0 34844 17562.0
1 전국 소계 남자인구수 (명) 25827594.0 1877127.0 1682988.0 1806754 2045265.0 1072395.0 806680.0 600607 319391.0 113221.0 32695.0 7658 4137.0
2 전국 소계 여자인구수 (명) 25868622.0 1663934.0 1534379.0 1711114 1971007.0 1164950.0 974549.0 857283 589739.0 302943.0 108793.0 27186 13425.0
3 서울특별시 소계 총인구수 (명) 9930616.0 690728.0 751973.0 803507 817467.0 448956.0 350580.0 251961 141649.0 66067.0 24153.0 7058 5475.0
4 서울특별시 소계 남자인구수 (명) 4876789.0 347534.0 372249.0 402358 410076.0 211568.0 163766.0 112076 54033.0 19595.0 6146.0 1900 1406.0

소계 제거

population = population[population["시도"] != "소계"]
population.head()
광역시도 시도 항목 인구수 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

population.is_copy = False # copy 했을 때 warning X

population.rename(
    columns={"항목": "구분"}, inplace=True
)
population.head()
광역시도 시도 구분 인구수 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

population.loc[population["구분"] == "총인구수 (명)", "구분"] = "합계"
population.loc[population["구분"] == "남자인구수 (명)", "구분"] = "남자"
population.loc[population["구분"] == "여자인구수 (명)", "구분"] = "여자"
population.head()
광역시도 시도 구분 인구수 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

소멸지역 조사를 위한 데이터

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.tail()
광역시도 시도 구분 인구수 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세이상
841 제주특별자치도 제주시 남자 235977.0 17377.0 13118.0 15084 18350.0 8474.0 6782.0 4941 2737.0 854.0 226.0 53 17.0 63929.0 24084.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 60255.0 35075.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 39298.0 30030.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 20622.0 12294.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 18676.0 17736.0

pivot_table

pop = pd.pivot_table(
    data=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

소멸 비율 계산

pop["소멸비율"] = pop["20-39세", "여자"] / (pop["65세이상", "합계"] / 2)
pop.tail()
20-39세 65세이상 인구수 소멸비율
구분 남자 여자 합계 남자 여자 합계 남자 여자 합계
광역시도 시도
충청북도 진천군 9391.0 7622.0 17013.0 4731.0 6575.0 11306.0 36387.0 33563.0 69950.0 1.348311
청원구 32216.0 27805.0 60021.0 8417.0 11914.0 20331.0 97006.0 93807.0 190813.0 2.735232
청주시 128318.0 115719.0 244037.0 37882.0 53671.0 91553.0 419323.0 415874.0 835197.0 2.527913
충주시 26600.0 22757.0 49357.0 14407.0 20383.0 34790.0 104877.0 103473.0 208350.0 1.308249
흥덕구 40933.0 37675.0 78608.0 9788.0 13671.0 23459.0 127647.0 125916.0 253563.0 3.211987

소멸 위기 지역 컬럼 생성

pop["소멸위기지역"] = pop["소멸비율"] < 1.0
pop
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
... ... ... ... ... ... ... ... ... ... ... ... ...
충청북도 진천군 9391.0 7622.0 17013.0 4731.0 6575.0 11306.0 36387.0 33563.0 69950.0 1.348311 False
청원구 32216.0 27805.0 60021.0 8417.0 11914.0 20331.0 97006.0 93807.0 190813.0 2.735232 False
청주시 128318.0 115719.0 244037.0 37882.0 53671.0 91553.0 419323.0 415874.0 835197.0 2.527913 False
충주시 26600.0 22757.0 49357.0 14407.0 20383.0 34790.0 104877.0 103473.0 208350.0 1.308249 False
흥덕구 40933.0 37675.0 78608.0 9788.0 13671.0 23459.0 127647.0 125916.0 253563.0 3.211987 False

소멸 위기 지역 조회

pop[pop["소멸위기지역"] == True].index.get_level_values(1)

=>

Index(['고성군', '삼척시', '양양군', '영월군', '정선군', '평창군', '홍천군', '횡성군', '가평군', '양평군',
       '연천군', '거창군', '고성군', '남해군', '밀양시', '산청군', '의령군', '창녕군', 
       								.
                                    .
                                    .
       '괴산군', '단양군',
       '보은군', '영동군', '옥천군'],
      dtype='object', name='시도')

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

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

3. 지도 시각화를 위한 지역별 ID 만들기

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

pop["시도"].unique()

=>

array(['강릉시', '고성군', '동해시', '삼척시', '속초시', '양구군', '양양군', '영월군', '원주시',
       '인제군', '정선군', '철원군', '춘천시', '태백시', '평창군', '홍천군', '화천군', '횡성군',

										.
                                        .
                                        .

'단양군',
       '보은군', '상당구', '서원구', '영동군', '옥천군', '음성군', '제천시', '증평군', '진천군',
       '청원구', '청주시', '충주시', '흥덕구'], dtype=object)

si_name = [None] * len(pop)

tmp_gu_dict = {
    "수원": ["장안구", "권선구", "팔달구", "영통구"],
    "성남": ["수정구", "중원구", "분당구"],
    "안양": ["만안구", "동안구"],
    "안산": ["상록구", "단원구"],
    "고양": ["덕양구", "일산동구", "일산서구"],
    "용인": ["처인구", "기흥구", "수지구"],
    "청주": ["상당구", "서원구", "흥덕구", "청원구"],
    "천안": ["동남구", "서북구"],
    "전주": ["완산구", "덕진구"],
    "포항": ["남구", "북구"],
    "창원": ["의창구", "성산구", "진해구", "마산합포구", "마산회원구"],
    "부천": ["오정구", "원미구", "소사구"]
}

pop["광역시도"].unique()

=>

array(['강원도', '경기도', '경상남도', '경상북도', '광주광역시', '대구광역시', '대전광역시', '부산광역시',
       '서울특별시', '세종특별자치시', '울산광역시', '인천광역시', '전라남도', '전라북도', '제주특별자치도',
       '충청남도', '충청북도'], dtype=object)

pop["시도"].unique()

=>

array(['강릉시', '고성군', '동해시', '삼척시', '속초시', '양구군', '양양군', '영월군', '원주시',
       '인제군', '정선군', '철원군', '춘천시', '태백시', '평창군', '홍천군', '화천군', '횡성군',
											.
											.
                                            .
       '보은군', '상당구', '서원구', '영동군', '옥천군', '음성군', '제천시', '증평군', '진천군',
       '청원구', '청주시', '충주시', '흥덕구'], dtype=object)

(1) 일반 시 이름과 세종시, 광역시도 일반 구 정리

for idx, row in pop.iterrows():
    if row["광역시도"][-3:] not in ["광역시", "특별시", "자치시"]:
        si_name[idx] = row["시도"][:-1]

    elif row["광역시도"] == "세종특별자치시":
        si_name[idx] = "세종"

    else:
        if len(row["시도"]) == 2:
            si_name[idx] = row["광역시도"][:2] + " " + row["시도"]
        else:
            si_name[idx] = row["광역시도"][:2] + " " + row["시도"][:-1]

(2) 행정구

for idx, row in pop.iterrows():
    if row["광역시도"][-3:] not in ["광역시", "특별시", "자치시"]:
        for keys, values in tmp_gu_dict.items():        # dataframe -> iterrows() == dictionary -> items()
            if row["시도"] in values:
                if len(row["시도"]) == 2:
                    si_name[idx] = keys + " " + row["시도"]

                elif row["시도"] in ["마산합포구", "마산회원구"]:
                    si_name[idx] = keys + " " + row["시도"][2:-1]

                else:
                    si_name[idx] = keys + " " + row["시도"][:-1]

(3) 고성군

for idx, row in pop.iterrows():
    if row["광역시도"][-3:] not in ["광역시", "특별시", "자치시"]:
        if row["시도"][:-1] == "고성" and row["광역시도"] == "강원도":
            si_name[idx] = "고성(강원)"
        elif row["시도"][:-1] == "고성" and row["광역시도"] == "경상남도":
            si_name[idx] = "고성(경남)"
pop["ID"] = si_name
pop
광역시도 시도 20-39세남자 20-39세여자 20-39세합계 65세이상남자 65세이상여자 65세이상합계 인구수남자 인구수여자 인구수합계 소멸비율 소멸위기지역 ID
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 속초
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
259 충청북도 진천군 9391.0 7622.0 17013.0 4731.0 6575.0 11306.0 36387.0 33563.0 69950.0 1.348311 False 진천
260 충청북도 청원구 32216.0 27805.0 60021.0 8417.0 11914.0 20331.0 97006.0 93807.0 190813.0 2.735232 False 청주 청원
261 충청북도 청주시 128318.0 115719.0 244037.0 37882.0 53671.0 91553.0 419323.0 415874.0 835197.0 2.527913 False 청주
262 충청북도 충주시 26600.0 22757.0 49357.0 14407.0 20383.0 34790.0 104877.0 103473.0 208350.0 1.308249 False 충주
263 충청북도 흥덕구 40933.0 37675.0 78608.0 9788.0 13671.0 23459.0 127647.0 125916.0 253563.0 3.211987 False 청주 흥덕

del pop["20-39세남자"]
del pop["65세이상남자"]
del pop["65세이상여자"]

4. 지도 그리기(카르토그램)

draw_korea_raw = pd.read_excel("../data/07_draw_korea_raw.xlsx")
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

draw_korea_raw_stacked = pd.DataFrame(draw_korea_raw.stack())
draw_korea_raw_stacked
0
0 7 철원
8 화천
9 양구
10 고성(강원)
1 3 양주
... ... ...
24 2 장흥
3 보성
25 2 완도
5 제주
26 5 서귀포

draw_korea_raw_stacked.reset_index(inplace=True)
draw_korea_raw_stacked
level_0 level_1 0
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 서귀포

콜럼 재정의

draw_korea_raw_stacked.rename(
    columns={
    "level_0": "y",
    "level_1": "x",
    0: "ID"
    }, inplace=True
)

draw_korea = draw_korea_raw_stacked
draw_korea
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 서귀포

우리나라 경계선

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)],
    [(27, 5), (27, 6), (25, 6)]
]

def plot_text_simple(draw_korea):
    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"]
        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,
            linespacing=linespacing,
            ha="center", # 수평 정렬
            va="center", # 수직 정렬

        )        

def simpleDraw(draw_korea):
    plt.figure(figsize=(8, 11))
    plot_text_simple(draw_korea)

    for path in BORDER_LINES:
        ys, xs = zip(*path)
        plt.plot(xs, ys, c="black", lw=1.5)

    plt.gca().invert_yaxis()
    plt.axis("off")
    plt.tight_layout()
    plt.show()
simpleDraw(draw_korea)


검증 작업

set(draw_korea["ID"].unique()) - set(pop["ID"].unique())

=>

set()

set(pop["ID"].unique()) - set(draw_korea["ID"].unique())

=>

{'고양', '부천', '성남', '수원', '안산', '안양', '용인', '전주', '창원', '천안', '청주', '포항'}

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()

merge()

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 0 강릉 3 11
1 강원도 고성군 2529.0 7023.0 7151.0 15899.0 14215.0 30114.0 0.707314 1 고성(강원) 0 10
2 강원도 동해시 9753.0 21264.0 15124.0 47166.0 46131.0 93297.0 1.289738 0 동해 4 11
3 강원도 삼척시 7115.0 15823.0 14610.0 35253.0 34346.0 69599.0 0.973990 1 삼척 5 11
4 강원도 속초시 8752.0 18708.0 12752.0 40288.0 41505.0 81793.0 1.372647 0 속초 1 10

그림 그리기 위한 데이터를 계산하는 함수

  • 색상을 만들 때, 최소값을 흰색
  • blockedMap: 인구현황(pop)
  • targetData: 그리고싶은 컬럼
def get_data_info(targetData, blockedMap):
    whitelabelmin = (
        max(blockedMap[targetData]) - min(blockedMap[targetData])
        
    ) * 0.25 + min(blockedMap[targetData])
    
    vmin = min(blockedMap[targetData])
    vmax = max(blockedMap[targetData])

    mapdata = blockedMap.pivot_table(index="y", columns="x", values=targetData)

    return mapdata, vmax, vmin, whitelabelmin
def get_data_info_for_zero_center(targetData, blockedMap):
    whitelabelmin = 5
    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)

    return mapdata, vmax, vmin, whitelabelmin
def plot_text(targetData, blockedMap, whitelabelmin):
    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"]
        if len(dispname.splitlines()[-1]) >= 3:
            fontsize, linespacing = 9.5, 1.5
        else:
            fontsize, linespacing = 11, 1.2

        annocolor = "white" if np.abs(row[targetData]) > whitelabelmin else "black"

        plt.annotate(
            dispname,
            (row["x"] + 0.5, row["y"] + 0.5),
            weight="bold",
            color=annocolor,
            fontsize=fontsize,
            linespacing=linespacing,
            ha="center", # 수평 정렬
            va="center", # 수직 정렬

        )        
def drawKorea(targetData, blockedMap, cmapname, zeroCenter=False):
    if zeroCenter:
        masked_mapdata, vmax, vmin, whitelabelmin = get_data_info_for_zero_center(targetData, blockedMap)

    if not zeroCenter:
        masked_mapdata, vmax, vmin, whitelabelmin = get_data_info(targetData, blockedMap)

    plt.figure(figsize=(8, 11))
    plt.pcolor(masked_mapdata, vmin=vmin, vmax=vmax, cmap=cmapname, edgecolor="#aaaaaa", linewidth=0.5)

    plot_text(targetData, blockedMap, whitelabelmin)

    for path in BORDER_LINES:
        ys, xs = zip(*path)
        plt.plot(xs, ys, c="black", lw=1.5)

    plt.gca().invert_yaxis()
    plt.axis("off")
    plt.tight_layout()
    cb = plt.colorbar(shrink=0.1, aspect=10)
    cb.set_label(targetData)
    plt.show()

drawKorea("인구수합계", pop, "Blues")


pop["소멸위기지역"]

=>

0      0
1      1
2      0
3      1
4      0
      ..
247    0
248    0
249    0
250    0
251    0
Name: 소멸위기지역, Length: 252, dtype: int64

소멸 위기 지역

pop["소멸위기지역"] = [1 if con else 0 for con in pop["소멸위기지역"]]
drawKorea("소멸위기지역", pop, "Reds")


여성비

pop["여성비"] = (pop["인구수여자"] / pop["인구수합계"] - 0.5) * 100
drawKorea("여성비", pop, "RdBu", zeroCenter=True)


2030여성비

pop["2030여성비"] = (pop["20-39세여자"] / pop["20-39세합계"] - 0.5) * 100
drawKorea("2030여성비", pop, "RdBu", zeroCenter=True)


5. folium

import folium
import json

pop_folium = pop.set_index("ID")
pop_folium
광역시도 시도 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
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
증평 충청북도 증평군 4554.0 10085.0 5323.0 19110.0 18198.0 37308.0 1.711065 0 13 6 -1.222258 -4.843827
진천 충청북도 진천군 7622.0 17013.0 11306.0 36387.0 33563.0 69950.0 1.348311 0 12 5 -2.018585 -5.198965
청주 청원 충청북도 청원구 27805.0 60021.0 20331.0 97006.0 93807.0 190813.0 2.735232 0 13 7 -0.838255 -3.674547
충주 충청북도 충주시 22757.0 49357.0 34790.0 104877.0 103473.0 208350.0 1.308249 0 12 6 -0.336933 -3.893065
청주 흥덕 충청북도 흥덕구 37675.0 78608.0 23459.0 127647.0 125916.0 253563.0 3.211987 0 12 7 -0.341335 -2.072308

인구수합계 지도시각화

geo_path = "../data/07_skorea_municipalities_geo_simple.json"
geo_str = json.load(open(geo_path, encoding="utf-8"))

mymap = folium.Map(location=[36.2002, 127.054], zoom_start=7)
mymap.choropleth(
    geo_data=geo_str,
    data=pop_folium["인구수합계"],
    key_on="feature.id",
    columns=[pop_folium.index, pop_folium["인구수합계"]],
    fill_color="YlGnBu"
)
mymap


소멸 위기 지역 지도시각화

mymap = folium.Map(location=[36.2002, 127.054], zoom_start=7)
mymap.choropleth(
    geo_data=geo_str,
    data=pop_folium["소멸위기지역"],
    key_on="feature.id",
    columns=[pop_folium.index, pop_folium["소멸위기지역"]],
    fill_color="PuRd"
)
mymap