Проверка гипотез: что на самом деле означает p-значение
Предыдущая глава — неопределённость и доверительные интервалы — закончилась на том, что у любой оценки по выборке есть разброс, и его надо показывать. Эта глава про следующий шаг, где разброс превращается в решение: у нас есть два числа, они разные — эта разница настоящая или это шум?
Вопрос звучит просто, и именно поэтому вокруг него выросла самая плотная в прикладной статистике заросль недопонимания. «p < 0,05» произносят как заклинание; «результат статистически значим» пишут в письме руководителю; «значимости нет, эффекта нет» закрывают эксперимент. Все три фразы в обычном употреблении означают не то, что думает произносящий.
Держите в голове одну мысль на всю главу. Тест не отвечает на вопрос «есть ли эффект». Он отвечает на вопрос «насколько странно выглядели бы мои данные, если бы эффекта не было — и если бы всё, во что я верю про способ их сбора, было правдой». Второе условие обычно и ломается. Тест не знает, что вы посмотрели двенадцать разрезов, что один пользователь дал вам восемь наблюдений, что треть событий не долетела. Он честно считает свою условную вероятность в мире, которого нет.
Что именно спрашивает тест
Начнём с ситуации, к которой всё сводится. Вы поменяли форму оформления заказа. За две недели:
| Вариант | Пользователей | Оплатили | Конверсия |
|---|---|---|---|
| Контроль (A) | 12 000 | 600 | 5,00 % |
| Новый (B) | 12 000 | 660 | 5,50 % |
Плюс 0,5 процентного пункта, или +10 % относительно базы. Продакт уже пишет в чат «раскатываем». Аналитик обязан спросить одну вещь: а если бы обе группы видели одну и ту же форму, могла ли разница в 0,5 п.п. возникнуть сама собой?
Это не риторика. Разделите пользователей на две случайные половины, ничего им не меняя, и конверсии всё равно разойдутся — просто потому, что в одну половину случайно попало чуть больше склонных купить. Вопрос в том, насколько они обычно расходятся. Если «сами собой» две группы по 12 000 расходятся на 0,05 п.п., то 0,5 п.п. — событие исключительное. Если расходятся на 0,6 п.п., то ваши 0,5 п.п. — рядовой вторник.
Проверка гипотез — это процедура, которая делает это сравнение количественно. Ей нужны три вещи:
- Модель мира без эффекта — что бы происходило, если бы изменение ничего не дало.
- Статистика — одно число, сжимающее данные в меру «странности» (разность долей, t, χ², что угодно).
- Распределение этой статистики в мире без эффекта — как часто она принимает какие значения.
Вероятностный аппарат под всем этим — в теории вероятностей и статистике. Здесь нас интересует прикладная сторона: что мы имеем право сказать после того, как процедура выдала число.
Нулевая гипотеза — это модель, а не убеждение
Нулевая гипотеза $H_0$ — точное утверждение о том, что интересующего эффекта нет: истинные конверсии в двух вариантах равны, истинная разница средних равна нулю, распределения совпадают. Альтернатива $H_1$ — что не равны.
Три вещи, которые про $H_0$ обычно понимают неправильно.
$H_0$ не то, во что вы верите. Никто всерьёз не считает, что новая форма оформления заказа изменила конверсию ровно на ноль знаков после запятой. $H_0$ — это соломенное чучело, поставленное намеренно: удобная точка отсчёта, у которой есть считаемое распределение. Мы не проверяем её истинность, мы проверяем, выдерживает ли она наблюдение.
$H_0$ — это не только «эффекта нет». Это ещё и весь набор допущений о том, как получены данные: наблюдения независимы, распределение такое-то, выборка случайна, правило остановки фиксировано, метрика определена заранее. Когда p-значение выходит маленьким, формально опровергается вся связка целиком, а не только «эффекта нет». Расходящееся с ожиданием p может означать «эффект есть», а может — «ваши наблюдения не независимы» или «вы остановились, когда понравилось». Об этом — раздел про то, что позволяет ваш способ сбора данных.
Асимметрия встроена намеренно. Тест устроен так, чтобы редко объявлять эффект там, где его нет, и молчать в остальных случаях. Он не устроен так, чтобы редко пропускать эффект. Это правовая презумпция невиновности, перенесённая в статистику: «недостаточно улик» и «невиновен» — разные вердикты, и оба выглядят как оправдательный приговор.
p-значение без формул: перестановочный тест
Самый честный способ понять p-значение — не читать определение, а построить мир без эффекта своими руками.
У нас 24 000 пользователей, из них 1 260 купили. Если вариант ни на что не влияет, то метка «A» или «B» — просто наклейка, случайно приклеенная к пользователю; кто купил, купил бы в любом случае. Значит, можно взять реальные данные, перемешать наклейки и посчитать разницу конверсий заново. Повторить двадцать тысяч раз. Получится распределение разниц, которые порождает один только случайный дележ — ровно то самое «как расходятся группы сами собой».
import numpy as np
rng = np.random.default_rng(42)
# По одной строке на пользователя: 1 — оплатил, 0 — нет.
a = np.concatenate([np.ones(600), np.zeros(12_000 - 600)]) # контроль
b = np.concatenate([np.ones(660), np.zeros(12_000 - 660)]) # новый вариант
observed = b.mean() - a.mean() # +0,005 = +0,5 п.п.
pool = np.concatenate([a, b]) # мир, где наклейка ничего не значит
n_a = len(a)
n_iter = 20_000
extreme = 0
for _ in range(n_iter):
perm = rng.permutation(pool) # переклеиваем наклейки
diff = perm[n_a:].mean() - perm[:n_a].mean()
if abs(diff) >= abs(observed) - 1e-12: # двусторонний вариант
extreme += 1
# +1 в числителе и знаменателе: наблюдённая перестановка тоже допустима,
# без неё p может получиться ровно 0, чего быть не должно.
p_value = (extreme + 1) / (n_iter + 1)
print(round(p_value, 4)) # ≈ 0,088
Сложность — $O(n_{\text{iter}} \cdot N)$ по времени и $O(N)$ по памяти, где $N$ — общее число наблюдений; для 24 000 строк и 20 000 повторов это секунды. Для бинарной метрики то же самое считается точно через гипергеометрическое распределение: p = 0,0877.
Вот теперь определение читается само:
p-значение — это доля миров без эффекта, в которых случайность дала бы отклонение не меньше наблюдённого.
Формально это записывается так:
$$p = \Pr\big(|T| \geq |t_{\text{obs}}| \mid H_0\big)$$
где $T$ — статистика, $t_{\text{obs}}$ — её значение на ваших данных, а условие $H_0$ включает все допущения модели.
Обратите внимание на два слова в определении. «Не меньше» — в p входит не только наблюдённое значение, но и все более крайние, которых вы не видели; это делает p зависимым от того, что вы считаете «более крайним», то есть от вашей альтернативы. «Миров без эффекта» — вероятность условная, посчитанная внутри гипотезы. Условная вероятность нельзя перевернуть: $\Pr(A \mid B) \neq \Pr(B \mid A)$. Отсюда все дальнейшие беды.
Чего p-значение НЕ означает
Это самый важный раздел главы. Каждый пункт здесь — реальная фраза, которую произносят на разборе результатов.
Разберём три самых дорогих заблуждения.
«p = 0,03, значит вероятность, что эффекта нет, — 3 %». Нет. p посчитано при условии, что эффекта нет. Чтобы получить вероятность гипотезы при данных, нужна ещё априорная вероятность — и она резко меняет ответ. Считаем на тысяче проверенных гипотез, если в среднем верна каждая десятая, порог 0,05, мощность 0,8:
| Эффект есть (100) | Эффекта нет (900) | Всего | |
|---|---|---|---|
| Тест сказал «значимо» | 80 | 45 | 125 |
| Тест сказал «незначимо» | 20 | 855 | 875 |
Из 125 «открытий» ложны 45 — 36 % значимых результатов неверны, при том что порог был 5 %. Уроните мощность до реалистичных 0,4 (типичный недобор выборки), и из 85 «открытий» ложными окажутся 45 — больше половины. Это и есть арифметика статьи Джона Иоаннидиса Why Most Published Research Findings Are False (PLoS Medicine, 2005); в продуктовой аналитике доля правдивых гипотез обычно ниже, чем в науке, а мощность — ниже, чем думают.
«p = 0,001 — эффект сильнее, чем при p = 0,04». Нет. p зависит от величины эффекта и от размера выборки. Та же разница 0,5 п.п. на 12 000 пользователях в группе даёт p ≈ 0,082, а на 120 000 — p ≈ 4·10⁻⁸. Эффект не изменился ни на йоту; изменилась ваша способность его разглядеть. Маленькое p говорит «шум это объяснить не может», а не «эффект большой».
«p = 0,2, эффекта нет». Нет. Это «данных не хватило». Наш пример: 95 %-й интервал для разности — от −0,06 до +1,06 п.п. Он совместим и с крошечным ухудшением, и с ростом на пятую часть. Утверждать «эффекта нет» на таких данных — то же самое, что искать ключи под фонарём и заключить, что их вообще не существует.
Каноническое перечисление ошибок толкования, все двадцать пять штук, — Sander Greenland et al., Statistical tests, P values, confidence intervals, and power: a guide to misinterpretations (European Journal of Epidemiology, 2016). Официальная позиция Американской статистической ассоциации — The ASA Statement on p-Values (2016) и сборник Moving to a World Beyond «p < 0.05» (2019).
Две ошибки, порог и мощность
Решение бинарно, истина бинарна, значит ошибок ровно две.
| На самом деле эффекта нет | На самом деле эффект есть | |
|---|---|---|
| Объявили эффект | Ошибка I рода, вероятность $\alpha$ | Верное обнаружение, вероятность $1-\beta$ (мощность) |
| Не объявили | Верное молчание, $1-\alpha$ | Ошибка II рода, вероятность $\beta$ |
$\alpha$ — это не свойство данных, а ваше решение, принятое до эксперимента: какую долю ложных тревог вы согласны терпеть в длинной серии. Число 0,05 не имеет обоснования, кроме привычки: Фишер в 1920-х назвал его «удобным», и оно осталось. Для решения «покрасить кнопку» 0,1 может быть разумнее, для решения «переписать биллинг» — 0,01.
Мощность $1-\beta$ — вероятность заметить эффект заданного размера, если он есть. У мощности нет смысла в отрыве от размера эффекта: «мощность теста 80 %» — незаконченная фраза, полная звучит «80 % при истинном эффекте +10 % относительно базы».
Левая картинка — самое полезное, что стоит запомнить про p-значения. Когда эффекта нет, p распределено равномерно на отрезке от 0 до 1. Значение 0,03 в таком мире не редкость, а ровно один случай из тридцати трёх. Отсюда напрямую следует всё, что будет сказано ниже про множественные сравнения: чем больше тестов вы проводите, тем гарантированнее вытянете маленькое p из чистого шума.
Правая картинка — почему недомощный тест бесполезен в обе стороны. При мощности 0,8 вы в 20 % случаев не увидите настоящий эффект; при мощности 0,4 — в 60 %.
Считаем руками: z-тест для двух долей
Вернёмся к нашим числам и доведём их до конца. Пусть $n_A = n_B = 12000$, $x_A = 600$, $x_B = 660$.
Оценки долей и объединённая оценка под $H_0$ (если конверсии равны, лучшая оценка общей конверсии — по всем данным сразу):
$$\hat{p}_A = 0{,}0500 \qquad \hat{p}_B = 0{,}0550 \qquad \hat{p} = \frac{600 + 660}{12000 + 12000} = 0{,}0525$$
Стандартная ошибка разности под нулевой гипотезой:
$$SE_0 = \sqrt{\hat{p}(1-\hat{p})\left(\frac{1}{n_A} + \frac{1}{n_B}\right)} = \sqrt{0{,}0525 \cdot 0{,}9475 \cdot \frac{2}{12000}} = 0{,}002879$$
Статистика и двустороннее p-значение:
$$z = \frac{\hat{p}_B - \hat{p}_A}{SE_0} = \frac{0{,}005}{0{,}002879} = 1{,}737 \qquad p = 2\big(1 - \Phi(1{,}737)\big) = 0{,}082$$
Нормальное приближение дало 0,082, точный перестановочный расчёт — 0,088. Расхождение в 0,006 — цена приближения на дискретных данных; на решение оно не влияет, но показывает, что третья цифра после запятой в p-значении обычно фиктивна.
Доверительный интервал для разности считается без объединения долей — здесь мы уже не предполагаем, что они равны:
$$SE_{\text{CI}} = \sqrt{\frac{\hat{p}_A(1-\hat{p}_A)}{n_A} + \frac{\hat{p}_B(1-\hat{p}_B)}{n_B}} = 0{,}002879$$
$$0{,}005 \pm 1{,}96 \cdot 0{,}002879 = (-0{,}00064; +0{,}01064)$$
То есть от −0,06 до +1,06 процентного пункта, или от −1,3 % до +21,3 % относительно базы. Интервал накрывает ноль — это тот же вывод, что и «p > 0,05», но он несёт больше: видно, что данные не исключают ни лёгкого ухудшения, ни очень хорошего роста. Ноль в интервале — не «эффекта нет», а «мы не различили».
Как перевести z в p без scipy:
from math import erf, sqrt
def two_sided_p(z: float) -> float:
"""Двустороннее p-значение по z из стандартного нормального распределения."""
cdf = 0.5 * (1.0 + erf(abs(z) / sqrt(2.0)))
return 2.0 * (1.0 - cdf)
print(round(two_sided_p(1.7365), 4)) # 0.0825
Хватало ли выборки вообще
Теперь главный вопрос, который надо было задать до эксперимента. Сколько нужно пользователей, чтобы поймать рост конверсии с 5,0 % до 5,5 % при $\alpha = 0{,}05$ и мощности 0,8?
$$n = \frac{\big(z_{1-\alpha/2} + z_{1-\beta}\big)^2 \big(p_A(1-p_A) + p_B(1-p_B)\big)}{\Delta^2}$$
$$n = \frac{(1{,}96 + 0{,}84)^2 \cdot (0{,}0475 + 0{,}051975)}{0{,}005^2} = \frac{7{,}849 \cdot 0{,}099475}{0{,}000025} \approx 31\ 200$$
Больше 31 тысячи на группу. У нас было 12 000. Обратный расчёт — какая мощность была фактически:
$$1-\beta = \Phi\left(\frac{|\Delta|}{SE_{\text{CI}}} - z_{1-\alpha/2}\right) = \Phi(1{,}737 - 1{,}960) = \Phi(-0{,}223) = 0{,}41$$
41 %. Даже если бы эффект был ровно таким, как мы надеялись, тест находил бы его реже, чем подбрасывание монетки. Результат «p = 0,082» — не сюрприз и не «почти значимо», а ровно то, как выглядит недомощный эксперимент. Запомните грубое правило: удвоение точности требует учетверения выборки, потому что $SE \propto 1/\sqrt{n}$. Полезный ориентир для бинарных метрик:
$$n \approx \frac{16 \cdot p(1-p)}{\Delta^2}$$
при стандартных 0,05 и 0,8. Для нашего случая — 30 400, сходится.
Дизайн эксперимента, расчёт длительности и что делать, когда выборки принципиально не хватает, — в главе про A/B-тесты.
Значимость и важность — разные оси
Самая частая управленческая ошибка: слово «значимый» в статистике не имеет отношения к слову «значимый» в русском языке. Статистическая значимость говорит «это не шум», практическая значимость — «это стоит денег или усилий». Они независимы.
Правый нижний угол — то место, где рождается больше всего плохих решений. Эффект выглядит крупным, значимости нет, и дальше происходит одно из двух: либо результат объявляют «трендом» и катят, либо объявляют «эффекта нет» и хоронят живую идею. Правильное действие — третье: признать, что эксперимент не был способен ответить, и досчитать выборку или изменить дизайн.
Левый верхний угол не менее вреден, просто тише. На большой аудитории значимым становится всё: при 5 миллионах пользователей на группу вы поймаете разницу в сотые доли процентного пункта. Дальше команда полгода катит релизы, каждый из которых «статистически значим» и ни один не двигает бизнес. Поэтому порог практической значимости надо фиксировать до теста — «внедряем, если нижняя граница интервала выше +2 % относительно базы», а не «внедряем, если p < 0,05». Как связать порог с деньгами и приоритетами — метрики и приоритизация в треке продакт-менеджмента.
Какой тест выбирать
Тестов много, и выбор определяется не вкусом, а тремя вопросами: какого типа метрика, сколько групп, что известно про распределение.
или точный тест Фишера при малых n"] B1 -->|нет| B3["Хи-квадрат на таблице сопряжённости
плюс поправка на множественность"] C --> C1{"Интересует среднее
или типичное значение?"} C1 -->|среднее| C2{"Тяжёлый хвост?"} C2 -->|нет| C3["t-тест Уэлча
дисперсии не предполагаем равными"] C2 -->|да| C4["Бутстрап или перестановочный тест
либо усечение выбросов, объявленное заранее"] C1 -->|типичное| C5["Тест Манна — Уитни
отвечает на другой вопрос: кто чаще больше"] D --> B3 C3 --> E["Проверить независимость наблюдений"] C4 --> E C5 --> E B2 --> E B3 --> E E --> F["Считать интервал для эффекта,
а не только p"]
t-тест Уэлча — версия t-теста, не предполагающая равных дисперсий. Используйте по умолчанию вместо классического Стьюдента: он почти не теряет мощности, когда дисперсии равны, и спасает, когда нет.
$$t = \frac{\bar{x}_1 - \bar{x}_2}{\sqrt{\dfrac{s_1^2}{n_1} + \dfrac{s_2^2}{n_2}}} \qquad \nu \approx \frac{\left(\dfrac{s_1^2}{n_1} + \dfrac{s_2^2}{n_2}\right)^2}{\dfrac{(s_1^2/n_1)^2}{n_1-1} + \dfrac{(s_2^2/n_2)^2}{n_2-1}}$$
Важная поправка к распространённому мифу: t-тест не требует, чтобы данные были нормальными. Он требует, чтобы примерно нормальным было распределение среднего, а центральная предельная теорема обеспечивает это на больших выборках почти всегда. Проблема не в нормальности, а в тяжёлых хвостах: если 0,1 % пользователей приносят 40 % выручки, среднее сходится к нормальному так медленно, что на реальных n оно ещё не сошлось. Почему выручка почти всегда так устроена — глава про распределения.
Тест Манна — Уитни часто советуют как «непараметрическую замену t-теста». Это неверно по смыслу: он проверяет не равенство средних, а $\Pr(X > Y) = 0{,}5$. Если ваш бизнес живёт на сумме выручки, ответ на вопрос «кто чаще больше» бесполезен: вариант может выигрывать у 60 % пользователей по копейке и терять миллион на крупных клиентах.
Хи-квадрат для таблицы 2×2 математически эквивалентен z-тесту для двух долей: $\chi^2 = z^2$. Проверим на наших числах: ожидаемые при $H_0$ значения 630 и 11 370 в каждой строке, откуда
$$\chi^2 = 2\left(\frac{30^2}{630} + \frac{30^2}{11370}\right) = 2(1{,}4286 + 0{,}0792) = 3{,}0155 = 1{,}737^2$$
Бутстрап и перестановки не требуют формулы для распределения статистики и работают для чего угодно — медианы, 95-го перцентиля, отношения сумм. Цена — вычисления и необходимость аккуратно ресемплировать единицу рандомизации, а не строки.
В ClickHouse часть тестов есть прямо в SQL: агрегатные функции welchTTest, studentTTest, mannWhitneyUTest, kolmogorovSmirnovTest — документация. В PostgreSQL готовых тестов нет, но всё, что нужно для z-теста, считается запросом.
Считаем в SQL: сначала гранулярность, потом статистика
Главная ошибка в аналитическом SQL для тестов — считать не по той единице. Рандомизация идёт по пользователю, значит и наблюдение — пользователь. Если считать по событиям или заказам, стандартная ошибка окажется занижена, а p — фальшиво маленьким. Про гранулярность подробно — SQL для анализа.
Сначала проверка целостности разбиения. Она должна возвращать ноль строк — иначе считать нечего:
-- Пользователь, попавший в оба варианта, ломает рандомизацию.
SELECT user_id, COUNT(DISTINCT variant) AS variants
FROM experiment_assignments
WHERE experiment_id = 'checkout_v2'
GROUP BY user_id
HAVING COUNT(DISTINCT variant) > 1;
Дальше — свёртка до одной строки на пользователя и агрегаты:
WITH exposed AS (
-- Одна строка на пользователя: момент первого попадания в эксперимент.
SELECT
user_id,
MIN(variant) AS variant, -- после проверки выше он единственный
MIN(assigned_at) AS assigned_at
FROM experiment_assignments
WHERE experiment_id = 'checkout_v2'
GROUP BY user_id
),
per_user AS (
SELECT
e.user_id,
e.variant,
-- Конверсия — свойство пользователя, а не заказа: MAX, а не COUNT.
MAX(CASE WHEN o.order_id IS NOT NULL THEN 1 ELSE 0 END) AS converted,
COALESCE(SUM(o.total_amount), 0) AS revenue
FROM exposed AS e
LEFT JOIN orders AS o
ON o.user_id = e.user_id
AND o.status = 'paid'
AND o.created_at >= e.assigned_at
AND o.created_at < e.assigned_at + INTERVAL '7 days'
GROUP BY e.user_id, e.variant
),
agg AS (
SELECT
COUNT(*) FILTER (WHERE variant = 'control') AS n_a,
COUNT(*) FILTER (WHERE variant = 'treatment') AS n_b,
SUM(converted) FILTER (WHERE variant = 'control') AS x_a,
SUM(converted) FILTER (WHERE variant = 'treatment') AS x_b
FROM per_user
),
calc AS (
SELECT
n_a, n_b, x_a, x_b,
x_a::numeric / n_a AS p_a,
x_b::numeric / n_b AS p_b,
(x_a + x_b)::numeric / (n_a + n_b) AS p_pool
FROM agg
)
SELECT
round(p_a, 5) AS conv_control,
round(p_b, 5) AS conv_treatment,
round(p_b - p_a, 5) AS diff_abs,
round((p_b - p_a) / p_a, 4) AS diff_rel,
-- z: стандартная ошибка под нулевой гипотезой, доли объединены
round(
(p_b - p_a) / sqrt(p_pool * (1 - p_pool) * (1.0 / n_a + 1.0 / n_b)),
4
) AS z,
-- Интервал: доли НЕ объединяем, равенство здесь не предполагается
round((p_b - p_a) - 1.959964 * sqrt(p_a * (1 - p_a) / n_a
+ p_b * (1 - p_b) / n_b), 5) AS ci_low,
round((p_b - p_a) + 1.959964 * sqrt(p_a * (1 - p_a) / n_a
+ p_b * (1 - p_b) / n_b), 5) AS ci_high
FROM calc;
Функции нормального распределения в PostgreSQL нет, поэтому перевод z в p делается на стороне приложения — two_sided_p из примера выше или scipy.stats.norm.sf. Это не недостаток: p-значение всё равно не то число, которое стоит класть на дашборд.
Множественные сравнения: как гарантированно найти то, чего нет
Теперь центральный механизм, из-за которого аналитика чаще всего выдаёт уверенный неверный вывод.
Порог 0,05 означает: в мире без эффекта вы ошибаетесь в одном случае из двадцати. Проведите двадцать тестов — и вероятность хотя бы одной ложной находки уже не 5 %:
$$\text{FWER} = 1 - (1-\alpha)^m$$
| Число проверок $m$ | Вероятность хотя бы одной ложной находки |
|---|---|
| 1 | 5 % |
| 5 | 23 % |
| 10 | 40 % |
| 20 | 64 % |
| 40 | 87 % |
Двадцать проверок — это не «мы прогнали двадцать экспериментов». Это один эксперимент, посмотренный в двадцати разрезах. Один запрос:
-- Тот самый запрос, который «просто посмотреть по сегментам»
-- (per_user — CTE из предыдущего примера).
-- Каждая строка результата — отдельная проверка гипотезы.
SELECT
u.country,
COUNT(*) FILTER (WHERE p.variant = 'control') AS n_a,
SUM(p.converted) FILTER (WHERE p.variant = 'control') AS x_a,
COUNT(*) FILTER (WHERE p.variant = 'treatment') AS n_b,
SUM(p.converted) FILTER (WHERE p.variant = 'treatment') AS x_b
FROM per_user AS p
JOIN users AS u USING (user_id)
GROUP BY u.country
HAVING COUNT(*) >= 500 -- отсекаем совсем мелкие сегменты
ORDER BY u.country;
Двенадцать стран — двенадцать тестов, вероятность ложной находки 46 %. Добавьте разбивку по платформам, по новым и старым пользователям, по каналам привлечения — и вы уже не «ищете инсайт», а гарантированно его производите. Классическая иллюстрация — xkcd 882 про желейные конфеты: двадцать цветов, один даёт p < 0,05, газета выносит его в заголовок.
Хуже того, вы обычно не считаете свои проверки. Эндрю Гелман и Эрик Локен назвали это садом расходящихся троп: даже честный аналитик, сделавший ровно один тест, принимал по дороге десяток развилок — как определить конверсию, какое окно атрибуции взять, выкидывать ли ботов, считать ли последний неполный день. Каждая развилка была бы решена иначе, будь данные другими. Множественность возникает, даже если формально проведён один тест. В жизни это выглядит так:
Честный ответ: не различили. P->>A: А может, на мобильных сработало? A->>W: Разрез по платформам W-->>A: iOS p = 0,31, Android p = 0,04 A->>W: А внутри Android — по странам? W-->>A: Одна страна: p = 0,008 A->>D: Публикует график по этой стране D-->>P: «Значимый рост в сегменте» P->>P: Решение принято Note over A,D: Проведено 15+ проверок.
Вероятность такой находки в чистом шуме — выше 50 %.
Что с этим делать
Заранее объявить одну главную метрику и один главный разрез. Всё остальное — разведка, порождающая гипотезы для следующего эксперимента, а не выводы. Это не бюрократия, а единственный способ сохранить смысл у числа 0,05. Практика фиксации плана анализа до данных описана у Simmons, Nelson, Simonsohn, False-Positive Psychology (Psychological Science, 2011) — там же показано, насколько легко «доказать» заведомую чушь четырьмя безобидными степенями свободы.
Поправка Бонферрони — самая грубая и самая надёжная: делите порог на число проверок, $\alpha’ = \alpha/m$. Для 20 разрезов при $\alpha = 0{,}05$ порог становится 0,0025. Метод контролирует вероятность хотя бы одной ложной находки, но при большом $m$ убивает мощность. Чуть менее консервативный вариант — поправка Шидака, $\alpha’ = 1 - (1-\alpha)^{1/m}$, дающая для 20 проверок 0,00256; разница с Бонферрони пренебрежимо мала.
Процедура Бенджамини — Хохберга контролирует другое: не вероятность хоть одной ошибки, а ожидаемую долю ложных среди объявленных находок (FDR). Для разведки это правильнее — вы согласны, что каждая десятая находка ложна, лишь бы не потерять настоящие. Алгоритм: отсортировать p по возрастанию, найти наибольшее $k$, при котором $p_{(k)} \leq \frac{k}{m} q$, отвергнуть первые $k$ гипотез.
Двенадцать сегментов, $q = 0{,}10$:
| Ранг $k$ | p-значение | Порог $\frac{k}{m}q$ | Проходит |
|---|---|---|---|
| 1 | 0,001 | 0,0083 | да |
| 2 | 0,008 | 0,0167 | да |
| 3 | 0,021 | 0,0250 | да |
| 4 | 0,033 | 0,0333 | да |
| 5 | 0,041 | 0,0417 | да |
| 6 | 0,060 | 0,0500 | нет |
| 7 | 0,120 | 0,0583 | нет |
| … | … | … | нет |
Наибольший проходящий ранг — 5, значит отвергаются первые пять гипотез. Бонферрони при том же $q$ дал бы порог 0,0083 и только одну находку.
def benjamini_hochberg(pvalues: list[float], q: float = 0.10) -> list[bool]:
"""Процедура «шаг вверх»: контролирует ожидаемую долю ложных открытий."""
m = len(pvalues)
order = sorted(range(m), key=lambda i: pvalues[i]) # индексы по возрастанию p
k_max = 0
for rank, idx in enumerate(order, start=1):
if pvalues[idx] <= rank * q / m:
k_max = rank # запоминаем НАИБОЛЬШИЙ ранг
rejected = [False] * m
for rank, idx in enumerate(order, start=1):
if rank <= k_max: # отвергаем всё до него включительно
rejected[idx] = True
return rejected
ps = [0.001, 0.008, 0.021, 0.033, 0.041, 0.060, 0.12, 0.19, 0.31, 0.44, 0.61, 0.78]
print(sum(benjamini_hochberg(ps, q=0.10))) # 5
Сложность — $O(m \log m)$ по времени, $O(m)$ по памяти. Оригинальная работа: Benjamini, Hochberg, Controlling the False Discovery Rate, JRSS B, 1995.
Подглядывание — множественные сравнения во времени. Если смотреть на результат каждый день и останавливаться, когда p впервые упало ниже 0,05, вероятность ложной находки при десяти проверках подскакивает примерно до 20–25 %, а при непрерывном мониторинге стремится к единице. Это отдельная большая тема с готовыми решениями — последовательные тесты, всегда-валидные p-значения, групповые последовательные границы; вся она в следующей главе.
Что ваш способ сбора данных разрешает считать
Возвращаемся к сквозной мысли трека. Формула теста работает всегда. Осмысленный ответ она даёт только тогда, когда выполняются её допущения — а допущения эти про сбор данных, не про математику.
Независимость наблюдений
Самое частое и самое дорогое нарушение. Тест считает, что каждая строка — независимое наблюдение. В реальности один пользователь порождает восемь сессий, десять просмотров, три заказа. Строки внутри пользователя похожи друг на друга, и эффективный объём выборки меньше числа строк.
Мера искажения — эффект дизайна, во столько раз растёт дисперсия оценки:
$$\text{DEFF} = 1 + (\bar{m} - 1)\rho$$
где $\bar{m}$ — среднее число наблюдений на пользователя, $\rho$ — внутриклассовая корреляция. При $\bar{m} = 8$ и умеренной $\rho = 0{,}3$ получаем $\text{DEFF} = 3{,}1$: истинная стандартная ошибка в $\sqrt{3{,}1} = 1{,}76$ раза больше наивной. Наш $z = 1{,}737$, посчитанный по событиям вместо пользователей, на самом деле равен $1{,}737/1{,}76 = 0{,}99$, а p — не 0,082, а 0,32.
Практический вывод короткий: единица анализа = единица рандомизации. Свернули до пользователя — считаете честно. Не свернули — получили p, заниженное в разы, и сами не знаете, во сколько.
Метрики-отношения
Отдельный случай той же проблемы: метрика вида «заказов на сессию» или «CTR = клики / показы», где числитель и знаменатель — суммы по пользователю. Обычная формула для доли здесь неприменима: знаменатель тоже случаен и коррелирует с числителем. Дисперсия отношения $R = \bar{X}/\bar{Y}$ считается дельта-методом:
$$\operatorname{Var}(\hat{R}) \approx \frac{1}{n \mu_Y^2}\left(\sigma_X^2 - 2R \sigma_{XY} + R^2 \sigma_Y^2\right)$$
Как это применяют в промышленном масштабе — Alex Deng et al., Applying the Delta Method in Metric Analytics (KDD 2018). Если под рукой нет реализации, бутстрап по пользователям даёт тот же результат ценой вычислений и без вывода формул.
Выборка не случайна
Тест предполагает, что группы различаются только вмешательством. В A/B-тесте это обеспечено рандомизацией — она и есть источник права говорить о причине. В наблюдательных данных ничего подобного нет: «пользователи функции X конвертируются лучше» — сравнение тех, кто сам выбрал X, с теми, кто не выбрал, а выбор коррелирует со всем на свете. Никакое p-значение это не чинит; маленькое p в наблюдательном сравнении означает лишь «разница между самоотобранными группами не случайна» — что и так очевидно.
Сюда же: выживший источник данных (события от упавших приложений не приходят), пропуски и поздние события (последний день всегда «проседает»), блокировщики трекеров. Каждое из этих искажений входит в $H_0$ наравне с «эффекта нет», и опровергнуться может любое.
Что делать, когда рандомизация невозможна, — контроль на наблюдаемые различия, разность разностей, естественные эксперименты, инструментальные переменные и границы применимости всего этого — глава про корреляцию и причинность.
Как отчитываться о результате
Правило: p-значение не бывает выводом. Вывод состоит из четырёх частей — эффект, интервал, решение, ограничение.
Плохо:
Новая форма показала статистически значимый рост конверсии (p = 0,04). Раскатываем.
Хорошо:
Конверсия в оплату за 7 дней: 5,00 % в контроле, 5,50 % в тесте. Разница +0,50 п.п., 95 %-й интервал от −0,06 до +1,06 п.п. (от −1 % до +21 % относительно базы), p = 0,08. Заранее объявленный порог внедрения — нижняя граница выше +2 % относительно базы — не достигнут. Мощность эксперимента для ожидаемого эффекта была 41 %: чтобы получить ответ, нужно 31 000 пользователей на группу вместо 12 000. Решение: продлить набор на три недели либо закрыть гипотезу, если ожидание дороже возможных 21 % роста. Ограничение: пользователи без cookie-согласия в анализ не попали, это около 9 % трафика.
Второй вариант длиннее ровно настолько, насколько честнее. Он даёт читателю принять решение, а не подчиниться числу. И он выполняет требование, с которого трек начинался: какое решение изменится от ответа.
Полезная привычка — предварительный план анализа: до старта одним абзацем зафиксировать метрику, единицу рандомизации, размер выборки, порог, длительность и правило остановки. Абзац в тикете, ничего тяжёлого. Его ценность в том, что он делает сад расходящихся троп видимым: любое отступление придётся объяснить.
Байесовская альтернатива — коротко
Если вас раздражает, что p-значение отвечает не на тот вопрос, есть подход, отвечающий на тот. Байесовский анализ даёт распределение самого эффекта при данных и позволяет говорить «вероятность, что B лучше A, — 87 %», «вероятность, что выигрыш больше 2 %, — 61 %». Для конверсий это буквально несколько строк: бета-распределение сопряжено с биномиальным, апостериорное — $\text{Beta}(\alpha_0 + x, \beta_0 + n - x)$, дальше сравнение двух выборок из него.
Цена: нужно явно задать априорное распределение, и результат от него зависит; при слабом априорном на больших выборках ответы численно близки к частотным. Байес не отменяет ни необходимости считать выборку, ни проблемы множественных сравнений, ни требования, чтобы данные были собраны корректно. Он меняет форму ответа, а не его надёжность. Введение по теме — Cameron Davidson-Pilon, Bayesian Methods for Hackers (свободно доступна).
Типичные ошибки
| Ошибка | Что происходит на самом деле | Как заметить |
|---|---|---|
| «p = 0,03 → эффекта нет с вероятностью 3 %» | Условная вероятность перевёрнута | Спросите: а какова была априорная доля верных гипотез? |
| «p = 0,06 → эффекта нет» | Данных не хватило | Посмотрите на интервал: что он не исключает? |
| «p = 0,001 → эффект большой» | Скорее большая выборка | Смотрите на величину эффекта, не на p |
| Тест на событиях при рандомизации по пользователям | SE занижена в $\sqrt{\text{DEFF}}$ раз | Сравните n строк и n пользователей |
| Разрезы «на всякий случай» | FWER растёт до 60–90 % | Посчитайте число проверок честно, включая развилки |
| Остановка при первом p < 0,05 | Порог перестаёт что-либо значить | Правило остановки объявлено до старта? |
| Наблюдательное сравнение с p-значением | Отбор в группы неслучаен | Кто и почему попал в каждую группу? |
| Тест на среднем при тяжёлом хвосте | ЦПТ ещё не сработала | Гистограмма и вклад топ-1 % в сумму |
| Мощность и порог не зафиксированы до старта | Это уже не проверка гипотезы | Расчёт выборки есть в тикете? |
| Метрика-отношение по обычной формуле | Дисперсия недооценена | Дельта-метод или бутстрап по пользователям |
Мини-итог
- p-значение — это вероятность данных при гипотезе, а не вероятность гипотезы при данных. Перевернуть нельзя; чтобы получить второе, нужна априорная доля верных гипотез, и она обычно низкая.
- В $H_0$ входит не только «эффекта нет», но и все допущения о сборе данных. Маленькое p опровергает связку целиком, и виновником чаще оказывается сбор, а не эффект.
- Значимость и важность — разные оси. Порог практической значимости фиксируется до эксперимента и выражается в деньгах или пользователях, а не в p.
- Отсутствие значимости — не отсутствие эффекта. Это либо отсутствие эффекта, либо отсутствие выборки, и различить их можно только по интервалу и мощности.
- Мощность считается до, а не после. Недомощный эксперимент бесполезен в обе стороны: он и пропускает настоящее, и делает найденное ненадёжным.
- Каждый дополнительный разрез — ещё одна проверка. Двадцать разрезов дают ложную находку в 64 % случаев; лечится предварительным планом, Бонферрони для подтверждения и Бенджамини — Хохбергом для разведки.
- Единица анализа равна единице рандомизации. Иначе стандартная ошибка занижена в разы, и вы не знаете, во сколько.
- Отчёт — это эффект, интервал, решение и ограничение. p-значение в нём — деталь, а не вывод.
Дополнительное чтение: Ronald Kohavi, Diane Tang, Ya Xu, Trustworthy Online Controlled Experiments (Cambridge University Press, 2020) — практическая библия онлайн-экспериментов от людей, проводивших их тысячами; Alex Reinhart, Statistics Done Wrong — короткий разбор того, как ломаются тесты, с бесплатной онлайн-версией; Regina Nuzzo, Scientific method: statistical errors (Nature, 2014) — лучшее популярное объяснение того, почему p-значение обманывает.
Ближайшая мысль, к которой мы идём: всё сказанное выше про множественность и мощность было теорией. На практике эти ошибки совершаются не по невежеству, а потому что эксперимент идёт две недели, а ответ нужен в четверг.
Что дальше
A/B-тесты: подглядывание, множественные сравнения, мощность — как устроен эксперимент от рандомизации до решения: почему нельзя смотреть на результат каждый день и что делать, если очень хочется; как считать длительность; A/A-тесты как проверка вашей же инфраструктуры; и почему большая часть экспериментов в индустрии заканчивается ничем — и это нормальный, а не провальный результат.