6 minute read

6.2 데이터 전처리

앞서 6.1에서 살펴본 사이킷런에서 제공하는 데이터와 달리 보통 머신러닝 데이터들은 데이터 전처리가 필요함

‘피처 엔지니어링’ 이라 불림

결측치 처리 / 클래스 라벨 설정 / 원-핫 인코딩 / 데이터 스케일링 등이 있다.

6.2.1 결측치 처리

주어진 데이터 셋에 데이터가 존재하지 않는다? 빵꾸가 나있다?(missing value) 이 ‘결측치’를 처리해줘야 한다.

import numpy as np
import pandas as pd
# 먼저 이 결측치를 포함하는 데이터 프레임을 생성한다 
# 데이터 프레임 직접 생성
df = pd.DataFrame([
                [42, 'male',12,'reading','class2'],
                 [35, 'unknown',3,'cooking','class1'],
                 [1000, 'female',7,'cycling','class3'],
                 [1000, 'unknown',21,'unknown','unknown']
])

# columns이름 정해주기 
df.columns = ['age','gender', 'month_birth', 'hobby', 'target']
# 생성한 데이터 프레임 출력해보기 
df
age gender month_birth hobby target
0 42 male 12 reading class2
1 35 unknown 3 cooking class1
2 1000 female 7 cycling class3
3 1000 unknown 21 unknown unknown

모두 값이 존재하긴하나, 부적절한 값들이 있을 때 그것을 결측치로 여겨야 한다. 고로 이 ‘부적절’한 값들을 ‘결측치’로 변경해야 한다.

# 먼저 unique() 메소드로 중복 제거한 해당 각 column의 값들을 array로 출력. 
df['age'].unique()
# df['gender'].unique()
# df['month_birth'].unique()
# df['hobby'].unique()
# df['target'].unique()
array([  42,   35, 1000])

부적절한 해당 값들을 np.nan을 이용하여 결측치 처리할 것이다.

nan 이 결측치를 의미한다

# 결측치로 세팅
df.loc[df['age'] > 150, ['age']] = np.nan
df.loc[df['gender'] == 'unknown', ['gender']] = np.nan
df.loc[df['month_birth'] > 12, ['month_birth']] = np.nan
df.loc[df['hobby'] == 'unknown', ['hobby']] = np.nan
df.loc[df['target'] == 'unknown', ['target']] = np.nan
# 결과 확인
df
age gender month_birth hobby target
0 42.0 male 12.0 reading class2
1 35.0 NaN 3.0 cooking class1
2 NaN female 7.0 cycling class3
3 NaN NaN NaN NaN NaN

결측치를 포함하는 데이터는 머신러닝 모델에 적합하지 않은 경우가 많다

고로, 처음 데이터셋을 접했을 때, 결측치가 존재하는지 확인해야 한다. (부적절한 값이 존재하면 결측치로 바꿔줘야함)

# 각 열마다 결측치가 몇개 있는지 확인
df.isnull().sum()
age            2
gender         2
month_birth    1
hobby          1
target         1
dtype: int64

- 삭제하는 방식은 df.dropna() 메소드로 처리

# 결측치 포함했다면 해당 row(data) 삭제하는 방식으로 처리
df2 = df.dropna(axis=0)
df2
age gender month_birth hobby target
0 42.0 male 12.0 reading class2
# 결측치 포함했다면 해당 column 삭제하는 방식으로 처리
# 모든 column에 결측치 있으므로 출력되는 것이 없을 것임
df3 = df.dropna(axis=1)
df3
0
1
2
3
# 모든 값이 결측치인 row 삭제하기
df4 = df.dropna(how = 'all')
df4
age gender month_birth hobby target
0 42.0 male 12.0 reading class2
1 35.0 NaN 3.0 cooking class1
2 NaN female 7.0 cycling class3
# 결측치 제외한 값이 thresh = n 개보다 작을 경우, 해당 row를 삭제
df5 = df.dropna(thresh=2)
df5
age gender month_birth hobby target
0 42.0 male 12.0 reading class2
1 35.0 NaN 3.0 cooking class1
2 NaN female 7.0 cycling class3
# 특정 column에 결측치가 있는 경우 해당 row삭제 (gender column에 결측치 있을 때 해당 row 삭제)
df6 = df.dropna(subset = ['gender'])
df6
age gender month_birth hobby target
0 42.0 male 12.0 reading class2
2 NaN female 7.0 cycling class3
# 결측치 대체하기 
# 왜 에러나는지 잘 모르겠음(after_values는 왜 안될까)
# fillna(0) 등으로 특정값으로 그냥 대체하면 될 것 같음

# after_values = ('age': 0,
#                 'gender' : 'U',
#                 'month_birth' : 0,
#                 'hobby' : 'U',
#                 'target' : 'class4')

df7 = df

# 다른 방식으로 대체 
df7['age'] = df7['age'].fillna(0)
df7['gender'] = df7['gender'].fillna('U')
df7['month_birth'] = df7['month_birth'].fillna(0)
df7['hobby'] = df7['hobby'].fillna('U')
df7['target'] = df7['target'].fillna('class4')

df7
age gender month_birth hobby target
0 42.0 male 12.0 reading class2
1 35.0 U 3.0 cooking class1
2 0.0 female 7.0 cycling class3
3 0.0 U 0.0 U class4

6.2.2 클래스 라벨 설정

# 현재 df는 타겟(라벨)이 string이다. int로 바꿔줘보자

# 사이킷런의 preprocessing 패키지의 LabeEncoder 함수 import
from sklearn.preprocessing import LabelEncoder as LE
df8 = df7

# 라벨링 모델을 LE로 설정
class_label = LE()

# 'target' 의 값들을 data_value에 저장 
data_value = df8['target'].values

# 라벨링 모델 'class_label'에 데이터 값을 넣고 변환한 값을 y_new에 저장
y_new = class_label.fit_transform(data_value)

# 결과 확인
y_new
array([1, 0, 2, 3])
# 실제 df8 데이터 프레임에 int로 바꿔준 target column(라벨링)을 대체 
df8['target'] = y_new
df8
age gender month_birth hobby target
0 42.0 male 12.0 reading 1
1 35.0 U 3.0 cooking 0
2 0.0 female 7.0 cycling 2
3 0.0 U 0.0 U 3
# 원래대로 되돌리고 싶다면?
y_ori = class_label.inverse_transform(y_new)
y_ori

df8['target'] = y_ori
df8
age gender month_birth hobby target
0 42.0 male 12.0 reading class2
1 35.0 U 3.0 cooking class1
2 0.0 female 7.0 cycling class3
3 0.0 U 0.0 U class4
사이킷런 사용하지 않고 직접 라벨링하는 법을 알아보자!
# label들을 담은 배열을 만든다
y_arr = df8['target'].values
y_arr.sort()
y_arr
array(['class1', 'class2', 'class3', 'class4'], dtype=object)
# dictionary를 구성한다 (string label : int label) pair로
num_y = 0
dic_y = {}
for ith_y in y_arr:
    dic_y[ith_y] = num_y
    num_y += 1

dic_y
{'class1': 0, 'class2': 1, 'class3': 2, 'class4': 3}
# 대체한다(에러발생)
# df8['target'] = df8['target'].replace(dic_y)

6.2.3 원-핫 인코딩

0과 1만을 써서 데이터값을 나타내는 것

dummy variable이라고도 불림

df9 = df8
# 타깃 라벨링을 현재 int인 것을 string으로 바꿔줌 
df9['target'] = df9['target'].astype(str)
df10 = pd.get_dummies(df9['target'])

# 타깃 0 은 1000 / 타깃 1 은 0100 ... 이런식으로 표현됨
print(df10)
df10
   0  1  2  3
0  1  0  0  0
1  0  1  0  0
2  0  0  1  0
3  0  0  0  1
0 1 2 3
0 1 0 0 0
1 0 1 0 0
2 0 0 1 0
3 0 0 0 1
# 벡터길이 3인 원-핫 인코딩 하기
df11 = pd.get_dummies(df9['target'], drop_first = True)

print(df11)
   1  2  3
0  0  0  0
1  1  0  0
2  0  1  0
3  0  0  1
# 특정 열이 아니라 데이터 프레임 전체의 column을 원-핫 인코딩하기 (column이 추가 변경된다 0,1로만 표현해야하므로)
df12 = df8
df13 = pd.get_dummies(df12)
df13
age month_birth gender_U gender_female gender_male hobby_U hobby_cooking hobby_cycling hobby_reading target_0 target_1 target_2 target_3
0 42.0 12.0 0 0 1 0 0 0 1 1 0 0 0
1 35.0 3.0 1 0 0 0 1 0 0 0 1 0 0
2 0.0 7.0 0 1 0 0 0 1 0 0 0 1 0
3 0.0 0.0 1 0 0 1 0 0 0 0 0 0 1
# 데이터 프레임 형식이 아닌 array형식으로 출력하기
from sklearn.preprocessing import OneHotEncoder as OHE

# 텐서플로우 라이브러리에서는 to_categorical() 함수 이용한다 
# from tensorflow.keras.utils import to_categorical
# y_hotec = to_categorical(y)
# 이런 식으로 진행하면 된다. 

# 원-핫 인코더 함수 불러오기
hot_encoder = OHE()

# 원-핫 인코딩 변수 설정
# 해당 target column을 인코딩할꺼니까 target column 값들을 array로 저장 
y = df7[['target']]

# fit_transform으로 주어진 데이터 원-핫 인코딩 한다. 
y_hot = hot_encoder.fit_transform(y)

# y_hot을 array형태로 출력 
print(y_hot.toarray())
[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]

6.2.4 데이터 스케일링

feature(=각 data의 column들)은 제각기 다른 단위를 가진다

데이터 스케일링은 ‘단위’에 영향받지 않도록 데이터를 변형하는 것을 말한다

(1) 표준화 스케일링(Standard Scaling)

평균이 0 , 표준편차가 1 이 되는 표준정규분포가 되도록 하는 scaling 방식

# df8의 month_birth feature를 표준화해보자 
from sklearn.preprocessing import StandardScaler as ss

# 표준화 스케일러의 이름을 std로 정함
std = ss()

# 표준화 스케일러에 해당 feature를 적합시킨다
std.fit(df8[['month_birth']])

# 해당 feature 값 실제로 변경하는 단계
# 반드시 fit과정을 거친 후 transform 해줘야함 안그러면 에러남
# 귀찮아서 하나로 합친 함수 등장 
x_std = std.transform(df8[['month_birth']])
x_std
array([[ 1.44444444],
       [-0.55555556],
       [ 0.33333333],
       [-1.22222222]])
# 위 fit 이후 , transform 해주는 과정을 하나로 압축!!! 
x_std2 = std.fit_transform(df8[['month_birth']])
x_std2
array([[ 1.44444444],
       [-0.55555556],
       [ 0.33333333],
       [-1.22222222]])
# 정말로 표준 정규분포를 따르는지 확인해볼까?
np.mean(x_std)
print(np.std(x_std))
1.0

(2) 로버스트 스케일링

표준화 스케일 변형한 방식으로 중강값(median)과 사분위수(quantile)를 사용한다.

극단값의 영향을 거의 받지 않는다는 장점

from sklearn.preprocessing import RobustScaler as rs

# 함수를 따로 호출을 반드시해줘야한다. 안해주면 에러남
rb = rs()

# 적합시키고, 변형
x_robust = rb.fit_transform(df8[['month_birth']])
x_robust

array([[ 1.16666667],
       [-0.33333333],
       [ 0.33333333],
       [-0.83333333]])

(3) 최소-최대 스케일링

데이터가 최댓값이 1, 최솟값이 0이 되도록 범위를 정하는 변형방식.

from sklearn.preprocessing import MinMaxScaler

mms = MinMaxScaler()
x_mms = mms.fit_transform(df8[['month_birth']])
x_mms
array([[1.        ],
       [0.25      ],
       [0.58333333],
       [0.        ]])

(4) 노멀 스케일링

Euclidean Length 가 1이 되도록 데이터값을 변경

벡터 길이는 상관 없고, 방향(각도)만 고려할 때 해당 전처리 방식을 사용한다

!! 앞선 세가지 스케일링과 달리 행 기준이다.

고로, age, month_birth 값을 이용하여 스케일링해보자

from sklearn.preprocessing import Normalizer

nm = Normalizer()

x_nm = nm.fit_transform(df8[['age','month_birth']])
x_nm
array([[0.96152395, 0.27472113],
       [0.99634665, 0.08540114],
       [0.        , 1.        ],
       [0.        , 0.        ]])

! 데이터 스케일링 주의점!

트레이닝 데이터에 대해서만 fit을 해준다!

테스트 데이터에서는 transform만 해줘야 트레이닝 데이터에서와 동일한 평균, 표준편차 등을 사용하게 된다

이렇게 하지 않으면 train 이 아니라 test 데이터에 대한 평균, 표준편차를 이용하게 되므로 스케일링 범위와 파라미터가 달라지게 된다!!

# 표준화 스케일링
from sklearn.preprocessing import StandardScaleer

ss = StandardScaler()

# x_train과 x_test가 있다고 가정!
x_train_std = ss.fit_transform(x_train)
x_test_std = ss.transform(x_test)