「distance」は統計データ分析の文脈で扱う重要概念のひとつ。 本ページでは「distance」を取り巻く中核キーワードを以下にチップで一覧化する。 各キーワードは関連する概念・手法・道具立てを含み、 文献検索や学習計画の起点になる。
これらのキーワードは「distance の理解 → 適用 → 検証」のプロセスを構成する。 各章で詳しく解説する。
🍰 まずはやさしく
データの「近さ」を測る定規のようなものです。
似ているものを見つけるために使います。
スマホで好みの曲を探すときなどに役立ちます。
代表的な測り方の種類と選び方を学びます。
2 つの確率分布 $P, Q$ の「違い」を測る指標は機械学習で頻出します(生成モデル、 ベイズ、 強化学習)。
| 場面 | 推奨 |
|---|---|
| 変分推論、 VAE の目的関数 | KL ダイバージェンス |
| GAN(古典) | JS ダイバージェンス |
| WGAN、 分布輸送最適化 | Wasserstein 距離 |
| 確率の最大ズレ評価 | Total Variation |
| 真の距離公理が必要 | Hellinger 距離 |
| マルコフ連鎖の収束証明 | Total Variation または KL |
文字列 $s_1, s_2$ について、 一方を他方に変換するのに必要な 挿入・削除・置換の最小回数:
応用:スペルチェック、 文字列検索、 DNA 配列アライメント、 OCR 誤り訂正、 重複検出(fuzzy matching)。
レーベンシュタインの改良版。 「先頭が一致するほどボーナス」 という人名・住所マッチングに特化した重み付け:
jellyfish.jaro_winkler_similarity("Smith", "Smithe")2 ノード間の エッジ数(重みなし)または重み和(重み付き)。 Dijkstra 法、 BFS、 Floyd-Warshall で計算:
応用:SNS の「友達の友達」、 地図ナビ、 知識グラフ。
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-B-2026 の 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)) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: マハラノビス距離 = 6.53(東京)は「全国の典型パターンから 6.5 標準偏差離れている」という意味。 ① 東京は人口規模・若年層流入で特異、 ② 愛知・大阪・神奈川は大都市圏としての人口・移動規模で特異、 ③ 北海道は広大な面積に人口が分散する構造で特異、 と異なる軸での外れ値がそれぞれ高スコアになる。 単純なユークリッド距離だと「人口の大きい県」だけが上位を占めるが、 マハラノビスは「規模を割り引いた異常度」を測れる。
🍰 まずはやさしく
データ同士の「違い」を数値にする道具です。
分析の基礎として幅広く使われています。
おすすめの商品を表示する仕組みなどで使われます。
なぜこの道具が重要なのかを解説します。
機械学習の教科書を開くと、 こんなフレーズが必ず登場します:
これらは全て 距離・類似度(Distance and Similarity Metrics) の話。 距離は「遠ければ違う、 近ければ似ている」という当たり前の感覚を、 多次元空間で厳密に数値化する道具です。 k-NN、 k-means、 階層的クラスタリング、 推薦システム、 検索エンジン、 異常検知――現代のデータ分析で 距離を使わない手法を探す方が難しい ほどの基礎技術です。
🍰 まずはやさしく
測り方で「遠さ」の結果は変わります。
状況に合った最適な選び方を知るためです。
街を歩くとき、直線か道なりかで距離が違います。
イメージ図を使って測り方の違いを考えます。
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 となる点の集合(単位ボール)を描くと、 距離関数の性格が一目で分かります:
コサイン距離は 「ベクトルの向き」だけ を見ます。 (1, 1) と (10, 10) は方向が完全に同じなのでコサイン距離 = 0(最大限類似)、 ユークリッド距離は大きく離れる。 テキスト分類や推薦のように「文書の長さ」より「単語の比率」が重要な場面 でコサインが活躍します。
マハラノビス距離の最も典型的な応用が 多変量データの外れ値検出。 「単一指標では普通」だが、 「変数の組み合わせで異常」を発見できます。
データ $X \in \mathbb{R}^{n \times d}$ の平均 $\mu$ と共分散 $\Sigma$ を推定し、 各点のマハラノビス距離:
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]) |
| 都道府県 | マハラノビス距離² | p 値 | 解釈 |
|---|---|---|---|
| 東京都 | 15.2 | 0.009 | 人口・所得・進学率が極端 → 外れ値 |
| 沖縄県 | 13.8 | 0.017 | 所得低 × 高齢化率低 という珍しい組み合わせ |
| 秋田県 | 11.4 | 0.043 | 高齢化率最高、 過疎が極端 |
| 愛知県 | 5.1 | 0.402 | 大都市圏内では「典型的」 |
注意:マハラノビス距離は 自分自身を含むデータの共分散から計算するため、 外れ値が共分散推定を歪める「マスキング効果」が問題になることがあります。 対策:Minimum Covariance Determinant (MCD) などロバスト共分散推定を使う。
これまでの議論を踏まえ、 実務で「結局どの距離を使うか」を決めるための徹底ガイドを提供します。 3 つの質問に答えれば、 ほぼ自動的に適切な距離が見つかります。
| データ種類 | 第一選択 | 第二選択 |
|---|---|---|
| 連続値(標準化済) | ユークリッド距離 | マハラノビス距離 |
| 連続値(外れ値あり) | マンハッタン距離 | ロバスト Z + ユークリッド |
| テキスト(TF-IDF や埋め込み) | コサイン類似度 | Pearson 相関 |
| カテゴリ(One-hot エンコード) | ハミング距離 | Jaccard 係数 |
| 集合(タグ、 単語) | Jaccard 係数 | Dice 係数 |
| 文字列・配列 | レーベンシュタイン距離 | Jaro-Winkler |
| 時系列・波形 | DTW | Pearson 相関 + シフト |
| 確率分布 | Wasserstein 距離 | KL ダイバージェンス |
| グラフ・ネットワーク | 最短経路距離 | PageRank 類似度 |
| 地理座標(緯度経度) | Haversine 距離 | 道路網最短経路 |
| 画像(CNN 埋め込み) | コサイン類似度 | L2 距離(ユークリッド) |
| 音声(MFCC) | DTW | コサイン類似度 |
| 症状 | 原因 | 対処 |
|---|---|---|
| 距離が「ある変数」だけで決まる | 標準化漏れ | StandardScaler() 必須 |
| 「全てが等距離」になる | 高次元の呪い | PCA で次元削減、 マンハッタンに変更 |
| 外れ値で結果が激変 | L2 距離が外れ値に弱い | L1 距離 or ロバスト標準化 |
| マハラノビスで NaN や ∞ | 共分散行列が特異 | 擬似逆行列、 Ledoit-Wolf shrinkage |
| 「大きいベクトル」が「常に似ている」 | コサインを使っていない | 方向ベースに切り替え |
| 意味的に近い文書が遠く判定 | 単語の表記揺れ | 埋め込み(BERT)+ コサイン |
$n$ 個のデータ点から作る $n \times n$ 距離行列は、 単にクラスタリングの入力以上の 「データ構造の宝庫」 です。 様々な可視化手法で、 データの構造的特徴が浮き彫りになります。
距離行列を色付きの行列として描画すると、 自然なグループ構造が浮かび上がります。 行・列を クラスタリングで並べ替え(seriation)すると、 対角線付近に「ブロック」が見え、 これがクラスタの存在を視覚的に示します:
seaborn.clustermap で描画し、 階層クラスタリングで行・列を並べ替えて対角ブロックを可視化する。 都市圏・地方圏など類似県の塊が浮かび上がる。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 点 A・B をドラッグ(スマホはタップして指でスライド)すると、 同じ 2 点でも ユークリッド・マンハッタン・チェビシェフ・ミンコフスキーの各距離がどう変わるかをリアルタイムで確認できます。 右の図は「中心から距離 1 の点の集合(=単位球)」で、 距離の種類ごとに形が変わる様子を見られます。
| 距離の種類 | 定義 | 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-means/k 近傍法。 マハラノビス距離は本ページ内「関連手法・派生」節で詳述します。
🍰 まずはやさしく
近さを計算するための数式です。
厳密に値を出すために使います。
テストの点数の差を計算する感覚に似ています。
よく使われる計算式について詳しく読みます。
2 つの $n$ 次元ベクトル $\mathbf{x} = (x_1, \dots, x_n)$, $\mathbf{y} = (y_1, \dots, y_n)$ について:
「高次元では距離が意味を失う」と教科書は言う。 これは本当か?SSDSE データを使って実証してみます。
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 | min 距離 | max 距離 | contrast |
|---|---|---|---|
| 2 | 0.3 | 4.5 | 14.0 |
| 5 | 0.9 | 5.8 | 5.4 |
| 10 | 1.5 | 7.2 | 3.8 |
| 20 | 2.5 | 9.1 | 2.6 |
| 50 | 4.2 | 11.5 | 1.7 |
| 100 | 6.1 | 13.8 | 1.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(時系列) |
緯度・経度の 2 点間距離を地球の曲率を考慮して計算:
2 つの時系列の相関 $\rho$ から距離を作る伝統的方法:
「距離」を測ることは人類の最も古い数学的営みの 1 つ。 その歴史をたどると、 抽象化と一般化の連続です。
| 時期 | 研究・人物 | 意義 |
|---|---|---|
| 紀元前 300 年 | ユークリッド『原論』 | 2 点間最短経路としての「直線距離」を公理化 |
| 17 世紀 | デカルト座標系 | 「距離」を代数的に表現可能に |
| 19 世紀 | 非ユークリッド幾何学(リーマン、 ロバチェフスキー) | 「直線」も「距離」も場によって異なる多様体 |
| 1906 | フレシェ — 抽象距離空間 | 距離公理を満たす任意の集合へ一般化 |
| 1930s | マハラノビス — 統計的距離 | 分布の形を考慮した距離(人類学者集団の比較) |
| 1950 年代 | ハミング — 情報理論の距離 | 誤り訂正符号、 ビット単位距離 |
| 1965 | レーベンシュタイン — 編集距離 | 文字列の違いを編集操作回数で測定 |
| 1970s | 多次元尺度法(MDS)の発展 | 距離行列から座標を復元する手法群 |
| 1990s | カーネル法、 SVM | 「高次元空間での内積 = カーネル」で距離を一般化 |
| 2000s | 距離学習(LMNN、 NCA) | データから「タスクに合った距離」を学習 |
| 2010s | Word2Vec、 BERT 埋め込み | 「意味の距離」を埋め込みで表現 |
| 2020s | 対照学習、 自己教師あり距離学習 | SimCLR、 CLIP、 MoCo — 大規模埋め込み |
現代の機械学習における「距離」は、 もはや幾何学的な意味を超えて、 「ある問いに対するデータ点の類似性を表す抽象的関数」 として捉えられています。 ニューラルネットワークの埋め込み空間でのコサイン類似度は、 「物理的距離」ではなく「意味的近さ」を表現する道具となっています。
距離行列 $D \in \mathbb{R}^{n \times n}$ だけが与えられているとき、 $n$ 個の点を $k$ 次元空間に「距離関係を保つように」配置する方法が 多次元尺度法(Multidimensional Scaling, MDS)。 距離の逆問題と言えます。
古典的 MDS は 距離が正確にユークリッド距離由来なら、 元の座標を正確に復元します。 これは「PCA を距離行列の世界に持ち込んだ」と言っても良い手法で、 PCA と数学的に等価です。
距離の 大小関係(順位)だけ 保存する版。 距離の絶対値が信頼できない場合(アンケート、 心理実験)に有効。 ストレス関数を最小化する反復解法。
sklearn.manifold.MDS で 2 次元埋め込み座標を作成し、 散布図に都道府県名をプロットする。 「東京・神奈川は近く、 沖縄・北海道は遠い」など地理を越えた経済構造の類似がマップで読める。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() |
距離 (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-B-2026 の最新年から 5 指標を抜粋し、 都道府県間の距離を 4 種類(ユークリッド・マンハッタン・コサイン・マハラノビス)で計算してみます。
| 県 | 人口(万人) | 大学進学率(%) | 持家率(%) | 平均所得(万円) | 高齢化率(%) |
|---|---|---|---|---|---|
| 東京 | 1404 | 72.8 | 45.0 | 568 | 22.8 |
| 神奈川 | 923 | 67.5 | 59.3 | 421 | 25.7 |
| 大阪 | 880 | 64.2 | 54.7 | 373 | 27.9 |
| 秋田 | 93 | 49.8 | 78.4 | 248 | 39.1 |
| 沖縄 | 147 | 40.5 | 44.4 | 234 | 22.8 |
| 愛知 | 748 | 61.6 | 57.6 | 389 | 25.6 |
「人口(万人)」と「進学率(%)」は桁が違いすぎるので、 そのまま距離を取ると人口だけで決まってしまいます。 各指標を Z 標準化:
| 東京 vs | ユークリッド | マンハッタン | コサイン距離 | マハラノビス |
|---|---|---|---|---|
| 神奈川 | 1.42 | 2.95 | 0.16 | 2.10 |
| 大阪 | 1.83 | 3.66 | 0.39 | 2.61 |
| 愛知 | 2.18 | 4.32 | 0.45 | 2.85 |
| 沖縄 | 3.27 | 6.81 | 0.82 | 4.18 |
| 秋田 | 4.81 | 9.97 | 1.79 | 5.92 |
解釈の差異:ユークリッド・マンハッタン・マハラノビスはほぼ同じ順位を返す。 一方、 コサイン距離は「ベクトルの向き」だけを見るので、 沖縄(小規模・低所得+若年比率)と東京の差を強調する。 用途に応じて選ぶこと。
これまでの議論を踏まえ、 SSDSE-B-2026 から得られる 47×47 距離行列 を 4 種類すべてで計算し、 結果を比較する End-to-End パイプラインを示します。
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() |
| 距離関数 | 東京の Top 5 最近傍 | 解釈 |
|---|---|---|
| ユークリッド | 神奈川, 大阪, 愛知, 千葉, 埼玉 | 大都市プロファイル。 量と質を総合 |
| マンハッタン | 神奈川, 大阪, 愛知, 千葉, 兵庫 | 外れ値の影響が少ない順位 |
| コサイン | 大阪, 京都, 福岡, 神奈川, 愛知 | 「都市型ライフスタイル」の方向性で選定 |
| マハラノビス | 神奈川, 千葉, 埼玉, 大阪, 愛知 | 共分散考慮で「首都圏」をより強く識別 |
| Euc | Man | Cos | Cheb | Mah | |
|---|---|---|---|---|---|
| Euc | 1.00 | 0.97 | 0.78 | 0.92 | 0.81 |
| Man | 0.97 | 1.00 | 0.79 | 0.86 | 0.83 |
| Cos | 0.78 | 0.79 | 1.00 | 0.72 | 0.65 |
| Cheb | 0.92 | 0.86 | 0.72 | 1.00 | 0.76 |
| Mah | 0.81 | 0.83 | 0.65 | 0.76 | 1.00 |
解釈:ユークリッドとマンハッタンは r ≈ 0.97 とほぼ同等。 コサインは他の距離と r ≈ 0.7〜0.8 でやや異なる順位を返す。 マハラノビスは独自性が最も強く、 共分散構造の取り込みが効いている。
同じ都道府県データに対し、 ユークリッド・マンハッタン・コサイン・チェビシェフの 4 つの距離を計算し、 「東京の最近傍 Top 5」がどう変わるかを観察する。 距離選択が結果に与える影響を一望できる。
📥 使うデータ:
4 距離の最近傍 Top 5(東京から):
| 順位 | ユークリッド (L2) | マンハッタン (L1) | コサイン | チェビシェフ (L∞) |
|---|---|---|---|---|
| 1 | 神奈川県 | 神奈川県 | 神奈川県 | 神奈川県 |
| 2 | 大阪府 | 大阪府 | 千葉県 | 埼玉県 |
| 3 | 埼玉県 | 埼玉県 | 福岡県 | 大阪府 |
| 4 | 愛知県 | 愛知県 | 埼玉県 | 千葉県 |
| 5 | 千葉県 | 千葉県 | 大阪府 | 愛知県 |
💬 読み方: ① L2 と L1 は同じ結果(首都圏 3 県+大阪・愛知)、 ② コサインは「ベクトル方向」を測るので順位が入れ替わり、愛知が top5 から外れて福岡が入る、 ③ L∞ は「最大成分の差」だけを見るので顔ぶれは L2 と同じだが順位が入れ替わる。 SSDSE の都道府県データのように「全変数が人口比例で動く」ケースでは、 L1/L2/L∞ は近い結果を返すが、 コサインだけが本質的に違う情報を返す。
合成 2D 点 p=(1,2), q=(4,6) でユークリッド/マンハッタン/チェビシェフ距離を計算する。
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()}") |
💬 手計算 (Step 2) 5/7/4 と Python 出力が完全一致。
scipy.spatial.distance.pdist + squareform で 47 都道府県の 5 種類距離行列(ユークリッド・マンハッタン・コサイン・チェビシェフ・マハラノビス)を一括計算する。 標準化済みデータを使い、 東京と類似度の高い県を Top10 で出力する。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)) |
sklearn.metrics.pairwise の euclidean_distances/manhattan_distances/cosine_distances で距離行列を計算し、 東京都インデックスから「最近傍 5 県」と「最遠 5 県」を np.argsort で抽出する。 大規模データで高速計算したい場合の定番。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 で共分散行列 Σ とその逆行列を明示的に渡し、 「変数間の相関を考慮した距離」を計算する。 ユークリッド距離との違いを並べて表示し、 相関が強い指標で結果がどう変わるか比較する。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)) |
scipy.spatial.distance.hamming で「異なる位置の割合」を計算する。 数値距離では測れない名義尺度データの類似性に使う。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)) |
matplotlib.imshow でヒートマップ描画し、 県を地方区分順(北海道→九州)に並べる。 対角線付近の青いブロック(似た県同士)と赤い領域(遠い県同士)が地域的構造を反映していることを確認する。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() |
🎯 このコードでやること: SSDSE-B-2026 の都道府県データを標準化し、 ユークリッド距離行列を計算した上で scipy.cluster.hierarchy.linkage で階層クラスタリングし、 dendrogram で 47 都道府県の関係を可視化する。 距離選択が結果(cluster tree の形)にどう影響するかを実感できる。
📥 入力データ:
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])) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: ユークリッド距離 + Ward 法での 5 群分割は、 ほぼ「人口規模」階層に対応する。 東京(特異点=単独クラスタ)、 大都市圏(埼玉・千葉・神奈川・愛知・大阪)、 政令市を持つ大規模県(北海道・兵庫・福岡)、 地方中核県、 人口減少地域、 という階層が自然に現れる。 もしコサイン距離を使えば「規模」が消えて「人口構造のプロファイル」で分類されるので、 沖縄(若年層多)と東京(一人世帯多)が異なる cluster に分かれる、 などの結果になる。 距離関数の選択 = 分類軸の選択である。
🎯 このコードでやること: SSDSE 47 都道府県の距離行列を heatmap で可視化。 seaborn の clustermap を使うと、 距離が近い県が自動的に隣り合うようにソートされ、 構造が一目で分かる。
📥 入力データ:
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]) |
📤 実行すると次の出力が得られる:
💬 結果の読み方: ① 東京の最近傍は首都圏 + 大阪・愛知(大都市圏)、 ② 最遠は人口最小級の県。 clustermap を見ると「大都市圏」「地方中核」「人口減少地域」の 3 群が明瞭に浮かび上がる。 距離行列の可視化は、 数値的な分析の前段階として「データの全体像を掴む」最高の手段。
A. 厳密には「コサイン類似度」が cos θ を返し、 1 - cos θ を「コサイン距離」と呼ぶ。 しかしこれは三角不等式を満たさないので、 数学的には「距離」ではなく「非類似度」。 距離公理を満たすバージョンが必要なら arccos(s)(角距離)を使う。
A. データが少ない(n=47)と共分散行列の推定が不安定になる。 d=8 程度なら問題ないが、 d > 20 になると np.linalg.pinv の擬似逆行列を使うか、 縮約推定(Ledoit-Wolf shrinkage)を併用すべき。 SSDSE で 5〜10 列なら普通のマハラノビスで OK。
A. K-means の「平均」操作はユークリッド距離の最適化と数学的に対応するので、 マンハッタンを使いたいなら K-medoids(中央値ベース)か、 sklearn の KMeans を捨てて自前実装する必要がある。 簡単にはマンハッタン版を使いたいなら sklearn_extra.cluster.KMedoids を試す。
A. これらは「2 つの確率分布間の距離」で、 「一方の分布を他方に変えるコスト」と解釈される。 SSDSE で「東京の年齢分布」と「沖縄の年齢分布」を比べたいとき、 通常のユークリッド・コサインだと意味が薄いが、 Wasserstein なら「年齢構成のどれだけ動かす必要があるか」を直接測れる。 scipy.stats.wasserstein_distance で計算可能。
A. Levenshtein 距離(編集距離)が最も基本。 「東京都」と「東京府」のように 1 文字違いは距離 1。 python-Levenshtein ライブラリで計算できる。 単語レベルの類似度なら Jaccard 係数(共通単語 / 全単語)、 文章レベルなら cosine + TF-IDF が標準。
距離・類似度はデータサイエンスの「物差し」。 何を測るかが分析の根幹を決める。 SSDSE-B-2026 のような小規模・多変量データでは、 ① 標準化、 ② ユークリッドかコサインの選択、 ③ 可視化、 の 3 ステップを基本に据えれば、 多くの分析が安全に動く。
距離関数の代表 (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 年抜粋):
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)}') |
📤 実行例:
💬 標準化により各変数の平均 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]) |
📤 実行例:
💬 上位 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 では順位が大きく変わり、 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}') |
📤 実行例:
💬 規模を無視するとほぼ全ての県が東京と「ベクトル方向が似ている」(最小でも 0.99995)。 これは 3 変数(総人口・出生数・死亡数)がいずれも人口規模と強く相関しているためで、 向きだけを見る Cosine では県の差がほとんど出ず、 ここでは Euclidean を使うべき事例です。 距離尺度の選択は分析目的次第と痛感できます。
ほとんどの入門書がユークリッド距離から始めるため、 「何も指定しなければユークリッド」と思い込みがち。 SSDSE のように「人口」のスケール差が 100 倍ある変数では、 標準化前のユークリッドは人口列だけが効くという問題に直結する。 対処: 距離を計算する前に必ず StandardScaler を通す。
高次元(d > 50)では「全ペアの距離がほぼ同じ」になる現象が起きる(curse of dimensionality)。 SSDSE は 125 列あるので、 全列を入力すると最近傍検索が無意味化する。 対処: PCA・特徴選択で次元を 10〜20 に落としてから距離計算。
コサイン類似度は「方向だけ」を見るので「スケール非依存」とよく言われるが、 これは「全変数の同時スケーリング」に限った話。 個別変数の単位が違うと(人口 vs 出生率など)、 やはり標準化が必要。 対処: コサインでも StandardScaler は省略しない。
「東京 → 大阪」と「大阪 → 東京」を別の値として保存している場合(pandas の groupby 結果など)、 距離公理の対称性が壊れて多くの手法が動かない。 対処: squareform や (D + D.T) / 2 で対称化する。
距離関数は欠損値を許容しない。 SSDSE のように一部欠損がある列を扱うと、 行を削除(dropna)するか補完(fillna)する必要がある。 不適切な補完は距離行列を歪める。 対処: 欠損率の高い列は除外、 残った欠損は中央値補完を試す。
「県コード」を数値として距離計算に入れると、 「北海道(01) と 沖縄(47) は遠い」「北海道(01) と 青森(02) は近い」という無意味な距離が生まれる。 対処: カテゴリ変数は除外するか one-hot encoding 後に Hamming 距離など適切な距離を使う。
どの距離を使えばよいか迷ったら、 以下を上から順に確認する:
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)の特殊例として:
古典的距離(ユークリッド等)は 「全次元を平等に扱う」 ため、 タスクに関係ない次元のノイズに弱い。 たとえば顔認証で 背景の色 は顔の同一性と無関係なのに、 そのまま距離計算すると誤判定の原因になる。 距離学習はこれを克服します。
| 手法 | 仕組み | 応用 |
|---|---|---|
| LMNN(Large Margin Nearest Neighbor) | 同クラス点を近く、 異クラス点を遠くするマハラノビス距離を最適化 | k-NN 改良 |
| NCA(Neighborhood Components Analysis) | 確率的近傍分類の精度を最大化 | 次元削減 + 距離学習 |
| Siamese Network | 2 入力を共有 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 の挙動を決定づける。
「距離・類似度」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。
このフローに沿って判断することで、 「距離・類似度」を中核とした適切な手法選択ができる。
本節は既存の解説を壊さない追記の補足です。 上の各章で触れた「直感/落とし穴/発展」を、 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$(下の反例参照) |
「相関を無視するユークリッドの誤りをマハラノビスが補正する」を、 実データで一撃で見せる例です。 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.62 | 7.84 |
| 鳥取県 | 13.99 | 6.89 |
💬 読み方:ユークリッドは「東京は鳥取と最も違う」と言い、 マハラノビスは「東京は愛知や北海道のような大規模県と最も違う」と言う。 さらに順位が逆転します — 東京から鳥取は L2 で最遠(14.0)なのにマハラノビスでは 6.89、 逆に神奈川は L2 で最近傍級(5.62)なのにマハラノビスでは 7.84 と鳥取より遠い。 理由:総人口・出生・死亡・移動といった大都市系の 8 軸は互いに強く相関しているため、 マハラノビスは「みんな同じ向きに小さい」という地方県の差を割り引き、 「規模の割に○○が突出」という独立成分の差だけを拾う。 「東京 vs 地方の小ささ」は共分散で説明できるありきたりな差、 「東京 vs 愛知」は共分散で説明しきれない特異な差、 というわけです。
※ 上の値は SSDSE-B-2026.csv(cp932, skiprows=[1], 2023 年 47 県)を z 標準化して算出した実測。 標準化前の生値で計算すると、 桁の大きい総人口・床面積だけで距離がほぼ決まり、 この構造は見えなくなります(=下の落とし穴①)。
コサイン距離 $d_{\cos}=1-\cos\theta$ は三角不等式を満たさない(半距離)。 これは抽象論ではなく、 2 次元の架空ベクトル 3 つで即座に破れます。
結果として、 コサイン距離では KD-tree・Ball-tree・多くの近似最近傍(三角不等式で探索を刈り込む)が正しく使えない。 回避策は、 (1) ベクトルを L2 正規化してからユークリッド距離を使う(正規化ベクトルでは $\|x-y\|=\sqrt{2\,d_{\cos}}$ の単調変換で、 これは真の距離)、 または (2) 角距離 $d_a=\arccos(\cos\theta)/\pi$ を使う(これは三角不等式を満たす真の距離)。
他の補足的落とし穴:
ここまでは人間が距離関数を選んできましたが、 現代では距離そのものをデータから学習します。
本節の話題に対応する用語ページ(サイト内実在ページのみ):
※ 「マハラノビス距離」の単独ページはサイト内に未整備のため、 本ページ内 関連手法・派生および 数式読み解き節を参照してください。