ふくふくHukuhuku Inc.
EP.18KPI 14分公開: 2026-05-10

Python で実践 A/Bテスト:頻度論からベイジアンまで、可視化付きで比較検証する

EC サイトの CTA 文言(A: カートに追加 vs B: 今すぐ買う)を題材に、Python で A/B テストを最初から最後まで動かす。t 検定・χ² 検定・Mann-Whitney・Bayesian A/B の比較、信頼区間の可視化、判定の作法まで。

#A/Bテスト#Python#頻度論#ベイジアン#実装
CO📔 Google Colab で開く(上から順にセルを実行)
シェア

EP.17 で の設計を扱いました。今回は 実際に動かす。EC サイトの CTA 文言を題材に、データ生成 → 仮説検定 → 信頼区間 → ベイジアン → 可視化 まで一気通貫で示します。コードは で動きます。

シナリオ:CTA 文言テスト

  • A 案(コントロール): ボタン文言「カートに追加」
  • B 案(テスト): ボタン文言「今すぐ買う」
  • 仮説: 「今すぐ買う」の方が 意思決定が明確 で、購入率が改善するはず
  • Target: 購入率(クリック→購入の転換率)
  • Guardrail: 平均購入額、返品率、サポート問合せ
  • サンプルサイズ: 各群 5,000 人(合計 10,000 人)
  • 期間: 14 日間

1. シミュレーションデータの生成

Step 1: A/B 群のデータ生成
Python
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport japanize_matplotlib  # noqa: F401import seaborn as sns
np.random.seed(42)
# 真の購入率(実際にはこれが知りたい)TRUE_RATE_A = 0.050   # 5.0%TRUE_RATE_B = 0.063   # 6.3% (B 案が +1.3pt 良い、という仮の真実)
n_a, n_b = 5000, 5000
a_purchases = np.random.binomial(1, TRUE_RATE_A, n_a)b_purchases = np.random.binomial(1, TRUE_RATE_B, n_b)
# データフレーム化df = pd.DataFrame({    "variant":  ["A"] * n_a + ["B"] * n_b,    "purchase": np.concatenate([a_purchases, b_purchases]),})print(df.groupby("variant")["purchase"].agg(["count", "sum", "mean"]))
シミュレーションの利点

「正解」を知っているデータで各手法を試せるのが利点。実データでは「本当の真実」が分からないので、各手法の挙動を比較する材料になる。

2. チェック(最初に必ず)

Step 2: 割付比率のズレを確認
Python
from scipy import stats
a_count = (df["variant"] == "A").sum()b_count = (df["variant"] == "B").sum()expected = (a_count + b_count) / 2chi2, p_srm = stats.chisquare([a_count, b_count], [expected, expected])print(f"A: {a_count}, B: {b_count}")print(f"chi2 = {chi2:.4f}, p = {p_srm:.4f}")print("✓ SRM なし" if p_srm > 0.001 else "⚠️ SRM 検出 - 結果無効")

3. 頻度論:χ² 検定(カテゴリ変数)

購入したか / しなかったか2x2 分割表を作り、カイ二乗検定で「群と購入の独立性」を検定。p < 0.05 なら「群によって購入確率が違う」と判定。

Step 3: カイ二乗検定
Python
# 2x2 分割表contingency = pd.crosstab(df["variant"], df["purchase"])print(contingency)#  purchase     0    1#  variant#  A         4763  237#  B         4685  315
chi2, p_value, dof, expected = stats.chi2_contingency(contingency)print(f"\nchi2 = {chi2:.2f}, p-value = {p_value:.4f}")
rate_a = a_purchases.mean()rate_b = b_purchases.mean()print(f"\nA の購入率: {rate_a:.2%}")print(f"B の購入率: {rate_b:.2%}")print(f"絶対差: {(rate_b - rate_a) * 100:+.2f} pt")print(f"相対差: {(rate_b - rate_a) / rate_a * 100:+.1f}%")

4. 信頼区間の計算と可視化

p 値だけでは伝わらない信頼区間付きの棒グラフで経営層・PM に見せるのが定石。

Step 4: 95% ci と棒グラフ
Python
from statsmodels.stats.proportion import proportion_confint
# Wilson 法(小サンプルにも安全)ci_a_low, ci_a_high = proportion_confint(a_purchases.sum(), n_a, alpha=0.05, method="wilson")ci_b_low, ci_b_high = proportion_confint(b_purchases.sum(), n_b, alpha=0.05, method="wilson")
print(f"A: {rate_a:.2%}  95%CI [{ci_a_low:.2%}, {ci_a_high:.2%}]")print(f"B: {rate_b:.2%}  95%CI [{ci_b_low:.2%}, {ci_b_high:.2%}]")
fig, ax = plt.subplots(figsize=(7, 5))variants = ["A: カートに追加", "B: 今すぐ買う"]rates = [rate_a, rate_b]errors = [    [rate_a - ci_a_low, rate_b - ci_b_low],   # 下振れ    [ci_a_high - rate_a, ci_b_high - rate_b], # 上振れ]ax.bar(variants, rates, yerr=errors, capsize=12,       color=["#1f77b4", "#ff7f0e"], alpha=0.85, edgecolor="black")for i, r in enumerate(rates):    ax.text(i, r + 0.003, f"{r:.2%}", ha="center", fontweight="bold", fontsize=12)ax.set_ylabel("購入率"); ax.set_title("CTA文言A/Bテスト結果(95% CI)")ax.grid(alpha=0.3, axis="y"); ax.set_ylim(0, 0.085)plt.tight_layout(); plt.show()
「エラーバーが重なっていれば有意差なし」が直感

経営層・PM は p 値より棒グラフの重なり で理解する。95% CI のエラーバーが重ならない → 有意差あり、と覚えておけば十分。

5. ベイジアン A/B:「B が A より良い確率は何%か」

頻度論の p 値は 「両者が同じだとしたら、観測差がこれくらい極端になる確率」 で、直感的に解釈しにくい。「B が A より良い確率」 を直接出してくれる。

Step 5: ベイジアン A/B(Beta 分布の事後分布)
Python
from scipy import stats as ss
# 事前分布: Beta(1, 1) = uniform(弱情報事前)# 事後分布: Beta(1 + 成功数, 1 + 失敗数)post_a = ss.beta(1 + a_purchases.sum(), 1 + n_a - a_purchases.sum())post_b = ss.beta(1 + b_purchases.sum(), 1 + n_b - b_purchases.sum())
# モンテカルロで P(B > A) を推定n_sim = 100_000samples_a = post_a.rvs(n_sim)samples_b = post_b.rvs(n_sim)p_b_better = (samples_b > samples_a).mean()
# 期待損失(B を選んで実は A の方が良かった場合の損失期待値)expected_loss_a = np.maximum(samples_b - samples_a, 0).mean()expected_loss_b = np.maximum(samples_a - samples_b, 0).mean()
print(f"P(B > A) = {p_b_better:.1%}")print(f"Expected loss if B chosen: {expected_loss_b:.5f}")print(f"Expected loss if A chosen: {expected_loss_a:.5f}")
# 可視化: 事後分布xs = np.linspace(0.03, 0.085, 500)fig, ax = plt.subplots(figsize=(10, 5))ax.fill_between(xs, post_a.pdf(xs), alpha=0.4, color="#1f77b4", label="A 事後分布")ax.fill_between(xs, post_b.pdf(xs), alpha=0.4, color="#ff7f0e", label="B 事後分布")ax.set_xlabel("購入率"); ax.set_ylabel("密度")ax.set_title(f"A/B 事後分布   P(B > A) = {p_b_better:.1%}")ax.legend(); ax.grid(alpha=0.3); plt.tight_layout(); plt.show()
ベイジアンの判定基準

P(B > A) > 95% で「B 採用」、< 5% で「A 採用」、その間は 「結論保留 → サンプル増やす」 の3択判定が定石。Expected Loss < 閾値 で打ち切る方法もある(GrowthBook の判定ロジック)。

6. 連続値メトリクス:Mann-Whitney U

平均購入額(連続値) の比較なら t 検定Mann-Whitney U 検定金額分布は強く右に裾を引く(log-normal) ため、t検定の正規性仮定が崩れるケースが多い。Mann-Whitney(順位ベース、ノンパラ)の方が安全。

Step 6: 平均購入額の Mann-Whitney 検定
Python
# 購入者だけの平均購入額を比較a_amounts = np.random.lognormal(mean=8.5, sigma=0.6, size=int(a_purchases.sum()))b_amounts = np.random.lognormal(mean=8.4, sigma=0.6, size=int(b_purchases.sum()))
t_stat, t_p = ss.ttest_ind(a_amounts, b_amounts)u_stat, u_p = ss.mannwhitneyu(a_amounts, b_amounts, alternative="two-sided")
print(f"t-test:        p = {t_p:.4f}")print(f"Mann-Whitney:  p = {u_p:.4f}")print(f"A 中央値: {np.median(a_amounts):,.0f} 円")print(f"B 中央値: {np.median(b_amounts):,.0f} 円")

7. 判定とアクション

判定マトリクス(CTA テスト の結果例)
指標ABp値判定
購入率(Target)4.74%6.30%+1.56pt0.0009B 勝ち(有意)
平均購入額(Guardrail)5,200円5,150円-50円0.31差なし(OK)
返品率(Guardrail)2.1%2.3%+0.2pt0.62差なし(OK)
P(B > A) ベイジアン99.9%B 採用

8. Decision Log(EP.16 と接続)

Decision Log への記録
YAML
id: H-2026-Q4-024title: CTA文言テスト「カートに追加」vs「今すぐ買う」period: 2026-12-15 〜 2026-12-28(14日)sample: A=5,000 / B=5,000(SRM check OK)result:  status: success  primary: 購入率 +1.56pt (95% CI +0.65〜+2.47, p=0.0009)  bayesian: P(B > A) = 99.9%  guardrails:    - 平均購入額: 差なし    - 返品率: 差なしdecision: B 全展開learning: |  「次のアクション」が明示的な文言の方が転換率が高い。  他の CTA(「お気に入り」「予約」など)でも同様の検証を行う価値ありnext_hypothesis: H-2026-Q4-031(リスト一覧画面の「詳細を見る」を「在庫を確認」へ)

ふくふくの進め方

A/B テストを始めたいけどどこから手を付けるか分からない」というご相談には、ツール選定 + 設計フレームワーク1 ヶ月最初の 3 件の実験を伴走しながら 2〜3 ヶ月で社内に運用フローを定着させる、というロードマップ。自社 / )と の連携で「自社データ完結・ ベース」の実験基盤を組むのが定型パターンです。

ここまでのまとめ

EP.01〜18 の「指標設計の教科書」、 設計の基本から A/Bテスト実践(本記事) までを通すと、指標を作る → 育てる → 学習に繋げる → 因果を確かめる という一連の工程が見えてきます。応用編として、業界別 KPI(小売・・ゲーム・B2B)/ 時代の KPI(出力品質、ハルシネーション率)サンプル → 全体への拡大推計などのバリエーションを今後追加していきます。続編は読者リアクションに応じて随時。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp