데이터 사이언스 05 지도데이터 처리1

게시: by Creative Commons Licence

1. 도구들

1) Pandas 데이터 핸들링

2) 시각화 도구 1 : matplotlib

3) 시각화 도구 2: seaborn

4) 지도 시각화

(1) folium

folium 공식문서 지도시각화 갤러리 서울지도 시각화 - 임재곤의 블로그 Python으로 만드는 시각화::공간시각화::지도시각화 만들기 D3를 이용한 서울시내 맛집 시각화 (feat. 식신로드) - Lucy Park

(2) SHP 포맷

GeoJSON으로 대한민국 시군구읍면동 맵차트 그리기

SHP(Shape file) 포맷 : GIS(지리정보시스템) : ESRI 개발

공간정보시스템 연구소
국가공간정보포털

(3) SHP to GeoJSON

GeoJSON포맷으로 바꾸기 for Web ⬅ SHP(Shape file) 포맷

pyshp

(4) grid map (Square Tile Grid Map)

ESRI 혜식이의 떼로 보는 세상 A semi-automatic way to create your own grid map Square Tile grid map

5) 웹 페이지 정보 읽어오기 1 - BeautifulSoup

BeautifulSoup 공식문서 BeautifulSoup Paser- lxml

6) 웹 페이지 정보 읽어오기 2 - Selenium

Selenium with Python Selenium 공식문서


2. 한국 인구 소멸 위기 지역 - 지도 시각화

1) 개념 정의 - 인구소멸위기지역

  • 65세 이상 인구가 19세에서 39세 여성 인구보다 두 배 이상 많은 지역

2) folium으로 지도 시각화 하기

  • ➀ 지역별 고유 id 부여하기
  • ➁ 지역 경계선 json 파일 얻기
  • ➂ 이에 대응하는 인구현황 데이터에 지역별 고유 id를 부여하기

3) 지역별 인구 데이터 얻기

국가통계포털

import pandas as pd
import numpy as np

import matplotlib.pyplot as plt
%matplotlib inline

# 한글폰트 적용
import matplotlib.font_manager as fm

font_location = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font_name = fm.FontProperties(fname=font_location).get_name()

from matplotlib import rc
rc('font', family=font_name)
population = pd.read_excel('./data_science/05. population_raw_data.xlsx', header=1)
population.head()
행정구역(동읍면)별(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 NaN NaN 남자인구수 (명) 25827594.0 1877127.0 1682988.0 1806754 2045265.0 1072395.0 806680.0 600607 319391.0 113221.0 32695.0 7658 4137.0
2 NaN NaN 여자인구수 (명) 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 NaN NaN 남자인구수 (명) 4876789.0 347534.0 372249.0 402358 410076.0 211568.0 163766.0 112076 54033.0 19595.0 6146.0 1900 1406.0
# Nan 부분을  채우기
population.fillna(
    method='pad',
    inplace=True
)
# column 제목 바꾸기 
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.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

4) 데이터 가공 - 인구소멸위기지역

  • 20 ~ 39세 여성 인구
  • 65세 이상 인구
  • pandas.pivot_table( data, index=[ '광역시도', '시도' ], columns=[ '항목' ], values=[ '인구수', '20-39세', '65세 이상' ] )
  • data.reset_index( inplace=True )
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()    
광역시도 시도 항목 인구수 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
pop = pd.pivot_table(
    population,
    index=['광역시도', '시도'],
    columns=['항목'],
    values=['인구수', '20-39세', '65세 이상']
)

pop.head()
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
pop['소멸비율'] = 2 * pop['20-39세', '여자'] / ( pop['65세 이상', '합계'] ) 
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
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
# coloumn title 다듬기
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)))
            ]

tmp_columns
['광역시도',
 '시도',
 '20-39세남자',
 '20-39세여자',
 '20-39세합계',
 '65세 이상남자',
 '65세 이상여자',
 '65세 이상합계',
 '인구수남자',
 '인구수여자',
 '인구수합계',
 '소멸비율',
 '소멸위기지역']
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
len(pop['시도'])
264

5) 시각화 전 처리 - 지역에 고유 id 부여하기

  • 시/군 개념을 유지하되, 광역시는 구로 나누고, 광역시 아닌 시 중 구를 가진 곳 또한 구로 단위를 나눈다.
  • 예) 서울 중구 / 서울 서초 / 통영 / 남양주 / 포항 북구 / 인천 남동 / 안양 만안 / 안산 단원
si_name = []
tmp_gu_list = ['장안구', '권선구', '팔달구', '영통구', 
               '수정구', '중원구', '분당구', 
               '만안구', '동안구',
                   '상록구', '단원구', 
               '덕양구', '일산동구', '일산서구', 
               '처인구', '기흥구', '수지구', 
               '상당구', '서원구', '흥덕구', '청원구', 
               '동남구', '서북구', 
               '완산구', '덕진구', 
               '남구', '북구', 
               '의창구', '성산구', '진해구', 
               '마산합포구', '마산회원구', 
               '오정구', '원미구', '소사구']

for n in pop.index:
    if pop['광역시도'][n][-3:] not in ['광역시', '특별시', '자치시']:
        if pop['시도'][n] in tmp_gu_list:
            #['장안구', '권선구', '팔달구', '영통구']:
            if pop['시도'][n] in tmp_gu_list[:4]:                
                si_name.append('수원' + ' ' + pop['시도'][n][:-1])
            
            #['수정구', '중원구', '분당구']:
            elif pop['시도'][n] in tmp_gu_list[4:7]:
                si_name.append('성남' + ' ' + pop['시도'][n][:-1])
                    
            #['만안구', '동안구']:
            elif pop['시도'][n] in tmp_gu_list[7:9]: 
                si_name.append('안양' + ' ' + pop['시도'][n][:-1])
                
            #['상록구', '단원구']:
            elif pop['시도'][n] in tmp_gu_list[9:11]: 
                si_name.append('안산' + ' ' + pop['시도'][n][:-1])

             #['덕양구', '일산동구', '일산서구']:
            elif pop['시도'][n] in tmp_gu_list[11:14]: 
                si_name.append('고양' + ' ' + pop['시도'][n][:-1])
    
            #['처인구', '기흥구', '수지구']:
            elif pop['시도'][n] in tmp_gu_list[14:17]: 
                si_name.append('용인' + ' ' + pop['시도'][n][:-1])
                
           #['상당구', '서원구', '흥덕구', '청원구']:
            elif pop['시도'][n] in tmp_gu_list[17:21]: 
                si_name.append('청주' + ' ' + pop['시도'][n][:-1])

            #['동남구', '서북구']:
            elif pop['시도'][n] in tmp_gu_list[21:23]: 
                si_name.append('천안' + ' ' + pop['시도'][n][:-1])
                  
            #['완산구', '덕진구']:
            elif pop['시도'][n] in tmp_gu_list[23:25]: 
                si_name.append('전주' + ' ' + pop['시도'][n][:-1])
                
            #['남구', '북구']:
            elif pop['시도'][n] in tmp_gu_list[25:27]: 
                si_name.append('포항' + ' ' + pop['시도'][n])
                
            #['의창구', '성산구', '진해구']:
            elif pop['시도'][n] in tmp_gu_list[27:30]: 
                si_name.append('창원' + ' ' + pop['시도'][n][:-1])
 
           #['마산합포구', '마산회원구']:
            elif pop['시도'][n] in tmp_gu_list[30:32]: 
                si_name.append('창원' + ' ' + pop['시도'][n][2:-1])
  
            #['소사구', '오정구', '원미구']:
            elif pop['시도'][n] in tmp_gu_list[32:35]: 
                si_name.append('부천' + ' ' + pop['시도'][n][:-1])
  
        else:
            if pop['시도'][n][:-1]=='고성' and pop['광역시도'][n]=='강원도':
                si_name.append('고성(강원)')
            elif pop['시도'][n][:-1]=='고성' and pop['광역시도'][n]=='경상남도':
                si_name.append('고성(경남)')
            else:
                si_name.append(pop['시도'][n][:-1])
 
    elif pop['광역시도'][n] == '세종특별자치시':
        si_name.append('세종')
  
    else:
        if len(pop['시도'][n])==2:
            si_name.append(pop['광역시도'][n][:2] + ' ' + pop['시도'][n])
        else:
            si_name.append(pop['광역시도'][n][:2] + ' ' + pop['시도'][n][:-1])  
len(si_name)
264
pop.tail()
광역시도 시도 20-39세남자 20-39세여자 20-39세합계 65세 이상남자 65세 이상여자 65세 이상합계 인구수남자 인구수여자 인구수합계 소멸비율 소멸위기지역
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
pop['ID'] = si_name

pop.head()
광역시도 시도 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 속초

6) Square Tile Grid Map 만들기

  • 각 행정구역의 화면상 좌표를 얻기위해 pivot_table의 반대개념으로 .stack() 명령을 사용한다
  • 그리고 인덱스를 재설정하고
  • 컬럼의 이름을 다시 설정한다.
draw_korea_raw = pd.read_excel(
    './data_science/05. draw_korea_raw.xlsx',
    encoding='EUC-KR'
)

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
# 행정구역상 좌표 얻기 :  .stack()

draw_korea_raw_stacked = pd.DataFrame( draw_korea_raw.stack() )

draw_korea_raw_stacked.head()
0
0 7 철원
8 화천
9 양구
10 고성(강원)
1 3 양주
# 인덱스 재설정

draw_korea_raw_stacked.reset_index( inplace=True )

draw_korea_raw_stacked.head()
level_0 level_1 0
0 0 7 철원
1 0 8 화천
2 0 9 양구
3 0 10 고성(강원)
4 1 3 양주
# column 이름 재설정

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

draw_korea = draw_korea_raw_stacked
draw_korea.head()
y x ID
0 0 7 철원
1 0 8 화천
2 0 9 양구
3 0 10 고성(강원)
4 1 3 양주
# 저장
draw_korea.to_csv('./data_ouput/05.draw_korea.csv', encoding='utf-8', sep=',' )
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:
        display_name = '{}\n{}'.format(row['ID'].split()[0], row['ID'].split()[1])
    elif row['ID'][:2] =='고성':
        display_name = '고성'
    else:
        display_name = row['ID']
        
    # 서대문구, 서귀포시 같이 이름이 3자 이상인 경우에 작은 글자로 표시한다
    if len(display_name.splitlines()[-1]) >= 3:
        fontsize, linespacing = 9.5, 1.5
    else:
        fontsize, linespacing = 11, 1.2
        
    plt.annotate(
        display_name, 
        (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.axis('off')
plt.tight_layout()
plt.show()

png

# 지역 ID 고유성 유지
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
    )

pop.head()    
광역시도 시도 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 속초
# pop와 draw_korea를 merge함
pop = pd.merge(
    pop, draw_korea,
    how='left',
    on=['ID']
)

pop.head()
광역시도 시도 20-39세남자 20-39세여자 20-39세합계 65세 이상남자 65세 이상여자 65세 이상합계 인구수남자 인구수여자 인구수합계 소멸비율 소멸위기지역 ID y x
0 강원도 강릉시 26286.0 23098.0 49384.0 15767.0 21912.0 37679.0 106231.0 107615.0 213846.0 1.226041 False 강릉 3 11
1 강원도 고성군 4494.0 2529.0 7023.0 2900.0 4251.0 7151.0 15899.0 14215.0 30114.0 0.707314 True 고성(강원) 0 10
2 강원도 동해시 11511.0 9753.0 21264.0 6392.0 8732.0 15124.0 47166.0 46131.0 93297.0 1.289738 False 동해 4 11
3 강원도 삼척시 8708.0 7115.0 15823.0 5892.0 8718.0 14610.0 35253.0 34346.0 69599.0 0.973990 True 삼척 5 11
4 강원도 속초시 9956.0 8752.0 18708.0 5139.0 7613.0 12752.0 40288.0 41505.0 81793.0 1.372647 False 속초 1 10
map_data = pop.pivot_table(
    index='y',
    columns='x',
    values='인구수합계'
)

masked_map_data = np.ma.masked_where(
    np.isnan(map_data),
    map_data
)

map_data
x 0 1 2 3 4 5 6 7 8 9 10 11 12 13
y
0 NaN NaN NaN NaN NaN NaN NaN 48013.0 26264.0 24010.0 30114.0 NaN NaN NaN
1 NaN NaN NaN 205513.0 98277.0 45907.0 154763.0 438457.0 32720.0 280707.0 81793.0 NaN NaN NaN
2 NaN NaN NaN 446233.0 292612.0 348220.0 567581.0 662154.0 70076.0 45991.0 27218.0 NaN NaN NaN
3 NaN NaN 430781.000000 300839.0 363443.0 327195.0 450355.0 62448.0 193763.0 211101.0 38718.0 213846.0 NaN NaN
4 NaN NaN 283793.333333 252353.0 339484.0 314194.0 152737.0 355069.0 411005.0 111367.0 47070.0 93297.0 NaN NaN
5 NaN 68010.0 283793.333333 345061.0 491476.0 379892.0 125249.0 299259.0 444168.0 111563.0 337979.0 69599.0 NaN NaN
6 NaN 510733.0 283793.333333 402888.0 595485.0 400997.0 230241.0 357215.0 657831.0 210359.0 43318.0 51738.0 NaN NaN
7 NaN 71014.0 330284.000000 375857.0 477739.0 506851.0 447192.0 237909.0 63778.0 327723.0 40073.0 39052.0 NaN NaN
8 NaN NaN 549716.000000 314002.0 370613.0 235386.0 567115.0 503830.0 232841.0 347833.0 74702.0 33539.0 NaN 10001.0
9 NaN 115249.0 417103.000000 640890.0 417551.0 284890.0 156763.0 340654.0 417163.0 226130.0 168798.0 17713.0 NaN NaN
10 21351.0 328627.0 530982.000000 208656.0 182896.0 358393.0 296479.0 136517.0 46166.0 109247.0 419891.0 26301.0 272027.0 NaN
11 63900.0 302929.0 258919.000000 359036.0 470832.0 97787.0 198515.0 30503.0 101799.0 142256.0 24171.0 54014.0 244748.0 NaN
12 NaN 166630.0 99971.000000 81339.0 109931.0 69950.0 208350.0 253563.0 38973.0 123199.0 100521.0 258037.0 259452.0 NaN
13 NaN 170788.0 103873.000000 32753.0 243048.0 192688.0 37308.0 190813.0 34221.0 34257.0 43564.0 45205.0 195285.0 NaN
14 NaN NaN 70187.000000 123213.0 42634.0 234959.0 173701.0 217120.0 440383.0 79712.0 447011.0 219255.0 174514.0 NaN
15 NaN NaN 56012.000000 54612.0 343222.0 252490.0 52267.0 50552.0 199507.0 156433.0 351352.0 242536.0 340714.0 NaN
16 NaN NaN 277551.000000 300479.0 491011.0 24949.0 63308.0 48026.0 591891.0 218268.0 244624.0 272745.0 158527.0 NaN
17 NaN NaN 57005.000000 87782.0 95480.0 23628.0 40241.0 63982.0 108354.0 310202.0 376526.0 207268.0 419853.0 NaN
18 NaN 60597.0 115173.000000 289899.0 26069.0 84188.0 346739.0 28111.0 108909.0 232800.0 89826.0 45208.0 NaN NaN
19 NaN 55618.0 46104.000000 361845.0 30197.0 36098.0 68937.0 317037.0 181797.0 112973.0 334603.0 278779.0 NaN NaN
20 NaN 34397.0 47229.000000 29949.0 27412.0 49622.0 252435.0 231571.0 187313.0 529422.0 126362.0 179324.0 NaN NaN
21 42652.0 82109.0 403049.000000 30400.0 65303.0 155580.0 114912.0 210791.0 138160.0 NaN NaN NaN NaN NaN
22 237739.0 104376.0 309579.000000 441066.0 278548.0 67656.0 45129.0 54703.0 257183.0 NaN NaN NaN NaN NaN
23 75121.0 57045.0 219729.000000 95791.0 288988.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN
24 32078.0 37753.0 40669.000000 44469.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
25 NaN NaN 52668.000000 NaN NaN 470665.0 NaN NaN NaN NaN NaN NaN NaN NaN
26 NaN NaN NaN NaN NaN 170932.0 NaN NaN NaN NaN NaN NaN NaN NaN
def draw_Korea(target_data, blocked_map, c_map_name):
    gamma = 0.75
    white_label_min = (
        max(blocked_map[target_data]) - min(blocked_map[target_data]) * 0.25 + min( blocked_map[target_data] )        
    )
    data_label = target_data
    vmin = min( blocked_map[target_data] )
    vmax = max( blocked_map[target_data] )
    
    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)], 
    ]
    
    map_data = blocked_map.pivot_table(
        index='y',
        columns='x',
        values=target_data
        )
    masked_map_data = np.ma.masked_where(
        np.isnan(map_data),
        map_data
        )
    plt.figure( figsize=(9, 11) )
    plt.pcolor(masked_map_data, vmin=vmin, vmax=vmax, cmap=c_map_name, edgecolor='#aaaaaa', linewidth=0.5 )
    
    # 지역 이름 표시
    for idx, row in blocked_map.iterrows():
        # 광역시는 구 이름이 겹치는 경우가 많아서 시단위 이름을 같이 표시한다. 
        if len( row['ID'].split() ) ==2:
            display_name = '{}\n{}'.format(row['ID'].split()[0], row['ID'].split()[1])
        elif row['ID'][:2] =='고성':
            display_name = '고성'
        else:
            display_name = row['ID']
        # 서대문구, 서귀포시 같이 이름이 3자 이상인 경우에 작은 글자로 표시한다
        if len(display_name.splitlines()[-1]) >= 3:
            fontsize, linespacing = 9.5, 1.5
        else:
            fontsize, linespacing = 11, 1.2
        annocolor = 'white' if row[target_data] > white_label_min else 'black'
        plt.annotate(
            display_name, 
            (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')
    plt.tight_layout()
    plt.show()
draw_Korea('인구수합계', pop, 'Blues')

png

pop['소멸위기지역'] = [ 1 if con else 0 for con in pop['소멸위기지역'] ]

draw_Korea('소멸위기지역', pop, 'Reds')

png

7) folium으로 Map을 그리기

(1) JSON 파일(2013년 자료)에서 반영되지 않은 것

  • 부천시의 구가 사라짐 - [행정구](https://ko.wikipedia.org/wiki/%EA%B5%AC_(%ED%96%89%EC%A0%95_%EA%B5%AC%EC%97%AD)
  • 2014년 생긴 청주 서원구는 반영되지 않음
pop[ pop['ID'] == '청주 서원' ]
광역시도 시도 20-39세남자 20-39세여자 20-39세합계 65세 이상남자 65세 이상여자 65세 이상합계 인구수남자 인구수여자 인구수합계 소멸비율 소멸위기지역 ID y x
242 충청북도 서원구 31810.0 29168.0 60978.0 9950.0 14237.0 24187.0 107866.0 109254.0 217120.0 2.411874 0 청주 서원 14 7
# 청주 서원 index 삭제
pop_folium = pop.drop( pop[ pop['ID'] == '청주 서원' ].index )
pop_folium = pop_folium.set_index('ID')
pop_folium.head()
광역시도 시도 20-39세남자 20-39세여자 20-39세합계 65세 이상남자 65세 이상여자 65세 이상합계 인구수남자 인구수여자 인구수합계 소멸비율 소멸위기지역 y x
ID
강릉 강원도 강릉시 26286.0 23098.0 49384.0 15767.0 21912.0 37679.0 106231.0 107615.0 213846.0 1.226041 0 3 11
고성(강원) 강원도 고성군 4494.0 2529.0 7023.0 2900.0 4251.0 7151.0 15899.0 14215.0 30114.0 0.707314 1 0 10
동해 강원도 동해시 11511.0 9753.0 21264.0 6392.0 8732.0 15124.0 47166.0 46131.0 93297.0 1.289738 0 4 11
삼척 강원도 삼척시 8708.0 7115.0 15823.0 5892.0 8718.0 14610.0 35253.0 34346.0 69599.0 0.973990 1 5 11
속초 강원도 속초시 9956.0 8752.0 18708.0 5139.0 7613.0 12752.0 40288.0 41505.0 81793.0 1.372647 0 1 10
import folium
import json
import warnings
warnings.simplefilter(
    action='ignore',
    category=FutureWarning
)
geo_path = './data_science/05. skorea_municipalities_geo_simple.json'
geo_string = json.load(
    open(geo_path, encoding='utf-8')
)

fmap = folium.Map(
    location=[36.2002, 127.054],
    zoom_start=7
)

fmap.choropleth(
    geo_data = geo_string,
    data=pop_folium['소멸위기지역'],
    columns = [ pop_folium.index, pop_folium['소멸위기지역'] ],
    fill_color = 'PuRd',
    key_on = 'feature.id'
)

fmap
# 파일 저장
draw_korea.to_csv("./data_output/05.draw_korea.csv", encoding='utf-8', sep=',')