scikit-learn数値系特徴量の前処理まとめ(Feature Scaling)

[KaggleのTitanicチャレンジ](https://www.kaggle.com/c/titanic)で前処理をスムーズにできないかを調べていたら、知らないことも多く勉強となりました。もともと、標準化と正規化という単語すら毎回思い出している程度の理解度だったのでいい機会となりました。標準化と正規化程度しか知らなかったですが、**意外と処理方法が多く異常値への考慮で多くの知見を得る**ことができました。
**scikit-learnの数値系特徴量に対する前処理(preprocessing)モジュール**を整理しています。基本的に[scikit-learn ユーザガイド 6.3. Preprocessing data](https://scikit-learn.org/stable/modules/preprocessing.html)中心です。本当はカテゴリ型変数もやろうと思ったのですが、数値系だけで書くこと多すぎたので他記事に書くと思います。
scikit-learnのユーザガイドは素晴らしいのだけど、一方で単語で理解に時間がかかることも多いですね(私の英語力や数理的能力不足)。
カテゴリ系変数は[記事「カテゴリ変数系特徴量の前処理(scikit-learnとcategory_encoders)」](https://qiita.com/FukuharaYohei/items/06cc7b1c5eb124db6cdf)に書きました。

# `preprocessing`モジュール
scikit-learnの[`preprocessing`モジュール](https://scikit-learn.org/stable/modules/classes.html#module-sklearn.preprocessing)に機械学習向け前処理用関数があります。

## 数値系特徴への前処理関数
### 正規化と標準化
まずは、よく忘れる**正規化(normalization)**と**標準化(standardization)**についてです。

- 正規化(normalization): 特徴量の値の範囲を一定の範囲におさめる変換
- 標準化(standardization): 特徴量の平均を0、分散を1にする変換( $z = (x - \mu) / s$ )

Feature Scaling(特徴量をscaleすること)についてはCourseraの機械学習入門コースで習いました。

https://qiita.com/FukuharaYohei/items/bcd676691f61dac8d0c8#123-gradient-descent-in-practice-i---feature-scaling

正規化は画像データのように最小値や最大値が決まっている場合に使い、標準化は最小値・最大値が決まっていない場合に使う、という解説をよく見ます。個人的にその使い分け方法に異論もあるのですが、まだ使い分け方法について頭の中で整理しきれていないので書きません。

### 関数一覧
数値系特徴への代表的な関数の一覧です。全関数を当記事で解説はしていません(追加で更新する可能性あり)。

|関数|内容|
|:-:|:--|
|[Binarizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Binarizer.html)|しきい値を使った2値化|
|[KBinsDiscretizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.KBinsDiscretizer.html)|ビニング|
|[MaxAbsScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MaxAbsScaler.html)|正規化(絶対値の最大値)|
|[MinMaxScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html)|正規化(最小値と最大値)|
|[Normalizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html)|正規化(L1ノルム/L2ノルム/絶対値の最大値)|
|[StandardScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html)|標準化|
|[PolynomialFeatures](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html)|多項式化|
|[PowerTransformer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PowerTransformer.html)|ガウス分布化|
|[QuantileTransformer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.QuantileTransformer.html)|分位による変換|
|[RobustScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.RobustScaler.html)|パーセンタイルを使った変換|
|[FunctionTransformer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.FunctionTransformer.html)|関数を使った変換|

## 試したデータと前処理
数値の特徴量のみを持つ[カリフォルニア住宅価格のデータセット](https://scikit-learn.org/stable/datasets/real_world.html#california-housing-dataset)を使いました。以下の特徴量を使いました。

- MedInc median income in block: 収入の中央値
- Population block population: 人口
- Latitude house block latitude: 緯度

```python
import numpy as np
import pandas as pd

from matplotlib import pyplot as plt
from sklearn.preprocessing import MinMaxScaler, MaxAbsScaler, StandardScaler, RobustScaler, Normalizer, QuantileTransformer, PowerTransformer, KBinsDiscretizer
from sklearn.datasets import fetch_california_housing

dataset = fetch_california_housing()
housing = pd.DataFrame(dataset.data[:, [0, 4, 6]], 
                       columns=[dataset.feature_names[0], dataset.feature_names[4], dataset.feature_names[6]])
housing['Target'] = dataset.target
print(housing)
```

```:出力結果
       MedInc  Population  Latitude  Target
0      8.3252       322.0     37.88   4.526
1      8.3014      2401.0     37.86   3.585
2      7.2574       496.0     37.85   3.521
3      5.6431       558.0     37.85   3.413
4      3.8462       565.0     37.85   3.422
...       ...         ...       ...     ...
20635  1.5603       845.0     39.48   0.781
20636  2.5568       356.0     39.49   0.771
20637  1.7000      1007.0     39.43   0.923
20638  1.8672       741.0     39.43   0.847
20639  2.3886      1387.0     39.37   0.894

[20640 rows x 4 columns]
```

```python
print(housing.describe())
```

```:出力結果
             MedInc    Population      Latitude        Target
count  20640.000000  20640.000000  20640.000000  20640.000000
mean       3.870671   1425.476744     35.631861      2.068558
std        1.899822   1132.462122      2.135952      1.153956
min        0.499900      3.000000     32.540000      0.149990
25%        2.563400    787.000000     33.930000      1.196000
50%        3.534800   1166.000000     34.260000      1.797000
75%        4.743250   1725.000000     37.710000      2.647250
max       15.000100  35682.000000     41.950000      5.000010
```

```python
print(housing.info())
```

```:出力結果
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20640 entries, 0 to 20639
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype  
---  ------      --------------  -----  
 0   MedInc      20640 non-null  float64
 1   Population  20640 non-null  float64
 2   Latitude    20640 non-null  float64
 3   Target      20640 non-null  float64
dtypes: float64(4)
memory usage: 645.1 KB
None
```

箱ひげ図とヒストグラムで表示しました。

```python
def output_graphs(column):
    fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 3))
    fig.subplots_adjust(wspace=0.5, hspace=0.5)

    housing.boxplot(ax=axes[0], column=[column])
    housing[column].plot.hist(ax=axes[1])
    plt.show()

_ = [output_graphs(column_name) for column_name in housing]
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/70d10afe-0d7d-4ae0-11fb-f2099f702f49.png)

これから前処理モジュールを使って特徴量をScaleします。
`output_graphs_with_scale`でグラフ表示をしています。
`output_scaled`がSclae処理と相関係数および統計量を表示している部分です。相関係数を見やすくするためにフォーマット指定しています。
グラフは以下を表示しています。

- 1列目: 箱ひげ図(Scale前)
- 2列目: 箱ひげ図(Scale後)
- 3列目: ヒストグラム(Scale前)
- 4列目: ヒストグラム(Scale後)


```python
def output_graphs_with_scale(column, housing_scaled):
    fig, axes = plt.subplots(nrows=1, ncols=4, figsize=(18, 3))
    fig.subplots_adjust(wspace=0.5, hspace=0.5)

    housing.boxplot(ax=axes[0], column=[column])
    housing[column].plot.hist(ax=axes[2])
    
    housing_scaled.boxplot(ax=axes[1], column=[column])
    housing_scaled[column].plot.hist(ax=axes[3])
    plt.show()

def output_scaled(scaler):
    housing_scaled = housing.copy()
    housing_scaled.iloc[:, 0:3] = scaler.fit_transform(housing.iloc[:, 0:3])
    _ = [output_graphs_with_scale(column_name, housing_scaled) for column_name in housing.iloc[:, 0:3]]
    display(housing.corr().style.background_gradient(axis=None))
    display(housing_scaled.corr().style.background_gradient(axis=None))
    print(housing_scaled.iloc[:, 0:3].describe())

pd.options.display.float_format = '{:.3f}'.format
```

## Scale処理
### 標準化([`StandardScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html))
[`StandardScaler`関数](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html)を使って平均値0、分散値1に標準化します(平均値と分散値を変更も可能)。

```python
output_scaled(StandardScaler())
```

グラフを見ると平均値が0になっているのが、よくわかります。また、線形変換なので分布の形自体は変わっていないことがわかります。[「Compare the effect of different scalers on data with outliers」](https://scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.html)に書かれているのですが、Populationを見ると異常値の影響を強く受けていて、Scalerでは取り除けていないことがよくわります(最大値がそのまま残るため、ヒストグラムの右側に何もないように見える部分が残る)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/3d7b6573-36dc-3da0-277a-65aa49c0990f.png)


```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      0.000      -0.000    -0.000
std       1.000       1.000     1.000
min      -1.774      -1.256    -1.448
25%      -0.688      -0.564    -0.797
50%      -0.177      -0.229    -0.642
75%       0.459       0.264     0.973
max       5.858      30.250     2.958
```

### 正規化
3種類の正規化用関数があります。[Nomalizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html)は、次の章の非線形変換に入れても良かったかもしれません。

|関数|説明|最小値|最大値|式|
|:-:|:--|:-:|:-:|:-:|
|[MaxAbsScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MaxAbsScaler.html)|絶対値の最大値で除算|-1|1|$\frac{x}{\max &#124;x&#124;}$|
|[MinMaxScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html)|最小値と最大値でScale|0|1|$\frac{x - \min x}{\max x-\min x}$|
|[Nomalizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html)|L1\L2ノルムで正規化|-1|1|$\frac{x}{ノルム}$|

#### [`MaxAbsScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MaxAbsScaler.html)
まずは[`MaxAbsScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MaxAbsScaler.html)を使って0から1の間にScaleさせます。

```python
output_scaled(MaxAbsScaler())
```

グラフを見ると0から1の範囲に収束していることが、よくわかります。また、線形変換なので分布の形自体は変わっていないことがわかります。標準化と同じく[「Compare the effect of different scalers on data with outliers」](https://scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.html)に書かれているのですが、Populationを見ると異常値の影響を強く受けていて、Scalerでは取り除けていないことがよくわります(最大値がそのまま残るため、ヒストグラムの右側に何もないように見える部分が残る)。また、`MinMaxScaler`ではないため、Latitudeが0.7以上しかデータがないことがわかります(基本統計量のminの値が0.776)。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/6f90c751-7d08-6051-06d0-e457620618c3.png)


```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      0.258       0.040     0.849
std       0.127       0.032     0.051
min       0.033       0.000     0.776
25%       0.171       0.022     0.809
50%       0.236       0.033     0.817
75%       0.316       0.048     0.899
max       1.000       1.000     1.000
```

#### [MinMaxScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html)
続いて[MinMaxScaler](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html)です。

```python
output_scaled(MinMaxScaler())
```

ほぼ`MaxAbsScaler`と同じですね。Latitudeは`MaxAbsScaler`と違い、最小値を0としているため0から1の範囲に分布できていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/dd44f396-34a1-cde5-d33a-828535151a68.png)


```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      0.232       0.040     0.329
std       0.131       0.032     0.227
min       0.000       0.000     0.000
25%       0.142       0.022     0.148
50%       0.209       0.033     0.183
75%       0.293       0.048     0.549
max       1.000       1.000     1.000
```

#### [Nomalizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html)
正規化の最後に[Nomalizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html)です。L1とL2ノルムについて忘れた人は以下を見て思い出しましょう。

https://qiita.com/kenmatsu4/items/cecb466437da33df2870

デフォルトのL2ノルムから。以下の計算式となります。MaxAbsScalerやMinMaxScalerと異なり、各行ごとの計算でノルムを出すため、特徴量同士の値が関係します。


```math
\frac{特徴量}{\sqrt{{MedInc}^2+{Population}^2+{Latitude}^2}}
```

```python
output_scaled(Nomalizer())
```

(負の値がないので)0から1までに収束しているのですが、それぞれ短い範囲内にしかばらけていないです。いまいち使い方を理解できていないのですが、最初に全特徴量を標準化などして尺度を一定にしてから使うのでしょうか。時間がないので、調べずに先に行きます。L1ノルムは、今回のデータだと結果がほぼ同じで、実行方法も同じ関数に対してパラメータを変えるだけなのでここでは記述しません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/8067416d-a330-0547-bafc-a6c7049c575f.png)

```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      0.005       0.996     0.045
std       0.010       0.033     0.071
min       0.000       0.088     0.001
25%       0.002       0.999     0.020
50%       0.003       1.000     0.030
75%       0.005       1.000     0.046
max       0.354       1.000     0.996
```

### [`RobustScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.RobustScaler.html)
[`RobustScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.RobustScaler.html)と呼ばれる異常値にある程度強いScalerもあります。中央値を0にし、25%と75%のパーセンタイルでScaleします(25%と75%のパーセンタイル値の差で除算)。

```python
output_scaled(RobustScaler())
```

ただ線形変換をしているだけなので分布は変わっていません。中央値(基本統計量の50%)が0になっているのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/77d37531-a084-4418-37b5-6bcca8783135.png)

```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      0.154       0.277     0.363
std       0.872       1.207     0.565
min      -1.392      -1.240    -0.455
25%      -0.446      -0.404    -0.087
50%       0.000       0.000     0.000
75%       0.554       0.596     0.913
max       5.260      36.797     2.034

```

### 非線形変換
#### 分位([`QuantileTransformer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.QuantileTransformer.html))
分位である[`QuantileTransformer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.QuantileTransformer.html)には主に2種類の実行方法があります。どちらも異常値に対して強いScale方法です。
まずはデフォルであるuniformから。

```python
output_scaled(QuantileTransformer())
```

グラフを見ると一様分布しているのが一目瞭然です。値の大小によってランク付けして0から1の範囲に順にScaleしてくれるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/b9b93711-1624-5800-0e4a-896a7cdf9053.png)


```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      0.500       0.500     0.500
std       0.289       0.289     0.289
min       0.000       0.000     0.000
25%       0.250       0.250     0.247
50%       0.500       0.500     0.502
75%       0.750       0.750     0.749
max       1.000       1.000     1.000
```

今度はパラメータ`output_distribution`に`normal`を渡し、ガウス分布にします。

```python
output_scaled(QuantileTransformer(output_distribution='normal'))
```

グラフを見るとガウス分布しているのが一目瞭然です。値の大小によってランク付けしてガウス分布pにScaleしてくれるようです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/cc89ba66-d6bf-0a0c-d567-b502a1adb61c.png)

```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      0.004      -0.000     0.000
std       1.024       1.000     1.001
min      -5.199      -5.199    -5.199
25%      -0.675      -0.675    -0.683
50%      -0.000      -0.000     0.004
75%       0.674       0.675     0.671
max       5.199       5.199     5.199
```

#### [`PowerTransformer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PowerTransformer.html)
[`PowerTransformer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PowerTransformer.html)はガウス分布化します。実行時のパラメータによってBox-CoxとYeo-Johnsonの2種類の方法が選べます。
Box-Coxは負の値を受け付けないのが注意点で、両者の違いはそれ以外理解していません(式だけ載せておきます)。

```math 
\begin{split}x_i^{(\lambda)} =
\begin{cases}
 [(x_i + 1)^\lambda - 1] / \lambda & \text{if } \lambda \neq 0, x_i \geq 0, \\[8pt]
\ln{(x_i + 1)} & \text{if } \lambda = 0, x_i \geq 0 \\[8pt]
-[(-x_i + 1)^{2 - \lambda} - 1] / (2 - \lambda) & \text{if } \lambda \neq 2, x_i < 0, \\[8pt]
 - \ln (- x_i + 1) & \text{if } \lambda = 2, x_i < 0
\end{cases}\end{split}
```

```math
\begin{split}x_i^{(\lambda)} =
\begin{cases}
\dfrac{x_i^\lambda - 1}{\lambda} & \text{if } \lambda \neq 0, \\[8pt]
\ln{(x_i)} & \text{if } \lambda = 0,
\end{cases}\end{split}
```

##### PowerTransformer(Yeo-Johnson)
`QuantileTransformer`ほどきれいにガウス分布化されないですが、平均値0で分散値1になっていますね。そして`StandardScaler`と比べてきれいに分布しています。上の数式に書いてある場合分けが異常値の影響を緩和しているからでしょう(正確に理解していないで雰囲気で言っています)。

```python
output_scaled(PowerTransformer('yeo-johnson'))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/6f9449a0-b9c4-e4b3-afd8-74c8acf953ac.png)

```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      0.000       0.000    -0.000
std       1.000       1.000     1.000
min      -3.446      -4.529    -1.816
25%      -0.671      -0.570    -0.776
50%       0.021      -0.034    -0.566
75%       0.668       0.552     1.032
max       3.147       7.451     2.082
```

##### PowerTransformer(Box-Cox)
そういうデータだからか、あまりYeo-Johnsonと変わりません。

```python
output_scaled(PowerTransformer('box-cox'))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/c167c829-b160-72c0-90de-394d6eea0667.png)

```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean     -0.000       0.000     0.000
std       1.000       1.000     1.000
min      -3.803      -4.604    -1.817
25%      -0.658      -0.570    -0.776
50%       0.017      -0.034    -0.566
75%       0.653       0.552     1.032
max       3.310       7.475     2.081
```

### Binning([`KBinsDiscretizer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.KBinsDiscretizer.html))
Binningです。encodeはデフォルトでない`ordinal`を使いました。一様分布するようにビニングしてくれます。

```python
output_scaled(KBinsDiscretizer(encode='ordinal'))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/54359c44-f98e-1f15-8b02-142006f64824.png)

```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean      2.000       2.000     2.002
std       1.414       1.414     1.415
min       0.000       0.000     0.000
25%       1.000       1.000     1.000
50%       2.000       2.000     2.000
75%       3.000       3.000     3.000
max       4.000       4.000     4.000
```

strategyを`uniform`に変えると等間隔で切ってくれます。strategyの違いによる影響は以下を見るとわかりやすいです。

https://scikit-learn.org/stable/auto_examples/preprocessing/plot_discretization_strategies.html#sphx-glr-auto-examples-preprocessing-plot-discretization-strategies-py

```python
output_scaled(KBinsDiscretizer(encode='ordinal', strategy='uniform'))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/393810f7-6380-4c78-858d-32745f74b4d7.png)

```:基本統計量
         MedInc  Population  Latitude
count 20640.000   20640.000 20640.000
mean     -0.000       0.000     0.000
std       1.000       1.000     1.000
min      -3.803      -4.604    -1.817
25%      -0.658      -0.570    -0.776
50%       0.017      -0.034    -0.566
75%       0.653       0.552     1.032
max       3.310       7.475     2.081
```

ちなみにデフォルトのencode `onehot` を使うと疎行列で結果が返ってきます。

```python
# OutputはCSRでCompressed Sparse Row。圧縮行格納方式。最もスタンダードな方式らしい。
# https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html
X = [[-2, 1, -4,   -1],
      [-1, 2, -3, -0.5],
      [ 0, 3, -2,  0.5],
      [ 1, 4, -1,    2]]

est = KBinsDiscretizer(n_bins=3, encode='onehot', strategy='quantile')
Xt = est.fit_transform(X)
print('---疎行列---')
print(Xt)

print('---data---')
print(Xt.data)

print('---indices---')
print(Xt.indices)

print('---行インデックスを圧縮したリスト---')
print(Xt.indptr)
```

```:出力結果
---疎行列---
  (0, 0)	1.0
  (0, 3)	1.0
  (0, 6)	1.0
  (0, 9)	1.0
  (1, 1)	1.0
  (1, 4)	1.0
  (1, 7)	1.0
  (1, 10)	1.0
  (2, 2)	1.0
  (2, 5)	1.0
  (2, 8)	1.0
  (2, 11)	1.0
  (3, 2)	1.0
  (3, 5)	1.0
  (3, 8)	1.0
  (3, 11)	1.0
---data---
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
---indices---
[ 0  3  6  9  1  4  7 10  2  5  8 11  2  5  8 11]
---行インデックスを圧縮したリスト---
[ 0  4  8 12 16]
```

# こぼれ話
## zero mean and unit variance
[6.3.1. Standardization, or mean removal and variance scaling](https://scikit-learn.org/stable/modules/preprocessing.html#standardization-or-mean-removal-and-variance-scaling)に以下の記述があり、初見で意味が理解できませんでした。

> Gaussian with zero mean and unit variance

平均値0と分散1のガウス分布の意味。scikit-learnユーザガイドで理解しにくかった単語。私の英語力不足。

## 異常値の影響
異なるscalerによる異常値を含んだデータに対する影響の違いを以下で学びました。

https://scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.html

上記記事の内容は、とっつきにくいのでいくつか補足です。ちょっとしたメモ書き程度。
### グラフ全般について
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/53d10ae0-63d5-bd97-e000-11d139d81f68.png)

- 左と右のグラフの違い: 左はZoom-in(パーセンタイル処理)なしで表示。右はズームインしたもの。異常値があるので左は線のようにY軸がバラけていない。両者ともにScaler適用後。
- X軸: 特徴(収入の中央値)
- Y軸: 特徴(住んでいる人数)
- 上のヒストグラム: 特徴(収入の中央値)
- 右のヒストグラム: 特徴(住んでいる人数)の分布
- 散布図上の点の色と右のカラーマップ: 目的変数(住宅価格)の値に応じた色
- 住宅価格の単位は、(調べていないがおそらく)100万円。
- 右散布図は99%パーセンタイル処理をして上位1%と下位1%は除外。
- yに関してはパーセンタイル処理なし(異常値がないのでしょう)

```python:右散布図のパーセンタイル処理箇所
    # zoom-in
    zoom_in_percentile_range = (0, 99)
    cutoffs_X0 = np.percentile(X[:, 0], zoom_in_percentile_range)
    cutoffs_X1 = np.percentile(X[:, 1], zoom_in_percentile_range)
```

### StandardScaler
StandardScalerが平均値を0にし、分散値を1にした。実際の平均や標準偏差計算時に異常値が影響を与えているため、左グラフは線のような散布図になってしまっている。
zoom-inでは、異常値があるため、X(収入の中央値)は-2から4の範囲にある。一方で、Y(住んでいる人数)が-0.2から0.2の相対的に小さな幅に収束している。つまり、StandardScalerではバランスのあるFeature scaleは保証されない。
異常値の影響を受けやすい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/f8c03ca4-c892-e644-efdc-4621540adfa1.png)

### MinMaxScaler
StandardScalerと同様に異常値の影響を受けており、左グラフは直線のような散布図になっている。また、右グラフのY(住んでいる人数)は0.005以下のとても狭い範囲に分布してしまう。
つまり、異常値に影響を大きく受けやすい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/5d5baf73-57d2-f6cc-fc5b-69bd9aa22a82.png)

## 参考記事
あと、この記事を参考にしました。

https://own-search-and-study.xyz/2016/11/23/sklearn%E3%81%AEpreprocessing