ディシジョンツリー作成時のデータの離散化について

**Pythonで0からディシジョンツリーを作って理解する**
[1.概要編](https://qiita.com/toyohisa/items/ab60d3eab11730f156da) - [2. Pythonプログラム基礎編](https://qiita.com/toyohisa/items/087ac1b03a187be2292f) - [3. データ分析ライブラリPandas編](https://qiita.com/toyohisa/items/180c0b1df42fb188f920) - [4.データ構造編](https://qiita.com/toyohisa/items/de59b5f01b34f69e596d) - [5.情報エントロピー編](https://qiita.com/toyohisa/items/eb5bfd2e08e580d99690) - [6.ツリー生成編](https://qiita.com/toyohisa/items/cf38e4f815f4a7a03e09) - (番外編) 離散化

# ID3によるディシジョンツリー生成は、数値データを扱うことができません

数値データの100,10,1があるとします。次のような関係があります。
100 >> 10 > 1

数値データ以外には、例えば、文字型のデータ（名義尺度とも呼ばれます）があり、100,10,1を文字として扱うと次のようになります。
"100" ≠ "10" ≠ "1"

「大きい、小さい」という情報が消えるだけでなく、10は、100よりも1に近い、という情報も消えてしまいます。

# 離散化せずに、ID3によるディシジョンツリーを作成する場合の問題点

次のようなデータがあるとします。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275020/60dfe996-fd87-151d-9904-2ae154853e0c.png">

このデータから、[Pythonで0からディシジョンツリーを作って理解する　（1. 概要編）](https://qiita.com/toyohisa/items/ab60d3eab11730f156da)によってディシジョンツリーを作成すると、次のようになります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275020/31822830-d4a0-71d0-2337-bda9fb417366.png">
ゴルフ〇はゴルフに行く、×は行かないとして、ゴルフに行くのには天気が最も重要で、晴なら次は湿度、雨なら風が次に重要になると分かります。

一方、温度を離散化せずに数値データのまま、同様にディシジョンツリーを作成してみます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275020/d3e51a23-85b0-92fb-bf9e-2245dcf563f6.png">

結果は、次のようになります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275020/f82994c9-10cc-db76-3059-bf89d5f842e2.png">

温度は、29度以外はすべて単独の値であるため、それぞれの温度のときにゴルフに行く／行かないが決定されます。おそらくこのディシジョンツリーを見ても、人が何かの気づきを得ることは難しいでしょう。

# 数値データをグルーピングして文字型のデータに分けることを離散化といいます

一般的には、データの数値を等間隔に分割して離散化する方法、データの数を同じように分割する等頻度の方法があります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275020/e1e2d5dc-e816-792e-fdd5-162e7041f9a4.png">

### 等間隔でデータを離散化する

例えば上のようなデータの場合、データから離散化された文字に変換する計算式は、次のようになります。

```python
# math.floor (小数点以下の切り捨て)を使うために、インポートします。
import math

# この数値を、これから離散化します。
values = [1,5,7,11,12,14,19,20]

# span変数の間隔内にある数値は、同じラベルの数値として離散化します。
span = 10

# 離散化後の各グループの名前を決めておきます。
labels = ["小さい方","大きい方"]

# 離散化する計算式は、 「( x - 最小値 ) / 1グループの大きさ」として結果の小数点を切り捨てます。
# そこでまず最初に「最小値」を求めておきます。
mi = min(values)

# map関数によって、values配列の各値を離散化します。listは、mapの結果からリスト（配列）を作るというものです。
discretized_values = list(map(lambda x:labels[math.floor((x-mi)/span)],values))

# 以下のように離散化後のデータが作成されます。
print(discretized_values)
# ['小さい方', '小さい方', '小さい方', '大きい方', '大きい方', '大きい方', '大きい方', '大きい方']
```

### 等頻度でデータを離散化する

```python
# math.floor (小数点以下の切り捨て)を使うために、インポートします。
import math

# この数値を、これから離散化します。
values = [1,5,7,11,12,14,19,20]

# n変数の個数だけ1つのグループにして、離散化します。
n = 4

# 離散化後の各グループの名前を決めておきます。
labels = ["最初の方","後の方"]

# 離散化する計算式は、配列の添え字（インデックス）を i として、「i / n」として小数点を切り捨てます。
# [ x for i in range(n) ] はリストの内包表記というもので、forを使って配列を生成、初期化するものです。
discretized_values = [labels[math.floor(i/n)] for i in range(len(values))]

# 以下のように離散化後のデータが作成されます。
print(discretized_values)
# ['最初の方', '最初の方', '最初の方', '最初の方', '後の方', '後の方', '後の方', '後の方']
```

### 独自にデータを分類して離散化する

これまでの2例は、客観的な視点に立ってデータを離散化してきました。一方、データの意味を考慮して離散化する方法もあります。

例えばあるゲームでのランキングに影響を与える要素を分析したい場合、そのランキングを1位、2位のような数値データではなく、ランキング上位、下位のように離散化した方が良いわけですが、この「ランキング上位」は、恣意的に決めることもできます。1位では無ければ意味がないと考えるならば、1位とそれ以外と離散化しても良いでしょう。また、2分割ではなく、ランキング上位、中位、下位と分けても良いでしょう。

# 参考
自動的に数値データを離散化してディシジョンツリーを作成するC4.5[^1]というアルゴリズムが知られています。このアルゴリズムを使用することも、データを離散化する1つの手法となりますが、結果のディシジョンツリーが複雑になりやすいことから、自らマニュアルでデータを離散化する、という方法がとられることもあります。

[^1]: Quinlan, J. R.: C4.5: Programs for Machine Learning. Morgan Kaufmann Publishers, (1993).