自動車の燃費を予測してみた。【signate練習問題】その３

　こちらの記事をご覧いただきありがとうございます。
　↓の続きです。

https://qiita.com/tomyu/items/a9321657349fc6da4bef

　前回の簡単なまとめをしておきます。

* kmeansを試したらカテゴリ変数が使えなかったのであまり有意義な感じがしなかった。
* Gower距離行列に変換してからkmeansを試した。
* サンプルをザックリ分けると`cylinders`で分かれる。もう少し分けると`origin`で分かれる。


　前回はkmeansでいろいろしたので、今回はkmeansとは違うクラスタリング手法を試します。


### ward法

　ward法とは、ザックリ言えばクラスタごとに分けるときにクラスタ間の距離（類似度と言えるかもしれない）を測ってくれます。よくデンドログラムと一緒に用いられます。
　こういうのは見たほうが早いと思うので、さっそく試します。

#### 前処理

　前処理の手法はkmeansの時と同じです。
　欠損値処理 -> 標準化 -> Gower距離行列に変換 -> ward法　の順で進めます。Gower距離行列への変換まで前回行っているので、それをそのまま使用します。

#### モデルで学習

　sklearnにもward法とデンドログラムは実装されていますが、微妙に使い勝手が悪い（というより僕がちゃんと使い方を調べていないだけな気がする）ので、scipyを使います。

```python: ward法とデンドログラム
# fclusterは後で使います。
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
# デンドログラムの作成
result = linkage(df_train_gw, method='ward')
dendrogram(result)
plt.show()
```

__なんか警告が出た__
<img width=500 alt='デンドログラム.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/ec6e6410-9318-6109-2ac1-301f54e63ba0.png'>

　デンドログラムを書くことに成功しましたが、警告も出てきました。
　警告を要約すると、君が入れたデータって凝縮されていない距離行列じゃね？ってことらしい。確かに、Gower距離行列に変換したデータなのでおっしゃる通りでございます。何か良くないのか？と思って調べると、`linkage`は凝縮された距離行列を想定して設計されているのでただの距離行列をそのまま使うのが良くないらしいです。
　距離行列は対称性がある+自分との距離が0の情報も入っているので、確かに情報の半分以上が無駄だよなーという想定がありましたが、確信があるわけでもないので距離行列の凝縮についてさらに調べました。そこで以下のサイトが見つかりました。

https://www.haya-programming.com/entry/2019/02/16/214750

　詳しくはサイト内を見て欲しいのですが、要するに距離行列のいらないところ（対称にある情報と自分との距離の情報）を削って1次元に変換することができるらしいです。この変換をすればデンドログラムに利用できるとかなんとか。この表現方式を「condensed distance matrix」と呼ぶらしいです。和訳すると「凝縮距離行列」です。言われてみれば確かに、さっきの警告文の中にも`uncondensed distance matrix`って書いてありますね。
　なんのこっちゃって思った人も、もう少し先で図解しますのでぜひそこまでご覧ください。
　この凝縮距離行列に変換してからもう一度デンドログラムを試みます。まずは凝縮距離行列に変換します。

```python:凝縮距離行列に変換
from scipy.spatial.distance import squareform
# 距離行列を凝縮距離行列に変換
df_train_gw_1d = squareform(df_train_gw)
print(df_train_gw_1d)
```
__それっぽいものが生成された__
<img width=500 alt='凝縮距離行列.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/ec69d899-6ec0-6691-6da2-d871362ff099.png'>

　Gower距離行列をまっすぐに伸ばしたようなものが生成されました。変換前と比較します。

__変換前__
<img width=500 alt='凝縮する前と後.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/53c3e6b0-c8e2-b2cc-a3b8-657f0d1b868b.png'>

__変換後__
<img width=500 alt='凝縮距離行列.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/ec69d899-6ec0-6691-6da2-d871362ff099.png'>

　囲った部分が共通になっていることがわかります。このあたりで凝縮の仕方が分かりました。

__こういうことらしい__
<img width=500 alt='凝縮の仕方.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/f592114b-848e-8e99-2089-8197233d099a.png'>

　黄色の部分が対称の情報または自分との距離(0)で不要な情報です。これをなかったことにして、赤の矢印に沿って情報を1次元に直しています。無駄な情報がなくなりました。メモリにやさしいですね。

　凝縮距離行列に変換したところで、もう一度デンドログラム生成を試みます。
　※コードは省略します。

__うまくいっているっぽい__
<img width=400 alt='デンドログラム1d.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/dc6821fe-89b7-8914-0335-d0a8e868704d.png'>

　警告が出なくなりましたので、うまくいっていそうです。一応、凝縮距離行列に変換していない時のデンドログラムと比較します。

__←凝縮前　凝縮後→__

<img width=300 alt='デンドログラム凝縮前.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/45385716-b3c7-75b2-95d5-74f4fc9b2808.png'><img width=300 alt='デンドログラム1d.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/dc6821fe-89b7-8914-0335-d0a8e868704d.png'>

　ぱっと見はそんなに違いもないのか…？と思いましたが、よく見ると細かいところで結構違いますね。一番違うのが、縦軸のスケールでしょうか。結果にかなり差が出るので、手法をうっかり間違えると誤解になってしまいますね。

　最初に見たらわかるって言っておきながらここまでデンドログラムの説明をまるでしていなかったのでここで説明します。
　トーナメント表みたいな感じになっていますが、一番下にはサンプルのデータ点がひとつづつ並んでいます。トーナメント表を下から進んでいくと別のデータ点にぶつかりますが、ぶつかったらそのデータ点同士は似ていると判断できます。
　トーナメント表を上から見れば最初に2つに分かれますが、全てのデータ点を2グループに分けるならこう分けるという主張をしています。ザックリオレンジと緑のグループは似ているということです。
　またトーナメント表の縦棒の長さにも意味があり、他のデータ点とぶつかるまでの縦棒が短いほどよく似ていることを表します。オレンジと緑のグループがぶつかるまでに縦棒が結構長いので、2つのグループはあまり似ておらず、結構な違いがあることがわかります。

　さて、グループが何を基準に分かれているか確認します。手法はだいたいkmeansと同じです。
　まずはward法を基準にクラスタ分けをします。

```python:ward法でクラスター分け
# fclusterでクラスタ分け。tはデンドログラムの縦軸と同じ。
result = linkage(df_train_gw_1d, method='ward')
cluster = fcluster(result, t=2.5, criterion='distance')
df_train_cy_ye['cluster'] = cluster
```

__各データ点が2つに分かれる__
<img width=600 alt='デンドログラムでクラスタ分け.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/d77accf9-5999-35f5-80f1-5ef33e3fa9dc.png'>

　クラスタ分けに成功したので、あとはkmeans法と同様に散布図や棒グラフを作成して違いを比べます。
　一番違いが分かりやすかったやつを載せます。

__cylindersとクラスタ分け__
<img width=400 alt='cylinders_dendrogram2.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/faa2836a-0cf7-f25a-ee37-6c7fcddf2abb.png'>

　はい。`cylinders`の違いでクラスタが分かれています。kmeans法のときと同じですね。
　ということはデンドログラムでもクラスタ数を3にすると`cylinders`の違いで分かれてくれるのでしょうか？と思ったので試します。
※過程を省略します。

__cylindersと3つのクラスタ分け__
<img width=400 alt='cylinders_dendrogram3.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/d2807c9c-175f-2874-00f2-6fffabc998d9.png'>

　はい。予想通りですね。クラスタ分けと`cylinders`が一致しています。kmeans法の時は一部混ざっていてここまできれいに分かれていませんでした。ward法では`cylinders`でキッチリ分かれています。
　ほんじゃ、kmeansが`origin`で分け始めたように、クラスタ数増やしたら今度は`origin`を基準にして別れるんか？と思ったので試します。
　結論を申し上げますと、kmeans法とward法でほぼほぼ同じ結果になりました。ward法でクラスタを5つに分けた時の結果を出します。

__originと5つのクラスタ分け__
<img width=400 alt='origin_dendrogram5.png' src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2774344/7cc222b0-d415-6f55-ec7a-cd9175a96b25.png'>

　このグラフはkmeans法でクラスタ分けしたときとだいたい同じです。同じような分け方になるんやなーって思いました。

※今回のデータで行った場合にほぼほぼ同じ結果になったというだけで、kmeans法とward法がほぼほぼ同じものという主張ではありません。データが変われば違う結果を出してくれることもあるでしょう。

## 次回に続きます。

　今回はさらに次元削減とクラスタ分けの組み合わせまでやりたかったですが、警告の処理で思ったより長くなってしまったのでここまでにします。次回は次元削減します。その次に回帰予測します。
　…Qiita書くのって思ったより大変ですね。なるべく早めに次回以降を書きます。
→書きました。↓からぜひご覧ください。

https://qiita.com/tomyu/items/6379f552075a9f718b6e