Почему ваш A/B-тест врёт: 5 ловушек p-value
Содержание
p-value — это не вероятность того, что ваша гипотеза верна. И не вероятность, что наблюдаемое различие появилось случайно. p-value отвечает на очень узкий вопрос: «если бы нулевая гипотеза была правдой, насколько экстремальные данные мы бы увидели?»
Формально:
Где — статистика критерия, — её наблюдаемое значение. Всё. Из этого определения сразу вытекают ловушки.
Ловушка 1: подглядывание (peeking)
Запустили тест, каждое утро открываете дашборд и смотрите, не пробило ли . Как только пробило — останавливаете. Кажется логичным? А на деле вы только что сломали контроль ошибок первого рода.
Если проверять тест раз и останавливаться при первом , реальная вероятность ошибки растёт примерно как:
При 10 проверках и реальная вероятность ошибочно отвергнуть уже около 40%.
Что делать: фиксировать размер выборки до старта теста либо использовать sequential testing (mSPRT, Always Valid p-values).
Ловушка 2: множественные сравнения
Тестируете 5 метрик разом и где-то увидели . Вероятность, что хоть одна из 5 независимых метрик случайно пробьёт порог при истинной :
23%, не 5%. Бонферрони (делите на число тестов) — простое, но консервативное решение. FDR (Benjamini–Hochberg) мягче и чаще уместнее в продуктовой аналитике.
Ловушка 3: недостаточная мощность
Мощность — вероятность обнаружить эффект, если он действительно есть. При маленькой выборке даже реальный эффект в 2% конверсии может не пробиться через шум — и вы сделаете ложный вывод «эффекта нет».
Размер выборки для сравнения двух пропорций (приближённо):
Python:
from math import ceil
from scipy.stats import norm
def sample_size(p1: float, mde: float, alpha: float = 0.05, power: float = 0.8) -> int:
p2 = p1 + mde
p_bar = (p1 + p2) / 2
z_alpha = norm.ppf(1 - alpha / 2)
z_beta = norm.ppf(power)
n = (2 * (z_alpha + z_beta) ** 2 * p_bar * (1 - p_bar)) / (mde ** 2)
return ceil(n)
print(sample_size(p1=0.10, mde=0.01)) # ≈ 31 200 на группу
Увидели нужное число пользователей — значит, эксперимент спланирован корректно. Нет — отложите запуск или поднимите базу.
Ловушка 4: эффект выжившего в метрике
Метрика «средний чек среди заплативших» выглядит невинно, но когортирует по результату теста. Вы сравниваете не случайные группы, а самоотобранных людей, и тест ломается — рандомизация разрушена.
Считайте метрики по намерению лечить (ITT): знаменатель — все пользователи группы, включая не-конвертировавшихся.
Ловушка 5: стат. значимость ≠ практическая значимость
При любое микроскопическое различие бьёт порог . Перед тестом всегда определяйте MDE — minimum detectable effect, который имеет смысл для бизнеса. Если реальный эффект меньше MDE — даже «значимый» результат не заслуживает релиза.
Что в итоге
- Планируйте выборку и MDE до старта, не после.
- Не подглядывайте или используйте sequential-методы.
- Корректируйте при множественных сравнениях.
- Считайте метрики по ITT, не по подвыборке.
- Проверяйте не только значимость, но и размер эффекта с доверительным интервалом.
p-value — полезный инструмент, но не оракул. Если вы помните его определение буквально, большая часть «парадоксов» A/B-тестов перестаёт быть парадоксами.