演習:A/Bテストの結果を分析しよう
田中VPoE「NetShop社がトップページのレイアウトを変更するA/Bテストを実施したんだ。結果が出たから、統計的に分析してほしい。」
あなた「テストの概要を教えてください。」
田中VPoE「コントロール群(現行デザイン)とテスト群(新デザイン)にユーザーをランダムに割り振って、2週間のデータを取った。購入率と購入金額に違いがあるかを検証してほしい。」
ミッション概要
NetShop社のA/Bテスト結果を統計的手法で分析し、新デザインの導入可否を判断します。
サンプルデータの作成
import pandas as pd
import numpy as np
from scipy import stats
np.random.seed(42)
n_control = 5000
n_test = 5000
# コントロール群
control = pd.DataFrame({
'user_id': [f'U{str(i).zfill(6)}' for i in range(1, n_control + 1)],
'group': 'control',
'visited': True,
'purchased': np.random.binomial(1, 0.032, n_control), # CVR 3.2%
'amount': np.random.lognormal(mean=7.8, sigma=0.6, size=n_control).round(0),
'pages_viewed': np.random.poisson(4.5, n_control),
'time_on_site': np.random.exponential(180, n_control).round(0), # 秒
'device': np.random.choice(['desktop', 'mobile', 'tablet'], n_control, p=[0.4, 0.5, 0.1]),
'age_group': np.random.choice(['18-24', '25-34', '35-44', '45-54', '55+'], n_control)
})
control.loc[control['purchased'] == 0, 'amount'] = 0
# テスト群(少し高いCVRと購入金額)
test = pd.DataFrame({
'user_id': [f'U{str(i).zfill(6)}' for i in range(n_control + 1, n_control + n_test + 1)],
'group': 'test',
'visited': True,
'purchased': np.random.binomial(1, 0.038, n_test), # CVR 3.8%
'amount': np.random.lognormal(mean=7.9, sigma=0.6, size=n_test).round(0),
'pages_viewed': np.random.poisson(5.2, n_test),
'time_on_site': np.random.exponential(200, n_test).round(0),
'device': np.random.choice(['desktop', 'mobile', 'tablet'], n_test, p=[0.4, 0.5, 0.1]),
'age_group': np.random.choice(['18-24', '25-34', '35-44', '45-54', '55+'], n_test)
})
test.loc[test['purchased'] == 0, 'amount'] = 0
# 統合
ab_data = pd.concat([control, test], ignore_index=True)
Mission 1: 記述統計による概要把握
まず、両群の基本統計量を比較してください。
要件
- 各群のサンプルサイズ、CVR(購入率)、平均購入金額を算出
- デバイス別・年齢層別のCVRを算出
- 両群のユーザー属性(デバイス比率、年齢層分布)が均等かを確認
解答例
# === 基本統計量 ===
summary = ab_data.groupby('group').agg(
n_users=('user_id', 'count'),
n_purchased=('purchased', 'sum'),
cvr=('purchased', 'mean'),
avg_amount_all=('amount', 'mean'),
avg_pages=('pages_viewed', 'mean'),
avg_time=('time_on_site', 'mean')
).round(4)
print("=== 基本統計量 ===")
print(summary)
# 購入者のみの平均金額
buyers = ab_data[ab_data['purchased'] == 1]
buyer_stats = buyers.groupby('group')['amount'].agg(['mean', 'median', 'std']).round(0)
print("\n=== 購入者の注文金額 ===")
print(buyer_stats)
# === デバイス別CVR ===
device_cvr = ab_data.pivot_table(
values='purchased', index='device', columns='group', aggfunc='mean'
).round(4)
print("\n=== デバイス別CVR ===")
print(device_cvr)
# === 年齢層別CVR ===
age_cvr = ab_data.pivot_table(
values='purchased', index='age_group', columns='group', aggfunc='mean'
).round(4)
print("\n=== 年齢層別CVR ===")
print(age_cvr)
# === 属性の均等性チェック ===
print("\n=== デバイス分布 ===")
device_dist = pd.crosstab(ab_data['group'], ab_data['device'], normalize='index').round(3)
print(device_dist)
print("\n=== 年齢層分布 ===")
age_dist = pd.crosstab(ab_data['group'], ab_data['age_group'], normalize='index').round(3)
print(age_dist)
Mission 2: 仮説検定の実施
統計的検定を行い、新デザインの効果を検証してください。
要件
- CVR(購入率)の差の検定(カイ二乗検定 or Z検定)
- 購入者の平均注文金額の差の検定(t検定)
- 効果量(Cohen’s d / Odds Ratio)の算出
- 95%信頼区間の計算
- デバイス別のサブグループ分析
解答例
# === 1. CVRの検定(カイ二乗検定)===
contingency = pd.crosstab(ab_data['group'], ab_data['purchased'])
chi2, p_value_cvr, dof, expected = stats.chi2_contingency(contingency)
print("=== CVRの検定(カイ二乗検定)===")
print(f"カイ二乗統計量: {chi2:.4f}")
print(f"p値: {p_value_cvr:.4f}")
# CVRの差と信頼区間
cvr_control = control['purchased'].mean()
cvr_test = test['purchased'].mean()
cvr_diff = cvr_test - cvr_control
# CVR差の95%信頼区間
se_diff = np.sqrt(
cvr_control * (1 - cvr_control) / n_control +
cvr_test * (1 - cvr_test) / n_test
)
ci_lower = cvr_diff - 1.96 * se_diff
ci_upper = cvr_diff + 1.96 * se_diff
print(f"\nCVR差: {cvr_diff:.4f} ({cvr_diff/cvr_control*100:.1f}%改善)")
print(f"95%CI: [{ci_lower:.4f}, {ci_upper:.4f}]")
# === 2. 購入金額の検定(Welchのt検定)===
buyers_control = control[control['purchased'] == 1]['amount']
buyers_test = test[test['purchased'] == 1]['amount']
t_stat, p_value_amount = stats.ttest_ind(buyers_control, buyers_test, equal_var=False)
print("\n=== 購入金額の検定(Welchのt検定)===")
print(f"t統計量: {t_stat:.4f}")
print(f"p値: {p_value_amount:.4f}")
# === 3. 効果量 ===
# Cohen's d for amount
pooled_std = np.sqrt(
((len(buyers_control)-1)*buyers_control.var() + (len(buyers_test)-1)*buyers_test.var()) /
(len(buyers_control) + len(buyers_test) - 2)
)
cohens_d = (buyers_test.mean() - buyers_control.mean()) / pooled_std
print(f"\nCohen's d (金額): {cohens_d:.3f}")
# Odds Ratio for CVR
odds_control = cvr_control / (1 - cvr_control)
odds_test = cvr_test / (1 - cvr_test)
odds_ratio = odds_test / odds_control
print(f"オッズ比 (CVR): {odds_ratio:.3f}")
# === 4. デバイス別サブグループ分析 ===
print("\n=== デバイス別サブグループ分析 ===")
for device in ['desktop', 'mobile', 'tablet']:
sub = ab_data[ab_data['device'] == device]
sub_contingency = pd.crosstab(sub['group'], sub['purchased'])
chi2_sub, p_sub, _, _ = stats.chi2_contingency(sub_contingency)
cvr_c = sub[sub['group'] == 'control']['purchased'].mean()
cvr_t = sub[sub['group'] == 'test']['purchased'].mean()
print(f"{device}: control={cvr_c:.4f}, test={cvr_t:.4f}, diff={cvr_t-cvr_c:.4f}, p={p_sub:.4f}")
Mission 3: ビジネスインパクトの算出とレポート
統計的な分析結果をビジネスインパクトに変換し、導入可否の提言をまとめてください。
要件
- 新デザイン導入時の月間売上増加額の推定
- 信頼区間を用いた最悪・最良シナリオの提示
- 導入可否の判断と根拠
- 注意点・留意事項のまとめ
解答例
# === ビジネスインパクト算出 ===
monthly_visitors = 500000 # 月間訪問数(仮定)
# 現行の月間売上
current_cvr = cvr_control
current_avg_amount = buyers_control.mean()
current_monthly_revenue = monthly_visitors * current_cvr * current_avg_amount
# 新デザインの月間売上
new_cvr = cvr_test
new_avg_amount = buyers_test.mean()
new_monthly_revenue = monthly_visitors * new_cvr * new_avg_amount
# 増加額
revenue_increase = new_monthly_revenue - current_monthly_revenue
revenue_increase_pct = revenue_increase / current_monthly_revenue * 100
print("=== ビジネスインパクト ===")
print(f"現行月間売上推定: {current_monthly_revenue:,.0f}円")
print(f"新デザイン月間売上推定: {new_monthly_revenue:,.0f}円")
print(f"増加額: {revenue_increase:,.0f}円 ({revenue_increase_pct:.1f}%)")
print(f"年間換算: {revenue_increase * 12:,.0f}円")
# 信頼区間を用いたシナリオ
worst_cvr_diff = ci_lower
best_cvr_diff = ci_upper
worst_revenue = monthly_visitors * (current_cvr + worst_cvr_diff) * new_avg_amount - current_monthly_revenue
best_revenue = monthly_visitors * (current_cvr + best_cvr_diff) * new_avg_amount - current_monthly_revenue
print(f"\n=== シナリオ分析 ===")
print(f"悲観シナリオ: 月間 {worst_revenue:,.0f}円")
print(f"基本シナリオ: 月間 {revenue_increase:,.0f}円")
print(f"楽観シナリオ: 月間 {best_revenue:,.0f}円")
# === 結論レポート ===
print("\n" + "=" * 60)
print("A/Bテスト分析レポート - 結論")
print("=" * 60)
print(f"""
【結論】新デザインの導入を推奨する
【根拠】
1. CVR: {cvr_control:.3%} → {cvr_test:.3%} (p={p_value_cvr:.4f}, 有意)
2. 月間売上増加見込み: {revenue_increase:,.0f}円
3. 悲観シナリオでも{worst_revenue:,.0f}円の増加
【留意事項】
- テスト期間が2週間と短い(季節変動の影響を受ける可能性)
- デバイス別の効果差を考慮し、段階的な展開を推奨
- 導入後もモニタリングを継続し、長期的な効果を検証すること
- 新奇性効果(テスト初期だけ高い可能性)に注意
""")
達成度チェック
- 両群の基本統計量を比較できた
- ユーザー属性の均等性を確認できた
- CVRの差をカイ二乗検定で検証できた
- 購入金額の差をt検定で検証できた
- 効果量と信頼区間を算出できた
- デバイス別のサブグループ分析ができた
- ビジネスインパクトを金額で推定できた
- 統計的な根拠に基づいた提言をまとめられた
推定所要時間:90分