Большинство A/B-тестов в продукте сравнивают два средних или две доли. Для этого есть два рабочих инструмента: z-тест для долей и t-тест для средних. На собесе их спрашивают почти всегда, и важно не столько помнить формулы, сколько понимать, что они проверяют и когда ломаются.
Общая идея
Любой такой тест устроен одинаково:
статистика = (наблюдаемая разница - разница при H0) / стандартная ошибка разницы
Если нулевая гипотеза верна (эффекта нет), статистика распределена известным образом: нормально для z-теста, по Стьюденту для t-теста. p-value показывает, насколько необычно большое по модулю значение мы получили при условии H0.
z-тест для двух долей
Конверсия в группе A: p1 = x1 / n1, в группе B: p2 = x2 / n2. При H0 доли равны, поэтому для стандартной ошибки берут объединённую долю:
from math import sqrt
from statistics import NormalDist
def z_test(x1, n1, x2, n2):
p1, p2 = x1 / n1, x2 / n2
p = (x1 + x2) / (n1 + n2)
se = sqrt(p * (1 - p) * (1 / n1 + 1 / n2))
z = (p2 - p1) / se
p_value = 2 * (1 - NormalDist().cdf(abs(z)))
return z, p_value
Работает, когда в каждой группе достаточно и успехов, и неуспехов (практическое правило: хотя бы по 10, а лучше сотни). Для 3 конверсий из 40 лучше точный тест Фишера.
t-тест Стьюдента и Уэлча
Для средних (средний чек, время в приложении) используют t-тест. Классический t-тест Стьюдента предполагает равные дисперсии в группах. Тест Уэлча этого не требует:
def welch_t(a, b):
from statistics import mean, variance
va, vb = variance(a) / len(a), variance(b) / len(b)
t = (mean(b) - mean(a)) / (va + vb) ** 0.5
df = (va + vb) ** 2 / (va ** 2 / (len(a) - 1) + vb ** 2 / (len(b) - 1))
return t, df
statistics.variance считает выборочную дисперсию с делителем n - 1. Число степеней свободы у Уэлча дробное, это нормально.
Практический совет, который хорошо звучит на собесе: по умолчанию берите Уэлча. При равных дисперсиях он почти не теряет мощности, а при неравных (особенно с разными размерами групп) Стьюдент может заметно ошибаться в уровне значимости.
Парный t-тест
Если измерения связаны (один и тот же пользователь до и после, одна и та же модель на тех же данных), считают разности по парам и проверяют, что их среднее равно нулю: это одновыборочный t-тест по разностям. Он мощнее, потому что убирает разброс между объектами. Применять обычный двухвыборочный тест к парным данным можно, но он теряет мощность; применять парный к независимым группам нельзя вообще.
Допущения и ЦПТ
Частое заблуждение: «t-тест требует нормальности данных». На самом деле нужна приблизительная нормальность выборочного среднего. По ЦПТ при больших выборках (тысячи наблюдений, типичных для A/B) это выполняется и для скошенных метрик вроде выручки. Проблемы начинаются при:
- маленьких выборках с сильной асимметрией;
- тяжёлых хвостах (пара «китов» с огромными покупками), когда ЦПТ сходится медленно;
- зависимых наблюдениях: например, единица рандомизации пользователь, а метрика считается по сессиям. Стандартная ошибка тогда занижена, и тест слишком часто находит эффект.
Тяжёлые хвосты лечат винзоризацией (обрезка выбросов по квантилю), логарифмом, бутстрепом или переходом к другой метрике.
Когда z, когда t
При тысячах наблюдений распределение Стьюдента практически совпадает с нормальным, и разница между z и t для средних исчезает. На маленьких выборках t честнее: его хвосты тяжелее, и он учитывает, что дисперсию мы оценили, а не знаем.
Частые ошибки
- Стьюдент при сильно разных дисперсиях и размерах групп.
- Независимый тест для парных данных.
- Наблюдения не независимы (несколько сессий одного пользователя считаются отдельными точками).
- «Данные не нормальны, значит t-тест нельзя» при n = 50 000.
- Односторонний тест, выбранный после того, как посмотрели на знак эффекта.
Что спросят на собесе
- Чем t-тест Уэлча отличается от теста Стьюдента и какой брать по умолчанию?
- Нужна ли нормальность данных для t-теста?
- Как проверить разницу конверсий двух групп?
- Когда нужен парный t-тест?
- Что делать с выручкой, где у пары пользователей огромные чеки?