- 국가 통계 포털에서 인구 정보 내려받기

http://kosis.kr/statHtml/statHtml.do?orgId=101&tblId=DT_1IN1509&

 

KOSIS

 

kosis.kr

 

기본코드

import pandas as pd
import numpy as np
import platform
import matplotlib.pyplot as plt

%matplotlib inline
path = "c:/Windows/Fonts/malgun.ttf"
from matplotlib import font_manager, rc
if platform.system() == 'Darwin':
    rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
    font_name = font_manager.FontProperties(fname=path).get_name()
    rc('font', family=font_name)
else:
    print('알 수 없는 시스템')    
plt.rcParams['axes.unicode_minus'] = False

 

 

 

 

1. 데이터 읽어오기

population = pd.read_excel('./data/population.xlsx', header=1)
population

 

 

 

 

2. 데이터 전처리

 

#결측치를 앞의 데이터로 채우기

population.fillna(method='ffill', inplace=True)

 

 

#컬럼이름 변경하기

population.rename(columns = {'행정구역별(시군구)(1)':'광역시도', 
                             '행정구역별(시군구)(2)':'시도', 
                             '합계':'인구수'}, inplace=True)
population.head()

 

 

# 소계를 제외한 데이터만 가져오기

population = population[(population['시도'] != '소계')]
population.head()

 

 

# 컬럼 이름 변경

population.is_copy = False
population.rename(columns = {'성별(1)':'구분'}, inplace=True)
population.loc[population['구분'] == '계', '구분'] = '합계'
population.head()

 

 

# 청년과 노년 구분

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세이상']

population.head(5)

 

 

# 피벗 테이블 생성

pop = pd.pivot_table(population, 
                     index = ['광역시도', '시도'], 
                     columns = ['구분'],
                     values = ['인구수', '20-39세', '65세이상'])
pop.head()

 

 

# 새로운 컬럼 추가 - 소멸 비율

pop['소멸비율'] = pop['20-39세','합계'] / (pop['65세이상','합계'] / 2)
pop.head()

 

 

# 새로운 컬럼 추가 - 소멸 위기 지역

pop['소멸위기지역'] = pop['소멸비율'] < 1.0
pop.head()
print(pop[pop['소멸위기지역']==True].index.get_level_values(1))
Index(['영월군', '남해군', '산청군', '의령군', '하동군', '함양군', '합천군', '군위군', '면부', '봉화군',
       '영덕군', '영양군', '의성군', '청도군', '청송군', '강화군', '면부', '강진군', '고흥군', '곡성군',
       '구례군', '면부', '보성군', '신안군', '완도군', '장흥군', '진도군', '함평군', '해남군', '고창군',
       '면부', '무주군', '부안군', '순창군', '임실군', '장수군', '진안군', '부여군', '서천군', '청양군',
       '괴산군', '단양군', '보은군'],
      dtype='object', name='시도')

 

 

# 인덱스 초기화

pop.reset_index(inplace=True) 
pop.head()

 

 

 

 

 

3. 시도 이름 합치기

# 새로운 컬럼 추가 - 첫번째와 두번째 컬럼 이름 합치기

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

 

 

# 데이터 확인

pop.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 303 entries, 0 to 302
Data columns (total 13 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   광역시도      303 non-null    object 
 1   시도        303 non-null    object 
 2   20-39세남자  303 non-null    int64  
 3   20-39세여자  303 non-null    int64  
 4   20-39세합계  303 non-null    int64  
 5   65세이상남자   303 non-null    int64  
 6   65세이상여자   303 non-null    int64  
 7   65세이상합계   303 non-null    int64  
 8   인구수남자     303 non-null    int64  
 9   인구수여자     303 non-null    int64  
 10  인구수합계     303 non-null    int64  
 11  소멸비율      303 non-null    float64
 12  소멸위기지역    303 non-null    bool   
dtypes: bool(1), float64(1), int64(9), object(2)
memory usage: 28.8+ KB

 

 

# 시도 고유값 확인

print(pop['시도'].unique())
['강릉시' '고성군' '동부' '동해시' '면부' '삼척시' '속초시' '양구군' '양양군' '영월군' '원주시' '읍부'
 '인제군' '정선군' '철원군' '춘천시' '태백시' '평창군' '홍천군' '화천군' '횡성군' '가평군' '고양시' '과천시'
 '광명시' '광주시' '구리시' '군포시' '권선구' '기흥구' '김포시' '남양주시' '단원구' '덕양구' '동두천시' '동안구'
 '만안구' '부천시' '분당구' '상록구' '성남시' '수원시' '수정구' '수지구' '시흥시' '안산시' '안성시' '안양시'
 '양주시' '양평군' '여주시' '연천군' '영통구' '오산시' '용인시' '의왕시' '의정부시' '이천시' '일산동구'
 '일산서구' '장안구' '중원구' '처인구' '파주시' '팔달구' '평택시' '포천시' '하남시' '화성시' '거제시' '거창군'
 '김해시' '남해군' '마산합포구' '마산회원구' '밀양시' '사천시' '산청군' '성산구' '양산시' '의령군' '의창구'
 '진주시' '진해구' '창녕군' '창원시' '통영시' '하동군' '함안군' '함양군' '합천군' '경산시' '경주시' '고령군'
 '구미시' '군위군' '김천시' '남구' '문경시' '봉화군' '북구' '상주시' '성주군' '안동시' '영덕군' '영양군'
 '영주시' '영천시' '예천군' '울릉군' '울진군' '의성군' '청도군' '청송군' '칠곡군' '포항시' '광산구' '동구'
 '서구' '달서구' '달성군' '수성구' '중구' '대덕구' '유성구' '강서구' '금정구' '기장군' '동래구' '부산진구'
 '사상구' '사하구' '수영구' '연제구' '영도구' '해운대구' '강남구' '강동구' '강북구' '관악구' '광진구' '구로구'
 '금천구' '노원구' '도봉구' '동대문구' '동작구' '마포구' '서대문구' '서초구' '성동구' '성북구' '송파구' '양천구'
 '영등포구' '용산구' '은평구' '종로구' '중랑구' '세종시' '울주군' '강화군' '계양구' '남동구' '미추홀구' '부평구'
 '연수구' '옹진군' '강진군' '고흥군' '곡성군' '광양시' '구례군' '나주시' '담양군' '목포시' '무안군' '보성군'
 '순천시' '신안군' '여수시' '영광군' '영암군' '완도군' '장성군' '장흥군' '진도군' '함평군' '해남군' '화순군'
 '고창군' '군산시' '김제시' '남원시' '덕진구' '무주군' '부안군' '순창군' '완산구' '완주군' '익산시' '임실군'
 '장수군' '전주시' '정읍시' '진안군' '서귀포시' '제주시' '계룡시' '공주시' '금산군' '논산시' '당진시' '동남구'
 '보령시' '부여군' '서북구' '서산시' '서천군' '아산시' '예산군' '천안시' '청양군' '태안군' '홍성군' '괴산군'
 '단양군' '보은군' '상당구' '서원구' '영동군' '옥천군' '음성군' '제천시' '증평군' '진천군' '청원구' '청주시'
 '충주시' '흥덕구']

 

 

# 시도 고유 이름

si_name = [None] * len(pop)

#광역시가 아닌 곳 중에서 구를 가지고 있는 시도들의 구이름 디셔너리 생성
tmp_gu_dict = {'수원':['장안구', '권선구', '팔달구', '영통구'], 
                       '성남':['수정구', '중원구', '분당구'], 
                       '안양':['만안구', '동안구'], 
                       '안산':['상록구', '단원구'], 
                       '고양':['덕양구', '일산동구', '일산서구'], 
                       '용인':['처인구', '기흥구', '수지구'], 
                       '청주':['상당구', '서원구', '흥덕구', '청원구'], 
                       '천안':['동남구', '서북구'], 
                       '전주':['완산구', '덕진구'], 
                       '포항':['남구', '북구'], 
                       '창원':['의창구', '성산구', '진해구', '마산합포구', '마산회원구'], 
                       '부천':['오정구', '원미구', '소사구']}

for n in pop.index:
    #고성이 2곳이므로 도를 추가
    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]
print(si_name)
['강릉', '고성(강원)', '동', '동해', '면', '삼척', '속초', '양구', '양양', '영월', '원주', '읍', '인제', '정선', '철원', '춘천', '태백', '평창', '홍천', '화천', '횡성', '가평', '고양', '과천', '광명', '광주', '구리', '군포', '수원 권선', '용인 기흥', '김포', '남양주', '안산 단원', '고양 덕양', '동두천', '동', '안양 동안', '안양 만안', '면', '부천', '성남 분당', '안산 상록', '성남', '수원', '성남 수정', '용인 수지', '시흥', '안산', '안성', '안양', '양주', '양평', '여주', '연천', '수원 영통', '오산', '용인', '읍', '의왕', '의정부', '이천', '고양 일산동', '고양 일산서', '수원 장안', '성남 중원', '용인 처인', '파주', '수원 팔달', '평택', '포천', '하남', '화성', '거제', '거창', '고성(경남)', '김해', '남해', '동', '창원 합포', '창원 회원', '면', '밀양', '사천', '산청', '창원 성산', '양산', '읍', '의령', '창원 의창', '진주', '창원 진해', '창녕', '창원', '통영', '하동', '함안', '함양', '합천', '경산', '경주', '고령', '구미', '군위', '김천', '포항 남구', '동', '면', '문경', '봉화', '포항 북구', '상주', '성주', '안동', '영덕', '영양', '영주', '영천', '예천', '울릉', '울진', '읍', '의성', '청도', '청송', '칠곡', '포항', '광주 광산', '광주 남구', '광주 동구', '광주 북구', '광주 서구', '대구 남구', '대구 달서', '대구 달성', '대구 동구', '대구 동부', '대구 면부', '대구 북구', '대구 서구', '대구 수성', '대구 읍부', '대구 중구', '대전 대덕', '대전 동구', '대전 서구', '대전 유성', '대전 중구', '부산 강서', '부산 금정', '부산 기장', '부산 남구', '부산 동구', '부산 동래', '부산 동부', '부산 면부', '부산 부산진', '부산 북구', '부산 사상', '부산 사하', '부산 서구', '부산 수영', '부산 연제', '부산 영도', '부산 읍부', '부산 중구', '부산 해운대', '서울 강남', '서울 강동', '서울 강북', '서울 강서', '서울 관악', '서울 광진', '서울 구로', '서울 금천', '서울 노원', '서울 도봉', '서울 동대문', '서울 동작', '서울 마포', '서울 서대문', '서울 서초', '서울 성동', '서울 성북', '서울 송파', '서울 양천', '서울 영등포', '서울 용산', '서울 은평', '서울 종로', '서울 중구', '서울 중랑', '세종', '세종', '세종', '세종', '울산 남구', '울산 동구', '울산 동부', '울산 면부', '울산 북구', '울산 울주', '울산 읍부', '울산 중구', '인천 강화', '인천 계양', '인천 남동', '인천 동구', '인천 동부', '인천 면부', '인천 미추홀', '인천 부평', '인천 서구', '인천 연수', '인천 옹진', '인천 읍부', '인천 중구', '강진', '고흥', '곡성', '광양', '구례', '나주', '담양', '동', '면', '목포', '무안', '보성', '순천', '신안', '여수', '영광', '영암', '완도', '읍', '장성', '장흥', '진도', '함평', '해남', '화순', '고창', '군산', '김제', '남원', '전주 덕진', '동', '면', '무주', '부안', '순창', '전주 완산', '완주', '읍', '익산', '임실', '장수', '전주', '정읍', '진안', '동', '면', '서귀포', '읍', '제주', '계룡', '공주', '금산', '논산', '당진', '천안 동남', '동', '면', '보령', '부여', '천안 서북', '서산', '서천', '아산', '예산', '읍', '천안', '청양', '태안', '홍성', '괴산', '단양', '동', '면', '보은', '청주 상당', '청주 서원', '영동', '옥천', '음성', '읍', '제천', '증평', '진천', '청주 청원', '청주', '충주', '청주 흥덕']

 

 

#도시 이름을 DataFrame에 추가하고 불필요한 데이터 삭제

pop['ID'] = si_name

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

pop.head()

 

 

 

 

3. Cartogram을 위한 엑셀 파일 

 

 

# 엑셀 파일 읽기

draw_korea_raw = pd.read_excel('./data/draw_korea_raw.xlsx')
draw_korea_raw

 

 

 

 

 

 

4. stack으로 묶기

# 컬럼 이름을 인덱스로 만들기

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

 

 

# 인덱스 초기화

draw_korea_raw_stacked.reset_index(inplace=True)
draw_korea_raw_stacked

 

 

# 컬럼 이름 변경

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

 

 

 

 

 

5. cartogram의 기본이 되는 지도 그리기

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_raw_stacked.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']

 

 

# 시도 경계 그리기

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

#y축의 위아래 변경
plt.gca().invert_yaxis()
#plt.gca().set_aspect(1)

#축과 라벨 제거
plt.axis('off')

#자동 레이아웃 설정
plt.tight_layout()
plt.show()

 

 

 

# draw_korea_raw_stacked와 pop의 도시이름 일치시키기

#draw_korea_raw_stacked 와 pop 의 도시이름 비교
print(set(draw_korea_raw_stacked['ID'].unique()) - set(pop['ID'].unique()))
print(set(pop['ID'].unique()) - set(draw_korea_raw_stacked['ID'].unique()))

#일치하지 않는 데이터 삭제
tmp_list = list(set(pop['ID'].unique()) - set(draw_korea_raw_stacked['ID'].unique()))

for tmp in tmp_list:
    pop = pop.drop(pop[pop['ID']==tmp].index)
                       
print(set(pop['ID'].unique()) - set(draw_korea_raw_stacked['ID'].unique()))
{'부천 원미', '부천 오정', '부천 소사', '인천 남구'}
{'포항', '부산 면부', '대구 읍부', '성남', '용인', '인천 면부', '대구 동부', '인천 미추홀', '인천 동부', '안산', '부산 동부', '수원', '창원', '울산 면부', '읍', '인천 읍부', '부산 읍부', '고양', '천안', '동', '청주', '울산 읍부', '대구 면부', '안양', '전주', '면', '울산 동부', '부천'}
set()
pop = pd.merge(pop, draw_korea_raw_stacked, how='left', on=['ID'])
pop.head()

 

 

# 좌표와 인구수 이용해서 피벗 테이블 생성

mapdata = pop.pivot_table(index='y', columns='x', values='인구수합계')
mapdata

 

 

# Nan이 아닌 데이터 확인




 

 

 

 

 

 

6. Cartogram 그리기

# DataFrame과 컬럼 이름 및 색상 값을 받아서 Cartogram을 그려주는 함수

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])
    
    #x 와 y를 가지고 피봇 테이블 만들기
    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()

 

 

 

# 인구수 합계로 Catrogram 그리기

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

 

 

#소멸위기지역으로 Cartogram 그리기

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

 

 

# 여성 비율로 Catrogram 그리기

pop['여성비'] = (pop['인구수여자']/pop['인구수합계'])*100
drawKorea('여성비', pop, 'RdBu')

 

 

# 청년 층의 여성 비율을 이용한 Cartogram 그리기

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

 

 

# 지역 이름을 index로 설정

import folium
import json
import warnings

warnings.simplefilter(action='ignore', category=FutureWarning)
pop_folium = pop.set_index('ID')
pop_folium.head()

 

 

#인구 수 합계를 이용한 단계구분도

#한국 지도 시도 경계 좌표를 가진 파일을 가져오기
geo_path = './data/korea_geo_simple.json'
geo_str = json.load(open(geo_path, encoding='utf-8'))

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

map
map = folium.Map(location=[36.2002, 127.054], zoom_start=7)
folium.Choropleth(geo_data = geo_str,
               data = pop_folium['소멸위기지역'],
               columns = [pop_folium.index, pop_folium['소멸위기지역']],
               fill_color = 'YlOrRd',
               key_on = 'feature.id').add_to(map)

map