EDA - 인구 분석 : 데이터 취업 스쿨 스터디 노트 12/13

slocat·2023년 12월 13일
0

start-data

목록 보기
39/75

1. 데이터 정리

population = pd.read_excel("../data/07_population_raw_data.xlsx", header=1)

# nan 값을 이전 데이터로 채우기
population.fillna(method="pad", inplace=True)
# 컬럼명 변경
population.rename(
    columns={"행정구역(동읍면)별(1)": "광역시도",
             "행정구역(동읍면)별(2)": "시도",
             "계": "인구수"},
    inplace=True
)
# 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+"]
)
# 피벗 테이블 적용
pop = population.pivot_table(
    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)

>>>
Index(['고성군', '삼척시', '양양군', '영월군', '정선군', '평창군', ...
# 인덱스 재설정
pop.reset_index(inplace=True)
# 컬럼명 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)))
]
pop.columns = tmp_columns

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

  • 도는 고유 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] = "고성(경남)"
# ID 컬럼을 만들어서 si_name을 넣어줌
pop["ID"] = si_name
# 불필요한 컬럼 삭제
del pop["20-39세남자"]
del pop["65세이상남자"]
del pop["65세이상여자"]

3. 지도 준비, 데이터프레임 병합

# 교수님이 한땀 한땀 만드신 자료 가져오기
draw_korea_raw = pd.read_excel("경로")
# stack() : pivot_table()의 반대라고 생각
# 각 지역별 위치(좌표)가 나타남
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

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"]
        
        # 자치구의 이름이 3글자 이상이면
        # splitlines() : 문자열을 줄바꿈 기준으로 쪼개기
        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,
            ha="center",
            va="center",
            linespacing=linespacing
        )
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)
    
    # matplotlib에서 y는 밑에서 위로 증가하나 excel은 반대라서 바꿔줌
    plt.gca().invert_yaxis()
    plt.axis("off")
    plt.tight_layout()
    plt.show()
    
simpleDraw(draw_korea)

⭐파이썬 내장함수 zip()

number = [1, 2, 3]
alphabet = ["A", "B", "C"]

for pair in zip(number, alphabet):
    print(pair, type(pair))
    
>>>
(1, 'A') <class 'tuple'>
(2, 'B') <class 'tuple'>
(3, 'C') <class 'tuple'>

# set() : 집합
# 차집합은 교환법칙이 성립하지 않는다.
set(draw_korea["ID"].unique()) - set(pop["ID"].unique())

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

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

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)

ID를 기준으로 pop와 draw_korea를 합쳐준다.

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

4. 카르토그램 시각화

4-1. 색상 설정하는 함수

# blockedMap = 인구 현황(pop), targetData = 그리고 싶은 컬럼
# 색상을 만들 때 "최소값"을 흰색으로 하는 함수
def get_data_info(targetData, blockedMap):
    # 바탕이 흰색일 때는 검정색 글씨를 사용해야함
    # 그 경계선을 지정하는 것 = whitelabelmin
    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
# 색상을 만들 때 "중간값"을 흰색으로 하는 함수
# 음수, 양수 값이 있어서 0을 센터에 두고 싶을 때
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

4-2. 지도 그리기

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"]
        
        # 자치구의 이름이 3글자 이상이면
        # splitlines() : 문자열을 줄바꿈 기준으로 쪼개기
        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,
            ha="center",
            va="center",
            linespacing=linespacing
        )
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"]
        
        # 자치구의 이름이 3글자 이상이면
        # splitlines() : 문자열을 줄바꿈 기준으로 쪼개기
        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,
            ha="center",
            va="center",
            linespacing=linespacing
        )
drawKorea("인구수합계", pop, "Blues")
drawKorea("인구수합계", pop, "Blues", True)

>>>

교수님의 해석이 기억에 남는 코드...😂

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

5. folium 시각화

import folium
import json
pop_folium = pop.set_index("ID")
geo_path = "경로"
geo_str = json.load(open(geo_path, encoding="utf-8"))

my_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="PuRd",
    key_on="feature.id"
).add_to(my_map)

my_map

0개의 댓글