選択型コンジョイント分析(choice-based conjoint analysis, CBC) は、回答者に複数のプロファイル(通常2〜5個程度)を1つの選択セット(choice set)として提示し、その中から最も好ましい1つを選ばせる方式である。個々のプロファイルを独立に評価する評定型(評定型(トラディショナル)コンジョイント分析)と異なり、実際の購買行動に近い相対比較をさせられる点が特徴で、現在最も広く使われているコンジョイント分析の方式である。
ランダム効用モデル(Random Utility Model)¶
回答者がプロファイルから得る効用を
と分解する。は観測可能な属性から決まる確定効用(deterministic utility)、は観測できない要因による誤差項である。回答者は選択セットの中で効用が最大のプロファイルを選ぶと仮定する(効用最大化原理)。
確定効用は、評定型と同様に属性の部分効用の和として表す。
多項ロジットモデル(Multinomial Logit, MNL)¶
誤差項が独立に 標準ガンベル分布(Gumbel distribution、type I extreme value分布) に従うと仮定すると、プロファイルが選ばれる確率は解析的に閉じた形で求まる(McFadden, 1974)。
これが**多項ロジットモデル(MNL)**である。この確率を選択セットごとに掛け合わせた尤度
を最大化することでを最尤推定する。
Bradley-Terryモデルとの関係¶
選択セットの大きさが2(ペア比較)の場合、MNLは
となる。これは次章で扱うBradley-Terryモデルの式と一致する。つまりBradley-TerryモデルはMNLの2択(ペア比較)における特殊ケースであり、CBCはこれを一般の択に拡張したものと位置づけられる(Luceの選択公理)。
実装例¶
3属性(価格・容量・ブランド)からなるプロファイルの中から1つを選択する、という設定でCBCデータをシミュレーションし、statsmodelsの条件付きロジット(ConditionalLogit)で部分効用を推定する。
import itertools
import numpy as np
import pandas as pd
rng = np.random.default_rng(0)
attributes = {
"価格": ["1,000円", "1,500円", "2,000円"],
"容量": ["500ml", "1000ml"],
"ブランド": ["A社", "B社", "C社"],
}
true_beta = {
"価格::1,000円": 1.2, "価格::1,500円": 0.0, "価格::2,000円": -1.2,
"容量::500ml": -0.3, "容量::1000ml": 0.3,
"ブランド::A社": 0.6, "ブランド::B社": 0.1, "ブランド::C社": -0.7,
}
all_profiles = list(itertools.product(*attributes.values()))
def profile_to_dummies(profile):
price, volume, brand = profile
return {
"価格::1,000円": price == "1,000円", "価格::1,500円": price == "1,500円", "価格::2,000円": price == "2,000円",
"容量::500ml": volume == "500ml", "容量::1000ml": volume == "1000ml",
"ブランド::A社": brand == "A社", "ブランド::B社": brand == "B社", "ブランド::C社": brand == "C社",
}
def utility(profile):
d = profile_to_dummies(profile)
return sum(true_beta[k] for k, v in d.items() if v)
N_RESPONDENTS = 400
N_ALTS = 3 # 選択セットあたりの選択肢数
records = []
for n in range(N_RESPONDENTS):
choice_set = rng.choice(len(all_profiles), size=N_ALTS, replace=False)
utilities = np.array([utility(all_profiles[j]) for j in choice_set])
utilities_noisy = utilities + rng.gumbel(size=N_ALTS) # ガンベル誤差を付与
chosen = np.argmax(utilities_noisy)
for alt_idx, profile_idx in enumerate(choice_set):
d = profile_to_dummies(all_profiles[profile_idx])
records.append({
"resp_id": n,
"alt_id": alt_idx,
"chosen": int(alt_idx == chosen),
**d,
})
df = pd.DataFrame(records)
df.head(6)
from statsmodels.discrete.conditional_models import ConditionalLogit
# 各属性の最終水準を基準として除外(ダミーコーディング; 効果コーディングでも可)
feature_cols = [
"価格::1,000円", "価格::1,500円",
"容量::500ml",
"ブランド::A社", "ブランド::B社",
]
X = df[feature_cols]
y = df["chosen"]
groups = df["resp_id"] # 選択セット単位のグループID(回答者ごとに1セット)
model = ConditionalLogit(y, X, groups=groups)
result = model.fit()
result.summary()
ダミーコーディングを用いているため、除外した基準水準(価格「2,000円」、容量「1000ml」、ブランド「C社」)の部分効用は0に固定された相対値として推定されている。真の部分効用の差分(例:価格「1,000円」と「2,000円」の差は)とおおむね整合する結果になっていることを確認できる。
モデルの拡張¶
属性×属性の交互作用:加法モデルでは表現できない相乗効果を捉えたい場合、交互作用項を追加する
異質性の考慮:本章のMNLは全回答者が同じを持つ前提(プールドロジット)。個人差を考慮する場合は階層ベイズモデルによる個人レベル部分効用の推定や混合ロジット(mixed logit / random parameters logit)を用いる
None(どれも選ばない)オプション:実務のCBCでは「どれも選ばない」という選択肢を含めることが多く、これによって市場浸透率の推定が可能になる