# [데이터 전처리] 수치형 변수 전처리

## 피처 스케일링 feature scaling

---

- 피처 스케일링은 데이터의 값을 변환하는 것을 의미한다.

- 주어진 데이터의 범위를 계산을 통해 새롭게 바꾼다.

- 특정 알고리즘은 큰 범위를 가진 데이터에 가중치를 두기도 하고, 어떤 알고리즘은 학습 과정의 효율성에 영향을 미치기도 한다.

### 피처 스케일링 유의점

1. 스케일링은 분포를 바꾸는 작업이 아니다.

2. 스케일링은 이상치를 제거하는 작업이 아니다.

3. 꼭 필요한 작업인지, 어느 피처에 어떤 스케일러를 사용하는게 좋은지 생각해 보자.

4. 스케일링 함수는 정확하게 사용하다. 

    1. .fit(): 스케일러가 사용하는 변수 정보 설정 (StandardScaler의 경우 평균, 표준편차)

    2. .transform(): 설정된 값을 바탕으로 피처 변환

    3. .fit_transform(): fit과 transform 모두 실행

5. 스케일러를 저장하는 습관을 갖자.

## 피처 스케일링 방법의 선택기준

---

|  | **표준화** | **정규화** |
| --- | --- | --- |
| **목적** | 평균 0, 표준편차 1이 되도록 변환 2개 이상 피처에서 단위가 다를 때 표준화를 통해 같은 기준으로 비교 가능 | 피처 범위를 [0, 1] 혹은 특정 범위 [a, b]로 변환 |
| **표현 & 용어** | 표준화 (Standardization) 표준화를 Unit variano라고 표현 | 정규화 (Normalization) |
| **파이썬 패키지** | StandardScaler: 평균, 표준편차 사용 RoboustScaler: 중앙값, IQR 사용 | MinMaxScaler: 최대/최솟값 MaxAbsScaler: 최대 절댓값 |
| **주요사항** | 기존 변수가 정규분포를 가질 때 더 적합 평균 0, 표준편차 1을 가진다고 해서 분포 자체가 표준 정규분포가 되지는 않음 | 분포가 바뀌지 않음 이상치에 민감 변환 후 표준편차가 줄어들 수 있음 |

## 표준화 Stnadardization

---

x_{scaled} = \frac{x-mean}{sd}

- 표준정규분포, z-score

- 평균이 1, 표준편차가 1을 갖도록 변환

- 표준편차 값이 1일 때 단위 분산 (Unit Variance)라고 한다.

- 표준화 작업이 피처를 표준정규분포로 바꿔주지는 않는다.
- → 평균, 표준편차는 변하지만 왜도와 첨도는 변하지 않음

- 이상치 영향을 받는다.

### StandardScaler

- 표준화를 쉽게 지원하기 위한 클래스

- 개별 피처를 평균이 0이고 분산이 1인 값으로 변환

- 사이킷런 RBF 커널 SVM, 선형회귀, 로지스틱 회귀는 데이터가 가우신 반포 가정, 표준화를 적용하는 것은 예측 성능 향상에 중요한 요소

```javascript
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())
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180000_VI5ZRT1ap9k3TuFNIm?q=80&s=1280x180&t=outside&f=webp)

**[sklearn.preprocessing.StandardScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html)**

- StandardScaler 객체를 생성 후에 fit()과 trnasform() 메서드에 변환 대상 피처 데이터 세트를 입력하고 호출하면 간단하게 변환

- transform()을 호출할 때 스케일 변환된 데이터 세트가 넘파이의 ndarray이므로 이를 DataFrame으로 변환해 평균값 분산값 다시 확인해야한다.

```javascript
from sklearn.preprocessing import StandardScaler

# StandardScaler
scaler = StandardScaler()               # 객체생성
scaler.fit(iris_df)                     # fit
iris_scaled = scaler.transform(iris_df) # transform

# 데이터프레임으로 변환
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())
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180021_ImTh1rDTtUna5byM60?q=80&s=1280x180&t=outside&f=webp)

```javascript
import matplotlib.pyplot as plt

iris_df_standarded= (iris_df - iris_df.mean()) / iris_df.std()

# 기존 데이터셋 시각화
plt.title('Origin Dataset')
plt.hist(iris_df)
plt.legend(iris_df.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180041_BMmbL9lwDKk9t9Ld4E?q=80&s=1280x180&t=outside&f=webp)

```javascript
# 표준화한 데이터셋 시각
plt.title('Standarded Dataset')
plt.hist(iris_df_standarded)
plt.legend(iris_df_scaled.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180049_7MXdF6UXogXs0rmdek?q=80&s=1280x180&t=outside&f=webp)

---

### 로버스트(Robus)

- 건강한, 강력한

- 이상치에 크게 영향받지 않는 통계치

- ex) 중간값, 사분위 범위(Interquartiel Range; IQR)

### Robust Scaling

x_{scaled} = \frac{x_i - Q_2}{Q_3 - Q_1}

- 4분위수 range

- 표준화에서는 mean, std값 이용하여 계산

- robust는 median, IQR 이용하여 계산

- 로버스트 스케일을 진행한다고 이상치가 없어지거나 비대칭도가 줄어들지는 않는다.

**[sklearn.preprocessing.RobustScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.RobustScaler.html#sklearn.preprocessing.RobustScaler)**

```javascript
from sklearn.preprocessing import RobustScaler

# RobustScaler
robust_scaler = RobustScaler()
robust_scaler.fit(iris_df)
iris_scaled = robust_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())
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180104_sa6fyxPvQZ84ZULhpu?q=80&s=1280x180&t=outside&f=webp)

## 정규화

---

x_{new} = \frac{x - x_{min}}{x_{max} - x_{min}}

- 새로운 x의 값은 x의 평균을 뺀 값을 피처 x의 표준편차로 나눈 값으로 계산

- 데이터의 범위를 0부터 1까지로 변환하여 데이터 분포를 조정하는 방법이다.

[scklearn.preprocessing.Normalizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html)

x_i new = \frac{x_i}{\sqrt{x_i^2 + y_i^2 +z_i^2}}

- 사이킷런 전처리에서 제공하는 Normalizer 모듈과 일반적인 정규화는 약간의 차이가 있음

- 사이킷런의 Normalizer 모듈은 선형대수에서의 정규화 개념 적용, 개별 벡터의 크기를 맞추기 위해 변환 의미

- 즉, 개별 벡터를 모든 피처 벡터의 크기로 나눔, 세 개의 피처 x,y,z가 있으면 새로운 데이터 x_new는 원래 값에서 세 개의 피처의 i번째 피처 값에 해당하는 크기를 합한 값으로 나눠줌 (정규 벡터화)

- 앞의 4가지 스케일러는 각 특성(열)의 통계치를 이용하여 진행됩니다.

- 그러나 Normalizer 의 경우 각 샘플(행)마다 적용되는 방식입니다.

- 이는 한 행의 모든 특성들 사이의 유클리드 거리(L2 norm)가 1이 되도록 스케일링합니다.

- 일반적인 데이터 전처리의 상황에서 사용되는 것이 아니라 모델(특히나 딥러닝) 내 학습 벡터에 적용하며,

- 특히나 피쳐들이 다른 단위(키, 나이, 소득 등)라면 더더욱 사용하지 않습니다.

```javascript
from sklearn.preprocessing import Normalizer

# 변형 객체 생성
normal_scaler = Normalizer()
normal_scaler.fit(iris_df)

# 훈련 데이터 스케일링
X_train_scaled = normal_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())
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180208_0H39wkHNFnyAwge4RX?q=80&s=1280x180&t=outside&f=webp)

## MinMax Scaler

---

x_{scaled} = \frac{x-x_{min}}{x_{max}-x_{min}}

- 최대/최솟값으로 범위 변환

- 피처 범위를 0과 1로, 혹은 특정 값으로 제한할 수 있다.

- 정규화를 진행해도 왜도, 첨도, 이상치 여부는 원래 값과 같다.

- 이상치의 영향을 많이 받는다.

- 데이터에 이상치가 있을 경우 최솟값 혹은 최댓값으로 설정된다.

- 따라서 스케일링 전 정리해야 할 이상치가 없는지 확인하는 것이 바람직하다.

**[sklearn.preprocessing.MinMaxScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html#sklearn.preprocessing.MinMaxScaler)**

```javascript
from sklearn.preprocessing import MinMaxScaler

# MinMaxScaler
scaler = MinMaxScaler()
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())
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180239_nqeCB8EidzCAM2xs1K?q=80&s=1280x180&t=outside&f=webp)

```javascript
import matplotlib.pyplot as plt

iris_df_standarded= (iris_df - iris_df.max()) / (iris_df.max() - iris_df.min())

# 기존 데이터셋 시각화
plt.title('Origin Dataset')
plt.plot(iris_df)
plt.legend(iris_df.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180253_KBtsxZzciR6nl1QHzM?q=80&s=1280x180&t=outside&f=webp)

```javascript
# 정규화한 데이터셋 시각
plt.title('Standarded Dataset')
plt.plot(iris_df_standarded)
plt.legend(iris_df_scaled.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180305_BsiDemn8GoxgQZEsim?q=80&s=1280x180&t=outside&f=webp)

```javascript
df = pd.DataFrame([[166, 18],
                  [172, 25],
                  [158, 30],
                  [182, 21],
                  [161, 26],
                  [155, 15]])
df.columns = ['height', 'age']
df_new = (df - df.min())/(df.max() - df.min())

# 시각화
plt.title('Origin Dataset')
plt.plot(df)
plt.legend(df.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180320_PRt72d9R30u3g630uK?q=80&s=1280x180&t=outside&f=webp)

```javascript
plt.title('Scaled Dataset')
plt.plot(df_new)
plt.legend(df_new.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180328_JGgn4Z5m2tEzwq2Vqu?q=80&s=1280x180&t=outside&f=webp)

## MAxAbsScaler

---

x_{scaled} = \frac{x_i}{abs(X_{max})}

- 절대값으로

- 피처 값을 최대 절댓값으로 나누면 스케일링 된 피처 범위가 -1에서 1 사이로 설정

- 피처 각 값에 최솟값을 빼지 않는 다는 것이 MinMax와 다르다.

- 데이터가 0인 것은 피처 스케일링 이후에도 0

- 피처에 0이 많으면 희소 데이터 (Sparse Data)라고 하는데, 스케일링 한 후에도 희소성 유지 가능

**[sklearn.preprocessing.MaxAbsScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MaxAbsScaler.html#sklearn.preprocessing.MaxAbsScaler)**

```javascript
from sklearn.preprocessing import MaxAbsScaler

# MaxAbsScaler
maxabs_scaler = MaxAbsScaler()
maxabs_scaler.fit(iris_df)
iris_scaled = maxabs_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())
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180418_alTZD9X44KCnlgkr3X?q=80&s=1280x180&t=outside&f=webp)

```javascript
import matplotlib.pyplot as plt

iris_df_standarded= (iris_df) / (iris_df.abs().max())

# 기존 데이터셋 시각화
plt.title('Origin Dataset')
plt.plot(iris_df)
plt.legend(iris_df.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180428_UX5M3OS7uDevY4M1Jf?q=80&s=1280x180&t=outside&f=webp)

```javascript
# 정규화한 데이터셋 시각
plt.title('Standarded Dataset')
plt.plot(iris_df_standarded)
plt.legend(iris_df_scaled.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180439_4Zf7yzMWV8ImLZxqRP?q=80&s=1280x180&t=outside&f=webp)

```javascript
df = pd.DataFrame([[1.0, 18],
                  [0.8, 25],
                  [-0.5, 30],
                  [-1.8, 21],
                  [1.2, 26],
                  [0.6, 15]])
df.columns = ['sight', 'age']
df_new = df/df.abs().max()

# 시각화
plt.title('Origin Dataset')
plt.plot(df)
plt.legend(df.columns)
```

![Image](https://upload.cafenono.com/image/slashpagePost/20250201/180448_z2xMqG41b8JPMPVCrK?q=80&s=1280x180&t=outside&f=webp)

```javascript
plt.title('Scaled Dataset')
plt.plot(df_new)
plt.legend(df_new.columns)
```

For the site tree, see the [root Markdown](https://slashpage.com/yerim-devnote.md).
