결측치(Missing Value)

Sklearn 속 Imputer별 하이퍼파라미터 정리

IterativeImputer 주요 하이퍼파라미터

하이퍼파라미터설명가능한 값기본값 (default)
estimator누락값 추정에 사용할 추정기. 기본은 BayesianRidge, 다른 회귀 모델로 변경 가능BayesianRidge, LinearRegression, KNeighborsRegressorBayesianRidge()
max_iter누락값 추정을 위한 최대 반복 횟수정수10
random_state난수 생성기의 시드 값 설정. 동일한 결과 재현 가능정수 또는 NoneNone
initial_strategy초기 대치 전략'mean', 'median', 'most_frequent', 'constant''mean'
imputation_order특성을 대치하는 순서'ascending', 'descending', 'roman', 'arabic', 'random''ascending'
skip_complete완전한 케이스(누락값 없는 특성)를 계산에서 제외 여부True, FalseFalse
add_indicator누락값 여부를 나타내는 지표 변수 추가 여부True, FalseFalse
tol반복 중지 임계값. 이전과 현재 반복 사이 평균 제곱 차이가 작으면 중지양수 실수1e-3
n_nearest_features각 특성을 대치할 때 사용할 다른 특성의 수. None이면 모든 특성을 사용정수 또는 NoneNone
verbose실행 중 출력 메시지의 양 제어정수 (0 이상)0
sample_posteriorBayesianRidge 추정기로 후방 분포에서 샘플링 여부True, FalseFalse

SimpleImputer 주요 하이퍼파라미터

하이퍼파라미터설명가능한 값기본값 (default)
missing_values누락값을 나타내는 placeholdernp.nan, 숫자 또는 기타 표현np.nan
strategy누락값 대체 전략'mean', 'median', 'most_frequent', 'constant''mean'
fill_valuestrategy="constant"일 때 사용할 대체 값숫자, 문자열Nㄱone
copyTrue일 경우 원본 데이터 변경 없이 새 배열 생성True, FalseTrue
add_indicator누락값 여부를 나타내는 지표 변수 추가 여부True, FalseFalse

KNNImputer 주요 하이퍼파라미터

하이퍼파라미터설명가능한 값기본값 (default)
missing_values누락값을 나타내는 placeholdernp.nan, 숫자 또는 기타 표현np.nan
n_neighbors누락값 대체에 사용할 가장 가까운 이웃의 수정수5
weights이웃의 가중치 방식'uniform', 'distance', 사용자 정의 함수'uniform'
metric거리 계산 메트릭'nan_euclidean', 사용자 정의 거리 함수'nan_euclidean'
copyTrue일 경우 원본 데이터 변경 없이 새 배열 생성True, FalseTrue
add_indicator누락값 여부를 나타내는 지표 변수 추가 여부True, FalseFalse

interpolate() 메서드 주요 옵션

옵션설명가능한 값
linear기본값. 결측치를 양쪽 값으로부터 선형적으로 추정'linear'
time시간 간격을 고려하여 선형적으로 보간. 시계열 데이터에서 유용'time'
index/values인덱스 값에 기반하여 선형적으로 보간'index', 'values'
pad/ffill앞 방향으로 이전 유효 값으로 결측치 채움'pad', 'ffill'
bfill/backfill뒷 방향으로 다음 유효 값으로 결측치 채움'bfill', 'backfill'
nearest가장 가까운 인덱스 값으로 보간'nearest'
polynomial다항식 보간. order 옵션으로 다항식 차수 지정 필요'polynomial', 정수 차수 (예: order=2)
slinear, quadratic, cubic선형, 이차, 삼차 스플라인 보간'slinear', 'quadratic', 'cubic'
akimaAkima의 3차 스플라인 보간법'akima'
from_derivatives데이터의 미분값을 알고 있을 때 이를 활용한 보간'from_derivatives'

실습

결측치 처리

라이브러리 불러오기

import missingno as msno
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import numpy as np

데이터 불러오기

import missingno as msno
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import numpy as np

결측값 만들기

# 결측값 만들기
np.random.seed(42)

# 데이터 복사
data_with_missing = data.copy()

missing_rate = 0.05 # 결측치 퍼센트

# 전체 데이터에서 무작위로 결측값을 삽입할 위치 선택
n_total = data_with_missing.size
n_missing = int(n_total * missing_rate)

# 무작위 인덱스 생성
missing_indices = np.random.choice(n_total, n_missing, replace=False)

# 결측치 무작위로 만들기
rows, cols = np.unravel_index(missing_indices, data_with_missing.shape)
for row, col in zip(rows, cols):
  data_to_modify.iloc[row, col] = np.nan

data_with_missing.iloc[:, 1:] = data_to_modify

결측치 확인 (isna().sum())

data_with_missing.isna().sum()
0
Year11
Country11
Spending_USD17
Life_Expectancy15

dtype: int64

결측치 확인 (msno.matrix())

msno.matrix(data_with_missing)
plt.show()

결측치 확인 (msno.bar())

msno.bar(data_with_missing)
plt.show()

결측치 제거 (dropna())

data_with_missing.dropna(how='all')
YearCountrySpending_USDLife_Expectancy
01970Germany252.311
11970France192.143
21970Great Britain123.993
31970Japan150.437
41970USA326.961
............
2692020GermanyNaN
2702020France5468.418
2712020Great Britain5018.700
2722020Japan4665.641
2732020USANaN

274 rows × 4 columns

  • 삭제된 데이터가 없다.
    • how='all': 데이터가 거의 완전하지 않은 행/열만 삭제하고 싶을 때
    • how='any': 결측치가 하나라도 있으면 해당 행/열을 완전히 제거하고 싶을 때

결측치 대치 (fillna())

# 결측치 index 찾기
index = data_with_missing.index[data_with_missing.isna().any(axis=1)]

# 모두 0으로 대치
data_with_missing.iloc[index, :].fillna(0)
YearCountrySpending_USDLife_Expectancy
110.0Japan185.39073.2
121972.0USA397.0970.0
140.0Japan205.77873.4
171974.0Japan0.00073.7
211975.0Japan0.00074.3
# 평균값으로 대치
data_with_missing.iloc[index, :].fillna(data_with_missing['Life_Expectancy'].mean())
YearCountrySpending_USDLife_Expectancy
1177.887645Japan185.39000073.200000
121972.000000USA397.09700077.887645
1477.887645Japan205.77800073.400000
171974.000000Japan77.88764573.700000
211975.000000Japan77.88764574.300000
# 결측값을 뒤쪽 값으로 채우기
data_with_missing.fillna(method='bfill').head(5)
YearCountrySpending_USDLife_Expectancy
01970.0Germany252.31170.6
11970.0France192.14372.2
21970.0Great Britain123.99371.9
31970.0Japan150.43772.0
41970.0USA326.96170.9

결측치 처리에 따른 모델 성능 테스트

라이브러리 불러오기

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.impute import KNNImputer

데이터 불러오기

data = sns.load_dataset("healthexp")
  • 분석 목표 : 건강 지출과 연도, 국가가 기대수명(Life_Expectancy)에 미치는 영향을 분석하여 기대수명을 예측
    • 독립 변수 : Year, Country, Spending_USD
    • 종속 변수 : Life_Expectancy

결측값 만들기

np.random.seed(42)

data_with_missing = data.copy()

missing_rate = 0.1
n_missing = int(len(data_with_missing) * missing_rate)

missing_indices = np.random.choice(len(data_with_missing), n_missing, replace=False)

print("추가된 결측값 개수 : ", len(missing_indices))

data_with_missing.loc[missing_indices, 'Spending_USD'] = np.nan
  • Spending_USD의 10% 정도를 임의로 결측치로 만듦

결측치 확인

data_with_missing.isna().sum()
0
Year0
Country0
Spending_USD27
Life_Expectancy0

dtype: int64

결측치 처리 함수 정의

def drop_na(df, column):
    return df.dropna(subset=[column])

def mean_imputation(df, column):
    df_filled = df.copy()
    df_filled[column] = df_filled[column].fillna(df_filled[column].mean())
    return df_filled

def linear_interpolation(df, column):
    df_filled = df.copy()
    df_filled[column] = df_filled[column].interpolate(method='linear')
    return df_filled

def knn_imputation(df, column, n_neighbors=5):
    df_filled = df.copy()
    
    imputer = KNNImputer(n_neighbors=n_neighbors)
    df_filled[[column]] = imputer.fit_transform(df_filled[[column]])
  
    return df_filled
  • drop_na : 결측값이 있는 행을 제거한다.
  • mean_imputation : 결측값을 해당 열의 평균값으로 채운다.
  • linear_interpolation : 결측값을 앞뒤 값으로 선형 보간법을 통해 채운다.
  • knn_imputation : KNN 알고리즘을 사용하여 결측값을 가장 가까운 이웃의 값으로 채운다.

결측치 처리별 모델 학습 및 평가

column = 'Spending_USD'

for method_name, impute_func in methods.items():
    # 결측치 처리
    data_filled = impute_func(data_with_missing, column)

    # Feature와 Target 분리
    X = data_filled.drop(columns=column)
    y = data_filled[column]

    # 범주형 변수 더미 인코딩 (drop_first=True로 첫 번째 레벨 제거)
    X = pd.get_dummies(X, drop_first=True)

    # Train-Test Split
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

    # 모델 학습
    model = RandomForestRegressor(random_state=42)
    model.fit(X_train, y_train)

    # Train/Test 평가
    for dataset_name, X_data, y_data in (['Train', X_train, y_train], ['Test', X_test, y_test]):
        y_pred = model.predict(X_data)

        # 평가 지표 계산
        mse = mean_squared_error(y_data, y_pred)
        r2 = r2_score(y_data, y_pred)

        # 결과 저장
        result_now = pd.DataFrame({
            'Method': [method_name],
            'Dataset': [dataset_name],
            'MSE': [mse],
            'R2': [r2]
        })
        results = pd.concat([results, result_now], ignore_index=True)

# 결과 출력
print(results)
MethodDatasetMSER2
0Drop NATrain1.008259e+040.997793
1Drop NATest7.043155e+040.985579
2Mean ImputationTrain1.184271e+040.997469
3Mean ImputationTest3.112351e+06-0.498801
4Linear InterpolationTrain1.184271e+040.997469
5Linear InterpolationTest2.068646e+060.582370
6Knn ImputationTrain1.184271e+040.997469
7Knn ImputationTest3.112351e+06-0.498801

결과분석

  • Drop NA: Train/Test 세트 모두 높은 R2, 낮은 MSE로 좋은 성능을 보인다.
  • Mean Imputation & KNN Imputation: Train 세트에서는 높은 성능이지만, Test 세트에서는 R2 음수와 높은 MSE로 예측 실패하였다. 이는 결측치를 평균값으로 대체하거나, KNN 방식으로 대체해 데이터 왜곡이 발생했기 때문이다. (비정상적으로 낮음)
  • Linear Interpolation: Train에서 좋은 성능, Test에서는 다소 성능 저하되지만 여전히 예측 가능하다.

결측치 처리 후 시각화

import matplotlib.pyplot as plt

fig, axes = plt.subplots(2,2, figsize=(16,12), sharex= True, sharey=True)
axes = axes.flatten()

for ax,(method_name, impute_func) in zip (axes, methods.items()):
    data_filled = impute_func(data_with_missing, column)

    ## 분포를 살펴보기
    sns.histplot(data_filled[column], bins= 30, kde=True, ax=ax)
    ax.set_title(f'Spending_USD Distribution - {method_name}')
    ax.set_xlabel('Spending_USD')
    ax.set_ylabel('Frequency')

plt.show()

비정상적으로 낮은 점수 원인

  • Mean Imputation & KNN Imputation: 특정 값에 집중된 데이터로 인해 모델이 과대적합(overfitting)되어 Test 세트에 일반화되지 못하였기 때문이다.

결론

  • 현재 데이터셋에서는 결측치를 제거(Drop NA)하는 것이 분포를 덜 왜곡시키고, 모델의 성능도 더 안정적이기 때문에 다른 대체 기법(Mean, KNN 등)을 적용하는 것보다 더 나은 선택으로 보여진다.
profile
제 글이 유익하셨다면 ♡와 팔로우로 응원 부탁드립니다.

0개의 댓글

관련 채용 정보

Powered by GraphCDN, the GraphQL CDN