статистиканепараметрические тестыМанна-Уитни

Тест Манна-Уитни и непараметрические критерии: когда t-тест бесполезен

2026-07-11 12 мин

Когда параметрический t-тест уже не работает?

Короткий ответ: как только распределение перестаёт быть похожим на нормальное — тяжёлые хвосты, сильный скос, редкие выбросы — или данные вообще приходят в виде рангов и оценок, среднее превращается в плохую характеристику, а t-тест начинает врать. В таких случаях я беру непараметрические критерии: тест Манна-Уитни для двух независимых групп, критерий Уилкоксона для парных измерений и Краскела-Уоллиса, когда групп больше двух. Они работают не со значениями, а с их рангами, поэтому им всё равно, насколько уродливая у вас гистограмма.

На практике аналитик почти никогда не имеет дело с красивым колоколом. Выручка на пользователя, время до первой покупки, длина сессии, количество заказов — всё это распределения с длинным правым хвостом. Один кит с чеком в двести тысяч перетягивает среднее так, что оно перестаёт описывать типичного клиента. t-тест сравнивает именно средние и предполагает, что они устойчивы и что данные примерно нормальны. Когда оба условия нарушены, p-value из t-теста — это красивое число без смысла.

Здесь важно не путать две вещи. Центральная предельная теорема действительно спасает при больших выборках: среднее становится примерно нормальным, даже если сами данные — нет. Но «большая выборка» для тяжёлого хвоста — это не сто наблюдений, а иногда десятки тысяч, и до этого момента t-тест на скошенных данных даёт заниженную мощность и кривой доверительный интервал. Если у меня 40 пользователей в группе и распределение выручки как обычно, я даже не буду проверять нормальность — сразу иду в ранговые методы.

Что именно проверяет тест Манна-Уитни?

Тест Манна-Уитни (он же критерий Уилкоксона для двух независимых выборок, отсюда путаница в названиях) отвечает на один вопрос: если я наугад возьму по одному наблюдению из каждой группы, какова вероятность, что значение из первой группы окажется больше, чем из второй? Нулевая гипотеза — эта вероятность равна 0.5, то есть ни одна группа систематически не «выше» другой. Это называется стохастическим доминированием, и по-русски проще всего сказать так: тест ловит сдвиг одного распределения относительно другого.

Механика простая и в этом её красота. Я сваливаю обе группы в одну кучу, сортирую все значения и присваиваю каждому ранг: самому маленькому — 1, следующему — 2 и так далее. Потом складываю ранги внутри каждой группы. Если группы одинаковые, суммы рангов будут примерно пропорциональны размерам групп. Если одна группа стабильно даёт значения побольше, её ранги окажутся в верхней части списка, и сумма будет заметно выше ожидаемой. Статистика U — это как раз мера того, насколько сумма рангов отклонилась от того, что было бы при полном перемешивании.

Отсюда сразу два следствия. Первое: конкретные значения не важны, важен только порядок. Выброс в миллион даёт ранг «последний», ровно как значение в тысячу — тоже «последний». Второе, о котором часто забывают: тест сравнивает распределения целиком, а не медианы. Медианный сдвиг — это удобная интерпретация, но она строго верна только когда формы распределений в группах одинаковы и отличаются лишь положением. Если в одной группе разброс шире, чем в другой, Манн-Уитни может показать значимость из-за разницы формы, а не из-за сдвига центра. Я держу это в голове и всегда смотрю на две гистограммы рядом, прежде чем писать в отчёт «медиана выросла».

Как посчитать Манна-Уитни в scipy?

Одна строчка. Дальше — как это выглядит на реальной задаче: сравниваю время до первой покупки (в часах) у контрольной группы и у варианта с новым онбордингом.

import numpy as np
from scipy.stats import mannwhitneyu

# часы до первой покупки; обратите внимание на выбросы 120 и 90
control = np.array([2, 5, 1, 8, 3, 120, 4, 6, 2, 7, 5, 9, 3, 4, 6])
variant = np.array([1, 3, 2, 4, 1, 2, 5, 3, 2, 90, 3, 2, 4, 1, 3])

stat, p = mannwhitneyu(control, variant, alternative='two-sided')
print(f"U = {stat:.1f}, p-value = {p:.4f}")
print(f"медианы: control={np.median(control)}, variant={np.median(variant)}")

Аргумент alternative управляет гипотезой: two-sided — «группы различаются в любую сторону», less и greater — односторонние проверки, если у меня заранее есть направленная гипотеза. По умолчанию scipy делает поправку на связки (одинаковые значения получают средний ранг) и на непрерывность, так что руками ничего донастраивать не нужно.

Только p-value недостаточно — оно говорит «различие есть», но не «насколько велико». Для размера эффекта у Манна-Уитни есть удобная метрика — rank-biserial correlation, которая напрямую выводится из U:

n1, n2 = len(control), len(variant)
# доля пар, где control > variant, минус доля обратных пар
rank_biserial = 1 - (2 * stat) / (n1 * n2)
print(f"rank-biserial = {rank_biserial:.3f}")

Значение близко к нулю — группы перемешаны, эффект мизерный; ближе к 1 или -1 — почти в каждой паре одна группа стабильно выше. Это гораздо честнее, чем гнать в презентацию только звёздочки значимости. Если хочется покрутить такие расчёты вживую, у нас есть Python-тренажёр с реальными датасетами, а быстрые шпаргалки по функциям я держу в Python-справочнике.

Уилкоксон для парных данных — чем он отличается?

Критерий Уилкоксона для связанных выборок (signed-rank) нужен, когда я меряю одних и тех же людей дважды: до и после изменения. Например, среднее число заказов на пользователя за неделю до запуска фичи и за неделю после. Тут наблюдения не независимы — это один и тот же человек, — и Манн-Уитни применять нельзя, он рассчитан на две разные группы.

Уилкоксон работает с разностями внутри пар. Считаю разницу «после минус до» для каждого пользователя, отбрасываю нулевые, беру модули, ранжирую их, а потом возвращаю знаки. Если изменения хаотичны, положительные и отрицательные ранги уравновесятся. Если фича реально сдвинула поведение, ранги одного знака перевесят.

from scipy.stats import wilcoxon

before = np.array([3, 5, 2, 8, 4, 6, 1, 7, 3, 5, 2, 4])
after  = np.array([4, 5, 3, 7, 6, 8, 2, 9, 3, 6, 4, 5])

stat, p = wilcoxon(before, after, alternative='two-sided')
print(f"W = {stat:.1f}, p-value = {p:.4f}")

Это непараметрический аналог парного t-теста. Логика выбора та же: если разности распределены примерно нормально и без диких выбросов — можно и парный t-тест, если хвосты тяжёлые или наблюдений мало — беру Уилкоксона. Похожие сюжеты «до/после» постоянно всплывают на собеседованиях, разбор типовых вопросов лежит в разделе вопросов для интервью.

Что делать, если групп больше двух?

Тут выходит на сцену критерий Краскела-Уоллиса — обобщение Манна-Уитни на три и более независимых группы. Классический кейс: сравниваю выручку на пользователя по трём тарифам или конверсию по четырём источникам трафика. Соблазн прогнать попарно Манна-Уитни между всеми парами велик, но так я раздуваю вероятность ложного срабатывания: чем больше сравнений, тем выше шанс поймать «значимость» на пустом месте.

Краскел-Уоллис делает один общий тест: «хотя бы одна группа отличается от остальных по расположению распределения?». Механика та же ранговая — все значения ранжируются в общей куче, а статистика смотрит, насколько средние ранги по группам разошлись.

from scipy.stats import kruskal

tier_a = np.array([120, 90, 150, 200, 80, 110, 95])
tier_b = np.array([300, 250, 280, 500, 260, 310, 290])
tier_c = np.array([90, 110, 100, 130, 85, 95, 105])

stat, p = kruskal(tier_a, tier_b, tier_c)
print(f"H = {stat:.2f}, p-value = {p:.4f}")

Если общий тест значим, дальше идут попарные сравнения с поправкой на множественность (тест Данна или Манн-Уитни с поправкой Холма/Бонферрони). Порядок ровно как в дисперсионном анализе: сначала общий тест, потом post-hoc — иначе p-value можно не верить. Разбор задач, где именно так сегментируют пользователей, есть в блоке бизнес-кейсов, а собрать сами сегменты SQL-ем можно потренироваться в SQL-тренажёре.

Правда ли главный плюс — устойчивость к выбросам?

Да, и это не маркетинг, а прямое следствие ранжирования. Когда я заменяю значения на порядковые номера, любой монструозный выброс превращается в обычный «самый большой ранг». Значение 120 и значение 12000 дадут один и тот же ранг, если оба — максимумы в своей выборке. Поэтому один кит не смещает результат, как это происходит со средним и t-тестом.

Отсюда растут и остальные достоинства. Не нужно предположение о нормальности — тест валиден на любой форме распределения. Он естественно работает с порядковыми данными: оценки удовлетворённости от 1 до 5, уровни NPS, ранжирование позиций — там среднее вообще не имеет смысла («средняя оценка 3.7» между «нейтрально» и «скорее доволен» — это что?), а ранги имеют. И он не разваливается на маленьких выборках, где проверить нормальность всё равно нечем: на 15 наблюдениях тест Шапиро-Уилка почти всегда пропустит ненормальность, так что честнее сразу пойти в непараметрику.

Ещё один практический бонус — устойчивость к цензурированию сверху. Если у меня время до события обрезано границей наблюдения («не купил за 30 дней» записано как 30), точные значения на хвосте всё равно потеряны, но их порядок сохраняется, и ранговый тест это переживает спокойнее, чем расчёт среднего.

А в чём минусы и теряю ли я мощность?

Расплата есть, и о ней стоит знать заранее. Первое — мощность. Когда данные действительно нормальны, Манн-Уитни чуть слабее t-теста: его относительная эффективность около 0.95, то есть на нормальных данных он «стоит» примерно как t-тест на выборке на 5% меньше. Небольшая цена, но она есть. Правда, разворот тут в мою пользу: на тяжёлых хвостах Манн-Уитни бывает уже мощнее t-теста, потому что тот тонет в дисперсии, раздутой выбросами.

Второе — интерпретация. t-тест даёт понятную вещь: разницу средних с доверительным интервалом, «в среднем на 40 рублей больше». Манн-Уитни возвращает U и p-value, из которых бизнес-величину надо доставать отдельно — через rank-biserial или разницу медиан. Сказать заказчику «вероятность, что случайный клиент из B заплатит больше случайного из A, равна 0.63» можно, но это требует привычки.

Третье, самое коварное: тест значим — а что именно сдвинулось? Как я уже говорил, строго про медиану Манн-Уитни говорит только при одинаковой форме распределений. Если формы разные, значимость может прийти от разброса, а не от центра. Поэтому p-value я никогда не подаю в одиночку — рядом всегда медианы, размер эффекта и картинка с двумя распределениями. И ещё: непараметрика не чинит смещённую выборку и не спасает от плохого дизайна эксперимента. Если группы набраны криво, ранги соврут ровно так же, как среднее.

Как выбрать критерий на практике за минуту?

Мой рабочий алгоритм короткий. Две независимые группы, метрика скошенная или это ранги — Манн-Уитни. Одни и те же объекты измерены дважды — Уилкоксон. Три и больше независимых групп — Краскел-Уоллис плюс post-hoc с поправкой. Данные явно нормальные, симметричные, выборка приличная — можно оставаться на t-тесте или ANOVA, непараметрика тут только чуть проиграет в мощности.

Проверку нормальности я не превращаю в ритуал: на больших выборках тест Шапиро-Уилка забракует и почти-нормальные данные из-за микроскопических отклонений, на маленьких — не заметит ничего. Гораздо полезнее посмотреть на гистограмму и на QQ-plot глазами и спросить себя, есть ли в метрике природа тяжёлого хвоста. Выручка, время, счётчики — почти всегда да. Клики по бинарному признаку — там вообще другой инструмент, хи-квадрат или тест пропорций.

И последнее, что экономит часы: непараметрический тест — это ответ на «есть ли различие», а не «какое оно и стоит ли оно денег». Значимость на выборке в миллион строк ловится от копеечного эффекта. Поэтому решение принимается по размеру эффекта и его бизнес-смыслу, а тест лишь отсекает шум. Сами метрики, по которым стоит сравнивать группы, я разбираю в справочнике — от DAU до когорт и retention.

Если хочется довести это до автоматизма — прорешать ранговые тесты на живых датасетах, разобрать разбор A/B в Python-тренажёре и заодно натренировать SQL-выборки под них с нуля по курсу, — всё это открывается в Pro-доступе вместе с полным банком задач и AI-разбором собеседований. Непараметрика — не запасной вариант «когда t-тест не завёлся», а честный инструмент для тех данных, с которыми аналитик и работает каждый день.

Готовься к собесу аналитика
Вопросы по статистике и A/B с разбором — попробуй бесплатно.
Банк вопросов →