파이썬으로 주민등록인구 데이터 조회하기
Python 라이브러리 PublicDataReader를 이용하면 최신 지역별 주민등록인구 데이터를 쉽게 가져올 수 있다. 해당 데이터는 국가통계포털(KOSIS) 웹 사이트에 접속해 직접 조회할 수도 있지만, KOSIS 공유서비스 Open API를 이용해서도 조회할 수 있다. PublicDataReader는 KOSIS 공유서비스 Open API의 데이터 조회 기능을 포함하고 있어, 이를 활용하면 원하는 데이터를 쉽게 찾고 조회할 수 있다. KOSIS 공유서비스 Open API 신청 방법과 PublicDataReader에서 제공하는 KOSIS 공유서비스 Open API의 모든 기능을 살펴보려면 Python으로 KOSIS 데이터 조회하기를 참고하면 된다. 여기서는 주민등록인구 데이터를 조회하는 방법을 다루기로 한다.
설치하기
- 운영체제(OS)에 따라 아래 중 하나를 선택한다.
- Windows: CMD(명령 프롬프트) 실행
- Mac: Terminal(터미널) 실행
아래 Shell 명령어를 입력 후 실행한다.
pip install PublicDataReader --upgrade
라이브러리 임포트하기
앞에서 설치한 PublicDataReader에서 Kosis 클래스를 임포트한다. KOSIS 공유서비스에서 발급받은 오픈 API 사용자 인증키 정보를 service_key 변수에 할당한다. Kosis의 인자료 service_key를 입력하여 데이터 조회 인스턴스 api를 만든다.
from PublicDataReader import Kosis
# KOSIS 공유서비스 Open API 사용자 인증키
service_key = "사용자 인증키"
# 인스턴스 생성하기
api = Kosis(service_key)
조회할 데이터 찾기
api.get_data() 메서드의 첫 번째 인자로 ‘KOSIS통합검색’을 지정한다. searchNm
에는 조회할 데이터를 찾기 위한 키워드를 입력한다. 반환된 데이터프레임에서 조회할 데이터를 찾아 기관ID(ORG_ID)와 통계표ID(TBL_ID) 값을 확인한다. ‘행정구역(읍면동)별/5세별 주민등록인구(2011년~)’ 데이터의 기관ID는 101이고, 통계표ID는 DT_1B04005N 이므로 이 값들을 데이터를 조회할 때 사용한다. 참고로 api.get_data() 메서드에 translate=False 옵션을 지정하면 영문 컬럼명으로 데이터를 조회할 수 있다. 이 인자의 기본값은 True이므로 국문 컬럼명으로 조회한다.
df = api.get_data(
"KOSIS통합검색",
searchNm="읍면동 주민등록 인구"
)
df.head(1)
기관ID | 기관명 | 통계표ID | 통계표명 | 조사ID | 조사명 | KOSIS목록구분 | KOSIS통계표위치 | 통계표위치 | 통계표주요내용 | 수록기간시작일 | 수록기간종료일 | 통계표주석 | 추천통계표여부 | KOSIS목록URL | KOSIS통계표URL | 검색결과건수 | 검색어명 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 101 | 행정안전부 | DT_1B04005N | 행정구역(읍면동)별/5세별 주민등록인구(2011년~) | 2008001 | 주민등록인구현황 | MT_ZTITLE | 인구 > 주민등록인구현황 | A > A_7 | 행정구역(동읍면)별 5세별 남자인구수 여자인구수 총인구수 개포3동 신당제4동 중림동... | 2011 | 2022 | * 등록구분의 "전체"는 "거주자", "거주불명자", "재외국민"이 포함된 자료입니... | N | https://kosis.kr/statisticsList/statisticsList... | http://kosis.kr/statHtml/statHtml.do?orgId=101... | 573 | 읍면동 주민등록 인구 |
데이터 수록 주기와 시점 확인
api.get_data() 메서드의 첫 번째 인자로 ‘통계표설명’을 지정한다. 통계표설명의 경우 상세기능도 추가적으로 지정해야 한다. 두 번째 인자로 ‘자료갱신일’을 입력하고, 위에서 복사해둔 기관ID와 통계표ID의 값들을 api.get_data()의 인자로 입력해 데이터 수록주기와 수록시점을 확인한다. 수록주기(PRD_SE) 별 최근 수록시점(PRD_DE) 값을 조회하면 수록주기가 년인 경우는 2022년, 수록주기가 월인 경우는 2022년 11월이 최신 데이터임을 알 수 있다.
df = api.get_data(
"통계표설명",
"자료갱신일",
orgId="101",
tblId="DT_1B04005N"
)
df.groupby(by=['수록주기']).agg({"수록시점": ["min", "max"]})
수록시점 | ||
---|---|---|
min | max | |
수록주기 | ||
년 | 2011 | 2022 |
월 | 201207 | 202212 |
데이터 항목 및 분류 확인하기
api.get_data() 메서드의 첫 번째 인자로 ‘통계표설명’을 지정한다. 두 번째 인자로 ‘분류항목’을 입력하고, 이번에도 기관ID와 통계표ID를 api.get_data()의 인자로 입력해 결과를 확인한다. 분류ID의 값이 ‘ITEM’ 이면 항목을 뜻하고 이 때, 분류값ID의 값을 통계자료 조회 시 itmId의 값으로 입력한다. 분류ID 값이 ITEM
이 아니고, 분류값순번 값에 숫자가 입력되어 있는 경우 이는 분류를 뜻하고, 분류값순번의 숫자 값이 분류 수준을 뜻한다. 예를 들어, 분류ID 값이 A이고, 분류값순번 값이 1이라면, 분류ID 값을 통계자료 조회 시 objL1의 값으로 입력한다.
item = api.get_data(
"통계표설명",
"분류항목",
orgId="101",
tblId="DT_1B04005N",
)
item.head()
기관ID | 통계표ID | 코드ID | 코드명 | 분류ID | 분류명 | 분류영문명 | 분류값ID | 분류값명 | 분류값영문명 | 상위분류값ID | 분류값순번 | CD_ENG_NM | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 101 | DT_1B04005N | 14STD04553 | 명 | ITEM | 항목 | Item code list | T2 | 총인구수 | Population | NaN | NaN | Person |
1 | 101 | DT_1B04005N | 14STD04553 | 명 | ITEM | 항목 | Item code list | T3 | 남자인구수 | Male | NaN | NaN | Person |
2 | 101 | DT_1B04005N | 14STD04553 | 명 | ITEM | 항목 | Item code list | T4 | 여자인구수 | Female | NaN | NaN | Person |
3 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 00 | 전국 | Whole country | NaN | 1 | NaN |
4 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 11 | 서울특별시 | Seoul | NaN | 1 | NaN |
통계표 조회 시 항목은 총인구수, 남자인구수 그리고 여자인구수 모두 사용하기로 한다. 분류값ID의 값인 T2, T3 그리고 T4를 모두 복사해둔다.
item.loc[item["분류ID"]=="ITEM"]
기관ID | 통계표ID | 코드ID | 코드명 | 분류ID | 분류명 | 분류영문명 | 분류값ID | 분류값명 | 분류값영문명 | 상위분류값ID | 분류값순번 | CD_ENG_NM | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 101 | DT_1B04005N | 14STD04553 | 명 | ITEM | 항목 | Item code list | T2 | 총인구수 | Population | NaN | NaN | Person |
1 | 101 | DT_1B04005N | 14STD04553 | 명 | ITEM | 항목 | Item code list | T3 | 남자인구수 | Male | NaN | NaN | Person |
2 | 101 | DT_1B04005N | 14STD04553 | 명 | ITEM | 항목 | Item code list | T4 | 여자인구수 | Female | NaN | NaN | Person |
분류1의 경우 전체 선택하기로 한다. 분류값ID를 일부 선택하려면 ‘00 11 11110’ 과 같이 공백으로 구분해 입력해야 하지만 전체 선택의 경우 ‘ALL’ 이라고 입력한다. 여기에서는 종류가 많으므로 직접 값을 복사하지 않고 넘어간다.
item.loc[item["분류ID"]=="A"]
기관ID | 통계표ID | 코드ID | 코드명 | 분류ID | 분류명 | 분류영문명 | 분류값ID | 분류값명 | 분류값영문명 | 상위분류값ID | 분류값순번 | CD_ENG_NM | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 00 | 전국 | Whole country | NaN | 1 | NaN |
4 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 11 | 서울특별시 | Seoul | NaN | 1 | NaN |
5 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 11110 | 종로구 | Jongno-gu | 11 | 1 | NaN |
6 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 1111051500 | 청운효자동 | Cheongunhyoja-dong | 11110 | 1 | NaN |
7 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 1111053000 | 사직동 | Sajik-dong | 11110 | 1 | NaN |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
4182 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 5013058000 | 서홍동 | Seohong-dong | 50130 | 1 | NaN |
4183 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 5013059000 | 대륜동 | Daeryun-dong | 50130 | 1 | NaN |
4184 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 5013060000 | 대천동 | Daecheon-dong | 50130 | 1 | NaN |
4185 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 5013061000 | 중문동 | Jungmun-dong | 50130 | 1 | NaN |
4186 | 101 | DT_1B04005N | NaN | NaN | A | 행정구역(동읍면)별 | By Administrative District | 5013062000 | 예래동 | Yerae-dong | 50130 | 1 | NaN |
4184 rows × 13 columns
분류2의 경우 연령별 구분은 사용하지 않고 총계만 조회하기로 한다. 따라서 분류값ID 값이 0인 경우만 해당하므로 0을 복사해둔다.
item.loc[item["분류ID"]=="B"]
기관ID | 통계표ID | 코드ID | 코드명 | 분류ID | 분류명 | 분류영문명 | 분류값ID | 분류값명 | 분류값영문명 | 상위분류값ID | 분류값순번 | CD_ENG_NM | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
4187 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 0 | 계 | Total | NaN | 2 | NaN |
4188 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 5 | 0 - 4세 | 0-4 Years old | NaN | 2 | NaN |
4189 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 10 | 5 - 9세 | 5-9 Years old | NaN | 2 | NaN |
4190 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 15 | 10 - 14세 | 10-14 Years old | NaN | 2 | NaN |
4191 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 20 | 15 - 19세 | 15-19 Years old | NaN | 2 | NaN |
4192 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 25 | 20 - 24세 | 20-24 Years old | NaN | 2 | NaN |
4193 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 30 | 25 - 29세 | 25-29 Years old | NaN | 2 | NaN |
4194 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 35 | 30 - 34세 | 30-34 Years old | NaN | 2 | NaN |
4195 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 40 | 35 - 39세 | 35-39 Years old | NaN | 2 | NaN |
4196 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 45 | 40 - 44세 | 40-44 Years old | NaN | 2 | NaN |
4197 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 50 | 45 - 49세 | 45-49 Years old | NaN | 2 | NaN |
4198 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 55 | 50 - 54세 | 50-54 Years old | NaN | 2 | NaN |
4199 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 60 | 55 - 59세 | 55-59 Years old | NaN | 2 | NaN |
4200 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 65 | 60 - 64세 | 60-64 Years old | NaN | 2 | NaN |
4201 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 70 | 65 - 69세 | 65-69 Years old | NaN | 2 | NaN |
4202 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 75 | 70 - 74세 | 70-74 Years old | NaN | 2 | NaN |
4203 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 80 | 75 - 79세 | 75-79 Years old | NaN | 2 | NaN |
4204 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 85 | 80 - 84세 | 80-84 Years old | NaN | 2 | NaN |
4205 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 90 | 85 - 89세 | 85-89 Years old | NaN | 2 | NaN |
4206 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 95 | 90 - 94세 | 90-94 Years old | NaN | 2 | NaN |
4207 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 100 | 95 - 99세 | 95-99 Years old | NaN | 2 | NaN |
4208 | 101 | DT_1B04005N | NaN | NaN | B | 5세별 | By Age Group (Five-Year) | 105 | 100+ | 100 Years old & over | NaN | 2 | NaN |
통계표 조회하기
api.get_data() 메서드의 첫 번째 인자로 ‘통계자료’를 지정한다. 행정구역(읍면동)별/5세별 주민등록인구(2011년~)
통계표를 조회를 위해 위에서 복사해둔 값들을 순서대로 api.get_data()의 인자로 입력한다. itmId의 값으로 모든 항목값을 입력했지만, objL1과 같이 ‘ALL’이라고 입력해도 전체 선택이 된다.
df = api.get_data(
"통계자료",
orgId="101",
tblId="DT_1B04005N",
objL1="ALL",
objL2="0",
itmId="T2 T3 T4",
prdSe="M",
startPrdDe="202211",
endPrdDe="202211",
)
df.head()
기관ID | 통계표ID | 통계표명 | 분류명1 | 분류영문명1 | 분류값명1 | 분류값영문명1 | 분류값ID1 | 분류명2 | 분류영문명2 | 분류값명2 | 분류값영문명2 | 분류값ID2 | 항목ID | 항목명 | 항목영문명 | 단위명 | 단위영문명 | 수록주기 | 수록시점 | 수치값 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 101 | DT_1B04005N | 행정구역(읍면동)별/5세별 주민등록인구(2011년~) | 행정구역(동읍면)별 | By Administrative District | 전국 | Whole country | 00 | 5세별 | By Age Group (Five-Year) | 계 | Total | 0 | T2 | 총인구수 | Population | 명 | Person | M | 202211 | 51450829 |
1 | 101 | DT_1B04005N | 행정구역(읍면동)별/5세별 주민등록인구(2011년~) | 행정구역(동읍면)별 | By Administrative District | 전국 | Whole country | 00 | 5세별 | By Age Group (Five-Year) | 계 | Total | 0 | T3 | 남자인구수 | Male | 명 | Person | M | 202211 | 25643889 |
2 | 101 | DT_1B04005N | 행정구역(읍면동)별/5세별 주민등록인구(2011년~) | 행정구역(동읍면)별 | By Administrative District | 전국 | Whole country | 00 | 5세별 | By Age Group (Five-Year) | 계 | Total | 0 | T4 | 여자인구수 | Female | 명 | Person | M | 202211 | 25806940 |
3 | 101 | DT_1B04005N | 행정구역(읍면동)별/5세별 주민등록인구(2011년~) | 행정구역(동읍면)별 | By Administrative District | 서울특별시 | Seoul | 11 | 5세별 | By Age Group (Five-Year) | 계 | Total | 0 | T2 | 총인구수 | Population | 명 | Person | M | 202211 | 9436836 |
4 | 101 | DT_1B04005N | 행정구역(읍면동)별/5세별 주민등록인구(2011년~) | 행정구역(동읍면)별 | By Administrative District | 서울특별시 | Seoul | 11 | 5세별 | By Age Group (Five-Year) | 계 | Total | 0 | T3 | 남자인구수 | Male | 명 | Person | M | 202211 | 4574781 |
피벗 테이블 만들기
통계표 조회 결과를 아래와 같이 원하는 형태로 재구조화하여 분석 목적에 맞게 사용하면 된다.
pv = df.pivot(index=["분류값ID1","분류값명1","수록시점"], columns=["항목명"], values="수치값").reset_index()
pv.columns.name = None
pv['수록시점'] = pd.to_datetime(pv['수록시점'], format="%Y%m")
numCols = ["남자인구수","여자인구수","총인구수"]
for col in numCols:
pv[col] = pd.to_numeric(pv[col])
pv
분류값ID1 | 분류값명1 | 수록시점 | 남자인구수 | 여자인구수 | 총인구수 | |
---|---|---|---|---|---|---|
0 | 00 | 전국 | 2022-11-01 | 25643889 | 25806940 | 51450829 |
1 | 11 | 서울특별시 | 2022-11-01 | 4574781 | 4862055 | 9436836 |
2 | 11110 | 종로구 | 2022-11-01 | 68488 | 73162 | 141650 |
3 | 1111051500 | 청운효자동 | 2022-11-01 | 5366 | 6311 | 11677 |
4 | 1111053000 | 사직동 | 2022-11-01 | 4041 | 5065 | 9106 |
... | ... | ... | ... | ... | ... | ... |
3853 | 5013058000 | 서홍동 | 2022-11-01 | 5555 | 5670 | 11225 |
3854 | 5013059000 | 대륜동 | 2022-11-01 | 7846 | 7734 | 15580 |
3855 | 5013060000 | 대천동 | 2022-11-01 | 7030 | 6828 | 13858 |
3856 | 5013061000 | 중문동 | 2022-11-01 | 6227 | 6055 | 12282 |
3857 | 5013062000 | 예래동 | 2022-11-01 | 1987 | 1896 | 3883 |
3858 rows × 6 columns
참고
- Python으로 KOSIS 데이터 조회하기
- KOSIS 국가통계포털 Open API 가이드
- KOSIS 공유서비스
- PublicDataReader 깃허브 저장소
- PublicDataReader 사용자 모임
댓글남기기