결측치가 발생한 변수의 값에 상관없이 전체에 걸쳐 무작위로 발생한 경우
통계적으로 결측치의 영향이 없으므로 제거 가능
ex) 설문조사에서 응답자가 어떤 질문을 건너뛴 경우
결측치가 발생한 변수의 값이 다른 변수와 상관관계가 있어 추정이 가능한 경우
통계적으로 결측치의 영향이 다소 있으나 편향은 없으므로 대체 가능
ex) 여성 응답자들이 특정 질문(예: 소득 관련 질문)에 대해 답변하지 않는 경향이 있다면, '성별' 변수가 결측치 발생 확률에 영향을 주는 것
ex) 고소득자일수록 소득 정보를 제공하지 않는 경향이 있다면 '소득' 변수 자체가 결측치 발생 확률에 영향을 주는 것
import pandas as pd
# 데이터세트 불러오기
df = pd.read_csv('../data/preprocessing_students.csv', sep=',')
df.head()
df.info()
다음과 같은 결과에서
이 정보는 pandas의 DataFrame에 대한 정보를 나타내는 것으로, 각 열(변수)에 대한 세부 정보를 제공
요약하면 주어진 DataFrame 객체는 id, name, sex, height, final_score 열에서 결측치가 없으며, weight, IQ, mid_score, employed 열에서는 일부 결측치를 가지고 있음
# isnull()의 True 개수를 합하여 확인
print(df.isnull().sum(axis=0)) # axis = 0 열기준, 1 행기준
pandas DataFrame에서 결측치의 수를 계산하는 방법
따라서 전체 코드의 의미는 "표(df)에서 각 열마다 비어 있는 값(결측치)이 몇 개인지 찾아내서 화면에 보여주기"
!pip install klib
klib 설치후에.
import klib
import warnings
# 경고 메시지 무시
warnings.filterwarnings(action='ignore')
# 결측치에 대한 프로파일링 플롯
klib.missingval_plot(df)
'klib'라이브러리의 'missingval_plot' 함수를 사용하여 데이터프레임(df) 내의 결측치(missing value, 비어있는 값) 분포를 시각화
# 결측치에 대한 프로파일링 플롯
klib.missingval_plot(df, sort=True)
이 코드는 'klib' 라이브러리의 'missingval_plot' 함수를 사용하여 데이터프레임(df) 내의 결측치(비어있는 값) 분포를 시각화하고, 그 결과를 결측치가 많은 순서대로 정렬해서 보여쥼
sort=True 옵션은 결측치가 많은 열(column)을 위로 정렬하라는 의미
# 상관관계 플롯
klib.corr_plot(df)
변수간 상관분석 결과 weight와 height의 상관관계가 강한 양의 상관관계(0.78), IQ와 mid_score가 양의 상관관계(0.59), mid_score와 final_score가 양의 상관관계(0.53)을 보이고 있음
# 한글이 안나올 경우 폰트 지정
import matplotlib.pyplot as plt
plt.rc('font', family='Malgun Gothic')
-> 사실상 아래코드와 연관은 없음
# 범주형 변수에 대한 분석
klib.cat_plot(df)
# 결측치가 있는 변수의 분포 확인
klib.dist_plot(df.weight)
klib.dist_plot(df.IQ)
klib.dist_plot(df.mid_score)
weight는 특정한 패턴이 보이지 않음
IQ는 중간 영역대의 밀도가 낮아 중간영역대의 데이터가 누락되었음을 확인 가능
mid_score는 낮은 점수대의 데이터가 누락되었음을 확인 가능
제거(deletion)
대체(imputation)
import pandas as pd
# Listwise deletion
df_listwise = df.dropna()
# Pairwise deletion
df_pairwise = df.dropna(subset=['weight', 'mid_score'])
print(f'Original Data:\n {df}\n')
print(f'Listwise deletion:\n {df_listwise}\n')
print(f'Pairwise deletion:\n {df_pairwise}\n')
df_listwise = df.dropna(): 이 코드는 'listwise deletion'(전체 행 삭제) 방법을 사용하여 결측치를 처리
즉, 어떤 행에 하나라도 결측치가 있다면 그 전체 행을 삭제
df_pairwise = df.dropna(subset=['weight', 'mid_score']): 이 코드는 'pairwise deletion'(부분적인 삭제) 방법을 사용하여 결측치를 처리
여기서는 오직 'weight'와 'mid_score' 열에 대해서만 고려하며, 이 열들 중 어느 하나라도 결측치가 있는 행만 삭제
'\n'(줄바꿈 문자)
💠 단순대체법 종류
from sklearn.impute import SimpleImputer
df_imputed = pd.DataFrame.copy(df)
# 110대가 결측인 IQ는 평균으로 대체
df_imputed[['IQ']] = SimpleImputer(strategy="mean").fit_transform(df[['IQ']])
# 비대칭 분포를 갖는 mid_score는 중앙값으로 대체
df_imputed[['mid_score']] = SimpleImputer(strategy="median").fit_transform(df[['mid_score']])
# 범주형 employed는 Hot deck으로 대체
df_imputed['employed'].fillna(method='bfill', inplace=True)
# height와 양의 상관관계가 있는 weight는 Stochastic regression으로 대체
from sklearn.linear_model import LinearRegression
import numpy as np
# 결측치가 있는 인덱스 검색
idx = df.weight.isnull() == True
# 학습을 위한 데이터 세트 분리
X_train, X_test, y_train = df[['height']][~idx], df[['height']][idx], df[['weight']][~idx]
# 선형회귀모형 인스탄스 생성 후 학습
lm = LinearRegression().fit(X_train, y_train)
# 예측값 + 변동값하여 결측치를 대체
df_imputed.loc[idx, 'weight'] = lm.predict(X_test) + 5*np.random.rand(4,1)
df_imputed
fit_transform()은 사이킷런(scikit-learn) 라이브러리의 변환기(transformer) 클래스에 포함된 메소드
이 메소드는 '학습'과 '변환' 두 가지 작업을 한 번에 수행
fit(): 이 단계에서는 변환기가 데이터를 분석하고, 그 결과를 내부적으로 저장
예를 들어 SimpleImputer(strategy="median")의 경우, fit() 메소드는 데이터에서 각 열의 중앙값을 계산하고 이 값을 내부에 저장
transform(): 이 단계에서는 fit() 단계에서 학습한 정보(여기서는 중앙값)를 사용하여 데이터를 변환
SimpleImputer의 경우, transform() 메소드는 결측치를 해당 열의 중앙값으로 대체
fit_transform()은 두 단계를 한 번에 수행
정리하면 ,
'mid_score' 열의 중앙값을 계산(fit)
'mid_score' 열의 결측치를 그 중앙값으로 대체(transform)
그 결과로 반환된 데이터프레임을 df_imputed의 'mid_score' 열에 할당
df_imputed[['mid_score']] = SimpleImputer(strategy="median").fit_transform(df[['mid_score']]): 'mid_score' 열의 결측치를 중앙값으로 대체
df_imputed['employed'].fillna(method='bfill',
inplace=True): 'employed' 열의 결측치를 Hot deck 방식으로 대체
Hot deck 방식은 바로 다음 행의 값('bfill': backward fill) 또는 바로 앞 행의 값('ffill': forward fill)으로 결측치를 채우는 방법
선형 회귀 모델(LinearRegression)을 사용해서 'weight'열의 결측값을 예상하고 그 값을 채워 넣는것
idx = df.weight.isnull() == True
idx 변수에 weight 칼럼이 null인 경우 True 값을 저장
X_train, X_test, y_train = df[['height']][~idx], df[['height']][idx], df[['weight']][~idx]
학습용 데이터(X_train, y_train)와 예상해야 하는 데이터(X_test)로 분리
lm = LinearRegression().fit(X_train, y_train)
선형회귀모형 인스턴스(lm) 생성 후 학습용 데이터로 학습(fit())시킵니다.
"인스턴스 생성"은 특정 클래스로부터 객체를 만드는 과정을 의미
df_imputed.loc[idx, 'weight'] = lm.predict(X_test) + 5*np.random.rand(4,1)
'weight' 열의 결측치를 선형 회귀 모델을 사용해 예측한 값과 랜덤한 변동값을 더한 값으로 대체하는 작업을 수행합니다.
lm.predict(X_test): 이 부분은 선형 회귀 모델(lm)을 사용해 'height'에 대응하는 'weight' 값을 예측
여기서 X_test는 'height' 열에서 결측치가 있는 행
5*np.random.rand(4,1): 이 부분은 0부터 1사이의 균일 분포(uniform distribution)에서 랜덤하게 생성된 숫자에 5를 곱해서 변동값을 만듬
(4,1)은 변동값을 생성할 배열의 크기(shape)를 의미하며, 이 경우에는 4행 1열짜리 배열 -> 임의적 변동성을 만드는것
lm.predict(X_test) + 5*np.random.rand(4,1): 위 두 부분을 합치면, 선형 회귀로 예측한 값에 임의의 변동값이 추가된 결과
df_imputed.loc[idx, 'weight'] = ...: 마지막으로 .loc 메소드를 사용해 df_imputed 데이터프레임에서 결측치가 있는 위치(idx)에 위에서 계산한 값을 할당하여 원래의 결측치를 대체
코드는 선형 회귀로 예상된 값과 임의적인 변동성을 고려하여 원래 데이터셋에서 발생한 'weight' 열의 결측치를 보완하는 방법
결측치의 대체값을 여러 추정값을 종합하여 선정하는 것
Multiple Imputation 3단계
import numpy as np
# scikit-learn에서 R의 MICE 패키지를 따라서 실험적으로 개발 중
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
# 데이터 세트
X_train = [[33, np.nan, .153], [18, 12000, np.nan], [np.nan, 13542, .125]]
X_test = [[45, 10300, np.nan], [np.nan, 13430, .273], [15, np.nan, .165]]
두 개의 2차원 리스트를 생성하고, 각각 X_train과 X_test라는 변수에 할당하는 과정
# mice 인스탄스 생성 (붕어빵틀..)
mice = IterativeImputer(max_iter=10, random_state=0)
mice.fit(X_train)
np.set_printoptions(precision=5, suppress=True)
print('X_train MICE: \n', mice.transform(X_train))
print('X_test MICE: \n', mice.transform(X_test))
결측치가 대체된 데이터 출력과정...
과정이 너무 복잡해서 여기는 그냥 그렇다고 기억.
import numpy as np
from sklearn.impute import KNNImputer
knn = KNNImputer(n_neighbors=2, weights="uniform")
knn.fit(X_train)
print('X_train KNN: \n', knn.transform(X_train))
print('X_test KNN: \n', knn.transform(X_test))