[0205] 인구 소멸 위기 지역 파악하기

이아연·2024년 2월 4일
0

1. 배경
2. 데이터 읽고 인구 소멸 지역 계산하기
3. 지도 시각화를 위한 지역별 ID 만들기
4. 지도 그리기(카르토그램)

1. 배경

  1. 인구 소멸 위기 지역 파악
  2. 인구 소멸 위기 지역의 지도 표현
  3. 지도 표현에 대한 카르토그램 표현

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

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

warnings.filterwarnings(action='ignore')
%matplotlib inline
population = pd.read_excel('../data/07_population_raw_data.xlsx', header=1)
population

<엑셀파일>

빈 값 채우기(fillna)

method='pad'/'ffill' : 앞에 있는 데이터 셋으로 채우기
method='backfill'/'bfill' : 뒤에 있는 데이터 셋으로 채우기

population = pd.read_excel('../data/07_population_raw_data.xlsx', header=1)
population.fillna(method='pad', inplace=True)
population

header = 1 인덱스 1부터 읽기
.fillna(method='pad', inplace=True) : 앞에 있는 값으로 Nan값 채우기

컬럼 이름 변경

population.rename(
    columns={'행정구역(동읍면)별(1)':'광역시도',
             '행정구역(동읍면)별(2)':'시도',
             '계':'인구수'
            }, inplace=True
)
population.tail()

소계 컬럼 제거

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

컬럼명 변경(항목)
.is_copy : copy했을 때 warning X

population.is_copy=False 

population.rename(
    columns={
        '항목':'구분'
    }, inplace=True
)
population.head()

컬럼명 변경(합계, 남자, 여자)

population.loc[population['구분']=='합계','구분']

<결과>
6 합계
9 합계
12 합계
15 합계
18 합계
..
828 합계
831 합계
834 합계
840 합계
843 합계

population.loc[population['구분']=='총인구수 (명)', '구분'] = '합계'
population.loc[population['구분']=='남자인구수 (명)', '구분'] = '남자'
population.loc[population['구분']=='여자인구수 (명)', '구분'] = '여자'

소멸지역 조사하기 위한 데이터
20-39세 / 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+'] 
)

pivot_table

pd.pivot_table(data=,index=,columns=,values=)

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

소멸 비율 계산
소멸 비율 정의 : 20-39세 여자 / 65세 이상 전체 인구 비율의 절반

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

소멸 위기 지역 컬럼 생성

pop['소멸위기지역'] = pop['소멸비율'] < 1.0
pop

소멸 위기 지역 조회
.get_level_values() : MultiIndex에서 특정 레벨의 값을 가져오기 위해 사용

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

인덱스 재설정

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


컬럼[0], 컬럼[1] 합치기

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 

pop 컬럼으로 설정

pop.columns = tmp_columns

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

si_name 리스트 만들기

si_name=[None]*len(pop)
si_name

gu_list딕셔너리 만들기
시도 데이터 안에 시/구 모두 들어있어 구분하기 위함

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

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

  1. 광역시도 값의 끝 3자리가 [광역시, 특별시, 자치시]가 아니면, 시도의 값에서 마지막 글자(시, 구)빼기
  2. 광역시도가 세종특별자치시이면, 세종 으로 reset
  3. 끝 3자리가 광역시, 특별시, 자치이면서,
    (1) 시도의 글자수가 2개이면, 광역시도 앞 두자리+시도
    (2) 시도의 글자수가 2개가 아니면, 광역시도 앞 두자리+시도의 마지막 한 글자 빼기
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) 행정구

  1. 광역시도 값의 끝 3자리가 [광역시, 특별시, 자치시]가 아니면,
    tmp_gu_dict 안에 있는 지역이면서, 시도가 두 글자이면,
    tmp_gu_dict[key]+시도
  2. 마산합포구이거나 마산회원구이면 합포, 회원만 추출
  3. 모두 해당 안되면 시도의 마지막 글자(시, 구)빼고 추출
for idx, row in pop.iterrows():
    if row['광역시도'][-3:] not in ['광역시','특별시','자치시']:
        for keys, values in tmp_gu_dict.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] = '고성(경남)'

'ID'컬럼 생성 후 si_name으로 설정

필요한 컬럼만 남겨놓기

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

데이터프레임으로 표현

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

인덱스 재설정 후, 컬럼 y축, x축으로 표현

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

이름 재설성 후, 경계선 좌표 리스트에 담기

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

그래프 위의 텍스트 설정

ID가 두 글자이면, 두 줄로 표현
ID가 고성이면 고성으로 표현
모두 해당 안되면 ID로 표현
만약 dispname이 세글자 이상이면 폰트 작게 표현
plt.annotate(입력글자, 위치, 굵기, 폰트사이즈, 줄간격, 수평/수직 설정)

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

그래프 그리기 함수

zip() : 두개의 리스트 묶어주는 기능
plot_text_simpole함수로 글자 입력
Border_lines의 x축과 y축으로 plot그래프그리기
plt.gca().invert_yaxis() : y값 거꾸로
그 외 축숨기기,줄간격 설정

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() # y값 거꾸로
    plt.axis('off')
    plt.tight_layout()
    plt.show()

pop, draw_korea데이터 합치기- 검증작업

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()))
pop = pd.merge(pop, draw_korea, how='left', on='ID')
pop.head()

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

  • 색상을 만들 때, 최소값을 흰색
  • blockedMap : 인구현황(pop)
  • targetData: 그리고 싶은 컬럼

1. mapdata, vmax, vmin, whitelabelmin 반환하는 함수
whitelabelmin = 최댓값 - 최솟값의 1/4지점 + 최솟값

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

2. zero_center함수 절댓값을 기준으로,,,가운데로 몰아주는 함수,,?사실 이해를 못했다.

import numpy as np
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

3. 텍스트 표시 함수
plot_text_simple함수에서 글자 색상 설정 추가
whitelablemin보다 절댓값이 크면 흰색으로, 작으면 검정색으로 표현

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

4. 그래프 그리는 함수
zeroCenter==True이면, get_data_info_for_zero_center 적용
False이면 get_data_info적용
plt.pcolor 설정(data,vmin=,vmax=,cmap=,edgecolor=,linewidth=,etc)

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() # y값 거꾸로
    plt.axis('off')
    plt.tight_layout()
    cb = plt.colorbar(shrink=0.1, aspect=10)
    cb.set_label(targetData)
    plt.show()

인구수합계 그래프 그리기

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


인구소멸위기지역 그래프 그리기

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

여성비율 그래프

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

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

folium 시각화

import folium
import json

pop_folium = pop.set_index('ID')
pop_folium.head()

인구수합계 지도 시각화

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.15)
folium.Choropleth(
    geo_data = geo_str,
    data = pop_folium['인구수합계'],
    key_on='feature.id',
    columns=[pop_folium.index, pop_folium['인구수합계']],
    fill_color='YlGnBu'
).add_to(mymap)
mymap

소멸위기지역 시각화

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

자료 출처: 제로베이스 데이터 취업 스쿨

profile
Hi Welcome

0개의 댓글