Dealing with NA-01
강의 홍보
- 취준생을 위한 강의를 제작하였습니다.
- 본 블로그를 통해서 강의를 수강하신 분은 게시글 제목과 링크를 수강하여 인프런 메시지를 통해 보내주시기를 바랍니다.
스타벅스 아이스 아메리카노를 선물
로 보내드리겠습니다.
- [비전공자 대환영] 제로베이스도 쉽게 입문하는 파이썬 데이터 분석 - 캐글입문기
공지
제 수업을 듣는 사람들이 계속적으로 실습할 수 있도록 강의 파일을 만들었습니다. 늘 도움이 되기를 바라며. 참고했던 교재 및 Reference
는 꼭 확인하셔서 교재 구매 또는 관련 Reference
를 확인하시기를 바랍니다.
I. Overview
실제 데이터를 다루다보면 여러가지 이유로 결측치와 마주하게 된다. 특별한 이유가 없다면, 현업에서는 당연히 NA를 처리해야 한다. 그렇지 않다면 데이터 분석(시각화, 통계, 머신러닝 모형)에 영향을 줄 수 밖에 없다.
원인은 크게 3가지로 구분될 수 있지만, 이러한 주제는 보통 논문을 통해서 다뤄지니, 여기에서는 일단 건너뛰자. (You like theory?)
질문, 어떻게 처리해야 할까? (결국 이런걸 원하는 것이니!)
참고로, 여기에서는
Module
설치 등은 다루지 않으며, 데이터는Scikit-learn
의California Housing Dataset
을 참고했다.
방법 1. 아무것도 하지 않는다!
굉장히 편한 방법이다. 그런데, 이 방법론을 쓰려면 알고리즘을 잘 선택해야 한다. XGBoost
와 같은 알고리즘은 NA값에 대해 대체할 만한 가장 최적의 것으로 대체하며 학습하기도 하지만, 일반적으로 선형회귀모형은 결과가 도출되지 않는다.
방법 2. 평균 또는 중간값으로 대체
Numeric
데이터에만 적용이 가능하고, 각 Column마다 독립적으로 적용을 한다.
- 장점: 쉽고 빠르고, small 데이터에 적용이 가능하다.
- 단점 (오역 방지차 원문을 그대로 인용)
- Doesn’t factor the correlations between features. It only works on the column level.
- Will give poor results on encoded categorical features (do NOT use it on categorical features).
- Not very accurate
- Doesn’t account for the uncertainty in the imputations
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import mean_squared_error
from math import sqrt
import random
import numpy as np
random.seed(20200502)
(1) Fetching 데이터
데이터를 가져와서 확인해보자.
import pandas as pd
from tabulate import tabulate
dataset = fetch_california_housing()
train, target = pd.DataFrame(dataset.data), pd.DataFrame(dataset.target)
train_columns = ['0', '1', '2', '3', '4', '5', '6' '7']
train.insert(loc=len(train_columns), column='target', value=target)
print(tabulate(train.head(5), tablefmt='pipe', headers='keys'))
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | target | 7 |
|---:|-------:|----:|--------:|--------:|-----:|--------:|------:|---------:|--------:|
| 0 | 8.3252 | 41 | 6.98413 | 1.02381 | 322 | 2.55556 | 37.88 | 4.526 | -122.23 |
| 1 | 8.3014 | 21 | 6.23814 | 0.97188 | 2401 | 2.10984 | 37.86 | 3.585 | -122.22 |
| 2 | 7.2574 | 52 | 8.28814 | 1.07345 | 496 | 2.80226 | 37.85 | 3.521 | -122.24 |
| 3 | 5.6431 | 52 | 5.81735 | 1.07306 | 558 | 2.54795 | 37.85 | 3.413 | -122.25 |
| 4 | 3.8462 | 52 | 6.28185 | 1.08108 | 565 | 2.18147 | 37.85 | 3.422 | -122.25 |
(2) NA값 임의 대체
실험을 위해 첫번째 Column에 약 40%에 해당하는 Column
을 부여한다.
column = train[0] # 20640
missing_pct = int(column.size * 0.4) # 8256
i = [random.choice(range(column.shape[0])) for _ in range(missing_pct)]
print(i[0:5])
[17455, 4645, 7718, 11453, 7913]
column[i]=np.NaN
column.head(10)
0 8.3252
1 8.3014
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
8 2.0804
9 NaN
Name: 0, dtype: float64
(3) 평균값 대체 (Scikit-learn) 활용
Scikit-learn 모듈을 활용해서 결측값을 대체해보자.
from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer(strategy='mean')
imputed_DF = pd.DataFrame(imp_mean.fit_transform(train))
print(tabulate(imputed_DF.head(10), tablefmt='pipe', headers='keys'))
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---:|--------:|----:|--------:|---------:|-----:|--------:|------:|------:|--------:|
| 0 | 8.3252 | 41 | 6.98413 | 1.02381 | 322 | 2.55556 | 37.88 | 4.526 | -122.23 |
| 1 | 8.3014 | 21 | 6.23814 | 0.97188 | 2401 | 2.10984 | 37.86 | 3.585 | -122.22 |
| 2 | 3.86937 | 52 | 8.28814 | 1.07345 | 496 | 2.80226 | 37.85 | 3.521 | -122.24 |
| 3 | 3.86937 | 52 | 5.81735 | 1.07306 | 558 | 2.54795 | 37.85 | 3.413 | -122.25 |
| 4 | 3.86937 | 52 | 6.28185 | 1.08108 | 565 | 2.18147 | 37.85 | 3.422 | -122.25 |
| 5 | 3.86937 | 52 | 4.76166 | 1.10363 | 413 | 2.1399 | 37.85 | 2.697 | -122.25 |
| 6 | 3.86937 | 52 | 4.93191 | 0.951362 | 1094 | 2.1284 | 37.84 | 2.992 | -122.25 |
| 7 | 3.86937 | 52 | 4.79753 | 1.06182 | 1157 | 1.78825 | 37.84 | 2.414 | -122.25 |
| 8 | 2.0804 | 42 | 4.29412 | 1.11765 | 1206 | 2.02689 | 37.84 | 2.267 | -122.26 |
| 9 | 3.86937 | 52 | 4.97059 | 0.990196 | 1551 | 2.17227 | 37.84 | 2.611 | -122.25 |
첫번째 행의 값을 보면, NaN
이 3.869374
형태로 바뀐 것을 볼 수가 있다.
만약, 바로 머신러닝 모형에 적용한다면, 2D array 형태로 출력해야 되면, 다음과 같은 코드를 유지하면 된다.
imp_mean = SimpleImputer(strategy='mean')
imp_mean.fit(train)
imputed_train_df = imp_mean.transform(train)
for i in range(len(imputed_train_df)):
if i < 10:
print(i, "-", imputed_train_df[i])
0 - [ 8.3252 41. 6.98412698 1.02380952 322.
2.55555556 37.88 4.526 -122.23 ]
1 - [ 8.30140000e+00 2.10000000e+01 6.23813708e+00 9.71880492e-01
2.40100000e+03 2.10984183e+00 3.78600000e+01 3.58500000e+00
-1.22220000e+02]
2 - [ 3.86937366 52. 8.28813559 1.07344633 496.
2.80225989 37.85 3.521 -122.24 ]
3 - [ 3.86937366 52. 5.8173516 1.07305936 558.
2.54794521 37.85 3.413 -122.25 ]
4 - [ 3.86937366 52. 6.28185328 1.08108108 565.
2.18146718 37.85 3.422 -122.25 ]
5 - [ 3.86937366 52. 4.76165803 1.10362694 413.
2.13989637 37.85 2.697 -122.25 ]
6 - [ 3.86937366e+00 5.20000000e+01 4.93190661e+00 9.51361868e-01
1.09400000e+03 2.12840467e+00 3.78400000e+01 2.99200000e+00
-1.22250000e+02]
7 - [ 3.86937366e+00 5.20000000e+01 4.79752705e+00 1.06182380e+00
1.15700000e+03 1.78825348e+00 3.78400000e+01 2.41400000e+00
-1.22250000e+02]
8 - [ 2.08040000e+00 4.20000000e+01 4.29411765e+00 1.11764706e+00
1.20600000e+03 2.02689076e+00 3.78400000e+01 2.26700000e+00
-1.22260000e+02]
9 - [ 3.86937366e+00 5.20000000e+01 4.97058824e+00 9.90196078e-01
1.55100000e+03 2.17226891e+00 3.78400000e+01 2.61100000e+00
-1.22250000e+02]
방법 3. 최빈값 대체
최빈값은, 특정 Column에서 가장 많이 나타나는 값으로 대체하는 것이다. 특히 이 방법은 categorical features
를 다룰 때 사용한다. 그러나, 데이터에 자칫 편향성을 가져다 줄 수 있다.
column = train[1] # 20640
missing_pct = int(column.size * 0.4) # 8256
i = [random.choice(range(column.shape[0])) for _ in range(missing_pct)]
column[i]=np.NaN
column.head(10)
0 41.0
1 21.0
2 52.0
3 NaN
4 NaN
5 NaN
6 NaN
7 52.0
8 42.0
9 52.0
Name: 1, dtype: float64
from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer(strategy='most_frequent')
imputed_DF = pd.DataFrame(imp_mean.fit_transform(train))
print(tabulate(imputed_DF.head(10), tablefmt='pipe', headers='keys'))
| | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---:|-------:|----:|--------:|---------:|-----:|--------:|------:|------:|--------:|
| 0 | 8.3252 | 41 | 6.98413 | 1.02381 | 322 | 2.55556 | 37.88 | 4.526 | -122.23 |
| 1 | 8.3014 | 21 | 5 | 0.97188 | 2401 | 2.10984 | 37.86 | 3.585 | -122.22 |
| 2 | 3.125 | 52 | 8.28814 | 1.07345 | 496 | 2.80226 | 37.85 | 3.521 | -122.24 |
| 3 | 3.125 | 52 | 5.81735 | 1.07306 | 558 | 2.54795 | 37.85 | 3.413 | -122.25 |
| 4 | 3.125 | 52 | 5 | 1.08108 | 565 | 2.18147 | 37.85 | 3.422 | -122.25 |
| 5 | 3.125 | 52 | 5 | 1.10363 | 413 | 2.1399 | 37.85 | 2.697 | -122.25 |
| 6 | 3.125 | 52 | 4.93191 | 0.951362 | 1094 | 2.1284 | 37.84 | 2.992 | -122.25 |
| 7 | 3.125 | 52 | 5 | 1.06182 | 1157 | 1.78825 | 37.84 | 2.414 | -122.25 |
| 8 | 2.0804 | 42 | 5 | 1.11765 | 1206 | 2.02689 | 37.84 | 2.267 | -122.26 |
| 9 | 3.125 | 52 | 5 | 0.990196 | 1551 | 2.17227 | 37.84 | 2.611 | -122.25 |
1
Column을 보면 알겠지만, 모두 가장 빈번하게 나온 52
로 대체가 된 것을 확인할 수 있다.
결론
결측치 처리에 대해 잠깐 다뤘다. 각각의 방법론에는 모두 장단점이 있기 때문에 신중을 기해야 하며, 특히, numeric feature
에 적용해야 할 방법과 categorical feature
에 적용해야 할 방법에 대해 구분 지어서 생각을 해야 한다.
다음 시간에는 통계적 기법을 활용한 결측치 대체에 대해 포스팅을 하도록 하겠다. 작은 도움이 되기를 바란다.