Search

데이터 전처리

데이터 전처리는 알고리즘만큼 중요하다. 먼저 결측치를 처리하는 부분, 문자열로 된 특성값, 중요하지 않은 특성값 처리 등등이 있다.
일단 결측치는 너무 많다면 해당 칼럼을 제거하는 것이 좋고, 아주 작다면 대체값으로 대체하는것이 좋다. 하지만, 일정 수준 이상 되는 경우에는 해당 칼럼의 중요도를 따져서 대체할지 삭제할지 결정한다.
사이킷런의 알고리즘은 문자열 데이터는 입력값으로 받지 않는다. 따라서 인코딩하여 숫자형으로 변환해야 한다.
불필요한 칼럼이라고 생각된다면 제거하는 것이 좋다. 예를들어 식별 칼럼 등등..

데이터 인코딩

대표적인 인코딩 방식에는 레이블 인코딩, 원-핫 인코딩이 있다.
레이블 인코딩 : 카테고리 특성을 코드형 숫자 값으로 변환한다. 각 카테고리마다 1,2,... 숫자를 부여하는 것
LabelEncoder 클래스로 구현한다. 객체를 생성한 뒤 fit() 과 transform()을 수행한다.
from sklearn.preprocessing import LabelEncoder items = ['TV', '냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서'] #LabelEncoder 를 객체로 생성한 뒤, fit()과 transform()으로 레이블 인코딩 수행 encoder = LabelEncoder() encoder.fit(items) labels = encoder.transform(items) print('인코딩 변환값:',labels) # 인코딩 변환값: [0 1 4 5 3 3 2 2] # 어떤 숫자값으로 변환됐는지 확인(classes_) # 순서대로 변환된 인코딩 값에 대한 원본값을 알려준다. print('인코딩 클래스:',encoder.classes_) ''' 인코딩 클래스: ['TV' '냉장고' '믹서' '선풍기' '전자레인지' '컴퓨터'] '''
Python
복사
inverse_transform()을 이용하면 인코딩된 값을 디코딩할 수 있다.
print('디코딩 원본값:', encoder.inverse_transform([4,5,2,0,1,1,3,3]))
Python
복사
하지만, 레이블 인코딩은 숫자값의 크기로 가중치가 더 부여되거나 중요하게 인식할 가능성이 있기 때문에 선형 회귀와 같은 머신러닝 알고리즘에는 적용하지 않아야 한다.
이런 문제를 반영한 것이 원 핫 인코딩이다.
원-핫 인코딩(One-Hot Encoding)
원-핫 인코딩은 피처 값의 유형에 따라 열을 새로 만들어 고유 값에 해당하는 열에 1을 부여하고 나머지 칼럼에는 0을 표시하는 방식이다.
OneHotEncoder 클래스를 사이킷런에서 쉽게 변환할 수 있다. 주의할 점이 있는데, 원-핫 인코더로 변환하기 전에 모든 문자열 값이 숫자형 값으로 변환돼야 한다는 것이다.
두번째는 2차원 데이터가 필요하다는 점이다.
from sklearn.preprocessing import OneHotEncoder import numpy as np items = ['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서'] # 먼저 숫자 값으로 변환을 위해 LabelEncoder로 변환한다. encoder = LabelEncoder() encoder.fit(items) labels = encoder.transform(items) # 2차원 데이터로 변환한다. labels = labels.reshape(-1,1) # 원-핫 인코딩을 적용한다. oh_encoder = OneHotEncoder() oh_encoder.fit(labels) oh_labels = oh_encoder.transform(labels) print('웟-핫 인코딩 데이터') print(oh_labels.toarray()) print('원-핫 인코딩 데이터 차원') print(oh_labels.shape)
Python
복사
판다스에는 원-핫 인코딩을 더 쉽게 지원하는 API가 있다. get_dummies()를 이용하면 된다.
OneHotEncoder와 다르게 문자열 카테고리 값을 숫자 형으로 변환할 필요 없이 바로 변환할 수 있다.
import pandas as pd df = pd.DataFrame({'item':['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서']}) pd.get_dummies(df)
Python
복사

피처 스케일링과 정규화

대표적으로 표준화와 정규화가 있다.
표준화는 피처값이 각각 평균이 0이고 분산이 1인 정규 분포를 가진 값으로 변환하는 것이고,
정규화는 서로 다른 피처의 크기를 통일하기 위해 크기를 변환해주는 개념이다. 즉, 최소0 ~ 최대1의 값으로 변환하는 것이다. 즉, 모든 피처의 크기를 똑같은 단위로 변경하는 것이다.
주의할 점이 사이킷런의 전처리에서 제공하는 Normalizer 모듈은 일반적인 정규화와 다르다. 개별 벡터를 모든 피처 벡터의 크기로 나눠준다.
따라서, 일반적인 의미의 스케일링은 표준화와 정규화이고 선형대수 개념의 정규화를 벡터 정규화로 말한다.
대표적으로 StandardScaler(표준화) 와 MinMaxScaler(정규화) 가 있다.

StandardScaler

평균이 0이고 분산이 1인 값으로 변환한다. 즉, 정규분포를 가정하는 모델을 사용하기 위해서는 꼭 필요한 과정이다. (서포트 벡터 머신, 선형 회귀, 로지스틱 회귀 등)
from sklearn.datasets import load_iris import pandas as pd # 붓꽃 데이터 세트를 로딩하고 데이터프레임으로 변환 iris = load_iris() iris_data = iris.data iris_df = pd.DataFrame(data=iris_data, columns=iris.feature_names) print('feature들의 평균값') print(iris_df.mean()) print('\nfeature 들의 분산 값') print(iris_df.var()) # 일반적인 피처들의 분산 ''' feature들의 평균값 sepal length (cm) 5.843333 sepal width (cm) 3.057333 petal length (cm) 3.758000 petal width (cm) 1.199333 dtype: float64 feature 들의 분산 값 sepal length (cm) 0.685694 sepal width (cm) 0.189979 petal length (cm) 3.116278 petal width (cm) 0.581006 dtype: float64 ''' # 이제 표준화를 진행한다. from sklearn.preprocessing import StandardScaler # standardscaler 객체 생성 scaler = StandardScaler() # StandardScaler 로 데이터 세트 변환. fit() 과 transform() 호출 scaler.fit(iris_df) iris_scaled = scaler.transform(iris_df) # transform() 시 반환값이 ndarray이므로 다시 데이터프레임으로 변환 iris_df_scaled = pd.DataFrame(data=iris_scaled, columns=iris.feature_names) print('feature 들의 평균값') print(iris_df_scaled.mean()) print('\nfeature 들의 분산 값') print(iris_df_scaled.var()) ''' feature 들의 평균값 sepal length (cm) -1.690315e-15 sepal width (cm) -1.842970e-15 petal length (cm) -1.698641e-15 petal width (cm) -1.409243e-15 dtype: float64 feature 들의 분산 값 sepal length (cm) 1.006711 sepal width (cm) 1.006711 petal length (cm) 1.006711 petal width (cm) 1.006711 dtype: float64 '''
Python
복사

MinMaxScaler

데이터값을 0과 1 사이의 범위값으로 변환한다. 음수값이 있다면 -1에서 1값으로 변환한다.
from sklearn.preprocessing import MinMaxScaler # MinMaxScaler객체 생성 scaler = MinMaxScaler() # MinMaxScaler 로 데이터 세트 변환. fit() 과 transform() 호출 scaler.fit(iris_df) iris_scaled = scaler.transform(iris_df) # 마찬가지로 데이터프레임으로 변환 iris_df_scaled = pd.DataFrame(data=iris_scaled, columns=iris.feature_names) print('feature들의 최솟값') print(iris_df_scaled.min()) print('\nfeature들의 최댓값') print(iris_df_scaled.max()) ''' feature들의 최솟값 sepal length (cm) 0.0 sepal width (cm) 0.0 petal length (cm) 0.0 petal width (cm) 0.0 dtype: float64 feature들의 최댓값 sepal length (cm) 1.0 sepal width (cm) 1.0 petal length (cm) 1.0 petal width (cm) 1.0 dtype: float64 '''
Python
복사

학습 데이터와 테스트 데이터의 스케일링 변환 시 유의점

학습 데이터를 가지고 fit() 과 transform()을 했다면 테스트 데이터의 전처리 변환은 오직 transform만 해야한다.
scaler = MinMaxScaler() train_array = np.arange(0,11).reshape(-1,1) test_array = np.arange(0,6).reshape(-1,1) train_scaled = scaler.fit(train_array) print('원본 train_array 데이터:', np.round(train_array.reshape(-1),2)) print('scaled된 train_array 데이터:', np.round(train_scaled.reshape(-1),2)) # test_array 변환 test_scaled = scaler.transform(test_array) print('\n 원본 test_array 데이터:', np.round(test_array.reshape(-1),2)) print('scaled된 test_array 데이터:', np.round(test_scaled.reshape(-1),2)) ''' 원본 train_array 데이터: [ 0 1 2 3 4 5 6 7 8 9 10] scaled된 train_array 데이터: [0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ] 원본 test_array 데이터: [0 1 2 3 4 5] scaled된 test_array 데이터: [0. 0.1 0.2 0.3 0.4 0.5] '''
Python
복사
따라서, transform()만 적용해야하는 테스트 데이터에는 fit_transform()을 사용하면 안된다.
결론적으로 순서를 따지자면 전체 데이터 세트에 스케일링을 적용한 뒤,
학습/ 테스트 데이터 세트를 분리하는 것이 바람직하다.

결측치 채우기

from sklearn.impute import SimpleImputer my_imputer = SimpleImputer() #default imputed_df = pd.DataFrame(my_imputer.fit_transform(df))
Python
복사
SimpleImputer 파라미터
missing_values : default는 np.nan , 필요시 다른값으로 인식하게 할 수 있음
strategy : 채울 값에 대한 함수. default=”mean” , “median”, ”most_frequent”, ”constant” 가 있다.
fill_value : strategy가 “constant”인 경우 여기에 구체적인 값을 넣어주면 된다.
SimpleImputer(strategy='constant', fill_value='Unknown') # Unknown값으로 범주형 변수의 결측치 대체
SQL
복사
verbose : default=0 (출력 안함), 1(자세히 출력), 2(간단히 출력)
copy : default=True (복사본을 만들어 채워주기), False(기존의 데이터프레임에 채워주기)
add_indicator =False , 원래 비어있던 값인지 아닌지를 구분하는 구분자 칼럼을 추가할지의 여부

각 컬럼당 결측치의 비율 확인하기(시각화 포함)

import plotly.express as px missing_values_pct = train_df.isnull().sum() / len(train_df) missing_values_pct_df = pd.DataFrame() missing_values_pct_df['features'] = missing_values_pct.index missing_values_pct_df['missing_values_pct'] = mising_values_pct.values px.bar(x = 'missing_values_pct', y = 'features', data_frame = missing_values_pct_df, title = 'Missing values in %', color = 'features')
SQL
복사