p-value отвечает ровно на один вопрос: можно ли отличить наблюдаемую разницу от случайного шума. И всё. Он ничего не говорит о том, насколько разница большая и стоит ли она хоть чего-то для продукта. За «насколько большая» отвечает размер эффекта, и для сравнения средних самый ходовой способ его измерить — Cohen's d: разница между средними, поделённая на стандартное отклонение. Если совсем коротко: p-value решает «есть или нет», размер эффекта — «много или мало». Принимать решения только по первому — надёжный способ выкатить в прод изменение, которое статзначимо и при этом бесполезно.
Что показывает p-value, а что — размер эффекта?
Давай разведём эти две вещи по углам, потому что их путают даже сеньоры, когда торопятся.
p-value — это вероятность увидеть такую же (или ещё большую) разницу между группами, если на самом деле никакой разницы нет. Маленький p-value значит «такое совпадение маловероятно, скорее эффект реальный». Он НЕ говорит: насколько эффект большой, насколько он важен, и уж тем более не даёт «вероятность того, что гипотеза верна» (это самая частая ошибка на собеседованиях).
Размер эффекта — это про величину. На сколько рублей вырос средний чек, на сколько минут дольше сессия, на сколько стандартных отклонений разъехались две группы. Он отвечает на вопрос, который на самом деле волнует бизнес: «окей, разница есть — а она вообще заметная?»
Ключевая засада в том, что p-value смешивает в себе две вещи сразу: реальную величину эффекта и размер выборки. Из-за этого по одному p-value невозможно понять, что перед тобой — крупный эффект на скромной выборке или микроскопический эффект на миллионах наблюдений. Размер эффекта эту связку разрывает: он не зависит от того, сколько данных ты собрал.
Что такое Cohen's d простыми словами?
Cohen's d — это разница между средними двух групп, выраженная в стандартных отклонениях. Формула словами: (среднее теста − среднее контроля) / объединённое стандартное отклонение.
Смысл деления на стандартное отклонение — сделать эффект безразмерным и сопоставимым. Разница в 45 рублей сама по себе ни о чём не говорит: если чеки скачут в диапазоне тысяч рублей, 45 рублей — это шум; если разброс чеков всего 100 рублей, те же 45 рублей — огромный сдвиг. Cohen's d нормирует разницу на естественный масштаб данных.
Интуиция такая: d = 0.5 означает, что среднее тестовой группы лежит на пол-стандартного-отклонения выше контроля. Можно представить два колокола нормального распределения — d показывает, насколько сильно они разъехались относительно собственной ширины. При d = 0.2 колокола почти наложены друг на друга, при d = 0.8 между ними уже видимый зазор.
Важно: Cohen's d придуман для непрерывных метрик — средний чек, длительность сессии, число действий. Для конверсий и долей у него есть родственники (Cohen's h, относительный подъём, odds ratio), но это отдельная история, и в неё лучше не лезть с формулой для средних.
Как посчитать Cohen's d: формула и код
Объединённое (pooled) стандартное отклонение считается как корень из взвешенного среднего дисперсий обеих групп. На Python это несколько строк — держи функцию, которую можно унести в свой notebook:
import numpy as np
def cohens_d(test, control):
test, control = np.asarray(test), np.asarray(control)
n1, n2 = len(test), len(control)
s1, s2 = test.std(ddof=1), control.std(ddof=1)
# объединённое стандартное отклонение
pooled_sd = np.sqrt(
((n1 - 1) * s1**2 + (n2 - 1) * s2**2) / (n1 + n2 - 2)
)
return (test.mean() - control.mean()) / pooled_sd
На практике данные обычно лежат в одном датафрейме с колонкой группы — тогда всё сводится к двум срезам:
import pandas as pd
df = pd.read_csv('ab_orders.csv') # columns: user_id, group, aov
control = df.loc[df.group == 'control', 'aov']
test = df.loc[df.group == 'test', 'aov']
print(round(cohens_d(test, control), 3)) # например, 0.058
Прогнать это на живых данных можно прямо в браузере — открой Python-тренажёр и вставь функцию в задачу с pandas. Если подзабыл, как работает std(ddof=1) или срезы по маске, загляни в справочник по Python.
Одна тонкость, на которой спотыкаются: ddof=1. Это выборочное стандартное отклонение (делим на n−1, а не на n). Для оценки эффекта по выборке нужен именно он, иначе на маленьких группах d будет систематически завышен.
Как понимать значения 0.2, 0.5 и 0.8?
Джейкоб Коэн предложил грубую шкалу, которая с тех пор кочует из статьи в статью:
d ≈ 0.2— маленький эффект. Есть, но глазами в данных почти не виден.d ≈ 0.5— средний эффект. Заметен внимательному наблюдателю.d ≈ 0.8— большой эффект. Видно невооружённым глазом.
Дальше — важная оговорка, которую в учебниках обычно проглатывают. Эти пороги придуманы для поведенческих исследований — психология, медицина, где эффекты крупные. В продуктовой аналитике всё иначе: там нормальный выигрыш от фичи — это d порядка 0.01–0.05, и по шкале Коэна он «ничтожно мал», хотя приносит компании миллионы. Кнопка, которая двигает конверсию на 0.5 процентного пункта при базе в 10% — это гигантский бизнес-результат и микроскопический Cohen's d одновременно.
Поэтому я не сравниваю d с 0.2/0.5/0.8 вслепую. Правильный бенчмарк — твоя собственная история: какой эффект давали прошлые успешные тесты, какой минимум окупает разработку. Шкала Коэна годится как язык для разговора («это средний по силе эффект»), но не как критерий «внедрять или нет».
Почему на большой выборке значимо почти всё?
Вот тут прячется главная ловушка, из-за которой команды с миллионами пользователей тонут в «значимых» результатах.
Статистика критерия примерно равна размер эффекта / стандартная ошибка. А стандартная ошибка пропорциональна 1 / корень(n). Значит, чем больше выборка, тем меньше стандартная ошибка, тем больше значение критерия — и тем меньше p-value. При достаточно большом n статзначимой становится любая, даже абсолютно неинтересная разница.
Покажу на цифрах. Средний чек: контроль 800 рублей, тест 815 рублей, разброс (стандартное отклонение) около 800. Разница — 15 рублей, Cohen's d ≈ 15 / 800 ≈ 0.019. Мизер.
from scipy import stats
import numpy as np
def significant(diff, sd, n):
se = sd * np.sqrt(2 / n) # стандартная ошибка разности средних
t = diff / se
p = 2 * (1 - stats.norm.cdf(abs(t)))
return round(t, 2), round(p, 4)
print(significant(15, 800, 5_000)) # (0.94, 0.3472) — незначимо
print(significant(15, 800, 500_000)) # (9.38, 0.0) — «значимо»
Разница одна и та же — 15 рублей. Размер эффекта один и тот же — d ≈ 0.019. Но на 5 тысячах наблюдений тест «не выиграл», а на полумиллионе — «выиграл с оглушительным p-value». Изменилась не польза, изменилось только n. Cohen's d при этом не дрогнул, потому что он не зависит от размера выборки — в этом вся его ценность. Когда видишь p < 0.001 на огромной выборке, первым делом смотри на размер эффекта: скорее всего, значимость есть, а смысла нет.
Как размер эффекта связан с MDE и практической значимостью?
MDE (minimum detectable effect) — это минимальный эффект, который тебе важно уметь ловить. По сути это тот же размер эффекта, только заданный заранее, на этапе планирования. Ты говоришь: «эффект меньше вот такого мне неинтересен, а такой и крупнее я хочу гарантированно замечать» — и из этого считаешь нужный размер выборки.
Связь прямая: чем меньший эффект хочешь ловить, тем больше данных нужно. Посчитаем через statsmodels, сколько наблюдений на группу требуется, чтобы поймать эффект заданной силы при стандартных 80% мощности и alpha 0.05:
from statsmodels.stats.power import TTestIndPower
analysis = TTestIndPower()
for d in (0.2, 0.5, 0.8):
n = analysis.solve_power(effect_size=d, alpha=0.05,
power=0.8, alternative='two-sided')
print(d, round(n))
# 0.2 -> 394 (маленький эффект — нужна большая выборка)
# 0.5 -> 64
# 0.8 -> 26 (большой эффект видно на горстке наблюдений)
Ловить d = 0.2 — почти 400 человек на группу, а d = 0.8 хватает и пары десятков. Отсюда практический вывод: прежде чем запускать тест, реши, какой минимальный эффект тебе вообще нужен по деньгам, переведи его в размер эффекта и посчитай выборку. Иначе рискуешь либо не набрать данных на реальный эффект, либо набрать столько, что поймаешь бессмысленную мелочь.
Практическая значимость — это уже бизнес-порог, который живёт снаружи статистики. Ты смотришь на конкретную метрику (DAU, выручку, retention) и спрашиваешь: этот сдвиг окупит разработку и поддержку? Статзначимость говорит «эффект реален», размер эффекта — «эффект вот такого масштаба», а практическая значимость — «и нам это выгодно». Решение принимается только когда сошлись все три.
Пример: одинаковая разница, разные выводы
Разберу случай, который у меня был почти дословно. Две команды тестируют изменения, обе приносят одну и ту же абсолютную прибавку к среднему чеку — плюс 40 рублей. Обе получают p < 0.05. На планёрке обе рапортуют «тест выиграл».
Дальше считаем Cohen's d. У первой команды продукт — массовый маркетплейс с огромным разбросом чеков, стандартное отклонение около 900 рублей. d = 40 / 900 ≈ 0.044. У второй — нишевый сервис с ровными чеками, разброс около 120 рублей. d = 40 / 120 ≈ 0.33.
print(round(40 / 900, 3)) # 0.044 — тонет в общем разбросе
print(round(40 / 120, 3)) # 0.333 — заметный сдвиг
Абсолютная прибавка идентичная, p-value у обеих «зелёный». Но у первой команды +40 рублей растворяются в шуме — на фоне разброса в 900 это статистический артефакт большой выборки, который на юнит-экономике почти не отражается. У второй те же +40 рублей — это треть стандартного отклонения, ощутимый и воспроизводимый сдвиг поведения. Один и тот же «выигрыш» — два совершенно разных решения. Без размера эффекта отличить их по p-value невозможно в принципе.
Какие ошибки чаще всего портят выводы?
Список того, на чём я сам обжигался и что регулярно вижу в чужих отчётах.
- Показывают p-value без размера эффекта. «Тест значим, p = 0.03» — и ни слова о величине. Всегда рядом с p ставь d и доверительный интервал самого эффекта. Одно без другого — половина картины.
- Слепо лепят ярлыки 0.2/0.5/0.8. В продукте эффекты мелкие по этой шкале, но прибыльные. Сравнивай с историей своих тестов, а не с порогами из учебника психологии.
- Путают статистическую и практическую значимость. «Значимо» не равно «важно». На большой выборке значимо будет всё, что угодно.
- Считают Cohen's d на конверсиях. Формула для средних на долях врёт. Для конверсий — относительный подъём или Cohen's h.
- Забывают про доверительный интервал у эффекта. d = 0.3 с интервалом [0.28; 0.32] и d = 0.3 с интервалом [−0.1; 0.7] — это две разные вселенные уверенности.
- Подглядывают в тест раньше времени. Остановить эксперимент в момент, когда p впервые пробил 0.05, — это раздутая ошибка первого рода. Дождись расчётной выборки. Похожие подводные камни постоянно всплывают на реальных продуктовых кейсах.
Что забрать с собой
p-value и размер эффекта отвечают на разные вопросы, и ни один не заменяет второй. p-value говорит, отличается ли результат от шума; Cohen's d говорит, насколько сильно; практическая значимость — стоит ли оно денег. На большой выборке значимым становится всё подряд, поэтому именно размер эффекта — та величина, которая не даёт себя обмануть числом наблюдений. Возьми за правило: увидел p-value — тут же спрашивай «а d какой?».
Разбор размера эффекта и MDE любят на собеседованиях в аналитику — это как раз тот вопрос, который отделяет «прочитал про A/B» от «понимаю, что делаю». Если готовишься к собесу, посмотри, как это спрашивают в конкретных компаниях, и собери подготовку в систему по плану на месяц.
Открой Python-тренажёр, возьми задачу с A/B или средними и посчитай Cohen's d своими руками на реальном датафрейме. Одна прогнанная функция откладывается в голове надёжнее, чем десять прочитанных абзацев про H0. С Pro открывается весь каталог задач, метрик и кейсов — если хочешь довести статистику до автоматизма.
Связанная тема — p-value простыми словами: что это и как читать.