2018년 10월 13일 토요일

대한민국 지도에 인구수, 세대수 표현하기 - 시도, 시군구, 행정동, 법정동 코드를 활용한 데이터 매핑 과정

웹사이트에 대한민국 지도(시도, 시군구, 법정동)를 표현하고 각각 권역에 데이터를 입혀야하는 사람들에게 도움이 되기를 바라며 포스팅한다.

필자도 필요해서 집에서 며칠동안 연구해본 결과 전처리 과정이 꽤나 난감했다. 사실 데이터를 엮어서 표현하기 위해 가장 중요한 것은 기준인데 우리나라는 주소체계가 참 복잡하다. 신우편번호, 구우편번호, 행정동기준, 법정동기준 등 여러 기준이 있고 (게다가 행정구역이 통폐합이 된다.) 각 데이터가 여러 곳에서 독립적으로 제공하기때문에 데이터를 이어붙이기가 참 애매하다.

그나마 행정안전부에서 제공하는 것들끼리는 기준이 같기 때문에 행정동-법정동 매핑 데이터와 주민등록기준 세대수, 인구수 데이터를 붙일 수가 있었다. (하지만 어느정도 전처리 과정을 통해 가공이 필요하다.)

필자가 사용한 행정안전부 데이터이다.
링크바로가기 - https://drive.google.com/open?id=1_eBNYuvm6EVWFnQ2fPpebNUzCjCDXa0C

먼저 행정안전부>업무안내>지방자치분권실>주민등록 및 인감>주민등록 및 인감에서
시도, 시군구, 행정동/법정동 코드 기반의 데이터를 다운받는다.


링크 : http://www.mois.go.kr/frt/bbs/type001/commonSelectBoardList.do?bbsId=BBSMSTR_000000000052



여기에서 KIKmix.YYYYMMDD를 다운받아보면 행정동과 법정동(리 단위까지)을 매핑해놓았다. 이를 법정동 기준으로 사용하기 위해서는 조금 가공해야한다.

먼저 우리나라 행정구역코드는 다음과 같은 규칙으로 이루어져있다.
총 10자리이며 시도2자리+시군구2자리+읍면동4자리+리2자리인 것 같다. (아니면 참 난감해진다.) 따라서 법정동 기준으로 하기 위해서 5열의 법정동 코드 끝 2자리를 모두 '00'으로 치환한 후 중복을 제거하면 된다.

대한민국 법정동 코드를 조회할 수 있는 링크이다.
https://www.code.go.kr/index.do

위의 링크에 몇가지 케이스를 검증해보면 '%)', '%리'가 존재하고 이를 REPLACE(법정동,행정동)으로 치환해주도록 했다.

위의 과정을 처리하는 파이썬 코드는 다음과 같다.

import urllib.request
import time
from openpyxl import load_workbook
import os

try:
    if not(os.path.isdir("map_json")):
        os.makedirs(os.path.join("map_json"))
except OSError as e:
    print("Failed to create directory!!!!!")
    raise

angelEx=load_workbook(filename='KIKmix.20181008.xlsx')
sheet = angelEx.active
arr = []

for i in sheet.iter_rows(min_row=2):
    arr.append(i[4].value[0:8]+'00')
    
arr2 = list(set(arr))

"""
쉐이프 api 호출 부분
"""

5번째 열을 읽어서 끝 2자리를 모두 '00'으로 치환 후 배열에 넣고 중복을 제거했다.
이렇게 유니크한 법정동 코드를 구했으니 이를 이용하여 api나 크롤링을 하여 주소나 지도 쉐이프를 구하면 된다.

대략 글을 쓰는 기준으로 법정동코드는 5338개가 존재한다. 같은 파일에서 유니크한 행정동 코드는 3858개가 존재했다. 따라서 행정동-법정동이 M:N 관계라고 할 지라도 행정동 코드 1개에 법정동 코드가 N개가 매핑될 가능성이 더 높다는 것이다.
파일 전체를 db에 올리고 이에 따른 5338개에 매핑되는 행정동 코드도 남겨놓도록 하자.
다행히 행정동 코드가 더 적기때문에 이 과정은 문제가 없을 것이고 행정동 코드가 있어야 인구수와 세대수를 붙일 수 있기때문이다.




이제 이 법정동 코드에 세대수와 인구수를 붙이는 방법이다.
먼저 행정동 기준 세대수와 인구수 데이터를 구해야한다.

데이터를 구할 수 있는 행정안전부 주소는 다음과 같다.

링크 : http://www.mois.go.kr/frt/sub/a05/totStat/screen.do

이 곳에서 인구수와 세대수 파일을 다운 받아서 필요한 데이터만 병합하였다.
구글 드라이브에 올려놓은 ingu_sedae_merge.csv 파일이다. 병합하는 과정은 코드로 짜는 것보다 수기로 하는 것이 빠르다.
(참고로 인구수는 생산가능 인구(만15~64세)수의 총합이다.)

저 파일을 열어보면 컬럼 하나에 "서울특별시 강북구 수유제1동(1130561000)"이 모두 들어가있다. 따라서 이 파일에서 행정코드, 시도, 시군구, 읍면동을 분리하는 작업이 필요하다.

코드는 다음과 같다.
import pandas as pd
from pandas import DataFrame, Series

csv = pd.read_csv("ingu_sedae_merge.csv", encoding = 'euc-kr')
columns = ['HJ_CODE','SD','SSG','EMD','SPL_POP_CNT','GEN_CNT']
df = DataFrame(columns=columns)

hjgg_code = ''
sd = ''
sgg = ''
emd = ''
spl_pop_cnt = ''
gen_cnt = ''
a = ''
b = ''
c = ''

for i, row in csv.iterrows():

    if(len(row.iloc[0].split('(')[0].split(' ')) == 3):
        hjgg_code = row.iloc[0].split('(')[1].split(')')[0]
        sd = row.iloc[0].split('(')[0].split(' ')[0]
        sgg = row.iloc[0].split('(')[0].split(' ')[1]
        emd = row.iloc[0].split('(')[0].split(' ')[2]
        spl_pop_cnt = row.iloc[1]
        gen_cnt = row.iloc[2]
        
        df = df.append(Series([hjgg_code,sd,sgg,emd,spl_pop_cnt,gen_cnt]
        ,index=columns), ignore_index=True)

        print(hjgg_code)
        print(sd)
        print(sgg)
        print(emd)
        print(spl_pop_cnt)
        print(gen_cnt)
    
    if(len(row.iloc[0].split('(')[0].split(' ')) == 4):
        hjgg_code = row.iloc[0].split('(')[1].split(')')[0]
        sd = row.iloc[0].split('(')[0].split(' ')[0]
        b = row.iloc[0].split('(')[0].split(' ')[1]
        c = row.iloc[0].split('(')[0].split(' ')[2]
        sgg = b+' '+c
        emd = row.iloc[0].split('(')[0].split(' ')[3]
        spl_pop_cnt = row.iloc[1]
        gen_cnt = row.iloc[2]
        
        print(hjgg_code)
        print(sd)
        print(sgg)
        print(emd)
        print(spl_pop_cnt)
        print(gen_cnt)

        df = df.append(Series([hjgg_code,sd,sgg,emd,spl_pop_cnt,gen_cnt] ,index=columns), ignore_index=True)
        
print("완료")
df.to_csv(r'result.csv',index=False,encoding='euc-kr')


결과는 다음과 같다.



이제 위에서 구한 행정동-법정동 매핑 데이터(+@ map polygon 등)와 아래에서 구한 인구, 세대 데이터를 매핑할 수 있하여 법정동 기준으로 지도를 표현할 수 있게 되었다.
추가적으로 엑셀 파일들을 한번에 merge하는 코드는 시간이 나면 작업하도록 하겠다.
하지만 수동으로 작업하는 것이 빠르기 때문에 안했다.


정리하면 다음과 같다.
1. 법정동으로 지도와 인구수, 세대수를 표현하고싶다.
2. 행정동-법정동 매핑 데이터를 구한다.
3. 행정동 기반 인구수, 세대수 데이터를 구해서 2번의 법정동에 매핑한다.
(M:N관계로 인한 1/N + SUM 과정이 필요)
4. 2번 법정동 기준으로 MAP POLYGON(MAP SHAPE)를 구해서 지도를 그리고 데이터를 입힌다.



댓글 2개:

  1. 안녕하세요~좋은 정보 감사합니다..몇가지 궁금한게 있어서 예를 들어 행정동의 인구가 50만이고 거기에 포함된 법정동이 5개일경우
    각 법정동에 맵핑 자료를 입힐 수 없는데 그럴경우는 어떻게 하나요?
    지도 기반으로 대략적인 시각화는 가능할것 같은데 정확한 정보는 얻기 어렵던데.. 관련 자료가 있을까요?

    답글삭제
    답글
    1. 가장 좋은 방법은 법정동별 인구를 구해서 입히는 것이겠지만 불가피하게 행정동 인구를 가지고 법정동 인구를 추측해야한다면 저처럼 N/1 하는 것도 한가지 방법일 것입니다. 말씀하신데로 행정동 인구가 50만인데 그 안에 법정동이 5개라면 각 법정동 당 10만씩 분배해주는 방법이겠지요.. 이렇게 하기 위해서는 행정동-법정동 매핑 데이터가 필요한데 그 자료는 위 블로그에서 소개한 것처럼 행정안전부 사이트에서(http://www.mois.go.kr/frt/bbs/type001/commonSelectBoardList.do?bbsId=BBSMSTR_000000000052)에서 CSV 자료를 받아 활용하였습니다.
      그리고 시각화 부분은 대한민국 우편번호별 쉐이프 구해서 Shape 합성 및 DB에 json 형태로 저장하기(https://parksuseong.blogspot.com/2018/12/shape-db-json.html) 포스팅을 참고해주세요. 결국 저같은 경우에는 우편번호별로 들어오는 데이터와 행정동 인구를 법정동 지도(폴리곤)에 입히기 위해 우편번호-법정동, 행정동-법정동 관계 데이터를 구해서 전처리 작업을 했습니다.

      삭제

2022년 회고

 올해는 블로그 포스팅을 열심히 못했다. 개인적으로 지금까지 경험했던 내용들을 리마인드하자는 마인드로 한해를 보낸 것 같다.  대부분의 시간을 MLOps pipeline 구축하고 대부분을 최적화 하는데 시간을 많이 할애했다. 결국에는 MLops도 데이...