Target Mean Encoding

## Target Mean Encodingとは

カテゴリカル変数はそのままでは、機械学習モデルの入力にすることができません。
普通は、One Hot Encoding(ダミー変数化)をするかと思います。(自分はこれしか知りませんでした。)

カテゴリカル変数のEncoding手法の一つであるTarget Mean Encoding(Likelihood Encoding)は、目的変数の情報を利用し、カテゴリカル変数を数値に変換する方法で、Kaggle等の機械学習コンペではよく用いられているようです。
目的変数がBoolean表現の場合、カテゴリごとの確率を、数値であればカテゴリごとの平均を特徴量とします。


## 実行例

例として、以下のようなデータセットでTarget Mean Encoding(以下、Target Encoding)を試してみます。

```python
df = pd.DataFrame({ 'category': ['A','A','B','B','B','C','C','C','C','D'],
                    'label': [1,0,1,0,0,1,0,1,1,1]
                  })
```

```
  category  label
0        A      1
1        A      0
2        B      1
3        B      0
4        B      0
5        C      1
6        C      0
7        C      1
8        C      1
9        D      1
```

Pandasのgroupbyを使った実行方法を試してみます。
方法は以下の2つです。

```python
# 1
label_mean = df.groupby('category').label.mean()
df = df.assign(target_enc=df['category'].map(label_mean).copy())

# 2
df = df.assign(target_enc2=df.groupby('category')['label'].transform('mean').copy())
```

得られる結果はこちらです。

```
  category  label  target_enc  target_enc2
0        A      1    0.500000     0.500000
1        A      0    0.500000     0.500000
2        B      1    0.333333     0.333333
3        B      0    0.333333     0.333333
4        B      0    0.333333     0.333333
5        C      1    0.750000     0.750000
6        C      0    0.750000     0.750000
7        C      1    0.750000     0.750000
8        C      1    0.750000     0.750000
9        D      1    1.000000     1.000000
```

Target Encodingできていることが確認できました。


## Leakageについて

Leakageとは、モデルを学習する際に、本来知らないはずの情報を不当に使ってしまうことです。
[Kaggleのドキュメント](https://www.kaggle.com/docs/competitions)には、Leakageについての項目が設けられています。
ここでは以下の具体例が挙げられています。

- 患者が前立腺癌かどうかを予測するためのデータに「前立腺の手術を受けたか」という変数を含めていた

Target Encodingでは、学習データで得られた各カテゴリの特徴量をテストデータ内の各カテゴリに適用します。これが、Leakageが引き起こしてしまいます。
本記事では、Leakageによる過学習を防ぐための手法について、2つ紹介します。

### Leave One Out(LOO)

この手法は、値を計算するときに、その行データ以外で計算するというものです。

**通常のTarget Encoding**

| |category|label|target_enc|
|:--|--:|--:|--:|
|0|A|1|0.50|
|1|A|0|0.50|
|2|B|1|0.33|
|3|B|0|0.33|
|4|B|0|0.33|
|5|C|1|0.75|
|6|C|0|0.75|
|7|C|1|0.75|
|8|C|1|0.75|
|9|D|1|1.00|

**Target Encoding with LOO**

||category|label|target_enc|
|:--|--:|--:|--:|
|0|A|1|0.00|
|1|A|0|1.00|
|2|B|1|0.00|
|3|B|0|0.50|
|4|B|0|0.50|
|5|C|1|0.66|
|6|C|0|1.00|
|7|C|1|0.66|
|8|C|1|0.66|
|9|D|1|0.00|

Cについて見てみましょう。
5行目のCのtarget_encは

```math
(0+1+1)/3 = 2/3 = 0.66
```

となります。
また、6行目のCのtarget_encは

```math
(1+1+1)/3 = 3/3 = 1.00
```

となります。

ただ、この手法だと余計にLeakageを起こしているように見えます。
(例えばBについて、labelの0, 1がtarget_encの0.5, 0に対応してしまっている)

### Smoothing

この手法は、データ数が少ないカテゴリを持つカテゴリカル変数に対して効果的にTarget Encodingを用いるための手法です。
データ数が十分あるカテゴリについては通常のTarget Encodingに近い値を示し、データ数が少ないカテゴリについてはデータセット全体での値に近づくようにスムージングを行います。
詳しく知りたい方は、[こちらのブログ](https://mikebird28.hatenablog.jp/entry/2018/06/14/172132)がわかりやすいです。

```math

S_i = λ(n_i)\frac{n_iy}{n_i}+(1−λ(n_i))\frac{n_y}{n_tr}\\

λ(n_i) = \frac{1}{1+exp(-\frac{n_i−k}{f})}

```

`k=0, f=1`(λは標準シグモイド関数になる)でカテゴリA,Bについて計算してみると、以下のようになります。

```math

λ(2) = \frac{1}{1+exp(-2)} = 0.880\\

S_A = 0.880*\frac{1}{2} + (1−0.880)*\frac{6}{10} = 0.512

```

```math

λ(3) = \frac{1}{1+exp(-3)} = 0.953\\

S_B = 0.953*\frac{1}{3} + (1−0.953)*\frac{6}{10} = 0.346

```


## 変換時の注意点

以下でも実行できますが、どちらも警告が出ます。

```python
# 1
label_mean = df.groupby('type').label.mean()
df['target_enc'] = df['type'].map(label_mean)

# 2
df['target_enc2'] = df.groupby('type')['label'].transform('mean')
```

```
SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: 
http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
```

ドキュメントの注意事項を参照しても、どう対応すればいいのかわからなかったのですが、
実行例で示したようにすることで警告は出なくなりました。


## 参考

[カテゴリカル変数のEncoding手法について]
(http://nami3373.hatenablog.com/entry/2018/07/26/230655)

[Feature Enginnering]
(https://www.slideshare.net/0xdata/feature-engineering-83511751)

[TargetEncodingのスムーシング]
(https://mikebird28.hatenablog.jp/entry/2018/06/14/172132)