데이터 변환

Keywords

python, 파이썬, numpy, 넘파이, 넘피, pandas, 판다스, machine learning, 기계학습, 머신러닝, 회귀, 분류, 군집

import pandas as pd
from palmerpenguins import load_penguins

df = load_penguins()
df.head()
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex year
0 Adelie Torgersen 39.1 18.7 181.0 3750.0 male 2007
1 Adelie Torgersen 39.5 17.4 186.0 3800.0 female 2007
2 Adelie Torgersen 40.3 18.0 195.0 3250.0 female 2007
3 Adelie Torgersen NaN NaN NaN NaN NaN 2007
4 Adelie Torgersen 36.7 19.3 193.0 3450.0 female 2007

정규화와 표준화

정규화

정규화(Normalization)는 값 범위를 일정 구간으로 압축하는 방법으로 보통 [0, 1] 범위로 변환한다. 크기 비율이 중요한 경우 수행한다.

\[ x' = \frac{x - x_{min}}{x_{max} - x_{min}} \]

위 예제는 Min-Max Scaling이며 최소값은 0, 최대값은 1을 갖는다. 참고로 이상치에 매우 민감하다.

거리 기반 알고리즘, 신경망, 서로 단위나 범위가 다른 경우 사용한다.

# 정규화 대상 컬럼
cols = ["bill_length_mm", "bill_depth_mm", "body_mass_g"]
df_norm = df[cols].copy()

df_norm = (df_norm - df_norm.min()) / (df_norm.max() - df_norm.min())

df_norm.head()
bill_length_mm bill_depth_mm body_mass_g
0 0.254545 0.666667 0.291667
1 0.269091 0.511905 0.305556
2 0.298182 0.583333 0.152778
3 NaN NaN NaN
4 0.167273 0.738095 0.208333
df_norm.describe()
bill_length_mm bill_depth_mm body_mass_g
count 342.000000 342.000000 342.000000
mean 0.429888 0.482282 0.417154
std 0.198530 0.235094 0.222765
min 0.000000 0.000000 0.000000
25% 0.259091 0.297619 0.236111
50% 0.449091 0.500000 0.375000
75% 0.596364 0.666667 0.569444
max 1.000000 1.000000 1.000000

표준화

표준화(Standardization)는 평균을 0으로, 표준편차를 1로 데이터를 변환하는 방법이다. 분포 형태를 유지하면서 범위(스케일)을 조정한다.

\[ x' = \frac{x-\mu}{\sigma} \]

표준화를 수행하면 평균이 0이 되고 표준편차는 1이된다. 정규분퐁 모델에 적합하고 이상치 영향은 존재하지만 정규화에 비해 완만하다. 주로 선형회귀, 로지스틱 회귀, PCA 그리고 대부분의 통계 및 기계학습 모델에 있어 기본값으로 사용된다.

# 표준화 대상 컬럼
cols = ["bill_length_mm", "bill_depth_mm", "body_mass_g"]
df_std = df[cols].copy()

df_std = (df_std - df_std.mean()) / df_std.std()

df_std.head()
bill_length_mm bill_depth_mm body_mass_g
0 -0.883205 0.784300 -0.563317
1 -0.809939 0.126003 -0.500969
2 -0.663408 0.429833 -1.186793
3 NaN NaN NaN
4 -1.322799 1.088129 -0.937403
df_std.describe()
bill_length_mm bill_depth_mm body_mass_g
count 3.420000e+02 3.420000e+02 3.420000e+02
mean 1.662088e-16 4.155221e-16 1.246566e-16
std 1.000000e+00 1.000000e+00 1.000000e+00
min -2.165354e+00 -2.051440e+00 -1.872618e+00
25% -8.603092e-01 -7.854846e-01 -8.127074e-01
50% 9.672352e-02 7.536506e-02 -1.892307e-01
75% 8.385383e-01 7.843001e-01 6.836368e-01
max 2.871660e+00 2.202170e+00 2.616415e+00

scikit-learn Scaler

scikit-learn 라이브러리에는 MinMaxScaler, StandardScaler를 제공한다.

from sklearn.preprocessing import MinMaxScaler, StandardScaler

# 정규화
cols = ["bill_length_mm", "bill_depth_mm", "body_mass_g"]
df_norm = df[cols].copy()

minmax = MinMaxScaler()
df_norm = minmax.fit_transform(df_norm)

df_norm 
array([[0.25454545, 0.66666667, 0.29166667],
       [0.26909091, 0.51190476, 0.30555556],
       [0.29818182, 0.58333333, 0.15277778],
       ...,
       [0.63636364, 0.60714286, 0.29861111],
       [0.68      , 0.70238095, 0.38888889],
       [0.65818182, 0.66666667, 0.29861111]], shape=(344, 3))
# 표준화
cols = ["bill_length_mm", "bill_depth_mm", "body_mass_g"]
df_std = df[cols].copy()

standard = StandardScaler()
df_std = standard.fit_transform(df_std)

df_std
array([[-0.88449874,  0.78544923, -0.56414208],
       [-0.81112573,  0.1261879 , -0.50170305],
       [-0.66437972,  0.43046236, -1.18853234],
       ...,
       [ 1.04154272,  0.53188718, -0.53292256],
       [ 1.26166175,  0.93758646, -0.1270689 ],
       [ 1.15160224,  0.78544923, -0.53292256]], shape=(344, 3))

참고로, scikit-learn 라이브러리에서 표준화를 진행할 경우 표준편차를 모집단 기준으로 계산한다(ddof=0). 따라서 수식을 통해 표준화를 진행 경우 자유도 설정에 따라 값이 상이할 수 있다. 라이브러리와 수식 결과를 동일하게 맞추려면 수식 계산 시 df.std(ddof=0)과 같이 명시적으로 모집단 표준편차를 계산하도록 설정한다.

분포 변환

로그/Box-Cox/Yeo-Jonhson 변환은 왜도를 줄이고 분포를 안정화하기 위한 대표적인 수치형 데이터 변환 기법이다.

로그 변환

로그 변환(Log Transform)은 큰 값을 강하게 압축하는 방법이다. 오른쪽으로 긴 꼬리(rightskewed) 분포 완화하는 가장 단순하고 직관적인 방법이다.

\[ x' = log(x+1) \]

0 이상 값을 갖게되고(0 이하 값 없음) 이상치 영향을 감소시킨다. 분포 해석이 직관적이다. 일반적으로 매출, 인구수, 몸무게, 금액 데이터 등 지수적으로 증가하는 값에 사용한다.

import numpy as np

cols = ["bill_length_mm", "bill_depth_mm", "body_mass_g"]
df_log = df[cols].copy()

df_log = np.log(df_log)

df_log
bill_length_mm bill_depth_mm body_mass_g
0 3.666122 2.928524 8.229511
1 3.676301 2.856470 8.242756
2 3.696351 2.890372 8.086410
3 NaN NaN NaN
4 3.602777 2.960105 8.146130
... ... ... ...
339 4.021774 2.985682 8.294050
340 3.772761 2.895912 8.131531
341 3.903991 2.901422 8.236156
342 3.927896 2.944439 8.318742
343 3.916015 2.928524 8.236156

344 rows × 3 columns

Box-Cox 변환

Box-Cox 변환은 로그 변환을 일반화한 방법이다. 최적의 지수(\(\lambda\))를 자동으로 찾는다. 또한 분포를 정규분포에 가깝게 변환한다.

\[ x' = \begin{cases} \dfrac{x^{\lambda} - 1}{\lambda}, & \lambda \neq 0 \\ \log(x), & \lambda = 0 \end{cases} \]

양수 데이터만 사용이 가능(x>0)하다. 로그 변환은 Box-Cox의 한 형태이며 통계 분석에 자주 사용된다.

from scipy.stats import boxcox

df_bc = df.copy()

# 결측치 제거 후 적용
x = df["body_mass_g"].dropna()

x_bc, lambda_bc = boxcox(x)

df_bc.loc[x.index, "body_mass_boxcox"] = x_bc

lambda_bc
np.float64(-0.4656520878253136)
x_bc
array([2.1010014 , 2.10128747, 2.0977956 , 2.09915947, 2.10041214,
       2.10026112, 2.1055408 , 2.09932181, 2.10363547, 2.09814789,
       2.10070969, 2.09743527, 2.10128747, 2.10433867, 2.10070969,
       2.09915947, 2.10478825, 2.09832112, 2.10339293, 2.09882956,
       2.10010856, 2.10128747, 2.10211356, 2.10128747, 2.10128747,
       2.09979874, 2.09743527, 2.09706659, 2.10211356, 2.0977956 ,
       2.10184338, 2.09814789, 2.10184338, 2.09832112, 2.10314613,
       2.10211356, 2.09979874, 2.09814789, 2.10543584, 2.09706659,
       2.10184338, 2.09668923, 2.10433867, 2.09590706, 2.10522342,
       2.09899539, 2.09570553, 2.09915947, 2.10314613, 2.09948245,
       2.10387386, 2.09915947, 2.10263919, 2.09508572, 2.10070969,
       2.09979874, 2.10128747, 2.09465931, 2.1010014 , 2.09706659,
       2.10433867, 2.10010856, 2.10263919, 2.09465931, 2.10211356,
       2.09849245, 2.10289492, 2.09630285, 2.10456531, 2.10010856,
       2.10184338, 2.09979874, 2.10314613, 2.10070969, 2.10363547,
       2.10070969, 2.10184338, 2.09979874, 2.10237878, 2.09743527,
       2.10564494, 2.10128747, 2.10339293, 2.09849245, 2.09979874,
       2.10128747, 2.09948245, 2.10211356, 2.10010856, 2.09979874,
       2.10387386, 2.09882956, 2.10456531, 2.09814789, 2.10387386,
       2.10070969, 2.10410822, 2.09508572, 2.10289492, 2.10085626,
       2.10574827, 2.09649719, 2.10363547, 2.09529491, 2.09979874,
       2.1010014 , 2.10184338, 2.09725199, 2.10595255, 2.10142844,
       2.10522342, 2.09743527, 2.10375518, 2.10184338, 2.10276763,
       2.09508572, 2.10114513, 2.09849245, 2.09832112, 2.09706659,
       2.09948245, 2.09915947, 2.10170637, 2.09630285, 2.10237878,
       2.09797273, 2.10387386, 2.09630285, 2.10237878, 2.09832112,
       2.09948245, 2.09948245, 2.10467724, 2.09899539, 2.10184338,
       2.09725199, 2.10224679, 2.09882956, 2.10363547, 2.09882956,
       2.09932181, 2.09630285, 2.10085626, 2.09590706, 2.10041214,
       2.10363547, 2.09932181, 2.09915947, 2.1010014 , 2.10070969,
       2.10237878, 2.10478825, 2.10924294, 2.10456531, 2.10924294,
       2.10826687, 2.10500759, 2.10605352, 2.10757084, 2.10433867,
       2.10739067, 2.10543584, 2.10876457, 2.10543584, 2.10970321,
       2.10339293, 2.10970321, 2.10314613, 2.11098615, 2.10605352,
       2.10809644, 2.10924294, 2.10683442, 2.10433867, 2.10702252,
       2.10683442, 2.10720792, 2.10289492, 2.10908555, 2.10522342,
       2.10876457, 2.10774848, 2.10564494, 2.10702252, 2.11029066,
       2.10739067, 2.10826687, 2.10664354, 2.10774848, 2.10410822,
       2.10809644, 2.10211356, 2.10924294, 2.10387386, 2.10585081,
       2.10876457, 2.10644981, 2.10339293, 2.10826687, 2.10720792,
       2.10792366, 2.10625316, 2.10792366, 2.10433867, 2.10683442,
       2.10644981, 2.10702252, 2.10387386, 2.10683442, 2.10456531,
       2.10876457, 2.10339293, 2.10792366, 2.10433867, 2.10908555,
       2.10564494, 2.10924294, 2.10543584, 2.10955172, 2.10564494,
       2.10876457, 2.10585081, 2.10683442, 2.10720792, 2.10757084,
       2.10564494, 2.10955172, 2.10522342, 2.11014649, 2.10585081,
       2.11000055, 2.10533005, 2.108435  , 2.10574827, 2.10809644,
       2.10585081, 2.10892611, 2.10522342, 2.10792366, 2.10635186,
       2.10876457, 2.10664354, 2.10826687, 2.10585081, 2.10908555,
       2.10625316, 2.10757084, 2.10654703, 2.10635186, 2.10533005,
       2.10774848, 2.10625316, 2.10892611, 2.10673933, 2.10860088,
       2.10574827, 2.10860088, 2.10564494, 2.10860088, 2.10511594,
       2.10860088, 2.10683442, 2.11000055, 2.10543584, 2.10860088,
       2.10422393, 2.10970321, 2.10635186, 2.11014649, 2.10654703,
       2.10625316, 2.10939832, 2.10757084, 2.10826687, 2.09948245,
       2.10184338, 2.10041214, 2.09964142, 2.10085626, 2.10211356,
       2.0977956 , 2.1010014 , 2.10314613, 2.10070969, 2.10128747,
       2.10114513, 2.10070969, 2.10263919, 2.09995444, 2.10263919,
       2.09814789, 2.10070969, 2.09915947, 2.10433867, 2.10010856,
       2.09882956, 2.09508572, 2.10128747, 2.09814789, 2.10314613,
       2.09882956, 2.10128747, 2.10070969, 2.10500759, 2.09743527,
       2.10387386, 2.09849245, 2.10289492, 2.10010856, 2.10184338,
       2.10156807, 2.10605352, 2.09331141, 2.10478825, 2.10211356,
       2.10041214, 2.09979874, 2.09948245, 2.10056166, 2.10456531,
       2.09882956, 2.10387386, 2.0977956 , 2.10056166, 2.09832112,
       2.10211356, 2.10010856, 2.10263919, 2.09849245, 2.09915947,
       2.0977956 , 2.10263919, 2.10128747, 2.09964142, 2.10211356,
       2.10041214, 2.10041214, 2.10237878, 2.09882956, 2.10114513,
       2.10289492, 2.10114513])

Yeo-Johnson 변환

Yeo-Jonhson 변환은 Box-Cox 확장판이다. 0과 음수까지 허용하는 방법으로 머신러닝 전처리에서 표준적으로 사용된다.

라이브러리 사용 시 결측치를 제외한다.

from sklearn.preprocessing import PowerTransformer

pt = PowerTransformer(method="yeo-johnson")

df_yj = df.copy()

df_yj[["body_mass_yj"]] = pt.fit_transform(
    df[["body_mass_g"]]
)

df_yj[['body_mass_g', 'body_mass_yj']]
body_mass_g body_mass_yj
0 3750.0 -0.481138
1 3800.0 -0.407311
2 3250.0 -1.308479
3 NaN NaN
4 3450.0 -0.956498
... ... ...
339 4000.0 -0.125666
340 3400.0 -1.041641
341 3775.0 -0.444045
342 4100.0 0.007538
343 3775.0 -0.444045

344 rows × 2 columns

위 3가지 변환을 비교 정리하면 다음과 같다.

구분 로그 변환 (Log) Box–Cox 변환 Yeo–Johnson 변환
기본 목적 값 범위 압축, 왜도 완화 정규성에 가깝게 변환 정규성 개선 + 범용성
자동 λ 추정
0 값 허용 ❌ (log1p로 우회 가능)
음수 값 허용
수학적 관계 기본 로그 로그의 일반화 Box–Cox 확장
이상치 완화 중간 중간 중간
통계 분석 적합성 보통 높음 보통
머신러닝 활용 높음 제한적 매우 높음
대표 사용 사례 매출, 금액, 인구수 회귀 분석, 통계 모델 ML 전처리 파이프라인
파이썬 구현 np.log, np.log1p scipy.stats.boxcox sklearn.PowerTransformer