Skip to main content

데이터 클리닝 및 전처리 기법

import pandas as pd
df = pd.read_excel('car.xlsx')
df.head() # 데이터 첫 5줄 보기
output
   mileage model  price  year  my_car_damage  other_car_damage
0 63608 K3 970 2017 0 564596
1 69336 K3 1130 2015 1839700 1140150
2 36000 K3 1380 2016 446520 2244910
3 19029 K3 1390 2017 889000 4196110
4 97090 K3 760 2015 2339137 2029570

결측값 처리

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
# 10%의 확률로 x를 nan(Not A Number, 결측값)으로 대치
)

원래 데이터 크기

df.shape
output
(274, 6)

결측치 있는 행 보기

df_miss[df_miss['mileage'].isnull()].head()
# df_miss['mileage'].isnull(): mileage의 결측 되었는가?
# df_miss[ ... ]: 위의 조건이 참인 행만
output
    mileage model  price  year  my_car_damage  other_car_damage
6 NaN K3 899 2015 2229758 1877410
10 NaN K3 1490 2015 0 0
29 NaN K3 890 2012 1680780 147980
32 NaN K3 780 2013 603000 0
37 NaN K3 800 2014 3004070 4657280

mileage에 결측치 없는 행 보기

df_miss[df_miss['mileage'].notnull()].head()
# df_miss['mileage'].isnull(): mileage의 결측 안되었는가?
# df_miss[ ... ]: 위의 조건이 참인 행만
output
   mileage model  price  year  my_car_damage  other_car_damage
0 63608.0 K3 970 2017 0 564596
1 69336.0 K3 1130 2015 1839700 1140150
2 36000.0 K3 1380 2016 446520 2244910
3 19029.0 K3 1390 2017 889000 4196110
4 97090.0 K3 760 2015 2339137 2029570
df_miss.dropna().head()
output
   mileage model  price  year  my_car_damage  other_car_damage
0 63608.0 K3 970 2017 0 564596
1 69336.0 K3 1130 2015 1839700 1140150
2 36000.0 K3 1380 2016 446520 2244910
3 19029.0 K3 1390 2017 889000 4196110
4 97090.0 K3 760 2015 2339137 2029570
df['mileage'].mean()
output
77483.22262773722

결측치가 있는 데이터의 평균(원래와 달라지지만 오차가 커진 것일 뿐 더 편향된 것은 아님)

df_miss['mileage'].mean()
output
79713.21224489796

기본적으로 결측치는 삭제하고 평균냄(skipna=True). 필요는 없지만 굳이 쓴다고하면 결측치도 확인하고 싶고, 결측치가 없을 때는 평균도 내고 싶을 때 한 줄로 처리하려는 의도.

df_miss['mileage'].mean(skipna=False)
output
nan

아는 값과 모르는 값(결측치)를 계산하면 그 결과도 모름. 하나라도 결측치가 포함이 되어있으면 통계치를 계산할 수 없음

1 + np.nan
output
nan

KDE(Kernel Density Estimation): 확률분포를 대략 추정하는 기법

정규분포는 언제 생기나? 독립적인 변수들이 많이 있어서 서로 더해질 때 생김. 키에 영향을 주는 유전자 10000개 쯤 있음(사람마다 태어날때 동전 10000개를 던져서 그 합계만큼이 자기 키)

두 개의 플롯 그리는 코드를 한 셀에서 실행하면 겹쳐서 그려줌

import seaborn as sns
import matplotlib.pyplot as plt
sns.kdeplot(df['mileage'], label='original')
sns.kdeplot(df_miss['mileage'], label='missing')
plt.legend() # 범례 표시
output
<matplotlib.legend.Legend at 0x17ff87e88e0>
<Figure size 640x480 with 1 Axes>

평균으로 대체하면 결측값을 모두 평균으로 바꾸기 때문에 변동성이 감소

m = df_miss['mileage'].mean() # 평균
impute_mean = df_miss['mileage'].fillna(m) # 결측값을 대체

sns.kdeplot(df['mileage'], label='original')
sns.kdeplot(impute_mean, label='mean')
plt.legend()
output
<matplotlib.legend.Legend at 0x17fea46dd30>
<Figure size 640x480 with 1 Axes>

KNN(K Nearest Neighbor): K개의 가장 비슷한 사례를 참조

비슷하다는 것은 결측되지 않은 값이 비슷하다는 것(거리를 계산해서 거리가 가까움) 루트((x1 - x2)**2 + (y1 - y2)**2)

K=2: 비슷한 사례를 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]
sns.kdeplot(df_miss['mileage'], label='original')
sns.kdeplot(impute_knn, label='knn')
plt.legend()
output
<matplotlib.legend.Legend at 0x17fea649610>
<Figure size 640x480 with 1 Axes>

삭제나 대체로는 평균에는 큰 차이가 없을 수도 있다.

df['mileage'].mean()
output
77483.22262773722
df_miss['mileage'].mean() # 리스트 삭제를 했을 때 평균
output
79713.21224489796
impute_mean.mean() # 평균 대체를 했을 때 평균
output
79713.21224489798
impute_knn.mean() # 예측 대체를 했을 때 평균
output
78922.5602189781
표준편차: 데이터가 퍼져 있는 정도
df['mileage'].std()
output
43015.79445317075
df_miss['mileage'].std()
output
44020.89207331192
impute_mean.std() # 평균 대체를 하면 표준편차가 감소(똑같은 값으로 채워넣기 때문에)
output
41617.15891448403
impute_knn.std() # 예측 대체를 했을 때 표준편차가 조금 덜 감소(차마다 채워넣는 값이 달라서)
output
42832.918597044976

표준편차가 원본 데이터랑 가깝게 나오는게 대체가 적당히 잘 되었다는 것을 수치적으로 볼 수 있는 건가요? 원본데이터보다 너무 차이가 나지 않는 대체를 하는 것이 좋다는 뜻으로 이해해도 되나요?

표준편차만이 아니라 원본 데이터의 분포에 가깝게 나와야함(모든 통계치가 똑같게 나와야한다는 뜻)

표준편차도 다르다면 다른 통계치도 다르게 나올 수 있음

현실에서는 이걸 확인해볼 수가 없음(우리는 결측된 데이터만 볼 수 있고, 원본은 볼 수 없음)

상당 부분이 가정에 의존함

df.mileage.min() # 최솟값
output
2287
df.mileage.max() # 최댓값
output
310000
df.mileage.mean() # 평균
output
77483.22262773722
df.mileage.std() # 표준편차
output
43015.79445317075

스케일링

정규화

from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
mileage_normalized = scaler.fit_transform(df[['mileage']])
mileage_normalized[:5]
output
array([[0.19927985],
[0.2178946 ],
[0.10955988],
[0.05440784],
[0.30808903]])

표준화

X=XμσX' = \frac{X - \mu}{\sigma}
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
mileage_standardized = scaler.fit_transform(df[['mileage']])
mileage_standardized[:5]
output
array([[-0.32315135],
[-0.1897473 ],
[-0.96613652],
[-1.36138794],
[ 0.45663819]])

비대칭 데이터 변환

log_transformed = np.log(df['mileage'])
sns.kdeplot(log_transformed, label='log')
output
<Axes: xlabel='mileage', ylabel='Density'>
<Figure size 640x480 with 1 Axes>

from scipy import stats
boxcox_transformed, lambda_value = stats.boxcox(df['mileage'])
sns.kdeplot(boxcox_transformed, label='boxcox')
output
<Axes: ylabel='Density'>
<Figure size 640x480 with 1 Axes>

lambda_value
output
0.5323138494091241

범주형 데이터

원핫인코딩

from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse_output=False)
X = encoder.fit_transform(df[['model']])
X[:5]
output
array([[0., 1.],
[0., 1.],
[0., 1.],
[0., 1.],
[0., 1.]])
df['model'].str.get_dummies()
output
     Avante  K3
0 0 1
1 0 1
2 0 1
3 0 1
4 0 1
.. ... ..
269 1 0
270 1 0
271 1 0
272 1 0
273 1 0

[274 rows x 2 columns]

더미 코딩

다중공선성: 여러 개의 변수로 함께 선형적인 성질을 띈다(다른 변수로 조합해서 만들 수 있다)

x1 x2 y 1 1 2 2 2 4

y = x1 + x2, y = 2x1, y = 2x2, y = 3x1 - x2, ..

encoder = OneHotEncoder(sparse_output=False, drop='first')
X = encoder.fit_transform(df[['model']])
X[:5]
output
array([[1.],
[1.],
[1.],
[1.],
[1.]])

타겟 인코딩

df.groupby('model').agg({'price': 'mean'}) # 모델별 평균가격
output
             price
model
Avante 833.414634
K3 913.811594
from sklearn.preprocessing import TargetEncoder
encoder = TargetEncoder(target_type='continuous')
model_encoded = encoder.fit_transform(df[['model']], df['price'])
model_encoded[:5]
output
array([[889.81206363],
[907.43802815],
[889.81206363],
[917.55272079],
[915.5145453 ]])

퀴즈