결측치 대체
결측치 대체(Imputation)는 누락된 값을 합리적인 값으로 채우는 과정입니다. 단순 삭제는 정보 손실을, 부적절한 대체는 편향을 초래합니다. 데이터의 결측 패턴을 이해하고 적절한 대체 전략을 선택하는 것이 중요합니다.
학습 목표
결측치의 유형(MCAR, MAR, MNAR)을 구분할 수 있다
SimpleImputer로 기본적인 대체를 수행할 수 있다
KNNImputer로 유사 데이터 기반 대체를 수행할 수 있다
IterativeImputer로 다변량 대체를 수행할 수 있다
결측 패턴에 따라 적절한 전략을 선택할 수 있다
왜 중요한가
대부분의 ML 모델은 결측치를 처리할 수 없습니다. 결측치를 무조건 삭제하면 데이터가 크게 줄어들고, 평균으로 대체하면 분산이 줄어듭니다. Pandas 결측치 문서에서 기본 탐지와 처리를 배웠다면, 여기서는 sklearn의 고급 대체 전략을 다룹니다.
결측치 유형
유형 설명 예시 대처 MCAR 완전 무작위 결측 설문지 실수로 빈칸 삭제 또는 단순 대체 MAR 조건부 무작위 결측 고소득자가 소득 미응답 조건부 대체 MNAR 비무작위 결측 심각한 질환자가 건강 설문 미응답 도메인 지식 필요
import numpy as np
import pandas as pd
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
np.random.seed( 42 )
# 예시 데이터 생성
n = 200
df = pd.DataFrame({
'salary' : np.random.normal( 4500 , 800 , n),
'experience' : np.random.uniform( 1 , 20 , n),
'performance' : np.random.normal( 75 , 10 , n),
'education_years' : np.random.choice([ 12 , 14 , 16 , 18 ], n)
})
# 결측치 삽입
df.loc[np.random.choice(n, 30 , replace = False ), 'salary' ] = np.nan
df.loc[np.random.choice(n, 20 , replace = False ), 'performance' ] = np.nan
df.loc[np.random.choice(n, 10 , replace = False ), 'education_years' ] = np.nan
print ( "결측치 현황:" )
print (df.isnull().sum())
print ( f " \n 완전한 행: { df.dropna().shape[ 0 ] } / { n } " )
SimpleImputer — 기본 대체
# 평균 대체
mean_imputer = SimpleImputer( strategy = 'mean' )
df_mean = pd.DataFrame(
mean_imputer.fit_transform(df),
columns = df.columns
)
# 중앙값 대체
median_imputer = SimpleImputer( strategy = 'median' )
df_median = pd.DataFrame(
median_imputer.fit_transform(df),
columns = df.columns
)
# 최빈값 대체 (범주형에 적합)
mode_imputer = SimpleImputer( strategy = 'most_frequent' )
# 상수 대체
const_imputer = SimpleImputer( strategy = 'constant' , fill_value =- 1 )
# 대체 전후 비교
import matplotlib.pyplot as plt
fig, axes = plt.subplots( 1 , 2 , figsize = ( 12 , 4 ))
axes[ 0 ].hist(df[ 'salary' ].dropna(), bins = 20 , alpha = 0.5 , label = '원본' , color = '#4a9eca' )
axes[ 0 ].hist(df_mean[ 'salary' ], bins = 20 , alpha = 0.5 , label = '평균 대체' , color = '#e6a23c' )
axes[ 0 ].set_title( 'salary: 평균 대체' )
axes[ 0 ].legend()
axes[ 1 ].hist(df[ 'salary' ].dropna(), bins = 20 , alpha = 0.5 , label = '원본' , color = '#4a9eca' )
axes[ 1 ].hist(df_median[ 'salary' ], bins = 20 , alpha = 0.5 , label = '중앙값 대체' , color = '#4a9e4a' )
axes[ 1 ].set_title( 'salary: 중앙값 대체' )
axes[ 1 ].legend()
plt.tight_layout()
plt.show()
전략 적합한 상황 장점 단점 평균 정규분포, 이상치 없음 단순, 빠름 이상치에 민감, 분산 축소 중앙값 편향 분포, 이상치 있음 이상치에 강건 분산 축소 최빈값 범주형 데이터 범주형에 적합 연속형에 부적절 상수 결측 자체가 정보인 경우 의도 명확 임의적
KNNImputer — 유사 데이터 기반 대체
KNN Imputer는 결측값이 있는 행과 유사한 k개의 이웃을 찾아, 이웃들의 값으로 대체합니다.
# KNN Imputer
knn_imputer = KNNImputer( n_neighbors = 5 , weights = 'distance' )
df_knn = pd.DataFrame(
knn_imputer.fit_transform(df),
columns = df.columns
)
# 대체 결과 비교
print ( "원본 기술통계:" )
print (df.describe().round( 1 ))
print ( " \n KNN 대체 기술통계:" )
print (df_knn.describe().round( 1 ))
# 시각화
fig, ax = plt.subplots( figsize = ( 8 , 5 ))
ax.hist(df[ 'salary' ].dropna(), bins = 20 , alpha = 0.5 , label = '원본' , color = '#4a9eca' )
ax.hist(df_knn[ 'salary' ], bins = 20 , alpha = 0.5 , label = 'KNN 대체' , color = '#e6a23c' )
ax.set_title( 'salary: KNN 대체 결과' )
ax.legend()
plt.tight_layout()
plt.show()
KNNImputer는 거리 기반이므로, 스케일이 다른 변수들은 사전에 스케일링을 해야 합니다. Pipeline에서 스케일링과 함께 사용하는 것이 권장됩니다.
IterativeImputer — 다변량 대체
IterativeImputer는 각 결측 피처를 다른 피처들로 모델링하여 대체합니다. MICE(Multiple Imputation by Chained Equations) 알고리즘을 구현합니다.
# IterativeImputer
iter_imputer = IterativeImputer( max_iter = 10 , random_state = 42 )
df_iter = pd.DataFrame(
iter_imputer.fit_transform(df),
columns = df.columns
)
# 세 가지 방법 비교
methods = {
'평균' : df_mean,
'KNN' : df_knn,
'Iterative' : df_iter
}
fig, axes = plt.subplots( 1 , 3 , figsize = ( 15 , 4 ))
for ax, (name, df_imp) in zip (axes, methods.items()):
# 대체된 값만 추출
mask = df[ 'salary' ].isnull()
ax.hist(df[ 'salary' ].dropna(), bins = 20 , alpha = 0.5 , label = '원본' , color = '#4a9eca' )
ax.hist(df_imp.loc[mask, 'salary' ], bins = 10 , alpha = 0.7 , label = '대체값' , color = '#e6a23c' )
ax.set_title( f ' { name } 대체' )
ax.legend()
plt.tight_layout()
plt.show()
전략 선택 가이드
결측 비율 권장 전략 < 5% 삭제 또는 SimpleImputer (평균/중앙값) 5~20% KNNImputer 또는 IterativeImputer 20~50% IterativeImputer + 결측 표시 피처 > 50% 피처 삭제 고려, 또는 도메인 전문가 상담
# 결측 표시 피처 생성
from sklearn.impute import SimpleImputer
# 결측 여부를 별도 피처로 생성
df[ 'salary_missing' ] = df[ 'salary' ].isnull().astype( int )
print ( f "결측 표시 피처 생성 완료: salary_missing" )
print (df[ 'salary_missing' ].value_counts())
AI/ML에서의 활용
파이프라인 통합 : sklearn Pipeline에 Imputer를 포함하여 학습/예측 시 일관된 대체를 보장합니다
결측 패턴 활용 : 결측 여부 자체를 피처로 추가하면 모델 성능이 향상될 수 있습니다
교차검증 안전 : Pipeline 내에서 대체하면 데이터 누수를 방지합니다
모델별 선택 : 트리 기반 모델은 결측에 강건하여 단순 대체로도 충분한 경우가 많습니다
XGBoost/LightGBM은 결측치를 자체 처리하는데, 왜 대체가 필요한가요?
트리 기반 모델은 결측치를 내부적으로 처리할 수 있지만, 1) 다른 모델(선형, SVM 등)과 비교할 때 동일한 데이터가 필요하고, 2) 전처리 파이프라인의 일관성을 위해, 3) 결측 비율이 높으면 성능에 영향을 줄 수 있기 때문에 명시적 대체를 권장합니다.
체크리스트
다음 문서