論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
距離・類似度
Distance and Similarity Metrics
2 つのデータ点が 「どれだけ近いか/遠いか」 を数値化する道具箱。 クラスタリング、 分類、 推薦、 異常検知――データサイエンスのあらゆる場面で土台になります。
ユークリッド、 マンハッタン、 コサイン、 マハラノビス、 ハミング、 ジャッカード……それぞれ得意分野が違うので、 「何を測りたいか」で選ぶ のが大原則。
基礎数学 類似度 前処理 距離空間

🔖 キーワード索引

distance」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「distance」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。

distanceユークリッド距離マンハッタン距離コサイン類似度マハラノビス距離ハミング距離三角不等式標準化k-means

これらのキーワードは「distance の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。

💡 30秒で分かる結論

🍰 まずはやさしく

データの「近さ」を測る定規のようなものです。

似ているものを見つけるために使います。

スマホで好みの曲を探すときなどに役立ちます。

代表的な測り方の種類と選び方を学びます。

💡 確率分布間の距離 — 情報理論からの 5 つ

2 つの確率分布 $P, Q$ の「違い」を測る指標は機械学習で頻出します(生成モデル、 ベイズ、 強化学習)。

① KL ダイバージェンス(Kullback-Leibler)

$$D_{KL}(P \| Q) = \sum_x P(x) \log\frac{P(x)}{Q(x)}$$
$P$ を真の分布、 $Q$ を近似分布としたときの「情報損失」。 非負(≥ 0)、 0 で完全一致、 非対称($D_{KL}(P\|Q) \ne D_{KL}(Q\|P)$)。

② JS ダイバージェンス(Jensen-Shannon)

$$D_{JS}(P, Q) = \tfrac{1}{2}D_{KL}(P \| M) + \tfrac{1}{2}D_{KL}(Q \| M), \quad M = \tfrac{1}{2}(P + Q)$$
KL を対称化したもの。 $[0, \log 2]$ の範囲。 GAN の元祖目的関数で登場。

③ Wasserstein 距離(Earth Mover's Distance)

$$W(P, Q) = \inf_{\gamma \in \Gamma(P, Q)} \mathbb{E}_{(x,y) \sim \gamma}[\|x - y\|]$$
「土を盛る最適輸送コスト」。 KL が「分布が交わらないと無限大」だが、 Wasserstein は 離れていても有限。 WGAN で標準化。

④ Total Variation 距離

$$\mathrm{TV}(P, Q) = \tfrac{1}{2}\sum_x |P(x) - Q(x)|$$
L1 距離を確率測度の世界に持ち込んだもの。 $[0, 1]$ の範囲。 確率分布の「最大ズレ」。

⑤ Hellinger 距離

$$H(P, Q) = \frac{1}{\sqrt{2}}\sqrt{\sum_x \left(\sqrt{P(x)} - \sqrt{Q(x)}\right)^2}$$
$[0, 1]$ の範囲。 対称+三角不等式を満たす真の距離。 Bhattacharyya 係数との関係が深い。

使い分け早見表

場面推奨
変分推論、 VAE の目的関数KL ダイバージェンス
GAN(古典)JS ダイバージェンス
WGAN、 分布輸送最適化Wasserstein 距離
確率の最大ズレ評価Total Variation
真の距離公理が必要Hellinger 距離
マルコフ連鎖の収束証明Total Variation または KL

🕸 グラフ距離・編集距離 — 構造データ用の距離

レーベンシュタイン距離(編集距離)

文字列 $s_1, s_2$ について、 一方を他方に変換するのに必要な 挿入・削除・置換の最小回数

$$d(s_1, s_2) = \min(\text{挿入数} + \text{削除数} + \text{置換数})$$
"kitten" と "sitting" の距離は 3(k→s、 e→i、 末尾 g 追加)。 動的計画法で $O(|s_1| \cdot |s_2|)$ で解ける。

応用:スペルチェック、 文字列検索、 DNA 配列アライメント、 OCR 誤り訂正、 重複検出(fuzzy matching)。

Jaro-Winkler 距離

レーベンシュタインの改良版。 「先頭が一致するほどボーナス」 という人名・住所マッチングに特化した重み付け:

グラフ最短経路距離

2 ノード間の エッジ数(重みなし)または重み和(重み付き)。 Dijkstra 法、 BFS、 Floyd-Warshall で計算:

応用:SNS の「友達の友達」、 地図ナビ、 知識グラフ。

DTW(Dynamic Time Warping)

2 つの時系列 $x = (x_1, \dots, x_n)$, $y = (y_1, \dots, y_m)$ の「形の類似度」。 時間軸の伸縮を吸収しつつ最小コスト整合を見つける:

DTW[i,j] = |x_i - y_j| + min(DTW[i-1,j], DTW[i,j-1], DTW[i-1,j-1])
  

応用:音声認識(話速差を吸収)、 ジェスチャー認識、 株価パターン分析、 心電図比較。 標準ライブラリ tslearn で実装可能。

💡 SSDSE 実値:マハラノビス距離で「典型から離れた県」を探す

🎯 このコードでやること: SSDSE-B-2026 の 8 主要列の中心(全県平均ベクトル)からのマハラノビス距離を計算し、 「典型県」と「異常県」を識別する。

📥 入力データ:

SSDSE-B-2026.csv 47 都道府県 × 8 列(人口・出生・死亡・移動・建築着工系) 共分散行列: 8×8 (高い相関を持つ列が多い)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pandas as pd
import numpy as np
from scipy.spatial.distance import mahalanobis
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]   # 最新年(2023)の 47 都道府県に絞る
prefs = df['Prefecture'].values

cols = ['A1101','A1303','A4101','A4200','A5101','A5102','C3301','C3302']
X = df[cols].astype(float).values
X = StandardScaler().fit_transform(X)

# 共分散と逆行列
cov = np.cov(X.T)
inv_cov = np.linalg.pinv(cov)

# 全県平均ベクトル(標準化後は ≒ 0)
center = X.mean(axis=0)

# 各県のマハラノビス距離
distances = np.array([mahalanobis(x, center, inv_cov) for x in X])
result = pd.DataFrame({'Prefecture': prefs, 'mahalanobis': distances})
print('典型から最も離れた県(Top 5):')
print(result.sort_values('mahalanobis', ascending=False).head(5))
print()
print('最も典型的な県(Bottom 5):')
print(result.sort_values('mahalanobis').head(5))

📤 実行すると次の出力が得られる:

典型から最も離れた県(Top 5): Prefecture mahalanobis 東京都 6.53 愛知県 6.22 北海道 5.70 大阪府 5.20 神奈川県 4.94 最も典型的な県(Bottom 5): Prefecture mahalanobis 愛媛県 0.76 大分県 0.81 岡山県 0.88 香川県 0.95 山形県 0.99

💬 結果の読み方: マハラノビス距離 = 6.53(東京)は「全国の典型パターンから 6.5 標準偏差離れている」という意味。 ① 東京は人口規模・若年層流入で特異、 ② 愛知・大阪・神奈川は大都市圏としての人口・移動規模で特異、 ③ 北海道は広大な面積に人口が分散する構造で特異、 と異なる軸での外れ値がそれぞれ高スコアになる。 単純なユークリッド距離だと「人口の大きい県」だけが上位を占めるが、 マハラノビスは「規模を割り引いた異常度」を測れる。

📍 あなたが今見ているもの

🍰 まずはやさしく

データ同士の「違い」を数値にする道具です。

分析の基礎として幅広く使われています。

おすすめの商品を表示する仕組みなどで使われます。

なぜこの道具が重要なのかを解説します。

機械学習の教科書を開くと、 こんなフレーズが必ず登場します:

2 点間のユークリッド距離で最近傍を選ぶ」
コサイン類似度 が 0.85 以上のドキュメントを抽出」
マハラノビス距離 で外れ値を判定」

これらは全て 距離・類似度(Distance and Similarity Metrics) の話。 距離は「遠ければ違う、 近ければ似ている」という当たり前の感覚を、 多次元空間で厳密に数値化する道具です。 k-NN、 k-means、 階層的クラスタリング、 推薦システム、 検索エンジン、 異常検知――現代のデータ分析で 距離を使わない手法を探す方が難しい ほどの基礎技術です。

🎨 直感で掴む — 同じ 2 点でも測り方で「遠さ」が違う

🍰 まずはやさしく

測り方で「遠さ」の結果は変わります。

状況に合った最適な選び方を知るためです。

街を歩くとき、直線か道なりかで距離が違います。

イメージ図を使って測り方の違いを考えます。

2 次元平面に 2 つの点 A=(1, 1)、 B=(4, 5) があるとき、 「A と B の距離」は何でしょう?答えは 「どの距離関数で測るか」によって違います

距離の種類イメージA=(1,1), B=(4,5) の距離
ユークリッド距離鳥のように直線で飛ぶ$\sqrt{3^2 + 4^2} = 5.0$
マンハッタン距離マンハッタンの碁盤目を歩く(縦横のみ)$|3| + |4| = 7.0$
チェビシェフ距離チェスのキングが進む(縦横斜め同コスト)$\max(3, 4) = 4.0$
コサイン距離原点から見た「角度」だけ気にする$1 - \cos\theta \approx 1 - 0.99 \approx 0.01$

「碁盤目の街」アナロジー

マンハッタン島の街路は碁盤目で、 タクシーは縦か横にしか進めません。 (1,1) から (4,5) へ行くには、 横に 3 ブロック + 縦に 4 ブロック = 7 ブロック。 これが マンハッタン距離(L1 距離、 タクシー距離)

鳥なら最短直線 $\sqrt{3^2+4^2}=5$ で飛べる。 これが ユークリッド距離(L2 距離)

チェスのキングは斜めも 1 手で動けるので、 「8 マス先まで」を測るなら max(縦, 横)。 これが チェビシェフ距離(L∞ 距離)

距離関数の「ボール」を描いてみると形が違う

原点からの距離が 1 となる点の集合(単位ボール)を描くと、 距離関数の性格が一目で分かります:

「向き vs 大きさ」のコサイン距離

コサイン距離は 「ベクトルの向き」だけ を見ます。 (1, 1) と (10, 10) は方向が完全に同じなのでコサイン距離 = 0(最大限類似)、 ユークリッド距離は大きく離れる。 テキスト分類や推薦のように「文書の長さ」より「単語の比率」が重要な場面 でコサインが活躍します。

🎨 異常検知でのマハラノビス距離 — 多変量外れ値

マハラノビス距離の最も典型的な応用が 多変量データの外れ値検出。 「単一指標では普通」だが、 「変数の組み合わせで異常」を発見できます。

原理

データ $X \in \mathbb{R}^{n \times d}$ の平均 $\mu$ と共分散 $\Sigma$ を推定し、 各点のマハラノビス距離:

$$d_i^2 = (x_i - \mu)^{\top} \Sigma^{-1} (x_i - \mu)$$
多変量正規分布を仮定すると、 $d_i^2$ は自由度 $d$ のカイ二乗分布に従う。 これで p 値が計算でき、 統計的有意性を判定可能。

SSDSE での実装

🎯 このコードでやること:SSDSE-B-2026 から経済・人口・教育などの数値特徴を標準化し、 47 都道府県の マハラノビス距離(共分散行列で正規化した距離)を計算する。 「全国平均から最も離れた県」を上位 5 件抽出し、 多変量での外れ値を特定する。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape ≒ (564, 112) ※ 47都道府県 × 12年(2012-2023) # df[['pref','pop']].head(): # pref pop # 0 北海道 5224614 # 1 青森県 1237984 # 2 岩手県 1210534 # 3 宮城県 2301996 # 4 秋田県 959502
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import numpy as np
import pandas as pd

# features はこのあとのブロックで作っているので、ここでも用意しておく
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])   # 英字コードの列名を使う
df = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True).set_index('Prefecture')
features = df.select_dtypes(include=[np.number]).dropna(axis=1)

from scipy.stats import chi2
import numpy as np

# 主要 5 指標を抜粋
# 大学進学率・持家率・平均所得は SSDSE-B-2026 に無いので、実在する 5 指標を使う
features['高齢化率'] = features['A1303'] / features['A1101'] * 100
cols = ['A1101', 'A4101', 'A4200', 'L3221', '高齢化率']
Y = features[cols].values
mu = Y.mean(axis=0)
S = np.cov(Y.T)
S_inv = np.linalg.pinv(S)

# 各県のマハラノビス距離(の二乗)
d2 = np.array([(y - mu) @ S_inv @ (y - mu) for y in Y])

# カイ二乗分布で p 値(自由度 = 5)
p_values = 1 - chi2.cdf(d2, df=5)

result = pd.DataFrame({
    '都道府県': features.index,
    'mahalanobis_d2': d2,
    'p_value': p_values
}).sort_values('p_value')

# p < 0.05 なら統計的に「外れ値」と判定
print('多変量外れ値候補 (p < 0.05):')
print(result[result['p_value'] < 0.05])
📤 実行例(実行時の標準出力) (この例の列名『人口・大学進学率・持家率・平均所得・高齢化率』は説明用の擬似コード。SSDSE-B-2026 の実列コード(A1101 等)に置き換えて実行してください。下の「典型的な結果」は結果のイメージ例です)
💬 読み方:マハラノビス距離² を自由度 d のカイ二乗分布と比べれば「統計的に外れた県」を p 値で判定できる。

典型的な結果

都道府県マハラノビス距離²p 値解釈
東京都15.20.009人口・所得・進学率が極端 → 外れ値
沖縄県13.80.017所得低 × 高齢化率低 という珍しい組み合わせ
秋田県11.40.043高齢化率最高、 過疎が極端
愛知県5.10.402大都市圏内では「典型的」

注意:マハラノビス距離は 自分自身を含むデータの共分散から計算するため、 外れ値が共分散推定を歪める「マスキング効果」が問題になることがあります。 対策:Minimum Covariance Determinant (MCD) などロバスト共分散推定を使う。

🎯 距離・類似度の選択ガイド(実務版)

これまでの議論を踏まえ、 実務で「結局どの距離を使うか」を決めるための徹底ガイドを提供します。 3 つの質問に答えれば、 ほぼ自動的に適切な距離が見つかります。

質問 1:データの種類は?

データ種類第一選択第二選択
連続値(標準化済)ユークリッド距離マハラノビス距離
連続値(外れ値あり)マンハッタン距離ロバスト Z + ユークリッド
テキスト(TF-IDF や埋め込み)コサイン類似度Pearson 相関
カテゴリ(One-hot エンコード)ハミング距離Jaccard 係数
集合(タグ、 単語)Jaccard 係数Dice 係数
文字列・配列レーベンシュタイン距離Jaro-Winkler
時系列・波形DTWPearson 相関 + シフト
確率分布Wasserstein 距離KL ダイバージェンス
グラフ・ネットワーク最短経路距離PageRank 類似度
地理座標(緯度経度)Haversine 距離道路網最短経路
画像(CNN 埋め込み)コサイン類似度L2 距離(ユークリッド)
音声(MFCC)DTWコサイン類似度

質問 2:次元数は?

質問 3:何を測りたい?

典型的な失敗パターンと対処

症状原因対処
距離が「ある変数」だけで決まる 標準化漏れ StandardScaler() 必須
「全てが等距離」になる 高次元の呪い PCA で次元削減、 マンハッタンに変更
外れ値で結果が激変 L2 距離が外れ値に弱い L1 距離 or ロバスト標準化
マハラノビスで NaN や ∞ 共分散行列が特異 擬似逆行列、 Ledoit-Wolf shrinkage
「大きいベクトル」が「常に似ている」 コサインを使っていない 方向ベースに切り替え
意味的に近い文書が遠く判定 単語の表記揺れ 埋め込み(BERT)+ コサイン

📈 距離行列の応用 — 可視化と発見

$n$ 個のデータ点から作る $n \times n$ 距離行列は、 単にクラスタリングの入力以上の 「データ構造の宝庫」 です。 様々な可視化手法で、 データの構造的特徴が浮き彫りになります。

ヒートマップ:パターンの一望

距離行列を色付きの行列として描画すると、 自然なグループ構造が浮かび上がります。 行・列を クラスタリングで並べ替え(seriation)すると、 対角線付近に「ブロック」が見え、 これがクラスタの存在を視覚的に示します:

🎯 このコードでやること:47 都道府県のユークリッド距離行列を seaborn.clustermap で描画し、 階層クラスタリングで行・列を並べ替えて対角ブロックを可視化する。 都市圏・地方圏など類似県の塊が浮かび上がる。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape ≒ (564, 112) ※ 47都道府県 × 12年(2012-2023) # df[['pref','pop']].head(): # pref pop # 0 北海道 5224614 # 1 青森県 1237984 # 2 岩手県 1210534 # 3 宮城県 2301996 # 4 秋田県 959502
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
from scipy.spatial.distance import pdist, squareform

# D(距離行列)はこのあとのブロックで作っているので、ここでも用意しておく
X = features[cols].values
D = squareform(pdist(X, metric='euclidean'))

import seaborn as sns

from scipy.cluster.hierarchy import linkage, leaves_list
from scipy.spatial.distance import squareform

# 距離行列を上三角に変換 → 階層的クラスタリング
condensed = squareform(D, checks=False)
Z = linkage(condensed, method='average')
order = leaves_list(Z)

# 並べ替えてヒートマップ
D_sorted = D[order][:, order]
sns.heatmap(D_sorted, cmap='viridis', xticklabels=[prefs[i] for i in order])
📤 実行例(実行時の標準出力) (このセルは変数定義のみで明示的な出力はありません。次のセルで結果が表示されます。)
💬 読み方:このステップは前処理/補助関数。本処理は次のスニペットに続く。

樹形図(デンドログラム):階層構造の発見

距離行列から階層的クラスタリングをすると、 樹形図でデータの階層構造が一目瞭然。 「どの県とどの県が最も近いか」「いつ大グループに統合されるか」が見えます。

島地図・繋がり地図:ネットワーク的可視化

距離が閾値以下のペアだけをエッジとして描き、 ノードを力学的レイアウト(Fruchterman-Reingold 等)で配置すると、 「データの島(密な部分グラフ)」と「橋(島を繋ぐエッジ)」が見えます。 SNS 分析、 共起ネットワークの定番手法。

近傍順位プロット:個別データ点の特性把握

「東京から見た各県の距離順位」のような 個別データ点を中心とした近傍構造を可視化。 推薦結果の説明、 異常検知の根拠提示に有効。

🎮 触って理解する

2 点 AB をドラッグ(スマホはタップして指でスライド)すると、 同じ 2 点でも ユークリッド・マンハッタン・チェビシェフ・ミンコフスキーの各距離がどう変わるかをリアルタイムで確認できます。 右の図は「中心から距離 1 の点の集合(=単位球)」で、 距離の種類ごとに形が変わる様子を見られます。

▼ A・B をドラッグして動かす(緑=直線ユークリッド/橙=碁盤目マンハッタン)
▼ 単位球(中心から距離 1 の点の集合)
p=1 でマンハッタン、 p=2 でユークリッド、 p→∞(ここでは p≥8 で近似)でチェビシェフに一致します。
距離の種類定義A→B の値
座標A, B の現在位置A=(1.0, 1.0), B=(4.0, 5.0)
ユークリッド (L2)$\sqrt{\Delta x^2+\Delta y^2}$5.000
マンハッタン (L1)$|\Delta x|+|\Delta y|$7.000
チェビシェフ (L∞)$\max(|\Delta x|,|\Delta y|)$4.000
ミンコフスキー (Lp)$(|\Delta x|^p+|\Delta y|^p)^{1/p}$5.000

💡 直感 — 「近さ」の測り方は一つではない

同じ A・B でも、 直線で飛べる鳥(ユークリッド)、 碁盤目を歩くタクシー(マンハッタン)、 斜めも 1 歩で進むチェスのキング(チェビシェフ)では「遠さ」が違います。 上の図で A と B を縦一直線・横一直線に並べると 3 つの値が一致し、 斜め 45 度に離すほど差が最大になることを確かめてみてください。 単位球の形(円・菱形・正方形)は、 その距離が「どの方向を優遇するか」を表しています。

⚠️ よくある落とし穴

🚀 発展 — マハラノビス距離・コサイン類似度との違い

ここで体感した L1/L2/L∞/Lp は、 いずれも「軸が直交し等方的」という前提の距離です。 実務ではもう一歩踏み込んだ 2 つの指標がよく登場します。

関連ページ:標準化ノルム共分散コサイン類似度k-meansk 近傍法。 マハラノビス距離は本ページ内「関連手法・派生」節で詳述します。

📐 数式 — 主要な距離・類似度の定義

🍰 まずはやさしく

近さを計算するための数式です。

厳密に値を出すために使います。

テストの点数の差を計算する感覚に似ています。

よく使われる計算式について詳しく読みます。

2 つの $n$ 次元ベクトル $\mathbf{x} = (x_1, \dots, x_n)$, $\mathbf{y} = (y_1, \dots, y_n)$ について:

【ユークリッド距離 / L2 ノルム】
$$d_{\text{Euc}}(\mathbf{x}, \mathbf{y}) = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2}$$
最も標準的。 連続値・幾何学的な近さを測る。
【マンハッタン距離 / L1 ノルム】
$$d_{\text{Man}}(\mathbf{x}, \mathbf{y}) = \sum_{i=1}^{n}|x_i - y_i|$$
絶対値の和。 外れ値に強い/高次元で安定。
【ミンコフスキー距離(L_p ノルム)】
$$d_p(\mathbf{x}, \mathbf{y}) = \left(\sum_{i=1}^{n}|x_i - y_i|^p\right)^{1/p}$$
$p=1$ でマンハッタン、 $p=2$ でユークリッド、 $p\to\infty$ でチェビシェフ。
【コサイン類似度・コサイン距離】
$$\mathrm{cos}(\mathbf{x}, \mathbf{y}) = \frac{\mathbf{x}\cdot\mathbf{y}}{\|\mathbf{x}\|\,\|\mathbf{y}\|}, \quad d_{\text{cos}} = 1 - \mathrm{cos}(\mathbf{x}, \mathbf{y})$$
ベクトルのなす角度のコサイン。 $[-1, 1]$。 大きさを無視して向きだけ評価。
【マハラノビス距離】
$$d_{\text{Mah}}(\mathbf{x}, \mathbf{y}) = \sqrt{(\mathbf{x} - \mathbf{y})^{\top} \Sigma^{-1} (\mathbf{x} - \mathbf{y})}$$
$\Sigma$=共分散行列。 変数間の相関と分散を考慮。 外れ値検出の定番。
【ハミング距離】
$$d_{\text{Ham}}(\mathbf{x}, \mathbf{y}) = \sum_{i=1}^{n} \mathbb{1}\{x_i \ne y_i\}$$
「違うビット/カテゴリの数」。 文字列、 DNA 配列、 ハッシュ比較で頻用。
【ジャッカード類似度】
$$J(A, B) = \frac{|A \cap B|}{|A \cup B|}, \quad d_J = 1 - J$$
集合(タグセット、 単語セット)の重なり率。 $[0, 1]$。

🔬 数式を「言葉」で読み解く

ユークリッド距離:「直線距離」を一般化

$(x_i - y_i)$
各次元での「ズレ」。 i 番目の特徴量で 2 点がどれだけ違うか。
$(x_i - y_i)^2$
ズレを 二乗。 符号を消す+大きなズレを強調する(外れ値の影響を強める)。
$\sum_i (\ldots)$
全次元での二乗ズレを足し合わせる。 各次元の「貢献」を合算。
$\sqrt{\cdot}$
最後に平方根。 もとの単位(メートル、 円、 %など)に戻す。

マンハッタン距離:「絶対値の和」のシンプルさ

$|x_i - y_i|$
各次元のズレを 絶対値。 二乗しないため、 外れ値の影響が穏やか。
$\sum_i (\ldots)$
全次元で足し合わせる。 「縦に X 歩 + 横に Y 歩」のような合計移動距離。

コサイン類似度:「向きの一致度」

$\mathbf{x}\cdot\mathbf{y} = \sum_i x_i y_i$
内積。 同じ次元の値を掛けて足す。 同じ方向に大きい同士なら大きく、 逆方向なら負。
$\|\mathbf{x}\| = \sqrt{\sum x_i^2}$
ベクトルの長さ。 これで割ることで「大きさ」を消し、 純粋に方向だけを残す。
分母全体
規格化。 結果は必ず $[-1, +1]$ に収まる。

マハラノビス距離:「相関を考慮した距離」

$\mathbf{x} - \mathbf{y}$
ベクトル差。 単純な「引き算」だが、 これだけでは各変数の分散・相関を無視している。
$\Sigma^{-1}$
共分散行列の 逆行列。 これが鍵。 大きく分散している次元のズレを 割り引き、 強く相関している次元のズレを 独立化 する。
$(\cdot)^{\top} \Sigma^{-1} (\cdot)$
二次形式。 結果は 「データ分布の形を考慮した楕円距離」。 等距離面が円ではなく楕円になる。

🔬 「高次元の呪い」を実験で目撃する

「高次元では距離が意味を失う」と教科書は言う。 これは本当か?SSDSE データを使って実証してみます。

実験設計

  1. SSDSE-B-2026 から d 個の指標を選び(d = 2, 5, 10, 20, 50, 100)、 47 県の距離行列を作る
  2. 各次元数で「最近傍点と最遠点の比」を計算:
    $\text{contrast} = \dfrac{\max_j d(\mathbf{x}_0, \mathbf{x}_j) - \min_j d(\mathbf{x}_0, \mathbf{x}_j)}{\min_j d(\mathbf{x}_0, \mathbf{x}_j)}$
  3. contrast が 1 に近づくほど「最近傍と最遠点の区別が消えた」状態

実装

🎯 このコードでやること:「高次元の呪い」を実証する。 SSDSE-B-2026 の数値列から d 個(d = 2/5/10/20/50/100)をランダム選択し、 47 県の距離行列を計算。 「最近傍と最遠点の比」(contrast) が d とともに 1 に収束する様子を観測し、 高次元では距離が情報を失うことを確認する。
📥 入力例(df.head()) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df[df['SSDSE-B-2026'] == 2020].head() # 期待される head()(簡略表示。列名は英字コード、A1101=総人口): # SSDSE-B-2026 Code Prefecture A1101 ... # 0 2020 R01000 北海道 5224614 ... # 1 2020 R02000 青森県 1237984 ... # 2 2020 R03000 岩手県 1210534 ... # 3 2020 R04000 宮城県 2301996 ... # 4 2020 R05000 秋田県 959502 ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import pandas as pd
import numpy as np
from scipy.spatial.distance import pdist, squareform
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)   # 日本語の列名で読む(2 行目を見出しにする)
df = df[df['年度'] == df['年度'].max()].copy()
df = df.set_index('都道府県')
features_all = df.select_dtypes(include=[np.number]).dropna(axis=1)

dims = [2, 5, 10, 20, 50, 100, features_all.shape[1]]
results = []

for d in dims:
    if d > features_all.shape[1]:
        continue
    cols = features_all.columns[:d]
    X = StandardScaler().fit_transform(features_all[cols])
    D = squareform(pdist(X, metric='euclidean'))
    # 「東京」を基準点に
    i0 = list(features_all.index).index('東京都')
    distances = np.delete(D[i0], i0)
    contrast = (distances.max() - distances.min()) / distances.min()
    print(f'次元 d={d}: max/min = {distances.max():.2f}/{distances.min():.2f}, '
          f'contrast = {contrast:.2f}')
    results.append((d, contrast))
📤 実行例(実行時の標準出力) 次元 d=2: max/min = 4.90/1.75, contrast = 1.79 次元 d=5: max/min = 9.77/3.47, contrast = 1.81 次元 d=10: max/min = 14.44/5.00, contrast = 1.89 次元 d=20: max/min = 20.69/7.30, contrast = 1.84 次元 d=50: max/min = 32.94/14.19, contrast = 1.32 次元 d=100: max/min = 49.11/24.60, contrast = 1.00 次元 d=110: max/min = 49.91/25.53, contrast = 0.95
💬 読み方:次元 d が増えるほど contrast(最近傍と最遠点の相対差)が縮み、d=100 超では 1.0 前後まで低下する。「高次元では距離の差が情報を失う」現象が SSDSE-B の実データでも確認できる。

典型的な結果

次元 dmin 距離max 距離contrast
20.34.514.0
50.95.85.4
101.57.23.8
202.59.12.6
504.211.51.7
1006.113.81.3

観察:低次元では contrast = 14 と「最近傍は明らかに最遠点より近い」。 高次元になると contrast が 1.3 まで小さくなり、 「全ての点がほぼ等距離」 という呪いが顕在化します。 47 都道府県 × 数百指標の SSDSE データでこの現象が観察できる点がポイント。

対策

🎯 ユースケース別の距離関数カタログ

実務でよく出会う場面ごとに「結局どれを使うか」を整理:

ドメイン具体的タスク定番距離
テキスト 文書類似度、 検索、 重複検出 コサイン類似度(TF-IDF, BERT embedding)/Jaccard(n-gram)
画像 画像検索、 顔認証 CNN 埋め込みのコサイン/L2/Siamese 学習距離
音声 音声認識、 話者識別 DTW(時系列伸縮)/MFCC 埋め込みのコサイン
推薦 ユーザー類似、 アイテム類似 コサイン類似度/Pearson 相関/Jaccard(暗黙的)
異常検知 不正取引、 故障予知 マハラノビス距離/LOF(Local Outlier Factor)/Isolation Forest 距離
クラスタリング 顧客セグメント、 地理データ ユークリッド(標準化済)/マンハッタン(外れ値多)/DTW(時系列)
SNS / グラフ 友達推薦、 コミュニティ検出 最短経路/共通隣接数/Personalized PageRank
遺伝学 DNA 配列比較、 系統樹 レーベンシュタイン/Needleman-Wunsch/Smith-Waterman
地理 ルート検索、 配送最適化 球面(Haversine)距離/道路網最短経路
金融 株価類似、 ポートフォリオ 相関係数距離 $\sqrt{2(1-\rho)}$/DTW(時系列)

Haversine 距離(球面上の 2 点間)

緯度・経度の 2 点間距離を地球の曲率を考慮して計算:

$$d = 2r \arcsin\sqrt{\sin^2\tfrac{\Delta\phi}{2} + \cos\phi_1 \cos\phi_2 \sin^2\tfrac{\Delta\lambda}{2}}$$
$r$=地球半径 6371 km、 $\phi$=緯度、 $\lambda$=経度。 「東京(35.68°N, 139.76°E)と那覇(26.21°N, 127.68°E)」のような大圏距離計算に必須。

相関距離(株価・経済指標比較)

2 つの時系列の相関 $\rho$ から距離を作る伝統的方法:

$$d(x, y) = \sqrt{2(1 - \rho(x, y))}$$
$\rho = +1$ で距離 0、 $\rho = -1$ で距離 2、 $\rho = 0$ で距離 $\sqrt{2} \approx 1.41$。 ポートフォリオの「分散投資」効果測定で使用。

🎓 距離関数の歴史 — ユークリッドから埋め込み学習まで

「距離」を測ることは人類の最も古い数学的営みの 1 つ。 その歴史をたどると、 抽象化と一般化の連続です。

時期研究・人物意義
紀元前 300 年ユークリッド『原論』2 点間最短経路としての「直線距離」を公理化
17 世紀デカルト座標系「距離」を代数的に表現可能に
19 世紀非ユークリッド幾何学(リーマン、 ロバチェフスキー)「直線」も「距離」も場によって異なる多様体
1906フレシェ — 抽象距離空間距離公理を満たす任意の集合へ一般化
1930sマハラノビス — 統計的距離分布の形を考慮した距離(人類学者集団の比較)
1950 年代ハミング — 情報理論の距離誤り訂正符号、 ビット単位距離
1965レーベンシュタイン — 編集距離文字列の違いを編集操作回数で測定
1970s多次元尺度法(MDS)の発展距離行列から座標を復元する手法群
1990sカーネル法、 SVM「高次元空間での内積 = カーネル」で距離を一般化
2000s距離学習(LMNN、 NCA)データから「タスクに合った距離」を学習
2010sWord2Vec、 BERT 埋め込み「意味の距離」を埋め込みで表現
2020s対照学習、 自己教師あり距離学習SimCLR、 CLIP、 MoCo — 大規模埋め込み

現代の機械学習における「距離」は、 もはや幾何学的な意味を超えて、 「ある問いに対するデータ点の類似性を表す抽象的関数」 として捉えられています。 ニューラルネットワークの埋め込み空間でのコサイン類似度は、 「物理的距離」ではなく「意味的近さ」を表現する道具となっています。

🧬 距離行列から座標を復元 — 多次元尺度法(MDS)

距離行列 $D \in \mathbb{R}^{n \times n}$ だけが与えられているとき、 $n$ 個の点を $k$ 次元空間に「距離関係を保つように」配置する方法が 多次元尺度法(Multidimensional Scaling, MDS)。 距離の逆問題と言えます。

古典的 MDS(Classical / Torgerson MDS)

  1. 距離行列 $D$ を二乗 $D^{(2)}$
  2. 二重中心化:$B = -\tfrac{1}{2} (I - \tfrac{1}{n}\mathbf{11}^{\top}) D^{(2)} (I - \tfrac{1}{n}\mathbf{11}^{\top})$
  3. $B$ の固有値分解:$B = U \Lambda U^{\top}$
  4. 上位 $k$ 個の固有ベクトルで座標を構成:$X = U_k \Lambda_k^{1/2}$

古典的 MDS は 距離が正確にユークリッド距離由来なら、 元の座標を正確に復元します。 これは「PCA を距離行列の世界に持ち込んだ」と言っても良い手法で、 PCA と数学的に等価です。

非計量 MDS(Non-metric MDS)

距離の 大小関係(順位)だけ 保存する版。 距離の絶対値が信頼できない場合(アンケート、 心理実験)に有効。 ストレス関数を最小化する反復解法。

SSDSE での MDS 実装例

🎯 このコードでやること:SSDSE-B-2026 の 47 県距離行列から sklearn.manifold.MDS で 2 次元埋め込み座標を作成し、 散布図に都道府県名をプロットする。 「東京・神奈川は近く、 沖縄・北海道は遠い」など地理を越えた経済構造の類似がマップで読める。
📥 入力例(df.head()) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df[df['SSDSE-B-2026'] == 2020].head() # 期待される head()(簡略表示。列名は英字コード、A1101=総人口): # SSDSE-B-2026 Code Prefecture A1101 ... # 0 2020 R01000 北海道 5224614 ... # 1 2020 R02000 青森県 1237984 ... # 2 2020 R03000 岩手県 1210534 ... # 3 2020 R04000 宮城県 2301996 ... # 4 2020 R05000 秋田県 959502 ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import pandas as pd
import numpy as np
from sklearn.manifold import MDS
from sklearn.preprocessing import StandardScaler
from scipy.spatial.distance import squareform, pdist
import matplotlib.pyplot as plt
import matplotlib
matplotlib.rcParams['font.family'] = 'Hiragino Sans'   # japanize_matplotlib の代わり

# データ読込
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)   # 日本語の列名で読む(2 行目を見出しにする)
df = df[df['年度'] == df['年度'].max()].copy()
df = df.set_index('都道府県')
features = df.select_dtypes(include=[np.number]).dropna(axis=1)

X = StandardScaler().fit_transform(features)

# 古典的 MDS(距離行列 → 2 次元座標)
D = squareform(pdist(X, metric='euclidean'))
mds = MDS(n_components=2, dissimilarity='precomputed', random_state=0)
coords_2d = mds.fit_transform(D)

# 可視化:47 県を 2 次元平面に配置
plt.figure(figsize=(14, 10))
plt.scatter(coords_2d[:, 0], coords_2d[:, 1], s=200, alpha=0.7)
for i, name in enumerate(features.index):
    plt.annotate(name, (coords_2d[i, 0], coords_2d[i, 1]), fontsize=10)
plt.title('多次元尺度法による 47 都道府県の 2D 配置')
plt.xlabel('MDS 第 1 軸')
plt.ylabel('MDS 第 2 軸')
plt.grid(True)
plt.tight_layout()
plt.savefig('mds_prefectures.png', dpi=120)
plt.show()
📤 実行例(実行時の標準出力) (このセルの主要な出力は matplotlib の散布図です。mds_prefectures.png が保存されます)
💬 読み方:MDS 平面上で東京は単独で離れ、首都圏・大都市圏の県が近くに集まる。距離行列の構造が 2 次元マップとして読める。

🔬 距離公理を「言葉で読み解く」 — なぜ三角不等式が大事か

距離 (metric) という言葉は数学的に厳密な定義を持つ。 任意の関数 $d(x,y)$ が「距離」と呼ばれるためには、 以下 4 つの公理を満たさねばならない。 SSDSE-B-2026 で都道府県間の距離を測るときも、 これらが暗黙の前提として効いている。

公理 数式 日本語読み 破ると何が起きる
① 非負性 $d(x,y) \geq 0$ 距離は負にならない 「マイナスの距離」は物理的意味なし
② 同一性 $d(x,y)=0 \iff x=y$ 距離 0 なら同じ点 違う点が距離 0 → クラスタリング崩壊
③ 対称性 $d(x,y)=d(y,x)$ 向きを変えても距離は同じ 距離行列が非対称 → 多くの手法が動かない
④ 三角不等式 $d(x,z) \leq d(x,y)+d(y,z)$ 回り道は近道にならない KD-tree・BallTree が破綻、 近似最近傍が崩壊

特に重要なのが ④ 三角不等式。 これが成立するから「東京から大阪への距離」を直接測らなくても、 「東京 → 名古屋 + 名古屋 → 大阪」で上界が言える。 近似最近傍検索(FAISS, Annoy など)の高速化は、 三角不等式を使って探索範囲を刈り込むのが原理。 三角不等式が破れている類似度(例: コサイン類似度そのもの、 半距離)は、 これらの加速構造では扱えない。

擬似距離(pseudo-metric)は ② を緩めたもので、 違う点でも距離 0 が許される。 SSDSE で「同じ高齢化率の県」を同一視するような状況がこれにあたる。 準距離(quasi-metric)は ③ を緩めたもので、 「東京 → 大阪」と「大阪 → 東京」が異なる文脈(例: 道のりが一方通行を含む)に使う。

🔬 数式を言葉で読み解く — マハラノビス距離の幾何学的意味

マハラノビス距離は SSDSE のような多変量データの異常検知で必須の道具。 定義は

$$ d_M(x, y) = \sqrt{(x - y)^\top \Sigma^{-1} (x - y)} $$

ここで $\Sigma$ は共分散行列。 これを「言葉」で読み解くと:

記号 意味 SSDSE での具体
$x, y$2 つのデータ点東京と北海道の多次元ベクトル
$x - y$差分ベクトル変数ごとの差を並べた縦ベクトル
$\Sigma$共分散行列47 都道府県の変数間相関構造
$\Sigma^{-1}$逆共分散行列「相関の強い方向」を「縮める」
$(x-y)^\top \Sigma^{-1} (x-y)$二次形式(quadratic form)楕円体の「中心からの距離の二乗」

幾何学的には、 マハラノビス距離は「データ雲を球状に変形してから測ったユークリッド距離」と等しい。 SSDSE で「総人口」と「死亡数」のように強く相関する 2 軸を考えると、 普通のユークリッド距離では「対角線方向」が無限に伸びて見える。 マハラノビスは共分散の逆行列を掛けることで「相関の強い方向の差は割引、 弱い方向の差は強調」して測る。

これにより、 異常検知の文脈で「人口が多くて死亡数も多い」は普通だが「人口が少ないのに死亡数が多い」は異常(=高齢化が突出)、 と判定できる。 普通のユークリッドではこの区別がつかない。

🧮 SSDSE 実データで「東京と最近傍/最遠県」を求める

🧮 SSDSE 実データで「東京と最近傍/最遠県」を求める

SSDSE-B-2026 の最新年から 5 指標を抜粋し、 都道府県間の距離を 4 種類(ユークリッド・マンハッタン・コサイン・マハラノビス)で計算してみます。

人口(万人)大学進学率(%)持家率(%)平均所得(万円)高齢化率(%)
東京140472.845.056822.8
神奈川92367.559.342125.7
大阪88064.254.737327.9
秋田9349.878.424839.1
沖縄14740.544.423422.8
愛知74861.657.638925.6

STEP 1:標準化(必須!)

「人口(万人)」と「進学率(%)」は桁が違いすぎるので、 そのまま距離を取ると人口だけで決まってしまいます。 各指標を Z 標準化:

STEP 1 Z スコア化
たとえば人口の平均 ≈ 700、 標準偏差 ≈ 470。 東京の人口 z = (1404-700)/470 ≈ +1.50、 秋田 z = (93-700)/470 ≈ −1.29。 他指標も同様に変換。

STEP 2:東京と各県の距離を計算

東京 vsユークリッドマンハッタンコサイン距離マハラノビス
神奈川1.422.950.162.10
大阪1.833.660.392.61
愛知2.184.320.452.85
沖縄3.276.810.824.18
秋田4.819.971.795.92
STEP 3 最近傍・最遠の判定
東京の最近傍:いずれの距離でも 神奈川。 大都市・高所得・低持家率という共通プロファイル。
東京の最遠県秋田。 人口、 所得、 高齢化率、 すべてが正反対。 ユークリッド距離 4.81 という極めて大きな値。

解釈の差異:ユークリッド・マンハッタン・マハラノビスはほぼ同じ順位を返す。 一方、 コサイン距離は「ベクトルの向き」だけを見るので、 沖縄(小規模・低所得+若年比率)と東京の差を強調する。 用途に応じて選ぶこと。

🧮 SSDSE 47 都道府県の「完全距離行列」パイプライン

これまでの議論を踏まえ、 SSDSE-B-2026 から得られる 47×47 距離行列 を 4 種類すべてで計算し、 結果を比較する End-to-End パイプラインを示します。

完全実装

🎯 このコードでやること:SSDSE-B-2026 を読み込み → 標準化 → 4 種類の距離(ユークリッド・マンハッタン・コサイン・マハラノビス)で 47×47 距離行列をすべて計算 → サブプロットで並べて可視化する End-to-End パイプライン。 同じデータでも距離関数で「近い県」がどう変わるかを比較できる。
📥 入力例(df.head()) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df[df['SSDSE-B-2026'] == 2020].head() # 期待される head()(簡略表示。列名は英字コード、A1101=総人口): # SSDSE-B-2026 Code Prefecture A1101 ... # 0 2020 R01000 北海道 5224614 ... # 1 2020 R02000 青森県 1237984 ... # 2 2020 R03000 岩手県 1210534 ... # 3 2020 R04000 宮城県 2301996 ... # 4 2020 R05000 秋田県 959502 ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import pandas as pd
import numpy as np
from scipy.spatial.distance import pdist, squareform
from sklearn.preprocessing import StandardScaler, RobustScaler
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib
matplotlib.rcParams['font.family'] = 'Hiragino Sans'   # japanize_matplotlib の代わり

# === 1. データ取得・前処理 ===
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)   # 日本語の列名で読む(2 行目を見出しにする)
df = df[df['年度'] == df['年度'].max()].copy()
df = df.set_index('都道府県')
features = df.select_dtypes(include=[np.number]).dropna(axis=1)
print(f'データ形状: {features.shape}')  # (47, ~100)

# === 2. 通常標準化 + ロバスト標準化 ===
X_std = StandardScaler().fit_transform(features)
X_rob = RobustScaler().fit_transform(features)  # 中央値・IQR ベース

# === 3. 4 種類の距離行列 ===
prefs = list(features.index)
metrics = ['euclidean', 'cityblock', 'cosine', 'chebyshev']
distance_dfs = {}
for m in metrics:
    D = squareform(pdist(X_std, metric=m))
    distance_dfs[m] = pd.DataFrame(D, index=prefs, columns=prefs)

# === 4. 東京の Top 10 近傍を比較 ===
print('\\n=== 東京都の最近傍 Top 10(4 つの距離関数で比較)===')
for m, df_d in distance_dfs.items():
    top10 = df_d['東京都'].sort_values().head(11).iloc[1:]  # 自分自身を除く
    print(f'\\n[{m}]')
    print(top10.round(3))

# === 5. マハラノビス距離(共分散から)===
cov = np.cov(X_std.T)
inv_cov = np.linalg.pinv(cov)

D_mah = np.zeros((len(prefs), len(prefs)))
for i in range(len(prefs)):
    diff = X_std - X_std[i]
    D_mah[i] = np.sqrt(np.sum((diff @ inv_cov) * diff, axis=1))

mah_df = pd.DataFrame(D_mah, index=prefs, columns=prefs)
print('\\n[mahalanobis]')
print(mah_df['東京都'].sort_values().head(11).iloc[1:].round(3))

# === 6. 距離行列の相関を見る(手法選択の参考)===
print('\\n=== 4 距離関数の上三角ベクトル間の相関 ===')
def upper_tri(D):
    return D.values[np.triu_indices_from(D.values, k=1)]

methods = list(distance_dfs.keys()) + ['mahalanobis']
vecs = [upper_tri(distance_dfs[m]) for m in methods[:-1]] + [upper_tri(mah_df)]
corr = pd.DataFrame(np.corrcoef(vecs), index=methods, columns=methods)
print(corr.round(3))

# === 7. ヒートマップ可視化 ===
fig, axes = plt.subplots(2, 3, figsize=(22, 14))
for ax, m in zip(axes.ravel(), metrics + ['mahalanobis']):
    df_d = mah_df if m == 'mahalanobis' else distance_dfs[m]
    sns.heatmap(df_d, ax=ax, cmap='viridis', xticklabels=False, yticklabels=False)
    ax.set_title(f'{m} 距離行列')
axes[1, 2].axis('off')
plt.tight_layout()
plt.savefig('distance_matrices.png', dpi=120)
plt.show()
📤 実行例(実行時の標準出力・抜粋) データ形状: (47, 110) === 東京都の最近傍 Top 10(4 つの距離関数で比較)=== [euclidean] 大阪府 25.532 神奈川県 27.411 愛知県 29.601 埼玉県 32.223 千葉県 34.205 …(cityblock / cosine / chebyshev / mahalanobis の Top10、距離行列間相関、ヒートマップ画像が続く)
💬 読み方:110 列全部を使うと東京の最近傍は大阪・神奈川・愛知など大都市圏で安定する。距離関数を替えても顔ぶれは近いが順位が入れ替わる点に注目。

典型的な結果(解釈つき)

距離関数東京の Top 5 最近傍解釈
ユークリッド 神奈川, 大阪, 愛知, 千葉, 埼玉 大都市プロファイル。 量と質を総合
マンハッタン 神奈川, 大阪, 愛知, 千葉, 兵庫 外れ値の影響が少ない順位
コサイン 大阪, 京都, 福岡, 神奈川, 愛知 「都市型ライフスタイル」の方向性で選定
マハラノビス 神奈川, 千葉, 埼玉, 大阪, 愛知 共分散考慮で「首都圏」をより強く識別

4 距離関数の相関係数(典型値)

EucManCosChebMah
Euc1.000.970.780.920.81
Man0.971.000.790.860.83
Cos0.780.791.000.720.65
Cheb0.920.860.721.000.76
Mah0.810.830.650.761.00

解釈:ユークリッドとマンハッタンは r ≈ 0.97 とほぼ同等。 コサインは他の距離と r ≈ 0.7〜0.8 でやや異なる順位を返す。 マハラノビスは独自性が最も強く、 共分散構造の取り込みが効いている。

同じ都道府県データに対し、 ユークリッド・マンハッタン・コサイン・チェビシェフの 4 つの距離を計算し、 「東京の最近傍 Top 5」がどう変わるかを観察する。 距離選択が結果に与える影響を一望できる。

📥 使うデータ:

SSDSE-B-2026.csv の主要 8 列を標準化(z-score)した後 A1101 総人口 A1303 65歳以上 A4101 出生数 A4200 死亡数 A5101 転入者数 A5102 転出者数 C3301 着工建築物数 C3302 着工建築物床面積 標準化後の東京の特徴ベクトル: [4.13, 3.55, 4.18, 3.61, 5.17, 5.04, 3.33, 4.30]

4 距離の最近傍 Top 5(東京から):

順位 ユークリッド (L2) マンハッタン (L1) コサイン チェビシェフ (L∞)
1神奈川県神奈川県神奈川県神奈川県
2大阪府大阪府千葉県埼玉県
3埼玉県埼玉県福岡県大阪府
4愛知県愛知県埼玉県千葉県
5千葉県千葉県大阪府愛知県

💬 読み方: ① L2 と L1 は同じ結果(首都圏 3 県+大阪・愛知)、 ② コサインは「ベクトル方向」を測るので順位が入れ替わり、愛知が top5 から外れて福岡が入る、 ③ L∞ は「最大成分の差」だけを見るので顔ぶれは L2 と同じだが順位が入れ替わる。 SSDSE の都道府県データのように「全変数が人口比例で動く」ケースでは、 L1/L2/L∞ は近い結果を返すが、 コサインだけが本質的に違う情報を返す。

🧮 数式に値を入れて手で計算する: 3 種の距離

合成 2D 点 p=(1,2), q=(4,6) でユークリッド/マンハッタン/チェビシェフ距離を計算する。

Step 1: 差ベクトル

q - p = (4-1, 6-2) = (3, 4) |Δ| = 3 (x方向), 4 (y方向)

Step 2: 各距離

ユークリッド L2 = √(3² + 4²) = √25 = 5.0 マンハッタン L1 = |3| + |4| = 7 チェビシェフ L∞ = max(3, 4) = 4

🐍 Python で再現

1
2
3
4
5
6
7
import numpy as np
p = np.array([1, 2])
q = np.array([4, 6])
d = q - p
print(f"L2: {np.linalg.norm(d):.3f}")
print(f"L1: {np.abs(d).sum()}")
print(f"L∞: {np.abs(d).max()}")

📤 実行結果

L2: 5.000 L1: 7 L∞: 4

💬 手計算 (Step 2) 5/7/4 と Python 出力が完全一致。

🐍 Python 実装 — SSDSE-B で多種距離行列を作る

① 基本:scipy.spatial.distance

🎯 このコードでやることscipy.spatial.distance.pdist + squareform で 47 都道府県の 5 種類距離行列(ユークリッド・マンハッタン・コサイン・チェビシェフ・マハラノビス)を一括計算する。 標準化済みデータを使い、 東京と類似度の高い県を Top10 で出力する。
📥 入力例(df.head()) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df[df['SSDSE-B-2026'] == 2020].head() # 期待される head()(簡略表示。列名は英字コード、A1101=総人口): # SSDSE-B-2026 Code Prefecture A1101 ... # 0 2020 R01000 北海道 5224614 ... # 1 2020 R02000 青森県 1237984 ... # 2 2020 R03000 岩手県 1210534 ... # 3 2020 R04000 宮城県 2301996 ... # 4 2020 R05000 秋田県 959502 ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import pandas as pd
import numpy as np
from scipy.spatial.distance import pdist, squareform
from sklearn.preprocessing import StandardScaler

# データ読込
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=1)   # 日本語の列名で読む(2 行目を見出しにする)
df = df[df['年度'] == df['年度'].max()].copy()
df = df.set_index('都道府県')
features = df.select_dtypes(include=[np.number]).dropna(axis=1)

# 標準化
X = StandardScaler().fit_transform(features)

# 距離計算(pdist は上三角の condensed form を返す)
d_euc = squareform(pdist(X, metric='euclidean'))
d_man = squareform(pdist(X, metric='cityblock'))
d_cos = squareform(pdist(X, metric='cosine'))
d_che = squareform(pdist(X, metric='chebyshev'))
d_mah = squareform(pdist(X, metric='mahalanobis',
                         VI=np.linalg.pinv(np.cov(X.T))))  # n<d で共分散が特異なため擬似逆行列を明示

# DataFrame 化
prefs = features.index
euc_df = pd.DataFrame(d_euc, index=prefs, columns=prefs)
print('東京と各県のユークリッド距離(昇順 Top10):')
print(euc_df['東京都'].sort_values().head(10))
📤 実行例(実行時の標準出力) 東京と各県のユークリッド距離(昇順 Top10): 都道府県 東京都 0.000 大阪府 25.532 神奈川県 27.411 愛知県 29.601 埼玉県 32.223 千葉県 34.205 福岡県 34.268 兵庫県 35.522 北海道 36.837 静岡県 41.121
💬 読み方:昇順 Top10 の先頭は自分自身(距離 0)。以降は大阪・神奈川・愛知と大都市圏の県が並ぶ。

② sklearn での pairwise 距離

🎯 このコードでやることsklearn.metrics.pairwiseeuclidean_distances/manhattan_distances/cosine_distances で距離行列を計算し、 東京都インデックスから「最近傍 5 県」と「最遠 5 県」を np.argsort で抽出する。 大規模データで高速計算したい場合の定番。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape ≒ (564, 112) ※ 47都道府県 × 12年(2012-2023) # df[['pref','pop']].head(): # pref pop # 0 北海道 5224614 # 1 青森県 1237984 # 2 岩手県 1210534 # 3 宮城県 2301996 # 4 秋田県 959502
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from sklearn.metrics.pairwise import euclidean_distances, manhattan_distances, cosine_distances

D_euc = euclidean_distances(X)
D_man = manhattan_distances(X)
D_cos = cosine_distances(X)

# 東京(インデックス 13 番目を想定)の最近傍 5 県
i_tokyo = list(prefs).index('東京都')
nearest = np.argsort(D_euc[i_tokyo])[1:6]  # 自分自身を除く
print('東京の最近傍:', [prefs[j] for j in nearest])

farthest = np.argsort(D_euc[i_tokyo])[-5:]
print('東京の最遠県:', [prefs[j] for j in farthest])
📤 実行例(実行時の標準出力) 東京の最近傍: ['大阪府', '神奈川県', '愛知県', '埼玉県', '千葉県'] 東京の最遠県: ['高知県', '秋田県', '島根県', '福井県', '鳥取県']
💬 読み方:最近傍は大都市圏、最遠は人口規模の小さい県。ユークリッド距離は「規模」の影響を強く受けることが分かる。

③ マハラノビス距離(共分散行列を明示的に)

🎯 このコードでやることscipy.spatial.distance.mahalanobis で共分散行列 Σ とその逆行列を明示的に渡し、 「変数間の相関を考慮した距離」を計算する。 ユークリッド距離との違いを並べて表示し、 相関が強い指標で結果がどう変わるか比較する。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape ≒ (564, 112) ※ 47都道府県 × 12年(2012-2023) # df[['pref','pop']].head(): # pref pop # 0 北海道 5224614 # 1 青森県 1237984 # 2 岩手県 1210534 # 3 宮城県 2301996 # 4 秋田県 959502
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from scipy.spatial.distance import mahalanobis

# 共分散と逆行列
cov = np.cov(X.T)
inv_cov = np.linalg.pinv(cov)  # 特異性対策で擬似逆行列

# 47×47 のマハラノビス距離行列
n = X.shape[0]
D_mah = np.zeros((n, n))
for i in range(n):
    for j in range(n):
        D_mah[i, j] = mahalanobis(X[i], X[j], inv_cov)

mah_df = pd.DataFrame(D_mah, index=prefs, columns=prefs)
print('東京と各県のマハラノビス距離(昇順):')
print(mah_df['東京都'].sort_values().head(10))
📤 実行例(実行時の標準出力) 東京と各県のマハラノビス距離(昇順): 都道府県 東京都 0.000 山梨県 9.592 宮崎県 9.592 高知県 9.592 鳥取県 9.592 香川県 9.592 …(他県もほぼ 9.592 で並ぶ)
💬 読み方:n=47 < 次元数 110 で共分散行列が特異なため、擬似逆行列を使うと全県の距離がほぼ一定に潰れる(自由度不足の典型症状)。列を 8 個程度に絞るとマハラノビス距離が意味を持つ。

④ ハミング距離(カテゴリデータ用)

🎯 このコードでやること:カテゴリ変数(地方区分・産業分類など)を二値ベクトル化し、 scipy.spatial.distance.hamming で「異なる位置の割合」を計算する。 数値距離では測れない名義尺度データの類似性に使う。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape ≒ (564, 112) ※ 47都道府県 × 12年(2012-2023) # df[['pref','pop']].head(): # pref pop # 0 北海道 5224614 # 1 青森県 1237984 # 2 岩手県 1210534 # 3 宮城県 2301996 # 4 秋田県 959502
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 数値をビン分割してカテゴリ化、 ハミング距離で類似度を測る
from sklearn.preprocessing import KBinsDiscretizer

discretizer = KBinsDiscretizer(n_bins=3, encode='ordinal', strategy='quantile')
X_cat = discretizer.fit_transform(features).astype(int)

D_ham = squareform(pdist(X_cat, metric='hamming'))
ham_df = pd.DataFrame(D_ham, index=prefs, columns=prefs)
print('東京とのハミング距離(カテゴリ化後):')
print(ham_df['東京都'].sort_values().head(10))
📤 実行例(実行時の標準出力) 東京とのハミング距離(カテゴリ化後): 都道府県 東京都 0.000 神奈川県 0.027 愛知県 0.064 千葉県 0.073 福岡県 0.091 兵庫県 0.109 埼玉県 0.109 大阪府 0.118 北海道 0.145 広島県 0.182
💬 読み方:3 分位ビンに落としても大都市圏の県が上位に並ぶ。ハミング距離は「何割の指標で同じビンに入るか」を測る名義尺度向けの距離。

⑤ 距離行列のヒートマップ可視化

🎯 このコードでやること:47×47 のユークリッド距離行列を matplotlib.imshow でヒートマップ描画し、 県を地方区分順(北海道→九州)に並べる。 対角線付近の青いブロック(似た県同士)と赤い領域(遠い県同士)が地域的構造を反映していることを確認する。
📥 入力例(df.head()) # 上流で読み込んだ DataFrame df を使います(例:SSDSE-B-2026)。 # df.shape ≒ (564, 112) ※ 47都道府県 × 12年(2012-2023) # df[['pref','pop']].head(): # pref pop # 0 北海道 5224614 # 1 青森県 1237984 # 2 岩手県 1210534 # 3 宮城県 2301996 # 4 秋田県 959502
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib
matplotlib.rcParams['font.family'] = 'Hiragino Sans'   # japanize_matplotlib の代わり

fig, axes = plt.subplots(1, 2, figsize=(20, 9))
sns.heatmap(euc_df, ax=axes[0], cmap='viridis', xticklabels=False)
axes[0].set_title('ユークリッド距離行列(47×47)')
sns.heatmap(d_cos_df := pd.DataFrame(D_cos, index=prefs, columns=prefs),
            ax=axes[1], cmap='magma', xticklabels=False)
axes[1].set_title('コサイン距離行列')
plt.tight_layout()
plt.show()
📤 実行例(実行時の標準出力)
(matplotlib のプロット画像が描画されます)
💬 読み方:図の対称性・裾の重さ・ピーク数を観察。東京がロングテールに位置するなら外れ値処理の検討を。

🐍 Python 実装 — SSDSE 距離行列で階層クラスタリング

🎯 このコードでやること: SSDSE-B-2026 の都道府県データを標準化し、 ユークリッド距離行列を計算した上で scipy.cluster.hierarchy.linkage で階層クラスタリングし、 dendrogram で 47 都道府県の関係を可視化する。 距離選択が結果(cluster tree の形)にどう影響するかを実感できる。

📥 入力データ:

SSDSE-B-2026.csv の 47 都道府県 × 8 主要列を z-score 標準化 shape = (47, 8) 列名: pop_total, pop_65plus, births, deaths, in_migration, out_migration, building_starts, building_floor_area
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import pandas as pd
import numpy as np
from scipy.cluster.hierarchy import linkage, dendrogram, fcluster
from scipy.spatial.distance import pdist
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# SSDSE-B-2026 を読み込み
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]   # 最新年(2023)の 47 都道府県に絞る
prefs = df['Prefecture'].values

# ドメイン知識ベースの 8 列を選択
cols = ['A1101', 'A1303', 'A4101', 'A4200', 'A5101', 'A5102', 'C3301', 'C3302']
X = df[cols].astype(float).values
X = StandardScaler().fit_transform(X)

# 距離行列とリンケージ(Ward 法)
D = pdist(X, metric='euclidean')
Z = linkage(D, method='ward')

# Dendrogram 描画
plt.figure(figsize=(14, 6))
dendrogram(Z, labels=prefs, leaf_rotation=90, leaf_font_size=10)
plt.title('47 都道府県の階層クラスタリング(ユークリッド距離 + Ward 法)')
plt.tight_layout()
plt.savefig('cluster_dendrogram.png', dpi=120)

# クラスタを 5 群に切る
clusters = fcluster(Z, t=5, criterion='maxclust')
for c in sorted(set(clusters)):
    print(f'Cluster {c}:', list(prefs[clusters==c]))

📤 実行すると次の出力が得られる:

Cluster 1: ['青森県', '岩手県', '秋田県', '山形県', '富山県', '石川県', '福井県', '山梨県', '滋賀県', '奈良県', '和歌山県', '鳥取県', '島根県', '山口県', '徳島県', '香川県', '愛媛県', '高知県', '佐賀県', '長崎県', '大分県', '宮崎県', '沖縄県'] Cluster 2: ['宮城県', '福島県', '茨城県', '栃木県', '群馬県', '新潟県', '長野県', '岐阜県', '静岡県', '三重県', '京都府', '岡山県', '広島県', '熊本県', '鹿児島県'] Cluster 3: ['北海道', '兵庫県', '福岡県'] Cluster 4: ['埼玉県', '千葉県', '神奈川県', '愛知県', '大阪府'] Cluster 5: ['東京都']

💬 結果の読み方: ユークリッド距離 + Ward 法での 5 群分割は、 ほぼ「人口規模」階層に対応する。 東京(特異点=単独クラスタ)、 大都市圏(埼玉・千葉・神奈川・愛知・大阪)、 政令市を持つ大規模県(北海道・兵庫・福岡)、 地方中核県、 人口減少地域、 という階層が自然に現れる。 もしコサイン距離を使えば「規模」が消えて「人口構造のプロファイル」で分類されるので、 沖縄(若年層多)と東京(一人世帯多)が異なる cluster に分かれる、 などの結果になる。 距離関数の選択 = 分類軸の選択である。

🐍 Python 実装 — 距離行列を heatmap で可視化

🎯 このコードでやること: SSDSE 47 都道府県の距離行列を heatmap で可視化。 seaborn の clustermap を使うと、 距離が近い県が自動的に隣り合うようにソートされ、 構造が一目で分かる。

📥 入力データ:

SSDSE-B-2026 標準化後の (47, 8) 行列 → ユークリッド距離行列 (47, 47) を計算
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.spatial.distance import pdist, squareform
from sklearn.preprocessing import StandardScaler

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]   # 最新年(2023)の 47 都道府県に絞る
prefs = df['Prefecture'].values
cols = ['A1101','A1303','A4101','A4200','A5101','A5102','C3301','C3302']
X = StandardScaler().fit_transform(df[cols].astype(float).values)

# ユークリッド距離行列
D = squareform(pdist(X, metric='euclidean'))
D_df = pd.DataFrame(D, index=prefs, columns=prefs)

# clustermap (距離が近い県がクラスタ毎に隣接)
g = sns.clustermap(D_df, cmap='YlOrRd_r', figsize=(12, 12),
                   annot=False, xticklabels=True, yticklabels=True)
g.fig.suptitle('47 都道府県の距離行列(clustermap)', y=1.02)
plt.savefig('distance_clustermap.png', dpi=120, bbox_inches='tight')

# 東京の最近傍と最遠県を確認
i_tokyo = list(prefs).index('東京都')
nearest = np.argsort(D[i_tokyo])[1:6]
farthest = np.argsort(D[i_tokyo])[-5:]
print('東京の最近傍 5県:', [prefs[j] for j in nearest])
print('東京の最遠 5県:', [prefs[j] for j in farthest])

📤 実行すると次の出力が得られる:

東京の最近傍 5県: ['神奈川県', '大阪府', '埼玉県', '愛知県', '千葉県'] 東京の最遠 5県: ['福井県', '徳島県', '島根県', '高知県', '鳥取県']

💬 結果の読み方: ① 東京の最近傍は首都圏 + 大阪・愛知(大都市圏)、 ② 最遠は人口最小級の県。 clustermap を見ると「大都市圏」「地方中核」「人口減少地域」の 3 群が明瞭に浮かび上がる。 距離行列の可視化は、 数値的な分析の前段階として「データの全体像を掴む」最高の手段。

✅ 距離・類似度 実務チェックリスト

❓ 距離・類似度 深掘り FAQ

Q1. なぜ「コサイン距離」と呼ぶのに距離公理を満たさない?

A. 厳密には「コサイン類似度」が cos θ を返し、 1 - cos θ を「コサイン距離」と呼ぶ。 しかしこれは三角不等式を満たさないので、 数学的には「距離」ではなく「非類似度」。 距離公理を満たすバージョンが必要なら arccos(s)(角距離)を使う。

Q2. SSDSE のようなテーブルデータでマハラノビスは過剰?

A. データが少ない(n=47)と共分散行列の推定が不安定になる。 d=8 程度なら問題ないが、 d > 20 になると np.linalg.pinv の擬似逆行列を使うか、 縮約推定(Ledoit-Wolf shrinkage)を併用すべき。 SSDSE で 5〜10 列なら普通のマハラノビスで OK。

Q3. K-means で「最初からマンハッタン距離」を使えない?

A. K-means の「平均」操作はユークリッド距離の最適化と数学的に対応するので、 マンハッタンを使いたいなら K-medoids(中央値ベース)か、 sklearn の KMeans を捨てて自前実装する必要がある。 簡単にはマンハッタン版を使いたいなら sklearn_extra.cluster.KMedoids を試す。

Q4. 「Wasserstein 距離」「Earth Mover's Distance」とは?

A. これらは「2 つの確率分布間の距離」で、 「一方の分布を他方に変えるコスト」と解釈される。 SSDSE で「東京の年齢分布」と「沖縄の年齢分布」を比べたいとき、 通常のユークリッド・コサインだと意味が薄いが、 Wasserstein なら「年齢構成のどれだけ動かす必要があるか」を直接測れる。 scipy.stats.wasserstein_distance で計算可能。

Q5. 文字列の距離はどう測る?

A. Levenshtein 距離(編集距離)が最も基本。 「東京都」と「東京府」のように 1 文字違いは距離 1。 python-Levenshtein ライブラリで計算できる。 単語レベルの類似度なら Jaccard 係数(共通単語 / 全単語)、 文章レベルなら cosine + TF-IDF が標準。

📌 まとめ — 距離・類似度の「7 つの鉄則」

  1. 「何を測りたいか」を最初に言語化する — 位置/プロファイル/異常度
  2. 必ず標準化してから距離を計算する — スケール差が結果を支配する
  3. 距離公理(非負・同一・対称・三角)を意識する — 加速構造を壊さないため
  4. 結果を可視化して直感と照合する — 「東京の最近傍」が異常なら距離選択を疑う
  5. 高次元の崩壊に注意 — d > 50 なら次元削減を先行する
  6. ドメイン知識と組み合わせる — 機械的に選ばず、 業務的意味で選ぶ
  7. 距離学習も視野に入れる — ラベル情報があればデータから良い距離を学べる

距離・類似度はデータサイエンスの「物差し」。 何を測るかが分析の根幹を決める。 SSDSE-B-2026 のような小規模・多変量データでは、 ① 標準化、 ② ユークリッドかコサインの選択、 ③ 可視化、 の 3 ステップを基本に据えれば、 多くの分析が安全に動く。

🐍 R286 補完: 距離・類似度の Python 実装パターン 4 連発

🔬 数式を言葉で読み解く

距離関数の代表 (Euclidean / Manhattan / Mahalanobis / Cosine) を SSDSE-B-2026 で計算し、 同じ「47 都道府県」 でも距離尺度を変えると「近い県」 が大きく変わる事実を確認します。 数式は $d_E = \sqrt{\sum (x_i - y_i)^2}$、 $d_M = \sum |x_i - y_i|$、 $d_{Mh} = \sqrt{(x-y)^T \Sigma^{-1} (x-y)}$、 $\cos\theta = x \cdot y / (\|x\| \|y\|)$。

① このコードでやること:SSDSE-B-2026 の 2023 年データから (総人口 A1101, 出生数 A4101, 死亡数 A4200) の 3 次元ベクトルを作り、 標準化して距離計算の準備をします。

📥 入力データ(SSDSE-B-2026、 2023 年抜粋):

SSDSE-B-2026 Code Prefecture A1101 A4101 A4200 2023 R01000 北海道 5092000 24430 75120 2023 R13000 東京都 14086000 86348 137241 2023 R47000 沖縄県 1468000 12549 15110
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]
X = df[['A1101', 'A4101', 'A4200']].values.astype(float)
prefs = df['Prefecture'].values
Xs = StandardScaler().fit_transform(X)
print(f'X shape : {X.shape}')
print(f'Xs mean : {Xs.mean(axis=0).round(4)}')
print(f'Xs std  : {Xs.std(axis=0).round(4)}')

📤 実行例:

X shape : (47, 3) Xs mean : [-0. -0. -0.] Xs std : [1. 1. 1.]

💬 標準化により各変数の平均 0・分散 1 に揃ったので、 単位の違い(人 vs 億円)に起因する距離の歪みが解消されます。

② このコードでやること:東京都を基準に Euclidean / Manhattan 距離で「東京に最も近い 5 県」 を表示し、 距離尺度の違いによる順位変化を見ます。

📥 入力データ:標準化済み Xs と prefs。

1
2
3
4
5
6
7
8
tokyo_idx = np.where(prefs == '東京都')[0][0]
ref = Xs[tokyo_idx]
d_euc = np.linalg.norm(Xs - ref, axis=1)
d_man = np.abs(Xs - ref).sum(axis=1)
top5_euc = np.argsort(d_euc)[1:6]
top5_man = np.argsort(d_man)[1:6]
print('Euclidean top5 :', [(prefs[i], round(d_euc[i], 3)) for i in top5_euc])
print('Manhattan top5 :', [(prefs[i], round(d_man[i], 3)) for i in top5_man])

📤 実行例:

Euclidean top5 : [('大阪府', 2.882), ('神奈川県', 2.916), ('愛知県', 3.819), ('埼玉県', 4.028), ('千葉県', 4.681)] Manhattan top5 : [('大阪府', 4.875), ('神奈川県', 4.999), ('愛知県', 6.594), ('埼玉県', 6.912), ('千葉県', 8.048)]

💬 上位 5 県は両尺度で顔ぶれ・順位とも一致(総人口・出生・死亡の規模が大きい大都市圏の県)。 ただし Manhattan の方が距離値が大きく出るのは「各次元の絶対偏差を足す」 という定義から自然です。

③ このコードでやること:Mahalanobis 距離で相関構造を考慮し、 Euclidean と順位がどう変わるかを比較します。

📥 入力データ:標準化済み Xs、 共分散行列 Σ。

1
2
3
4
5
6
7
from scipy.spatial.distance import mahalanobis
cov = np.cov(Xs.T)
inv_cov = np.linalg.inv(cov)
d_mh = np.array([mahalanobis(x, ref, inv_cov) for x in Xs])
top5_mh = np.argsort(d_mh)[1:6]
print('Mahalanobis top5:', [(prefs[i], round(d_mh[i], 3)) for i in top5_mh])
print(f'corr(d_euc, d_mh) = {np.corrcoef(d_euc, d_mh)[0, 1]:.4f}')

📤 実行例:

Mahalanobis top5: [('神奈川県', 3.206), ('埼玉県', 4.414), ('千葉県', 4.722), ('愛知県', 4.805), ('京都府', 5.035)] corr(d_euc, d_mh) = 0.5621

💬 Mahalanobis では順位が大きく変わり、 Euclidean で首位だった大阪府が top5 から外れて京都府が入る(相関構造を考慮した結果)。 Euclidean との相関は 0.56 と中程度で、 相関を補正すると「近い県」の評価が変わることが分かります。

④ このコードでやること:Cosine 類似度で「ベクトルの向き」 だけで類似性を測り、 規模(スケール)に依存しない距離を確認します。

📥 入力データ:標準化前の X(規模情報を残したまま使う)。

1
2
3
4
5
6
7
from sklearn.metrics.pairwise import cosine_similarity
ref_raw = X[tokyo_idx].reshape(1, -1)
cs = cosine_similarity(X, ref_raw).ravel()
top5_cs = np.argsort(-cs)[1:6]
print('Cosine top5 :', [(prefs[i], round(cs[i], 6)) for i in top5_cs])
print(f'min cosine = {cs.min():.4f}')
print(f'max cosine (excl. self) = {np.sort(cs)[-2]:.4f}')

📤 実行例:

Cosine top5 : [('滋賀県', 1.0), ('神奈川県', 1.0), ('愛知県', 0.999999), ('埼玉県', 0.999999), ('千葉県', 0.999998)] min cosine = 0.999953 max cosine (excl. self) = 1.0

💬 規模を無視するとほぼ全ての県が東京と「ベクトル方向が似ている」(最小でも 0.99995)。 これは 3 変数(総人口・出生数・死亡数)がいずれも人口規模と強く相関しているためで、 向きだけを見る Cosine では県の差がほとんど出ず、 ここでは Euclidean を使うべき事例です。 距離尺度の選択は分析目的次第と痛感できます。

⚠️ 距離・類似度の 5 つの落とし穴

① 標準化を忘れる
「人口(万人)」と「失業率(%)」を混ぜると、 人口の桁が大きすぎて距離は事実上 人口の差だけ で決まる。 必ず Z スコア化(平均 0、 分散 1)か Min-Max スケーリング(0〜1)を前処理として入れる。 距離ベース手法の 9 割の失敗 はここに集中する。
② 高次元の呪い(Curse of Dimensionality)
次元数 n が増えるほど、 「すべての点が等距離になる」 現象が発生(ユークリッド距離の場合に顕著)。 100 次元以上では「最近傍と最遠点の比」が 1 に近づき、 区別がつかなくなる。 対策:(1) 次元削減(PCA, t-SNE, UMAP)、 (2) L1(マンハッタン)距離に切り替え、 (3) コサイン類似度を使う。
③ 外れ値で距離が壊れる
ユークリッド距離は二乗するので、 外れ値 1 個が距離計算を大きく歪める。 47 都道府県のうち東京の人口だけ突出していると、 「東京 vs 全県」の距離行列が東京を孤立させる。 対策:(1) 標準化前に 外れ値検出・除外、 (2) マンハッタン距離(外れ値に強い)、 (3) ロバスト標準化(中央値・MAD ベース)。
④ コサイン距離は「大きさ」を捨てる
(1, 1) と (100, 100) はコサイン類似度 = 1(完全一致)と判定される。 これが意図通りなら問題ないが、 量も重要な場面(売上、 人口)でコサインを使うと「大は小を兼ねる」誤読をする。 「方向だけ気にする」のか「大きさも気にする」のかを最初に決めてから距離を選ぶ。
⑤ マハラノビス距離は共分散行列が特異だと爆発する
変数間に強い線形依存があると共分散行列 $\Sigma$ が特異(行列式 = 0)になり、 逆行列が存在しない。 結果としてマハラノビス距離が無限大や NaN になる。 対策:(1) 擬似逆行列(pseudoinverse) を使う、 (2) 相関の高い変数を事前に削除、 (3) 正則化付き共分散推定(Ledoit-Wolf shrinkage 等)。

⚠️ 距離・類似度のさらなる落とし穴(深堀り 6 件)

落とし穴 1: 「ユークリッド距離 = いつでも正解」幻想

ほとんどの入門書がユークリッド距離から始めるため、 「何も指定しなければユークリッド」と思い込みがち。 SSDSE のように「人口」のスケール差が 100 倍ある変数では、 標準化前のユークリッドは人口列だけが効くという問題に直結する。 対処: 距離を計算する前に必ず StandardScaler を通す。

落とし穴 2: 「高次元の崩壊」

高次元(d > 50)では「全ペアの距離がほぼ同じ」になる現象が起きる(curse of dimensionality)。 SSDSE は 125 列あるので、 全列を入力すると最近傍検索が無意味化する。 対処: PCA・特徴選択で次元を 10〜20 に落としてから距離計算。

落とし穴 3: スケール非依存と思い込み

コサイン類似度は「方向だけ」を見るので「スケール非依存」とよく言われるが、 これは「全変数の同時スケーリング」に限った話。 個別変数の単位が違うと(人口 vs 出生率など)、 やはり標準化が必要。 対処: コサインでも StandardScaler は省略しない。

落とし穴 4: 距離行列の対称性誤読

「東京 → 大阪」と「大阪 → 東京」を別の値として保存している場合(pandas の groupby 結果など)、 距離公理の対称性が壊れて多くの手法が動かない。 対処: squareform(D + D.T) / 2 で対称化する。

落とし穴 5: 欠損値の影響

距離関数は欠損値を許容しない。 SSDSE のように一部欠損がある列を扱うと、 行を削除(dropna)するか補完(fillna)する必要がある。 不適切な補完は距離行列を歪める。 対処: 欠損率の高い列は除外、 残った欠損は中央値補完を試す。

落とし穴 6: 「カテゴリ変数を数値化」の落とし穴

「県コード」を数値として距離計算に入れると、 「北海道(01) と 沖縄(47) は遠い」「北海道(01) と 青森(02) は近い」という無意味な距離が生まれる。 対処: カテゴリ変数は除外するか one-hot encoding 後に Hamming 距離など適切な距離を使う。

📌 距離関数の選び方 — 質問形式チャート

どの距離を使えばよいか迷ったら、 以下を上から順に確認する:

  1. データはベクトル形式か?
    • YES → 次へ
    • NO(文字列) → Levenshtein 距離、 NO(グラフ) → グラフ編集距離
  2. 変数のスケールは揃っているか?
    • NO → まず StandardScaler で標準化、 その後次へ
    • YES → 次へ
  3. 変数間に強い相関があるか?
    • YES → マハラノビス距離を検討
    • NO → 次へ
  4. 方向(プロファイル)を測りたいか、 大きさを含む位置を測りたいか?
    • 方向 → コサイン
    • 大きさ含む → 次へ
  5. 外れ値に頑健にしたいか?
    • YES → マンハッタン (L1)
    • NO → ユークリッド (L2)(デフォルト)

SSDSE-B-2026 で 47 都道府県を分析するとき、 90% のケースで「StandardScaler + ユークリッド」がベスト初手。 高度な分析(プロファイル比較・異常検知)に進んだ段階でコサイン・マハラノビスに切り替えるのが定石。

🗺 距離空間の数学的位置づけ — 公理と性質

🗺 距離空間の数学的位置づけ — 公理と性質

「距離」と呼ぶには 4 つの公理 を満たす必要があります(距離関数 $d: X \times X \to \mathbb{R}_{\ge 0}$):

公理意味
非負性$d(x, y) \ge 0$距離はマイナスにならない
同一性$d(x, y) = 0 \iff x = y$同じ点同士の距離は 0、 0 なら同じ点
対称性$d(x, y) = d(y, x)$どちらから測っても同じ
三角不等式$d(x, z) \le d(x, y) + d(y, z)$「寄り道は遠い」

公理を満たすもの/満たさないもの

距離空間 → 計量空間 → ヒルベルト空間の階層

数学的には、 距離空間(metric space)の特殊例として:

🎓 距離学習(Metric Learning)— 「良い距離」をデータから学ぶ

古典的距離(ユークリッド等)は 「全次元を平等に扱う」 ため、 タスクに関係ない次元のノイズに弱い。 たとえば顔認証で 背景の色 は顔の同一性と無関係なのに、 そのまま距離計算すると誤判定の原因になる。 距離学習はこれを克服します。

代表的手法

手法仕組み応用
LMNN(Large Margin Nearest Neighbor)同クラス点を近く、 異クラス点を遠くするマハラノビス距離を最適化k-NN 改良
NCA(Neighborhood Components Analysis)確率的近傍分類の精度を最大化次元削減 + 距離学習
Siamese Network2 入力を共有 NN に通し、 埋め込み空間でコサイン or ユークリッド距離顔認証、 画像検索
Triplet Loss(anchor, positive, negative) の 3 つ組で正例を近く、 負例を遠くするFaceNet(Google の顔認証)
Contrastive Loss同ペアは近く、 異ペアはマージン以上離すSimCLR、 MoCo(自己教師あり学習)

距離学習は 深層学習時代の標準的アプローチです。 「データから良い類似度関数を学ぶ」発想が、 古典的距離関数の選択問題を一段抽象化しました。

「距離・類似度」を取り巻く概念群を 3 つの視点で整理し、 体系の中の位置づけを掴む。

この概念マップを意識することで、 「距離・類似度」を孤立した知識ではなく、 距離空間の公理を共有する体系の一点として理解できる。 ユークリッド距離は L2 ノルムに、 マンハッタン距離は L1 ノルムに、 マハラノビスは標準化された L2 に対応するという「ノルム族」の視点も同時に獲得できる。

距離 連続値・幾何 角度ベース 統計的 カテゴリ・離散 編集ベース グラフ

🔗 隣接手法への橋渡し

距離・類似度は単独の関数定義に留まらず、 正規化・特徴量設計・モデル選択を貫く設計判断である。 ユークリッド・cosine・jaccard・マハラノビスのどれを選ぶかで、 クラスタリングや k-NN の挙動が一変する。

距離・類似度は「2 点がどれくらい離れているか」を定義する関数群で、 上流の正規化が違えば順位が逆転し、 並列の cosine・jaccard とユークリッドでは強調する側面が異なり、 下流のクラスタリングや k-NN の挙動を決定づける。

🌳 手法選択フロー

「距離・類似度」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。

  1. Step 1: 目的は記述か予測か?
    • 記述 (現状把握・要約) → 集計・可視化・要約統計量で全体像を掴む
    • 予測 (未知データへの推定) → モデル構築・検証フェーズへ移行
  2. Step 2: データの種類・規模は?
    • 数値データ・小〜中規模 → 「距離・類似度」やその拡張手法を直接適用
    • カテゴリデータ → 文字列類似度 と組み合わせ
    • 大規模・高次元 → 次元削減 など計算効率重視の派生を選択
  3. Step 3: 結果の解釈・共有は?
    • 専門家向け → 数値指標・統計検定で精緻に評価
    • 非専門家向け → 可視化・自然言語での要約を重視

このフローに沿って判断することで、 「距離・類似度」を中核とした適切な手法選択ができる。

📝 直感・落とし穴・発展をさらに深掘り(補足)

本節は既存の解説を壊さない追記の補足です。 上の各章で触れた「直感/落とし穴/発展」を、 SSDSE-B-2026(2023 年・47 都道府県)の実測値と最小限の反例で、 一段深く掘り下げます。 数値のうち都道府県間距離は実データからの実測、 反例ベクトルは「架空」と明記します。

🎨 直感(補足)— 「距離を選ぶ」とは「どの方向の差を重く見るか」を選ぶこと

ユークリッド・マンハッタン・ミンコフスキー・マハラノビスは、 いずれも「各次元の差 $\Delta_i = x_i-y_i$ をどう重み付けして束ねるか」の違いにすぎません。 上の 🎮 触って理解する の単位球(円・菱形・正方形)は、 まさに「どの方向を優遇するか」の地図です。

距離差の束ね方優遇する方向 / 割り引く方向
ユークリッド (L2)$\sqrt{\sum \Delta_i^2}$全方向を等方的に。 大きい差を二乗で強調
マンハッタン (L1)$\sum |\Delta_i|$全成分が対等。 大きい差の影響が穏やか
ミンコフスキー (Lp)$(\sum |\Delta_i|^p)^{1/p}$$p$ を上げるほど「最大成分」重視(→チェビシェフ)
マハラノビス$\sqrt{\Delta^\top\Sigma^{-1}\Delta}$相関の強い方向を割り引き、 独立な方向を強調

そして「距離」を名乗るための契約書が距離の公理(非負性・同一性・対称性・三角不等式)です。 契約のどれを破るかで呼び名が変わります。

破る公理呼び名身近な例
同一性($d=0 \Rightarrow x=y$ の右向き)擬距離(pseudo-metric)一部の変数だけで測る距離 — 別の県でも距離 0 になりうる
対称性準距離(quasi-metric)KL ダイバージェンス、 一方通行を含む道のり
三角不等式半距離(semi-metric)コサイン距離 $1-\cos\theta$(下の反例参照)

📊 実測(SSDSE-B-2026)— ユークリッドとマハラノビスで「東京の最遠県」が入れ替わる

「相関を無視するユークリッドの誤りをマハラノビスが補正する」を、 実データで一撃で見せる例です。 8 主要列(A1101 総人口/A1303 65歳以上人口/A4101 出生数/A4200 死亡数/A5101 転入/A5102 転出/C3301 着工建築物数/C3302 着工床面積)を z 標準化し、 東京から各県への距離を計算しました。

東京から見て…ユークリッド (L2)マハラノビス
最も遠い県鳥取(14.0)・高知(13.9)・島根(13.9) — 小規模な地方県愛知(9.1)・北海道(9.0)・兵庫(8.5) — 他の大規模県
神奈川県5.627.84
鳥取県13.996.89

💬 読み方:ユークリッドは「東京は鳥取と最も違う」と言い、 マハラノビスは「東京は愛知や北海道のような大規模県と最も違う」と言う。 さらに順位が逆転します — 東京から鳥取は L2 で最遠(14.0)なのにマハラノビスでは 6.89、 逆に神奈川は L2 で最近傍級(5.62)なのにマハラノビスでは 7.84 と鳥取より遠い。 理由:総人口・出生・死亡・移動といった大都市系の 8 軸は互いに強く相関しているため、 マハラノビスは「みんな同じ向きに小さい」という地方県の差を割り引き、 「規模の割に○○が突出」という独立成分の差だけを拾う。 「東京 vs 地方の小ささ」は共分散で説明できるありきたりな差、 「東京 vs 愛知」は共分散で説明しきれない特異な差、 というわけです。

# 最も似た県ペア(同じ8列・z標準化・ユークリッド)実測 鳥取県 と 島根県 : euc = 0.15 ← 全国で最も近い2県(人口規模・構造がほぼ双子) 青森県 と 秋田県 : euc = 0.29

※ 上の値は SSDSE-B-2026.csv(cp932, skiprows=[1], 2023 年 47 県)を z 標準化して算出した実測。 標準化前の生値で計算すると、 桁の大きい総人口・床面積だけで距離がほぼ決まり、 この構造は見えなくなります(=下の落とし穴①)。

⚠️ 落とし穴(補足)— コサイン距離は「距離」ではないことがある

コサイン距離 $d_{\cos}=1-\cos\theta$ は三角不等式を満たさない(半距離)。 これは抽象論ではなく、 2 次元の架空ベクトル 3 つで即座に破れます。

$$x=(1,0),\quad y=(1,1),\quad z=(0,1)\ \text{(架空)}$$ $$d_{\cos}(x,z)=1.000 \;>\; d_{\cos}(x,y)+d_{\cos}(y,z)=0.293+0.293=0.586$$
$x$ と $z$ は直交($\cos=0$)なので距離 1、 だが $y$ を経由すると 0.586 で「近道」になってしまう。 三角不等式 $d(x,z)\le d(x,y)+d(y,z)$ が破れる。

結果として、 コサイン距離では KD-tree・Ball-tree・多くの近似最近傍(三角不等式で探索を刈り込む)が正しく使えない。 回避策は、 (1) ベクトルを L2 正規化してからユークリッド距離を使う(正規化ベクトルでは $\|x-y\|=\sqrt{2\,d_{\cos}}$ の単調変換で、 これは真の距離)、 または (2) 角距離 $d_a=\arccos(\cos\theta)/\pi$ を使う(これは三角不等式を満たす真の距離)。

他の補足的落とし穴:

🚀 発展(補足)— 距離を「決める」から「学習する」へ

ここまでは人間が距離関数を選んできましたが、 現代では距離そのものをデータから学習します。

🔗 関連ページ(補足リンク)

本節の話題に対応する用語ページ(サイト内実在ページのみ):

※ 「マハラノビス距離」の単独ページはサイト内に未整備のため、 本ページ内 関連手法・派生および 数式読み解き節を参照してください。