【python】階層型クラスタリングとデンドログラムの実装について
クラスタリングには、階層型と非階層型があります。今回は、階層型のクラスタリングについて解説しようと思います。
実装で使うデータは、【共線性解決】pythonで主成分分析をやってみたでも使った、学生のテストのデータを使っております。
階層型クラスタリング・ウォード法
階層型のクラスタリングとは、最も類似度が高い(さまざまな測り方あり)組み合わせからまとめていく手法です。
結果として出力される樹形図から、分類の過程でできるクラスターがどのように結合されていくのか確認できるというメリットがあります。
また、K-means法とは異なりクラスター数を事前に決めなくて良いが、距離尺度(ユークリッド距離)と併合方法(ウォード法,中心法,群平均)をハイパーパラメータとして設定する必要はあります。
樹形図はデンドログラムと呼ばれ、統計検定準一級の試験範囲にもなっています。
距離尺度
ここでは、距離尺度を3種類ご紹介します。
ユークリッド距離
$$\sum_{i=1}^n(x_{i}-y_{i})^2$$
マンハッタン距離
$$\sum_{i=1}^n|x_{i}-y_{i}|$$
マハラノビス距離
$$\sqrt{(x_{i}-y_{i})^T\sum^{-1}(x_{i}-y_{i})}$$
マハラノビス距離は相関関係を考慮した距離尺度になっており、真ん中の項に分散共分散行列が用いられています。
一番有名なのは、ユークリッド距離です。
正規化されたベクトルではコサイン類似度とほぼ同じ意味合いを持ちます。
【python】コサイン類似度は高校数学の知識で理解できます!
早速実装していきましょう。
CODE:python

今回は、国語と技術のテストの点数から生徒をクラスタリングしてみようと思います。
階層型のメリットとして、クラスター数を事前に設定する必要はありません。
#各ライブラリの読み込み
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
import sklearn
#データの読み込み
seiseki = pd.read_csv('seiseki.csv')
seiseki
SeisekiList = []
for i in range(len(seiseki)):
SeisekiList.append([seiseki["kokugo"][i], seiseki["gika"][i]])
SeisekiList
ライブラリを読み込みます。今回のデータは、以下のようになっています。列としてはgika(技科ですかね?)とkokugoを使います。
kokugo | shakai | sugaku | rika | ongaku | bijutu | taiiku | gika | eigo | |
0 | 30 | 43 | 51 | 63 | 60 | 66 | 37 | 44 | 20 |
1 | 39 | 21 | 49 | 56 | 70 | 72 | 56 | 63 | 16 |
2 | 29 | 30 | 23 | 57 | 69 | 76 | 33 | 54 | 6 |
3 | 95 | 87 | 77 | 100 | 77 | 82 | 78 | 96 | 87 |
4 | 70 | 71 | 78 | 67 | 72 | 82 | 46 | 63 | 44 |
… | … | … | … | … | … | … | … | … | … |
データフレームから目的の要素を抽出して、多重配列に入れます。
多重配列は、多次元配列とも呼ばれるもので、リストの中にリストが入っている構造です。
import numpy as np
X = np.array([[30, 44],[39, 63],[29, 54],[95, 96],[70, 63],[67, 66],[29, 43],[56, 67],[45, 46],[68, 72],[50, 65],[70, 67], [46, 45],[23, 48],[77, 88],[15, 17],[37, 55],[55, 68],[0, 6],[35, 39],[60, 57],[70, 70],[82, 99],[44, 22], [54, 61],[80, 68],[46, 46], [48, 52], [51, 41], [37, 35], [31, 7], [35, 33], [50, 60], [33, 33], [27, 58], [36, 49], [77, 49], [61, 52], [84, 100], [78, 44], [7, 18], [57, 38], [60, 52], [17, 9], [82, 61], [37, 40], [40, 62], [72, 74], [74, 50], [87, 73], [66, 51], [90, 68], [43, 31], [24, 11], [41, 55], [42, 90], [33, 20], [29, 4], [83, 84], [73, 69], [66, 33], [71, 55], [62, 61], [47, 65], [53, 51], [61, 34], [21, 10], [74, 77], [72, 74], [50, 57], [5, 18], [32, 61], [23, 37], [34, 55], [70, 69], [9, 17], [61, 74], [34, 45], [38, 35], [72, 37], [32, 21], [52, 59], [96, 75], [58, 45], [35, 47], [40, 36], [48, 25], [46, 26], [32, 27], [50, 13], [76, 73], [64, 80], [62, 44], [76, 32], [81, 35], [69, 78], [89, 51], [27, 23], [39, 39], [62, 55], [73, 72], [49, 32], [35, 27], [36, 20], [16, 11], [60, 30], [74, 92], [22, 11], [47, 33], [43, 49], [84, 44], [65, 70], [49, 33], [31, 29], [83, 78], [87, 76], [68, 66], [68, 77], [28, 47], [42, 85], [42, 45], [70, 52], [58, 55], [57, 31], [8, 5], [44, 21], [65, 43], [81, 51], [57, 59], [64, 7], [86, 79], [34, 38], [84, 50], [4, 4], [23, 13], [66, 72], [53, 50], [70, 27], [78, 32], [39, 23], [74, 74], [33, 53], [43, 46], [54, 18], [26, 54], [58, 24], [29, 23], [73, 73], [59, 67], [58, 52], [70, 61], [19, 19], [29, 24], [32, 19], [77, 82], [91, 74], [50, 49], [63, 31], [42, 20], [68, 49], [70, 32], [82, 78], [0, 2], [45, 40], [73, 48], [60, 45]])
インデックスのリストを指定して値を取得するのは、Python の組み込みのリストでは対応していないで、numpy の ndarray を使います。
#サンプルデータの可視化
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6,6))
ax = fig.add_subplot(1,1,1,title="test")
plt.scatter(X[:,0],X[:,1])
for i, element in enumerate(X):
plt.text(element[0]+0.02,element[1]+0.02,i)
plt.show()

#階層型クラスタリング
#ウォード法
from scipy.cluster.hierarchy import linkage
Z = linkage(X,method="ward",metric="euclidean")
pd.DataFrame(Z)
0 | 1 | 2 | 3 | |
0 | 47.0 | 68.0 | 0.000000 | 2.0 |
1 | 5.0 | 116.0 | 1.000000 | 2.0 |
2 | 8.0 | 26.0 | 1.000000 | 2.0 |
3 | 21.0 | 74.0 | 1.000000 | 2.0 |
4 | 23.0 | 125.0 | 1.000000 | 2.0 |
ここでウォード法を使っています。
ウォード法は、クラスタ同士が結合すると仮定した時、結合後の全てのクラスタにおいて、クラスタの重心とクラスタ内の各点の距離の2乗和の合計が最小となるように、クラスタを結合させていく方法です。
1列目と2列目:結合されたクラスターの番号
3列目:クラスター間の距離
4列目:結合後に新しくできたクラスタの中に入っている元のデータの数
デンドログラム
#デンドログラム
#視覚的にどうクラスターを分けたかわかる。
from scipy.cluster.hierarchy import dendrogram
import matplotlib.pyplot as plt
fig2,ax2 = plt.subplots(figsize=(20,5))
ax2 = dendrogram(Z)
fig2.show()

このように樹形図でクラスター分けの様子が見れます。
赤、緑、黄色の3色であり、3つのクラスターに分けてくれたことがわかります。
また、距離水準を3よりも大きく取るともっと細かくクラスター分けすることも可能であり、クラスター数を柔軟に決定することができます。
from scipy.cluster.hierarchy import fcluster
clusters = fcluster(Z,t=3,criterion="maxclust")
for i,c in enumerate(clusters):
print(i,c)
最後に、どの生徒がどのクラスターに分けられたのかを見てみましょう。
0 3 1 3 2 3 3 2 4 2 5 2 6 3 7 3 8 3 9 2 ‥
以上が出力結果です。これが縦に人数分続く形になっています。
さて、ここまで技術と国語のテストの点数を指標にして生徒をクラスターに分けると、3つが「ちょうどいい」というのが教師なし機械学習の提案です。
ただ、一つ問題があります。教師なし学習は指標がないので、「このクラスターにはこういう特徴がある」のような分析結果につなげる示唆が得られにくいということです。
実際の業務では、「なぜそういう分類になったのか」「こうしたクラスターの購買行動は○○だから、こうしたアプローチをとってみよう」のような「分析にとどまらない戦略の提案」が必須になります。
そういった点では、事前に分析者がクラスター数を設定する非階層型のクラスタリングの方が、「便利」なのかもしれませんね。
k-means法との違い
今回取り上げたウォード法は、教師なし学習のクラスタリングの中でも「階層型」と呼ばれる型に分類されます。
階層型の短所として、計算量が多いことが挙げられます。
k-means法については、【非階層型】K-means法でクラスタリングをしてみましょう。をご覧ください。
merit | demerit | |
非階層 | 次元が多い場合にも対応する | 事前にクラスター個数を指定する |
階層 | 分類過程を視覚的に見れる | 計算量が多い |
事前にクラスターの数を指定しない階層型ですが、正解データがなく解釈や評価が難しい教師なし学習では大きな問題点と思われるかもしれませんね。