モデリングのための特徴量の前処理

# はじめに
自身がデータ解析を行う上で, `モデリングのための特徴量の前処理`をメモとして残しておきます．
前処理を行う以前の，データのEDAなどは
[データ解析を行う際のTips ・注意点](https://qiita.com/Shogo-dayo/items/f0bd44793231f37b73c6)
に記載していますので，参考にしてください．


前処理を行う上で重要になるのは，
***・欠損値処理***
***・前処理を行う特徴量の型タイプが何であるか***ということです．

本稿では
- **Numeric（数値データ）**
- **Categorical（カテゴリカルデータ）**
の2つの型タイプに対しての前処理の手法について記載します．
`他にも, Datetime, 位置情報データなど様々なデータに対しての前処理がありますが， 本記事では記載しておりませんことをご了承ください．`

＜前情報＞
今回はKaggleのコンペで使用されているデータを用いて説明します．
[Kaggle House Prices DataSet](https://www.kaggle.com/c/house-prices-advanced-regression-techniques "HousePrices Data")

[Kaggle House Prices Kernel](https://www.kaggle.com/gloomychan/house-prices-labelencode "HousePrices Kernel")
を用いてメモしておきます．

`Memo用に残しおくため， モデル精度などは気にしていないことをご了承ください`

＜データの中身＞
特徴量は全てで81, このうちのSalePricesを目的変数( target )として解析を行います．
SalePrices以外を説明変数とします.

##欠損値処理
まずはデータの中身を確認して，そのデータに欠損値が含まれている場合，欠損値をなくすために二つのアプローチがある.
`1. 欠損値を含む列，行を削除する.`
`2. 欠損値に対して，別の数値で補完（穴埋め）する.`

上記を行う際に注意しなければならないのが，***そのデータをなぜ分析するのか，何をOutputとして出力したいのか***が重要になってくる．
データ分析を行う理由として考えられるのは，2点であり，***1つ目は目的変数(target)を精度良く予測するモデルの構築，2つ目はデータの理解***である．
仮に1つ目の精度良く予測するモデルの構築である場合，むやみに欠損値を含む列，行を削除してしまえば，データ数が大幅に減少してしまう可能性がある．これは得策でないことが言える．そこで欠損値に対して，別の数値で補完することが得策であるでしょう．この時，平均値で補完することが代表的な例です．
一方，データの理解である場合，むやみに補完してデータに手を加えすぎるのはデータの誤った理解につながってしまうかもしれません．
***つまり，欠損値を処理する場合は，そのデータを分析する際に何を重要視しているかが大事であるということです．***

下記では，`1. 欠損値を含む列，行を削除`, `2. 欠損値に対して，別の数値で補完（穴埋め）`についてのコードを記載します

```python

import pandas as pd

## load Data
df = pd.read~~~~(csv , json etc...)  

## count nan
df.isnull().sum()
```

![スクリーンショット 2020-08-23 17.56.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482799/f827c4b8-a03f-75b1-326f-090f20dd30c3.png)
上記の図から，今回のデータでは欠損値が含まれていることがわかります．
では，*LotFrontage*について，欠損値処理を行っていきたいと思います．


###欠損値を含む列，行を削除 dropna()

-  .dropna(how="???", axis=(0 or 1))  :how=???にはanyかall ,axis=0である場合は行, 1である場合は列

anyの場合， 欠損値が1つでも含まれる列か行が削除される．
一方allの場合は，全ての値が欠損値である列か行が削除される

```python
df.dropna(how="any", axis=0)
```

![スクリーンショット 2020-08-23 18.10.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482799/59a5ca91-f1b4-427b-63f2-134c8692edcc.png)

```python
df.dropna(how="all", axis=0)
```

![スクリーンショット 2020-08-23 18.11.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482799/11ad8e6d-dcc1-112b-c4dc-691c1a61569d.png)


anyの場合,欠損値が一つでも含まれる場合，指定したaxisの値は削除される.結果dfのshapeは(0,81)となった．
allの場合,欠損値が全て含まれる場合，指定したaxisの値は削除される.結果dfのshapeは(1460,81)となった．

***<補足>***
特定の行や列に欠損値がある行・列の削除を行いたい場合

- .dropna(subset=["???"]) : subsetで特定の列・行を選択

```python
df.dropna(subset=["LotFrontage"])
```
![スクリーンショット 2020-08-23 18.20.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482799/e04fa73e-2737-7ce6-5b8d-5be0402d03a4.png)

引数として, subsetを用いることで，特定の列・行に欠損値が含まれている場合，その列・行を削除することができる．
この引数は，たまに有効的に働くことができる．


###欠損値に対して，別の数値で補完（穴埋め） fillna()

- .fillna(??? , inplace=bool(True of False))  : ???に任意の値で補完（穴埋め）, inplaceを指定することで元のオブジェクトを変更可

inplace=Trueにした場合，メモリを増やさずに更新することができるが，オブジェクトが更新されているため，再利用が困難となってしまう．．．．．．
そのため，大規模でないデータに対しては，新しいオブジェクトを生成することを勧めます． (個人的な意見です．．．)

```python
# NANに対して， 0で穴埋めする.
df.fillna(0)

# 複数カラムに対して補完した場合
df.fillna({'LotFrontage': df["LotFrontage"].mean(),'PoolArea': df["PoolArea"].median(), 'MoSold': df["MoSold"].mode().iloc[0]})
```

上記のように引数部分に対して，その列の平均値(mean)や中央値(median)を穴埋めすることができる．
`最頻値(mode)の場合は, データフレームで返ってくるので，iloc[0]の先頭行を取得する．`

他にもfillnaには様々な補完方法があるので，公式ドキュメントで確認していただきたいです．
[pandas.DataFrame.fillna](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.fillna.html "Fillna")


##Numeric（数値データ）
Numericデータを取り扱うときに，まずするべきことは，***スケーリング***です．
スケーリングとは，ある一定の幅の数値に変換するということです．例えば， 気温と降水量，湿度などの変数からアイスの売り上げを予測したい場合，各変数では，単位と値の範囲が異なります．このまま学習を行うと，うまく学習できない可能性があり，ある一定の幅の数値に揃える必要があります．これがスケーリングです．

特徴量のスケーリングにはいくつか方法があります．本記事では，自身が比較的使っている `1.Min Max Scaler , 2.Standard Scaler, 3.log transformation`について触れたいと思います．


### 1. Min Max Scaler

`全ての特徴量の値を同じスケールに変換することです．`
`全ての値から最小値を引き，　MinとMaxの差で割る. その結果, 値は０〜１となる． `
***しかしこの手法にはデメリットがあり, 0〜1の範囲に値を収めるため, 標準偏差が小さくなり, 外れ値の影響が抑制されてしまいます．***
仮に外れ値を気にする必要がある場合, この手法では, その考察を行いにくいことが言えます．

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

```python
from sklearn import preprocessing

# まずはdfからdtype=numberのカラム列を抽出
numb_columns = df.select_dtypes(include=['number']).columns

# dtype=numberのデータのみ抽出
num_df = df[numb_columns]

# MinMaxScaler
mm_scaler = preprocessing.MinMaxScaler()

# arrayで取得で
num_df_fit = mm_scaler.fit_transform(num_df)

# array to Dataframeに変換
num_df_fit = pd.DataFrame(num_df_fit, columns=numb_columns)

```

**変換処理終了後は， 正しくスケーリングできているかを確認しておくほうが良いです．**
例えば，

```python

# 各特徴量の最大値の確認
num_df_fit.max()

# 各特徴量の最小値の確認
num_df_fit.min()
```

このようにして変換後は， 必ず確認しておくことがベストです:relaxed:


### 2. Standard Scaler

`平均0, 分散1の標準化された分布に変換することができます.`
まず, 平均値を引くことで0前後の値とする. 次に, 値を標準偏差で除算して, 結果の分布が平均0, 標準偏差1の標準となるようにする変換方法です. 
StandardScalerでも先ほどMinMaxScalerと同様のデメリット（外れ値処理）が考えられます．
StandardScalerでは, 平均値と標準偏差を計算する際に外れ値が影響を及ぼし, 特徴量のrangeが狭まります. 
***特に、各特徴量の外れ値の大きさが異なるため, 各特徴量の変換データの広がりは大きく異なる可能性が出てきます．***


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

```python

# StandardScaler
s_scaler = preprocessing.StandardScaler()

# arrayで取得
num_df_s_fit = s_scaler.fit_transform(num_df)

# array to Dataframeに変換
num_df_s_fit = pd.DataFrame(num_df_s_fit, columns=numb_columns)

```


### 3. log transformation

対数変換は以前の記事で議論させていただいたので， 軽めに説明させていただきます．
機械学習のモデルでは，　
`正規分布を仮定していることが多いので，　正規分布に従っていない変数がある場合，　正規分布に従うように対数変換などを行う`ことがあります．
[データ解析を行う際のTips ・注意点](https://qiita.com/Shogo-dayo/items/f0bd44793231f37b73c6 "対数変換")

```python

num_df = np.log(num_df)
```


##Categorical(カテゴリカルデータ)
Catergoricalデータとは, 性別（男性／女性）や都道府県（北海道／青森県／岩手県...）などの文字列データのことである． `このようなデータを取り扱うときは数値データに変換してから取り扱うことが多い. `
本記事では， 自身が比較的使っている `1.Label Encoding , 2.One Hot Encoding`について触れたいと思います．

### 1. Label Encoding
この手法が最も一般的に用いられる手法であると思います．
`選択した特徴量のユニークな値を抽出し，異なる数値にマッピングする手法です．`
このようにすることで，文字列で取得した特徴量を，数値に変更することができます.

[sklearn.preprocessing.LabelEncoder](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html#sklearn-preprocessing-labelencoder "LabelEncoder")

```python
from sklearn import preprocessing
le = preprocessing.LabelEncoder()

# まずはdfからdtype=objectのカラム列を抽出
obj_columns = df.select_dtypes(include=["object"]).columns

# dtype=objectのデータのみを抽出
obj_df = df[obj_columns]

# Streetの列のユニーク値を抽出
str_uniq = obj_df["Street"].unique()

# labelEncoder
le.fit(str_uniq)

list(le.classes_)

le.transform(str_uniq)
```

![スクリーンショット 2020-08-25 17.10.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/482799/40855a03-fcd4-6e27-f4cd-a4f091991574.png)

上記のように， **le.fit**でインスタンスを作成し，
**list(le.classes_)**で特徴量のユニーク値を取得することができる．
**le.transform()**でユニーク値を数値にマッピングする.

`LabelEncoderで変換した数値を扱うときは， Tree Based Model(ランダムフォレストなど)が良いとされている．`
`Non Tree Nased Model(回帰分析など)では効果的でない．`
***なぜならユニーク値を数値に変換するが，その数値の大小に何ら意味がないにも関わらずに，回帰分析の場合，数値の大小を取り扱ってしまうためである．***


### 2. One Hot Encoding
LabelEncoderでは，回帰分析に適用できないため，回帰分析に適用するために用いられるのがOne Hot Encodingである．（この言い方をすると語弊があるかもしれません．．．．．） というのも, `One Hot Encodingはすでに最大値1，最小値0にスケーリングされているからである．`
***逆にカテゴリ数分だけのバイナリデータが特徴量として生成されるため， Tree Based Modelには適さない．***

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

```python
from sklearn import preprocessing

# One Hot Encoder
oh = preprocessing.OneHotEncoder()

str_oh = oh.fit_transform(obj_df.values)
```

transformの引数にはarray型で指定する．

# まとめ
本記事では，モデリングのための特徴量の前処理について記載いたしました．
KaggleなどではCategoricalデータに対して，**Target Encoding**をよく用いられているのを見かけます．
自身の勉強不足でまだ記事に載せることはできていませんが，勉強次第，更新したいと思います．

# データ分析用記事
[データ解析を行う際のTips ・注意点](https://qiita.com/Shogo-dayo/items/f0bd44793231f37b73c6)