コンジョイント分析(選択型コンジョイント分析(CBC)と多項ロジットモデル、階層ベイズモデルによる個人レベル部分効用の推定)で推定した部分効用モデルを使うと、実際にはまだ市場に存在しない製品ラインナップ(自社の新製品案+競合製品)を仮想的に設定し、それぞれがどの程度のシェアを獲得しそうかを事前にシミュレーションできる。これをマーケットシミュレーション(market simulation)、あるいは**シェア・オブ・プリファレンス分析(share of preference analysis)**と呼ぶ。
シミュレーションの手順¶
シミュレーションしたい競合シナリオ(市場に並ぶ製品案の集合、)を定義する
各回答者の部分効用(階層ベイズなどで個人レベル推定したもの)を使って、シナリオ内の各製品の効用を計算する
何らかのシミュレーションルールに従って、回答者ごとの選択確率(またはシェア)を求める
全回答者について平均をとり、製品ごとの推定市場シェアとする
シミュレーションルール¶
(1) ファーストチョイス・ルール(first choice rule):各回答者は効用が最大の製品を確率1で選ぶとみなす。
シンプルで直感的だが、僅差の効用でも「勝者総取り」になるため、シェアの変化が滑らかでない(急激に0か1になる)。
(2) シェア・オブ・プリファレンス・ルール(share of preference rule / logit rule):MNLの選択確率をそのまま各回答者の「シェア」とみなす。
効用の差が僅かでも滑らかにシェアが変化するが、選択型コンジョイント分析(CBC)と多項ロジットモデルで触れたIIA仮定の影響を受けやすい(似た製品同士が過度にシェアを奪い合う)。
(3) ランダマイズド・ファーストチョイス(Randomized First Choice, RFC):Huber, Orme, Miller(1999)が提案した方法で、効用に追加のランダム誤差項(属性重みの分散+標準ガンベル誤差など)を加えたうえでファーストチョイス・ルールを適用し、これを多数回モンテカルロ・シミュレーションして平均をとる。ファーストチョイスの急峻さとロジットルールのIIA問題の中間的な性質を持ち、実務(Sawtooth Softwareなど)で広く使われている。
実装例¶
5人分の製品案(自社の新製品Xを含む)からなる競合シナリオについて、100人分の個人レベル部分効用(階層ベイズ推定を模して分散を持たせて生成)から、3つのルールでシェアを比較する。
import numpy as np
import pandas as pd
rng = np.random.default_rng(0)
feature_names = ["価格::1,000円", "価格::1,500円", "容量::500ml", "ブランド::A社", "ブランド::B社"]
gamma_true = np.array([1.2, 0.5, -0.3, 0.6, 0.1]) # 母集団平均の部分効用
sigma_true = np.array([0.5, 0.3, 0.2, 0.4, 0.2]) # 個人差
N_RESP = 100
beta_n = gamma_true + rng.normal(0, sigma_true, size=(N_RESP, len(feature_names)))
# シミュレーションしたい競合シナリオ(製品ごとの属性ダミー)
scenario = pd.DataFrame({
"product": ["自社新製品X", "競合A", "競合B", "競合C", "競合D"],
"価格::1,000円": [1, 0, 0, 1, 0],
"価格::1,500円": [0, 1, 0, 0, 1],
"容量::500ml": [1, 1, 0, 1, 0],
"ブランド::A社": [1, 0, 0, 0, 1],
"ブランド::B社": [0, 1, 1, 0, 0],
}).set_index("product")
V = beta_n @ scenario[feature_names].to_numpy().T # shape: (N_RESP, J)
scenario
# (1) ファーストチョイス・ルール
first_choice = np.zeros_like(V)
first_choice[np.arange(N_RESP), V.argmax(axis=1)] = 1
share_first_choice = first_choice.mean(axis=0)
# (2) シェア・オブ・プリファレンス・ルール(ロジットルール)
exp_V = np.exp(V)
share_logit = (exp_V / exp_V.sum(axis=1, keepdims=True)).mean(axis=0)
# (3) ランダマイズド・ファーストチョイス(RFC):ガンベル誤差を加えて多数回試行した平均
N_SIM = 2000
rfc_counts = np.zeros(V.shape[1])
for _ in range(N_SIM):
noisy_V = V + rng.gumbel(size=V.shape)
winners = noisy_V.argmax(axis=1)
rfc_counts += np.bincount(winners, minlength=V.shape[1])
share_rfc = rfc_counts / (N_SIM * N_RESP)
pd.DataFrame({
"first_choice": share_first_choice,
"share_of_preference(logit)": share_logit,
"RFC": share_rfc,
}, index=scenario.index).style.format("{:.1%}")
ファーストチョイス・ルールは最も極端な(0か1に近い)シェア配分になりやすく、ロジットルールは相対的に均等寄りのシェアを与える。RFCは両者の中間的な結果になる傾向がある。どのルールを採用するかは、実際の市場での「勝者総取り度合い」がどの程度かという業界知識と、ホールドアウトタスク(実験計画(属性・水準の設計)、モデル評価)での予測精度検証によって選ぶ。
What-ifシミュレーション¶
同じ枠組みで、自社製品Xの属性(価格や容量)を変化させたときにシェアがどう変化するかをシミュレーションできる。これにより、価格改定や新機能追加の意思決定を、実際の市場投入前に定量的に評価できる。
def simulate_share(scenario_df, beta_n, rule="logit"):
V = beta_n @ scenario_df[feature_names].to_numpy().T
if rule == "logit":
exp_V = np.exp(V)
return (exp_V / exp_V.sum(axis=1, keepdims=True)).mean(axis=0)
elif rule == "first_choice":
fc = np.zeros_like(V)
fc[np.arange(len(V)), V.argmax(axis=1)] = 1
return fc.mean(axis=0)
# 自社新製品Xの価格を1,000円 -> 1,500円に値上げした場合のシェア変化
scenario_price_up = scenario.copy()
scenario_price_up.loc["自社新製品X", ["価格::1,000円", "価格::1,500円"]] = [0, 1]
before = simulate_share(scenario, beta_n)
after = simulate_share(scenario_price_up, beta_n)
pd.DataFrame({"値上げ前": before, "値上げ後": after}, index=scenario.index).style.format("{:.1%}")
参考¶
Huber, J., Orme, B., & Miller, C. (1999). Dealing with product similarity in conjoint simulations. Sawtooth Software Conference Proceedings.
Orme, B. K. (2010). Getting Started with Conjoint Analysis: Strategies for Product Design and Pricing Research. Research Publishers LLC.