인구 분석

YJ·2023년 4월 7일
0

▷ 오늘 학습 계획: EDA 강의(인구분석)

01_목표

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

02_데이터 읽고 인구 소멸 지역 계산하기

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

from matplotlib import rc
rc("font", family="Malgun Gothic")

import warnings
warnings.filterwarnings(action="ignore")

%matplotlib inline

population = pd.read_excel("../data/07_population_raw_data.xlsx", header =1)
population.fillna(method="pad", inplace=True)  #Nan값 없애기

fillna()

1) df
2) df.fillna(value=0)
3) df.fillna(method="ffill", axis=0)
4) df.fillna(method="ffill", axis=1)
5) df.fillna(method="backfill", axis=0)
6) df.fillna(method="backfill", axis=1)

  • method : {'backfill', 'bfill', 'pad', 'ffill', None}, default None
    Method to use for filling holes in reindexed Series
    pad / ffill: propagate last valid observation forward to next valid
    backfill / bfill: use next valid observation to fill gap.
# 컬럼 이름 변경
population.rename(
    columns={
        "행정구역(동읍면)별(1)" : "광역시도",
        "행정구역(동읍면)별(2)" : "시도",
        "계" : "인구수",
        "항목" : "구분"
    }, inplace = True
)

# 소계 제거
population = population[population["시도"] != "소계"]

#copy 했을 때 warning 나오지 않게 설정
population.is_copy = False

# 구분 컬럼의 "총인구수 (명)" → "합계"로 변경
population.loc[population["구분"] == "총인구수 (명)", "구분"] = "합계"
population.loc[population["구분"] == "남자인구수 (명)", "구분"] = "남자"
population.loc[population["구분"] == "여자인구수 (명)", "구분"] = "여자"

# 소멸지역을 조사하기 위한 데이터 정리
```python
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
# index만 설정하게 되면 평균값이 기본으로 들어가게 됨
pop = pd.pivot_table(
    population,
    index = ["광역시도", "시도"],
    columns = ["구분"],
    values = ["인구수", "20-39세", "65세이상"]
)

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

# 소멸위기지역 컬럼 생성
pop["소멸위기지역"] = pop["소멸비율"] < 1.0

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

# 인덱스 재정렬
pop.reset_index(inplace=True)

# multi-index 정리
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

03_지도 시각화를 위한 지역별 ID 만들기

si_name = [None] * len(pop)

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

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

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]

행정구 정리

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]

고성군

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

04_지도 시각화

#엑셀에서 그린 지도모양 읽어오기
import pandas as pd
draw_korea_raw = pd.read_excel("../data/07_draw_korea_raw.xlsx")

#각 지역별 위치
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 = draw_korea_raw_stacked

pop, draw_korea DataFrame merge

# set(집합)으로 차집합 구하기
set(draw_korea["ID"].unique()) - set(pop["ID"].unique())
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()))

pop = pd.merge(pop, draw_korea, how="left", on="ID")

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

  • 색상을 만들 때, 최솟값을 흰색
  • 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()
#1
drawKorea("인구수합계", pop, "Blues")
#2
pop["소멸위기지역"] = [1 if con else 0 for con in pop["소멸위기지역"]]
drawKorea("소멸위기지역", pop, "Reds")
#3
pop["여성비"] = (pop["인구수여자"]/pop["인구수합계"]-0.5) *100
drawKorea("여성비", pop, "RdBu", zeroCenter=True)
#4
pop["2030여성비"] = (pop["20-39세여자"]/pop["20-39세합계"]-0.5) *100
drawKorea("2030여성비", pop, "RdBu", zeroCenter=True)
import folium
import json

pop_folium = pop.set_index('ID')

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

📝 인구분석 프로젝트는 코드가 길어져서 흐름을 놓칠뻔했다. EDA 전체 강의는 생각보다 빨리 끝났는데 내가 과연 프로젝트를 맡으면 흐름을 잃지 않고 잘 해낼 수 있을까 라는 생각이 들었다. 아직 부족한게 많으니까 하나씩 알아가면서 공부해야겠다.

▷ 내일 학습 계획: EDA 학습과제

[이 글은 제로베이스 데이터 취업 스쿨의 강의 자료 일부를 발췌하여 작성되었습니다.]

0개의 댓글