第1回 Google Colaboratoryで始める機械学習のための特徴量エンジニアリング - カウントデータの二値化と離散化 

## はじめに
本記事ではカウントデータに対しての前処理として使用される二値化と離散化について解説しています。本記事は主に「[機械学習のための特徴量エンジニアリング](https://www.amazon.co.jp/dp/4873118689/ref=cm_sw_em_r_mt_dp_U_lRGxEbH5KBJJX)」を参考とさせて頂いておりますので、気になる方は是非チェックしてみてください。

また本記事の内容をより詳しく[YouTube](https://youtu.be/eZKRzxtfMcI)で解説しているのでこちらも気になる方はチェックしてみてください。

※本記事で解説するプログラムは全て[こちら](https://colab.research.google.com/drive/1r1gAfK_ZpO6085jjp7DZWzxr8LGoAhv-#scrollTo=r7Sc5Ri32MiM)にあります。

## 二値化とは
名前の通りターゲットの値を二値にする処理のことです。例えば以下の様な例を考えます。

```
例
ユーザーにおすすめの曲をレコメンドするシステムを作成したい。
ユーザーが曲を聞いた回数を特徴量として使用したいがどのようにデータを整形すれば良いか？
```
そこでとあるユーザーのデータを取り出したところ、以下の様なデータであったと仮定します。1列目が曲のID、2列目がその曲を再生した回数です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/babf3cba-797b-597a-7a4f-72c58c1a4526.png)

このデータをヒストグラムにすると以下の様になました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/2a44bd9a-9fb0-e3e3-53f5-88cd6ce705a3.png)

さてここで、ユーザーにおすすめの曲をリコメンドするためにはユーザーがその曲に興味を持ったのかという情報が重要になります。しかし、上記のままでは20回聞いた曲は、1回しか聞いていない曲しか聞いていない曲の20倍好きという情報をモデルに与えてしまうことになります。そこで一度でも曲を再生していれば興味があると仮定して1度以上再生された曲は1、一回も再生されていない曲は0と二値化しました。こうすることで、曲ごとの差をなくし興味のある曲と興味のない曲に分けることができました。

これをグラフに表すと以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/909181b4-4e7e-9338-0845-3e85c81ace95.png)


実装したコードを以下に示します。

```python:binary.py
import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

##乱数固定
np.random.seed(100)

##疑似データを生成
data_array = []
for i in range(1, 1000):
  s = np.random.randint(0, i * 10, 10)
  data_array.extend(s)
data_array.extend(np.zeros(9000))
data = pd.DataFrame({'Listen Count': data_array})

data_binary = pd.DataFrame()
##1をかけることでTrue, Falseから1,0に変換
data_binary['Listen Count'] = (data['Listen Count'] > 0) * 1
```

##離散化とは
### 固定幅による離散化
離散化を行うことで、連続したデータを同じグループとして扱うことができるので、

- スケールによる影響を取り除くことができる
- 外れ値をなくすことができる

というメリットがあります

例えば人の年齢が数値データとして与えられている場合は、0~10をグループ1, 10~20をグループ2....., 80以上をグループ9とすることで全ての年齢をグループに分けることが可能になります。数値データのままでもいいように感じるかもしれませんが、例えば110歳まで生きた人が数人いた場合その大きなデータに引っ張られてしまい、他の要素の影響が小さくなってしまうという問題が発生する場合があります。ですが80歳も110歳も同様に高齢者として同グループにすることで、このような問題を解消することができます。

今回は年齢を10歳ごとに区切りましたが、ライフスタイルに合わせ0~12(幼少期から小学校)をグループ1, 12~17(中高生)をグループ2というようにグループ分けする場合もあります。

また、数値が複数の桁にまたがる場合は、0~9, 10~99, 100~999, といったように10の累乗でグループ分けする場合もあります。

**10ごとに区切る場合**

```python:discretization.py
import numpy as np

small_counts = np.random.randint(0, 100, 20)
print(small_counts)

print(np.floor_divide(small_counts, 10))
```

**実行結果**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/9c6c7bdd-6ab3-b23c-3cd3-fec857b676a2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/0f6cf295-40af-3f8a-e560-34f435bf30e3.png)

**10の累乗でグループ化する場合**

```python:discretization.py
import numpy as np

large_counts = []
for i in range(1, 100, 10):
  tmp = np.random.randint(0, i * 1000, 5)
  large_counts.extend(tmp)

print(np.array(large_counts))
print(np.floor(np.log10(large_counts)))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/fdc9a6da-bd97-53c9-e767-3a1ad4e6c90c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/971b0f73-02a5-de78-dab8-08e57d88e8ee.png)


### 分位数による離散化
固定幅による離散化は非常に便利ですが、例えばカウントデータに大きなギャップがあると、データの入らないグループが複数できてしまいます。その様な場合は分位数を用います。分位数はデータを中央値で2分し、分けられたデータに対してさらに中央値で2分します。なので四分位数はデータを4個に分割し、十分位数ではデータを10個に分割します。

例えば以下のような分布のデータの十分位数を求めると下の表の様になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/8fa01285-0ffe-19ae-287e-677dbcc65b4b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/3198b805-64f8-a0ed-e09f-a10b007ec8c1.png)

この値をグラフに示すと以下の図の様になり、データの数量が均等になる様に幅が求められていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/e1adbf22-c784-9b87-bc28-45111b55d433.png)

実装したプログラムを以下に示します。

**分位数でグループ化する場合(グラフ)**

```python:quantile.py
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

##乱数固定
np.random.seed(100)

data_array = []
for i in range(1, 1000):
  s = np.random.randint(0, i * 10, 10)
  data_array.extend(s)
data_array.extend(np.zeros(2000))
data = pd.DataFrame({'Listen Count': data_array})

deciles = data['Listen Count'].quantile([.1, .2, .3, .4, .5, .6, .7, .8, .9])

print(deciles)

plt.vlines(deciles, 0, 5500, "blue", linestyles='dashed') 
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/479ffa61-d9b2-befc-ad79-2cdd1502b534.png)


**分位数でグループ化する場合**

```python:quantile.py
import numpy as np

large_counts = []
for i in range(1, 100, 10):
  tmp = np.random.randint(0, i * 1000, 5)
  large_counts.extend(tmp)
np.array(large_counts)

# 四分位数に変換
print(pd.qcut(large_counts, 4, labels=False))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591117/b290102c-051c-7188-cd5a-7fc4eb490a92.png)


## 最後に
YouTubeで機械学習を中心に技術書のレビューや解説動画を上げていこうと思っています。またIT系に行くなら知っておいた方がいい企業の紹介もやっています。YoutubeとQiita更新のモチベーションに繋がるため、いいね、チャンネル登録、高評価をよろしくお願い致します。

YouTube: https://www.youtube.com/channel/UCywlrxt0nEdJGYtDBPW-peg
Twitter: https://twitter.com/tatelabo