🎓 レベル:標準 | 重要度:A(必須)
📎 関連:第5章 顧客選好と選択モデル 目次 | 前提:離散選択モデル(多項ロジット・MNL)
要点(BLUF)
- 離散選択モデル(多項ロジット・MNL) の単一 は、市場の全員が同じ重み付けで選ぶという 「平均的な消費者」を仮定します。しかし現実の消費者は選好が異質で、価格重視の人・品質重視の人が混在します。異質性を無視して集計レベルで を1本だけ当てると、**「誰にも当てはまらない平均」**を推定してしまい、弾力性や代替パターン(どの製品がどの製品を食うか)を誤ります。
- 混合ロジット/階層ベイズは、係数を固定値でなく分布 として個人差を表します。階層ベイズでは と置き、各個人の選択から を推定しつつ、母集団 をハイパー事前で結びます(縮約 shrinkage:データの薄い個人を母集団へ引き寄せて安定化)。
- 合成データ(2セグメント:価格重視 =、品質重視 =)で、集計 MNL は中間の =どちらのセグメントでもない平均を出し、セグメント別 MNL は各 を回復します。帰結として、ある製品の値下げに対する代替を集計 MNL が取り違える(真:原資はほぼ B から pt/集計 MNL:B pt・C pt と均等に誤配分)ことを数値で示します。本ノートは概念と帰結を numpy/scipy の軽量デモで扱い、本格的な推定(混合ロジットのシミュレーション最尤・階層ベイズの MCMC)はベイズ統計テキストへ譲ります。
1. 「平均的な消費者」という落とし穴:選好の異質性
離散選択モデル(多項ロジット・MNL) と コンジョイント分析(部分効用・WTP・市場シェア) では、確定効用を と置き、 を全員共通の1本として推定しました。これは暗黙に「市場には1種類の消費者しかいない(みんな同じ重みで価格と品質を天秤にかける)」と仮定しています。この仮想の人物を**代表的個人(representative agent)**と呼びます。
ところが実際の市場は、価格にうるさい層・品質にこだわる層・ブランドで決める層…と選好が割れているのがふつうです。これを**選好の異質性(preference heterogeneity)**と言います。異質な母集団に単一 を当てると何が起きるか——直感的には「平均」が出そうですが、 の母平均すら正しく出ません。さらに悪いことに、たとえ平均が出たとしても、その平均は誰の選好でもないことがあります。価格重視 と品質重視 の市場の「平均消費者」は、価格も品質もそこそこ気にする人ですが、そんな人は1人もいないかもしれません。
flowchart TB POP["異質な母集団(価格重視と品質重視が混在)"] POP --> AGG["集計MNL:全員に単一βを1本当てる"] POP --> MIX["混合ロジット/階層ベイズ:β_i を分布で表す"] AGG --> BAD["中間のβ=誰の選好でもない平均(代替・弾力性を誤る)"] MIX --> GOOD["個人差を分布で表現(IIAを緩め代替を正す)"]
解決は、 を固定値でなく確率分布として扱うことです。個人 ごとに固有の があり、それが母集団の分布 から引かれている——こう考えるのが混合ロジット(mixed logit / random-coefficients logit)であり、その分布をベイズの階層構造で推定するのが階層ベイズです。
2. 混合ロジットと階層ベイズ:係数を分布で表す(数式)
離散選択モデル(多項ロジット・MNL) の MNL では、 を所与として選択確率はソフトマックス でした。混合ロジットは、この が個人ごとに異なり、母集団の密度 に従うとして、選択確率を について**積分(期待)**します。
内側のソフトマックスは「 を持つ個人の条件付き選択確率」、外側の積分は「母集団の 分布で平均をとる」操作です。(たとえば の平均と分散)が推定対象になります。階層ベイズはこれをベイズの言葉で書いた、最も自然な定式化です。各個人 の係数 が母集団分布から引かれ、その個人の選択 が の MNL で生成される、という2段の構造を置きます。
母集団パラメータ にはハイパー事前を置き、全個人のデータから と各 を同時に推定します。生成方向は「母集団 → 個人 → 選択データ」です。
flowchart TB H["ハイパー事前:母集団 (μ, Σ)"] H --> B1["個人1の β_1"] H --> B2["個人2の β_2"] H --> Bi["…個人i の β_i"] B1 --> D1["個人1の選択データ"] B2 --> D2["個人2の選択データ"] Bi --> Di["個人i の選択データ"]
ここで数学的な核心が2つあります。第一に、なぜ集計 MNL が偏るのか。真の集計シェア(個人ごとの選択確率を母集団で平均したもの)と、平均 で計算したロジット確率は一致しません。
ソフトマックス が について非線形だからです(イェンセンの不等式)。集計 MNL は実質的に右辺を当てているので、左辺(本当の集計挙動)からずれます。これが代表的個人の誤謬の数式的な正体です。
第二に、階層ベイズの縮約(shrinkage)。個人 の事後 は、その個人自身のデータが示す推定値と、母集団平均 との精度で重み付けした折衷になります(ガウス・ガウスの直感)。
はその個人の選択回数です。データの薄い個人( が小さく )は母集団 へ強く引き寄せられ、データの厚い個人は自分の推定値に近いまま残ります。これが**部分プーリング(partial pooling)**で、個人ごとにバラバラに推定するより安定し、全員に同じ を強いる集計よりも個人差を拾えます——両極端のちょうど中間です。
実際の推定では上の積分に閉じた形がなく、混合ロジットは を何本もサンプリングして平均するシミュレーション最尤、階層ベイズは MCMC で事後分布を得ます。これらの推定アルゴリズムはベイズ統計テキストの守備範囲なので、本ノートでは重複させず、代わりに「異質性を無視すると何を間違えるか」を、真値が分かっている2セグメントの合成データで透明に見せます。
3. 異質性を無視するとどうなるか(コード)
2セグメントの異質な母集団を作ります。セグメント1「価格重視」が (、価格に強く反応・品質は軽め)、セグメント2「品質重視」が (、価格に鈍く・品質を強く評価)。個人600人 × 各20機会 × 3選択肢で、各自が自分のセグメントの で効用を作り、Gumbel ノイズを足して効用最大を選びます。このデータに対して、(2) 集計 MNL を1本当て、(3) セグメント別に MNL を当て、(4) ある製品の値下げへの反応を「真の異質母集団」と「集計 MNL」で予測して比べます。
import numpy as np
import pandas as pd
from scipy.optimize import minimize
# === 2セグメントの異質な母集団から選択データを生成 ===
# セグメント1「価格重視」60%=β=[-1.5, 0.5]、セグメント2「品質重視」40%=β=[-0.3, 1.8]([価格, 品質])
rng = np.random.default_rng(7)
N, T, J = 600, 20, 3 # 個人600人 × 各20機会 × 3選択肢
beta_seg = np.array([[-1.5, 0.5], # セグメント1:価格重視
[-0.3, 1.8]]) # セグメント2:品質重視
seg = (rng.random(N) >= 0.6).astype(int) # 0=価格重視(約60%), 1=品質重視(約40%)
beta_i = beta_seg[seg] # (N,2) 各個人の真のβ(自分の所属セグメントの値)
price = rng.uniform(1, 5, (N, T, J)) # 各個人・各機会・各選択肢の価格
quality = rng.uniform(1, 5, (N, T, J)) # 同・品質
V = beta_i[:, None, 0, None]*price + beta_i[:, None, 1, None]*quality
choice = (V + rng.gumbel(0, 1, (N, T, J))).argmax(axis=2) # (N,T) 各機会の選択
price_f = price.reshape(N*T, J) # タスク×選択肢に平坦化
quality_f = quality.reshape(N*T, J)
choice_f = choice.reshape(N*T)
seg_task = np.repeat(seg, T) # 各タスクの所属セグメント
# MNL を最尤推定(負の対数尤度を最小化)する関数
def fit_mnl(pr, qu, ch):
idx = np.arange(len(ch))
def nll(b):
v = b[0]*pr + b[1]*qu
v = v - v.max(axis=1, keepdims=True)
P = np.exp(v) / np.exp(v).sum(axis=1, keepdims=True)
return -np.sum(np.log(P[idx, ch]))
return minimize(nll, np.zeros(2), method="BFGS").x
# (2) 集計MNL:異質な母集団に単一βを1本当てる
beta_agg = fit_mnl(price_f, quality_f, choice_f)
agg = pd.DataFrame(
{"価格β": [beta_seg[0, 0], beta_seg[1, 0], beta_agg[0]],
"品質β": [beta_seg[0, 1], beta_seg[1, 1], beta_agg[1]]},
index=["セグ1 真値(価格重視)", "セグ2 真値(品質重視)", "集計MNL 推定(単一β)"])
print("=== (2) 集計MNL:異質な母集団に単一βを当てると中間値になる ===")
print(agg.to_string(formatters={"価格β": "{:.3f}".format, "品質β": "{:.3f}".format}))
print(" → 推定βはどちらのセグメントにも一致しない(誰の選好でもない平均)")
# (3) セグメント別にMNLを当てて各βを回復
rec = np.zeros((2, 2))
for s in range(2):
m = seg_task == s
rec[s] = fit_mnl(price_f[m], quality_f[m], choice_f[m])
recdf = pd.DataFrame(
{"価格β 真値": beta_seg[:, 0], "価格β 推定": rec[:, 0],
"品質β 真値": beta_seg[:, 1], "品質β 推定": rec[:, 1]},
index=["セグ1(価格重視)", "セグ2(品質重視)"])
print("\n=== (3) セグメント別MNL:各セグメントのβを回復 ===")
print(recdf.to_string(formatters={c: "{:.3f}".format for c in recdf.columns}))
# (4) 帰結:製品Aの価格を下げたときの集計シェア変化を2通りで予測
# 市場=A(中庸) / B(安い低品質) / C(高い高品質)。Aの価格を 3.0→2.0 に下げる。
q = np.array([3.0, 1.8, 4.2]) # 各製品の品質(A,B,C)
p0 = np.array([3.0, 1.8, 4.2]) # 値下げ前の価格
p1 = p0.copy(); p1[0] = 2.0 # 値下げ後(Aだけ 3.0→2.0)
def share_true(p): # 真の異質母集団:各個人が自分のβで選び母集団平均
v = beta_i[:, 0:1]*p[None, :] + beta_i[:, 1:2]*q[None, :]
v = v - v.max(axis=1, keepdims=True)
e = np.exp(v)
return (e / e.sum(axis=1, keepdims=True)).mean(axis=0)
def share_agg(p): # 集計MNL:単一βで選択確率
v = beta_agg[0]*p + beta_agg[1]*q
e = np.exp(v - v.max())
return e / e.sum()
st0, st1 = share_true(p0), share_true(p1)
sa0, sa1 = share_agg(p0), share_agg(p1)
res = pd.DataFrame({
"真:前": st0, "真:後": st1, "真:Δ": st1-st0,
"集計:前": sa0, "集計:後": sa1, "集計:Δ": sa1-sa0,
}, index=["A(値下げ)", "B(安い低品質)", "C(高い高品質)"])
print("\n=== (4) 製品Aを3.0→2.0に値下げ:集計シェア変化の予測 ===")
print(res.to_string(formatters={c: "{:.1%}".format for c in res.columns}))
print(f" Aが得たシェア : 真 {(st1-st0)[0]:+.1%} / 集計MNL {(sa1-sa0)[0]:+.1%}")
print(f" 原資(Bから) : 真 {(st1-st0)[1]:+.1%} / 集計MNL {(sa1-sa0)[1]:+.1%}")
print(f" 原資(Cから) : 真 {(st1-st0)[2]:+.1%} / 集計MNL {(sa1-sa0)[2]:+.1%}")
出力:
=== (2) 集計MNL:異質な母集団に単一βを当てると中間値になる ===
価格β 品質β
セグ1 真値(価格重視) -1.500 0.500
セグ2 真値(品質重視) -0.300 1.800
集計MNL 推定(単一β) -0.823 0.803
→ 推定βはどちらのセグメントにも一致しない(誰の選好でもない平均)
=== (3) セグメント別MNL:各セグメントのβを回復 ===
価格β 真値 価格β 推定 品質β 真値 品質β 推定
セグ1(価格重視) -1.500 -1.466 0.500 0.498
セグ2(品質重視) -0.300 -0.316 1.800 1.804
=== (4) 製品Aを3.0→2.0に値下げ:集計シェア変化の予測 ===
真:前 真:後 真:Δ 集計:前 集計:後 集計:Δ
A(値下げ) 18.4% 39.8% 21.4% 33.3% 53.2% 19.9%
B(安い低品質) 43.0% 24.9% -18.1% 34.1% 23.9% -10.2%
C(高い高品質) 38.6% 35.4% -3.2% 32.5% 22.8% -9.7%
Aが得たシェア : 真 +21.4% / 集計MNL +19.9%
原資(Bから) : 真 -18.1% / 集計MNL -10.2%
原資(Cから) : 真 -3.2% / 集計MNL -9.7%
出力の意味:
(2) 集計 MNL は中間の を出す。推定 は、価格重視 と品質重視 のどちらにも一致しません。しかも母集団重み付き平均 よりも** 寄りに縮んで**います(異質性が実効的な誤差を膨らませ、見かけの を弱めるため)。この単一 が描くのは「価格もそこそこ気にし、品質もそこそこ評価する」架空の平均消費者ですが、この市場にそんな人は1人もいません——全員が価格寄りか品質寄りのどちらかです。
(3) セグメント別 MNL は各 を回復する。所属で分けて別々に MNL を当てると、セグ1 、セグ2 と、真の選好がきれいに戻ります。「全員に1本」をやめてグループごとに を許すだけで、隠れていた異質性が見えるのです。これはセグメンテーション(第6章)の発想そのもので、そのモデル版が混合ロジット・階層ベイズです。ただし現実にはこの所属ラベルは観測できません——だからこそ、ラベル無しで の分布を推定する混合ロジット・階層ベイズが要るのです(§⚠️)。
(4) 集計 MNL は代替を取り違える。まず値下げ前のシェア(前)を見ると、真の母集団は A ・B ・C で、中庸の製品 A が最も選ばれていません——価格重視は安い B に、品質重視は高品質の C に集まり、妥協的な A は誰の第一候補でもないのです。ところが集計 MNL は A ・B ・C とほぼ均等に予測し、「A は3分の1取れている」と誤認します(平均 では価格と品質がほぼ打ち消し合い、3製品の効用が並ぶため)。次に A を に値下げすると、A が得るシェアは真 pt・集計 pt と大きさは近い。しかし原資(どこから奪うか)が全く違う。真の世界では B が pt 失い C は pt しか減りません——安くなった A が価格重視層を B から丸ごと奪い、C に忠実な品質重視層はほとんど動かない、非対称な代替です。一方、集計 MNL は B pt・C pt とほぼ均等に振り分けます。これは 離散選択モデル(多項ロジット・MNL) の IIA(無関係な選択肢からの独立)が「どの製品が近い代替か」を無視して比例的に奪わせるからです。単一 モデルに従うと「A の値下げは C も B と同じだけ削る」と判断しますが、実際は A は B キラー。交差弾力性・カニバリゼーションを読み違える——まさに選択モデルが支えるべき価格・製品ライン設計の意思決定で、です。
この帰結を1枚にまとめます(このブロックはデータ生成と推定を内部で再実行し、上の表と同じ値を描きます)。
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from scipy.optimize import minimize
# データ生成と推定をこのブロック内で完結させ、(左)2セグメントのβと集計βの位置、
# (右)Aの値下げに対するシェア変化のズレ(真の異質母集団 vs 集計MNL)を描く。
rng = np.random.default_rng(7)
N, T, J = 600, 20, 3
beta_seg = np.array([[-1.5, 0.5], [-0.3, 1.8]]) # セグ1=価格重視, セグ2=品質重視
seg = (rng.random(N) >= 0.6).astype(int)
beta_i = beta_seg[seg]
price = rng.uniform(1, 5, (N, T, J)); quality = rng.uniform(1, 5, (N, T, J))
V = beta_i[:, None, 0, None]*price + beta_i[:, None, 1, None]*quality
choice = (V + rng.gumbel(0, 1, (N, T, J))).argmax(axis=2)
pf, qf, cf = price.reshape(N*T, J), quality.reshape(N*T, J), choice.reshape(N*T)
def fit_mnl(pr, qu, ch):
idx = np.arange(len(ch))
def nll(b):
v = b[0]*pr + b[1]*qu; v = v - v.max(axis=1, keepdims=True)
P = np.exp(v) / np.exp(v).sum(axis=1, keepdims=True)
return -np.sum(np.log(P[idx, ch]))
return minimize(nll, np.zeros(2), method="BFGS").x
beta_agg = fit_mnl(pf, qf, cf)
q = np.array([3.0, 1.8, 4.2]); p0 = np.array([3.0, 1.8, 4.2]); p1 = p0.copy(); p1[0] = 2.0
def s_true(p):
v = beta_i[:, 0:1]*p[None, :] + beta_i[:, 1:2]*q[None, :]
v = v - v.max(axis=1, keepdims=True); e = np.exp(v)
return (e / e.sum(axis=1, keepdims=True)).mean(axis=0)
def s_agg(p):
v = beta_agg[0]*p + beta_agg[1]*q; e = np.exp(v - v.max())
return e / e.sum()
dt = s_true(p1) - s_true(p0) # 真の異質母集団でのΔシェア
da = s_agg(p1) - s_agg(p0) # 集計MNLでのΔシェア
fig, ax = plt.subplots(1, 2, figsize=(11.5, 4.6))
# 左:係数平面上の2セグメントと集計β
ax[0].scatter([-1.5], [0.5], s=360*1.2, color="C0", zorder=3,
label="セグ1 価格重視(60%)")
ax[0].scatter([-0.3], [1.8], s=240*1.2, color="C2", zorder=3,
label="セグ2 品質重視(40%)")
ax[0].plot([-1.5, -0.3], [0.5, 1.8], color="gray", ls="--", lw=1.0, zorder=1)
ax[0].scatter([beta_agg[0]], [beta_agg[1]], s=260, marker="*", color="C3",
zorder=4, label=f"集計MNL 単一β\n({beta_agg[0]:.2f}, {beta_agg[1]:.2f})")
ax[0].annotate("どちらにも一致しない\n(誰の選好でもない平均)",
xy=(beta_agg[0], beta_agg[1]), xytext=(-1.15, 1.25), fontsize=9,
arrowprops=dict(arrowstyle="->", color="C3"))
ax[0].set_xlabel("価格の係数 β(負=価格に敏感)")
ax[0].set_ylabel("品質の係数 β(正=品質を重視)")
ax[0].set_title("選好の異質性:単一βは2つの山の谷間に落ちる")
ax[0].legend(loc="lower left", fontsize=8.5)
ax[0].grid(alpha=0.3)
# 右:Aの値下げに対するΔシェア(真 vs 集計MNL)
labels = ["A(値下げ)", "B(安い低品質)", "C(高い高品質)"]
x = np.arange(3); w = 0.38
b1 = ax[1].bar(x - w/2, dt*100, w, color="C1", label="真の異質母集団")
b2 = ax[1].bar(x + w/2, da*100, w, color="C7", label="集計MNL(単一β・IIA)")
for b in list(b1) + list(b2):
h = b.get_height()
ax[1].text(b.get_x()+b.get_width()/2, h + (0.6 if h >= 0 else -1.4),
f"{h:+.0f}", ha="center", fontsize=9)
ax[1].axhline(0, color="black", lw=0.8)
ax[1].set_xticks(x); ax[1].set_xticklabels(labels, fontsize=9)
ax[1].set_ylabel("シェア変化(ポイント)")
ax[1].set_title("Aを値下げ:原資はBから? 集計MNLは代替を取り違える")
ax[1].legend(loc="lower right", fontsize=9)
ax[1].grid(alpha=0.3, axis="y")
fig.tight_layout()
plt.show()
左パネルは係数平面です。2つの大きな点が各セグメント(価格重視 ・品質重視 )、赤い星が集計 MNL の単一 。星は2つの山の谷間に落ち、どちらのセグメントとも一致しません——これが代表的個人の誤謬の絵です。右パネルは A の値下げによる シェア。オレンジが真の異質母集団、グレーが集計 MNL。A の増分はほぼ同じ( vs )でも、B の減り( vs )と C の減り( vs )が大きく食い違います。真の世界は B に集中した非対称な代替、IIA モデルは均等にばらまく。このグレーの棒が、異質性を無視して払う系統的な誤りそのものです。
⚠️ よくある誤解
- 代表的個人の誤謬(平均 は誰にも当てはまらない):「みんなの平均的な好み」を1本の で代表させれば市場全体を説明できる、というのは誤りです。選択確率はソフトマックスで について非線形なので、——母集団の平均挙動は、平均 のロジットと一致しません。本ノートの集計 は重み付き平均 よりさらに 寄りに縮み、しかもそんな選好の人は実在しません。「平均的な消費者向けに設計する」と、価格重視にも品質重視にも刺さらない中途半端な製品(まさに本ノートの製品 A)ができあがります。
- 異質性は IIA 問題の一因:離散選択モデル(多項ロジット・MNL) の赤バス・青バス問題(似た選択肢が混じると比例的に奪い合って非現実的になる)は、突き詰めれば観測されない異質性が誤差項に押し込まれていることが一因です。混合ロジットは係数を分布にすることで誤差の相関を生み、IIA を緩めます。本ノートの (4) で集計 MNL が代替を均等配分したのは IIA の症状で、異質性を入れた真のモデルでは非対称な(現実的な)代替が出ました。
- 個人の選択を単純平均しても異質性は復元できない:「各人の選択を集計してシェアを平均すれば、なんとなく分布が分かる」は誤りです。選択は離散(誰がどれを選んだか)で、しかも非線形を通っているため、集計シェアからは の分布が一意に逆算できません。本ノートで真の を取り戻せたのは所属ラベルで分けて MNL を当て直したからであって、単純平均からではありません。分布を推定するには、混合ロジット( をサンプリングして積分を近似)や階層ベイズというモデルが要ります。
- 階層ベイズの縮約は欠点ではなく利点:階層ベイズは個人 を母集団平均 へ引き寄せます(shrinkage)。一見「個人差を消している」ようですが逆で、データの薄い個人を母集団で補強して安定化する部分プーリングです( が小さい人ほど に寄る)。個人ごとにバラバラ推定すると少データの人が暴れ、全員共通にすると個人差が消える——その中間の最適点を、データ量に応じて自動で取ります。
- 本ノートは「概念と帰結」のデモで、推定そのものは別物:(3) では真の所属ラベルを使ってセグメントに分けました。現実にはラベルは観測できず、 の分布をラベル無しで推定するのが混合ロジット(シミュレーション最尤)と階層ベイズ(MCMC)です。その推定アルゴリズム(事前分布・サンプラー・収束診断)はベイズ統計テキストの守備範囲なので、ここでは重複させず wikilink で誘導します。本ノートで掴んでほしいのは「異質性を無視すると弾力性・代替を系統的に誤る」という動機の方です。
関連ノート
- 離散選択モデル(多項ロジット・MNL)(本ノートが緩める単一 の仮定。代表的個人・IIA の出どころ)
- コンジョイント分析(部分効用・WTP・市場シェア)(平均部分効用が隠す異質性。個人ごとの部分効用を分布で扱うのが本ノートの発想)
- 第5章 顧客選好と選択モデル 目次
- 異質性をグループ(セグメント)として切り分ける発想は第6章 セグメンテーションへ。本ノートはそのモデルベース版にあたります
- 混合ロジット・階層ベイズの本格的な推定(シミュレーション最尤・MCMC・事前分布・収束診断)はベイズ統計テキストにあり、重複させず wikilink で参照します
- マーケティング・サイエンス 全体目次