🎓 レベル:標準 | 重要度:A(必須)
📎 関連:第6章 セグメンテーション 目次 | 前提:RFM分析(リセンシー・フリークエンシー・マネタリー)
要点(BLUF)
- RFM分析(リセンシー・フリークエンシー・マネタリー) は R/F/M の3軸に人が決めたルールで顧客を分ける手法でした。これに対しクラスタリングは、顧客の特徴量から似た者どうしを教師なしで自動グループ化します。軸もルールも事前に決めず、「データの中にある自然な塊」を機械に見つけさせるのです。代表手法が k-means で、流れは 標準化(z化)→ k-means → クラスタ数 の選択(エルボー法)→ クラスタの解釈(プロファイリング)。
- k-means は級内平方和(WCSS=各点とそのクラスタ中心との距離の二乗和)を最小化します。ここで標準化が必須です——距離はスケールに依存するので、桁の大きい変数(単価)が小さい変数(購入回数)を押しつぶしてしまうからです。また は事前に決める必要があり、エルボー法( を増やしたときの inertia の折れ曲がり)で選びます。
- 3つの真の潜在クラスタを持つ顧客特徴(年間購入回数・平均単価、)を合成すると、単価の標準偏差が購入回数の約 235 倍で z化なしには距離が単価だけで決まってしまうこと、エルボーが で明確に折れること(inertia 、 までで激減し以降は鈍る)、そして真の中心 を推定中心 とほぼ完全に回復(真ラベルとの一致率 99.7%)することを示します。ただし教師なしなのでクラスタの「意味」は人が解釈します。RFM(解釈容易だが軸固定)とクラスタリング(多変量だが要解釈)は使い分けです。
1. ルールから「自動グループ化」へ:教師なしセグメンテーション
RFM分析(リセンシー・フリークエンシー・マネタリー) は強力ですが、2つの制約がありました。軸が R/F/M の3つに固定されていること、そしてセグメントの境界(「 かつ…なら優良客」)を人が決めることです。実際の顧客は、年間購入回数・平均単価・来店間隔・カテゴリ嗜好・割引感応度…と多変量で、しかも「自然な顧客の塊」がどの変数のどのあたりにあるかは、ふつう事前には分かりません。人が決めた格子で切ると、本当の塊を分断したり、空っぽのセグメントを作ったりします。
そこで発想を逆転します。ルールを与えるのをやめ、データに塊を見つけさせる——これが教師なし学習(unsupervised learning)であり、その代表がクラスタリングです。「正解の所属ラベル」を一切使わず、特徴空間上で近い点どうしをまとめることだけを頼りに、顧客を 個のグループに分けます。
最も広く使われるのが k-means です。アイデアは驚くほど単純で、次を繰り返すだけです。
- 個の中心を置く
- 各顧客を最も近い中心のグループに割り当てる(割り当てステップ)
- 各グループの中心を、所属する点の平均に更新する(更新ステップ)
- 割り当てが変わらなくなるまで 2〜3 を繰り返す
flowchart LR X["顧客の特徴量(多変量)"] --> Z["標準化 z化(各次元を平均0・分散1へ)"] Z --> KM["k-means:中心を置く→最寄りに割当→中心を更新…"] KM --> EL["エルボー法でクラスタ数 k を選ぶ"] EL --> PROF["プロファイリング:各クラスタの平均特徴を見て命名(人が解釈)"]
RFM とクラスタリングは好対照です。RFM は人が軸とルールを決めるので解釈は楽ですが、3軸より柔軟にはなれません。クラスタリングはデータに塊を見つけさせるので多変量で柔軟ですが、出てきた塊が「何者か」は後から人が解釈しなければなりません。どちらが上ということはなく、目的次第の使い分けです(§⚠️)。
2. k-means とエルボー法(数式)
k-means が最小化するのは級内平方和(within-cluster sum of squares, WCSS)、すなわち各点と自分のクラスタ中心との距離の二乗和です。クラスタ の中心 はそのクラスタに属する点の平均で定義されます。
上の割り当て/更新の反復(Lloyd のアルゴリズム)は、この WCSS を単調に減らしながら局所最適へ収束します。割り当てステップは「中心を固定して各点を最寄りへ」=WCSS を下げ、更新ステップは「割り当てを固定して中心を平均へ」=同じく WCSS を下げる(平均は二乗距離和を最小化する点)からです。WCSS(実装上は inertia と呼ぶ)が、クラスタの「まとまりの良さ」の指標になります。
ここで標準化(z化)が決定的に効きます。WCSS は距離 に基づきますが、距離は変数のスケールに敏感です。単価(数千円)と購入回数(数十回)をそのまま使うと、二乗距離は単価の項がほぼすべてを決め、購入回数は無視同然になります。そこで各変数を平均 ・標準偏差 で割って無次元化します。
これで全次元が平均 ・分散 に揃い、どの変数も距離に対等に効くようになります(どの変数をどれだけ重視するか別の重みを付けたい場合は別途設計しますが、まずは対等が出発点です)。
最後にクラスタ数 の選択。k-means は を与えなければ動きませんが、 を増やせば inertia は必ず減ります(極端には で各点が自分だけのクラスタになり inertia )。だから「inertia 最小」で選ぶと無意味に細かくなります。代わりに**エルボー法(elbow method)**を使います。 を と増やしながら inertia をプロットし、**減り方が急に鈍る「肘(elbow)」**を最適 とみなすのです。肘より手前では「クラスタを1つ増やすと大きくまとまりが改善」、肘より先では「増やしてもほとんど改善しない(過剰分割)」——その境目が、データが持つ自然な塊の数だと考えます。
3. 顧客特徴をクラスタリングする(コード)
3つの真の潜在クラスタ(ライト層・高単価層・ヘビー層)を持つ顧客特徴を合成し、z化 → scipy.cluster.vq.kmeans2(k-means++ 初期化・seed 固定)で → エルボーで を確認 → クラスタ別プロファイル → 真の中心と推定中心の照合を行います。数式との対応:inertia 関数が WCSS、Z が z化後の特徴、kmeans2 が Lloyd の反復です。
import numpy as np
import pandas as pd
from scipy.cluster.vq import kmeans2
from scipy.spatial.distance import cdist
# === 3つの真の潜在クラスタを持つ顧客特徴を合成(2次元:年間購入回数・平均単価)===
rng = np.random.default_rng(1)
N = 600
centers_true = np.array([[ 5.0, 2000.0], # ライト:低頻度・低単価
[12.0, 8000.0], # 高単価:中頻度・高単価
[30.0, 4000.0]]) # ヘビー:高頻度・中単価
sizes = [240, 180, 180] # 各クラスタの人数(合計600)
spreads = np.array([[2.0, 500.0],
[3.0, 1200.0],
[5.0, 900.0]])
parts, labels_true = [], []
for k, (c, n, sp) in enumerate(zip(centers_true, sizes, spreads)):
parts.append(rng.normal(c, sp, size=(n, 2)))
labels_true += [k] * n
X = np.vstack(parts)
X[:, 0] = np.clip(X[:, 0], 1.0, None) # 購入回数は1以上
X[:, 1] = np.clip(X[:, 1], 200.0, None) # 単価は200円以上
labels_true = np.array(labels_true)
print(f"顧客 N={N}、特徴量2次元(年間購入回数・平均単価)、真のクラスタ数=3")
# === 標準化(z化):z=(x-平均)/標準偏差。スケールの大きい単価が距離を支配しないように ===
mu, sd = X.mean(axis=0), X.std(axis=0)
Z = (X - mu) / sd
print(f"z化前のスケール差:購入回数 sd={X[:,0].std():.1f} / 単価 sd={X[:,1].std():,.0f}(約{X[:,1].std()/X[:,0].std():.0f}倍)")
# === k-means(k=3):scipy.cluster.vq.kmeans2、k-means++ 初期化・seed固定 ===
centroids_z, labels_km = kmeans2(Z, 3, seed=0, minit="++")
centroids_raw = centroids_z * sd + mu # z空間の中心を元のスケールに戻す
# === エルボー法:k=1..6 で級内平方和 inertia を計算 ===
def inertia(Z, k):
cz, lab = kmeans2(Z, k, seed=0, minit="++")
return float(((Z - cz[lab]) ** 2).sum())
ks = list(range(1, 7))
inertias = [inertia(Z, k) for k in ks]
print("\n=== エルボー法:クラスタ数 k と級内平方和 inertia ===")
for k, v in zip(ks, inertias):
drop = "" if k == 1 else f" (前kから減少 {inertias[k-2]-v:.0f})"
print(f" k={k}: inertia={v:8.1f}{drop}")
print(" → k=3 までは大きく減り、k=4 以降は減りが鈍る(折れ曲がり=3が妥当)")
# === 推定クラスタを真のクラスタに対応づけ(中心が最も近いもの同士)===
match = cdist(centroids_raw, centers_true).argmin(axis=1) # 推定i → 真のmatch[i]
# === クラスタ別プロファイル(人数・平均特徴)と中心の対応 ===
prof = pd.DataFrame({
"推定クラスタ": np.arange(3),
"対応する真クラスタ": match,
"人数": [int((labels_km == i).sum()) for i in range(3)],
"平均_購入回数": [X[labels_km == i, 0].mean() for i in range(3)],
"平均_単価": [X[labels_km == i, 1].mean() for i in range(3)],
}).sort_values("対応する真クラスタ").reset_index(drop=True)
print("\n=== クラスタ別プロファイル(推定)===")
print(prof.to_string(index=False, formatters={
"平均_購入回数": "{:.1f}".format, "平均_単価": "{:,.0f}".format}))
cmp = pd.DataFrame({
"真_購入回数": centers_true[:, 0],
"推定_購入回数": [centroids_raw[np.where(match == t)[0][0], 0] for t in range(3)],
"真_単価": centers_true[:, 1],
"推定_単価": [centroids_raw[np.where(match == t)[0][0], 1] for t in range(3)],
}, index=["真クラスタ0(ライト)", "真クラスタ1(高単価)", "真クラスタ2(ヘビー)"])
print("\n=== 真の中心 vs 推定中心(元スケール)===")
print(cmp.to_string(formatters={c: ("{:,.0f}".format if "単価" in c else "{:.1f}".format)
for c in cmp.columns}))
acc = (match[labels_km] == labels_true).mean()
print(f"\n真ラベルとの一致率(中心対応で相対評価): {acc:.1%}")
出力:
顧客 N=600、特徴量2次元(年間購入回数・平均単価)、真のクラスタ数=3
z化前のスケール差:購入回数 sd=11.3 / 単価 sd=2,644(約235倍)
=== エルボー法:クラスタ数 k と級内平方和 inertia ===
k=1: inertia= 1200.0
k=2: inertia= 684.7 (前kから減少 515)
k=3: inertia= 130.1 (前kから減少 555)
k=4: inertia= 106.3 (前kから減少 24)
k=5: inertia= 76.0 (前kから減少 30)
k=6: inertia= 65.3 (前kから減少 11)
→ k=3 までは大きく減り、k=4 以降は減りが鈍る(折れ曲がり=3が妥当)
=== クラスタ別プロファイル(推定)===
推定クラスタ 対応する真クラスタ 人数 平均_購入回数 平均_単価
2 0 242 4.9 2,014
1 1 179 11.5 7,956
0 2 179 30.4 3,943
=== 真の中心 vs 推定中心(元スケール)===
真_購入回数 推定_購入回数 真_単価 推定_単価
真クラスタ0(ライト) 5.0 4.9 2,000 2,014
真クラスタ1(高単価) 12.0 11.5 8,000 7,956
真クラスタ2(ヘビー) 30.0 30.4 4,000 3,943
真ラベルとの一致率(中心対応で相対評価): 99.7%
出力の意味:
z化が必須な理由が数字で出る。購入回数の標準偏差は 、単価は ——約 235 倍の開きです。z化せずに距離を測ると、二乗距離は単価の項がほぼ全部を決め、購入回数はほとんど無視されます。これでは「単価だけでクラスタリング」しているのと同じ。z化で両者を分散 に揃えて初めて、購入回数と単価が対等に効きます。距離ベースの手法で標準化が「ほぼ必須」と言われる理由です。
エルボーが を当てる。inertia は の から と減ります。注目は減り幅: で も減るのに、 ではわずか 。つまり3つ目までは入れる価値が大きく、4つ目以降はほとんど無駄——「肘」が にくっきり出ています。inertia は を増やせば必ず下がる( の は z化後の全分散 に一致)ので、最小値ではなくこの折れ曲がりで を選ぶのがエルボー法です。みごとに真のクラスタ数 を言い当てました。
真の構造をほぼ完全に回復する。プロファイル表で、推定された3クラスタが真の3クラスタに1対1で対応し、人数は (真は )とほぼ一致。平均特徴は =ライト層、=高単価層、=ヘビー層と、生成時の中心 をほぼそのまま回復しています。真ラベルとの一致率は 99.7%——正解を一切与えていない教師なし手法が、隠れた3グループ構造を当てたわけです。
ただし「番号」に意味はなく、解釈は人がやる。注意すべきは、推定クラスタの番号()が真のクラスタ番号と一致していないことです(推定 ↔真 、推定 ↔真 )。k-means が返すのはただの通し番号で、「クラスタ0=ヘビー層」と名付けたのは、こちらがプロファイル(平均特徴)を見て解釈した結果にすぎません。本ノートでは答え(真ラベル)を持っていたから一致率まで計算できましたが、現実には真ラベルはなく、「このクラスタは何者か」を平均特徴から読み解いて命名する作業が必ず要ります。ここが RFM との最大の違いです。
この結果を1枚にまとめます(このブロックはデータ生成からエルボー計算までを内部で再実行し、上の表と同じ値を描きます)。
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from scipy.cluster.vq import kmeans2
# データ生成 → z化 → k-means → エルボー をこのブロック内で再実行し、
# (左) 特徴平面のクラスタ色分けと推定中心、(右) エルボー曲線を描く。
rng = np.random.default_rng(1)
N = 600
centers_true = np.array([[5.0, 2000.0], [12.0, 8000.0], [30.0, 4000.0]])
sizes = [240, 180, 180]
spreads = np.array([[2.0, 500.0], [3.0, 1200.0], [5.0, 900.0]])
parts = []
for c, n, sp in zip(centers_true, sizes, spreads):
parts.append(rng.normal(c, sp, size=(n, 2)))
X = np.vstack(parts)
X[:, 0] = np.clip(X[:, 0], 1.0, None)
X[:, 1] = np.clip(X[:, 1], 200.0, None)
mu, sd = X.mean(axis=0), X.std(axis=0)
Z = (X - mu) / sd
centroids_z, labels_km = kmeans2(Z, 3, seed=0, minit="++")
centroids_raw = centroids_z * sd + mu
def inertia(Z, k):
cz, lab = kmeans2(Z, k, seed=0, minit="++")
return float(((Z - cz[lab]) ** 2).sum())
ks = list(range(1, 7))
inertias = [inertia(Z, k) for k in ks]
fig, ax = plt.subplots(1, 2, figsize=(11.5, 4.6))
# 左:元スケールの特徴平面でクラスタを色分け+推定中心
for i in range(3):
d = X[labels_km == i]
ax[0].scatter(d[:, 0], d[:, 1], s=18, alpha=0.5, color=f"C{i}",
edgecolor="none", label=f"推定クラスタ{i}")
ax[0].scatter(centroids_raw[:, 0], centroids_raw[:, 1], s=300, marker="X",
color="black", zorder=5, label="推定中心")
ax[0].set_xlabel("年間購入回数")
ax[0].set_ylabel("平均単価(円)")
ax[0].set_title("k-means(k=3):特徴平面のクラスタと推定中心")
ax[0].legend(loc="upper right", fontsize=8.5)
ax[0].grid(alpha=0.3)
# 右:エルボー曲線(k=3 で折れ曲がり)
ax[1].plot(ks, inertias, "o-", color="C3", lw=2)
ax[1].scatter([3], [inertias[2]], s=220, facecolor="none",
edgecolor="C2", lw=2.5, zorder=5)
ax[1].annotate("エルボー(k=3)\nここから減りが鈍る",
xy=(3, inertias[2]), xytext=(3.7, 600), fontsize=9,
arrowprops=dict(arrowstyle="->", color="C2"))
ax[1].set_xlabel("クラスタ数 k")
ax[1].set_ylabel("級内平方和 inertia")
ax[1].set_title("エルボー法:inertia の折れ曲がりで k を選ぶ")
ax[1].grid(alpha=0.3)
fig.tight_layout()
plt.show()
左パネルは元スケールの特徴平面です。横軸が年間購入回数、縦軸が平均単価。3つの色分けされた点群がきれいに分かれ、黒い × が推定中心(ライト層 ≒ 左下、高単価層 ≒ 上、ヘビー層 ≒ 右)。中心が各塊の真ん中に乗っているのが、k-means がうまく当たった証拠です。右パネルはエルボー曲線。 で inertia が崖のように落ち、(緑丸)以降はほぼ水平——この折れ曲がりを読み取って を選びます。左の「塊が3つ」と右の「肘が3」が一致しているのが、 が正しいことの裏付けです。
⚠️ よくある誤解
- k-means は「球状・等分散」を仮定し、 を事前に決める:k-means は中心からのユークリッド距離で割り当てるので、各クラスタがおおむね球状で同じくらいの広がりを持つときに最も素直に働きます。細長い・三日月型・密度が大きく違う・入れ子のクラスタは苦手で、無理やり球に切って歪めます。また は与えなければ動かず、エルボーやシルエットで選ぶにせよ最後は人の判断が要ります。「とりあえず k-means」がいつも当たるわけではありません。
- 標準化しないとスケール大の変数が支配する:本ノートで単価の標準偏差は購入回数の 約 235 倍でした。z化を忘れると距離は事実上「単価だけ」で決まり、購入回数は無視され、単価で輪切りにしただけのクラスタが出ます。距離・分散ベースの手法では標準化はほぼ必須の前処理です(対数変換や外れ値処理が要る変数もあります)。
- 教師なしなので、クラスタの「意味」はアルゴリズムが教えてくれない:k-means が返すのは「」という通し番号だけで、「クラスタ0はヘビーユーザー」というラベルは付きません。意味づけ(命名・解釈)は、各クラスタの平均特徴を見て人が行う作業です。本ノートで一致率 99.7% と言えたのは、デモ用に真の答えを持っていたからであって、実務では真ラベルはありません。だからこそプロファイリング(クラスタごとの特徴の要約)が欠かせません。
- RFM とクラスタリングは「使い分け」:RFM は軸が R/F/M に固定される代わりに、誰が見ても意味が分かり、現場の合意が速い。クラスタリングは多変量で柔軟な代わりに、標準化・ の選択・解釈・命名に手間がかかり、結果が初期値や乱数に依存しうる(だから seed を固定し、k-means++ で初期化を工夫します)。実務的には「まず RFM で素早く全体像をつかみ、3軸では足りないと感じたらクラスタリングで多変量に広げる」が王道です。どちらか一方ではなく、目的と段階で選びます。
- クラスタリングの理論そのものは機械学習の領域:k-means の収束保証、k-means++ による初期化、階層的クラスタリング・混合ガウスモデル(GMM)・DBSCAN といった他手法、シルエット係数などの評価指標は、機械学習テキストの守備範囲です。本ノートは重複させず、これらをマーケティングのセグメンテーションにどう使うかという応用面に絞っています。理論の詳細はそちらへ。
関連ノート
- RFM分析(リセンシー・フリークエンシー・マネタリー)(ルールベースとの対比。まず RFM、3軸で足りなければ多変量のクラスタリングへ、という段階づけ)
- 第6章 セグメンテーション 目次
- 選好の異質性(混合ロジット・階層ベイズ)(モデルベースで異質性を確率分布として表す版。クラスタリングが各顧客を1つのクラスタにハードに割り当てるのに対し、混合モデル・階層ベイズは所属を**確率的(ソフト)**に扱う——異質性の表し方の対比)
- k-means の収束・k-means++・階層的クラスタリング・混合ガウス・DBSCAN・シルエット係数などクラスタリングの一般理論は機械学習テキスト、クラスタや RFM の結果を実際の施策ターゲットへ落とすターゲティング(ペルソナ設計)は 06-03(予定)で扱います
- マーケティング・サイエンス 全体目次