데이터 결측치 처리의 최적 접근법: 상황별 분석과 적용 전략

결측치(Missing Values)는 데이터 분석과 머신러닝 모델링에서 피할 수 없는 현실이며, 이를 어떻게 처리하느냐에 따라 분석 결과와 모델 성능이 크게 달라질 수 있습니다. 이 글에서는 다양한 데이터 특성과 상황에 맞는 결측치 처리 전략을 심층적으로 살펴보겠습니다.

결측치 처리의 중요성

데이터 사이언스 프로젝트에서 깨끗한 데이터셋을 만나는 것은 매우 드문 일입니다. 실제 현업에서는 데이터 수집 오류, 시스템 장애, 사용자의 응답 거부 등 다양한 이유로 결측치가 발생합니다. 이러한 결측치를 무시하고 분석을 진행할 경우, 편향된 결과를 얻거나 모델의 예측 성능이 저하될 수 있습니다.

결측치 처리는 단순히 빈 공간을 채우는 작업이 아닌, 데이터의 본질을 이해하고 통계적 완결성을 유지하는 과정입니다. 따라서 데이터의 특성과 분석 목적에 맞는 결측치 처리 방법을 선택하는 것이 중요합니다.

데이터 유형에 따른 결측치 처리 전략

수치형 데이터(Numerical Data)의 결측치 처리

수치형 데이터의 결측치 처리는 데이터의 분포와 이상치 존재 여부에 따라 다른 접근법이 필요합니다.

정상 분포를 가진 수치형 데이터

정규 분포(Normal Distribution)를 따르는 수치형 데이터는 평균값으로 결측치를 대체하는 것이 효과적입니다. 이 방법이 적합한 이유는 다음과 같습니다:

  1. 중앙 경향성 반영: 평균은 정규 분포의 중심을 정확히 표현하며, 대부분의 데이터가 평균 주변에 밀집되어 있습니다.
  2. 통계적 안정성: 정규 분포에서 평균은 이상적인 대표값으로, 결측치를 평균으로 대체할 경우 전체 분포의 특성을 크게 훼손하지 않습니다.
  3. 계산 효율성: 평균 계산은 컴퓨팅 리소스를 적게 사용하면서도 효과적인 대체값을 제공합니다.

예를 들어, 키, 몸무게, 시험 점수 등과 같이 정규 분포를 따르는 데이터에서 결측치가 발생했다면, 해당 변수의 평균값으로 대체하는 것이 전체 데이터 분포를 크게 왜곡하지 않으면서도 분석의 정확도를 유지할 수 있습니다.

# 정규 분포를 가진 수치형 데이터의 결측치 처리 예제
import pandas as pd
import numpy as np

# 결측치가 있는 데이터프레임 가정
df = pd.DataFrame({'normal_dist_feature': [25, 27, np.nan, 24, 26, np.nan, 23, 25]})

# 평균값으로 결측치 대체
df['normal_dist_feature'].fillna(df['normal_dist_feature'].mean(), inplace=True)

이상치가 많은 수치형 데이터

데이터에 이상치(Outliers)가 많이 포함되어 있는 경우, 평균값은 이상치에 크게 영향을 받아 왜곡될 수 있습니다. 이런 상황에서는 중앙값(Median)을 사용하는 것이 더 적합합니다:

  1. 이상치에 대한 강건성: 중앙값은 극단적인 값에 영향을 덜 받기 때문에, 이상치가 있는 데이터에서 더 안정적인 대체값을 제공합니다.
  2. 분포 왜곡 방지: 평균은 이상치에 의해 크게 이동할 수 있지만, 중앙값은 상대적으로 안정적으로 유지됩니다.
  3. 대표성 유지: 중앙값은 데이터의 중심 경향을 여전히 잘 나타내며, 분포의 중심에 위치합니다.

예를 들어, 소득 데이터, 주식 가격, 특정 센서의 측정값 등은 종종 이상치를 포함하고 있으며, 이러한 데이터의 결측치는 중앙값으로 대체하는 것이 더 합리적입니다.

# 이상치가 많은 수치형 데이터의 결측치 처리 예제
# 소득 데이터와 같이 이상치가 존재할 수 있는 변수
df = pd.DataFrame({'income': [45000, 48000, np.nan, 51000, 200000, np.nan, 47000, 49000]})

# 중앙값으로 결측치 대체
df['income'].fillna(df['income'].median(), inplace=True)

범주형 데이터(Categorical Data)의 결측치 처리

범주형 데이터는 수치형 데이터와 달리, 값들 사이에 수학적 관계가 없으므로 다른 접근 방식이 필요합니다:

  1. 최빈값(Mode) 사용: 범주형 데이터의 결측치는 가장 빈번하게 발생하는 범주(최빈값)로 대체하는 것이 일반적입니다.
  2. 통계적 타당성: 최빈값은 해당 범주가 가장 일반적이라는 통계적 근거를 바탕으로 하므로, 가장 보수적인 대체 방법입니다.
  3. 분포 유지: 최빈값으로 대체할 경우, 원래 데이터의 분포 특성을 유지하는 데 도움이 됩니다.

예를 들어, 성별, 직업, 제품 카테고리 등의 범주형 변수에서 결측치가 발생한 경우, 해당 변수에서 가장 많이 등장하는 범주로 대체하는 것이 일반적입니다.

# 범주형 데이터의 결측치 처리 예제
df = pd.DataFrame({'category': ['A', 'B', np.nan, 'A', 'C', np.nan, 'A', 'B']})

# 최빈값으로 결측치 대체
mode_value = df['category'].mode()[0]  # 최빈값 계산
df['category'].fillna(mode_value, inplace=True)

특수 상황에서의 결측치 처리

모델 훈련에 영향을 주고 싶지 않은 경우

때로는 결측치 자체가 중요한 정보일 수 있으며, 인위적인 값으로 대체하는 것보다 결측 상태임을 명시적으로 나타내는 것이 더 유용할 수 있습니다:

  1. 의미 있는 결측: 0, -1 또는 다른 특수 값을 사용하여 해당 데이터가 결측되었음을 표시합니다.
  2. 모델의 학습 패턴: 이렇게 처리하면 모델이 값 자체보다는 값이 존재하는지 여부에 더 집중할 수 있습니다.
  3. 결측 패턴 탐지: 특정 패턴에 따라 결측이 발생하는 경우, 이를 명시적으로 표시하면 모델이 해당 패턴을 학습할 수 있습니다.

예를 들어, 고객의 구매 기록이나 센서 데이터에서 특정 시점의 데이터가 없는 경우, 이를 0이나 -1과 같은 특수값으로 표시하여 모델이 데이터 결측 자체를 하나의 특성으로 인식하도록 할 수 있습니다.

# 모델 훈련에 영향을 최소화하는 결측치 처리 예제
df = pd.DataFrame({'feature': [1.2, 3.4, np.nan, 2.1, np.nan, 5.6]})

# 결측치를 특수값(-1)으로 대체
df['feature'].fillna(-1, inplace=True)

딥러닝 입력 데이터의 결측치 처리

딥러닝 모델에 입력되는 데이터는 일반적으로 정규화(Normalization)나 표준화(Standardization)와 같은 스케일 조정 과정을 거칩니다. 이러한 상황에서의 결측치 처리는 다음과 같은 특별한 고려가 필요합니다:

  1. 정규화된 데이터: 데이터가 이미 정규화되어 있다면, 0으로 결측치를 대체하는 것이 적절할 수 있습니다. 이는 정규화된 데이터에서 0이 평균 또는 중립적인 값에 해당하기 때문입니다.
  2. 표준화된 데이터: 데이터가 표준화되어 있다면(평균 0, 표준편차 1), 평균값인 0으로 대체하는 것이 논리적입니다.
  3. 활성화 함수 고려: 사용하는 활성화 함수에 따라 적절한 대체값이 달라질 수 있습니다. 예를 들어, ReLU 활성화 함수를 사용하는 경우, 음수 값은 활성화되지 않으므로 0이나 작은 양수값으로 대체하는 것이 좋을 수 있습니다.

딥러닝 모델에서는 결측치 처리가 학습 과정과 모델 성능에 큰 영향을 미칠 수 있으므로, 데이터의 특성과 모델 아키텍처를 고려한 신중한 접근이 필요합니다.

# 딥러닝 입력을 위한 결측치 처리 예제
from sklearn.preprocessing import StandardScaler

# 원본 데이터
df = pd.DataFrame({'feature1': [1.2, 3.4, np.nan, 2.1, np.nan, 5.6],
                   'feature2': [0.5, np.nan, 1.3, 2.4, 3.1, np.nan]})

# 먼저 평균으로 결측치 대체
for col in df.columns:
    df[col].fillna(df[col].mean(), inplace=True)

# 표준화 적용
scaler = StandardScaler()
df_scaled = pd.DataFrame(scaler.fit_transform(df), columns=df.columns)

# 이제 새로운 결측치가 발생하면 0으로 대체 (표준화된 데이터의 평균)
df_scaled.fillna(0, inplace=True)

KNN이나 거리 기반 모델에서의 결측치 처리

거리 계산에 기반한 알고리즘(KNN, K-means 등)에서는 결측치 처리가 특히 중요합니다. 이러한 모델에서는 데이터 포인트 간의 거리 계산이 핵심이므로, 결측치가 있으면 거리 계산 자체가 불가능해질 수 있습니다:

  1. 스케일 조정 후 평균/중앙값 사용: 데이터를 먼저 정규화한 후, 평균이나 중앙값으로 결측치를 대체합니다.
  2. 왜곡 방지: 이상치에 민감한 거리 계산에서는 중앙값을 사용하는 것이 더 안정적일 수 있습니다.
  3. KNN 기반 대체: 아이러니하게도, KNN 알고리즘 자체를 결측치 대체에 활용할 수도 있습니다. 결측치가 있는 샘플과 가장 유사한 k개의 샘플을 찾아, 그들의 해당 특성 값 평균으로 결측치를 대체합니다.

거리 기반 모델에서는 결측치 처리 방법이 모델의 성능에 직접적인 영향을 미치므로, 데이터의 특성을 고려한 신중한 접근이 필요합니다.

# KNN이나 거리 기반 모델을 위한 결측치 처리 예제
from sklearn.impute import KNNImputer
from sklearn.preprocessing import StandardScaler

# 원본 데이터
df = pd.DataFrame({
    'feature1': [1.2, 3.4, np.nan, 2.1, np.nan, 5.6],
    'feature2': [0.5, np.nan, 1.3, 2.4, 3.1, np.nan]
})

# 표준화 (스케일 조정)
scaler = StandardScaler()
# 결측치가 있으므로 fit_transform 직접 사용은 불가, 비결측 데이터로 fit
cols = df.columns
df_no_missing = df.dropna()
scaler.fit(df_no_missing)

# 결측치 대체 이전에 데이터 변환 방법 (부분적 적용)
for col in cols:
    mask = df[col].notna()
    df.loc[mask, col] = scaler.transform(df.loc[mask, col].values.reshape(-1, 1)).flatten()

# 스케일 조정 후 중앙값으로 대체
for col in cols:
    df[col].fillna(df[col].median(), inplace=True)

# 또는 KNN 기반 결측치 대체 사용
# imputer = KNNImputer(n_neighbors=3)
# df_imputed = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)

결측치 처리의 고급 기법

지금까지 기본적인 결측치 처리 방법에 대해 살펴보았지만, 더 복잡한 데이터셋이나 특수한 상황에서는 다음과 같은 고급 기법도 고려할 수 있습니다:

다중 대체법(Multiple Imputation)

단일 값으로 대체하는 것이 아니라, 여러 번의 대체를 통해 결측치의 불확실성을 반영하는 방법입니다:

  1. 통계적 정확성: 결측치의 불확실성을 명시적으로 모델링하여, 더 견고한 추론을 가능하게 합니다.
  2. 편향 감소: 단일 대체법에서 발생할 수 있는 편향을 줄여줍니다.
  3. 분산 추정: 결측 데이터로 인한 불확실성이 최종 분석 결과에 어떻게 영향을 미치는지 평가할 수 있습니다.
# 다중 대체법 예제
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
import numpy as np

# 결측치가 있는 데이터프레임
df = pd.DataFrame({
    'feature1': [1.2, 3.4, np.nan, 2.1, np.nan, 5.6],
    'feature2': [0.5, np.nan, 1.3, 2.4, 3.1, np.nan],
    'feature3': [3.5, 2.1, 1.7, np.nan, 2.9, 4.2]
})

# 다중 대체 모델 생성
mice_imputer = IterativeImputer(max_iter=10, random_state=0)
df_imputed = pd.DataFrame(mice_imputer.fit_transform(df), columns=df.columns)

시계열 데이터의 결측치 처리

시계열 데이터는 시간적 의존성 때문에 특별한 결측치 처리 방법이 필요합니다:

  1. 전방 채우기(Forward Fill): 이전 시점의 값으로 결측치를 대체합니다.
  2. 후방 채우기(Backward Fill): 다음 시점의 값으로 결측치를 대체합니다.
  3. 보간법(Interpolation): 시간적 추세를 고려하여 선형 또는 비선형 보간을 통해 결측치를 추정합니다.
# 시계열 데이터의 결측치 처리 예제
# 일별 온도 데이터 (시계열)
dates = pd.date_range('20230101', periods=10)
df_ts = pd.DataFrame({
    'temperature': [23.5, 22.1, np.nan, 21.7, np.nan, np.nan, 24.3, 25.1, 24.8, 23.9]
}, index=dates)

# 전방 채우기 (이전 값으로 대체)
df_ts_ffill = df_ts.fillna(method='ffill')

# 후방 채우기 (다음 값으로 대체)
df_ts_bfill = df_ts.fillna(method='bfill')

# 선형 보간
df_ts_interp = df_ts.interpolate(method='linear')

결측치 처리 전략 결정을 위한 체크리스트

결측치 처리 방법을 결정할 때는 다음과 같은 체크리스트를 참고하면 도움이 됩니다:

  1. 데이터 타입 확인: 수치형인지, 범주형인지에 따라 기본 접근법이 달라집니다.
  2. 분포 분석: 정규 분포를 따르는지, 이상치가 많은지 등 데이터의 분포 특성을 파악합니다.
  3. 결측 메커니즘 파악: 완전 무작위 결측(MCAR), 무작위 결측(MAR), 비무작위 결측(MNAR) 중 어떤 패턴으로 결측이 발생했는지 분석합니다.
  4. 모델 특성 고려: 사용할 모델(딥러닝, KNN 등)의 특성에 맞는 결측치 처리 방법을 선택합니다.
  5. 도메인 지식 활용: 데이터가 속한 분야의 전문 지식을 활용하여, 가장 의미 있는 대체 방법을 선택합니다.

결론

결측치 처리는 데이터 전처리 과정에서 중요한 단계이며, 데이터의 특성과 분석 목적에 맞는 적절한 방법을 선택하는 것이 중요합니다. 수치형 데이터에서는 분포와 이상치 여부에 따라 평균 또는 중앙값을 사용하고, 범주형 데이터에서는 최빈값을 활용하는 것이 일반적입니다. 또한, 딥러닝 모델이나 거리 기반 알고리즘과 같은 특수한 상황에서는 해당 모델의 특성을 고려한 결측치 처리가 필요합니다.

결측치 처리는 단순히 빈 공간을 채우는 작업이 아니라, 데이터의 완결성과 분석의 정확성을 유지하기 위한 중요한 단계입니다. 따라서, 데이터 과학자는 다양한 결측치 처리 방법의 장단점을 이해하고, 상황에 맞는 최적의 전략을 선택할 수 있어야 합니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다