Гипотезы, p-value, размер выборки, выбор критерия — всё что спрашивают на собесах в продуктовые команды.
H0 и H1 — как правильно формулировать гипотезу до запуска теста, а не после.
Нулевая гипотеза (H0): изменение НЕ даёт эффекта.
Альтернативная (H1): изменение ДАЁТ эффект.
Пример:
H0: Новая кнопка «Купить» не меняет конверсию
H1: Новая кнопка повышает конверсию
Ошибки гипотез:
Тип I (False Positive / α): отвергаем H0, хотя она верна
→ «нашли эффект которого нет»
Тип II (False Negative / β): не отвергаем H0, хотя H1 верна
→ «пропустили реальный эффект»
Стандартные уровни:
α = 0.05 (5% вероятность ошибки типа I)
β = 0.20 (80% статистическая мощность = 1 - β)
Правило: гипотезу формулируют ДО запуска теста.
Менять метрику после просмотра данных — p-hacking!p-value — вероятность получить такие данные, если H0 верна. Как правильно интерпретировать и частые заблуждения.
p-value = вероятность получить ТАКОЙ ЖЕ ИЛИ БОЛЕЕ ЭКСТРЕМАЛЬНЫЙ результат,
если нулевая гипотеза (H0) верна.
p < 0.05 → результат статистически значимый
→ отвергаем H0
→ НЕ означает, что H1 верна на 95%!
Частые заблуждения:
✗ «p = 0.03 означает 97% вероятность что эффект есть»
✓ «При H0 мы получили бы такой результат с вероятностью 3%»
✗ «p > 0.05 означает нет эффекта»
✓ «Недостаточно данных чтобы отвергнуть H0»
✗ «Малый p-value = большой эффект»
✓ На большой выборке даже незначимый эффект даёт p < 0.05
Поэтому всегда смотрим ВМЕСТЕ:
• p-value (значимость)
• effect size / lift (размер эффекта)
• confidence interval (диапазон)Минимальный детектируемый эффект, мощность теста, α — формула расчёта и онлайн-калькуляторы.
Параметры для расчёта:
α = 0.05 (уровень значимости)
β = 0.20 → 80% (мощность теста)
p1 = baseline_cr (текущая конверсия, например 0.05 = 5%)
MDE = 0.10 (минимальный детектируемый эффект = +10% от baseline)
p2 = p1 * (1+MDE) (целевая конверсия = 0.055)
Python-расчёт:
from statsmodels.stats.power import TTestIndPower, zt_ind_solve_power
from statsmodels.stats.proportion import proportion_effectsize
es = proportion_effectsize(0.05, 0.055) # Cohen's h
n = zt_ind_solve_power(effect_size=es, alpha=0.05, power=0.80)
print(f'Нужно ≥ {int(n)} пользователей на группу')
Практические правила:
• Чем меньше MDE — тем больше нужна выборка
• Базовая конверсия 5%: для +10% нужно ~15 000/группу
• Базовая конверсия 20%: для +10% нужно ~3 400/группу
• Новинка: используй онлайн-калькулятор Evan's A/B ToolsКритерии выбора статистического теста в зависимости от типа данных и распределения.
t-test (Стьюдент):
• Непрерывная метрика (средний чек, время на сайте)
• Нормальное распределение ИЛИ n > 30 (ЦПТ)
from scipy.stats import ttest_ind
stat, p = ttest_ind(control, variant)
χ² (хи-квадрат):
• Категориальная метрика (конверсия: купил/не купил)
• Сравниваем частоты / пропорции
from scipy.stats import chi2_contingency
table = [[conv_c, no_conv_c], [conv_v, no_conv_v]]
chi2, p, dof, expected = chi2_contingency(table)
Mann-Whitney U-test:
• Непрерывная метрика с НЕНОРМАЛЬНЫМ распределением
• Нет выбросов-зависимости (долго, выручка с хвостом)
from scipy.stats import mannwhitneyu
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'A/B-тесты для аналитика',
description: 'Гипотезы, p-value, размер выборки, частые ошибки в A/B-экспериментах.',
openGraph: { title: 'A/B-тесты для аналитика', description: 'Гипотезы, p-value, размер выборки, частые ошибки в A/B-экспериментах.' },
};
stat, p = mannwhitneyu(control, variant, alternative='two-sided')
Шпаргалка:
Конверсия (доля) → χ² тест
Средний чек (нормальное) → t-test
Средний чек (с хвостами) → Mann-Whitney
Время удержания → Log-rank testPeeking problem, множественное тестирование, novelty effect — что убивает достоверность тестов.
1. Peeking / Ранняя остановка
Нельзя смотреть на результат и останавливать тест
как только p < 0.05 — inflate Type I error.
Решение: Sequential testing / bayesian approach.
2. Множественное тестирование (Multiple Comparisons)
Тестируешь 20 гипотез → одна значима по случайности (α=0.05).
Решение: Bonferroni correction → α_adj = 0.05 / 20 = 0.0025
3. Novelty Effect (эффект новизны)
Пользователи нажимают на новое просто потому что оно новое.
Решение: смотреть retention через 1-2 недели, не первый день.
4. Неправильная рандомизация
Одни пользователи попадают в обе группы, или
рандомизация по сессии а не по user_id.
Решение: всегда рандомизировать по entity (user/device) ID.
5. Разные исходные группы (SRM — Sample Ratio Mismatch)
Контроль: 10 000 пользователей, вариант: 9 200 — не 50/50.
Проверка χ²: ожидаем 50/50, получаем — другое → баг в рандомизации.
6. Измерение не той метрики
Оптимизируешь CTR кнопки «Купить» — растёт.
Но выручка падает, т.к. кликают случайно.
Решение: первичная метрика + guardrail metrics.