데이터 전처리 (Data Preprocessing)
“Garbage In, Garbage Out” 분석이나 머신러닝 모델링 전에 데이터를 정제하고 변형하여 최적의 상태로 만드는 과정
- 머신러닝 모델링에서 결손값, 즉 NaN, Null 값은 허용되지 않음
- 따라서 이러한 Null 값은 고정된 다른 값으로 변환해야 함!
- Null 값을 어떻게 처리해야 할지는 경우에 따라 다름
- feature 값 중 Null 값이 얼마 되지 않는다면 feature의 평균값 등으로 간단히 대체할 수 있음
- 하지만, Null 값이 대부분이라면 오히려 해당 feature는 drop 하는 것이 더 좋음!
- 가장 결정이 힘든 부분이 Null 값이 일정 수준 이상 되는 경우!
- 정확히 몇 퍼센트까지를 일정 수준 이상이라고 한다는 기준은 없지만, 해당 feature가 중요도가 높은 feature이고 Null을 단순히 feature의 평균값으로 대체할 경우 예측 왜곡이 심할 수 있다면 업무 로직 등을 상세히 검토해 더 정밀한 대체 값을 선정해야 함
- 사이킷런의 머신러닝 알고리즘은 문자열 값을 입력값으로 허용하지 않음
- 모든 문자열 값은 인코딩돼서 숫자형으로 변환해야 함!
- 문자열 feature는 일반적으로 카테고리형 feature와 텍스트형 feature를 의미
1. 데이터 인코딩
데이터 인코딩 (Data Encoding)
머신러닝 모델이 이해할 수 있도록 범주형(category) 데이터를 숫자로 변환하는 과정 머신러닝을 위한 대표적인 인코딩 방식은 레이블 인코딩(Label encoding)과 원-핫 인코딩(One-Hot encoding)
a. 레이블 인코딩 (Label Encoding)
레이블 인코딩 (Label Encoding)
각 범주(category)를 정수(0, 1, 2…)로 변환하는 방식
- 장점 : 간단하게 문자열 값을 숫자형 카테고리 값으로 변환
- 단점 : 숫자 값의 경우 크고 작음에 대한 특성이 작용하기 때문에 몇몇 ML 알고리즘에는 이를 적용할 경우 예측 성능이 떨어지는 경우가 발생할 수 있음
- 숫자 변환 값은 단순 코드이지 숫자 값에 따른 순서나 중요도로 인식돼서는 안 됨
- 이러한 특성 때문에 레이블 인코딩은 선형 회귀와 같은 ML 알고리즘에는 적용하지 않아야 함
- tree 계열의 ML 알고리즘은 적용 가능
- 사이킷런의 레이블 인코딩은
LabelEncoder클래스로 구현 LabelEncoder를 객체로 생성한 후fit()과transform()을 호출해 레이블 인코딩 수행
from sklearn.preprocessing import LabelEncoder
items=['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서']
# LabelEncoder를 객체로 생성한 후 , fit( ) 과 transform( ) 으로 label 인코딩 수행.
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)
print('인코딩 변환값:',labels)
>>> 인코딩 변환값: [0 1 4 5 3 3 2 2]- 위 예제는 데이터가 작아서 문자열 값이 어떤 숫자 값으로 인코딩됐는지 직관적으로 알 수 있지만, 많은 경우에 이를 알지 못함
- 이 경우에는
LabelEncoder객체의classes_속성값으로 확인!print('인코딩 클래스:',encoder.classes_)인코딩 클래스: ['TV' '냉장고' '믹서' '선풍기' '전자레인지' '컴퓨터']classes_속성은 0번부터 순서대로 변환된 인코딩 값에 대한 원본값을 가지고 있음
inverse_transform()을 통해 인코딩된 값을 다시 디코딩할 수 있음print('디코딩 원본 값:',encoder.inverse_transform([4, 5, 2, 0, 1, 1, 3, 3]))디코딩 원본 값: ['전자레인지' '컴퓨터' '믹서' 'TV' '냉장고' '냉장고' '선풍기' '선풍기']
b. 원-핫 인코딩 (One-Hot Encoding)
원-핫 인코딩 (One-Hot Encoding)
feature 값의 유형에 따라 새로운 feature를 추가해 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지 칼럼에는 0을 표시하는 방식
- 즉, row 형태로 되어 있는 feature의 고유 값을 column 형태로 차원을 변환한 뒤, 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지 column에는 0을 표시

- 원-핫 인코딩은 사이킷런에서
OneHotEncoder클래스로 변환 가능 - 단,
LabelEncoder와 다르게 약간 주의할 점은 입력값으로 2차원 데이터가 필요하다는 것과,OneHotEncoder를 이용해 변환한 값이 희소 행렬(Sparse Matrix) 형태이므로 이를 다시toarray()메서드를 이용해 밀집 행렬(Dense Matrix)로 변환해야 한다는 것- 여기서의 밀집 행렬은 모든 값을 명시적으로 메모리에 저장하는 행렬을 의미
from sklearn.preprocessing import OneHotEncoder
import numpy as np
items=['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서']
# 2차원 ndarray로 변환합니다.
items = np.array(items).reshape(-1, 1)
# 원-핫 인코딩을 적용합니다.
oh_encoder = OneHotEncoder()
oh_encoder.fit(items)
oh_labels = oh_encoder.transform(items)
# OneHotEncoder로 변환한 결과는 희소행렬이므로 toarray()를 이용해 밀집 행렬로 변환.
print('원-핫 인코딩 데이터')
print(oh_labels.toarray())
print('원-핫 인코딩 데이터 차원')
print(oh_labels.shape)
-
위 예제 코드의 변환 절차

-
판다스에는 원-핫 인코딩을 더 쉽게 지원하는 API 존재!
pd.get_dummies(df)- 사이킷런의
OneHotEncoder와 다르게 문자열 카테고리 값을 숫자형으로 변환할 필요 없이 바로 변환 가능!
import pandas as pd
from tabulate import tabulate
df = pd.DataFrame({'item':['TV','냉장고','전자레인지','컴퓨터','선풍기','선풍기','믹서','믹서'] })
print(tabulate(pd.get_dummies(df).astype(int), headers='keys', tablefmt='fancy_outline'))
2. 피처 스케일링과 정규화
피처 스케일링 (feature scaling)
서로 다른 변수의 값 범위를 일정한 수준으로 맞추는 작업
- 머신러닝 알고리즘은 입력 값의 크기에 영향을 받을 수 있기 때문에, 특성이 서로 다른 단위나 범위를 가지면 모델 성능이 저하될 수 있음!
- 대표적인 방법으로 **표준화(Standardization)**와 정규화(Normalization)
표준화 (Standardization)
데이터의 feature 각각이 평균이 0이고 분산이 1인 가우시안 정규 분포를 가진 값으로 변환하는 것
정규화 (Normalization)
서로 다른 feature 크기를 통일하기 위해 크기를 변환해주는 개념 데이터의 값을 모두 최소 0 ~ 최대 1의 값으로 변환하는 것
- 단, 사이킷런의 전처리에서 제공하는
Normalizer모듈과 일반적인 정규화는 약간의 차이가 존재! - 사이킷런의
Normalizer모듈은 선형대수에서의 정규화 개념이 적용됐으며, 개별 벡터의 크기를 맞추기 위해 변환하는 것을 의미! - 즉, 개별 벡터를 모든 feature 벡터의 크기로 나눠줌
Note
혼선을 방지하기 위해 일반적인 의미의 표준화와 정규화를 피처 스케일링으로 통칭하고 선형대수 개념의 정규화를 벡터 정규화로 지칭
3. StandardScaler
사이킷런에서 제공하는 대표적인 Feature Scaling 클래스
StandardScaler와MinMaxScaler
StandardScaler
- 표준화를 쉽게 지원하기 위한 클래스
- 즉, 개별 feature를 평균이 0이고, 분산이 1인 값으로 변환
- 가우시안 정규 분포를 가질 수 있도록 데이터를 변환하는 것은 몇몇 알고리즘에서 매우 중요!
- SVM, 선형회귀, 로지스틱 회귀는 데이터가 가우시안 분포를 가지고 있다고 가정하고 구현됐기 때문에 사전에 표준화를 적용하는 것은 예측 성능 향상에 중요한 요소가 될 수 있음
from sklearn.datasets import load_iris
import pandas as pd
# 붓꽃 데이터 셋을 로딩하고 DataFrame으로 변환합니다.
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())
- 이제
StandardScaler를 이용해 각 feature를 한 번에 표준화 StandardScaler객체를 생성한 후에fit()과transform()메서드에 변환 대상 feature 데이터 세트를 입력하고 호출하면 간단하게 변환transform()을 호출할 때 스케일 변환된 데이터 세트는 넘파이의ndarray이므로 이를 DataFrame으로 변환해 평균값과 분산 값을 다시 확인
from sklearn.preprocessing import StandardScaler
# StandardScaler객체 생성
scaler = StandardScaler()
# StandardScaler 로 데이터 셋 변환. fit( ) 과 transform( ) 호출.
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)
#transform( )시 scale 변환된 데이터 셋이 numpy ndarry로 반환되어 이를 DataFrame으로 변환
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())
- 모든 column 값의 평균이 0에 아주 가까운 값으로, 분산은 1에 아주 가까운 값으로 변환됨
4. MinMaxScaler
MinMaxScaler
- 정규화를 쉽게 지원하기 위한 클래스
- 데이터값을 0과 1 사이의 범위 값으로 변환
- 음수 값이 있으면 -1에서 1값으로 변환
- 데이터의 분포가 가우시안 분포가 아닐 경우에 Min, Max Scale을 적용해 볼 수 있음
from sklearn.preprocessing import MinMaxScaler
# MinMaxScaler객체 생성
scaler = MinMaxScaler()
# MinMaxScaler 로 데이터 셋 변환. fit() 과 transform() 호출.
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)
# transform()시 scale 변환된 데이터 셋이 numpy ndarry로 반환되어 이를 DataFrame으로 변환
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에 0에서 1 사이의 값으로 변환되는 스케일링이 적용됐음을 알 수 있음
왜
fit()과transform()을 따로 실행할까?훈련 데이터를 기준으로 변환 기준(scaling parameter)을 학습하고, 이를 나중에 테스트 데이터에도 적용하기 위해서!
- 훈련 데이터에서 fit → 학습된 기준을 훈련 데이터와 테스트 데이터에 적용
- 훈련 데이터에 대해서만 변환할 경우,
fit_transform()을 사용할 순 있지만, 학습 데이터와 테스트 데이터가 분리되어 있을 때는 반드시fit()과transform()을 따로 사용해야 함!
5. 학습 데이터와 테스트 데이터의 스케일링 변환 시 유의점
Scaler객체를 이용해 데이터의 스케일링 변환 과정fit()은 데이터 변환을 위한 기준 정보 설정을 적용transform()은 이렇게 설정된 정보를 이용해 데이터 변환- 그리고
fit_transform()은fit()과transform()을 한 번에 적용하는 기능 수행
fit()과transform()을 적용할 때 주의할 점!학습 데이터 세트로
fit()을 적용하면 테스트 데이터 세트로는 다시fit()을 수행하지 않고 학습 데이터 세트로fit()을 수행한 결과를 이용해transform()변환을 적용해야 함! 즉, 학습 데이터로fit()이 적용된 스케일링 기준 정보를 그대로 테스트 데이터에 적용
- 모델을 학습할 때는 훈련 데이터에서 패턴을 학습한 후, 테스트 데이터에서 그 패턴이 잘 적용되는 지를 평가해야 함
- 따라서 훈련 데이터에서 학습한 기준을 그대로 테스트 데이터에도 적용해야 일관성이 유지됨!
테스트 데이터에
fit()을 적용할 때 어떠한 문제 발생?
from sklearn.preprocessing import MinMaxScaler
import numpy as np
# 학습 데이터는 0 부터 10까지, 테스트 데이터는 0 부터 5까지 값을 가지는 데이터 세트로 생성
# Scaler클래스의 fit(), transform()은 2차원 이상 데이터만 가능하므로 reshape(-1, 1)로 차원 변경
train_array = np.arange(0, 11).reshape(-1, 1)
test_array = np.arange(0, 6).reshape(-1, 1)- 학습 데이터를 0부터 10까지, 테스트 데이터를 0부터 5까지 값을 가지는 ndarray
# MinMaxScaler 객체에 별도의 feature_range 파라미터 값을 지정하지 않으면 0~1 값으로 변환
scaler = MinMaxScaler()
# fit()하게 되면 train_array 데이터의 최솟값이 0, 최댓값이 10으로 설정.
scaler.fit(train_array)
# 1/10 scale로 train_array 데이터 변환함. 원본 10-> 1로 변환됨.
train_scaled = scaler.transform(train_array)
print('원본 train_array 데이터:', np.round(train_array.reshape(-1), 2))
print('Scale된 train_array 데이터:', np.round(train_scaled.reshape(-1), 2))
>>> 원본 train_array 데이터: [ 0 1 2 3 4 5 6 7 8 9 10]
Scale된 train_array 데이터: [0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]- 학습 데이터인
train_array부터MinMaxScaler를 이용해 변환
# MinMaxScaler에 test_array를 fit()하게 되면 원본 데이터의 최솟값이 0, 최댓값이 5로 설정됨
scaler.fit(test_array)
# 1/5 scale로 test_array 데이터 변환함. 원본 5->1로 변환.
test_scaled = scaler.transform(test_array)
# test_array의 scale 변환 출력.
print('원본 test_array 데이터:', np.round(test_array.reshape(-1), 2))
print('Scale된 test_array 데이터:', np.round(test_scaled.reshape(-1), 2))
>>> 원본 test_array 데이터: [0 1 2 3 4 5]
Scale된 test_array 데이터: [0. 0.2 0.4 0.6 0.8 1. ]- 테스트 데이터 세트 변환
fit()을 호출해 스케일링 기준 정보를 다시 적용한 뒤transform()을 수행한 결과 확인- 출력 결과를 확인하면 학습 데이터와 테스트 데이터의 스케일링이 맞지 않음!
- 이렇게 되면 학습 데이터와 테스트 데이터의 서로 다른 원본값이 동일한 값으로 반환되는 결과 초래
Tip
머신러닝 모델은 학습 데이터를 기반으로 학습되기 때문에 반드시 테스트 데이터는 학습 데이터의 스케일링 기준에 따라야 하며, 테스트 데이터의 1 값은 학습 데이터와 동일하게 0.1 값으로 변환되어야 함!
- 따라서, 테스트 데이터에 다시
fit()을 적용해서는 안 되며 학습 데이터로 이미fit()이 적용된Scaler객체를 이용해transform()으로 변환해야 함
- 다음 코드는 테스트 데이터에
fit()을 호출하지 않고 학습 데이터로fit()을 수행한MinMaxScaler객체의transform()을 이용해 데이터 변환
scaler = MinMaxScaler()
scaler.fit(train_array)
train_scaled = scaler.transform(train_array)
print('원본 train_array 데이터:', np.round(train_array.reshape(-1), 2))
print('Scale된 train_array 데이터:', np.round(train_scaled.reshape(-1), 2))
# test_array에 Scale 변환을 할 때는 반드시 fit()을 호출하지 않고 transform() 만으로 변환해야 함.
test_scaled = scaler.transform(test_array)
print('\n원본 test_array 데이터:', np.round(test_array.reshape(-1), 2))
print('Scale된 test_array 데이터:', np.round(test_scaled.reshape(-1), 2))
fit_transform()은fit()과transform()을 순차적으로 수행하는 메서드이므로 학습 데이터에서는 상관없지만 테스트 데이터에서는 절대 사용해서는 안됨!
Tip
학습과 테스트 데이터에
fit()과transform()을 적용할 때 주의 사항이 발생하므로 학습과 테스트 데이터 세트로 분리하기 전에 먼저 전체 데이터 세트에 스케일링을 적용한 뒤 학습과 테스트 데이터 세트로 분리하는 것이 더 바람직!
Summary
1. 가능하다면 전체 데이터의 스케일링 변환을 적용한 뒤 학습과 테스트 데이터로 분리
2. 1이 여의치 않다면 테스트 데이터 변환 시에는
fit()이나fit_transform()을 적용하지 않고 학습 데이터로 이미fit()된Scaler객체를 이용해transform()으로 변환