logo

결측치 처리

결측치 처리

  • 결측치: 데이터셋에서 누락된 값 또는 값이 기록되지 않은 경우
  • 주요 원인
    • 데이터 수집 오류: 데이터 입력 실수, 기기 오작동이나 데이터 전송 오류
    • 의도적 미응답: 설문 등에서 민감한 질문이나 답변하기 까다로운 질문
    • 데이터 손실: 데이터베이스 손상, 파일 손실, 시스템 충돌, 네트워크 문제
    • 불완전한 데이터 소스: 데이터가 일부만 제공되거나 누락된 경우

결측치의 종류

  • MCAR(Missing Completely at Random)
    • 결측치가 완전히 무작위로 발생하며, 다른 변수들과 상관관계가 없음
    • 예: 설문 조사에서 무작위로 몇 명이 응답하지 않은 경우
    • 큰 문제는 아니지만 MCAR은 드묾
  • MAR(Missing at Random)
    • 결측치가 다른 관측 가능한 변수와 관련이 있지만, 해당 변수와는 무관
    • 예: 남성 응답자들이 특정 질문에 응답하지 않은 경우, 성별은 관찰됨
    • 어느 정도 대응 가능하며, 일반적으로 결측치에 대해 MAR을 가정하고 대응
  • MNAR(Missing Not at Random)
    • 결측치가 해당 변수와 관련이 있으며, 무작위로 발생하지 않음
    • 예: 소득이 낮은 사람들이 소득 질문에 응답하지 않는 경우
    • 대응이 까다로움

결측치 대응 방법

  • 삭제(Deletion): 결측치가 있는 데이터를 삭제하고 분석
  • 대체(Imputation): 결측치를 다른 값으로 대체
  • 모형 기반 접근: 결측치에 영향을 받지 않는 모형 사용
  • 결측치 자체를 하나의 특징으로 사용: 특수한 경우에 사용

삭제의 방법

  • 리스트 삭제(Listwise Deletion)
    • 결측치가 있는 전체 행을 삭제
    • 간단하고 구현이 용이하지만, 많은 데이터 손실
    • MCAR일 때만 유효
  • 페어와이즈 삭제(Pairwise Deletion)
    • 분석에 필요한 변수에만 결측치가 있는 행을 삭제
    • 데이터 손실 최소화
    • 각각의 분석이 서로 다른 부분집합에 대해 이뤄지기 때문에 결과 일관성에 문제가 생길 수 있음
    • 예: 상관행렬이 양의 정부호(positive definite)가 아닐 수 있음

실습: 결측치 만들기

  • 실습 데이터 열기
import pandas as pd

df = pd.read_excel('car.xlsx')
  • mileage 열에서 10%의 데이터를 무작위 결측치로 만들기
import numpy as np

np.random.seed(42)   # 재현을 위해 씨앗값을 42로
miss_prob = 0.1      # 10%의 데이터를 결측치로
df_miss = df.copy()  # df를 복사

df_miss['mileage'] = df_miss['mileage'].apply(
    lambda x: np.nan if np.random.rand() < miss_prob else x
)

유사 난수

  • 대부분의 컴퓨터는 결정론적(deterministic)으로 작동하여 난수(random number)를 생성할 수 없음
  • 아주 긴 주기의 수열을 만들어 유사 난수로 사용
  • 간단한 알고리즘은 씨앗값으로부터 일정 항 이후부터 사용하는 방식
  • 보통 씨앗값은 현재 시각으로 결정
  • 같은 결과를 재현하려고 할 때는 씨앗값을 고정하여 동일한 유사 난수를 생성

코드 설명

  • df_miss['mileage'].apply()
    • apply 함수는 Pandas 시리즈(열)의 각 원소에 특정 함수를 적용하는 메소드
    • df_miss['mileage'] 열의 각 원소에 대해 지정된 lambda 함수를 적용
  • lambda x:
    • lambda 함수는 익명 함수
    • 예: lambda x: x + 1은 입력 x에 1을 더하는 함수
    • 여기서는 mileage 열의 각 개별 값이 x로 전달됨
  • np.nan if np.random.rand() < miss_prob else x
    • 삼항 연산자: 참일 경우 값 if 조건 else 거짓일 경우 값
    • np.random.rand()는 0과 1 사이의 임의의 실수를 생성
    • 이 값이 miss_prob보다 작으면 np.nan을 반환하여 결측치로 만듦
    • 조건이 거짓이면 원래 값 x를 그대로 반환

리스트 삭제

  • 결측치 있는 행 보기
df_miss[df_miss['mileage'].isnull()]
  • 결측치 없는 행 보기
df_miss[df_miss['mileage'].notnull()]
  • 리스트 삭제
df_miss.dropna()

평균과 표준편차

  • 원 데이터의 평균
df['mileage'].mean()
  • 결측치가 있는 데이터의 평균
df_miss['mileage'].mean()
  • 기본적으로 결측치는 삭제하고 평균냄(skipna=True)
df_miss['mileage'].mean(skipna=False)
  • 원 데이터와 결측치가 있는 데이터의 표준편차
df['mileage'].std()
df_miss['mileage'].std()

분포 시각화

import seaborn as sns
import matplotlib.pyplot as plt

sns.kdeplot(df['mileage'], label='original')
sns.kdeplot(df_miss['mileage'], label='missing')
plt.legend()

원 데이터와 결측치 데이터의 mileage 분포 비교

대체의 방법

  • 평균/중앙값 또는 최빈값으로 대체
    • 장점: 간단하고 빠름
    • 단점: 데이터 변동성 감소, 편향 발생 가능
  • 예측 대체
    • 회귀 모형이나 KNN을 사용하여 결측치를 예측하여 대체
    • 장점: 보다 정확한 대체
    • 단점: 데이터 변동성 감소
  • 다중 대체(Multiple Imputation)
    • 결측치를 여러 번 대체하고, 여러 대체 결과를 결합하여 불확실성을 반영
    • 장점: 불확실성 반영, 보다 정확한 대체
    • 단점: 복잡도 증가, 계산 비용 증가

평균으로 대체

  • 평균으로 결측치 대체
m = df_miss['mileage'].mean()
impute_mean = df_miss['mileage'].fillna(m)

impute_mean.mean()
impute_mean.std()
  • 분포 시각화
sns.kdeplot(df['mileage'], label='original')
sns.kdeplot(impute_mean, label='mean')
plt.legend()

원 데이터와 평균 대체 후 mileage 분포 비교

예측 대체

  • KNN(K=2)로 예측
from sklearn.impute import KNNImputer

imputer = KNNImputer(n_neighbors=2, weights="uniform")
X = imputer.fit_transform(df_miss[['mileage', 'price', 'year']])
impute_knn = X[:, 0]

impute_knn.mean()
impute_knn.std()
  • 분포 시각화
sns.kdeplot(df_miss['mileage'], label='original')
sns.kdeplot(impute_knn, label='knn')
plt.legend()

결측치 데이터와 KNN 대체 후 mileage 분포 비교

Previous
하이퍼파라미터 튜닝