はじめに
A/Bテストの結果レポートを見ていて、こんな経験はありませんか?
- 「p値が0.04なので、新デザインを採用します!」と報告したら、上司から「で、どれくらい売上が上がるの?」と聞かれて固まった
- サンプルサイズを決めずにテストを開始し、「いつ止めればいいのか」がわからなくなった
-
scipy.stats.ttest_ind()を呼び出したが、equal_var引数をTrueにすべきかFalseにすべきか自信がない - 「対応のあるt検定」と「2標本t検定」、どっちを使うべきか毎回迷う
これらは全て、t検定の「使い方」は知っていても、その「背後にある仕組み」を直感的に理解できていないことが原因です。
本記事では、A/Bテストでよく使われるt検定について、実務で本当に必要な3つのポイントを解説します。
- p値の正体 — 「サンプリングの偏り」という視点
- 3つのt検定の使い分け
- 「テスト開始前」に必要なサンプルサイズ設計
1. p値の正体:「サンプリングの偏り」という視点
よくある誤解
「p値が0.05未満 = この施策には効果がある確率が95%」
これは誤りです。
p値の正しい定義は:
「帰無仮説(=差がない)が正しいと仮定したときに、観測されたデータ(あるいはそれ以上に極端なデータ)が得られる確率」
…と言われても、ピンとこないですよね。私もそうでした。
Pythonでp値を「体感」してみる
「差がないはずの母集団」からランダムサンプリングを繰り返してみると、p値の正体が見えてきます。
```python
import numpy as np
from scipy import stats
母集団:CVR=2%の仮想ユーザー10万人(A群・B群で本来は差がない)
np.random.seed(42)
population_A = np.random.binomial(1, 0.02, 100000)
population_B = np.random.binomial(1, 0.02, 100000) # 本当は同じ確率
1000回サンプリングして「たまたま有意になる」回数を数える
false_positive = 0
for _ in range(1000):
sample_A = np.random.choice(population_A, 1000)
sample_B = np.random.choice(population_B, 1000)
_, p = stats.ttest_ind(sample_A, sample_B)
if p < 0.05:
false_positive += 1
print(f"差がないのに有意になった回数: {false_positive}/1000")
出力例: 差がないのに有意になった回数: 47/1000
```
差が全くない母集団なのに、約5%の確率で「有意差あり」と誤判定されます。これがp値の正体です。
つまりp値とは、「偶然この差が出る確率」を示しているだけで、ビジネス的なインパクトとは無関係なのです。
2. 3つのt検定、どれを使えばいい?
scipy.stats にはt検定の関数が3つあります。これらの使い分けを間違えると、正しい結論が得られません。
| 検定 | 関数 | 使用シーン |
|---|---|---|
| 1標本t検定 | ttest_1samp |
基準値(例:過去の平均単価3,000円)との比較 |
| 2標本t検定 | ttest_ind |
A群とB群(異なるユーザー)の比較 ← A/Bテストの主役 |
| 対応のあるt検定 | ttest_rel |
同一ユーザーの施策前後の比較 |
落とし穴:equal_var 引数
A/Bテストで最も使う ttest_ind ですが、こんなコードを書いていませんか?
```python
よくあるコード
t, p = stats.ttest_ind(group_A, group_B)
```
実は、デフォルトは equal_var=True(スチューデントのt検定)です。これは2群の分散が等しいことを仮定しています。
しかし、実務のA/Bテストでは、サンプルサイズや分散が群ごとに異なることが普通です。そのため、ウェルチのt検定を使うのが安全です。
```python
推奨:ウェルチのt検定
t, p = stats.ttest_ind(group_A, group_B, equal_var=False)
```
3. 「テスト開始前」のサンプルサイズ設計
なぜサンプルサイズ設計が必要なのか
サンプルサイズを決めずにA/Bテストを開始すると、こんな問題が起きます:
- 早めに止めすぎ:本当は効果があるのに「有意差なし」と判定(第二種の過誤)
- だらだら続けすぎ:p値が偶然0.05を下回るのを待つ「p-hacking」に陥る
必要な3要素
サンプルサイズを算出するには、以下の3つを事前に決めます:
- 検出したい効果量(Effect Size):例えば「CVRを2.0%→2.2%に改善したい」
- 有意水準(α):通常0.05
- 検出力(Power, 1-β):通常0.80
statsmodelsで計算する
```python
from statsmodels.stats.power import NormalIndPower
from statsmodels.stats.proportion import proportion_effectsize
CVR 2.0% → 2.2% を検出したい
effect_size = proportion_effectsize(0.022, 0.020)
analysis = NormalIndPower()
n = analysis.solve_power(
effect_size=effect_size,
alpha=0.05,
power=0.80,
alternative='two-sided'
)
print(f"各群に必要なサンプルサイズ: {int(n)}人")
出力: 各群に必要なサンプルサイズ: 78232人
```
0.2ポイントの差を検出するには、各群78,000人以上必要だとわかります。
これを知らずに「1日500人で1週間回しました!」と分析しても、そもそも検出できないテストを回していたことになります。
実務で陥りやすい3つの罠
ここまでの話を踏まえた、実務での注意点をまとめます。
罠1:p-hacking
「もう少しデータを集めればp<0.05になりそう」と延長を繰り返す。サンプルサイズは事前に決めて固定しましょう。
罠2:効果量を見ない
p値だけ見て「有意でした!」と報告。ビジネス判断には効果量(差の大きさ)と信頼区間も必須です。
罠3:検定の選択ミス
施策前後の比較で ttest_ind を使う(正しくは ttest_rel)など。データの取られ方から選びましょう。
さらに深く学びたい方へ
ここまで読んで「もっと体系的に、手を動かしながら学びたい」と思った方へ。
私が作成したUdemy講座 「【Python × t検定マスタードリル】p値とサンプルサイズ設計を、シミュレーションで直感理解する」 では、本記事の内容を含めて、以下を約3時間・全5セクションでドリル形式で学べます。
- セクション2:p値の正体をサンプリング・シミュレーションで体感
- セクション3:3つのt検定を5つのビジネスケースで解き分け
-
セクション4:
statsmodelsで必要nを逆算する実践ドリル - セクション5:ECサイトUI改善のA/Bテスト最終演習(データ読込→検定→ビジネス判断まで)
🎁 Qiita読者限定クーポン(30日間限定・半額)
👉 【Python】t検定マスタードリル|Udemy
数式ではなく Pythonコードとシミュレーションで直感理解 することを徹底した講座です。Google Colabがあればすぐ始められます。
まとめ
- p値は「差がない母集団から偶然この差が出る確率」であり、ビジネスインパクトとは別物
- t検定は「比較対象」と「データの取られ方」で3種類を使い分ける
- A/Bテストは始める前にサンプルサイズを決めることが最重要