論文一覧に戻る 📚 用語解説(ジャストインタイム型データサイエンス教育)
k-means法
k-Means Clustering
事前に決めた k 個のクラスタ中心からの距離が最小になるように、反復的にクラスタを更新するアルゴリズム。
教師なし学習k-meanskmeansK平均法

🔖 キーワード索引

🔖 キーワード索引(拡張)

直感 計算 🎓 設計判断 🧮 SSDSE-B 実例 🐍 実装バリエーション ⚠️ 落とし穴集 kの決定 DBSCAN代替 関連用語マップ

「k-means」は SSE (クラスタ内二乗距離和) を目的関数とする非階層クラスタリング手法。 本ページの中核キーワードを以下に整理する。

k-meanscentroid (クラスタ中心)SSE / inertia_Lloyd アルゴリズムk-means++ 初期化エルボー法シルエット係数標準化前提球形クラスタ仮定Mini-Batch K-MeansDBSCAN / GMM 代替SSDSE-B-2026 47 都道府県

これらのキーワードは「assignment step → update step → 収束判定」と「k 選択 → 初期化 → 形状判定」の 2 軸で k-means 実務を構成する。

💡 30秒で分かる結論

🍰 まずはやさしく

似たもの同士を分けるグループ分けです。

データを自動でいくつかの集団にまとめます。

スマホのアプリで好みの人を分けるようなものです。

この手法の結論を短くまとめます。

📖 もっと詳しく

k-means は機械学習の教師なし学習の代表手法。 「事前に決めた k 個のグループに、 似ているサンプル同士を集める」反復アルゴリズム。 1957年に Lloyd が提案し、 1967年に MacQueen が "k-means" と命名。 シンプルかつ高速で、 現在も クラスタリング手法の標準。

「means(平均)」が名前の由来:各クラスタの「中心(centroid)」を、 そのクラスタに属するサンプルの平均位置で表すから。 「k 個の中心」を探すアルゴリズム、 と言えます。

k-means は探索的データ分析の万能ツール。 顧客セグメンテーション、 地域類型化、 異常検知、 画像圧縮(色のクラスタリング)まで幅広く使われます。

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

🍰 まずはやさしく

データを分けるための定番の道具です。

似ているグループを自動で作るために使います。

都道府県を特徴で分けるときに便利です。

この手法がどこで使われるかを紹介します。

論文で「k-means クラスタリング」「k=4 でクラスタリングした」「エルボー法で k を決定」のように登場する手法。 47都道府県を「似ている県のグループ」に自動分類するときの定番ツール。

k-means法 とは:事前に決めた k 個のクラスタ中心からの距離が最小になるように、反復的にクラスタを更新するアルゴリズム。

🎨 直感で掴む

🍰 まずはやさしく

点と中心の距離で分けるイメージです。

自然なグループ構造を見つけるために使います。

部活のメンバーを能力で分けるような感覚です。

仕組みを直感的に理解するための例を見ます。

2023 年度 47 都道府県の高齢化率と死亡率を標準化して k=4 でクラスタリングした散布図
2023 年度の 47 都道府県を「高齢化率×死亡率」の 2 次元空間で(標準化してから)k=4 にクラスタリングした例。 × が各クラスタの中心(centroid、 元の単位での平均)。 右上=高齢化進行、左下=若年層多。

図を見ると、 4色の点群(クラスタ)と各クラスタの × 印(中心)が見えます。 k-meansは「各点が、 自分が属するクラスタ中心に最も近い」状態に収束した結果。

右上の黄のクラスタ 4(7 県、 中心は高齢化率 35.9%・死亡率 17.1)は「高齢化進行・死亡率高」(秋田・高知・青森・岩手・山形・山口・徳島)、 左下の青のクラスタ 1(9 都府県、 中心 26.3%・11.0)は「若年層多・死亡率低」(東京・神奈川・沖縄・埼玉・千葉・愛知・滋賀・大阪・福岡)と、 高齢化率の順に 4 段に分かれています(島根県は 1 段下のクラスタ 3 の端)。 2 つの指標がほぼ一直線に並ぶので、 群のあいだに隙間があるわけではなく「連続した帯をどこで切るか」を k-means が決めている点には注意。 それでも解釈可能な構造に分かれています。 「データに自然なグループ構造があれば、 k-means はそれを発見してくれる」ことが体感できる。

反復のイメージ — 「中心 → 割り当て」を交互に:(1) ランダムに k 個の中心を置く、 (2) 各点を「最も近い中心」に割り当てる、 (3) 各クラスタ内の点の平均位置に中心を移動、 (4) 中心が動かなくなるまで (2)-(3) を繰り返す。 これは E ステップ(割り当て)と M ステップ(中心更新)を交互に行う EM 法の一種で、 必ず単調に SSE が減少して収束します。 ただし初期値依存のため、 異なる初期値で 10 回走らせて最良の解を採用する n_init=10 が sklearn の標準動作です。

「平均」が中心の名前の由来:k-means の「means」は文字通り mean = 平均。 各クラスタの中心は所属点の 算術平均 です。 中央値(メドイド)を使う k-medoids、 確率分布で割り当てる GMM、 階層構造を作る 階層クラスタリング など、 「中心の定義」と「割り当て方」を変えることで様々な派生手法が生まれます。

🎮 点を置いてクラスタリング(触って理解する k-means)

下のキャンバスをクリックして点を配置し、 スライダーで k(クラスタ数)を決めて、 「▶ 1ステップ」で 割り当て(assignment)→ 重心更新(update) を1手ずつ、 「⏩ 自動収束」で最後まで動かせます。 各点が所属クラスタの色に塗られ、 ✕ 印の重心が平均位置へ移動していく様子を観察できます。 「🎲 初期値ランダム」を何度も押すと、 同じ点配置でも初期重心しだいで結果が変わる(初期値依存)ことが体感できます。 右上の WCSS(=SSE, クラスタ内二乗和)が反復のたびに単調に減るのがエルボー法の直感です。

状態:クリックで点を置いてください
反復回数:0
点の数 n:0
WCSS (SSE):—

✕ = 重心(centroid, 所属点の平均座標)/ ● = データ点(所属クラスタ色)。 割り当て手番では点の色が、 更新手番では ✕ の位置が変わります。

💡 この操作で確かめられること
  • 収束の仕組み:割り当て→更新を繰り返すと WCSS が必ず単調減少し、 割り当てが変わらなくなった時点で収束(Lloyd アルゴリズム)。
  • 初期値依存:「🎲 初期値ランダム」で同じ点配置を何度も走らせると、 収束後の WCSS や色分けが変わることがある → 局所最適。 だから実務では n_init≥10 で複数初期値を試す(k-means++ は離れた点を初期重心に選んでこれを緩和)。
  • k とエルボー:k を増やすと WCSS は必ず下がるが、 下がり方が鈍る「肘」が最適 k の目安。
  • 球状仮定の限界:三日月・細長い形の点群を置くと、 k-means はうまく分割できない。 そうした形状は DBSCAN(密度ベース)や 階層クラスタリング が向く。

📐 数式

🍰 まずはやさしく

中心からの距離を計算する数式です。

グループ内のズレを最小にするために使います。

買い物で予算に近い商品を選ぶ計算に似ています。

計算のルールを数式を使って詳しく説明します。

【k-means の目的関数:SSE 最小化】
$$\arg\min_{S_1, \ldots, S_k} \sum_{i=1}^{k} \sum_{x \in S_i} \|x - \mu_i\|^2$$
各クラスタ $S_i$ の中心 $\mu_i$ までの距離の二乗和を、 全クラスタで合計した量(SSE = Sum of Squared Errors)。 これを最小化する分割を探す

📐 k-means の数学

損失関数

$$ J = \sum_{i=1}^{n} \sum_{j=1}^{k} z_{ij} \| x_i - \mu_j \|^2 $$

z_ij ∈ {0, 1}:i番目点がクラスタjに属するかどうか。 μ_j:クラスタjの中心。

アルゴリズム(Lloyd's algorithm)

  1. 初期中心をランダムに k個選ぶ
  2. 各点を最寄りの中心に割り当てる(assignment step)
  3. 各クラスタの中心を再計算(update step)
  4. 収束するまで 2-3 を繰り返す

計算量は O(n × k × d × iter)。 大規模データでは k-means++ や Mini-Batch k-means を使用。

k-means++ の初期化

「離れた点を初期中心に選ぶ」工夫。 通常のランダム初期化より速く・確実に良い解に収束。 sklearn のデフォルト。

🔬 数式を言葉で読み解く

$k$
クラスタ数:事前に決める。 通常 2〜10。 エルボー法・シルエット係数で決定
$S_i$
$i$ 番目のクラスタ(サンプルの集合)
$\mu_i$
$S_i$ の中心(centroid):$\mu_i = \frac{1}{|S_i|} \sum_{x \in S_i} x$(クラスタ内サンプルの平均)
$\|x - \mu_i\|$
サンプル $x$ から中心 $\mu_i$ までのユークリッド距離
$\|x - \mu_i\|^2$
二乗することで「遠いほど強くペナルティ」

🔬 k-means を実データで多角的に確認する(R283 補強)

k-means は 4 行のコードで動くが、 「標準化」「k の選択」「初期化のランダム性」「形状の前提」を理解せずに使うと無意味なクラスタが出る。 SSDSE-B-2026 で 4 角度から検証する。

① 標準化前後の SSE 比較 — 単位の影響

このコードでやること: SSDSE-B-2026 から「総人口 A1101」「年平均気温 B4101」を取り出し、 標準化なし/ありで k-means を実行、 各クラスタの内訳を比較する。

📥 入力: SSDSE-B-2026.csv。 総人口 A1101(百万単位)、 年平均気温 B4101(十数℃)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd
from sklearn.cluster import KMeans
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都道府県
X_raw = df[['A1101', 'B4101']].fillna(df[['A1101', 'B4101']].mean()).values

# 標準化なし(総人口 A1101 は百万単位、年平均気温 B4101 は十数℃)
km_raw = KMeans(n_clusters=3, n_init=10, random_state=42).fit(X_raw)
# 標準化あり
X_std = StandardScaler().fit_transform(X_raw)
km_std = KMeans(n_clusters=3, n_init=10, random_state=42).fit(X_std)

vc = lambda km: pd.Series(km.labels_).value_counts().sort_index().to_dict()
print('標準化なし: クラスタ別件数', vc(km_raw))
print('標準化あり: クラスタ別件数', vc(km_std))

📤 実行例:

標準化なし: クラスタ別件数 {0: 38, 1: 8, 2: 1} 標準化あり: クラスタ別件数 {0: 30, 1: 8, 2: 9}

💬 結果の読み方: 標準化なしでは「クラスタ 2 が東京 1 件だけ」になる(人口の桁が大きく、 距離計算が人口だけで決まる)。 標準化後は 30/8/9 に分かれ、 総人口の桁に潰されていた気温の情報も距離に効くようになる → 意味のあるクラスタリング。

② エルボー法で最適な k を決定

このコードでやること: k=1〜8 まで k-means を実行し、 SSE(inertia_)の推移から肘を探す。

📥 入力: ①の df から実在6列 A1101・A1303・A4101・B4101・L3221・C5401 を取り、 標準化した X_std。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from sklearn.cluster import KMeans

cols = ['A1101', 'A1303', 'A4101', 'B4101', 'L3221', 'C5401']
X = df[cols].fillna(df[cols].mean()).values
X_std = StandardScaler().fit_transform(X)

sse = []
for k in range(1, 9):
    km = KMeans(n_clusters=k, n_init=10, random_state=42).fit(X_std)
    sse.append(km.inertia_)

for k, s in zip(range(1, 9), sse):
    print(f'k={k}: SSE={s:.2f}  減少={sse[k-2]-s if k>1 else 0:.2f}')

📤 実行例:

k=1: SSE=282.00 減少=0.00 k=2: SSE=145.24 減少=136.76 k=3: SSE=113.01 減少=32.22 k=4: SSE=79.18 減少=33.83 k=5: SSE=59.55 減少=19.63 k=6: SSE=50.70 減少=8.85 k=7: SSE=44.60 減少=6.10 k=8: SSE=37.43 減少=7.16

💬 結果の読み方: k=1→2 で 137 と大きく落ち、 k=2→3 は 32、 k=3→4 は 34、 k=4→5 で 20 と鈍りはじめる → 肘は k=4 前後。 SSE の減少幅が明確に小さくなる位置を目安に k=4 を採る。

③ シルエット係数で k を再検証

このコードでやること: k=2〜7 でシルエット係数を計算し、 エルボー法の結果と整合するか確認する。

📥 入力: ②の X_std。

 1
 2
 3
 4
 5
 6
 7
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

for k in range(2, 8):
    km = KMeans(n_clusters=k, n_init=10, random_state=42).fit(X_std)
    sil = silhouette_score(X_std, km.labels_)
    print(f'k={k}: silhouette={sil:.3f}')

📤 実行例:

k=2: silhouette=0.555 k=3: silhouette=0.330 k=4: silhouette=0.302 k=5: silhouette=0.347 k=6: silhouette=0.267 k=7: silhouette=0.243

💬 結果の読み方: シルエット係数は k=2 が最大(0.555)→「大きく 2 群(大都市圏 vs その他)に分かれている」。 一方エルボーは k=4 前後を推奨。 基準が食い違う場合は分析目的で決める。 「2 群でざっくり」なら k=2、「地域類型を細かく」なら k=4。

④ 初期化のランダム性 — n_init による結果の安定性

このコードでやること: n_init=1 と n_init=20 で k-means を 5 回実行し、 SSE のばらつきを比較。 局所最適に陥るリスクを定量化する。

📥 入力: ②の X_std。 random_state は変化させる。

 1
 2
 3
 4
 5
 6
 7
 8
from sklearn.cluster import KMeans

for n_init in [1, 20]:
    sse_list = []
    for rs in range(5):
        km = KMeans(n_clusters=4, n_init=n_init, random_state=rs).fit(X_std)
        sse_list.append(km.inertia_)
    print(f'n_init={n_init:2d}: SSE = {[round(s, 2) for s in sse_list]}  最大-最小 = {max(sse_list)-min(sse_list):.2f}')

📤 実行例:

n_init= 1: SSE = [80.08, 83.54, 80.08, 95.43, 78.88] 最大-最小 = 16.56 n_init=20: SSE = [78.87, 78.88, 78.87, 78.74, 78.88] 最大-最小 = 0.14

💬 結果の読み方: n_init=1 では SSE が 78〜95 と大きくばらつく(初期値次第で局所最適に陥る試行がある)。 n_init=20 では毎回ほぼ 78.8 に収束し安定 → 大域最適に到達できている。 実務では必ず n_init≥10 を指定する。

k-means 4 つの実験のまとめ

🧮 計算してみる

6都道府県のデータ(高齢化率と死亡率)で、 k=2 の k-means が実際にどう動くか、 反復計算を追ってみます。

STEP 0 データと初期化
都道府県高齢化率 x死亡率 y
秋田3919
高知3617
大阪2813
神奈川2611
東京239
沖縄2311

初期中心をランダムに:C1 = (40, 20)、 C2 = (20, 10)(k-means++ では確率的に選びますが、 簡単のためここでは固定)

STEP 1 イテレーション1:各点を最も近い中心に割り当て

各点から両中心までのユークリッド距離を計算:

点d(C1)d(C2)割当
秋田 (39,19)√(1+1) = 1.4√(361+81) = 21.0C1
高知 (36,17)√(16+9) = 5.0√(256+49) = 17.5C1
大阪 (28,13)√(144+49) = 13.9√(64+9) = 8.5C2
神奈川 (26,11)√(196+81) = 16.6√(36+1) = 6.1C2
東京 (23,9)√(289+121) = 20.2√(9+1) = 3.2C2
沖縄 (23,11)√(289+81) = 19.2√(9+1) = 3.2C2

C1 クラスタ:秋田、高知(2点)/C2 クラスタ:大阪、神奈川、東京、沖縄(4点)

STEP 2 イテレーション1:中心を更新

各クラスタの平均を新しい中心とする:

  • 新 C1 = (平均(39,36), 平均(19,17)) = (37.5, 18.0)
  • 新 C2 = (平均(28,26,23,23), 平均(13,11,9,11)) = (25.0, 11.0)

中心が大きく動きました:C1 は (40,20) → (37.5, 18)、 C2 は (20,10) → (25, 11)

STEP 3 イテレーション2:再割り当て

新しい中心で再度距離計算:

点d(新C1)d(新C2)割当
秋田 (39,19)1.8016.1C1(変化なし)
高知 (36,17)1.8012.5C1(変化なし)
大阪 (28,13)10.73.6C2(変化なし)
神奈川 (26,11)13.51.0C2(変化なし)
東京 (23,9)17.12.8C2(変化なし)
沖縄 (23,11)16.12.0C2(変化なし)

すべての点の割当が前回と同じ!中心も動かない。 収束完了。 たった2イテレーションで終了しました。

STEP 4 結果の解釈

最終結果:

  • クラスタ1(高齢化進行群):秋田、 高知。 中心 (37.5, 18.0)。 「高齢化率約37.5%、 死亡率約18‰の県」
  • クラスタ2(中・低齢化群):大阪、 神奈川、 東京、 沖縄。 中心 (25.0, 11.0)。 「高齢化率約25%、 死亡率約11‰の県」

SSE(残差二乗和) = (秋田からC1の距離²) + (高知からC1の距離²) + ... = 1.80² + 1.80² + 3.61² + 1.00² + 2.83² + 2.00² = 3.25 + 3.25 + 13 + 1 + 8 + 4 = 32.5。 これより小さい分割は存在しません(この初期値からは)。

k=3 にしたら?大阪・神奈川を1群、 東京・沖縄を別群、 秋田・高知を1群、 のように細分化されます。 SSEはさらに小さく。 でもどこかで「k を増やしても効率が悪い」境界(=エルボー)が来ます。

🎓 k-means の設計判断 — k の決定・初期化・距離

1. k(クラスタ数)の決め方

「いくつのクラスタに分けるか」は分析者が決める。 客観的指標の選択肢:

2. 初期化:k-means++

ランダム初期化は局所最適に陥りやすい。 k-means++ は次の手順で賢く初期化:

  1. 1つ目の中心はランダム選択
  2. 2つ目以降は、 「既選択中心から遠い」サンプルほど高確率で選ぶ(距離の二乗に比例する確率)
  3. k 個の中心が分散して配置される

scikit-learn の KMeans はデフォルトで k-means++。 さらに n_init 回試行して最良の SSE を採用。

3. 距離の選択

標準はユークリッド距離。 ただし他の選択肢もあります:

ただしユークリッド以外を使うと「平均」が幾何的に意味を失うので、 厳密には k-medoids(中心を実在サンプルに)が適切。

4. 計算量と収束性

計算量:$O(n \cdot k \cdot d \cdot \text{iter})$。 通常 iter は数十回。 n=100万 でも数秒で完了するのが強み。

収束性:k-means は必ず収束しますが、 大域最適解への収束は保証されません。 SSE は反復のたびに必ず減少(または同じ)となりますが、 局所最適に陥る可能性があります。 これが「複数回試行」が必要な理由。

NP困難性:k-means の大域最適解を求める問題は NP困難(k≥2 で)。 だから「最適解の近似」を反復で求めるしかない。

🧮 SSDSE-B-2026 で k-means を即実行

最新の SSDSE-B-2026 を使い、 47 都道府県を 4 クラスタに分けるコード。 教材標準パスを直書きしています。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1303(65歳以上人口) A4101(出生数) B4101(年平均気温) C5401(標準価格(平均価格)(住宅地)) 北海道 5,092,000 1,681,000 24,430 11.0 23,600 東京都 14,086,000 3,205,000 86,348 17.6 404,400 沖縄県 1,468,000 350,000 12,549 23.8 68,100 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
df = df[df['SSDSE-B-2026'] == 2023]          # 最新2023年・47都道府県
X = df[['A1101', 'A1303', 'A4101', 'B4101', 'L3221', 'C5401']].dropna()
X_scaled = StandardScaler().fit_transform(X)

km = KMeans(n_clusters=4, n_init=10, random_state=42).fit(X_scaled)
print(km.labels_)        # 各都道府県のクラスタ番号
print(km.inertia_)       # SSE(小さいほど締まりが良い)
📤 実行例(実測) [1 1 1 1 1 1 1 1 1 0 3 3 2 3 1 1 1 0 1 1 1 1 3 1 1 1 3 3 1 0 0 1 1 1 0 0 0 0 0 3 0 0 0 1 0 0 0] 79.17867759057592

💬 4 つのクラスタは、東京都だけの 1 県(ラベル 2)、埼玉・千葉・神奈川・愛知・大阪・兵庫・福岡の 7 府県(ラベル 3)、残りを 24 県と 15 県に分けた 2 群になった。6 変数とも規模に引きずられる量なので、実質は「人口の大きさ」で輪切りにした結果に近い。inertia 79.18 は標準化後の全平方和 282(47 県 × 6 変数)の約 28% が残った値で、ラベル番号 0〜3 そのものには順序の意味はない。

出力のとおり、 東京都は 1 県だけのクラスタ、 大阪・神奈川・愛知など 7 府県が大都市圏クラスタにまとまり、 秋田・高知や沖縄はいずれも残り 2 群のどちらかに入ります(単独クラスタにはならない)。 SSDSE-B-2026 の最新値で確かめられます。

🎯 理解度チェック — k-means 法

読み終えたら、 以下の練習問題で「自分で説明できるか」を確認しよう。 答えが詰まった箇所は対応する節に戻る。

  1. Q1. k-means の目的関数(クラスタ内 SSE)はどのように書ける? また、 なぜ 距離の二乗を使うのか 1 行で説明せよ。
  2. Q2. 初期セントロイドの選び方が結果に与える影響を述べ、 k-means++ がそれをどう緩和するか 2 行で説明せよ。
  3. Q3. SSDSE-B-2026 の「総人口 A1101(百万人)」と「年平均気温 B4101(十数℃)」を 標準化せずに k-means に投入すると何が起きるか。 標準化後と比べてクラスタ構成がどう変わるか 1 行で予想せよ。
  4. Q4. エルボー法とシルエット係数で最適 k を選ぶとき、 どちらがより信頼できるかとその理由を述べよ。
  5. Q5. k-means が 球形クラスタを前提とする理由を、 目的関数の形から説明せよ。 細長い・密度が異なるクラスタには何を使うべきか。

🛠 自分で手を動かす(5 分課題):

  1. SSDSE-B-2026.csv から「総人口 A1101」「65歳以上人口 A1303」「年平均気温 B4101」の 3 変数を抽出し、 StandardScaler で標準化。
  2. KMeans(n_clusters=k) を k=2〜8 で回し、 inertia_ の推移をプロットしてエルボーを目視する。
  3. 選んだ k で再度フィットし、 silhouette_score を計算してエルボーの選択と一致するか確認する。
  4. 各クラスタに属する都道府県名を print で出し、 「クラスタが何を意味するか」を 1 行で言語化する。

🧮 数式に値を入れて手で計算する: K-means 1 反復

合成 4 点 1D データで K=2 の 1 反復を手計算する。

Step 1: データと初期重心

x = [1, 2, 8, 10] 初期重心: c1=1, c2=10

Step 2: 割当 (最近傍)

x=1: |1-1|=0, |1-10|=9 → c1 x=2: |2-1|=1, |2-10|=8 → c1 x=8: |8-1|=7, |8-10|=2 → c2 x=10: |10-1|=9, |10-10|=0 → c2 クラスタ: c1={1,2}, c2={8,10}

Step 3: 重心更新

c1 新 = (1+2)/2 = 1.5 c2 新 = (8+10)/2 = 9.0 収束 (重心変化小)

🐍 Python で再現

1
2
3
4
5
6
import numpy as np
from sklearn.cluster import KMeans
X = np.array([[1],[2],[8],[10]])
km = KMeans(n_clusters=2, init=np.array([[1],[10]]), n_init=1, random_state=0).fit(X)
print(f"重心: {km.cluster_centers_.flatten()}")
print(f"ラベル: {km.labels_}")

📤 実行結果

重心: [1.5 9. ] ラベル: [0 0 1 1]

💬 手計算 (Step 3) [1.5, 9.0] と Python 出力が完全一致。

🐍 Python での実装

① scikit-learn での基本

🎯 このコードでやること:SSDSE-B-2026 の 2023 年度・47 都道府県 × 6 変数を標準化し、 k=3 の k-means と Ward 法の階層クラスタリングを当てて、 中心・inertia・両者の一致度・シルエットを出します。

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
from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import silhouette_score, adjusted_rand_score
import pandas as pd
import numpy as np

# SSDSE-B-2026 を読み込み、2023 年度の 47 都道府県 × 実在 6 変数を取り出す
d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = d[d['SSDSE-B-2026'] == 2023]
cols = ['A1101', 'A1303', 'A4101', 'B4101', 'L3221', 'C5401']
X = d[cols].astype(float).values

# データの標準化(重要!)
scaler = StandardScaler()
X_std = scaler.fit_transform(X)

# k-means
km = KMeans(n_clusters=3, random_state=0, n_init=10)
labels_km = km.fit_predict(X_std)
print('クラスタ中心(標準化後):')
print(pd.DataFrame(km.cluster_centers_, columns=cols).round(2))
print(f'inertia: {km.inertia_:.2f}')
print('クラスタ別件数:', np.bincount(labels_km).tolist())

# 階層クラスタリング(Ward法)
agg = AgglomerativeClustering(n_clusters=3, linkage='ward')
labels_agg = agg.fit_predict(X_std)
print(f'k-means と Ward の一致度 (ARI): {adjusted_rand_score(labels_km, labels_agg):.3f}')

# シルエットスコアで評価
score = silhouette_score(X_std, labels_km)
print(f'シルエットスコア: {score:.3f}')
📤 実行例(実測) クラスタ中心(標準化後): A1101 A1303 A4101 B4101 L3221 C5401 0 -0.39 -0.40 -0.40 -0.11 -0.10 -0.32 1 1.60 1.70 1.63 0.54 0.28 1.01 2 4.13 3.55 4.18 0.39 1.90 5.49 inertia: 111.09 クラスタ別件数: [39, 7, 1] k-means と Ward の一致度 (ARI): 1.000 シルエットスコア: 0.513
💬 読み方:k=3 では東京都 1 県・大都市圏 7 府県・その他 39 道県に分かれ、 Ward 法も同じ分け方になったので ARI は 1.000。 東京都の中心は地価 C5401 が 5.49 標準偏差と飛び抜けていて、 1 県で 1 クラスタを占める。 inertia 111.09 は ② の k=3(random_state=42 で 113.01)より小さく、 乱数の種が違うだけで別の局所解に落ちることがここでも分かる。

② 最適クラスタ数の探索

🎯 このコードでやること:総人口・出生数・死亡数・婚姻件数の 4 指標を標準化し、 k=2〜10 の inertia とシルエット係数を並べて図に描きます。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 総人口 出生数 死亡数 婚姻件数 北海道 5,092,000 24,430 75,120 17,281 東京都 14,086,000 86,348 137,241 71,774 沖縄県 1,468,000 12,549 15,110 6,316 …(全 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# ── この抜粋で使うデータを用意します(SSDSE-B の 47 都道府県・最新年度)──
import pandas as pd

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1)
df = df[df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy()
df['年度'] = pd.to_numeric(df['年度'], errors='coerce')
df = df[df['年度'] == df['年度'].max()]
for _c in df.columns[3:]:
    df[_c] = pd.to_numeric(df[_c], errors='coerce')
df['高齢化率'] = df['65歳以上人口'] / df['総人口'] * 100

# 見本でよく使われる仮の列名を、実データから作っておく
df['income'] = df['消費支出(二人以上の世帯)']
df['population'] = df['総人口']
_region = {'北海道': '北海道', '青森県': '東北', '岩手県': '東北', '宮城県': '東北',
           '秋田県': '東北', '山形県': '東北', '福島県': '東北', '茨城県': '関東',
           '栃木県': '関東', '群馬県': '関東', '埼玉県': '関東', '千葉県': '関東',
           '東京都': '関東', '神奈川県': '関東'}
df['region'] = df['都道府県'].map(_region).fillna('その他')
df['地域'] = df['region']

_feats = ['総人口', '出生数', '死亡数', '婚姻件数']
df = df.dropna(subset=_feats + ['消費支出(二人以上の世帯)'])
X = df[_feats].to_numpy(dtype=float)
y = df['消費支出(二人以上の世帯)'].to_numpy(dtype=float)
x = df['総人口'].to_numpy(dtype=float)

from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# この抜粋の X_std は 47 都道府県 × 4 指標(上で作った X を標準化)
X_std = StandardScaler().fit_transform(X)

inertias = []
silhouettes = []
for k in range(2, 11):
    km = KMeans(n_clusters=k, random_state=0, n_init=10).fit(X_std)
    inertias.append(km.inertia_)
    silhouettes.append(silhouette_score(X_std, km.labels_))
    print(f'k={k:2d}: inertia={km.inertia_:6.2f}  silhouette={silhouettes[-1]:.3f}')

# エルボー法
plt.subplot(1, 2, 1)
plt.plot(range(2, 11), inertias, 'o-')
plt.xlabel('k'); plt.ylabel('inertia')

# シルエット法
plt.subplot(1, 2, 2)
plt.plot(range(2, 11), silhouettes, 'o-')
plt.xlabel('k'); plt.ylabel('Silhouette')
📤 実行例(実測) k= 2: inertia= 45.78 silhouette=0.785 k= 3: inertia= 19.18 silhouette=0.777 k= 4: inertia= 10.65 silhouette=0.720 k= 5: inertia= 5.41 silhouette=0.565 k= 6: inertia= 3.85 silhouette=0.572 k= 7: inertia= 2.35 silhouette=0.559 k= 8: inertia= 1.82 silhouette=0.477 k= 9: inertia= 1.43 silhouette=0.458 k=10: inertia= 1.12 silhouette=0.465
💬 読み方:シルエットは k=2 の 0.785 が最大で k=4 の 0.720 まで高いが、 k=5 で 0.565 に落ちる。 4 指標はどれも人口規模に比例する量なので、 実質は 1 次元の「県の大きさ」を切っているだけで、 inertia が k=2 の 45.78 から k=4 の 10.65 へ急に減るのもそのため。 前半の検証 ③ の 6 変数版(k=2 で 0.555)よりシルエットが高いのは、 分けやすい変数を選んだ結果であってクラスタの質が上がったわけではない。

③ デンドログラムの描画

🎯 このコードでやること:総人口・15 歳未満人口・65 歳以上人口・出生数・消費支出の 5 指標を標準化し、 Ward 法で 47 都道府県を結合していく樹形図(デンドログラム)を描きます。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 A1101(総人口) A1301(15歳未満人口) A1303(65歳以上人口) A4101(出生数) L3221(消費支出(二人以上の世帯)) 北海道 5,092,000 514,000 1,681,000 24,430 296,888 東京都 14,086,000 1,513,000 3,205,000 86,348 341,320 沖縄県 1,468,000 236,000 350,000 12,549 251,222 …(全 47 行)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import pandas as pd

# ── この抜粋だけで動くように、47 都道府県・最新年度を読み込む ──
_d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
_d = _d[_d['SSDSE-B-2026'] == 2023]
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from scipy.cluster.hierarchy import linkage, dendrogram

# ── この抜粋だけで動くように、標準化した行列と葉のラベルを用意する ──
X_std = StandardScaler().fit_transform(
    _d[['A1101', 'A1301', 'A1303', 'A4101', 'L3221']].astype(float).values)
labels = _d['Prefecture'].tolist()

Z = linkage(X_std, method='ward')
plt.figure(figsize=(14, 6))
dendrogram(Z, labels=labels, leaf_rotation=90)
plt.show()
📤 実行例(実測) このブロックは標準出力を出さない(図を描く・変数を定義するだけ)。
💬 読み方:図は描くだけで数値は出ないが、 同じ Z を fcluster で切ると、 最後の結合(高さ 17.3)で北海道と大都市圏 8 都府県の 9 都道府県が残り 38 県から分かれ、 3 群にすると東京都が単独になる。 横軸の県名の並びは結合順なので、 隣り合う県ほど 5 指標が似ていると読める。

🐍 実装バリエーション — scikit-learn / scipy / faiss

(A) scikit-learn KMeans — 標準

🎯 このコードでやること:上の ② で作った 4 指標を標準化してから、 k-means++ 初期化・k=4 の KMeans を当て、 ラベル・中心・SSE を出します。

1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import numpy as np

# X は上の ② で作った 47 都道府県 × 4 指標(総人口・出生数・死亡数・婚姻件数)の生の値。
# 桁が違うので標準化してから渡す
Xs = StandardScaler().fit_transform(X)
km = KMeans(n_clusters=4, init='k-means++', n_init='auto', max_iter=300,
            random_state=42).fit(Xs)
print(km.labels_)                    # 各サンプルのクラスタ番号
print(np.round(km.cluster_centers_, 2))  # 中心(標準化後の単位)
print(round(km.inertia_, 2))         # SSE
📤 実行例(実測) [1 0 0 2 0 0 2 2 2 2 1 1 3 1 2 0 0 0 0 2 2 2 1 2 0 2 1 1 0 0 0 0 2 2 0 0 0 0 0 1 0 0 2 0 0 2 0] [[-0.59 -0.57 -0.64 -0.52] [ 1.51 1.49 1.63 1.31] [-0.18 -0.2 -0.13 -0.22] [ 4.13 4.18 3.61 4.77]] 13.25
💬 読み方:東京都(ラベル 3)が単独、 北海道と埼玉・千葉・神奈川・愛知・大阪・兵庫・福岡の 8 道府県がラベル 1、 残りが 23 県と 15 県に分かれた。 中心は標準化後の単位なので、 東京都は 4 指標とも平均より 3.6〜4.8 標準偏差上にいる。 SSE 13.25 は 47 県 × 4 指標の全平方和 188 の約 7%。 標準化せずに当てると SSE が 2.4×10^13 のように総人口の桁で決まる値になり、 他の k や他のデータと比べられない。

(B) scikit-learn MiniBatchKMeans — 大規模データ向け

🎯 このコードでやること:10 万件 × 4 次元の合成データに、 100 件ずつのミニバッチで中心を更新する MiniBatchKMeans を当てます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import numpy as np
from sklearn.cluster import MiniBatchKMeans

# ── この抜粋だけで動くように、大きめのデータを作る ──
# MiniBatchKMeans は「1 度に全件を見ない」ので大規模でも速い、という例
rng = np.random.default_rng(0)
X_large = rng.normal(size=(100_000, 4))    # 10 万件 × 4 次元(合成データ)

mbk = MiniBatchKMeans(n_clusters=4, batch_size=100, n_init=10, random_state=42)
mbk.fit(X_large)  # 件数が増えても数秒
print('クラスタ中心の形:', mbk.cluster_centers_.shape)
📤 実行例(実測) クラスタ中心の形: (4, 4)
💬 読み方:出力は中心の配列の形 (4, 4) だけで、 4 クラスタ × 4 次元の中心ができたことしか確かめていない。 データは 1 つの正規分布から作った乱数でクラスタ構造がもともと無いので、 ここでは「10 万件でもすぐ終わる」ことを見る例であり、 中心の位置に意味はない。

(C) scipy.cluster.vq — 低レベル

🎯 このコードでやること:同じ 4 指標を scipy の whiten で列ごとに尺度をそろえ、 scipy.cluster.vq の kmeans と vq で 4 クラスタに分けます。

📥 入力例(SSDSE-B-2026 の 2023 年・47 都道府県から 3 行) 都道府県 総人口 出生数 死亡数 婚姻件数 北海道 5,092,000 24,430 75,120 17,281 東京都 14,086,000 86,348 137,241 71,774 沖縄県 1,468,000 12,549 15,110 6,316 …(全 47 行)
1
2
3
4
5
6
7
8
9
10
from scipy.cluster.vq import kmeans, vq, whiten
import numpy as np

# whiten で各列を標準偏差 1 に揃える(scipy の kmeans は自分では揃えない)
Xw = whiten(X)
centroids, distortion = kmeans(Xw, 4, seed=0)
clusters, _ = vq(Xw, centroids)
print('クラスタ別件数:', np.bincount(clusters).tolist())
print(f'distortion(中心までの平均距離): {distortion:.3f}')
# シンプルだが n_init などのオプションが少ない
📤 実行例(実測) クラスタ別件数: [3, 23, 6, 15] distortion(中心までの平均距離): 0.381
💬 読み方:件数は 3・23・6・15 で、 東京都・神奈川県・大阪府の 3 都府県が 1 群、 北海道・埼玉・千葉・愛知・兵庫・福岡の 6 道県が 1 群になった。 sklearn(東京都単独 + 8 道府県)と分け方が違うのは、 scipy の kmeans が k-means++ ではなく乱数で選んだ点から始め、 distortion(平均距離 0.381)で判定するためで、 seed を変えると結果も変わる。

(D) k-medoids (PAM) — ロバスト代替

🎯 このコードでやること:4 指標を標準化し、 中心を実在の県に限る k-medoids(マンハッタン距離)で 4 クラスタに分けます。

1
2
3
4
5
6
from sklearn_extra.cluster import KMedoids
from sklearn.preprocessing import StandardScaler

Xs = StandardScaler().fit_transform(X)   # 桁をそろえてから距離を測る
kmed = KMedoids(n_clusters=4, metric='manhattan', random_state=42).fit(Xs)
# 中心が「実在サンプル」になる。 外れ値に強い
💬 読み方:scikit-learn-extra が必要なので本ページでは実行していない。 kmed.medoid_indices_ で中心になった 4 県の番号が分かり、 平均で中心を作る k-means と違って東京都 1 県に中心が引きずられることがない。

(E) Gaussian Mixture — 確率的拡張

🎯 このコードでやること:4 指標を標準化し、 共分散を自由に取る GMM(4 成分)で各県の所属確率を出します。

1
2
3
4
5
6
7
8
9
10
import numpy as np
from sklearn.mixture import GaussianMixture
from sklearn.preprocessing import StandardScaler

Xs = StandardScaler().fit_transform(X)   # 4 指標の桁をそろえる
gmm = GaussianMixture(n_components=4, covariance_type='full', random_state=42).fit(Xs)
proba = gmm.predict_proba(Xs)  # 各クラスタへの所属確率
print('クラスタ別件数:', np.bincount(proba.argmax(axis=1), minlength=4).tolist())
print('最大所属確率が 0.9 未満の県数:', int((proba.max(axis=1) < 0.9).sum()))
# 楕円体クラスタOK、 ソフトクラスタリング
📤 実行例(実測) クラスタ別件数: [20, 8, 18, 1] 最大所属確率が 0.9 未満の県数: 2
💬 読み方:最も確からしい成分で数えると 20・8・18・1 県で、 東京都 1 県と北海道 + 大都市圏 7 府県の 8 道府県は k-means と同じ顔ぶれになった。 最大所属確率が 0.9 未満なのは石川県(0.78)と長崎県(0.86)の 2 県だけで、 k-means ではこの迷いが 0/1 の割当に隠れてしまう。

(F) FAISS — 数億規模の超高速

🎯 このコードでやること:4 指標を標準化して float32 に直し、 faiss の Kmeans で 4 クラスタの中心を学習して各県を最も近い中心に割り当てます。

1
2
3
4
5
6
7
8
import faiss
from sklearn.preprocessing import StandardScaler

Xs = StandardScaler().fit_transform(X).astype('float32')   # faiss は float32 を要求
kmeans_faiss = faiss.Kmeans(d=Xs.shape[1], k=4, niter=20, verbose=False, seed=42)
kmeans_faiss.train(Xs)
_, labels = kmeans_faiss.index.search(Xs, 1)
# GPU 対応、 大規模ベクトル検索(埋め込み)に必須
💬 読み方:faiss が必要なので本ページでは実行していない。 labels は 47 行 × 1 列の配列で返るので、 sklearn のラベルと比べるときは labels.ravel() で 1 次元に直す。 47 県程度なら sklearn で十分で、 faiss が効くのは埋め込みベクトルが数百万件ある場面。

⚠️ よくある落とし穴

❌ k を恣意的に決める
「とりあえず k=3 でやりました」は通用しない。 必ずエルボー法とシルエット係数で客観的に決定。 さらに事前理論(「3地域に分けたい」)と整合性を確認。 複数の k で結果を比較するのも頑健性チェックとして有効。 実例:n=47 都道府県なら、 k=2〜7 が現実的範囲。 k=10 など細かすぎると各クラスタが少数で意味を失う。
❌ 標準化なしで実行(致命的)
「人口」(億単位)と「失業率」(%)を標準化せずに距離を計算すると、 桁の大きい変数だけで距離が決まり、 無意味なクラスタになります。 必ず StandardScaler で平均0・分散1に揃えてから。

具体例:人口100万人の差は、 失業率5%の差より桁が6つ違う。 ユークリッド距離はほぼ「人口の差」になり、 失業率の情報がほぼ消える。
❌ 1回だけ実行して採用
初期化のランダム性に依存するので、 通常は異なる初期値で複数回実行し、 最良の SSE を持つ結果を採用。 scikit-learn の KMeans(n_init=10) は10回試行して最良を返す("auto" なら自動調整)。 学術論文では random_state を固定し再現可能にする。
❌ 非球形クラスタに無理やり
三日月型、 渦巻き型、 同心円のような複雑な形状は、 k-means では分割できません。 「球形クラスタ前提」の手法だから。 こうしたデータには DBSCAN(密度ベース)、 Spectral Clustering(グラフ理論)、 GMM(楕円体クラスタOK)などへ。

判定方法:PCA で2次元プロットして、 クラスタの形状を視覚的に確認。
❌ 外れ値の影響を無視
k-means の中心は平均なので、 1個の極端な値で中心が大きく動きます。 例えば「東京」が他から離れていると、 東京を含むクラスタの中心が東京寄りになり、 他県との関係が歪む。 対処:(i) 外れ値を別クラスタにする(k+1で実行)、 (ii) k-medians や k-medoids を使う、 (iii) 外れ値を事前除外。
❌ クラスタ結果を「客観的真実」と思う
k-means は「与えた指標と距離の枠組みで似ているもの」を集めるだけ。 違う指標で実行すれば違うクラスタが出ます。 結果は分析者の選択の反映。 「このデータをこう分類した」という形で報告すべき。 「真のクラスタ」が存在する前提自体が幻想のことも。
✅ クラスタの解釈をしっかり
「k=4 でクラスタリングしました」だけでは研究にならない。 各クラスタの変数の平均値・標準偏差・代表サンプルを確認し、 「クラスタ1は何の特徴を持つ群か」を解釈・命名する。 ここが分析者の腕の見せどころ。 例:クラスタA=「大都市」、 B=「地方中核」、 C=「地方過疎」のように。

📊 SSDSE実データでの応用

2023 年度 47 都道府県の家計 5 項目(二人以上の世帯の食料費・住居費・光熱・水道費・教育費・教養娯楽費)を標準化して k-means (k=3) を適用しました:

家計 5 項目の k-means(k=3)の結果を PCA の PC1×PC2 平面に描いた散布図

PC1(寄与率 50%、 食料費・教育費・教養娯楽費が大きいほど右)と PC2(23%、 光熱・水道費が大きく住居費が小さいほど上)の散布図上で、 3 つのクラスタはおおむね別の場所に集まりますが、 境目では接していて「綺麗に分離」とまでは言えません(シルエット係数 0.27)。 各クラスタの平均を見ると「家計タイプ」として解釈できます:クラスタ 2(17 都府県:東京・埼玉・神奈川・大阪・愛知など)は食料費 86.3・教育費 13.7・教養娯楽費 31.4 千円と支出が大きい都市型、 クラスタ 3(15 道県:北海道・東北・北陸・山陰など)は光熱・水道費 27.8 千円が突出した寒冷地型、 クラスタ 1(15 県:四国・九州・沖縄など)は食料費 76.2・教育費 7.4 千円と全体に支出が小さい型です。

🎯 k の選び方

家計 5 項目での k=1〜8 の inertia と k=2〜8 のシルエット係数

エルボー法とシルエット法で最適な k を探します。

⚠️ k-means の限界

これらの限界を超えるには、 GMM、 DBSCAN、 スペクトラルクラスタリングなどを検討。

⚠️ 追加の落とし穴 — k-means の実務(既存に追加)

❌ 高次元データに素朴に適用
次元数 d > 50-100 になると次元の呪いで距離の差が縮まり、 クラスタ構造が見えなくなる。 対策:(i) 事前に PCA で次元削減(10-20次元へ)、 (ii) UMAP・t-SNE で2-3次元化(可視化用)、 (iii) ドメイン知識で重要変数のみ選択、 (iv) スパースな高次元なら別アプローチ(NMF 等)。 SSDSE-B でも全100変数を入れるのは推奨せず、 6-12 変数に絞るのが現実的。
❌ 「クラスタ数 = 自然な真の数」と思い込む
エルボー法・シルエットで「最適 k」を求めても、 それは数学的最適にすぎない。 データに真のクラスタ構造があるとは限らない。 連続的に分布するデータでも k-means は強制的に k 個に分割する。 ギャップ統計量で「クラスタ構造の有無」を事前検定するか、 結果を別の k で複数試して頑健性を確認する。 「分割結果」と「自然な群」を混同しない。
❌ クラスタサイズの不均衡を無視
k-means は「球形クラスタかつ等サイズ」を暗黙に仮定する目的関数を最小化する。 実データではクラスタが大小さまざまで、 小クラスタが大クラスタに吸収されたり、 大クラスタが不自然に分割されることがある。 サイズ不均衡を考慮するなら、 GMM、 重み付き k-means、 階層クラスタリング、 DBSCAN(密度ベース)などを検討。
❌ クラスタラベルを「順序」と扱う
k-means の出力ラベル(0, 1, 2, ...)は名義尺度であり、 順序や大小に意味はない。 「クラスタ0より2が良い」のような比較は無意味。 また実行ごとにラベル番号が変わるため、 報告時にはクラスタ特性で命名(「都市型」「過疎型」など)するのが標準。 ラベル番号での集計や可視化は誤解を生む。
❌ 新サンプルの分類を考慮しない
k-means は訓練時に決まった中心に新サンプルを割り当てる(最近隣中心)。 これを「分類モデル」と思い込むのは要注意:新サンプルが既存どのクラスタにもフィットしない場合、 強制的に最寄りクラスタに入れられる。 異常検知が必要な場面では、 距離の閾値超過を「クラスタ外」として扱う実装が必要。
❌ カテゴリ変数を素のまま入れる
k-means はユークリッド距離ベースなので、 カテゴリ変数(性別・地域・色など)には適用できない。 one-hot エンコード後でも、 高次元でスパースになり距離が無意味化する。 カテゴリ混在データには k-modes(カテゴリ用)、 k-prototypes(混在用)を使う。 距離尺度に Gower 距離を採用した階層クラスタリングも代替候補。
❌ 結果の再現性を担保しない
k-means の初期化はランダムなので、 random_state を固定しないと毎回違う結果になる。 学術論文・本番運用では必ず random_state=42 等を明示。 また n_init(複数初期化試行回数)の指定も再現性に影響する。 さらに、 ハイパーパラメータ・前処理 (標準化方法) も全て記録し、 完全な再現性確保が責任。

📝 補足解説(直感・落とし穴・発展の深掘り)

既存の各節を壊さずに、 k-means の「なぜ動くか(直感)」「どこで壊れるか(落とし穴)」「どう拡張するか(発展)」を一枚に凝縮した追記ノート。 数値はいずれも SSDSE-B-2026(2023年・47都道府県、 標準化済み6変数)の実測。

🎨 直感の芯 — Lloyd 法は「割当」と「平均」の往復

k-means の反復(Lloyd 法)は 2 手番の往復に尽きる。 (1) 割当ステップ:各点を最も近い重心に割り当てる(=空間を重心ごとのボロノイ領域に分割)。 (2) 更新ステップ:各クラスタの重心を、 所属点の平均座標へ動かす。 これを重心が動かなくなるまで繰り返す。

なぜ必ず収束するのか:目的関数は「クラスタ内平方和(inertia=慣性)」=各点から自分の重心までの二乗距離の総和。 割当ステップは「より近い重心へ移す」ので inertia を増やさない。 更新ステップも inertia を増やさない — なぜなら「二乗距離の和を最小にする点」は、 その集合の平均そのものだから。 平均に動かすこと自体が SSE を必ず下げる。 両ステップとも単調に inertia を減らす(または不変)ので、 取りうる割当が有限である以上、 有限回で収束する。 これが「means(平均)」が中心の定義に選ばれた数学的理由でもある。

※ 単調収束するのは「ある局所最適へ」であって、 大域最適とは限らない(後述)。 これが初期値依存と n_init の伏線になる。

⚠️ 落とし穴チェックリスト(要点の凝縮)

🚀 発展① k-means++ 対 random 初期化(SSDSE-B-2026 実測)

このコードでやること:単一初期化(n_init=1)を 30 通りの乱数で走らせ、 k-means++ と素朴な random(Forgy)で inertia の平均・最良を比較。 初期化の賢さが「良い解に届く確率」に効くことを定量化する。

📥 入力: 本ページ ② の X_std(実在6変数 A1101・A1303・A4101・B4101・L3221・C5401、 標準化済み、 47都道府県)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler

# 本ページ ② と同じ X_std(2023 年・47 都道府県 × 実在 6 変数、標準化済み)を作り直す
d = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
d = d[d['SSDSE-B-2026'] == 2023]
cols = ['A1101', 'A1303', 'A4101', 'B4101', 'L3221', 'C5401']
X_std = StandardScaler().fit_transform(d[cols].fillna(d[cols].mean()).values)

def stat(init):
    v = [KMeans(4, init=init, n_init=1, random_state=rs).fit(X_std).inertia_
         for rs in range(30)]
    return round(float(np.mean(v)), 2), round(float(np.min(v)), 2)

print('k-means++ (平均, 最良):', stat('k-means++'))
print('random    (平均, 最良):', stat('random'))

📤 実行例:

k-means++ (平均, 最良): (87.59, 78.88) random (平均, 最良): (96.41, 80.08)

💬 結果の読み方: 1 回きりの初期化だと、 k-means++ は平均 inertia 87.59・最良 78.88 に届きやすいのに対し、 random は平均 96.41 と局所最適に留まりがち。 k-means++ が「離れた点を初期重心に選ぶ」ぶん良い解へ寄る。 それでも 1 回では最良に届かない試行があるため、 実務では n_init≥10 の複数初期化が保険になる(本ページ ④ の安定性検証と対応)。

🚀 発展② GMM のソフト割当と k-means のハード割当の対比(実測)

このコードでやること:同じ X_std に GMM(ガウス混合モデル)を当て、 各県の「所属確率」を出す。 k-means が全県を 1 クラスタにハード割当するのに対し、 GMM は確率でソフト割当し、 境界にいる県を数値で炙り出せる。

📥 入力: 本ページ ② の X_std と df(Prefecture 列)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.mixture import GaussianMixture

# 途中のブロックで df を日本語列名で読み直しているので、ここで英字コード版に読み直す
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)   # 2023 年の 47 都道府県
X_std = StandardScaler().fit_transform(
    df[['A1101', 'A1303', 'A4101', 'B4101', 'L3221', 'C5401']].astype(float))

gmm = GaussianMixture(n_components=4, covariance_type='full',
                      random_state=42).fit(X_std)
proba = gmm.predict_proba(X_std)          # 各県 × 各クラスタの所属確率
maxp = proba.max(axis=1)                   # 最も確からしいクラスタの確率
i = maxp.argmin()                          # 最も「迷っている」県
print('境界県:', df['Prefecture'].values[i], round(float(maxp[i]), 3))
print('確率:', [round(float(p), 2) for p in proba[i]])
print('確率0.9未満の県数:', int((maxp < 0.9).sum()), '/ 47')

📤 実行例:

境界県: 富山県 0.84 確率: [0.84, 0.0, 0.16, 0.0] 確率0.9未満の県数: 1 / 47

💬 結果の読み方: 富山県は 2 つのクラスタに 0.84 対 0.16 でまたがる「境界県」。 k-means のハード割当ではこの曖昧さは 0/1 に潰れて見えないが、 GMM のソフト割当なら数値化できる。 残る 46 県も最大確率は石川県の 0.93 が最低で、 ほかは 0.96 以上とほぼ迷いなく割り当たっている。 「割当の硬さ」と「楕円体クラスタ(共分散)を許すか」が k-means と GMM の設計差。

🔗 関連ページ(相対リンク・実在ファイルのみ)

クラスタリング(総論)
教師なしグループ化の枠組み。 k-means はその代表。
標準化
距離を単位差から守る必須前処理。
距離尺度
ユークリッド/マンハッタン/コサイン。
GMM(ガウス混合)
ソフト割当・楕円体クラスタの確率的拡張。
階層クラスタリング
k を後から選べる。 デンドログラム/Ward法。
デンドログラム
結合過程の樹形図。 任意の高さで切って k を決める。
Ward法
分散増加最小で結合。 k-means と相性が良い。
DBSCAN
密度ベース。 非球状・外れ値に強い。
スペクトラルクラスタリング
グラフ構造で非凸クラスタを分離。
主成分分析(PCA)
前段の次元削減・後段の可視化。
次元削減
次元の呪いを避け距離を意味あるものに。

※ 「シルエット係数」「エルボー法」「k-means++」「k-medoids」は本用語集に単独ページが未整備のため、 リンクにせず本文(本ページ ③④、 数式節、 落とし穴節)内の解説を参照。

🗺️ 概念マップ — 3つの視点で体系を理解する

k-means法 がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。

📍 体系階層のパス

🌐 統計・データサイエンス › 教師なし学習 › クラスタリング › k-means

① 🔗 関係マップ — 「他の手法とどう繋がっているか」

中心に k-means法 を置き、 そこから クラスタリング・階層クラスタリング・標準化・平均・分散・Ward法 など 計 13 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。

凡例:現在の用語上位カテゴリ兄弟(並列)前提発展形応用先2階層先

② ⭕ 包含マップ — 「どのカテゴリに含まれているか」

大きな円が小さな円を包含する Circle Packing 図。 「k-means法」は緑色でハイライト。

📍現在地:統計・データサイエンス

③ 🌳 ツリーマップ — 「面積で見るボリューム比較」

長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「k-means法」は緑色でハイライト。

🎯 3つのマップの使い分け

マップ 分かること こんな時に見る
🔗 関係マップ手法間の横の関係(前提→発展→応用)「次に何を学べばよい?」 学習順序の判断
⭕ 包含マップ分類体系の入れ子構造(上位⊃下位)「この手法はどんなジャンルに属する?」
🌳 ツリーマップ分野の規模比較(面積=ボリューム)「データサイエンス全体の俯瞰像」

💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは k-means の隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス → クラスタリング → k-means という入れ子の位置を示します。 「クラスタリングには他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。

🔗 隣接手法への橋渡し

k-means 法は中心点更新 → 割り当て更新の反復で動作し、 単独で意味を成すには上流の標準化と下流の k 選定・解釈が不可欠である。

上流の標準化が距離計算の前提となり、 並列の階層クラスタリング/DBSCAN で形状仮定 (球状) の妥当性を逆検証し、 下流の PCA 可視化と組み合わせて初めて「k-means が見つけたクラスタ」が解釈可能になる。

🌳 手法選択フロー

クラスタリング手法は「クラスタ形状の前提」「k を事前に決められるか」「データ規模」「外れ値の多さ」で選び分ける。 以下の判断フローで k-means が適切かを確認する。

  1. Step 1 (k は決められるか): 業務要件や事前知識で k が決まっている → k-means / k-means++ が第一候補。 k が不明 → DBSCAN・階層クラスタリングを優先。
  2. Step 2 (クラスタ形状): 球状・等サイズに見える → k-means OK。 細長い・三日月型・ノイズ多 → DBSCAN / spectral clustering。
  3. Step 3 (データ規模): 数万件超え・高次元 → mini-batch k-means。 数百件で精度優先 → 標準 k-means + n_init=10〜50。
  4. Step 4 (外れ値): 外れ値が無視できないほど多い → k-medoids (中央値ベース)。 少ない → 事前除去 + k-means で十分。
  5. Step 5 (検証): 標準化済か / シルエット係数 0.3 以上か / エルボー曲線で k が妥当か / 異なる random_state で再現するか、 を必ず確認する。

「とりあえず k=3」ではなく、 上記 5 ステップを経た選択であれば、 結果のクラスタを業務判断や次工程 (顧客セグメント別施策、 都道府県分類など) に渡せる根拠が立つ。

🔖 キーワード索引(拡張)

直感 計算 🎓 設計判断 🧮 SSDSE-B 実例 🐍 実装バリエーション ⚠️ 落とし穴集 kの決定 DBSCAN代替 関連用語マップ