Если у вас три группы и больше, а нужно понять, отличаются ли их средние, — не запускайте попарные t-тесты пачками. Возьмите ANOVA, дисперсионный анализ. Она одним тестом отвечает на вопрос «есть ли вообще хоть какое-то различие между группами» и при этом не раздувает вероятность найти различие там, где его нет. Ниже разберу, почему куча t-тестов — это ловушка, как ANOVA сравнивает разброс между группами с разбросом внутри них, что за зверь F-статистика и как посчитать всё это в scipy за пять строк.
Почему нельзя сравнить три группы кучей t-тестов?
Потому что каждый t-тест на уровне значимости 0.05 несёт 5% шанс ложного срабатывания, и эти шансы накапливаются.
Представьте эксперимент с тремя вариантами лендинга: A, B и C. Чтобы сравнить их попарно, нужно три теста: A vs B, A vs C, B vs C. Для пяти вариантов уже десять пар, для шести — пятнадцать. Каждый тест — это отдельный бросок монетки с 5% шансом «увидеть» различие, которого на самом деле нет.
Если сравнения условно независимы, вероятность хотя бы одной ложной находки считается как 1 - 0.95^m, где m — число сравнений. Для трёх пар это примерно 14%, для десяти — уже около 40%. То есть на пяти группах вы почти гарантированно найдёте «значимое» различие на пустом месте. Это классическая проблема множественных сравнений, и её любят спрашивать на собеседованиях — если готовитесь, загляните в банк вопросов по статистике.
ANOVA решает это в лоб: вместо кучи попарных проверок она задаёт один общий вопрос — «есть ли хоть где-то разница между средними?» — и отвечает на него одним p-value с честным уровнем ошибки.
В чём идея ANOVA: межгрупповая и внутригрупповая дисперсия?
Идея красивая и на удивление простая: сравнить, насколько сильно различаются средние между группами, с тем, насколько значения гуляют внутри каждой группы.
Разброс данных ANOVA раскладывает на два источника:
- Внутригрупповая дисперсия — естественный шум. Даже внутри одной группы пользователи ведут себя по-разному: кто-то принёс 200 рублей, кто-то 500. Это разброс вокруг среднего своей группы, к эксперименту он отношения не имеет.
- Межгрупповая дисперсия — насколько далеко средние отдельных групп разбежались от общего среднего. Если варианты реально работают по-разному, средние разъедутся сильно.
Дальше логика такая. Если межгрупповой разброс большой на фоне внутригруппового шума — скорее всего, группы действительно разные. Если средние групп отличаются не сильнее, чем значения случайно гуляют внутри группы, — различие тонет в шуме, и говорить не о чем.
Отсюда и название «дисперсионный анализ»: мы сравниваем не сами средние напрямую, а два вида дисперсии. Немного контринтуитивно — тест про средние, а работает через разброс, — но именно это делает его устойчивым.
Что показывает F-статистика?
F-статистика — это и есть то самое отношение: межгрупповая дисперсия, делённая на внутригрупповую. Формула словами: F = средний межгрупповой квадрат / средний внутригрупповой квадрат.
Чтобы получить эти средние квадраты, суммы квадратов отклонений делят на степени свободы: для межгрупповой части их k - 1 (где k — число групп), для внутригрупповой — N - k (где N — общее число наблюдений). Делить обязательно, иначе большие группы автоматически давали бы больший разброс.
Как читать результат:
- Если группы не различаются, F крутится где-то около 1 — межгрупповой разброс примерно равен внутреннему шуму.
- Чем сильнее F больше единицы, тем убедительнее различие.
- Насколько именно F должен быть большим, чтобы считать различие неслучайным, подсказывает p-value, посчитанный по распределению Фишера с параметрами
(k-1, N-k).
Важный нюанс, о который спотыкаются новички: ANOVA — тест «омнибус». Маленький p-value говорит только, что где-то среди групп есть различие. Он не говорит, между какими именно. Вернёмся к этому в разделе про post-hoc.
Как посчитать one-way ANOVA в Python?
One-way ANOVA — это версия с одним фактором-группировкой (например, вариант эксперимента). В scipy она делается функцией f_oneway. Возьмём выручку на пользователя в трёх вариантах онбординга:
import pandas as pd
from scipy import stats
df = pd.read_csv("experiment.csv") # user_id, variant, revenue
# собираем массив выручки по каждому варианту
groups = [g["revenue"].values for _, g in df.groupby("variant")]
f_stat, p_value = stats.f_oneway(*groups)
print(f"F = {f_stat:.3f}, p = {p_value:.5f}")
Если данные лежат просто списками, ещё короче:
from scipy import stats
a = [340, 290, 410, 380, 300, 360, 420, 310]
b = [300, 280, 260, 330, 290, 275, 310, 295]
c = [450, 500, 470, 430, 490, 510, 460, 480]
f_stat, p_value = stats.f_oneway(a, b, c)
print(f"F = {f_stat:.3f}, p = {p_value:.5f}")
Читаем так: если p_value меньше выбранного порога (обычно 0.05), делаем вывод, что хотя бы один вариант отличается по средней выручке. В примере выше вариант C явно выбивается вверх, поэтому F будет большим, а p — крошечным.
Хотите пощупать руками — соберите такой датасет и прогоните код в Python-тренажёре, а синтаксис scipy и pandas подсмотрите в справочнике по Python.
ANOVA нашла различие — а где именно?
ANOVA сказала «различие есть», но между A и B, A и C или B и C — молчит. Чтобы узнать конкретику, нужен post-hoc тест. И тут нельзя снова скатиться в пачку обычных t-тестов — вернётся та же проблема раздувания ошибки.
Самый ходовой post-hoc — критерий Тьюки (Tukey HSD, honestly significant difference). Он проверяет все пары сразу, но держит суммарную ошибку под контролем, закладывая поправку прямо в расчёт. В свежих версиях scipy для этого есть tukey_hsd:
from scipy.stats import tukey_hsd
res = tukey_hsd(a, b, c)
print(res) # доверительные интервалы и p-value для каждой пары
На выходе — таблица по всем парам: для каждой видно p-value и доверительный интервал разницы средних. Если интервал не накрывает ноль, различие в этой паре значимо.
Логика всегда двухступенчатая: сначала ANOVA как привратник — есть ли смысл вообще копать. Если общий тест ничего не нашёл, лезть в попарные сравнения обычно не стоит. Если нашёл — Тьюки показывает, кто именно из групп отличается. Кроме Тьюки есть и другие поправки (Бонферрони — самая простая и консервативная), но для сравнения «каждый с каждым» Тьюки обычно даёт больше мощности.
Какие у ANOVA предпосылки и что делать, если они нарушены?
У классической one-way ANOVA три предпосылки, и знать их важнее, чем саму формулу.
- Независимость наблюдений. Пользователи не должны влиять друг на друга и попадать в несколько групп сразу. Это самое серьёзное требование — его нарушение чинится не тестом, а правильным дизайном эксперимента.
- Нормальность. Значения внутри групп должны быть примерно нормально распределены. На практике ANOVA к этому устойчива: на больших выборках за счёт центральной предельной теоремы лёгкая скошенность не страшна. Проблемы начинаются на маленьких группах с жирными хвостами.
- Однородность дисперсий (гомоскедастичность). Разброс внутри групп должен быть сопоставим. Проверяют тестом Левене:
from scipy import stats
stat, p = stats.levene(a, b, c) # p < 0.05 -> дисперсии разные
Что делать, если предпосылки поплыли:
- Дисперсии заметно разные — берите Welch ANOVA, она не требует их равенства (в statsmodels или через
pingouin.welch_anova). - Распределения совсем не нормальные или это порядковые данные (оценки, ранги) — переходите на непараметрический аналог, критерий Краскела — Уоллиса:
stats.kruskal(a, b, c). Он сравнивает не средние, а ранги, и не завязан на нормальность.
Выручка на пользователя, кстати, почти всегда скошена вправо: много мелких платежей и редкие крупные. Перед ANOVA такие метрики полезно прологарифмировать или сразу смотреть в сторону Краскела — Уоллиса.
Как использовать ANOVA в A/B-тесте с несколькими вариантами?
Типичный кейс — A/B/C/D-тест, где вы гоняете сразу несколько вариантов против контроля. ANOVA здесь работает как ворота: сначала одним тестом проверяем, есть ли различия в принципе, и только потом, если есть, идём в Тьюки смотреть, какой вариант выстрелил. Так вы не раздуваете ошибку первого рода и не радуетесь случайному выбросу.
Несколько практических оговорок, которые я усвоил на своих экспериментах:
- ANOVA — для непрерывных метрик: выручка на пользователя, длительность сессии, число действий, время до целевого события. Для конверсии (купил / не купил, бинарный флаг) это не тот инструмент — там берут хи-квадрат или логистическую регрессию.
- Если вас интересует не «каждый с каждым», а только «каждый вариант против контроля», есть более мощный специализированный post-hoc — критерий Даннета. Сравнений меньше, значит и штраф за множественность меньше.
- Размер выборки считайте заранее. Больше вариантов — нужно больше данных на каждый, иначе мощности не хватит и ANOVA просто ничего не увидит.
- Не подглядывайте в результат каждый день и не останавливайте тест на первом же значимом p-value — это отдельный источник ложных срабатываний, никак не связанный с ANOVA.
Разбор реальных многовариантных экспериментов удобно смотреть на кейсах, а типовые метрики вроде DAU и выручки — та самая непрерывная величина, которую ANOVA и сравнивает между группами. Если готовитесь к собеседованиям в продуктовые команды, вопрос «чем ANOVA отличается от t-теста и когда её брать» всплывает постоянно — структурированно пройтись по статистике помогает программа подготовки к собеседованию.
Короткая шпаргалка
Чтобы всё уложилось:
- Три группы и больше — не пачка t-тестов, а ANOVA. Иначе ошибка первого рода раздувается с 5% до 14%, 40% и выше.
- ANOVA сравнивает межгрупповую дисперсию с внутригрупповой. F больше единицы и маленький p-value — где-то есть различие.
- Где именно — покажет post-hoc, чаще всего Tukey HSD. Против контроля — критерий Даннета.
- Проверьте предпосылки: независимость, нормальность, равенство дисперсий (Левене). Не сходится — Welch ANOVA или непараметрический Краскел — Уоллис.
- В A/B с несколькими вариантами ANOVA работает воротами перед попарными сравнениями и только для непрерывных метрик.
Разница между t-тестом, ANOVA и их непараметрическими аналогами — один из самых частых блоков на собеседованиях аналитика. Если хочется не просто прочитать, а прорешать статистику и A/B по-настоящему, с проверкой прямо в браузере, — это открывается в Pro вместе со всеми задачами, кейсами и AI-собеседованиями. Начать можно бесплатно и без спешки.
Связанная тема — t-критерий Стьюдента простыми словами и пример.