クラスター分析の基礎

irisデータセットについてクラスター分析を行う。

irisデータセットは150個のデータからできており、3つの品種についてそれぞれ50個のデータが入っている。問題を簡単にするため、それぞれの品種について3つのデータを取ることにする。

from sklearn.datasets import load_iris

iris = load_iris()
print(iris.target[0], iris.target[1], iris.target[2])
print(iris.target[50], iris.target[51], iris.target[52])
print(iris.target[100], iris.target[101], iris.target[102])

このように、品種0、品種1、品種2がそれぞれ3件ずつであることが分かる。

0 0 0
1 1 1
2 2 2

対応するデータを取得し、標準化を行う。
ちなみに、scikit-learnを用いて標準化した値と、R言語のscale関数で求めた値はデルタの自由度の値が異なるため、違う値になることに注意する。

from sklearn.preprocessing import StandardScaler

X = [iris.data[0], iris.data[1], iris.data[2],
iris.data[50], iris.data[51], iris.data[52],
iris.data[100], iris.data[101], iris.data[102]
]

sc = StandardScaler()
X_sc = sc.fit_transform(X)
X_sc

このようなデータが得られる。

array([[-1.04454175, 1.73925271, -1.35065657, -1.30321735],
[-1.27106888, -0.63245553, -1.35065657, -1.30321735],
[-1.497596 , 0.31622777, -1.40444378, -1.30321735],
[ 1.10746595, 0.31622777, 0.42432131, 0.14778753],
[ 0.42788457, 0.31622777, 0.31674689, 0.26870461],
[ 0.99420239, -0.15811388, 0.53189573, 0.26870461],
[ 0.31462101, 0.79056942, 1.12355502, 1.47787534],
[-0.25169681, -2.05548048, 0.63947014, 0.7523729 ],
[ 1.22072952, -0.63245553, 1.06976781, 0.99420705]])

データ間の距離を求める

1件目のデータ$x_{1}$と2件目のデータ$x_2$について、ユーグリッド距離を求める。
ユーグリッド距離は、それぞれの値の差の平方を足して、その平方根を求めたものである。
$$
d = \sqrt{(x_{1,1}-x_{2,1})^2 + (x_{1,2}-x_{2,2})^2 + (x_{1,3}-x_{2,3})^2 + (x_{1,4}-x_{2,4})^2}
$$

# X_sc[0]とX_sc[1]のユーグリッド距離を求める
import numpy as np
np.sqrt((X_sc[0][0] - X_sc[1][0]) ** 2 +
(X_sc[0][1] - X_sc[1][1]) ** 2 +
(X_sc[0][2] - X_sc[1][2]) ** 2 +
(X_sc[0][3] - X_sc[1][3]) ** 2)
2.3825017395837125

scikit-learnでは、下記のように求める。

# ユーグリッド距離を求める
from sklearn.neighbors import DistanceMetric
dist = DistanceMetric.get_metric('euclidean')
dist.pairwise(X_sc)
array([[0. , 2.38250174, 1.49437319, 3.45139085, 3.0731437 ,
3.71098632, 4.07474207, 4.81815926, 4.67900277],
[2.38250174, 0. , 0.9768355 , 3.43706118, 3.00626276,
3.37215001, 4.2890106 , 3.35412806, 4.16481359],
[1.49437319, 0.9768355 , 0. , 3.49802011, 3.02339402,
3.55730355, 4.19933149, 3.9471889 , 4.43724864],
[3.45139085, 3.43706118, 3.49802011, 0. , 0.69858718,
0.51383054, 1.76399106, 2.80787035, 1.43033416],
[3.0731437 , 3.00626276, 3.02339402, 0.69858718, 0. ,
0.76941854, 1.53325205, 2.53474183, 1.61925829],
[3.71098632, 3.37215001, 3.55730355, 0.51383054, 0.76941854,
0. , 1.78156825, 2.32331059, 1.04497594],
[4.07474207, 4.2890106 , 4.19933149, 1.76399106, 1.53325205,
1.78156825, 0. , 3.0300838 , 1.75580771],
[4.81815926, 3.35412806, 3.9471889 , 2.80787035, 2.53474183,
2.32331059, 3.0300838 , 0. , 2.10634259],
[4.67900277, 4.16481359, 4.43724864, 1.43033416, 1.61925829,
1.04497594, 1.75580771, 2.10634259, 0. ]])

距離を求める方法としては、他に絶対距離(市街地距離、マンハッタン距離)、ミンコフスキー距離、マハラノビス距離がある。

階層型(凝集型)クラスタリング

scikit-learnで階層型クラスタリングを行う。
affinityの値は距離の求め方(デフォルトはユーグリッド距離)、linkageはクラスター間の距離をどの点に基づいて求めるかを示す。
linkageには、下記の種類がある。

  • 最短距離法(single
  • 最長距離法(complete
  • 平均距離法(average
  • 重心距離法
  • ウォード法(ward)※ウォード法を用いる場合、affinityはユーグリッド距離のみが使用できる

ここではウォード法を用いる。

# 階層型(凝集型)クラスタリング
from sklearn.cluster import AgglomerativeClustering

ac = AgglomerativeClustering(
affinity='euclidean',
linkage='ward',
distance_threshold=0,
n_clusters=None)
ac.fit(X_sc)

階層型クラスタリングによって求めたクラスター間の距離を図示する方法として、デンドログラムがある。下記の関数を定義して描くことができる。
<参考> https://scikit-learn.org/stable/auto_examples/cluster/plot_agglomerative_dendrogram.html

def plot_dendrogram(model, **kwargs):
counts = np.zeros(model.children_.shape[0])
n_samples = len(model.labels_)
for i, merge in enumerate(model.children_):
current_count = 0
for child_idx in merge:
if child_idx < n_samples:
current_count += 1 # leaf node
else:
current_count += counts[child_idx - n_samples]
counts[i] = current_count

linkage_matrix = np.column_stack([model.children_, model.distances_,
counts]).astype(float)

dendrogram(linkage_matrix, **kwargs)
from scipy.cluster.hierarchy import dendrogram

labels = range(1, len(X)+1)
plot_dendrogram(ac, labels=labels)

img

このように、1番目から3番目までのデータ、4番目から6番目までのデータはうまくクラスターに分類できているが、7番目から9番目までのデータについては鎖状効果が発生しておりうまく分類できていない。

クラスターの各階層の距離を求める。

for i in range(len(ac.children_)):
a = (ac.children_[i][0] + 1) * -1 if ac.children_[i][0] <= len(ac.children_) else ac.children_[i][0] - len(ac.children_)
b = (ac.children_[i][1] + 1) * -1 if ac.children_[i][1] <= len(ac.children_) else ac.children_[i][1] - len(ac.children_)
print('%2i %3i %3i %0.3f' % (i+1, a, b, ac.distances_[i]))
1 -4 -6 0.514
2 -5 1 0.795
3 -2 -3 0.977
4 -9 2 1.630
5 -7 4 1.994
6 -1 3 2.226
7 -8 5 3.139
8 6 7 6.979

ここでは、4番目のデータと6番目のデータで形成した1つ目のクラスターの距離が0.514、5番目のデータと1つ目のクラスターで形成した2つ目のクラスターの距離が0.795であることを示す。

非階層型クラスタリング

階層型クラスタリングで用いた9件のデータについて、非階層型クラスタリング手法であるK-Meansで3つのクラスターに分類する。

from sklearn.cluster import KMeans

kms = KMeans(n_clusters=3, random_state=0)
kms.fit(X_sc)

for i in range(0, len(kms.labels_)):
print(kms.labels_[i])
1
1
1
0
0
0
0
2
0

1つ目の品種については上手くクラスターを形成できているが、2つ目と3つ目の品種については上手くいっていない。

次に、150個すべてのデータを用いたクラスタリングを行う。まず、主成分分析を行い、第2主成分までを求めた上でK-Meansで3つのクラスターに分類する。

# 主成分分析を行い第2主成分までを使用する
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# すべてのデータを使用する
X = iris.data

# 標準化
sc = StandardScaler()
X_sc = sc.fit_transform(X)

# 主成分分析を行い、第2主成分までに射影する
pca = PCA(n_components=2)
pca.fit(X_sc)
X_projection = pca.transform(X_sc)

# K-Meansでクラスタリングする
kms = KMeans(n_clusters=3, random_state=0)
kms.fit(X_projection)
kms.labels_

このような結果が得られる。やはり2つ目と3つ目の品種の分類は上手く行かない。

array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 0, 0, 0, 2, 2, 2, 0, 2, 2, 2, 2, 2, 2, 2, 2, 0,
2, 2, 2, 2, 0, 2, 2, 2, 2, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0,
0, 0, 0, 2, 2, 0, 0, 0, 0, 2, 0, 2, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0,
0, 2, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 2], dtype=int32)

散布図を描く。

%matplotlib inline
from matplotlib import pyplot as plt

ax = plt.subplot()

markers = ['x', 'v', 'o']
colors = ['red', 'green', 'blue']

for i in range(0, len(X_projection)):
ax.scatter(X_projection[i][0], X_projection[i][1],
marker=markers[kms.labels_[i]], color=colors[kms.labels_[i]])
plt.show()

img