01. 판다스 전처리 메서드 마무리
전날 실습에서 남아 있던 세 개 메서드를 정리하였다. 이 세 가지는 오늘 인구 데이터 분석 전 과정에서 반복적으로 사용되었다.
메서드 기능 핵심 주의점
| drop(columns=[...]) | 컬럼 삭제 | 결과를 리턴만 하므로 변수에 재할당하거나 inplace=True를 명시해야 실제로 반영된다 |
| sort_values(by=...) | 정렬 | 아무 옵션도 주지 않으면 오름차순이다. 내림차순이 필요할 때 ascending=False를 준다 |
| assign(컬럼명=연산식) | 파생변수 생성 | 콤마로 구분하여 여러 개의 파생변수를 한 번에 만들 수 있다 |
sort_values는 by에 컬럼 리스트를 주면 첫 번째 컬럼을 1차 기준으로, 두 번째 컬럼을 동점 처리 기준으로 정렬한다. 정렬 방향이 컬럼마다 다를 경우 ascending에도 리스트를 전달하여 위치별로 지정한다.
강사는 분석 결과에서 정렬 기준이 곧 결과의 품질을 결정한다는 점을 강조하였다. 지자체 대상 보고에서는 어떤 기준으로 정렬했는지가 별도의 설명 대상이 된다는 실무 사례를 함께 제시하였다.
02. 패키지 임포트
패키지 구성은 데이터 분석용과 시각화용을 분리하였다.
import numpy as np
import pandas as pd
# 시각화
import plotly.express as px
import plotly.graph_objects as go
plotly를 선택한 이유는 데이터프레임을 그대로 입력으로 받기 때문이다. matplotlib은 x축과 y축에 수치 시퀀스를 직접 넘겨야 하지만, plotly express와 graph_objects는 데이터프레임을 넘기고 축을 컬럼명 문자열로 지정할 수 있다.
03. 필요 컬럼 추출
컬럼이 41개인데 필요한 것은 총인구수 10개와 행정구역 1개뿐이다. 컬럼명을 일일이 나열하는 대신 리스트 컴프리헨션으로 추출하였다.
# 컬럼명 추출
# [리스트에 담을 값 for 변수 이름 in 반복할 아이템 if 조건문 ]
col_list = [x for x in df.columns if '총인구수' in x]
# 행정구역 컬럼
col_list.insert(0,'행정구역')
df.columns는 데이터프레임이 가진 컬럼명을 인덱스 객체로 반환하는 속성이다. in 연산자로 문자열 포함 여부를 판정하여 '총인구수'가 들어간 컬럼명 10개만 걸러냈다.
append가 아닌 insert(0, '행정구역')를 사용한 이유는 위치 때문이다. append를 쓰면 행정구역이 맨 뒤로 밀려 분석할 때마다 좌우로 이동해야 한다.
population_df = df.loc[:,col_list] # loc[행, 열]
loc는 location의 약자로 행과 열의 위치를 지정하여 데이터를 추출한다. 행 자리의 콜론은 전체를 의미한다. 전날 배운 query가 조건에 맞는 데이터를 추출하는 것과 달리 loc는 조건 없이 지정한 위치의 데이터를 가져온다는 점이 다르다.
04. 행정코드 제거와 행·열 전치
행정구역 값은 서울특별시 (1100000000) 형태로 행정코드가 붙어 있다. 행정코드는 통계청 지도 서비스 등에서 실제 서비스 기준으로 사용되는 코드지만 이번 분석에서는 시각화 라벨을 방해한다.
문자열 슬라이싱으로 자를 수 없는 이유는 행정구역명의 글자 수가 제각각이어서 괄호가 시작되는 인덱스 위치가 일정하지 않기 때문이다. 따라서 인덱스가 아닌 키워드 기준으로 잘라야 한다.
# 서울특별시 (11000000000) -> 전국
# 소괄호 ( 기준으로 잘라내서 앞부분의 행정명에 앞뒤 공백을 제거하고 행정명만 다시 담기
population_df['행정구역']=population_df['행정구역'].str.split('(').str.get(0).str.strip()
세 단계 메서드 체인이다. str.split('(')는 여는 괄호를 기준으로 문자열을 잘라 리스트로 반환하고, str.get(0)은 그중 0번째 조각만 가져오며, str.strip()은 앞뒤 공백을 제거한다. strip()이 필요한 이유는 원본이 서울특별시 ( 형태로 괄호 앞에 공백이 하나 있어 분리 후에도 뒤에 공백이 남기 때문이다. 눈으로는 구분되지 않지만 처리하지 않으면 정확한 값이 아니다.
이어서 행과 열을 뒤집었다.
population_df = population_df.set_index('행정구역').T
population_df = population_df.drop(columns = '전국')
전치가 필요한 이유는 시각화 형태에서 역산된다. x축에 연도, y축에 인구수를 두려면 지역이 컬럼으로 매칭되어야 한다. 수치를 가진 데이터는 로우에, 기준이 되는 항목은 컬럼에 있어야 집계와 시각화가 모두 쉬워진다.
전국 컬럼은 값의 크기가 다른 시도와 자릿수 자체가 달라 분석 때마다 제외 처리를 해야 하므로 삭제하였다.
05. 콤마 제거와 결측치 병합
수치형 변환에 앞서 콤마를 먼저 제거하였다. 콤마가 남아 있는 상태로 형변환을 요청하면 오류가 발생한다.
# 특수기호(,) 삭제하기
# replace(찾을문자, 바꿀 문자),regex=True,inplace =True
population_df.replace(',','',regex=True,inplace =True)
regex=True는 정규 표현식(Regular Expression) 적용 여부를 묻는 옵션이다. 콤마는 일반 문자가 아니라 기호이므로 이 옵션을 주지 않으면 치환이 이루어지지 않는다.
#결측지 확인
population_df.isna().sum()
isna()는 각 셀이 결측인지 여부를 True/False로 반환하고, sum()은 True만 더하므로 컬럼별 결측 건수가 집계된다. 결과는 다음과 같았다.
컬럼 결측 건수
| 강원도 | 2 |
| 강원특별자치도 | 7 |
| 전라북도 | 1 |
| 전북특별자치도 | 8 |
원인은 행정구역 명칭 변경이다. 강원도는 2023년부터 강원특별자치도로, 전라북도는 2024년부터 전북특별자치도로 바뀌었다. 실제 데이터를 출력해 확인한 결과 강원도는 2015~2022년에 값이 있고 2023~2024년이 결측, 강원특별자치도는 2023~2024년에만 값이 있었다. 겹치는 구간이 없다는 것을 눈으로 확인한 뒤 병합을 진행하였다. 겹치는 구간이 있으면 더했을 때 인구수가 두 배가 되기 때문이다.
# NaN 데이터(아직 정의 되지 않은 값, 연산이 불가능) 처리 -> 강원도 컬럼 데이터를 강원특별자치도 컬럼으로 통합
# 처리 순서: NaN 데이터 -> 형변환 -> 0으로 채우기 -> 강원도 컬럼 데이터 강원특별자치도로 통합
population_df['강원특별자치도']=population_df['강원도'].astype(float).fillna(0) + population_df['강원특별자치도'].astype(float).fillna(0)
population_df['전북특별자치도']=population_df['전라북도'].astype(float).fillna(0) + population_df['전북특별자치도'].astype(float).fillna(0)
int가 아니라 float로 먼저 변환하는 이유는 실수형이 정수형을 포함하는 더 큰 범위이기 때문이다. 최초 형변환은 가장 큰 범위의 자료형으로 하고, 모든 처리가 끝난 뒤 정수형으로 좁히는 것이 안전하다. fillna(0)은 결측을 0으로 채워 덧셈 연산이 가능한 상태로 만든다. 반대로 결측 행 자체를 제거하는 dropna()도 존재한다.
population_df.drop(columns=['강원도','전라북도'], inplace =True)
population_df = population_df.astype(int)
병합 후 원본 컬럼 2개를 삭제하여 17개 시도 컬럼만 남겼다. 마지막으로 전체를 int로 변환하여 17개 컬럼 모두 int64가 되었다.
형변환을 마지막에 수행하는 이유는 결측치가 남아 있는 상태에서 형변환을 시도하면 오류 가능성이 크고, 오류가 나면 결측 처리 후 다시 형변환을 반복해야 하기 때문이다.
06. 전처리 결과 저장
population_df.to_csv('201512_202412_주민등록인구및세대현황_연간_전국_20260728.csv',
encoding ='utf-8-sig',index_label = ['연도'])
read_csv가 읽기라면 to_csv는 쓰기다. 확장자까지 파일명에 포함해야 한다.
- encoding='utf-8-sig' : 원본은 EUC-KR이지만 정제한 결과물은 UTF-8로 저장한다. 일반 utf-8로 저장해도 한글이 깨지는 경우가 있어 BOM이 포함된 utf-8-sig를 사용한다.
- index_label=['연도']: 지정하지 않으면 인덱스 컬럼명이 비어 있는 상태로 저장된다.
07. 시각화 — 17개 시도 전체와 단일 지역
17개 시도를 하나의 차트에 겹쳐 그리려면 도화지를 먼저 준비하고 시도별 그래프를 반복해서 추가하는 방식을 쓴다.
#차트 시각화 -> x축(가로, 연도), y축(세로, 총인구수)
#17개 시도의 연도별 총인구수의 변화를 하나의 차트로 시각화
fig = go.Figure() # 도화지 하나를 준비하는 단계
for location in pop_df.columns[1:]: # 서울특별시부터 시작 1번째 인덱스 설정. 0번째는 연도
fig.add_trace(
go.Scatter(
x=pop_df['연도'],
y=pop_df[location], # 지역의 컬럼명
name=location #범례
)
) #하나의 도화지에 이어서 계속 추가시킴
fig.update_layout(title = '대한민국 시도별 2015~2024년 총 인구수 변화')
fig.show()
pop_df.columns[1:]로 1번 인덱스부터 순회하는 이유는 0번이 연도 컬럼이기 때문이다. add_trace는 준비된 도화지에 그래프를 계속 추가하는 역할이고, 실제 그리기는 go.Scatter 안에서 이루어진다. name은 범례로 표시된다.


단일 지역은 plotly.express로 한 줄에 그릴 수 있다.
fig = px.scatter(pop_df,x='연도', y='대전광역시', text ='대전광역시',
title ='대전광역시 2015~2024년 총 인구수 변화')
fig.show()
text 속성을 지정하면 각 데이터 포인트 위에 실제 값이 출력된다. 지정하지 않으면 점만 표시된다.

08. 전국 인구 집계와 axis
앞서 전국 컬럼을 삭제했으므로 17개 시도를 합산하여 전국 인구를 다시 만들어야 한다.
#1) 17개 시도 컬럼명 리스트
region_col = pop_df.columns.drop('연도')
#2) 연도별 지역별 인구를 모두 합하여 전국인구 파생변수를 생성
pop_df = pop_df.assign(전국인구 = pop_df[region_col].sum(axis=1)) # 연도별로 전체 인구수 합계
pop_df.columns.drop('연도')는 실제로 컬럼을 삭제하는 것이 아니라 연도를 제외한 컬럼명 리스트만 반환한다.
핵심은 axis 값이다.
axis 연산 방향 이 데이터에서의 의미
| axis=0 | 컬럼 단위 집계 | 서울특별시의 2015~2024년 합계처럼 각 지역의 10년 총합 |
| axis=1 | 로우 단위 집계 | 2015년 17개 시도 합계처럼 각 연도의 전국 인구 |
연도별 전국 인구가 필요하므로 axis=1을 사용하였다. 강사는 코드 결과를 엑셀에서 별도로 합산하여 검증했다는 점을 언급하였다. 2015년 합계 51,529,338이 두 방식에서 일치하였다.
fig = px.line(pop_df,x='연도',y='전국인구',markers=True,title='대한민국 전체 인구 변화')
fig.show()
전국 인구는 2019년 약 5180만 명 , 정점을 찍은 뒤 감소로 전환하여 2024년 약 5121만 명이 되었다. 2020년 이후 감소 폭이 커지는 형태이며, 이는 출생 감소만이 아니라 사망을 포함한 순증감의 결과라는 점이 있다.

09. 2015년 대비 2024년 지역별 인구 증감
시작 연도와 마지막 연도를 각각 추출하여 비교 테이블을 만드는 흐름이다.
# 1) 2015년 데이터 추출
pop_2015 = (
pop_df.query("연도 == '2015년_총인구수'").drop(columns = ['연도','전국인구']).T.reset_index()
)
# 컬럼명 정리
pop_2015.columns = ['지역','2015년_총인구']
동일한 코드에서 연도만 바꾸어 pop_2024를 생성하였다. 각 단계의 역할은 다음과 같다.
- query: 해당 연도 로우 한 줄만 추출
- drop(columns=['연도','전국인구']): 비교에 불필요한 컬럼 제거
- .T: 지역이 세로로 나열되도록 전치
- .reset_index(): 인덱스를 일반 컬럼으로 내려 지역명을 컬럼으로 사용
- 컬럼명 재지정: index, 0 같은 의미 없는 컬럼명은 결과 보고 시 설명이 불가능하므로 반드시 정리한다
전체 코드를 소괄호로 감싼 것은 메서드 체인을 여러 줄로 나누어 쓰기 위한 형식이다.
# 3) 두 데이터 프레임 2015,2024년 데이터프레임 결합
pop_change = pd.merge(pop_2015, pop_2024, on='지역') #합칠 때 합칠 기준이 있어야함.
#4) 인구증감 계산하여 파생변수 만들기
pop_change = pop_change.assign(인구증감 = pop_change['2024년_총인구']-pop_change['2015년_총인구'])
# 5) 인구증감 순으로 정렬
pop_change = pop_change.sort_values(by='인구증감',ascending=False)
pd.merge는 두 데이터프레임을 합치는 메서드이며 합칠 기준 컬럼을 on으로 지정해야 한다.
정렬 결과 증가 지역은 6곳, 감소 지역은 11곳이었다.
지역 2015년 2024년 인구증감

fig = px.bar(
pop_change,
x='인구증감',
y='지역',
orientation='h',
title='2015년 대비 2024년 지역별 인구 증감',
text='인구증감',
color='인구증감',
color_continuous_scale='RdBu'
)
orientation='h'로 가로 방향을 지정하고, color_continuous_scale='RdBu'로 증감 부호에 따라 빨강과 파랑이 자동 배정되도록 하였다. add_vline(x=0, line_dash='dash')으로 0 기준선을 추가하였다.
증가한 지역이 수도권(경기·인천)과 세종 주변(세종·충남·충북)에 집중되어 있다는 점, 서울특별시가 -690,353명으로 최하위라는 점이 확인된다. 서울에서 경기·인천으로, 대전에서 세종·충남·충북으로 인구가 이동한 형태로 해석되었다.
10. 전년 대비 증감, 증감률, 3년 이동평균
10년 단위 비교로 큰 흐름을 확인한 다음, 연도 단위로 좁혀 어느 시점부터 변화가 발생했는지 분석하였다.
pop_df = pop_df.assign(전년대비증감 = pop_df['전국인구'].diff())
pop_df = pop_df.assign(전년대비증감률 = pop_df['전국인구'].pct_change()*100)
pop_df = pop_df.assign(이동평균3년 = pop_df['전국인구'].rolling(3).mean())
- diff(): difference의 약자로 전년 대비 차이를 계산한다. 2015년은 비교 대상이 없어 NaN이다.
- pct_change(): 전년 대비 변화 비율을 소수로 반환하므로 100을 곱해 퍼센트로 변환한다. 실무 보고에서는 절대 수치보다 비율로 설명하는 경우가 많기 때문에 함께 확인한다.
- rolling(3).mean(): 3년 구간 이동평균이며 3년치가 모이는 2017년부터 값이 생성된다.
연도 전국인구 전년대비증감 증감률(%) 이동평균3년

증감 수치와 증감률은 서로 다른 사실을 보여준다. 절대 수치로는 2020년 이후 계속 감소 중이지만, 증감률로 보면 2022년 -0.387%를 저점으로 2023년 -0.221%, 2024년 -0.211%로 감소 폭 자체는 줄어들고 있다.
이동평균은 추세(Trend) 확인을 목적으로 사용한다. 이번 데이터는 연도별 변동이 크지 않아 원본과 이동평균의 차이가 크지 않지만, 주식 가격이나 일별 방문자 수처럼 단기 변동이 큰 데이터에서 유효하다. 주식 차트의 5일선, 20일선, 60일선이 모두 이동평균이다.
'MLOps엔지니어' 카테고리의 다른 글
| ABC 부트캠프 AI파일럿 1기 MLOps 6일차_탐색적 데이터 분석(EDA) 실습 (0) | 2026.08.04 |
|---|---|
| ABC 부트캠프 AI파일럿 1기 MLOps 4일차_NumPy 배열 연산과 pandas 데이터 파악하기 (0) | 2026.08.02 |
| ABC 부트캠프 AI파일럿 1기 MLOps 3일차_서울 기온 CSV 데이터 분석과 matplotlib 시각화 (0) | 2026.08.01 |
| ABC 부트캠프 AI파일럿 1기 MLOps 2일차_ 데이터 분석을 위한 파이썬 기초 (1) | 2026.07.26 |
| ABC 부트캠프 AI파일럿 1기 MLOps 1일차 (0) | 2026.07.26 |