「k-means」は SSE (クラスタ内二乗距離和) を目的関数とする非階層クラスタリング手法。 本ページの中核キーワードを以下に整理する。
これらのキーワードは「assignment step → update step → 収束判定」と「k 選択 → 初期化 → 形状判定」の 2 軸で k-means 実務を構成する。
🍰 まずはやさしく
似たもの同士を分けるグループ分けです。
データを自動でいくつかの集団にまとめます。
スマホのアプリで好みの人を分けるようなものです。
この手法の結論を短くまとめます。
sklearn.cluster.KMeans(n_clusters=k, n_init="auto")k-means は機械学習の教師なし学習の代表手法。 「事前に決めた k 個のグループに、 似ているサンプル同士を集める」反復アルゴリズム。 1957年に Lloyd が提案し、 1967年に MacQueen が "k-means" と命名。 シンプルかつ高速で、 現在も クラスタリング手法の標準。
「means(平均)」が名前の由来:各クラスタの「中心(centroid)」を、 そのクラスタに属するサンプルの平均位置で表すから。 「k 個の中心」を探すアルゴリズム、 と言えます。
k-means は探索的データ分析の万能ツール。 顧客セグメンテーション、 地域類型化、 異常検知、 画像圧縮(色のクラスタリング)まで幅広く使われます。
🍰 まずはやさしく
データを分けるための定番の道具です。
似ているグループを自動で作るために使います。
都道府県を特徴で分けるときに便利です。
この手法がどこで使われるかを紹介します。
論文で「k-means クラスタリング」「k=4 でクラスタリングした」「エルボー法で k を決定」のように登場する手法。 47都道府県を「似ている県のグループ」に自動分類するときの定番ツール。
k-means法 とは:事前に決めた k 個のクラスタ中心からの距離が最小になるように、反復的にクラスタを更新するアルゴリズム。
🍰 まずはやさしく
点と中心の距離で分けるイメージです。
自然なグループ構造を見つけるために使います。
部活のメンバーを能力で分けるような感覚です。
仕組みを直感的に理解するための例を見ます。

図を見ると、 4色の点群(クラスタ)と各クラスタの × 印(中心)が見えます。 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(クラスタ数)を決めて、 「▶ 1ステップ」で 割り当て(assignment)→ 重心更新(update) を1手ずつ、 「⏩ 自動収束」で最後まで動かせます。 各点が所属クラスタの色に塗られ、 ✕ 印の重心が平均位置へ移動していく様子を観察できます。 「🎲 初期値ランダム」を何度も押すと、 同じ点配置でも初期重心しだいで結果が変わる(初期値依存)ことが体感できます。 右上の WCSS(=SSE, クラスタ内二乗和)が反復のたびに単調に減るのがエルボー法の直感です。
✕ = 重心(centroid, 所属点の平均座標)/ ● = データ点(所属クラスタ色)。 割り当て手番では点の色が、 更新手番では ✕ の位置が変わります。
n_init≥10 で複数初期値を試す(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の中心。
計算量は O(n × k × d × iter)。 大規模データでは k-means++ や Mini-Batch k-means を使用。
「離れた点を初期中心に選ぶ」工夫。 通常のランダム初期化より速く・確実に良い解に収束。 sklearn のデフォルト。
k-means は 4 行のコードで動くが、 「標準化」「k の選択」「初期化のランダム性」「形状の前提」を理解せずに使うと無意味なクラスタが出る。 SSDSE-B-2026 で 4 角度から検証する。
このコードでやること: 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: dict(zip(*pd.Series(km.labels_).value_counts().sort_index().reset_index().values.T)) print('標準化なし: クラスタ別件数', vc(km_raw)) print('標準化あり: クラスタ別件数', vc(km_std)) |
📤 実行例:
💬 結果の読み方: 標準化なしでは「クラスタ 2 が東京 1 件だけ」になる(人口の桁が大きく、 距離計算が人口だけで決まる)。 標準化後は 30/8/9 に分かれ、 総人口の桁に潰されていた気温の情報も距離に効くようになる → 意味のあるクラスタリング。
このコードでやること: 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→2 で 137 と大きく落ち、 k=2→3 は 32、 k=3→4 は 34、 k=4→5 で 20 と鈍りはじめる → 肘は k=4 前後。 SSE の減少幅が明確に小さくなる位置を目安に k=4 を採る。
このコードでやること: 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 が最大(0.555)→「大きく 2 群(大都市圏 vs その他)に分かれている」。 一方エルボーは k=4 前後を推奨。 基準が食い違う場合は分析目的で決める。 「2 群でざっくり」なら k=2、「地域類型を細かく」なら k=4。
このコードでやること: 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 が 78〜95 と大きくばらつく(初期値次第で局所最適に陥る試行がある)。 n_init=20 では毎回ほぼ 78.8 に収束し安定 → 大域最適に到達できている。 実務では必ず n_init≥10 を指定する。
6都道府県のデータ(高齢化率と死亡率)で、 k=2 の k-means が実際にどう動くか、 反復計算を追ってみます。
| 都道府県 | 高齢化率 x | 死亡率 y |
|---|---|---|
| 秋田 | 39 | 19 |
| 高知 | 36 | 17 |
| 大阪 | 28 | 13 |
| 神奈川 | 26 | 11 |
| 東京 | 23 | 9 |
| 沖縄 | 23 | 11 |
初期中心をランダムに:C1 = (40, 20)、 C2 = (20, 10)(k-means++ では確率的に選びますが、 簡単のためここでは固定)
各点から両中心までのユークリッド距離を計算:
| 点 | d(C1) | d(C2) | 割当 |
|---|---|---|---|
| 秋田 (39,19) | √(1+1) = 1.4 | √(361+81) = 21.0 | C1 |
| 高知 (36,17) | √(16+9) = 5.0 | √(256+49) = 17.5 | C1 |
| 大阪 (28,13) | √(144+49) = 13.9 | √(64+9) = 8.5 | C2 |
| 神奈川 (26,11) | √(196+81) = 16.6 | √(36+1) = 6.1 | C2 |
| 東京 (23,9) | √(289+121) = 20.2 | √(9+1) = 3.2 | C2 |
| 沖縄 (23,11) | √(289+81) = 19.2 | √(9+1) = 3.2 | C2 |
C1 クラスタ:秋田、高知(2点)/C2 クラスタ:大阪、神奈川、東京、沖縄(4点)
各クラスタの平均を新しい中心とする:
中心が大きく動きました:C1 は (40,20) → (37.5, 18)、 C2 は (20,10) → (25, 11)
新しい中心で再度距離計算:
| 点 | d(新C1) | d(新C2) | 割当 |
|---|---|---|---|
| 秋田 (39,19) | 1.80 | 16.1 | C1(変化なし) |
| 高知 (36,17) | 1.80 | 12.5 | C1(変化なし) |
| 大阪 (28,13) | 10.7 | 3.6 | C2(変化なし) |
| 神奈川 (26,11) | 13.5 | 1.0 | C2(変化なし) |
| 東京 (23,9) | 17.1 | 2.8 | C2(変化なし) |
| 沖縄 (23,11) | 16.1 | 2.0 | C2(変化なし) |
すべての点の割当が前回と同じ!中心も動かない。 収束完了。 たった2イテレーションで終了しました。
最終結果:
SSE(残差二乗和) = (秋田からC1の距離²) + (高知からC1の距離²) + ... ≈ 1.8² + 1.8² + 3.6² + 1.0² + 2.8² + 2.0² ≈ 34.5。 これより小さい分割は存在しません(この初期値からは)。
k=3 にしたら?大阪・神奈川を1群、 東京・沖縄を別群、 秋田・高知を1群、 のように細分化されます。 SSEはさらに小さく。 でもどこかで「k を増やしても効率が悪い」境界(=エルボー)が来ます。
「いくつのクラスタに分けるか」は分析者が決める。 客観的指標の選択肢:
ランダム初期化は局所最適に陥りやすい。 k-means++ は次の手順で賢く初期化:
scikit-learn の KMeans はデフォルトで k-means++。 さらに n_init 回試行して最良の SSE を採用。
標準はユークリッド距離。 ただし他の選択肢もあります:
ただしユークリッド以外を使うと「平均」が幾何的に意味を失うので、 厳密には k-medoids(中心を実在サンプルに)が適切。
計算量:$O(n \cdot k \cdot d \cdot \text{iter})$。 通常 iter は数十回。 n=100万 でも数秒で完了するのが強み。
収束性:k-means は必ず収束しますが、 大域最適解への収束は保証されません。 SSE は反復のたびに必ず減少(または同じ)となりますが、 局所最適に陥る可能性があります。 これが「複数回試行」が必要な理由。
NP困難性:k-means の大域最適解を求める問題は NP困難(k≥2 で)。 だから「最適解の近似」を反復で求めるしかない。
最新の SSDSE-B-2026 を使い、 47 都道府県を 4 クラスタに分けるコード。 教材標準パスを直書きしています。
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(小さいほど締まりが良い) |
出力例: 東京・大阪は「人口規模グループ」、 秋田・高知は「高齢化グループ」、 沖縄は単独クラスタ寄りなど、 SSDSE-B-2026 の最新値で確かめられます。
読み終えたら、 以下の練習問題で「自分で説明できるか」を確認しよう。 答えが詰まった箇所は対応する節に戻る。
🛠 自分で手を動かす(5 分課題):
SSDSE-B-2026.csv から「総人口 A1101」「65歳以上人口 A1303」「年平均気温 B4101」の 3 変数を抽出し、 StandardScaler で標準化。KMeans(n_clusters=k) を k=2〜8 で回し、 inertia_ の推移をプロットしてエルボーを目視する。silhouette_score を計算してエルボーの選択と一致するか確認する。print で出し、 「クラスタが何を意味するか」を 1 行で言語化する。合成 4 点 1D データで K=2 の 1 反復を手計算する。
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_}") |
💬 手計算 (Step 3) [1.5, 9.0] と Python 出力が完全一致。
🎯 このコードでやること:k-means — 重心型分割クラスタリングに関連するステップ #1。最初のスニペットです。SSDSE-B-2026 を読み込みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | from sklearn.cluster import KMeans, AgglomerativeClustering, DBSCAN from sklearn.preprocessing import StandardScaler from sklearn.metrics import silhouette_score import pandas as pd import numpy as np # データの標準化(重要!) 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(f'クラスタ中心: {km.cluster_centers_}') print(f'inertia: {km.inertia_}') # 階層クラスタリング(Ward法) agg = AgglomerativeClustering(n_clusters=3, linkage='ward') labels_agg = agg.fit_predict(X_std) # シルエットスコアで評価 score = silhouette_score(X_std, labels_km) print(f'シルエットスコア: {score:.3f}') |
🎯 このコードでやること:k-means — 重心型分割クラスタリングに関連するステップ #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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | # ── この抜粋で使うデータを用意します(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_)) # エルボー法 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-means — 重心型分割クラスタリングに関連するステップ #3。可視化(散布図/樹形図/時系列プロット)を描きます。
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() |
🎯 このコードでやること:k-means — 重心型分割クラスタリングに関連するステップ #8。比較・別パターンを検討します。
1 2 3 4 5 6 | from sklearn.cluster import KMeans km = KMeans(n_clusters=4, init='k-means++', n_init='auto', max_iter=300, random_state=42).fit(X) print(km.labels_) # 各サンプルのクラスタ番号 print(km.cluster_centers_) # 中心 print(km.inertia_) # SSE |
🎯 このコードでやること:k-means — 重心型分割クラスタリングに関連するステップ #9。ハイパーパラメータを変えて再計算します。
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) |
🎯 このコードでやること:k-means — 重心型分割クラスタリングに関連するステップ #10。最終結果のまとめ・保存を行います。
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 | # ── この抜粋で使うデータを用意します(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 scipy.cluster.vq import kmeans, vq centroids, distortion = kmeans(X, 4) clusters, _ = vq(X, centroids) # シンプルだが n_init などのオプションが少ない |
🎯 このコードでやること:k-means — 重心型分割クラスタリングに関連するステップ #11。可視化を仕上げ、レポートに統合します。
1 2 3 | from sklearn_extra.cluster import KMedoids kmed = KMedoids(n_clusters=4, metric='manhattan', random_state=42).fit(X) # 中心が「実在サンプル」になる。 外れ値に強い |
🎯 このコードでやること:k-means — 重心型分割クラスタリングに関連するステップ #12。追加検証・感度分析を実行します。
1 2 3 4 | from sklearn.mixture import GaussianMixture gmm = GaussianMixture(n_components=4, covariance_type='full', random_state=42).fit(X) proba = gmm.predict_proba(X) # 各クラスタへの所属確率 # 楕円体クラスタOK、 ソフトクラスタリング |
🎯 このコードでやること:k-means — 重心型分割クラスタリングに関連するステップ #13。応用パターン(別データ・別手法)に拡張します。
1 2 3 4 5 | import faiss kmeans_faiss = faiss.Kmeans(d=X.shape[1], k=4, niter=20, verbose=False) kmeans_faiss.train(X.astype('float32')) _, labels = kmeans_faiss.index.search(X.astype('float32'), 1) # GPU 対応、 大規模ベクトル検索(埋め込み)に必須 |
StandardScaler で平均0・分散1に揃えてから。 KMeans(n_init=10) は10回試行して最良を返す("auto" なら自動調整)。 学術論文では random_state を固定し再現可能にする。47都道府県の家計5項目データに k-means (k=3) を適用しました:
PC1 と PC2 の散布図上で、 3つのクラスタが綺麗に分離されているのが見えます。 各クラスタの特徴を解釈することで「家計タイプ」が見出せます。
エルボー法とシルエット法で最適な k を探します。
これらの限界を超えるには、 GMM、 DBSCAN、 スペクトラルクラスタリングなどを検討。
random_state を固定しないと毎回違う結果になる。 学術論文・本番運用では必ず random_state=42 等を明示。 また n_init(複数初期化試行回数)の指定も再現性に影響する。 さらに、 ハイパーパラメータ・前処理 (標準化方法) も全て記録し、 完全な再現性確保が責任。既存の各節を壊さずに、 k-means の「なぜ動くか(直感)」「どこで壊れるか(落とし穴)」「どう拡張するか(発展)」を一枚に凝縮した追記ノート。 数値はいずれも SSDSE-B-2026(2023年・47都道府県、 標準化済み6変数)の実測。
k-means の反復(Lloyd 法)は 2 手番の往復に尽きる。 (1) 割当ステップ:各点を最も近い重心に割り当てる(=空間を重心ごとのボロノイ領域に分割)。 (2) 更新ステップ:各クラスタの重心を、 所属点の平均座標へ動かす。 これを重心が動かなくなるまで繰り返す。
なぜ必ず収束するのか:目的関数は「クラスタ内平方和(inertia=慣性)」=各点から自分の重心までの二乗距離の総和。 割当ステップは「より近い重心へ移す」ので inertia を増やさない。 更新ステップも inertia を増やさない — なぜなら「二乗距離の和を最小にする点」は、 その集合の平均そのものだから。 平均に動かすこと自体が SSE を必ず下げる。 両ステップとも単調に inertia を減らす(または不変)ので、 取りうる割当が有限である以上、 有限回で収束する。 これが「means(平均)」が中心の定義に選ばれた数学的理由でもある。
※ 単調収束するのは「ある局所最適へ」であって、 大域最適とは限らない(後述)。 これが初期値依存と n_init の伏線になる。
k-means++(離れた点を確率的に初期重心へ)+ n_init≥10 で緩和。 大域最適を求める問題自体は NP 困難なので、 反復は「良い近似」を狙う手段。このコードでやること:単一初期化(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 | import numpy as np from sklearn.cluster import KMeans 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')) |
📤 実行例:
💬 結果の読み方: 1 回きりの初期化だと、 k-means++ は平均 inertia 87.59・最良 78.88 に届きやすいのに対し、 random は平均 96.41 と局所最適に留まりがち。 k-means++ が「離れた点を初期重心に選ぶ」ぶん良い解へ寄る。 それでも 1 回では最良に届かない試行があるため、 実務では n_init≥10 の複数初期化が保険になる(本ページ ④ の安定性検証と対応)。
このコードでやること:同じ 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', 'L3221']].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') |
📤 実行例:
💬 結果の読み方: 富山県は 2 つのクラスタに 0.84 対 0.16 でまたがる「境界県」。 k-means のハード割当ではこの曖昧さは 0/1 に潰れて見えないが、 GMM のソフト割当なら数値化できる。 残る 46 県は最大確率がほぼ 0.99 で、 データがよく分離していることの裏付けにもなる。 「割当の硬さ」と「楕円体クラスタ(共分散)を許すか」が k-means と GMM の設計差。
※ 「シルエット係数」「エルボー法」「k-means++」「k-medoids」は本用語集に単独ページが未整備のため、 リンクにせず本文(本ページ ③④、 数式節、 落とし穴節)内の解説を参照。
k-means法 がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 統計・データサイエンス › 教師なし学習 › クラスタリング › k-means
中心に k-means法 を置き、 そこから クラスタリング・階層クラスタリング・標準化・平均・分散・Ward法 など 計 13 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。
大きな円が小さな円を包含する Circle Packing 図。 「k-means法」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「k-means法」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは k-means の隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス → クラスタリング → k-means という入れ子の位置を示します。 「クラスタリングには他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。
k-means 法は中心点更新 → 割り当て更新の反復で動作し、 単独で意味を成すには上流の標準化と下流の k 選定・解釈が不可欠である。
上流の標準化が距離計算の前提となり、 並列の階層クラスタリング/DBSCAN で形状仮定 (球状) の妥当性を逆検証し、 下流の PCA 可視化と組み合わせて初めて「k-means が見つけたクラスタ」が解釈可能になる。
クラスタリング手法は「クラスタ形状の前提」「k を事前に決められるか」「データ規模」「外れ値の多さ」で選び分ける。 以下の判断フローで k-means が適切かを確認する。
「とりあえず k=3」ではなく、 上記 5 ステップを経た選択であれば、 結果のクラスタを業務判断や次工程 (顧客セグメント別施策、 都道府県分類など) に渡せる根拠が立つ。