Pythonで階層的クラスタリング

Pythonで階層的クラスタリングをするときは、scipy.cluster.hierarchyを使うのが簡単なのですが、先日ちょっと間違えてしまいまして。

準備

scipyやmatplotlibを使います(インストールは済んでいるとして)。

import scipy
import numpy
import scipy.spatial.distance as distance
from matplotlib.pyplot import show
from scipy.cluster.hierarchy import linkage, dendrogram
from random import random

特徴ベクトルの長さがdimのデータをn個ランダムに作り、実験用のデータとします。

n = 100
dim = 10
data = [[random() for i in range(dim)] for i in range(n)]

方法1

非類似度の指標とクラスタ連結法を指定して、クラスタリングを実行します。

result1 = linkage(data, metric = 'chebyshev', method = 'average')

デンドログラムを描きます。

dendrogram(result1)
show()

このように、データをそのまま使ってクラスタリングするのが簡単です。

方法2

データ間の距離を与えてクラスタリングすることもできます。ただし、データ間の距離は、n行n列の距離行列ではなく、pdist()で作られるベクトルで表現します。

dArray1 = distance.pdist(data, metric = 'chebyshev')
result2 = linkage(dArray1, method = 'average')

assert (result1 == result2).all()#同じ結果

補足

pdist()で作られるベクトルは、次のようなものです。

dArray2 = []
for i in range(n - 1):
  for j in range(i + 1, n):
    dArray2.append(distance.chebyshev(data[i], data[j]));

assert (dArray1 == dArray2).all()#同じもの

このベクトルは、n行n列の距離行列からsquareform()で作ることもできます。

dMatrix1 = numpy.zeros([n, n])
for i in range(n):
  for j in range(n):
    dMatrix1[i, j] = distance.chebyshev(data[i], data[j])

dArray3 = distance.squareform(dMatrix1)
assert (dArray1 == dArray3).all()#同じもの

逆に、pdist()の結果からn行n列の距離行列を作ることもできます。

dMatrix2 = distance.squareform(dArray1)
assert (dMatrix1 == dMatrix2).all()#同じもの

まとめ

  • 階層的クラスタリングは、linkage(data)あるいはlinkage(距離のベクトル表現)で行う。(ちょっとわかりにくい仕様ですね。)
  • 距離のベクトル表現は、pdist(data)あるいはsquareform(n行n列の距離行列)で作れる。
  • n行n列の距離行列は、squareform(距離のベクトル表現)で作れる。

間違い

linkage(n行n列の距離行列)でクラスタリングするのは間違いです。こうすると、行列の各行がクラスタリングの対象データになってしまいます。

しかし、距離行列の各行は、元データのそれぞれが、他のデータとどの程度似ていないかを表現したものなので、これ自体を特徴ベクトルと見なすこともできます。ですから、次のような「間違った」方法も、自覚して使うなら間違いではありません。

result3 = linkage(dMatrix1, metric = 'chebyshev', method = 'average')

さらに、非類似度の指標としてチェビシェフ距離を使うなら、「正しい」方法と同じ結果になります。

assert (result1 == result3).all()#同じ結果