Mímisbrunnr知恵の泉

← マーケティングサイエンス 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 関連:第5章 顧客選好と選択モデル 目次 | 前提:離散選択モデル(多項ロジット・MNL)

要点(BLUF)

1. 「平均的な消費者」という落とし穴:選好の異質性

離散選択モデル(多項ロジット・MNL)コンジョイント分析(部分効用・WTP・市場シェア) では、確定効用を Vij=βxijV_{ij}=\beta^\top x_{ij} と置き、β\beta全員共通の1本として推定しました。これは暗黙に「市場には1種類の消費者しかいない(みんな同じ重みで価格と品質を天秤にかける)」と仮定しています。この仮想の人物を**代表的個人(representative agent)**と呼びます。

ところが実際の市場は、価格にうるさい層・品質にこだわる層・ブランドで決める層…と選好が割れているのがふつうです。これを**選好の異質性(preference heterogeneity)**と言います。異質な母集団に単一 β\beta を当てると何が起きるか——直感的には「平均」が出そうですが、β\beta の母平均すら正しく出ません。さらに悪いことに、たとえ平均が出たとしても、その平均は誰の選好でもないことがあります。価格重視 50%50\% と品質重視 50%50\% の市場の「平均消費者」は、価格も品質もそこそこ気にする人ですが、そんな人は1人もいないかもしれません。

flowchart TB
  POP["異質な母集団(価格重視と品質重視が混在)"]
  POP --> AGG["集計MNL:全員に単一βを1本当てる"]
  POP --> MIX["混合ロジット/階層ベイズ:β_i を分布で表す"]
  AGG --> BAD["中間のβ=誰の選好でもない平均(代替・弾力性を誤る)"]
  MIX --> GOOD["個人差を分布で表現(IIAを緩め代替を正す)"]

解決は、β\beta を固定値でなく確率分布として扱うことです。個人 ii ごとに固有の βi\beta_i があり、それが母集団の分布 f(βθ)f(\beta\mid\theta) から引かれている——こう考えるのが混合ロジット(mixed logit / random-coefficients logit)であり、その分布をベイズの階層構造で推定するのが階層ベイズです。

2. 混合ロジットと階層ベイズ:係数を分布で表す(数式)

離散選択モデル(多項ロジット・MNL) の MNL では、β\beta を所与として選択確率はソフトマックス Pni(β)=eβxni/keβxnkP_{ni}(\beta)=e^{\beta^\top x_{ni}}/\sum_k e^{\beta^\top x_{nk}} でした。混合ロジットは、この β\beta が個人ごとに異なり、母集団の密度 f(βθ)f(\beta\mid\theta) に従うとして、選択確率を β\beta について**積分(期待)**します。

Pni=eβxnik=1Jeβxnkf(βθ)dβP_{ni} = \int \frac{e^{\beta^\top x_{ni}}}{\sum_{k=1}^{J} e^{\beta^\top x_{nk}}}\, f(\beta\mid\theta)\, d\beta

内側のソフトマックスは「β\beta を持つ個人の条件付き選択確率」、外側の積分は「母集団の β\beta 分布で平均をとる」操作です。θ\theta(たとえば β\beta の平均と分散)が推定対象になります。階層ベイズはこれをベイズの言葉で書いた、最も自然な定式化です。各個人 ii の係数 βi\beta_i が母集団分布から引かれ、その個人の選択 yity_{it}βi\beta_i の MNL で生成される、という2段の構造を置きます。

βiN(μ,Σ),Pr(yit=jβi)=eβixitjkeβixitk\beta_i \sim N(\mu, \Sigma), \qquad \Pr(y_{it}=j \mid \beta_i) = \frac{e^{\beta_i^\top x_{itj}}}{\sum_{k} e^{\beta_i^\top x_{itk}}}

母集団パラメータ (μ,Σ)(\mu, \Sigma) にはハイパー事前を置き、全個人のデータから (μ,Σ)(\mu,\Sigma) と各 βi\beta_i を同時に推定します。生成方向は「母集団 (μ,Σ)(\mu,\Sigma) → 個人 βi\beta_i → 選択データ」です。

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 が偏るのか。真の集計シェア(個人ごとの選択確率を母集団で平均したもの)と、平均 β\beta で計算したロジット確率は一致しません

Pni(β)f(βθ)dβ真の集計シェア    Pni ⁣(βf(βθ)dβ)平均βのロジット\underbrace{\int P_{ni}(\beta)\, f(\beta\mid\theta)\, d\beta}_{\text{真の集計シェア}} \;\neq\; \underbrace{P_{ni}\!\left(\int \beta\, f(\beta\mid\theta)\, d\beta\right)}_{\text{平均}\,\beta\,\text{のロジット}}

ソフトマックス Pni(β)P_{ni}(\beta)β\beta について非線形だからです(イェンセンの不等式)。集計 MNL は実質的に右辺を当てているので、左辺(本当の集計挙動)からずれます。これが代表的個人の誤謬の数式的な正体です。

第二に、階層ベイズの縮約(shrinkage)。個人 ii の事後 βi\beta_i は、その個人自身のデータが示す推定値と、母集団平均 μ\mu との精度で重み付けした折衷になります(ガウス・ガウスの直感)。

β^i    wiβ^i個人+(1wi)μ,wi=ni/σ個人2ni/σ個人2+1/σμ2\hat\beta_i \;\approx\; w_i\,\hat\beta_i^{\,\text{個人}} + (1-w_i)\,\mu, \qquad w_i = \frac{n_i/\sigma^2_{\text{個人}}}{\,n_i/\sigma^2_{\text{個人}} + 1/\sigma^2_{\mu}\,}

nin_i はその個人の選択回数です。データの薄い個人(nin_i が小さく wi0w_i\to 0)は母集団 μ\mu へ強く引き寄せられ、データの厚い個人は自分の推定値に近いまま残ります。これが**部分プーリング(partial pooling)**で、個人ごとにバラバラに推定するより安定し、全員に同じ β\beta を強いる集計よりも個人差を拾えます——両極端のちょうど中間です。

実際の推定では上の積分に閉じた形がなく、混合ロジットは β\beta を何本もサンプリングして平均するシミュレーション最尤、階層ベイズは MCMC で事後分布を得ます。これらの推定アルゴリズムはベイズ統計テキストの守備範囲なので、本ノートでは重複させず、代わりに「異質性を無視すると何を間違えるか」を、真値が分かっている2セグメントの合成データで透明に見せます。

3. 異質性を無視するとどうなるか(コード)

2セグメントの異質な母集団を作ります。セグメント1「価格重視」が 60%60\%β=[1.5,0.5]\beta=[-1.5,0.5]、価格に強く反応・品質は軽め)、セグメント2「品質重視」が 40%40\%β=[0.3,1.8]\beta=[-0.3,1.8]、価格に鈍く・品質を強く評価)。個人600人 × 各20機会 × 3選択肢で、各自が自分のセグメントの β\beta で効用を作り、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 は中間の β\beta を出す。推定 β=[0.823,0.803]\beta=[-0.823,\,0.803] は、価格重視 [1.5,0.5][-1.5,0.5] と品質重視 [0.3,1.8][-0.3,1.8]どちらにも一致しません。しかも母集団重み付き平均 0.6×[1.5,0.5]+0.4×[0.3,1.8]=[1.02,1.02]0.6\times[-1.5,0.5]+0.4\times[-0.3,1.8]=[-1.02,1.02] よりも**00 寄りに縮んで**います(異質性が実効的な誤差を膨らませ、見かけの β\beta を弱めるため)。この単一 β\beta が描くのは「価格もそこそこ気にし、品質もそこそこ評価する」架空の平均消費者ですが、この市場にそんな人は1人もいません——全員が価格寄りか品質寄りのどちらかです。

(3) セグメント別 MNL は各 β\beta を回復する。所属で分けて別々に MNL を当てると、セグ1 [1.466,0.498][1.5,0.5][-1.466,0.498]\approx[-1.5,0.5]、セグ2 [0.316,1.804][0.3,1.8][-0.316,1.804]\approx[-0.3,1.8] と、真の選好がきれいに戻ります。「全員に1本」をやめてグループごとに β\beta を許すだけで、隠れていた異質性が見えるのです。これはセグメンテーション(第6章)の発想そのもので、そのモデル版が混合ロジット・階層ベイズです。ただし現実にはこの所属ラベルは観測できません——だからこそ、ラベル無しで β\beta の分布を推定する混合ロジット・階層ベイズが要るのです(§⚠️)。

(4) 集計 MNL は代替を取り違える。まず値下げ前のシェア(前)を見ると、真の母集団は A 18.4%18.4\%・B 43.0%43.0\%・C 38.6%38.6\% で、中庸の製品 A が最も選ばれていません——価格重視は安い B に、品質重視は高品質の C に集まり、妥協的な A は誰の第一候補でもないのです。ところが集計 MNL は A 33.3%33.3\%・B 34.1%34.1\%・C 32.5%32.5\%ほぼ均等に予測し、「A は3分の1取れている」と誤認します(平均 β\beta では価格と品質がほぼ打ち消し合い、3製品の効用が並ぶため)。次に A を 3.02.03.0\to2.0 に値下げすると、A が得るシェアは真 +21.4+21.4pt・集計 +19.9+19.9pt と大きさは近い。しかし原資(どこから奪うか)が全く違う。真の世界では B が 18.1-18.1pt 失い C は 3.2-3.2pt しか減りません——安くなった A が価格重視層を B から丸ごと奪い、C に忠実な品質重視層はほとんど動かない、非対称な代替です。一方、集計 MNL は B 10.2-10.2pt・C 9.7-9.7pt とほぼ均等に振り分けます。これは 離散選択モデル(多項ロジット・MNL)IIA(無関係な選択肢からの独立)が「どの製品が近い代替か」を無視して比例的に奪わせるからです。単一 β\beta モデルに従うと「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つの大きな点が各セグメント(価格重視 (1.5,0.5)(-1.5,0.5)・品質重視 (0.3,1.8)(-0.3,1.8))、赤い星が集計 MNL の単一 β(0.82,0.80)\beta\,(-0.82,0.80)。星は2つの山の谷間に落ち、どちらのセグメントとも一致しません——これが代表的個人の誤謬の絵です。右パネルは A の値下げによる Δ\Delta シェア。オレンジが真の異質母集団、グレーが集計 MNL。A の増分はほぼ同じ(+21+21 vs +20+20)でも、B の減り(18-18 vs 10-10)と C の減り(3-3 vs 10-10)が大きく食い違います。真の世界は B に集中した非対称な代替、IIA モデルは均等にばらまく。このグレーの棒が、異質性を無視して払う系統的な誤りそのものです。

⚠️ よくある誤解

関連ノート