逐次検定と覗き見(peeking)問題 ― いつ止めてよいか
実験を途中で覗いて「有意になった瞬間に止める」と偽陽性が跳ね上がります。回数固定の検定と、途中で見ても誤らない逐次検定、そしてベイズA/Bの考え方を整理し、「いつ止めてよいか」を設計に落とします。
by Shin
有意になった瞬間に実験を止めるのはなぜいけないのか。理由は単純で、途中で何度も覗いて「p < 0.05 になったら止める」を繰り返すと、本当は差が無くても偽陽性をつかむ確率が設計値の 0.05 を大きく超えるからです。この記事では、回数固定の検定・逐次検定・ベイズA/Bの三つで「いつ止めてよいか」を設計に落とします。
本連載は介入を設計して因果を測る側で、扱うのは「自分で回す実験を、いつ締めるか」という設計判断です。観測データから因果に迫る話(因果編)とは別物なので、混ぜずに進めます。素材は架空のD2C企業SOLNA(数値はレンジで丸めています)。取引件数が多く毎日コンバージョンが積み上がるため、まさに「途中で覗きたくなる」誘惑が最も強い現場です。
覗き見(peeking)問題(覗くほど偽陽性が増える)
架空のD2C企業SOLNAでは1日あたり数万セッション規模のトラフィックがあり、ベースラインのCVRは数%。新しいLPをA/Bにかけると、ダッシュボードの差は日々動きます。担当者は当然、毎朝それを見ます。そして「今日ついに p < 0.05 になった」瞬間に止めたくなる。これが覗き見です。
なぜ危ないか。p < 0.05 という閾値は「1回だけ見る」前提で偽陽性率を 5% に抑える設計です。同じデータを期間中に何度も評価し、どこか一度でも閾値を割ったら止める、という運用にすると、その設計は崩れます。検定を繰り返すほど「たまたま閾値を割る」機会が増えるからです。
これは実際に回して確かめられます。真の差がゼロのA/A(同じLPをA群・B群に出す)を作り、期間中にK回覗いて一度でも |z| > 1.96 になったら「有意」と判定する運用を、2万回シミュレートします。
import numpy as np
rng = np.random.default_rng(42)
def aa_peeking_fpr(n_per_look, n_looks, n_sims=20000, z_crit=1.96, p_true=0.03):
"""A/A(真の差ゼロ)でK回覗き、一度でも|z|>z_critになる割合=偽陽性率."""
hits = 0
for _ in range(n_sims):
nA = nB = xA = xB = 0
crossed = False
for _k in range(n_looks):
xA += rng.binomial(n_per_look, p_true)
xB += rng.binomial(n_per_look, p_true)
nA += n_per_look
nB += n_per_look
p_pool = (xA + xB) / (nA + nB)
se = np.sqrt(p_pool * (1 - p_pool) * (1 / nA + 1 / nB))
if se == 0:
continue
z = (xB / nB - xA / nA) / se
if abs(z) > z_crit:
crossed = True
break
hits += crossed
return hits / n_sims
for K in [1, 2, 5, 10, 20]:
print(f"{K:>2}回覗く: 偽陽性率 ≈ {aa_peeking_fpr(2000, K):.3f}")手元での出力はおおむね次のようになりました(乱数シードで前後します)。
| 期間中に覗いた回数 | 偽陽性率(設計値は 0.05) |
|---|---|
| 1回(最後だけ) | ≈ 0.05 |
| 2回 | ≈ 0.08 |
| 5回 | ≈ 0.14 |
| 10回 | ≈ 0.19 |
| 20回 | ≈ 0.25 |
真の差はゼロなのに、毎日覗いて20回見れば4回に1回は「勝った」と誤判定します。連続的に監視し続ければ、偽陽性率は理屈の上では 1 に近づいていきます。「有意になったら止める」運用は、止める権利を行使するたびに自分で偽陽性率を釣り上げているのと同じです。
回数固定の検定
いちばん素直な対処は、覗かないことです。第1回(検出力・MDE・必要サンプル数)で決めた n を走らせ切り、その1回だけで判定する。これが回数固定(fixed horizon)の検定です。設計どおり偽陽性率は 5% に収まります。
回数固定の弱点は実務との相性です。明らかに勝っている/破綻している施策でも n に達するまで止められず、逆に途中の良い数字を見て我慢できずに止めてしまう。SOLNAのように毎日データが積み上がる現場では、「最後まで見ない」という規律はほぼ守られません。だからこそ、覗くことを前提に偽陽性を制御する設計が要ります。
逐次検定(途中で見ても誤らない設計)
逐次検定は「途中で何度見ても、トータルの偽陽性率を 5% に収める」ように閾値そのものを設計し直す方法です。大きく二系統あります。
ひとつは群逐次(group sequential)。覗くタイミングを事前に決め、各回の閾値を 1.96 より厳しくして、使える偽陽性の総量(アルファ)を各回に配分します(アルファ消費)。この配分の仕方には代表的な流儀が2つあります。Pocock は全回で一定の厳しい閾値に、O'Brien-Fleming は序盤を非常に厳しく終盤を緩める配分にします。先ほどのA/Aを、回数に応じたPocock境界で判定し直すと、偽陽性率は設計値に戻ります。
# Pocock の定数境界(両側 α=0.05・等間隔 K 回looks)の臨界z(近似)
pocock_z = {1: 1.960, 2: 2.178, 3: 2.289, 5: 2.413, 10: 2.555}
for K in [2, 5, 10]:
fpr = aa_peeking_fpr(2000, K, z_crit=pocock_z[K])
print(f"{K:>2}回覗く(Pocock z={pocock_z[K]}): 偽陽性率 ≈ {fpr:.3f}")出力はいずれも 0.05 前後に戻ります。同じ「途中で覗く」運用でも、閾値を回数に合わせて厳しくするだけで設計が守られる、という点が肝です。タダではなく、各回の検出力は少し下がる(同じ差を見つけるのにやや多くのサンプルが要る)のが代償です。
もうひとつは常時有効(always-valid)な推測。覗く回数を事前に固定せず、いつ止めても正しい p 値・信頼区間を与える設計で、mSPRT(混合逐次確率比検定)や信頼系列(confidence sequence)が該当します(この枠組みは Johari ほか「Always Valid Inference」で定式化され、商用では Optimizely の Stats Engine が実装しています)。「毎日見て、好きなときに止めたい」という現場の実態に最も素直に合いますが、自由に止められる代わりに回数固定の1回より保守的(必要データが増える)です。
実務の落とし所は、毎日全部見たいなら常時有効、覗く回数を週次など数回に固定できるなら群逐次。多くの組織には、事前にlook回数を決められる群逐次のほうが運用も説明もしやすい、というのが定石です(Kohavi ほか『A/Bテスト実践ガイド』)。いずれにせよ「停止ルールを走らせる前に決め、実行中は変えない」が共通の規律です。
ベイズA/Bの考え方
ベイズA/Bは問いの立て方が違います。p 値ではなく、事後分布から P(B > A)(Bが優れている確率)や、誤った採択で被る期待損失(expected loss)を出し、「期待損失が許容閾値を下回ったら採択」という決め方をします。判断が「有意/非有意」の二値ではなく、損失と利得の天秤になるのが利点です。
ただし、しばしば語られる「ベイズなら好きなときに覗いて止めてよい」は鵜呑みにしすぎない方が安全です。P(B > A) > 0.95 を停止条件にして連続的に覗けば、頻度論的な偽陽性(本当は差が無いのに採択する確率)はやはり上振れします。覗き見の害が消えるのではなく、評価する物差しが事後確率に替わっただけです。ベイズが本当に与えてくれるのは「停止ルール不要の魔法」ではなく、「不確実性下の意思決定を期待損失で設計し直す枠組み」です。長期の誤り率を担保したいなら、ベイズでも停止ルールは設計対象に残ります。
章末チェックリスト
- 覗き見がなぜ偽陽性を増やすか、A/Aの数字で説明できる
- 回数固定と逐次の違い(閾値を回数に配分する)を言える
- 覗くなら群逐次か常時有効か、自社の運用に合わせて選べる
- ベイズでも停止ルールは設計対象に残る、と理解している
- 「停止ルールを走らせる前に決め、実行中は変えない」を運用に明文化した
ここまでで「いつ止めてよいか」を設計に落としました。次回は、サンプルを増やせない現場で検出力を稼ぐ分散削減(CUPED)と層化を扱います。分けられない施策をどう測るか、観測手法(DiD・合成コントロール)との地続きを問う段階は、対になる因果編へ続きます。