評定型コンジョイント分析(rating-based / traditional conjoint analysis) は、回答者に個々のプロファイル(属性の組み合わせ)を1つずつ提示し、「どの程度好ましいか」を評点(例:1〜7点のリッカート尺度、0〜100点の購入意向)で回答させる、最も古典的なコンジョイント分析の方式である。
得られた評点を目的変数、各属性の水準をダミー変数化した説明変数として 線形回帰(OLS) を行うことで、部分効用を推定する。
ダミーコーディングと効果コーディング¶
属性はカテゴリ変数なので、回帰に使う前に数値化する必要がある。代表的な方式が2つある。
ダミーコーディング(dummy coding):ある水準を基準(リファレンス)としてで表す。基準水準の部分効用は0に固定され、他の水準の係数は基準水準からの差分として解釈される。
効果コーディング(effects coding):基準水準を-1、対象水準を+1、その他を0で表す。
この符号化では
という制約のもとで各水準の部分効用が推定されるため、切片がプロファイル全体の平均効用として解釈できる。
コンジョイント分析では、部分効用を「属性内水準の平均からの乖離」として解釈したいことが多く、水準間の比較が基準水準に依存しない効果コーディングが標準的に用いられる。
実装例¶
4属性からなる架空の飲料製品について、真の部分効用を設定してプロファイルへの評点データを生成し、効果コーディング+OLSで部分効用を復元できるか確認する。
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社"],
"パッケージ": ["缶", "瓶", "ペットボトル"],
}
# 真の部分効用(各属性内の和が0になるように設定)
true_partworths = {
"価格": {"1,000円": 1.5, "1,500円": 0.0, "2,000円": -1.5},
"容量": {"500ml": -0.5, "1000ml": 0.5},
"ブランド": {"A社": 0.8, "B社": 0.2, "C社": -1.0},
"パッケージ": {"缶": -0.3, "瓶": 0.6, "ペットボトル": -0.3},
}
intercept_true = 5.0
profiles = list(itertools.product(*attributes.values()))
df = pd.DataFrame(profiles, columns=attributes.keys())
def true_utility(row):
return intercept_true + sum(
true_partworths[attr][row[attr]] for attr in attributes
)
df["true_utility"] = df.apply(true_utility, axis=1)
df["rating"] = df["true_utility"] + rng.normal(0, 0.3, size=len(df))
df.head()
# 効果コーディング(各属性の最終水準を基準とし、-1/0/+1で符号化)
def effects_code(df, attributes):
X = pd.DataFrame(index=df.index)
for attr, levels in attributes.items():
base = levels[-1] # 最終水準を基準に
for level in levels[:-1]:
col = f"{attr}::{level}"
X[col] = 0
X.loc[df[attr] == level, col] = 1
X.loc[df[attr] == base, col] = -1
return X
X = effects_code(df, attributes)
X.insert(0, "intercept", 1)
X.head()import statsmodels.api as sm
model = sm.OLS(df["rating"], X)
result = model.fit()
result.summary()# 基準水準の部分効用は、他の水準の推定値の符号反転和として復元する
coef = result.params.drop("intercept")
partworths_hat = {"intercept": result.params["intercept"]}
for attr, levels in attributes.items():
attr_coefs = {
level: coef[f"{attr}::{level}"]
for level in levels[:-1]
}
base_level = levels[-1]
attr_coefs[base_level] = -sum(attr_coefs.values())
partworths_hat[attr] = attr_coefs
pd.DataFrame(partworths_hat)推定された部分効用は、ノイズの範囲内で真の部分効用(価格:1.5 / 0.0 / -1.5、容量:-0.5 / 0.5、ブランド:0.8 / 0.2 / -1.0、パッケージ:-0.3 / 0.6 / -0.3、切片:5.0)を概ね再現できている。
# 属性重要度
importance = {}
for attr, levels in attributes.items():
values = list(partworths_hat[attr].values())
importance[attr] = max(values) - min(values)
total = sum(importance.values())
importance_pct = {k: v / total for k, v in importance.items()}
pd.Series(importance_pct).sort_values(ascending=False).rename("importance")
価格 0.435710
ブランド 0.282776
パッケージ 0.143118
容量 0.138396
Name: importance, dtype: float64評定型コンジョイント分析の限界¶
個々のプロファイルを独立に評価させるため、属性間のトレードオフ(選好の一貫性)が反映されにくい(実際の購買では選択肢の中から1つを選ぶ相対比較が起きる)
評点尺度の使い方に回答者間で個人差がある(scale usage heterogeneity)
属性数・水準数が多いと、提示するプロファイル数が増え回答負荷が高くなる
これらの限界から、実務では 選択型コンジョイント分析(CBC) が主流になっている。