クラスタ数を指定不要・外れ値を自動で識別 できる。🍰 まずはやさしく
データの密度でグループを作る方法です。
自然なまとまりを見つけるために使います。
スマホのGPSで人の集まりを探すようなものです。
この手法の特徴と設定について読みましょう。
🍰 まずはやさしく
データの密度に注目する手法です。
不規則な形のグループを見つけるために使います。
部活のメンバーがバラバラに集まる様子に似ています。
他の手法と何が違うのかを詳しく解説します。
論文の手法欄で、こんな記述を見たことがあるはずです:
この DBSCAN は、 k-means や階層的クラスタリングとは 発想がまったく違うクラスタリング手法 です。 「データ点の密度」だけで自然なまとまりを見つけ、 例外的に孤立した点を「noise」として除外 します。 GPS 軌跡・画像解析・地理空間データ・ソーシャルネットワーク分析など、 不規則な形状や外れ値を含むデータで特に強力です。
🍰 まずはやさしく
人だかりを見つける魔法のような仕組みです。
密集している場所をグループにするために使います。
駅前広場に人が集まっている様子を想像してください。
点を3つの種類に分ける考え方を学びましょう。
夜の繁華街を上空から見るところを想像してください。 ある場所には人が密集しています(駅前広場、 ライブ会場、 屋台)。 別の場所はぽつぽつとしか歩いていません(住宅街、 公園の隅)。 DBSCAN は「人がたくさんいる場所」を 1 つのかたまりと認識し、 「ぽつんと孤立した人」は「群衆ではない」と分離する アルゴリズムです。
DBSCAN は、 すべてのデータ点を次の 3 種類に分類します:
| 種類 | 定義 | 例えるなら |
|---|---|---|
| コア点 (Core point) |
半径 ε 以内に minPts 個以上 の点が存在する点 | 「人だかりの真ん中で、 周りが密集している人」 |
| 境界点 (Border point) |
コア点ではないが、 あるコア点の ε 以内 にいる点 | 「人だかりの周縁にいて、 中の人と肩が触れている人」 |
| 雑音点 (Noise point) |
コア点でも境界点でもない点(誰のクラスタにも属さない) | 「ぽつんと一人で離れて立っている人」 → 外れ値扱い |
コア点同士で「半径 ε 以内」に到達できる関係を全部つないでいくと、 1 つのクラスタが浮かび上がります。 その周りに境界点をくっつければ完成。 残った点はすべて noise です。
下の散布図は 非凸(三日月)形状の2つのかたまり+孤立点 を初期配置しています。 ε(近傍半径)と minPts(コア点に必要な近傍点数・自分含む)の2本のスライダーを動かすと、 各点が コア点・境界点・ノイズ点 に再分類され、 クラスタの着色がリアルタイムに変わります。 図の上でマウスを動かすと、 その近くの点を中心とした 半径 ε の円 が表示されます。 クリックまたはドラッグで点を追加できます(スマホはタッチ/なぞりで追加)。
試してみてほしい操作:(1) ε を小さくすると近傍が痩せてコア点が減り、 ノイズが増える(クラスタが細切れに)。 (2) ε を大きくすると2つの三日月がつながって「1クラスタ」に融合する。 (3) minPts を上げるとコア判定が厳しくなり、 まばらな縁が境界点→ノイズへ転落する。 (4) 2つの塊の間にドラッグで「橋」を架けると、 密度連結によって別々だったクラスタが1つに合体する(=密度でつながる、というDBSCANの核心)。
関連ページ: k-means(重心ベースとの対比) / クラスタリング全体像 / 外れ値 / ノイズ / 標準化 / PCA / 次元削減。
🍰 まずはやさしく
密度を数式で決めるルールです。
グループの境界線を厳密に決めるために使います。
買い物客がどれくらい近くにいるかを測るようなものです。
半径や点数の条件について詳しく説明します。
データ集合 $D = \{x_1, x_2, \dots, x_n\} \subset \mathbb{R}^d$ と 2 つのパラメータ ($\varepsilon, \text{minPts}$) を考えます。
minPts = 4 程度、 一般的には minPts ≥ d + 1($d$ は特徴量の次元数)を推奨。metric 引数でマンハッタン距離・コサイン距離なども選択可。labels_ 配列で -1 として返される。$N_{1.5}(p) = \{q \in D \mid \mathrm{dist}(p, q) \le 1.5\}$ の要素数が $\ge 3$ であること(自分 $p$ を含む)。つまり $p$ から半径 1.5 以内に自分含め 3 点以上が存在すれば、$p$ はコア点となりクラスタの起点になれる。
$\varepsilon$ が大きくなると各点の $N_\varepsilon(p)$ が広がり、より多くの点が $\text{minPts}$ 条件を満たしコア点になる。その結果 noise に分類される点は減少する。極端に大きいと全点が 1 クラスタになる。
SSDSE-B(2026 年版・2023 年)の 「総人口 A1101」 ($x$) と 「着工新設住宅戸数 H1800」 ($y$) の 2 変数で、 47 都道府県を DBSCAN でクラスタリングします。 住宅着工戸数は都市の規模・経済活動を反映する量で、 人口とは完全には比例しないため 2 次元の散らばりが生まれます。 まずは 7 県だけのサンプルで手計算してみましょう(値は 47 県全体で標準化した z 得点)。
A1101 総人口 と H1800 着工新設住宅戸数(2023 年)を StandardScaler で標準化し、 実際に DBSCAN を回して得た値です。 住宅着工戸数は人口とは別の「都市の建設・経済活動」の量なので、 単純な人口の比例関係にならず 2 次元散布として意味を持ちます。 表・出力・ラベル配列はすべて実 CSV から再計算した実測値です。
| 都道府県 | 総人口 $z_x$(標準化) | 住宅着工戸数 $z_y$(標準化) | 位置イメージ |
|---|---|---|---|
| 東京 | +4.13 | +4.73 | 右上の極端な外れ値 |
| 神奈川 | +2.38 | +2.09 | 右上中央 |
| 大阪 | +2.21 | +2.14 | 右上中央 |
| 愛知 | +1.75 | +1.75 | 右上中央 |
| 福岡 | +0.89 | +0.84 | 中央付近 |
| 広島 | +0.03 | −0.04 | 中央付近 |
| 島根 | −0.72 | −0.61 | 左下小規模県 |
ポイント:DBSCAN の結果は ε と minPts の設定に強く依存します。 上の 7 県例では ε=0.8 で {神奈川・大阪・愛知} がまとまりましたが、 ε=0.5 まで狭めると近傍が 0.5 以内に収まるのは神奈川–大阪の 1 ペアだけになり、 minPts=3 を満たせず 7 県すべてが noise になります。 「最適な ε」を探すには次節の k-distance graph を使います。
都道府県データの前に、 より小さな 6 点で DBSCAN の動作を完全に追います。 クラスタリング対象 6 点(2 次元):
[0, 0], [1, 1], [1, 0], [2, 1], [8, 8], [9, 9]
パラメータ: $\varepsilon = 1.5$、 $\text{minPts} = 2$
[0, 0], [1, 1], [1, 0], [2, 1] → ラベル 0、
[8, 8], [9, 9] → ラベル 1[0, 0, 0, 0, 1, 1]
Python で同じ 6 点を DBSCAN にかけて確認する:
このコードでやること:上の手計算と同じ 6 点 [0,0],[1,1],[1,0],[2,1],[8,8],[9,9] を sklearn.cluster.DBSCAN にかけ、 手計算ラベルと一致するかを検証する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import numpy as np
from sklearn.cluster import DBSCAN
# 6 点の座標(手計算と同じ)
X = np.array([
[0, 0], # 点 A
[1, 1], # 点 B
[1, 0], # 点 C
[2, 1], # 点 D
[8, 8], # 点 E
[9, 9], # 点 F
])
db = DBSCAN(eps=1.5, min_samples=2).fit(X)
labels = db.labels_
print("Python ラベル:", labels)
hand = np.array([0, 0, 0, 0, 1, 1])
print("手計算ラベル:", hand)
print("一致確認:", np.all(labels == hand))
|
💬 手計算ラベル [0, 0, 0, 0, 1, 1] と Python 出力が一致。 np.all(labels == hand) = True により、 Step 1〜3 の手計算が正しく、 sklearn の DBSCAN も同じ結果を返すことが確認できた。 6 点のうち noise 点はゼロで、 2 つの密なクラスタがきれいに分離された。
scikit-learn の DBSCAN クラスを使えば数行で実行できます。 標準化 を忘れないこと。
📥 入力:SSDSE-B-2026.csv から 47 都道府県の数値列(人口 A1101・面積 B1101・医師数 I521101 など)を選び、 StandardScaler で平均 0・分散 1 に標準化した 2D 配列 X(shape=(47, k))。 距離計算が単位(人・km² 等)に依存しないようにします。
📤 出力:db.labels_ として 47 要素の整数配列。 値が -1 のサンプルは「ノイズ点」(クラスタに属さない孤立県)、 0・1・2... は順にクラスタ番号。 東京・大阪などの大都市は密度が低い領域に孤立するため -1 になりやすく、 地方の中規模県が同じクラスタに集まりやすい。
💬 narration:eps(近傍半径)と min_samples(コア点を成立させる隣接数)の決め方が最大の難所。 標準化後の SSDSE-B では eps=0.7〜1.5、 min_samples=3〜5 がしばしば適切。 NearestNeighbors で k 番目最近傍距離をプロット(k-distance graph)、 「肘」を取って eps を決めるのが王道。 シードを指定する必要がない(K-means と異なり乱数初期化に依存しない)ため再現性が高いのも特徴です。
このコードでやること:SSDSE-B-2026 から総人口 (A1101) と着工新設住宅戸数 (H1800) の 2 列を読み込み、StandardScaler で標準化後に DBSCAN(eps=0.5, min_samples=4) を実行。クラスタ数・noise 件数と noise 県の一覧を表示する。
📥 入力データ(SSDSE-B-2026、 2023 年・先頭 5 県):
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 sklearn.cluster import DBSCAN from sklearn.preprocessing import StandardScaler # SSDSE-B 2026 を読み込む(cp932・2 行目の日本語名は skiprows で除外) df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # 2023 年だけに絞り、必要 2 変数を抜き出す(先頭列名は 'SSDSE-B-2026' = 年度) df_23 = df[df['SSDSE-B-2026'] == 2023].copy() X = df_23[['A1101', 'H1800']].values # 総人口, 着工新設住宅戸数(戸) # 標準化(必須!) X_std = StandardScaler().fit_transform(X) # DBSCAN 実行 db = DBSCAN(eps=0.5, min_samples=4).fit(X_std) labels = db.labels_ # クラスタ数(noise=−1 は除く)と noise 数 n_clusters = len(set(labels)) - (1 if -1 in labels else 0) n_noise = list(labels).count(-1) print(f'クラスタ数: {n_clusters}, noise: {n_noise}件') # どの県が noise か df_23['cluster'] = labels print(df_23[df_23['cluster'] == -1][['Prefecture', 'A1101', 'H1800']]) |
📤 実行すると次の出力が得られる:
💬 eps=0.5 では人口・住宅着工がともに突出する大都市圏 9 県(東京・神奈川・大阪・愛知・埼玉・千葉・兵庫・福岡・北海道)が noise(-1) となり、残る 38 県が cluster 0 にまとまる。eps を 0.8 に緩めると noise は東京 1 県だけに減り、他の大都市圏はクラスタに合流する。
「ε をいくつにすればよいか」迷ったときは、 k-distance graph(k 番目最近傍点までの距離の昇順プロット)の 肘 (elbow) を ε に取るのが定石です。
このコードでやること:標準化済み X_std に NearestNeighbors(k=4) を当て、各点の 4 番目最近傍距離を昇順プロットする。急に立ち上がる「肘」の高さが eps の目安。
📥 入力データ(コード 1 で作成した X_std、標準化済み 47×2 行列の冒頭):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from sklearn.neighbors import NearestNeighbors import matplotlib.pyplot as plt k = 4 # minPts と同じ値が定番 nn = NearestNeighbors(n_neighbors=k).fit(X_std) dist, _ = nn.kneighbors(X_std) k_dist = np.sort(dist[:, k-1]) # k番目最近傍までの距離を昇順ソート plt.figure(figsize=(8, 4)) plt.plot(k_dist) plt.xlabel('Points sorted by distance') plt.ylabel(f'{k}-NN distance') plt.title('k-distance graph (elbow = eps)') plt.grid(True, alpha=0.3) plt.show() # 急に立ち上がる点(肘)の y 座標を eps に採用する |
📤 実行すると次の出力が得られる(グラフ表示):
💬 肘の位置が eps の目安。中央値付近(約 0.06)はクラスタ内部の密な間隔、急上昇する 0.4〜0.5 が「密な塊」と「孤立した大都市」の境目。0.5 以下では noise が急増し、0.8 以上では全体がほぼ 1 クラスタになる。47 県では eps=0.5〜0.8 が実用域。
このコードでやること:DBSCAN の labels_ を使い、クラスタ番号ごとに散布図を色分けして表示。noise(−1) は黒で描画し、どの県が外れ値かを視覚化する。
📥 入力データ(コード 1 の出力 labels を使用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(9, 6)) colors = ['#1976D2', '#E64A19', '#388E3C', '#7B1FA2', '#FBC02D'] for lbl in set(labels): mask = labels == lbl color = '#333' if lbl == -1 else colors[lbl % len(colors)] name = 'noise' if lbl == -1 else f'cluster {lbl}' ax.scatter(X_std[mask, 0], X_std[mask, 1], c=color, s=90, alpha=0.85, label=name, edgecolor='white', linewidth=1.2) ax.set_xlabel('総人口(標準化)') ax.set_ylabel('着工新設住宅戸数(標準化)') ax.legend(loc='lower right') ax.set_title('DBSCAN: 47 都道府県の密度ベースクラスタリング') plt.tight_layout() plt.show() |
📤 実行すると次の出力が得られる(散布図表示):
💬 東京は標準化後でも (x=+4.13, y=+4.73) と極端に離れており、noise として明確に分離される。散布図で「孤立した点 = 異質な県」が一目でわかる。
同じデータでも (ε, minPts) を変えると結果は大きく変わります。 感度を確認しましょう。
このコードでやること:eps=[0.3, 0.5, 0.8, 1.0] × minPts=[3, 4, 5] のグリッドで DBSCAN を全試行し、各設定のnoise 数をピボット表で比較する。
📥 入力データ(コード 1 の X_std を継続使用):
1 2 3 4 5 6 7 8 9 10 11 12 | from itertools import product results = [] for eps, mp in product([0.3, 0.5, 0.8, 1.0], [3, 4, 5]): db = DBSCAN(eps=eps, min_samples=mp).fit(X_std) lab = db.labels_ n_cl = len(set(lab)) - (1 if -1 in lab else 0) n_no = int((lab == -1).sum()) results.append({'eps': eps, 'minPts': mp, 'クラスタ数': n_cl, 'noise数': n_no}) print(pd.DataFrame(results).pivot(index='eps', columns='minPts', values='noise数')) |
📤 実行すると次の出力が得られる:
💬 eps=0.3 では地方の中規模県まで noise に落ちて 10 件、eps=0.8 以上では東京だけが noise として残り 1 件に収束する。eps=0.5 は minPts の増加に敏感(3→6 件、4→9 件)で、外れ値をどこまで拾うかを minPts で調整できる。eps=0.5〜0.8 が「大都市圏を外れ値として検出しつつ地方圏を 1 クラスタに保つ」バランスの良い範囲。
このコードでやること:noise(−1) を除いたクラスタ点だけを対象に silhouette_score を計算し、クラスタの内部結束と分離度を数値化する。
📥 入力データ(コード 1 の X_std と labels を継続使用):
1 2 3 4 5 6 7 8 9 | from sklearn.metrics import silhouette_score # noise を除く点だけで評価する mask = labels != -1 if len(set(labels[mask])) >= 2: s = silhouette_score(X_std[mask], labels[mask]) print(f'silhouette = {s:.3f}') else: print('クラスタ数が 2 未満: シルエット計算不可') |
📤 実行すると次の出力が得られる(eps=0.5, min_samples=4 のとき):
💬 このデータ(総人口・住宅着工の 2 変数)では eps を変えても地方圏が 1 つの密な塊にまとまるため、有効クラスタは常に 1 個で silhouette は計算できない。複数クラスタを得たい場合は、 人口当たりの指標(合計特殊出生率 A4103・高齢化率など)や気候(年平均気温 B4101)といった「規模とは独立した軸」を特徴量に加える。 そうすれば地方圏が複数の性格別クラスタに分かれ、 silhouette で分離の質を評価できるようになる。 いずれの場合も noise(−1) を除いて計算するのが鉄則。
StandardScaler や MinMaxScaler で同じスケールに揃える こと。 ε もスケール後の単位で考える。
OPTICS がある。
min_samples ≈ 2d(d は次元数)。
algorithm='ball_tree' や kd_tree を指定すると $O(n \log n)$ に近くなる。 さらに大規模なら GPU 版 cuML、 分散版 ELKI を検討。
| 手法 | 特徴 | DBSCAN との関係 |
|---|---|---|
| OPTICS | 各点の到達可能距離をプロットし、 「ε を 1 つに決めずに」階層的に密度クラスタを抽出 | 密度が不均一なデータに強い。 sklearn にあり |
| HDBSCAN | 階層的 DBSCAN。 局所的に最適な ε を自動で見つける | 近年の実務での 第一選択。 hdbscan パッケージ |
| Mean Shift | 密度の山(モード)に向かって点を移動させる | 密度ベースだが、 noise 概念は持たない |
| Gaussian Mixture Model (GMM) | 混合ガウス分布で確率的にクラスタ割り当て | パラメトリック。 球形〜楕円形クラスタに向く |
| k-means | 重心ベース、 K を事前指定 | 球形クラスタに最適。 DBSCAN とよく比較される |
| 階層的クラスタリング | 樹形図(dendrogram)で階層を可視化 | 小規模データで構造を可視化したいとき有用 |
DBSCAN を含むクラスタリング手法を体系的に学ぶには、 以下のグループ教材が役立ちます:
DBSCAN が機械学習・クラスタリングの体系の中でどこに位置し、他の手法・概念とどう繋がっているかを関係図で示す。
💬 DBSCAN(赤)はクラスタリング体系の一員で、 k-means・階層的クラスタリングと並列する手法。 標準化・距離計算が前提となり、 HDBSCAN・OPTICS へ発展、 外れ値検出・GPS 分析への応用につながる。
| 階層 | 概念 | 関係 |
|---|---|---|
| 上位 | 教師なし学習 → クラスタリング | DBSCAN はこのカテゴリの 1 手法 |
| 同列 | k-means, 階層的, GMM, Mean Shift | 同じ「クラスタリング」だが原理が異なる |
| 下位(発展) | OPTICS, HDBSCAN | DBSCAN の弱点(密度不均一性)を改良 |
| 前提 | 距離計算、 標準化、 ハイパーパラメータ | どれが欠けても結果が崩れる |
| 応用 | 外れ値検出、 異常検知、 GPS 軌跡分析 | noise ラベルをそのまま活用 |
本ページで扱った DBSCAN の知識を 1 つに集約:
DBSCAN は適切に使えば本当に強力な手法です。 SSDSE-B-2026 を題材に、 自分の手で何度も実行することが、 真の習得への近道です。
SSDSE のような統計データに限らず、 業務システムの異常検知でも DBSCAN は強力:
SSDSE-B-2026 の 12 年分(2012-2023)を時間スライスごとに DBSCAN し、 都道府県の「クラスタ系列」を作る分析:
HDBSCAN は DBSCAN を「複数の eps で同時実行」した結果を階層化したもの:
SSDSE-B-2026 47 都道府県では min_cluster_size=3 程度を起点に試すと、 DBSCAN より安定したクラスタが得られることが多いです。
OPTICS は eps を 1 つに固定せず、 「到達可能性距離」で全点を順序付けることで、 任意の eps に対応できる点で DBSCAN を超えています:
OPTICS(D, eps, MinPts):
for each point p in D:
p.reachability = UNDEFINED
ordered_list = []
for each unprocessed point p:
Neighbors = regionQuery(p, eps)
p.processed = TRUE
ordered_list.append(p)
if core_distance(p, eps, MinPts) != UNDEFINED:
seeds = empty priority queue
update(Neighbors, p, seeds)
while seeds is not empty:
q = seeds.pop_min()
Neighbors' = regionQuery(q, eps)
q.processed = TRUE
ordered_list.append(q)
if core_distance(q, eps, MinPts) != UNDEFINED:
update(Neighbors', q, seeds)
return ordered_list // 各点の到達可能性距離付き
結果として得られる「reachability plot」を見ると、 谷の数 = クラスタ数、 谷の深さ = クラスタの密度が一目瞭然です。
DBSCAN の局所密度概念は、 確率密度関数 $p(x)$ のカーネル密度推定 (KDE) と等価です:
$$\hat{p}(x) = \frac{1}{n h^d} \sum_{i=1}^{n} K\left(\frac{x - x_i}{h}\right)$$
ここで $K$ はカーネル関数、 $h$ はバンド幅、 $d$ は次元数。 DBSCAN の eps は KDE の $h$ に相当し、 min_samples は閾値 $\theta$ に対応します。 局所密度が閾値を超える領域=クラスタ、 という単純なルールが、 DBSCAN の数学的根拠です。
HDBSCAN ではこの $\theta$ を連続的に変化させて階層構造を抽出。 統計的に「確率密度の上位レベル集合」の階層を取り出すことに対応します。
本ページ「🐍 Python 実装」の数行コードを補足する詳細フロー:
pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) で読み込みdf[['Prefecture','A1101','B1101','I521101']]from sklearn.preprocessing import StandardScaler; X = StandardScaler().fit_transform(df.drop('Prefecture', axis=1))from sklearn.neighbors import NearestNeighbors; nbrs = NearestNeighbors(n_neighbors=5).fit(X); dist, _ = nbrs.kneighbors(X); import matplotlib.pyplot as plt; plt.plot(sorted(dist[:,-1]))from sklearn.cluster import DBSCAN; labels = DBSCAN(eps=1.0, min_samples=5).fit_predict(X)df['cluster'] = labels; df.to_csv('outputs/ssdse_clusters.csv', index=False)plt.scatter(X[:,0], X[:,1], c=labels) で散布図、 都道府県名を annotate統計・データ解析コンペティションで DBSCAN を使った分析を成功させるコツ:
これらは仮想的な研究テーマ例ですが、 SSDSE-B-2026 を用いた論文・卒業研究で DBSCAN が活用される典型的場面です。
SSDSE-B-2026 は 2012-2023 の 12 年分を含む。 各年で独立に DBSCAN し、 都道府県のクラスタ遷移を追跡することで、 「地域構造の変化」が見える:
経年クラスタ遷移は地方政策の効果検証にも使えます。 「2020 年の移住支援強化以降、 過疎クラスタから抜けた県があるか」など、 政策評価の根拠データに。
data/raw/ に配置。df.describe() で分布確認、 ペアプロットで関係性を把握。DBSCAN 結果を論文・レポートに書くときの定型:
使用:SSDSE-B-2026 / 期間:2023 年 / 観測数 n=47 / 特徴量 p=5(人口・面積・所得・医師数・失業率)。
欠損なし。 RobustScaler で標準化。 多重共線性確認(VIF<5)。
k-distance graph(k=10)で eps=1.0 を採用。 min_samples=5(次元数×1)。
3 クラスタ(n=20, 14, 8)+ ノイズ 5 件を抽出。 シルエットスコア=0.34、 DBCV=0.42。
各クラスタの特徴を表で示し、 政策示唆を 3 点。 ノイズ県は個別事情を説明。
2023 年単年。 5 指標のみ。 DBSCAN は形状自由だが eps 感度あり。 経年変化や別指標での再検証が必要。
scikit-learn 1.4 / Python 3.11 / 入力データの行は county_code 昇順固定。
DBSCAN を実行すると現れる典型的クラスタ構造の解釈:
東京・神奈川・大阪・愛知・埼玉・千葉・兵庫。 人口密度が高く、 第 3 次産業比率が高い。 ただし東京は人口で突出してノイズ判定されることが多い。
北海道(札幌中心)・宮城・新潟・静岡・京都・広島・福岡。 県人口 200-500 万、 県庁所在地に機能集中。
群馬・栃木・茨城・岐阜・三重・岡山・熊本など。 人口 100-200 万、 地方都市と農業のミックス。
島根・鳥取・徳島・高知・佐賀・福井・山梨など。 人口 100 万以下、 高齢化進行。
東京(巨大)、 沖縄(離島)、 北海道(広大な面積)など、 単独で「外れ」になる県。
これらは恣意的な分類ではなく、 SSDSE-B-2026 の客観的な数値から DBSCAN が自動抽出する構造です。 政策議論の土台に使えます。
47 都道府県を 5 指標でクラスタリングしたときの、 各アルゴリズムの典型的結果:
| 手法 | クラスタ数 | ノイズ/外れ値 | シルエット | 解釈性 |
|---|---|---|---|---|
| K-means (k=3) | 3 | なし | 0.30〜0.35 | ★★ |
| 階層的 (Ward) | 3-4 | なし | 0.32〜0.40 | ★★★(樹形図) |
| DBSCAN | 2-4 | 5-8 件 | 0.25〜0.40 | ★★★★ |
| HDBSCAN | 2-3 | 5-7 件 | 0.30〜0.45 | ★★★★★ |
| GMM | 3 | 確率付き | 0.28〜0.38 | ★★★ |
SSDSE-B-2026 のような外れ値県を含む小規模データでは、 DBSCAN・HDBSCAN がノイズ判定で本質を捉えます。 解釈性と理論的根拠を兼ね備えた手法選択を。
SSDSE-B-2026 は楽勝ですが、 顧客ログ 1 億行などでは戦略が必要:
| データ規模 | 推奨手法 | 所要時間 |
|---|---|---|
| 〜1 万行 | sklearn DBSCAN | 数秒 |
| 10 万行 | sklearn + KD-tree | 数分 |
| 100 万行 | HDBSCAN + UMAP | 10-30 分 |
| 1000 万行 | cuML(GPU) | 数分 |
| 1 億行以上 | Spark MLlib / サブサンプリング | 数十分〜 |
DBSCAN は本質的にカーネル密度推定 (KDE) の離散版と見なせます。 KDE では各点周りにガウシアン等を置いて連続密度関数を構成しますが、 DBSCAN は eps 半径の「箱」を置き、 箱内の点数で局所密度を測ります。 局所密度が閾値(min_samples / 体積)を超える領域が「クラスタ」と判定されるわけです。
HDBSCAN はこの「閾値」を連続的に変化させ、 階層的にクラスタ構造を抽出することで、 DBSCAN の固定 eps の弱点を克服しています。 統計的にはパーシステントホモロジーと呼ばれる位相幾何学とも関連が深く、 「データの形」を抽出する技術として理論研究が続いています。
地方創生の政策ターゲットを定量的に決める想定の事例:
「過疎県」を客観的に定義したい。 主観的に「東北 4 県」と決めるのではなく、 複数指標から DBSCAN で自動同定。
過疎県群(10-15 県)には移住支援・地域おこし協力隊・遠隔医療など共通支援、 ノイズ県(北海道、 沖縄等)には個別最適化された政策を適用、 という二段階アプローチが可能に。
文書クラスタリングに DBSCAN を使う手順:
1 回の DBSCAN 結果を信じすぎず、 安定性を検証する手順:
DBSCAN が最も活躍するのは GPS 軌跡データの分析。 SSDSE-B-2026 とは別領域ですが、 学んでおくと応用力が上がります:
SSDSE 47 都道府県の緯度経度を Haversine 距離で DBSCAN しても面白い実験になります。
シルエットスコアは K-means などの球状クラスタを前提にしているため、 DBSCAN の任意形状クラスタを正当に評価できないことがあります。 DBCV(Density-Based Clustering Validation)が密度ベース手法に特化した評価指標で、 HDBSCAN 論文と同時期に提案されました。
SSDSE-B-2026 のような小規模データでは、 シルエットも DBCV も両方計算してみて、 ドメイン解釈と整合する方を採用するのが実用的。
「3 クラスタ + ノイズ」が出たとして、 そこから示唆を引き出す手順:
df.groupby(cluster).describe() で平均・分散・中央値を確認。| ライブラリ | 言語 | 特徴 | 使い所 |
|---|---|---|---|
| scikit-learn | Python | 標準実装、 ドキュメント充実 | 学習・小〜中規模 |
| hdbscan | Python | HDBSCAN 専用、 確率付き | 密度不均一データ |
| cuML | Python/GPU | RAPIDS、 10-100倍高速 | 大規模・GPU 環境 |
| ELKI | Java | 研究用、 多数のバリエーション | アカデミア |
| dbscan (R) | R | R 統計エコシステム | 統計研究 |
| Spark MLlib | Scala/Python | 分散処理 | 超大規模 |
本サイトの題材 SSDSE-B-2026 は都道府県 × 年次 × 130 指標の小規模だが情報密度の高いデータです。 DBSCAN がここで効く理由:
HDBSCAN は DBSCAN の改良版で、 SSDSE-B のような密度が不均一なデータに圧倒的に強い:
| 特徴 | DBSCAN | HDBSCAN |
|---|---|---|
| パラメータ数 | 2 (eps, min_samples) | 1 (min_cluster_size) |
| 密度不均一 | 弱い | 強い |
| 階層構造 | 無し | あり(dendrogram) |
| 所属確率 | 無し | あり (membership_strength) |
| 外れ値スコア | バイナリ (noise/non-noise) | 連続値 (GLOSH) |
| 新点の予測 | 不可 | approximate_predict 可 |
DBSCAN は前処理の質に強く依存します。 SSDSE-B-2026 で守るべき手順:
np.log1p(A1101)。DBSCAN(および距離ベース手法全般)は高次元で性能が劣化します。 数学的な理由:
$d$ 次元空間でランダムな 2 点間のユークリッド距離は、 平均が $\sqrt{d}$、 標準偏差はほぼ一定。 つまり次元が増えるほど「全部の点が似たような距離」になり、 密度の濃淡が消えます。 SSDSE-B-2026 の 130 列全部を使うのは典型的な失敗例。
sklearn.__version__)matplotlib の tab10 や Set2。 ノイズは黒×でマーキング。plt.annotate(prefecture, (x, y))。 重なる場合 adjust_text で自動調整。kdeplot で背景に密度等高線を重ねると、 「なぜここがクラスタか」が一目瞭然。外れ値検出という観点で、 DBSCAN と Isolation Forest を比較:
| 観点 | DBSCAN | Isolation Forest |
|---|---|---|
| 原理 | 密度の低い点を異常 | 少ない分岐で隔離できる点を異常 |
| パラメータ | eps, min_samples | contamination, n_estimators |
| 出力 | クラスタ + ノイズラベル | 異常スコア(連続値) |
| 高次元 | 弱い(次元の呪い) | 強い |
| 速度 | O(n log n) | O(n log n) |
| SSDSE 適合 | クラスタリング目的 | スコアリング目的 |
47 都道府県を 5 つの指標(人口・面積・所得・医師数・失業率)で密度クラスタリングする実例:
SSDSE-B-2026 から該当年(2023)のデータを抽出、 欠損のない 47 行を確保。 標準化(StandardScaler)。
k=10(次元数 × 2)で実行、 ソートしてプロット。 約 1.0 あたりに肘が現れる。 eps=1.0 を採用。
DBSCAN(eps=1.0, min_samples=5)。 結果は概ね 3 クラスタ + ノイズ 5-7 件になる:
K-means k=4 と比較すると、 DBSCAN は東京を強引にどこかに入れずノイズとして識別する点で優位。 地理学的な異質性が自動で見える化されます。
algorithm パラメータ:通常は 'auto'。 高次元なら 'brute'、 低次元なら 'kd_tree' or 'ball_tree'。n_jobs=-1:近傍探索の並列化(一部の処理のみ)。leaf_size:KD-tree のリーフサイズ。 メモリ vs 速度のトレードオフ。fit_predict のみ。 新点には別途近傍判定が必要。DBSCAN 成功の 80% は eps 選択に左右されます。 標準的な手順:
NearestNeighbors(n_neighbors=k).kneighbors(X) で得られる。kneed ライブラリの KneeLocator。 curve='convex', direction='increasing'。SSDSE-B-2026 の 47 都道府県を標準化後、 k=4 で k-distance graph を描くと、 通常 0.8〜1.2 あたりに肘が現れます。 この近辺で eps を試すのが安定。
| 指標 | 範囲 | 良い値 | DBSCAN での注意 |
|---|---|---|---|
| シルエットスコア | -1〜+1 | +1 に近い | ノイズを除外して計算 |
| Calinski-Harabasz | 0以上 | 大きい程よい | 球状仮定があるため DBSCAN にはやや不向き |
| Davies-Bouldin | 0以上 | 小さい程よい | ノイズあると不安定 |
| DBCV | -1〜+1 | +1 に近い | 密度ベースに特化(推奨) |
| ARI | -1〜+1 | +1 に近い | 正解ラベルが必要(教師あり評価) |
SSDSE-B-2026 の 47 都道府県は楽勝ですが、 数十万〜数億点になると工夫が必要:
algorithm='auto' で自動選択。n_jobs=-1 で並列実行。 ただし scikit-learn 実装は単一プロセス。cuml.DBSCAN で 10-100 倍高速化。1996 年に Martin Ester らが発表した DBSCAN のオリジナルアルゴリズムを擬似コードで再構成:
DBSCAN(D, eps, MinPts):
C = 0 // クラスタ番号
for each point P in D:
if P is visited: continue
mark P as visited
Neighbors = regionQuery(P, eps)
if |Neighbors| < MinPts:
mark P as NOISE
else:
C = C + 1
expandCluster(P, Neighbors, C, eps, MinPts)
expandCluster(P, Neighbors, C, eps, MinPts):
add P to cluster C
for each point P' in Neighbors:
if P' is not visited:
mark P' as visited
Neighbors' = regionQuery(P', eps)
if |Neighbors'| >= MinPts:
Neighbors = Neighbors ∪ Neighbors'
if P' is not yet in any cluster:
add P' to cluster C
SSDSE-B-2026 の 47 都道府県を眺めながら動作を追うと、 「ある県をシードに密度連結でクラスタが成長していく」過程が直感的に理解できます。 visited フラグで O(n) を保証し、 regionQuery が KD-tree なら全体で O(n log n)。
| 点の種類 | 条件 | SSDSE 例 |
|---|---|---|
| コア点 (core) | eps 近傍に min_samples 個以上 | 愛知・福岡(中規模都市の集まり中央) |
| 境界点 (border) | コア点の eps 近傍にいるがコア条件は満たさない | 広島・宮城など中規模県の縁 |
| ノイズ (noise) | どのコア点の近傍にもいない | 東京(極端な大都市)・鳥取(小規模) |
DBSCAN の metric 引数で距離関数を変えられます。 デフォルトはユークリッド距離ですが、 用途で使い分けを:
gower パッケージ。DBSCAN は密度という概念を「点 p から半径 $\varepsilon$(eps)内に min_samples 個以上の他点が存在する」と定義します。 この条件を満たす点をコア点 (core point)、 コア点の $\varepsilon$ 近傍にあるがコア条件を満たさない点を境界点 (border point)、 どちらでもない点をノイズ (noise) と呼びます。 47 都道府県を 2 つの標準化済み指標(例:人口・面積)でプロットしたとき、 中規模県が集まる「真ん中の塊」がコア点群となり、 東京や鳥取のような外れた点はノイズ判定されやすい、 というわけです。
形式的には、 点 $q$ が点 $p$ から「密度直接到達可能」 (directly density-reachable) であるとは、 $q \in N_\varepsilon(p)$ かつ $p$ がコア点であること。 「密度到達可能」 (density-reachable) は、 中間にコア点を介して連鎖的に到達できる関係で、 これにより 1 つのクラスタが伸びていきます。 さらに「密度連結」 (density-connected) とは、 ある共通のコア点を介して 2 点が密度到達可能であることを意味します。 1 つのクラスタは「密度連結同値類」として定義され、 K-means のような球状仮定なしで任意の形状を抽出できるのが DBSCAN の本質的優位性です。
計算量は素朴実装で $O(n^2)$、 KD-tree や Ball tree などの空間索引を併用すれば平均 $O(n \log n)$。 SSDSE-B-2026 の 47 都道府県程度なら無視できるオーダー差ですが、 GPS 軌跡(10 万点)や顧客行動(100 万点)になると索引の有無が数十倍の速度差になります。 scikit-learn の DBSCAN(algorithm='auto') はサンプル数と次元から自動選択しますが、 高次元(次元 ≥ 20)では木索引が「次元の呪い」で劣化するため、 PCA で次元圧縮してから DBSCAN を実行するのが定石です。
DBSCAN の最大の強みは「クラスタ数を事前指定不要+ノイズ自動検出」。 これが業界で重宝される理由です。
| 手法 | クラスタ数 | 形状仮定 | ノイズ検出 | 計算量 | SSDSE-B 適合 |
|---|---|---|---|---|---|
| K-means | 事前指定 (k) | 球状 | なし | O(n·k·d·t) | ★★★(k 決め必要) |
| 階層的 | 事後決定 | 凝集 / 分割 | なし | O(n²-n³) | ★★★★(樹形図で見やすい) |
| DBSCAN | 自動決定 | 任意 | あり | O(n log n) | ★★★★★(外れ値県の発見) |
| HDBSCAN | 自動決定 | 任意・階層 | あり・確率付 | O(n log n) | ★★★★★(密度不均一に強い) |
| GMM | 事前指定 | 楕円体 | なし(確率低) | O(n·k·d²·t) | ★★★(柔軟) |
標準化後 DBSCAN(eps=1.0, min_samples=3) で実行。 北海道は面積で外れ、 東京は人口で外れるためノイズになりがち。
eps を増やすとクラスタが結合してクラスタ数↓・ノイズ数↓。 極端に大きいと全部 1 クラスタに。
コア点条件が厳しくなり、 ノイズが増える。 通常は 2*次元数 を起点に調整。
人口(1000 万単位)が面積(万 km²)を圧倒し、 距離計算がほぼ人口だけで決まる。 標準化は必須。
クラスタが非球状・密度差があるとき。 SSDSE-B-2026 のように外れ値(東京)が混ざる場合、 K-means は東京を強引にどこかに入れるが DBSCAN は noise 判定する。
「eps と min_samples をどう決めるか」が DBSCAN の最重要論点。 SSDSE-B-2026 を題材にした実践手順:
StandardScaler または RobustScaler(外れ値県の影響を抑える)。NearestNeighbors(n_neighbors=min_samples) で k 番目最近傍距離を全点に対して計算、 ソートしてプロット。kneedle ライブラリ。DBSCAN の弱点「密度の異なるクラスタが混在すると失敗する」を克服する派生手法:
1999 年 Ankerst らが提案。 eps を固定しない点が革新的。 「到達可能性距離」を計算し、 任意の eps で事後的にクラスタを切り出せる。 scikit-learn では sklearn.cluster.OPTICS で利用可能。
2013 年 Campello らが提案。 階層的に密度クラスタを抽出し、 「最も安定なクラスタ」を自動選択。 SSDSE-B-2026 のような密度が場所により異なるデータには HDBSCAN が圧倒的に強い。 hdbscan パッケージ(pip install hdbscan)でアクセス。 ハイパーパラメータが min_cluster_size のみで運用が楽。
クラスタリング目的を超え、 DBSCAN は異常検知のシンプルかつ強力な手法でもあります。 ノイズ(label=-1)= 異常 と読み替えるだけ:
K-means と違い「異常も無理にどこかのクラスタに押し込む」現象がないため、 異常検知用途では DBSCAN / HDBSCAN / Isolation Forest が定番です。
pip install pandas scikit-learn matplotlib、 SSDSE-B-2026.csv を data/raw/ に配置。StandardScaler で標準化。NearestNeighbors(n_neighbors=4) で k-distance graph を描画、 肘から eps を決定。DBSCAN(eps=決定値, min_samples=4) で実行、 labels_ を確認。SSDSE-B-2026 を用いた研究で DBSCAN がよく使われる場面:
approximate_predict を使う。本セクションは キーワード索引 として記事全体の構造を 3 図 + 6 表で再整理し、 「どこに何が書いてあるか」と「DBSCAN の意思決定」を一望できるようにしたものである。 図は <img> 要素として埋め込んでおり、 画像ビューア・スクリーンリーダ・印刷でも崩れない。
DBSCAN を理解する最初の鍵は、 すべての点が「コア点」「境界点」「ノイズ点」のいずれかに分類されることである。 下の図は、 半径 ε の円の中に minPts 以上の近傍が入っているか否かで分岐していく様子を表したものだ。
%E3%82%B3%E3%82%A2%E7%82%B9 (core)
💬 読み方:左の青円が コア点、 中央のオレンジが 境界点、 右の灰色が ノイズ。 境界点はコア点とつながっていてクラスタの一員になれるが、 ノイズ点はどのコアからも到達不能で −1 ラベルになる。
DBSCAN で最も悩ましいパラメータは ε だが、 経験則として 「k-distance graph の肘」 を見れば概ね当てが付く。 横軸を点の番号(k 番目の近傍距離でソート)、 縦軸を k-番目近傍距離としたとき、 急激に立ち上がる位置が外れ値とクラスタ点の境目になる。
%E7%82%B9 (k-%E8%BF%91%E5%82%8D%E8%B7%9D%E9%9B%A2%E3%81%A7%E6%98%87%E9%A0%86%E3%82%BD%E3%83%BC%E3%83%88)
💬 読み方:曲線が急に立ち上がる位置が ε の候補。 早すぎると微小クラスタが乱立し、 遅すぎると 1 つの巨大クラスタになる。 minPts は次元 d に対し 2d 程度から試すのが定番。
未訪問の点を起点に「近傍数 ≥ minPts なら BFS で密度連結成分を拡張、 そうでなければ一旦ノイズに置く」を繰り返す。 後で他のコア点の近傍に入ってくれば境界点に格上げされる。
%E7%82%B9 p %E3%82%92%E9%81%B8%E3%81%B6
💬 読み方:「ノイズ扱いになった点」が後で他のコア点から到達可能になれば 境界点に再分類される(左下から右下への矢印)。 この再分類が DBSCAN の「形状自由」の鍵である。
| 分類 | ε 近傍点数 | クラスタ参加 | ラベル例 |
|---|---|---|---|
| コア点 | ≥ minPts | 同じクラスタの中心メンバー | 0, 1, 2, ... |
| 境界点 | < minPts かつ コア点の ε 内 | 最寄りコアと同じクラスタ | 0, 1, 2, ... |
| ノイズ | < minPts かつ どのコアの ε にも入らない | どこにも所属しない | −1 |
| データ規模 | 推奨 minPts | ε 探索開始 | 調整方針 |
|---|---|---|---|
| 小規模 (n < 100) | 3 〜 5 | k-距離 70 〜 80 パーセンタイル | ノイズを許容しないなら minPts を下げる |
| 中規模 (100 〜 10,000) | 2d 〜 4d(d は次元) | k-距離の「肘」 | ノイズが多すぎたら ε を緩める |
| 大規模 (> 10,000) | 10 〜 50 | サブサンプルで決定 | HDBSCAN の方が安定 |
| 手法 | クラスタ数指定 | 扱える形状 | ノイズ判定 |
|---|---|---|---|
| DBSCAN | 不要 | 任意形状 | あり (−1) |
| K-means | 必要 (k) | 球形(凸) | なし |
| 階層的 | 事後 (dendrogram) | 連結成分次第 | なし |
| HDBSCAN | 不要 | 任意形状 | あり |
| 入力 | 標準化 | 代表 ε | 典型出力 |
|---|---|---|---|
| 総人口, 住宅着工戸数 | 必須 | 0.5 | 東京・大阪・神奈川・愛知など大都市圏 9 県が −1、 地方圏 1 クラスタ |
| 高齢化率, 出生率 | 必須 | 0.4 | 沖縄が −1、 東北・四国が同じクラスタ |
| 3 産業比率(1次/2次/3次) | 不要(合計100%) | 5.0 | 第 1 次産業県が独立クラスタとして抽出 |
DBSCAN 主要引数| 引数 | 既定値 | 意味 | 調整ポイント |
|---|---|---|---|
eps | 0.5 | 近傍とみなす半径 | k-distance graph で決定 |
min_samples | 5 | コア判定に必要な近傍数 | 次元 d の 2 倍が目安 |
metric | "euclidean" | 距離関数 | テキストなら cosine、 緯度経度なら haversine |
algorithm | "auto" | 近傍探索の索引 | 大規模なら "ball_tree" |
| 場面 | 向き不向き | 理由 |
|---|---|---|
| 外れ値検出を兼ねたい | ◎ | ノイズラベル −1 が標準 |
| バナナ・リング状クラスタ | ◎ | 密度連結で形状自由 |
| 密度が大きく違うクラスタ混在 | △ | 単一 ε では分けにくい → HDBSCAN へ |
| 高次元(d ≥ 20) | × | 距離が均一化、 ε が機能しない |
| クラスタ数を事前指定したい | × | k 指定の概念がない → K-means へ |
本記事を一通り読み終えた後、 自分の理解を 8 項目で点検したい。 1 つでも答えに詰まる場所があれば、 該当セクションへ戻って読み直すのが安全である。
DBSCAN は「魔法のように形を捉える」反面、 設定を誤ると全データが 1 クラスタ、 あるいは全データがノイズになる極端な結果を生むことがある。 代表的な失敗 3 つと、 即座に効く対策を整理しておく。
StandardScaler をパイプラインに必ず挟む。SSDSE-B-2026 等の県別データに DBSCAN を当てるとき、 単に fit_predict を呼ぶだけでは「期待した結果が出ない」「再現できない」事故が起きやすい。 以下の前処理・本実行・後処理の 3 段階を踏むのが安全である。
StandardScaler もしくは RobustScaler を必ず通す。 RobustScaler は外れ値が強い局面で安定する。NearestNeighbors(n_neighbors=minPts) で k-番目距離を取り、 ソートして肘を読む。random_state を固定する。approximate_predict を別途用意しておく。DBSCAN は密度ベースクラスタリングの最初期の成功例であり、 そこからいくつもの派生が生まれた。 代表的なものを年代順に並べると、 自分が扱おうとしている問題にどの派生が向くかを判断しやすい。
cluster.OPTICS 実装公開。 これ以降、 Python ユーザーは DBSCAN/OPTICS/HDBSCAN を同じインタフェースで比較できるようになった。本記事の本編では (総人口, 住宅着工戸数) の例を扱ったが、 SSDSE-B-2026 には DBSCAN を活かせる他の軸もある。 ここでは「軸の選び方」と「結果の解釈」を簡潔にまとめる。
DBSCAN を読み書きするときに頻出する用語の対応関係を、 日英・略号で整理しておく。 論文・公式ドキュメント・実装ソースコードを跨いで読むときに役立つ。
| 日本語 | 英語 | 略号 / コード上の名前 |
|---|---|---|
| 密度ベースクラスタリング | Density-Based Clustering | DBSCAN, HDBSCAN, OPTICS |
| 近傍半径 | neighborhood radius | eps (ε) |
| 最小近傍点数 | minimum samples | min_samples (minPts) |
| コア点 | core point | core_sample_indices_ |
| 境界点 | border point | non-core, labels_ ≠ −1 |
| ノイズ点 | noise point | labels_ = −1 |
| 密度到達可能 | density-reachable | — (概念のみ) |
| 密度連結 | density-connected | — (概念のみ) |
本編の Q&A に含まれなかった、 やや細かい質問を 4 件補足する。 いずれも実務でつまずきやすいポイントである。
Q. DBSCAN は再現性があるか — アルゴリズム自体は決定論的だが、 sklearn の実装ではタイブレーク順が入力順に依存する。 つまり入力の並び順を変えると、 境界点の所属クラスタが入れ替わることがある。 厳密な再現性が必要なら、 入力 DataFrame をソート固定してから渡す。
Q. NaN を含む列があるとどうなるか — sklearn の DBSCAN は NaN を許容しない。 SimpleImputer で median 補完するか、 NaN 行をドロップしてから渡すこと。 ドロップする場合は「外れ値疑い」と「欠損」の区別を意識する。
Q. クラスタごとの代表点を取りたい — sklearn の DBSCAN は重心を返さない。 core_sample_indices_ から各クラスタのコア点を取り出し、 平均ベクトルを別途計算するのが定石。 境界点を含めるかは目的に応じて決める。
Q. クラスタの大小に意味はあるか — 大きさ自体は単にデータ密度を反映するだけで、 「重要性」を示すわけではない。 小さなクラスタが業務上重要なセグメント(例: ハイバリュー顧客)を表すこともよくある。 サイズではなく「何で構成されるか」で読むこと。
新しいデータが手元に来たとき、 DBSCAN と K-means のどちらを使うべきか迷う場面は多い。 以下の質問を順に答えていけば、 概ね妥当な選択ができる。
この 5 つの質問を 30 秒で答えれば、 SSDSE-B-2026 のような中規模の県別データなら DBSCAN かどうかをすぐ判断できる。 業務データで密度が大きく不均一な場合は HDBSCAN を第一候補にすると失敗が少ない。
以上、 キーワード索引として図 3 点・表 6 点・チェックリスト 8 項目・失敗事例 3 点・前処理〜後処理テンプレート・歴史と派生年表・SSDSE-B での追加応用例・日英対応表・FAQ 補遺・使い分けフローチャートで記事全体の要点を再収録した。 個別の詳細はそれぞれのセクションを参照のこと。 DBSCAN は「密度連結」というシンプルな発想で外れ値検出とクラスタリングを同時にこなす、 きわめて実用的な道具である。 次に進むなら HDBSCAN・OPTICS の各記事へ、 また入力データの距離計量を考え直したいときは「標準化」「距離尺度」「次元の呪い」の各記事を参照すると、 DBSCAN を一段深く理解できる。 探索段階では小さな ε から始め、 ノイズ率の振る舞いを見ながら緩めていくのが安全策である。 最終的にどの ε と minPts に落ち着いたか、 そしてその選択を業務上どう説明したかを、 必ず再現可能な形で記録しておくことを強く推奨する。
DBSCAN を最短で腹落ちさせる 3 枚の図。 ① k-means が苦手な非凸クラスタを密度連結で拾う、 ② Core/Border/Noise の 3 役割、 ③ ε と minPts のチューニング感覚 (k-distance plot)。
💬 ①密度連結なら非凸でも切れる、 ②出力は Core/Border/Noise の 3 ラベルだけ、 ③ ε は k-distance plot の膝で決める — この 3 点を押さえれば DBSCAN の挙動はほぼ説明できる。
2 次元の数値ベクトル 6 点 [0, 0], [1, 1], [1, 0], [2, 1], [8, 8], [9, 9] に eps=2.0, minPts=3 を適用し、各点がコア点か境界点かノイズかを手計算で判定する。
入力点群(2 次元ベクトル): p1=[0, 0], p2=[1, 1], p3=[1, 0], p4=[2, 1], p5=[8, 8], p6=[9, 9]
パラメータ: ε (eps) = 2.0、 minPts = 3(自身を含む)
コア点判定式: dist(pi, pj) = √((xi−xj)² + (yi−yj)²) ≤ ε で近傍を列挙し、近傍数 ≥ minPts なら「コア点」。
| 点 | 座標ベクトル | 近傍 (eps=2.0、自含) | 近傍数 | コア? |
|---|---|---|---|---|
| p1 | [0, 0] | p2(√2≈1.41), p3(√1=1.0) | 3 (自+p2+p3) | ○ コア |
| p2 | [1, 1] | p1(√2), p3(√1), p4(√1) | 4 (自+p1+p3+p4) | ○ コア |
| p3 | [1, 0] | p1(√1), p2(√1), p4(√2) | 4 (自+p1+p2+p4) | ○ コア |
| p4 | [2, 1] | p2(√1), p3(√2) | 3 (自+p2+p3) | ○ コア |
| p5 | [8, 8] | p6(√2≈1.41) | 2 (自+p6) | × ノイズ |
| p6 | [9, 9] | p5(√2) | 2 (自+p5) | × ノイズ |
このコードでやること:手計算した数値ベクトル 6 点 [[0,0],[1,1],[1,0],[2,1],[8,8],[9,9]] を numpy 配列で定義し、DBSCAN(eps=2.0, min_samples=3) を実行。 labels_ とコアインデックスが手計算 Step 3 の結果と一致することを確認する。
📥 入力データ(Step 1 の 6 つの数値ベクトル):
1 2 3 4 5 6 | import numpy as np from sklearn.cluster import DBSCAN X = np.array([[0,0],[1,1],[1,0],[2,1],[8,8],[9,9]]) db = DBSCAN(eps=2.0, min_samples=3).fit(X) print(f"ラベル: {db.labels_}") print(f"コア index: {db.core_sample_indices_}") |
💬 手計算 (Step 3) の結果 ラベル=[ 0 0 0 0 -1 -1]、コアindex=[0 1 2 3] と Python 出力が完全に一致。 数値ベクトル [0,0],[1,1],[1,0],[2,1] が同じクラスタ 0 に、 [8,8],[9,9] がノイズ (-1) になることが手計算と Python 実装で同じ結果であることを確認した。
「DBSCAN」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。
これらの接続を意識することで、 「DBSCAN」を中核に据えた一貫した分析パイプライン(前処理 → DBSCAN → 評価・解釈)を構築できる。
「DBSCAN (密度ベースクラスタリング)」を扱う際の手法選択は、 状況に応じて以下のフローで判断すると迷いが減る。
このフローに沿って判断することで、 「DBSCAN (密度ベースクラスタリング)」を中核とした適切な手法選択ができる。
既存の各節を一切変えずに、 このページでまだ正面から扱っていない 3 つの角度を追記する。 数値はいずれも SSDSE-B-2026(2023 年・47 都道府県) の 6 変数 A1101・A1303・A4101・B4101・C5401・L3221 を StandardScaler で標準化した実測(pd.read_csv(encoding='cp932', skiprows=[1])、 df[df['SSDSE-B-2026']==2023])。 合成データは使わない。
DBSCAN を一言で言い直すと、 点をノード、 「コア点から ε 以内」を有向辺としたグラフの連結成分を取る操作である。 ここで決定的なのは 辺の向きが非対称な点だ。 コア点 $p$ から近傍点 $q$ へは常に辺が伸びる($q$ を $p$ のクラスタへ引き込む)が、 $q$ がコア点でなければ $q$ からは辺が出ない。 つまり コア点はクラスタを外へ伝播させる「幹」、 境界点は幹にぶら下がるだけの「葉」で、 葉から先へは密度が伝わらない。
この非対称性が DBSCAN のすべての挙動の源になっている。 (1) クラスタの骨格(コア点の集合)と、 どの点がノイズかは、 密度到達可能性だけで決まるので データの並び順に一切依存しない。 (2) ところが「2 つの幹の両方に葉として届く境界点」は、 どちらの幹に付くかが決まらない — ここに次の落とし穴が生まれる。
「同じデータ・同じ ε・同じ min_samples なら DBSCAN の結果は完全に一意」と思い込むと事故る。 クラスタ数・ノイズ集合・コア集合は一意だが、 境界点の所属クラスタだけは行の並び順で変わりうる。 2 つのコア点群の両方から ε 以内に届く境界点は、 「先に処理されたコア点」の側へ吸い込まれるため、 fit に渡す行順が変われば割当も変わる(scikit-learn の実装は行順に処理する)。
📥 入力: 標準化済み X(47 県 × 6 変数)。 47 行をランダムに並べ替えて DBSCAN(eps=0.7, min_samples=4) を 1000 回実行し、 各回で コア点集合を不変アンカーにしてクラスタ名を固定し、 各県の所属を数える。
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 | import pandas as pd, numpy as np from sklearn.preprocessing import StandardScaler from sklearn.cluster import DBSCAN df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) d = df[df['SSDSE-B-2026'] == 2023].reset_index(drop=True) cols = ['A1101','A1303','A4101','B4101','C5401','L3221'] X = StandardScaler().fit_transform(d[cols].values) pref = d['Prefecture'].values; n = len(X) def run(order): m = DBSCAN(eps=0.7, min_samples=4).fit(X[order]) lab = np.full(n, -99); lab[order] = m.labels_ core = np.zeros(n, bool); core[order[m.core_sample_indices_]] = True return lab, core lab0, core0 = run(np.arange(n)) # 素の行順 coreidx = np.where(core0)[0] # コア集合は並び順不変 → アンカーに使う print('clusters=%d noise=%d core=%d border=%d' % ( len(set(lab0[lab0>=0])), (lab0==-1).sum(), core0.sum(), ((lab0>=0)&~core0).sum())) def cluname(lab, i): # クラスタを「含まれるコア点の先頭県」で命名 if lab[i] < 0: return 'ノイズ' cm = sorted(set(np.where(lab==lab[i])[0]) & set(coreidx)) return 'C[' + pref[cm[0]] + ']' rng = np.random.RandomState(7) for name in ['佐賀県','宮崎県','奈良県']: t = list(pref).index(name); cnt = {} for _ in range(1000): lab, _ = run(rng.permutation(n)); k = cluname(lab, t) cnt[k] = cnt.get(k, 0) + 1 print(name, '->', cnt) |
📤 実行例:
💬 結果の読み方: eps=0.7・min_samples=4 では 3 クラスタ・ノイズ 25 県・コア 12 県・境界 10 県。 別途 300 回のシャッフルで確認すると クラスタ数(3)・ノイズ集合(25 県)・コア集合(12 県)は 1000 回すべて完全一致(不変)だった。 ところが 佐賀県だけは行順しだいで石川県クラスタ(907 回)にも長崎県クラスタ(93 回)にも割り当たる。 佐賀は両クラスタのコア点から ε 以内に届く「両属の葉」で、 先に処理された側へ吸われるからだ。 宮崎・奈良は片側のコア点にしか届かないので安定。 教訓: 境界点の所属を根拠に議論するときは、 (a) random_state や行順を固定して再現性を担保し、 (b) 境界点かどうか(core_sample_indices_ に無い割当点か)を明示し、 (c) 本質的に一意なコア骨格とノイズ判定の方を主結論に据えるのが安全。 これは k-means の初期値依存とは別種の、 DBSCAN 固有の「境界の曖昧さ」である。
47 県は点数が少なく標準化空間での密度が低いため、 eps を動かすだけで結果が「全点ノイズ」から「ほぼ 1 塊」まで極端に振れる。 min_samples=4 に固定して eps を掃引した実測が下表。
1 2 3 4 5 6 7 8 9 | for eps in [0.3,0.5,0.7,0.9,1.1,1.3,1.6,2.0,2.5,3.0]: lab = DBSCAN(eps=eps, min_samples=4).fit(X).labels_ print('eps=%.1f clusters=%d noise=%d' % (eps, len(set(lab[lab>=0])), (lab==-1).sum())) # 標準化を外すと(生の実数値のまま) labr = DBSCAN(eps=0.7, min_samples=4).fit(d[cols].values).labels_ print('未標準化 eps=0.7 -> clusters=%d noise=%d' % (len(set(labr[labr>=0])), (labr==-1).sum())) |
📤 実行例:
💬 結果の読み方: (1) 極端な感度 — eps≤0.5 では全 47 県がノイズ(クラスタ 0 個)、 eps=3.0 では 46 県が 1 クラスタでノイズは 1 県だけ。 実用的に「意味のある構造」が見えるのは eps=0.7〜1.3 の狭い窓に限られる。 (2) 非単調 — クラスタ数は 0→3→1→1→2→1 と単調でない。 eps を広げると密なコア群が先に融合して数が減り、 さらに広げると別の疎な群がコア化して一時的に増える、 が繰り返されるためで、 「eps を上げれば必ず塊が減る」わけではない。 ただしノイズ数は 47→25→16→…→1 と 単調減少する(本ページ数式節の「ε を大きくすると noise は減少」と整合)。 (3) 標準化は必須 — 同じ eps=0.7 でも 未標準化だと全 47 県がノイズになる。 生値では A1101(総人口、 数百万オーダー)がユークリッド距離を独占し、 どの 2 県も 0.7 以内に入らないからだ。 DBSCAN は距離の絶対値を閾値 ε で切るぶん、 k-means 以上にスケールへ敏感で、 標準化を外すと結果が壊滅する。
※ 「シルエット係数」「HDBSCAN」「OPTICS」「異常検知」は本用語集に単独ページが未整備のため、 リンクにせず本ページ内の該当節(評価指標・派生手法・異常検知の各節)を参照。