【翻訳】ベイズA/Bテスト入門(Maximilian Speicher, UX Collective)

uxdesign.cc

AIによる要約:A/Bテストの結果としてよく出される「p値」や「信頼区間」は、一見わかりやすそうに見えて、実は直感的に理解しにくい性質を持っています。多くの人は「改善が得られる確率」だと思ってしまいがちですが、それは誤解です。

こうした課題を解決するのが、ベイズ統計に基づくA/Bテストの手法です。これは、テストの結果から「この施策で効果が出る確率は何%か」「最低でもどの程度の成果が期待できるか」といった、意思決定に役立つ情報を直接的に計算することができます。

記事では、ベイズ統計の基本的な考え方を説明したうえで、Pythonを使った実装手順を紹介しています。難しい数式は最小限に抑えられており、実際の改善確率や期待できる効果の範囲をグラフとして視覚的に示す方法まで解説されています。

特に注目すべきは、「改善が得られる確率」と「悪化するリスク」の両方を同時に可視化できる点です。これにより、施策を導入すべきかどうかを数字と根拠にもとづいて判断できるようになります。

最終的に記事が伝えているのは、A/Bテストをより正しく、より有効に活用したいなら、ベイズ的な視点を取り入れるべきだということです。意思決定に必要なのは単なる「有意かどうか」ではなく、「どれだけの改善がどれほどの確率で期待できるか」であり、ベイズ統計はそれを明確に示してくれます。

UXに関わる人々や、より良い実験を目指す、すべての人へ(統計やPythonに対するある程度の覚悟は必要です)。

おめでとうございます。A/Bテストの結果が返ってきました。分析チームは、収集したデータについて、いつものように頻度主義的な分析を行いました。それによれば、ショッピングカート内に設置した新しいカウントダウンタイマーによって、1.8パーセントポイントの向上が見られ、p値は0.012となっています。向上率に関する95%信頼区間は [0.4%p, 3.2%p] です。

したがって、あなたはレポートの結論に次のように書きます。「1.8パーセントポイントのコンバージョン増加が起きる確率は98.8%、真の向上幅が0.4%pから3.2%pの範囲にある確率は95%」。なかなか良い結果のように思えます¹。

A/Bテストは、データ主導の意思決定においてゴールドスタンダードとなりましたが、実務者が知りたいことと、よく使われる統計手法――すなわち頻度主義統計――が伝えられることの間には、常にズレがあります。「頻度主義的」とは、ごくおおまかに言えば、(帰無)仮説が正しいときにデータが観測される確率を求めるという意味です。私は以前、ACM Interactions誌に掲載されたA/Bテストにおける統計的有意性に関する記事(Speicher, 2022)の中で、p値と有意性検定がどのように誤解され、誤用されているかを強調しました。

というのも、あなたが分析チームから提供された情報をもとに導いた上記の結論は、完全に誤りだからです🤯

「素晴らしい!1.8%pの向上が得られる確率は98.8%だ」というのは、ごく自然な反応です。そしてそれは、直感的な解釈として受け入れられるものです――ベイズ的分析を行う場合には、です。(「ベイズ的」とは、ごくおおまかに言えば、観測されたデータに基づいて、さまざまな仮説の確からしさを評価することです。)

一方で頻度主義的統計において、p値とは「もしコントロールとテストの間に違いがなかった場合に、今回観測されたものと同じかそれ以上に極端な結果が得られる確率」を意味します(Speicher, 2022)。また、95%信頼区間とは「この実験を無限に繰り返して、それぞれの実験において信頼区間を算出したとき、95%の信頼区間が真のパラメータを含む」という意味です(Fiksel, 2018)。これ以上に直感的な言い方は存在しません。

この齟齬が示すのは、実務者の多くが自然とベイズ的な思考をしているという事実です。つまり彼らは「この施策によって、一定以上の成功が得られる確率はどれくらいか」ということを知りたいのです。ベイズ的A/Bテストは、まさにその問いに直接答えることができます。たとえば「5%以上の改善が見られる確率は?」というような、ビジネスに即した疑問に答えるのです。

私が今でも特に注目しているのは、補完的累積分布関数(CCDF)を使って、ありうる最小のアップリフトやダウンリフトに対する確率全体の分布を可視化できる点です。

あるサンプルA/Bテストにおける補完的累積分布関数(CCDF)

この図からはたとえば、「10%以上の向上が見られる確率は69.2%」、「5%以上であれば81.7%」ということが読み取れます。すごく興味深いと思いませんか?

頻度主義とベイズ主義の統計は、どちらも正しく文脈に応じて使えば優れています。優劣は存在しません。したがって、頻度主義とベイズ主義のアプローチを比較するという、よくある論点を繰り返す代わりに、本記事では実践的な実装に注力します。つまり、上のような図に最終的にどうやって到達するかという点です。理論的な比較について詳しく知りたい方は、Moreno & Girón (2006) による仮説検定手法の体系的な比較や、Silva & Favoreto (2022)Kameleoon (2023)Burch (2024) などのA/Bテスト実務における比較記事をご覧ください。

このガイドでは、ベイズ的A/Bテストの完全なフレームワークをゼロから構築する方法、数学的な基盤の理解、そしてビジネスにとって分かりやすく結果を伝えるための視覚化手法について学びます。

この記事を最後まで読めば、「95%の確率でX%以上のコンバージョン改善が見込まれる」といった、実務的な意思決定を支える発言ができるようになります。そして必要なのは、コントロール群とテスト群における訪問者数とコンバージョン数――たった4つの数字だけです。

免責事項:図表の作成に関しては、PythonでClaudeの助けを少し借りました。あれは本当に私の得意分野ではないのです。

数学的基礎

ベイズ的A/Bテストは、データを収集していく中で、テスト結果に対する信念を更新するための枠組みを提供します。従来の手法が「帰無仮説の棄却」に焦点を当てるのに対し、ベイズ的アプローチは不確実性を直接的に定量し、ありうる結果に対する確率分布を与えてくれます。

なぜベイズ的手法がA/Bテストにおいて非常に強力なのかをよりよく理解するために、まずはその基礎にある数学を簡単に見ていきましょう。

ベイズの定理

ベイズ統計の中心にあるのは、ベイズの定理です。

P(θ|Data) ∝ P(Data|θ) × P(θ)

ここでは、事後確率の相対比較(例:θₐ と θᵦの比較)を行っているため、周辺尤度 P(Data) は打ち消しあうことになり、比例式の形で十分です(Sureshkumar, 2021b)。

各項の意味は以下の通りです:

  • P(θ|Data) は、観測されたデータが与えられたもとでのパラメータθの事後確率です。
  • P(Data|θ) は、パラメータθのもとでそのデータが観測される尤度です。
  • P(θ) は、データを見る前に抱いていたパラメータθに対する事前の信念です。

コンバージョン率テストにおいて、θは「真のコンバージョン率」を表します。

なぜベータ分布が適しているのか

コンバージョンのような「成功/失敗」という2値の結果に対しては、ベータ分布が事前分布として自然な選択となります。その理由は以下の通りです:

  1. 定義域が [0, 1] にあるため、コンバージョン率の取りうる範囲と一致しています。
  2. ベータ分布は二項分布に対する共役事前分布であり、更新後の事後分布もベータ分布になるという数学的な便利さがあります。
  3. 2つのパラメータ(αとβ)を使って、さまざまな形の信念を柔軟に表現できるからです。

ベータ分布の確率密度関数(PDF)は以下の通りです(パラメータα, βに基づく):

f(x; α, β) = (xα-1 × (1−x)β-1) / B(α, β)

ここでB(α, β)はベータ関数であり、全体を正規化するための定数です(Stucchio, 2015)。

もし2値の結果ではなく、たとえば収益ベースの指標などを扱う場合には、ベータ分布の代わりにガンマ分布などを事前分布として用いることも可能です(詳しくは Stucchio, 2015Sureshkumar, 2021a を参照)。

ベイズ的A/Bテストツールの構築

基礎を一通り押さえたところで、実践的なベイズ的A/BテストツールをPythonで構築していきましょう。ここでは、以下の処理を行う包括的なツールを作成します:

  1. 2つのバリアントにおけるコンバージョンデータを入力とする
  2. 事後確率を計算する
  3. 結果を直感的かつ実用的な形で視覚化する

ステップ1:環境のセットアップ

まず必要なライブラリをインポートします。

import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
from scipy.stats import beta
  • numpy は、配列演算などの数学的関数を提供します。
  • scipy.stats は、統計分布や統計関数を提供します。
  • matplotlib.pyplot は、グラフ描画のためのライブラリです。
  • beta は、scipy.stats から取得するベータ分布の専用関数です。

ステップ2:テストデータの設定

次に、テストデータを定義します。

# サンプルデータの生成
np.random.seed(42)  # もちろん、「人生、宇宙、すべての答え」です

# コンバージョンデータのシミュレーション
# コントロール群(A):訪問者1000人、コンバージョン120件(12%)
# テスト群(B):訪問者1000人、コンバージョン140件(14%)
control_visitors = 1000
control_conversions = 120
treatment_visitors = 1000
treatment_conversions = 140

この値は、簡略化されたA/Bテストのシナリオを表しています:

  • 両群ともサンプルサイズは同じ(1000人)
  • コントロール群のコンバージョン率は12%
  • テスト群のコンバージョン率は14%

つまり、相対的には16.67%の改善となります。

ここで示したのは一例にすぎませんので、ご自身の実験結果に差し替えることが可能です。必要なのは、2群それぞれの訪問者数とコンバージョン数だけです。

ステップ3:ベータ分布の理解

コンバージョン率のベイズ的A/Bテストでは、ベータ分布が共役事前分布として使われるのが一般的です(先述の通り)。この数学的な性質によって、データが増えても信念の更新が容易に行えるようになります。

ベータ分布には、α(アルファ)とβ(ベータ)の2つのパラメータがあります:

  • α は、「成功回数(=コンバージョン数)+事前の成功数」を表します
  • β は、「失敗回数(=非コンバージョン数)+事前の失敗数」を表します

ベータ(1,1)という事前分布は、「0から1まで等確率であり、事前情報を持たない」という一様分布と同じです。

# 事前パラメータ(事前情報が弱いことを仮定)
prior_a = 1
prior_b = 1

# 事後パラメータの計算
control_posterior_a = prior_a + control_conversions
control_posterior_b = prior_b + (control_visitors - control_conversions)

treatment_posterior_a = prior_a + treatment_conversions
treatment_posterior_b = prior_b + (treatment_visitors - treatment_conversions)

これにより、コントロール群の事後分布は Beta(121, 881)、テスト群は Beta(141, 861) となります。

ここでの「成功」とはコンバージョン数(control_conversions / treatment_conversions)のことで、αに加算されます。「失敗」はコンバージョンしなかった人の数(visitors - conversions)で、βに加算されます。

ステップ4:事後分布の視覚化

次に、これらの分布を視覚化する関数を作成しましょう。

def plot_beta_distribution(a1, b1, a2, b2):
    """2つのベータ分布をプロットする関数"""
    x = np.linspace(0, 0.3, 1000)  # コンバージョン率として妥当な範囲を選択

    # ベータ分布の確率密度関数(pdf)を計算
    y1 = beta.pdf(x, a1, b1)
    y2 = beta.pdf(x, a2, b2)

    plt.figure(figsize=(10, 6))
    plt.plot(x, y1, label="Control")
    plt.plot(x, y2, label="Treatment")
    plt.fill_between(x, y1, alpha=0.2)
    plt.fill_between(x, y2, alpha=0.2)
    plt.xlabel('Conversion Rate')
    plt.ylabel('Density')
    plt.title('Posterior Distributions of Conversion Rates')
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()

# 事後分布をプロット
plot_beta_distribution(
    control_posterior_a, control_posterior_b,
    treatment_posterior_a, treatment_posterior_b
)

この関数では、次のことを行っています:

  1. コンバージョン率の妥当な範囲(0〜30%)を定義(3行目。np.linspace(0, 0.3, 1000) により1000点の範囲を生成)
  2. ベータ分布の確率密度を各点で計算(5行目以降。beta.pdf() を用いてコントロールとテストの密度関数を算出)
  3. 2つの分布をグラフに描画(9行目以降。plt.plot()fill_between() による可視化)

この関数への入力は、ステップ3で計算したコントロールとテストのベータ事後パラメータです。

作成されたグラフは、観測データと事前分布に基づく各バリアントの「ありうるコンバージョン率」の分布範囲を示します。

この確率分布曲線の読み方

Y軸の「Density(密度)」とは何か?

密度とは、「特定のコンバージョン率において、どれだけ確率が集中しているか」を示します。値が高いほど、その点の確率が高いことを意味します。

読み取るべきポイント:

  • 山の頂点:そのバリアントにおいて、最もありそうなコンバージョン率
  • 曲線の幅:不確実性の度合い(狭い=確信が強い、広い=不確実性が高い)
  • 曲線の下の面積:全体で100%の確率(面積は常に1)
  • 曲線の高さ比較:高い=よりありそうなコンバージョン率

現実世界での解釈:

「この地点の密度が39である」というよりも、「このバリアントのコンバージョン率はおそらく12%あたりだ」と読み取るのが自然です。

視覚的に注目すべき点:

  1. どちらの曲線が高く尖っているか? → その分布に対する信頼度が高いことを示します。
  2. 2つの曲線がどれだけ重なっているか? → 重なりが少ないほど、明確な勝者がいることを意味します。
  3. ピークがどこにあるか? → 期待されるコンバージョン率を示しています。

重要なのは、密度の数値そのものよりも、曲線の形と位置です。

ステップ5:改善確率の計算

ベイズ的分析の大きな利点の1つは、「あるバリアントが他のバリアントより優れている確率」を直接的に計算できることです。ここでは、そのためにモンテカルロシミュレーションを使います(参考:Stucchio, 2015)。

# 両分布からサンプルを生成
samples = 100000
control_samples = np.random.beta(control_posterior_a, control_posterior_b, samples)
treatment_samples = np.random.beta(treatment_posterior_a, treatment_posterior_b, samples)

def calculate_probability_of_improvement():
    """
    モンテカルロシミュレーションを用いて、
    テスト群がコントロール群より優れている確率を計算
    """
    # 改善確率の計算
    prob_improvement = np.mean(treatment_samples > control_samples)

    # 期待されるリフト(改善率)の計算
    expected_lift = np.mean((treatment_samples - control_samples) / control_samples * 100)

    return prob_improvement, expected_lift

# 改善確率と期待リフトの計算
prob_improvement, expected_lift = calculate_probability_of_improvement()

print(f"テスト群がコントロール群より優れている確率: {prob_improvement:.1%}")
print(f"期待される改善率(リフト): {expected_lift:.1f}%")

出力結果:

テスト群がコントロール群より優れている確率: 90.9%
期待される改善率(リフト): 17.4%

このコードは以下の処理を行っています:

  1. 各分布から10万件の乱数サンプルを生成np.random.beta() を使って、各群の事後分布から100,000の可能な真のコンバージョン率をランダムに取得します。
  2. それぞれのサンプルペアを比較して、どちらが良いかを判定treatment_samples > control_samples によるブール配列を作成します。
  3. テスト群が勝った割合を計算np.mean() によって、勝率(改善確率)を算出します。
  4. リフトの期待値を算出 → 全体の差分平均を計算し、何%の改善が見込めるかを得ます。

ベイズ的なモンテカルロ法が有効なのは、各サンプルが「観測されたデータと事前分布に基づく、真のコンバージョン率の可能性の1つ」を表しているからです。

ステップ6:信用区間の計算

最後に、それぞれのバリアントに対して信用区間(credible interval)を計算する関数を追加します。

def get_credible_interval(a, b, confidence=0.95):
    """ベータ分布における信用区間を計算する関数"""
    return beta.ppf([(1-confidence)/2, 1-(1-confidence)/2], a, b)

# コントロール群とテスト群の信用区間を計算
control_ci = get_credible_interval(control_posterior_a, control_posterior_b)
treatment_ci = get_credible_interval(treatment_posterior_a, treatment_posterior_b)

print(f"\n95%信用区間:")
print(f"コントロール群: [{control_ci[0]:.1%}, {control_ci[1]:.1%}]")
print(f"テスト群: [{treatment_ci[0]:.1%}, {treatment_ci[1]:.1%}]")

出力結果:

95%信用区間:
コントロール群: [10.1%, 14.2%]
テスト群: [12.0%, 16.3%]

この信用区間は、「真のコンバージョン率がこの範囲に含まれている確率が95%である」ことを意味します。

頻度主義における信頼区間(confidence interval)とは異なり、ベイズ的な信用区間は直感的な解釈が可能です。つまり:

「真の値がこの範囲にある確率が95%である」

ということをそのまま意味します。 (これが、頻度主義の信頼区間にしばしば誤って帰される解釈です。)

ステップ7:アップリフト分布の視覚化

さて、前のステップが最終段階……と思いきや、ビジネス上の意思決定に非常に役立つもう1つの強力な機能があります。

テスト群が優れている確率や、平均リフトといった2つの数値を出すだけではなくアップリフトやダウンリフトの「分布そのもの」を可視化するグラフを作成してみましょう。

これも、先ほどと同じくモンテカルロシミュレーションを活用します(参考:Stucchio, 2015)。

# 相対的な差(アップリフト率)の計算
uplift_samples = (treatment_samples - control_samples) / control_samples * 100

def plot_uplift_distribution():
    """
    相対的な改善(アップリフト)の分布を描画
    正のリフトと負のリフトを色分けして表示
    """
    # グラフの準備
    plt.figure(figsize=(12, 6))

    # ヒストグラムの作成(透明)
    counts, bins, _ = plt.hist(uplift_samples, bins=50, density=True, alpha=0)
    bin_centers = (bins[:-1] + bins[1:]) / 2

    # 0%に最も近いx座標を見つける
    zero_idx = np.abs(bin_centers).argmin()

    # 負の領域(赤)
    plt.fill_between(bin_centers[:zero_idx + 1], counts[:zero_idx + 1],
                     color='red', alpha=0.3)
    # 正の領域(緑)
    plt.fill_between(bin_centers[zero_idx:], counts[zero_idx:],
                     color='green', alpha=0.3)

    # 曲線ラインの描画
    plt.plot(bin_centers, counts, color='black', alpha=0.7)

    # パーセンタイル(5%, 25%, 50%, 75%, 95%)を取得
    percentiles = np.percentile(uplift_samples, [5, 25, 50, 75, 95])

    # 95%信用区間を示す垂直線
    plt.axvline(percentiles[0], color='gray', linestyle='--', alpha=0.5)
    plt.axvline(percentiles[4], color='gray', linestyle='--', alpha=0.5)

    # 正の改善確率の再計算
    prob_positive = np.mean(uplift_samples > 0)

    plt.xlabel('相対的な改善率 (%)')
    plt.ylabel('密度')
    plt.title('相対的な改善率の分布\n'
             f'正の改善が得られる確率: {prob_positive:.1%}')

    # パーセンタイル注釈
    plt.text(0.02, 0.95, f'95%信用区間: [{percentiles[0]:.1f}%, {percentiles[4]:.1f}%]',
             transform=plt.gca().transAxes)
    plt.text(0.02, 0.90, f'中央値: {percentiles[2]:.1f}%',
             transform=plt.gca().transAxes)

    plt.grid(True, alpha=0.3)
    plt.show()

    # 詳細な確率テーブルを出力
    print("\n最低限達成できる可能性のある改善率:")
    for threshold in list(range(21)):
        prob = np.mean(uplift_samples > threshold)
        if prob >= 0.5:
            print(f"{threshold:>3}%以上の改善: {prob:.1%}")

    print("\nこれ以上の悪化となる確率:")
    for threshold in list(range(-1, -21, -1)):
        prob = np.mean(uplift_samples < threshold)
        if prob >= 0.01:
            print(f"{threshold:>3}%以下の悪化: {prob:.1%}")

# グラフを描画
plot_uplift_distribution()

出力例:

最低限達成できる可能性のある改善率:
  0%以上の改善: 90.9%
  1%以上の改善: 89.4%
  2%以上の改善: 87.7%
  3%以上の改善: 85.8%
  4%以上の改善: 83.9%
  5%以上の改善: 81.7%
  6%以上の改善: 79.4%
  7%以上の改善: 77.1%
  8%以上の改善: 74.6%
  9%以上の改善: 71.9%
 10%以上の改善: 69.2%
 11%以上の改善: 66.4%
 12%以上の改善: 63.5%
 13%以上の改善: 60.6%
 14%以上の改善: 57.7%
 15%以上の改善: 54.8%
 16%以上の改善: 51.8%

これ以上の悪化となる確率:
 -1%以下の悪化: 7.8%
 -2%以下の悪化: 6.6%
 -3%以下の悪化: 5.5%
 -4%以下の悪化: 4.6%
 -5%以下の悪化: 3.8%
 -6%以下の悪化: 3.1%
 -7%以下の悪化: 2.5%
 -8%以下の悪化: 2.0%
 -9%以下の悪化: 1.6%
-10%以下の悪化: 1.3%

このコードが行っていることは以下のとおりです:

  1. ステップ5で生成したサンプルからアップリフト率のリストを作成
  2. そのリストのヒストグラムを作成して、正と負の領域を色分け
  3. 5パーセンタイル〜95パーセンタイル区間を信用区間として描画
  4. 改善の確率中央値などの指標を追加で表示
  5. 特定の閾値ごとの改善・悪化確率を表形式で出力

この視覚化によって、以下が明確になります:

  • 期待される改善率の範囲
  • 何らかの改善が得られる確率(タイトルに表示)
  • 逆に悪化するリスク
  • 信用区間と中央値の位置関係

なお、このアップリフトの中央値は、ステップ2で計算した相対改善率(16.67%)と一致しています。

ただしY軸は「密度」であり、数値の直観的な意味は持ちません。 そのため、次に紹介する「ステップ7.2」で、より実用的かつ視覚的に理解しやすい図を作成します。

ステップ7.2:補完的累積分布関数(CCDF)の可視化

前のステップで作成した確率テーブルは、すでにビジネス上の意思決定に非常に役立ちますが、それをさらに直感的かつ視覚的に理解できる形にしてみましょう。 ここで使うのが、いわゆる補完的累積分布関数(CCDF)というものです。

def plot_uplift_downlift_ccdf():
    """
    CCDFを用いて、アップリフトとダウンリフトの確率を両方可視化する関数
    """
    # 評価するしきい値の範囲を定義
    thresholds = np.linspace(-20, 30, 200)
    probabilities_uplift = []
    probabilities_downlift = []

    # 各しきい値に対して確率を計算
    for threshold in thresholds:
        if threshold >= 0:
            # 正のしきい値:X%以上の改善が得られる確率
            prob_uplift = np.mean(uplift_samples >= threshold)
            probabilities_uplift.append(prob_uplift)
            probabilities_downlift.append(0)
        else:
            # 負のしきい値:|X|%以上の悪化となる確率
            prob_downlift = np.mean(uplift_samples <= threshold)
            probabilities_downlift.append(prob_downlift)
            probabilities_uplift.append(0)

    # 配列化
    thresholds = np.array(thresholds)
    probabilities_uplift = np.array(probabilities_uplift)
    probabilities_downlift = np.array(probabilities_downlift)

    # グラフの作成
    plt.figure(figsize=(12, 8))

    # アップリフト側(緑)
    uplift_mask = thresholds >= 0
    plt.plot(thresholds[uplift_mask], probabilities_uplift[uplift_mask],
             linewidth=3, color='green', label='P(Uplift ≥ X%)')

    # ダウンリフト側(赤)
    downlift_mask = thresholds < 0
    plt.plot(thresholds[downlift_mask], probabilities_downlift[downlift_mask],
             linewidth=3, color='red', label='P(Downlift ≥ |X|%)')

    # 水平の補助線(参考ライン)
    for prob_level in [0.01, 0.05, 0.1, 0.2, 0.5, 0.8, 0.9, 0.95]:
        plt.axhline(y=prob_level, color='gray', linestyle='--', alpha=0.3)
        if prob_level >= 0.05:
            plt.text(-19, prob_level + 0.005, f'{prob_level:.0%}', fontsize=9, alpha=0.7)

    # 0%の基準線
    plt.axvline(x=0, color='black', linestyle='-', alpha=0.5, linewidth=2)

    # 垂直の補助線(参考値)
    for level in [5, 10, 15, 20]:
        plt.axvline(x=level, color='gray', linestyle='--', alpha=0.3)
        plt.axvline(x=-level, color='gray', linestyle='--', alpha=0.3)

    # アップリフトの代表点(確率25%以上のしきい値)
    for threshold in [0, 5, 10, 15, 20]:
        prob = np.mean(uplift_samples >= threshold)
        if prob >= 0.25:
            plt.plot(threshold, prob, 'go', markersize=8)
            plt.annotate(f'{prob:.1%}',
                         xy=(threshold, prob),
                         xytext=(threshold + 1, prob + 0.03),
                         fontsize=10, fontweight='bold',
                         color='darkgreen',
                         arrowprops=dict(arrowstyle='->', color='darkgreen', alpha=0.7))

    # ダウンリフトの代表点(確率1%以上のしきい値)
    for threshold in [0, -5, -10, -15, -20]:
        prob = np.mean(uplift_samples <= threshold)
        if prob >= 0.01:
            plt.plot(threshold, prob, 'ro', markersize=8)
            plt.annotate(f'{prob:.1%}',
                         xy=(threshold, prob),
                         xytext=(threshold - 2, prob + 0.01),
                         fontsize=10, fontweight='bold',
                         color='darkred',
                         arrowprops=dict(arrowstyle='->', color='darkred', alpha=0.7))

    # 軸とラベルの設定
    plt.xlabel('…X%以上のアップリフト/ダウンリフトである確率', fontsize=12)
    plt.ylabel('確率', fontsize=12)
    plt.title('X%以上の改善確率 vs. X%以上の悪化確率\nCCDFによる可視化', fontsize=14, fontweight='bold')

    plt.grid(True, alpha=0.3)
    plt.xlim(-20, 25)
    plt.ylim(0, 1)

    # Y軸をパーセント表示に
    plt.gca().yaxis.set_major_formatter(plt.FuncFormatter(lambda y, _: f'{y:.0%}'))

    plt.legend(fontsize=12, loc='upper right')
    plt.tight_layout()
    plt.show()

# 可視化の実行
plot_uplift_downlift_ccdf()

図:上記のコードによって生成されたCCDFグラフ

この関数では以下のことを行っています:

  1. ステップ7で生成した10万組の比較結果を使って、しきい値に対する確率を200個算出
  2. それらの確率を、アップリフト(緑)とダウンリフト(赤)で分けて描画
  3. 主要なポイントに注釈と矢印を付けて、確率の読み取りを支援
  4. しきい値別のリスクとリターンを視覚的に比較可能に

このグラフの読み方

緑の線(右側)

  • 「X%以上の改善が得られる確率」を示しています。
  • 値が高いほど、対象の改善が得られる見込みが高いことを意味します。
  • 線が右下がりなのは、大きな改善ほど実現が難しいからです。

赤の線(左側)

  • 「X%以上の悪化が起きる確率」を示します。
  • これはリスク側の評価になります。
  • 成功しているテストでは、この値が低くなるのが理想です。

ビジネス的な解釈:

  • 緑線:リターンの可能性(上振れ)
  • 赤線:リスクの可能性(下振れ)
  • 0%の位置:改善全体 vs. 悪化全体の確率差

この可視化によって:

  • 「5%以上の改善が得られる確率は81.7%」
  • 「10%以上の悪化が起きる確率は1.3%」 のように、任意のリスク/リターン水準ごとに確率を即座に把握できます。

「アップリフトが得られたかどうか」だけでなく、「どの程度のアップリフトが、どのくらいの確率で得られるのか」まで可視化することで、従来のp値や単一の改善率よりもはるかにリスク・リターンの全体像を把握可能です。

結論

ここまでの内容は、特にA/BテストやPythonプログラミングにあまり関わってこなかった方にとっては、情報量が多く感じられるかもしれません。 しかしながら、現代のビジネスでは多くの重要な意思決定がA/Bテストに依拠しており、その仕組みを正しく理解することは極めて重要です――特にUXの立場にある人間にとっては、なおさらです。

私は、できる限り丁寧に注釈と解説を加えながら説明してきたつもりですが、コードの部分については、もし難しく感じた方がいれば、ぜひPythonやプログラミングの入門チュートリアルを検索してみてください。 現在では、素晴らしい教材がたくさん存在しています。 一度理解できるようになれば、それほど難しいものではありません――そして何よりも、今後の時代において極めて重要なスキルです

ベイズ的A/Bテストは、オンライン実験において強力かつ直感的なアプローチを提供してくれます。 それは、企業が最も知りたい問い――

「この変更によって、私たちの指標は改善される可能性がどれほどあるのか?」 「改善が得られるとすれば、その規模はどれほどか?」

――に、直接的な答えを与えてくれるからです。

また、実務においては多くの人が頻度主義的な結果をベイズ的に解釈してしまっている現状もあります。 であれば、最初からベイズ的手法を使ってしまうほうが、ずっと合理的ではないでしょうか。

ここで紹介したステップバイステップのガイドとツールは、ベイズ的A/Bテストを自身の仕事に取り入れるための出発点となるはずです。 理論的な基礎と実践的な実装の両方を理解することで、テスト結果にもとづいて、より確信をもって、根拠のある意思決定が可能になります。

もちろん、どんな統計手法にも万能性はありません。 大事なのは、それぞれの手法の限界を理解し、自分の目的や文脈に応じて適切なツールを選ぶという姿勢です。

脚注

¹ ^ この段階までしか読まない方のために言っておくと――残念ながら、ここまでの内容はすべて間違いです。

本記事および全コードスニペットは、Google Colab 上でも利用可能です: https://colab.research.google.com/drive/16ip4kdFyb_3Meo4jq2Z4rzVQQNh0I7dV?usp=sharing

参考文献

Burch, Phil. “Frequentist vs. Bayesian: Comparing Statistics Methods for A/B Testing,” 2024年。2025年6月7日アクセス。 https://amplitude.com/blog/frequentist-vs-bayesian-statistics-methods

Fiksel, Jacob. “Explaining frequentist confidence intervals,” 2018年。2025年6月7日アクセス。 https://jfiksel.github.io/2018-01-08-explaining-confidence-intervals/

Kameleoon. “Frequentist vs Bayesian AB Testing: Which Method Is Right for You?” 2023年。2025年6月7日アクセス。 https://www.kameleoon.com/blog/ab-testing-bayesian-frequentist-statistics-method

Moreno, Elias, and F. Javier Girón. “On the frequentist and Bayesian approaches to hypothesis testing”SORT: statistics and operations research transactions 第30巻 第1号(2006年):3–54頁。

Silva, Isabella B., and Favoreto, Bernardo. “Bayesian or frequentist: which approach is better for AB testing?” 2022年。2025年6月7日アクセス。 https://blog.croct.com/post/bayesian-vs-frequentist

Speicher, Maximilian. “We need to get rid of significance in A/B testing, seriously!” Interactions 第29巻 第2号(2022年):8–9頁。

Stucchio, Chris. Bayesian A/B Testing at VWO。ホワイトペーパー、Visual Website Optimizer、2015年。

Sureshkumar, Kaushik. “Bayesian AB Testing — Part II — Revenue,” 2021年。2025年6月8日アクセス。 https://towardsdatascience.com/bayesian-ab-testing-part-ii-revenue-1fbcf04f96cd/

Sureshkumar, Kaushik. “Bayesian AB Testing — Part IV — Choosing a Prior,” 2021年。2025年6月8日アクセス。 https://towardsdatascience.com/bayesian-ab-testing-part-iv-choosing-a-prior-5a4fe3223bfd/

さらなる学習のためのリソース