決定木による分類基準を集計する

決定木で分類できるのはいいんだけど、どういう基準で分類していることが多いのか整理したい。そこで、決定木による分類基準を概観する方法を検討しました。

参考にさせていただいたのは <a href="https://qiita.com/takahashi_yukou/items/5251bada1c3dc453c508" target="_blank">scikit-learnの決定木系モデルを視覚化する方法</a>

決定木の詳細を見るのは Graphviz (Graph Visualization Software) で視覚化するといいらしいですが、そこに出力された木を一個一個眺めるのってしんどいじゃないですか。なのでその結果を集計して概観したいなと。

# iris のデータをインポート
```python
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import re

from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.cross_validation import train_test_split
from sklearn import tree

iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['species'] = pd.Categorical.from_codes(iris.target, iris.target_names)
```

よく知られている、こういう感じのデータを使います。

```python
df
```




<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>sepal length (cm)</th>
      <th>sepal width (cm)</th>
      <th>petal length (cm)</th>
      <th>petal width (cm)</th>
      <th>species</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>0</th>
      <td>5.1</td>
      <td>3.5</td>
      <td>1.4</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>1</th>
      <td>4.9</td>
      <td>3.0</td>
      <td>1.4</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>2</th>
      <td>4.7</td>
      <td>3.2</td>
      <td>1.3</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>3</th>
      <td>4.6</td>
      <td>3.1</td>
      <td>1.5</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>4</th>
      <td>5.0</td>
      <td>3.6</td>
      <td>1.4</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>5</th>
      <td>5.4</td>
      <td>3.9</td>
      <td>1.7</td>
      <td>0.4</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>6</th>
      <td>4.6</td>
      <td>3.4</td>
      <td>1.4</td>
      <td>0.3</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>7</th>
      <td>5.0</td>
      <td>3.4</td>
      <td>1.5</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>8</th>
      <td>4.4</td>
      <td>2.9</td>
      <td>1.4</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>9</th>
      <td>4.9</td>
      <td>3.1</td>
      <td>1.5</td>
      <td>0.1</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>10</th>
      <td>5.4</td>
      <td>3.7</td>
      <td>1.5</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>11</th>
      <td>4.8</td>
      <td>3.4</td>
      <td>1.6</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>12</th>
      <td>4.8</td>
      <td>3.0</td>
      <td>1.4</td>
      <td>0.1</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>13</th>
      <td>4.3</td>
      <td>3.0</td>
      <td>1.1</td>
      <td>0.1</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>14</th>
      <td>5.8</td>
      <td>4.0</td>
      <td>1.2</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>15</th>
      <td>5.7</td>
      <td>4.4</td>
      <td>1.5</td>
      <td>0.4</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>16</th>
      <td>5.4</td>
      <td>3.9</td>
      <td>1.3</td>
      <td>0.4</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>17</th>
      <td>5.1</td>
      <td>3.5</td>
      <td>1.4</td>
      <td>0.3</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>18</th>
      <td>5.7</td>
      <td>3.8</td>
      <td>1.7</td>
      <td>0.3</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>19</th>
      <td>5.1</td>
      <td>3.8</td>
      <td>1.5</td>
      <td>0.3</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>20</th>
      <td>5.4</td>
      <td>3.4</td>
      <td>1.7</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>21</th>
      <td>5.1</td>
      <td>3.7</td>
      <td>1.5</td>
      <td>0.4</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>22</th>
      <td>4.6</td>
      <td>3.6</td>
      <td>1.0</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>23</th>
      <td>5.1</td>
      <td>3.3</td>
      <td>1.7</td>
      <td>0.5</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>24</th>
      <td>4.8</td>
      <td>3.4</td>
      <td>1.9</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>25</th>
      <td>5.0</td>
      <td>3.0</td>
      <td>1.6</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>26</th>
      <td>5.0</td>
      <td>3.4</td>
      <td>1.6</td>
      <td>0.4</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>27</th>
      <td>5.2</td>
      <td>3.5</td>
      <td>1.5</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>28</th>
      <td>5.2</td>
      <td>3.4</td>
      <td>1.4</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>29</th>
      <td>4.7</td>
      <td>3.2</td>
      <td>1.6</td>
      <td>0.2</td>
      <td>setosa</td>
    </tr>
    <tr>
      <th>...</th>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
      <td>...</td>
    </tr>

  </tbody>
</table>
<p>150 rows × 5 columns</p>
</div>


# scatter plot による概観

まずは scatter plot による概観をしてみましょうか。異なる species は異なる色で彩色しました。

```python
cmap = plt.get_cmap('coolwarm')
colors = [cmap((c)/ 3) for c in [list(iris.target_names).index(name) for name in df['species'].tolist()]]
pd.plotting.scatter_matrix(df.dropna(axis=1)[df.columns[:]], figsize=(8, 8), color=colors) 
plt.show()
```


![output_2_0.png](https://qiita-image-store.s3.amazonaws.com/0/211162/0d78b0c0-bd51-678d-6d41-4057d0c18ef0.png)


# 決定木による分類と、分類基準の集計

さて、ここからが本番です。

* 決定木による分類を１０回繰り返しました。
* １回の計算あたりの木の本数は１００本にしました。
* 特徴の重要性（feature importance）を計算しました。
* 分類基準の分布(criteria_distribution)を集計しました。

ここで、分類基準の取得方法を説明すると

* tree.export_graphviz(estimator) というメソッドを使うと "tree.dot" という名のテキストファイルが出力され、決定木の構造が記述される。
* その "tree.dot" のファイルを読み込んで、分類基準の部分だけ抜き出して criteria_distribution に集計する。
* サンプルサイズの大きなノードを分割したような分類基準は大きめにカウントする（ここでは、サンプルサイズの分だけカウントするようにする）。

という流れです。

```python
features = df.columns[:4]
label = df['species']

criteria_distribution = {}
accumulated_feature_importances = np.zeros(len(df.columns) - 1)

for times_of_learning in range(10):
    df_train, df_test, label_train, label_test = train_test_split(df[features], label)
    clf = RandomForestClassifier(n_estimators=100)
    clf.fit(df_train, label_train)
    accumulated_feature_importances += clf.feature_importances_

    for estimator in clf.estimators_:
        tree.export_graphviz(estimator)
        for line in open("tree.dot"):
            matched = re.search('X\[.+?\n', line)
            if matched:
                equation = matched.group(0).split("\\n")[0]
                numerals = re.findall('[0-9\.]+', line)
                if numerals:
                    #print(equation, numerals)
                    if int(numerals[1]) not in criteria_distribution.keys():
                        criteria_distribution[int(numerals[1])] = []
                    for num_of_samples in range(int(numerals[-1])):
                        criteria_distribution[int(numerals[1])].append(float(numerals[2]))
```

# 特徴の重要性の大きい順に、分類基準の分布とデータの分布を概観する

criteria が、分類基準の分布です。重要性の大きい特徴が、３つのクラスを比較的上手に分類できていることが見て取れます。

```python
ranking = np.argsort(accumulated_feature_importances)[::-1]
for rank, feature in enumerate(ranking):
    print("feature #" + str(rank + 1) + " = " + df.columns[feature])
    fig, axes = plt.subplots(nrows = len(iris.target_names) + 1, ncols=1, figsize=(8,8))
    print("Accumulated Feature importance = " + str(accumulated_feature_importances[feature]))
    x_max = max(list(criteria_distribution[feature]) + list(df[df.columns[feature]]))
    x_min = min(list(criteria_distribution[feature]) + list(df[df.columns[feature]]))
    axes[0].hist(criteria_distribution[feature], bins=20, label="criteria")
    axes[0].legend()
    axes[0].set_xlim([x_min, x_max])
    for target, target_name in enumerate(iris.target_names):
        axes[target + 1] = plt.subplot(len(iris.target_names) + 1, 1, target + 2)
        axes[target + 1].hist(list(df[df[df.columns[-1]] == target_name][df.columns[feature]]), bins=10,
                             label=target_name, alpha=0.5)
        axes[target + 1].legend()
        axes[target + 1].set_xlim([x_min, x_max])
    plt.xlabel(df.columns[feature])
    plt.show()
```

feature #1 = petal width (cm)
Accumulated Feature importance = 4.45000008496

![output_4_1.png](https://qiita-image-store.s3.amazonaws.com/0/211162/02bd4476-55f0-555e-71cf-a43ee49c1fc6.png)



feature #2 = petal length (cm)
Accumulated Feature importance = 4.30410137014



![output_4_3.png](https://qiita-image-store.s3.amazonaws.com/0/211162/58c3a7c9-85d6-e868-2799-d4952256bf6a.png)


feature #3 = sepal length (cm)
Accumulated Feature importance = 0.976500917131



![output_4_5.png](https://qiita-image-store.s3.amazonaws.com/0/211162/a92ce174-7e2f-74db-73cc-19014104e832.png)


feature #4 = sepal width (cm)
Accumulated Feature importance = 0.269397627763



![output_4_7.png](https://qiita-image-store.s3.amazonaws.com/0/211162/14407a41-ebce-3696-1355-c6b5bfcb6b7e.png)



```python

```