前の記事でちらっと pdist
関数が登場したので、scipyで距離行列を求める方法を紹介しておこうと思います。
距離行列の説明はwikipediaにあります。
距離行列 – Wikipedia
要するに、N個のデータに対して、(i, j)成分がi番目の要素とj番目の要素の距離になっているN*N正方行列のことです。
これは次の二つの関数を使って計算できます。
scipy.spatial.distance.pdist
scipy.spatial.distance.squareform
numpyで適当に5点取ってやってみましょう。
import numpy as np
from scipy.spatial.distance import pdist
from scipy.spatial.distance import squareform
# 出力する桁数を抑える
np.set_printoptions(precision=3)
# 乱数生成
X = np.random.randint(-5, 6, size=(5, 2))
print(X)
'''
[[-2 -4]
[-3 -4]
[ 2 -1]
[ 4 -2]
[-1 -2]]
'''
y = pdist(X)
print(y)
'''
[1. 5. 6.325 2.236 5.831 7.28 2.828 2.236 3.162 5. ]
'''
M = squareform(y)
print(M)
'''
[[0. 1. 5. 6.325 2.236]
[1. 0. 5.831 7.28 2.828]
[5. 5.831 0. 2.236 3.162]
[6.325 7.28 2.236 0. 5. ]
[2.236 2.828 3.162 5. 0. ]]
'''
変数Mに距離行列が入っているのが確認できますね。
(1,2)成分が [-2 -4] と [-3 -4] の距離の1,
(1,3)成分が [-2 -4] と [ 2 -1] の距離の5など、きちんと距離が入っています。
なお、 pdistの戻り値自体は、正方行列の形をしておらず、squareformで正方行列に変形する必要があるので注意です。
pdist の戻り値(コード中では変数y)には、上三角行列部分の値が、1行めから順番に入っています。
配列の長さは$(N*(N-1)/2) = 5*4/2 = 10$ です。
pdist 関数は metric という引数に様々な距離関数の値をとることができ、いろんな種類の距離行列を作ることができます。
(デフォルトはユークリッド距離)
ここまで紹介した、pdistは1つのデータセットに対して距離行列を生成しましたが、
cdistという関数を使うと、2個のデータセットからそれぞれ1個ずつ要素を抽出して距離行列を作ることもできます。
こちらはsquareformを使わなくても初めから行列の形で結果が得られます。
from scipy.spatial.distance import cdist
XA = X[:3]
XB = X[3:]
print(cdist(XA, XB))
# 出力
'''
[[6.325 2.236]
[7.28 2.828]
[2.236 3.162]]
'''
最初の例においても、
pdistとsquareform を使うよりも、 cdist(X, X) した方が便利かもしれません。
結果は同じになります。