Всем привет! Меня зовут Дима Лунин, я аналитик в компании Авито. В этой статье я расскажу про критерий Манна-Уитни и проблемы при его использовании. 

Этот критерий очень популярен. Во многих компаниях и на обучающих курсах рассказывают про два его важных преимущества:

  1. Манн-Уитни — непараметрический собрат T-test. Если данные в A/B-тесте не из нормального распределения, то T-test использовать нельзя. На помощь приходит Манн-Уитни.

  2. Манн-Уитни — робастный критерий. В данных часто бывает много шумов и выбросов, поэтому T-test неприменим по соображениям мощности, чувствительности или ненормальности данных. А Манн-Уитни в этот момент отлично срабатывает и более вероятно находит статистически значимый эффект.

Если вы анализировали A/B-тест, где вас интересовал прирост или падение какой-то метрики, то наверняка использовали критерий Манна-Уитни. Я хочу рассказать про подводные камни этого критерия, и почему мы в компании его не используем. А в конце вы поймёте, откуда взялся такой холиварный заголовок :)

План статьи такой:

  1. Я теоретически покажу, что проверяет Манн-Уитни и почему это не имеет ничего общего с ростом медиан и среднего значения. А ещё развею миф, что Манн-Уитни проверяет эту гипотезу:

H₀: P(T > C) = 1/2
  1. На примере искусственных и реальных данных продемонстрирую, что Манн-Уитни работает не так, как вы ожидаете. Его «преимущество» в большем числе прокрасов относительно T-test, и это — главный минус этого критерия, поскольку прокрасы ложные.

  2. Расскажу про логарифмирование метрики и продемонстрирую, почему это плохая идея.

  3. Покажу теоретические случаи, когда Манн-Уитни применим и его можно использовать для сравнения средних.

Отвечу на вопрос: как тогда жить и какой критерий использовать для анализа A/B-тестов.

Что проверяет критерий Манна-Уитни?

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

H₀: P(T > C) = 1/2

Например, у нас в A/B-тесте две выборки:

  • U[−1, 1] — равномерное распределение от −1 до 1

  • U[−100, 100] — равномерное распределение от −100 до 100

Про эти два распределения мы знаем, что у них равны средние и медианы, и что они симметричны относительно 0. Кроме того, вероятность, что сгенерированное значение в первой выборке будет больше значения во второй выборке, равна 1/2. Или, если сформулировать математически, P(T > C) = 1/2, где T и C — выборки теста и контроля.

Я хочу проверить, работает ли здесь корректно Манн-Уитни. Для этого запустим эксперимент 1000 раз и посмотрим на количество отвержений нулевой гипотезы у критерия. Подробнее про этот метод можно прочесть в моей статье про улучшение A/B тестов. Если бы критерий Манна-Уитни работал как надо и проверял одну из гипотез, то по определению уровня значимости он бы ошибался на этих тестах в 5% случаев, с некоторым разбросом из-за шума.  

Проверим корректность примера на уровне значимости для T-test критерия:

# Подключим библиотеки
import scipy.stats as sps
from tqdm.notebook import tqdm # tqdm – библиотека для визуализации прогресса в цикле
from statsmodels.stats.proportion import proportion_confint
import numpy as np
 
# Заводим счетчики количества отвергнутых гипотез для Манна-Уитни и для t-test
mann_bad_cnt = 0
ttest_bad_cnt = 0
 
# Прогоняем критерии 1000 раз
sz = 1000
for i in tqdm(range(sz)):
    # Генерируем распределение
    test = sps.uniform(loc=-1, scale=2).rvs(1000) # U[-1, 1]
    control = sps.uniform(loc=-100, scale=200).rvs(1000) # U[-100, 100]
     
    # Считаем pvalue
    mann_pvalue = sps.mannwhitneyu(control, test, alternative='two-sided').pvalue
    ttest_pvalue = sps.ttest_ind(control, test, alternative='two-sided').pvalue
     
    # отвергаем критерий на уровне 5%
    if mann_pvalue < 0.05:
        mann_bad_cnt += 1
 
    if ttest_pvalue < 0.05:
        ttest_bad_cnt += 1
 
# Строим доверительный интервал для уровня значимости критерия (или для FPR критерия)
left_mann_level, right_mann_level = proportion_confint(count = mann_bad_cnt, nobs = sz, alpha=0.05, method='wilson')
left_ttest_level, right_ttest_level = proportion_confint(count = ttest_bad_cnt, nobs = sz, alpha=0.05, method='wilson')
# Выводим результаты
print(f"Mann-whitneyu significance level: {round(mann_bad_cnt / sz, 4)}, [{round(left_mann_level, 4)}, {round(right_mann_level, 4)}]")
print(f"T-test significance level: {round(ttest_bad_cnt / sz, 4)}, [{round(left_ttest_level, 4)}, {round(right_ttest_level, 4)}]")

Результат:

Mann-whitneyu significance level: 0.114, [0.0958, 0.1352]
T-test significance level: 0.041, [0.0304, 0.0551]

T-test здесь корректно работает: он ошибается в 5% случаев, как и заявлено. А значит, пример валиден.

Манн-Уитни ошибается в 11% случаев. Если бы он проверял равенство средних, медиан или P(T > C) = 1/2, то должен был ошибиться только в 5%, как T-test. Но процент ошибок оказался в два раза больше. А значит, что эти гипотезы неверны для Манна-Уитни:

T и C — выборки теста и контроля
T и C — выборки теста и контроля

Этот метод можно использовать для проверки только такой гипотезы:

F — функция распределения метрики у пользователей в контроле и в тесте, T и C — выборки теста и контроля
F — функция распределения метрики у пользователей в контроле и в тесте, T и C — выборки теста и контроля

Этот критерий проверяет, что выборки теста и контроля взяты из одного распределения. Если он считает, что выборки из разных распределений — отвергает гипотезу. Это обычный критерий однородности.

Для демонстрации корректности я предлагаю снова запустить 1000 раз тесты, но выборки теста и контроля в этот раз взяты из одного распределения: U[-100, 100] (равномерное распределение от -100 до 100).

Проверка
# Подключим библиотеки
import scipy.stats as sps
from tqdm.notebook import tqdm # tqdm – библиотека для визуализации прогресса в цикле
from statsmodels.stats.proportion import proportion_confint
import numpy as np
 
# Заводим счетчики количества отвергнутых гипотез для Манна-Уитни и для t-test
mann_bad_cnt = 0
ttest_bad_cnt = 0
 
# Прогоняем критерии 1000 раз
sz = 1000
for i in tqdm(range(sz)):
    # Генерируем распределение
    test = sps.uniform(loc=-100, scale=200).rvs(1000) # U[-100, 100]
    control = sps.uniform(loc=-100, scale=200).rvs(1000) # U[-100, 100]
     
    # Считаем pvalue
    mann_pvalue = sps.mannwhitneyu(control, test, alternative='two-sided').pvalue
    ttest_pvalue = sps.ttest_ind(control, test, alternative='two-sided').pvalue
     
    # отвергаем критерий на уровне 5%
    if mann_pvalue < 0.05:
        mann_bad_cnt += 1
 
    if ttest_pvalue < 0.05:
        ttest_bad_cnt += 1
 
# Строим доверительный интервал для уровня значимости критерия (или для FPR критерия)
left_mann_level, right_mann_level = proportion_confint(count = mann_bad_cnt, nobs = sz, alpha=0.05, method='wilson')
left_ttest_level, right_ttest_level = proportion_confint(count = ttest_bad_cnt, nobs = sz, alpha=0.05, method='wilson')
# Выводим результаты
print(f"Mann-whitneyu significance level: {round(mann_bad_cnt / sz, 4)}, [{round(left_mann_level, 4)}, {round(right_mann_level, 4)}]")
print(f"T-test significance level: {round(ttest_bad_cnt / sz, 4)}, [{round(left_ttest_level, 4)}, {round(right_ttest_level, 4)}]")

Результаты:

Mann-whitneyu significance level: 0.045, [0.0338, 0.0597]
T-test significance level: 0.043, [0.0321, 0.0574]

Манн-Уитни не может проверить ничего, кроме равенства распределений. Этот критерий не подходит для сравнения средних или медиан. 

Разберёмся, почему Манна-Уитни нельзя применять для сравнения средних, медиан и P(T > C) = 1/2. Для начала посмотрим на статистику, которая считается внутри критерия:

Для неё считаются те критические области, при попадании в которые статистики U критерий отвергнется.

Отсюда видно, что:

  1. Манн-Уитни учитывает расположение элементов выборок относительно друг друга, а не значения элементов. Поэтому он не может сравнивать математические ожидания, даже если не знает абсолютные значения элементов выборки.

  2. Гипотеза H_0: P(T > C) = 1/2 неверна для данного критерия. Если P(T > C) = 1/2, то мат. ожидание EU = (произведение размера выборок) / 2. Но при равенстве распределений мат ожидание статистики U будет точно таким же. Почему критерий не сработал на примере с 2 разными равномерными распределениями U[-1, 1] VS U[-100, 100], но сработал при сравнении распределений U[-100, 100] VS U[-100, 100]? Суть в том, что мы не знаем распределение статистики U. Когда тест и контроль из одного распределения, мы знаем, что статистика U распределена нормально. Но если это не так, то теорема перестаёт работать. Тогда мы не знаем, как распределена статистика U, и не можем посчитать p-value.

  3. По этой же причине критерий не может проверять равенство медиан.

Мы рассмотрели, почему Манна-Уитни не следует применять для проверки равенства средних, медиан и смещённости распределений. Но на практике его все равно часто применяют для этих гипотез, особенно для равенства средних. Что плохого может случиться в этом случае? Правда ли, что в таком случае мы завысим ошибку 1 рода в 2 раза и будем ошибаться в 11% вместо 5%?

Манн-Уитни при анализе реальных A/B-тестов: подводные камни

На реальных задачах проблемы Манна-Уитни будут куда серьёзнее, чем завышение alpha в два раза. Все последующие примеры будут основаны на кейсах, которые встречаются на практике в Авито. Я продемонстрирую работу критерия Манна-Уитни на искусственно сгенерированных данных, моделирующих один из наших экспериментов, а потом продемонстрирую результаты на настоящих данных из Авито.

Допустим, мы проводим A/B-тест с целью улучшить какую-то метрику. Тогда наше изменение чаще всего приводит к двум ситуациям:

  • Части пользователей понравилось наше воздействие и нулей в выборке стало меньше. Например, мы дали скидки на услуги, платящих пользователей стало больше.

  •  Другой части пользователей не понравилось наше изменение и они оттекли по метрике — стало больше нулей.

В обоих этих случаях я покажу, как может работать критерий. 

Кейс 1: прирост количества пользователей

Я разберу этот кейс на примере выдачи скидки. Количество платящих пользователей увеличилось, но ARPPU упал. Наша ключевая метрика — выручка. Мы хотим понять: начнём ли мы больше зарабатывать со скидками. Или выросло ли ARPU, математическое ожидание выручки — что эквивалентно. 

Сразу зафиксируем проверяемую гипотезу о равенстве средних: H0 — средний чек не изменился или упал vs. H1 — средний чек вырос. Мы уже знаем, что Манн-Уитни может ошибаться в этой задаче. Но насколько?

Искусственно насимулируем эксперимент, который мог бы произойти в реальности. В этот раз это будут не два равномерных распределения, а что-то более приближенное к реальности, и что вы сможете проверить самостоятельно, запустив код.

  1. Пусть распределение ненулевой части метрики подчиняется экспоненциальному распределению. Для выручки это хорошее приближение.

  1. Пусть нашими скидками мы увеличили число платящих пользователей на 5%.

    • Изначально в контроле было 50% нулей. Половина пользователей ничего не покупали на сайте.

    • В тесте стало 55% платящих пользователей.

  2. Раньше пользователь в среднем платил 7 рублей, а сейчас из-за скидки он платит 6 ₽, скидка была примерно 15%. Тогда математическое ожидание выручки в контроле было 3.5 ₽ (7 * 0.5), а в тесте — 3.3 ₽ (6 * 0.55).

То есть платящих пользователей стало больше, а средний чек упал. А значит, верна H0 и процент ложноположительных срабатываний не может быть больше 5% у корректного критерия. Для проверки мы снова запустим тест 1000 раз и проверим, что здесь покажут T-test с Манном-Уитни. 

Демонстрация результатов:

# Подключим библиотеки
import scipy.stats as sps
from tqdm.notebook import tqdm # tqdm – библиотека для визуализации прогресса в цикле
from statsmodels.stats.proportion import proportion_confint
import numpy as np
 
 
# Заводим счетчики количества отвергнутых гипотез для Манна-Уитни и для t-test
mann_bad_cnt = 0
ttest_bad_cnt = 0
sz = 10000
 
for i in tqdm(range(sz)):
    test_zero_array = sps.bernoulli(p=0.55).rvs(1000)
    control_zero_array = sps.bernoulli(p=0.5).rvs(1000)
    test = sps.expon(loc=0, scale=6).rvs(1000) * test_zero_array # ET = 3.3
    control = sps.expon(loc=0, scale=7).rvs(1000) * control_zero_array # EC = 3.5
     
    # Проверяем гипотезу
    mann_pvalue = sps.mannwhitneyu(control, test, alternative='less').pvalue
    ttest_pvalue = sps.ttest_ind(control, test, alternative='less').pvalue
    if mann_pvalue < 0.05:
        mann_bad_cnt += 1
 
    if ttest_pvalue < 0.05:
        ttest_bad_cnt += 1
 
# Строим доверительный интервал
left_mann_power, right_mann_power = proportion_confint(count = mann_bad_cnt, nobs = sz, alpha=0.05, method='wilson')
left_ttest_power, right_ttest_power = proportion_confint(count = ttest_bad_cnt, nobs = sz, alpha=0.05, method='wilson')
# Выводим результаты
print(f"Mann-whitneyu LESS power: {round(mann_bad_cnt / sz, 4)}, [{round(left_mann_power, 4)}, {round(right_mann_power, 4)}]")
print(f"T-test LESS power: {round(ttest_bad_cnt / sz, 4)}, [{round(left_ttest_power, 4)}, {round(right_ttest_power, 4)}]")

Результат:

Mann-whitneyu LESS power: 0.3232, [0.3141, 0.3324]
T-test LESS power: 0.0072, [0.0057, 0.0091]

Процент принятий гипотезы H1: «средний чек вырос» у T-test около 0: это нормально, так как мы знаем, что на самом деле среднее в тесте упало, а не выросло — критерий корректен. А у Манна-Уитни процент отвержений 32%, что сильно больше 11%, которые были в первом, изначальном примере.

Представим, что на ваших настоящих данных была примерно такая же картина. Только в реальности вы не симулировали эксперимент, поэтому не знаете, что ваш средний чек упал. У T-test околонулевой процент обнаружения статистически значимого эффекта. Используя его, вы видите, что он ничего не задетектировал, начинаете использовать критерий Манна-Уитни, и в одной трети случаев обнаруживаете эффект, что среднее увеличилось. 

Увидев, что T-test не прокрасился, а Манн-Уитни даёт статистически значимый прирост, многие аналитики доверятся второму критерию и покатят на всех пользователей скидки. И выручка упадёт.

Кейс 2: отток части пользователей

Другой вариант: части пользователей не понравилось наше изменение и они ушли. Допустим, мы захотели нарастить количество сделок на Авито. Сделка у нас — событие, когда продавец нашёл покупателя на своё объявление. Для этого мы добавили новые обязательные параметры в объявление и процесс подачи стал сложнее. Часть продавцов отвалилась, зато остальным стало легче: теперь покупателям с помощью фильтров и поиска проще находить подходящие объявления. Осталось понять, нарастили ли мы количество сделок.

Например, у нас было 100 продавцов и в среднем они совершали 2 сделки на Авито. После того как мы ввели новые параметры, продавцов стало 70. Оставшимся 30 продавцам наша инициатива не понравилась и они ушли. Теперь на одного продавца приходится по 3 сделки, так как пользователям стало легче находить релевантный товар. Раньше было 200 сделок, а теперь 210.

В этот раз все распределения будут взяты не из теоретических соображений, а из реальных данных. Как это сделать, я подробно рассказывал в статье про улучшение A/B-тестов. Я собрал 1000 настоящих А/А-тестов на исторических данных в Авито: взял всех продавцов в разных категориях и регионах за разные промежутки времени и поделил случайно на тест и контроль. Повторил это 1000 раз. 

Теперь я хочу простимулировать эффект от ввода новых обязательных параметров в тестовой группе: я моделирую отток пользователей и прирост числа сделок у оставшихся продавцов. 

Как именно я это сделал:

  • взял в качестве метрики количество сделок;

  • обнулил число сделок 5 процентам случайных продавцов, как будто они ушли с площадки;

  • у оставшихся пользователей в тестовой группе я домножил количество сделок на некий коэффициент больше единицы. В итоге среднее число сделок на продавца стало больше даже с учетом оттока. В примере выше этот коэффициент равнялся бы 1.5: 2 сделки → 3 сделки.

На самом деле количество сделок выросло от введения новых параметров.

Замечание

Да, получились не совсем «реальные» AB-тесты, а лишь приближения к ним. Но в любом случае это наилучшее возможное приближение, где мы знаем реальный эффект.

Теперь построим таблицу процента отвержений нулевой гипотезы в зависимости от альтернативы на этих 1000 A/B-тестах:

в тесте количество сделок больше, чем в контроле

--//-- меньше, чем в контроле

T-test

27%

0.1%

Манн-Уитни

6%

80% ????

Мы видим, что T-test в 27% случаев отвергает гипотезу в нужную сторону. Манн-Уитни — только в 6% случаев. Как видим, тут мощность (или чувствительность) Манна-Уитни меньше, чем у T-test. Если бы мы поставили корректную гипотезу «количество сделок выросло» для проверки, то мощность Манна-Уитни была бы меньше, чем у T-test.

Но если проверять, не уменьшили ли мы количество сделок новыми параметрами, то тут нас поджидает беда: в 80% случаев Манн-Уитни задетектирует эффект. 

Если бы мы использовали этот критерий на практике, то Манн-Уитни увидел бы тут отток числа сделок. Тогда в 4 из 5 тестов мы бы не катили новые фильтры на Авито и сделали вывод, что новые параметры при подаче только отбирают у нас контент и не помогают покупателям находить интересующие товары. 

Благодаря Манну-Уитни мы делаем неверный вывод о наших данных. 11% ложных прокрасов в 1 примере — это не предел. В реальных A/B-тестах их может быть сильно больше.

Вспомним, почему критерий Манна-Уитни популярен:

  • В данных часто бывает много шумов и выбросов, поэтому T-test неприменим
    по соображениям мощности, чувствительности или ненормальности данных.
    А Манн-Уитни в этот момент отлично срабатывает и более вероятно находит статистически значимый эффект.

 Как видно, это предположение не соответствует действительности:

  • T-test везде показал себе корректно и не делал больше ложноположительных прокрасов, чем должен был. А как это происходит и почему на самом деле критерий корректен, вы можете прочесть всё в той же статье про улучшение A/B-тестов.

  • Манн-Уитни нас не спасает, а делает только больше ложноположительных прокрасов. С тем же успехом можно кидать монетку и отвергать гипотезу, если выпала решка. Во втором кейсе монетка ошиблась бы в 50% случаев, а Манн-Уитни ошибается в 80%.

Здесь же кроется самая главная беда этого критерия: Манн-Уитни сильно чаще обнаруживает эффект, чем T-test. Именно поэтому этот критерий стал популярен у аналитиков. Когда есть выбор:

  • проглотить «горькую» пилюлю и получить не статистически значимые результаты с помощью T-test;

  • поверить критерию Манну-Уитни, что эффект на самом деле есть;

то хочется верить последнему. И в этот момент аналитики ошибаются. Поэтому мы в Авито не используем критерий Манна-Уитни.

Замечания про примеры

Это, конечно, всё хорошо, но я рассмотрел два специфических случая, когда метрика конверсии не сонаправлена с финальной метрикой. Например, в примере со скидками: конверсия в покупку выросла, а среднее по выручке упало. Кажется, что более частая картина, когда конверсия выросла и ARPU выросло. В этом случае, вы могли бы вместо проверки прироста ARPU перейти к проверке роста конверсий — менее шумной метрике. Но, к сожалению, вы не знаете, так ли это на самом деле, поэтому вы не можете полагаться на это :( А значит два примера выше вполне могут иметь место в реальности.

Ещё Манна-Уитни любят применять для медиан. Здесь также можно привести примеры, почему это невалидно. В этом случае я советую пользоваться Bootstrap, который корректно работает на больших выборках. Или реализовать критерий из этой статьи.

Логарифмирование метрики

Для работы с выбросами в данных часто советуют идею логарифмирования метрики. Алгоритм состоит из двух частей:

  • применим y := log(x + 1);

  • к новым логарифмированным метрикам «y» применим T-test. 

Зачем используют этот метод? Когда в данных есть выбросы, метрика очень шумная и дисперсия в данных огромная. А при логарифмировании выбросы исчезают и все данные становятся примерно одинаковыми.

Проверим критерий на том же искусственном примере что и ранее.

Код
# Подключим библиотеки
import scipy.stats as sps
from tqdm.notebook import tqdm # tqdm – библиотека для визуализации прогресса в цикле
from statsmodels.stats.proportion import proportion_confint
import numpy as np
 
 
# Заводим счетчики количества отвергнутых гипотез для t-test
ttest_bad_cnt = 0
sz = 10000
 
for i in tqdm(range(sz)):
    test_zero_array = sps.bernoulli(p=0.45).rvs(1000)
    control_zero_array = sps.bernoulli(p=0.5).rvs(1000)
    test = sps.expon(loc=0, scale=7).rvs(1000) * test_zero_array # ET = 3.15
    control = sps.expon(loc=0, scale=6).rvs(1000) * control_zero_array # EC = 3
    test = np.log(test + 1)
    control = np.log(control + 1)
     
    ttest_pvalue = sps.ttest_ind(control, test, alternative='greater').pvalue
 
    if ttest_pvalue < 0.05:
        ttest_bad_cnt += 1
 
# Строим доверительный интервал
left_ttest_power, right_ttest_power = proportion_confint(count = ttest_bad_cnt, nobs = sz, alpha=0.05, method='wilson')
# Выводим результаты
print(f"Log T-test GREATER power: {round(ttest_bad_cnt / sz, 4)}, [{round(left_ttest_power, 4)}, {round(right_ttest_power, 4)}]")

Результат лучше, чем у Манна-Уитни: количество отвержений не в ту сторону всего 15% вместо 38%. Но это всё ещё значит, что этой штукой пользоваться нельзя!

Почему этот критерий неверен — рассмотрим на примере выборки из трёх элементов: a, b, c.

Метрика, которая нас интересует — это среднее (a + b + c) / 3. Именно её рост или падение мы хотим задетектировать. При логарифмировании мы считаем метрику (abc) ^ 1/3, или среднее геометрическое. Среднее и среднее геометрическое — разные метрики, поэтому критерий ошибается. 

С точки зрения бизнеса логика такая: прирост более мелких значений в такой метрике сильнее детектируется, чем прирост у больших значений. Например:

  • Раньше значение метрики было 0, а сейчас 2. log(2 + 1) - log(1) ≈ 1.09

  • Раньше значение метрики было 1000, а сейчас 2000. log(2001) - log(1001) ≈ 0.69

То есть, прирост метрики на 2 даёт больший вклад, чем прирост метрики на 1000. Поэтому у нас и получается фигня при использовании критерия. Но все выбросы при этом становятся меньше и поэтому критерий имеет лучшую мощность.

Когда Манн-Уитни работает?

Если тестируемая фича полностью сдвигает выборку на некий коэффициент theta или масштабирует выборку на некий параметр theta (theta > 0), то критерий Манна-Уитни применим, и он будет верно оценивать направление сдвига математического ожидания.

T_old — значение метрики в тесте, если бы не было никакого воздействия. T_new — эффект после введения воздействия.
T_old — значение метрики в тесте, если бы не было никакого воздействия. T_new — эффект после введения воздействия.
Доказательство корректности Манна-Уитни в более общем случае

Попробуем рассуждать в более общем случае. Если вы знаете, что ваше теситируемое изменение полностью сдвинуло функцию распредления в тесте, то можно использовать критерий Манна-Уитни. То есть, если возможны только три варианта:

Тогда вы можете использовать критерий Манна-Уитни. Что значит функция распределения F больше функции распределения G? Это верно, если в любой точке x: F(x) > G(x).

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

Я утверждаю, что если  F_T доминирует над F_C, то:

  • P(C < T) <= 1/2, а значит статистика Манна-Уитни будет смещена от нормального состояния

  • ET < EC

Сначала покажем второй факт:

Где первый переход следует из свойств мат. ождидания, а неравество следует из того, что в любой точке x: F_T(x) > F_C(x). А значит 1 - F_T(x) < 1 - F_C(x).

Так что если Манн-Уитни умеет верно детектировать, какое распределение доминирует над другим, то он умеет правильно детектировать рост или падение мат. ожидания. И здесь как раз нам поможет первый факт.

Докажем, что P(C < T) <= 1/2. Тогда это значит, что статистика Манна-Уитни при таком доминировании всегда будет смещена только в одну сторону. А значит, если односторонний критерий Манна-Уитни отвергся => ET < EC.

То есть Манн-Уитни умеет проверять прирост/падение средних при сдвиге распределений. А теперь одно из распределений будет доминировать над другим: когда вы сдвигаете распределение или масштабируете его, то у вас как раз один из случаев, описанных выше. Поэтому вы можете в этих случаях пользоваться критерием Манна-Уитни.

Вопрос: каким образом вы будете уверены, что у вас именно такой эффект от воздействия? Если вы можете привести пример реальной задачи в бизнесе с таким эффектом от тестируемого изменения — жду вас в комментариях к статье :) 

Даже если вы знаете, что у вас именно будет такой эффект, вместо Манна-Уитни я бы предложил использовать критерий однородности Колмогорова-Смирнова, который также может смотреть односторонние альтернативы и показывать направление сдвига мат. ожидания. Только в этот момент:

  • мощность у Колмогорова-Смирнова при сдвиге или масштабировании метрики не меньше, чем у Манна-Уитни. А где-то даже больше.

  • вы точно знаете, что изначальная гипотеза у вас — равенство распределений, а не странные интерпретации, присущие Манну-Уитни. А значит, у вас меньше шанс неправильно осознать результаты.

На втором пункте я предлагаю остановиться: я сравню Манна-Уитни и Колмогорова-Смирнова по мощности — в случае сдвига выборки в тесте и в случае масштабирования.

Код
# Подключим библиотеки
import scipy.stats as sps
from tqdm.notebook import tqdm # tqdm – библиотека для визуализации прогресса в цикле
from statsmodels.stats.proportion import proportion_confint
import numpy as np
 
 
# Заводим счетчики количества отвергнутых гипотез для Манна-Уитни, t-test, KS
mann_bad_cnt = 0
ks_bad_cnt = 0
ttest_bad_cnt = 0
sz = 10000
 
for i in tqdm(range(sz)):
    test = sps.expon(loc=0, scale=10).rvs(1000)
    control = sps.expon(loc=0, scale=10).rvs(1000)
    test += 0.5
     
    mann_pvalue = sps.mannwhitneyu(control, test, alternative='less').pvalue
    ks_pvalue = sps.ks_2samp(control, test, alternative='greater').pvalue # Потому что он сравнивает F_T > F_C. А в этот момент ET < EC. Поэтому и другая альтернатива
    ttest_pvalue = sps.ttest_ind(control, test, alternative='less').pvalue
    if mann_pvalue < 0.05:
        mann_bad_cnt += 1
 
    if ks_pvalue < 0.05:
        ks_bad_cnt += 1
     
    if ttest_pvalue < 0.05:
        ttest_bad_cnt += 1
     
# Выводим результаты
print(f"Mann-whitneyu: {round(mann_bad_cnt / sz, 4)}")
print(f"Kolmogorov-Smirnov: {round(ks_bad_cnt / sz, 4)}")
print(f"T-test: {round(ttest_bad_cnt / sz, 4)}")

Mann-whitneyu: 0.6027
Kolmogorov-Smirnov: 0.7074
T-test: 0.2984

Как видим, выиграл Колмогоров-Смирнов.

А теперь посмотрим на случай, когда происходит масштабирование. На экспоненциальном распределении разницы в мощности не было обнаружено — можете сами провести эксперимент и убедиться. Поэтому ради интереса я взял исторические данные Авито, создал 1000 тестов и помножил выручку в тесте на коэффициент больше единицы. Примерно то же самое мы делали во втором кейсе ранее, но без обнуления метрики у части пользователей.

Mann-whitneyu: 0.5778
Kolmogorov-Smirnov: 0.9949
T-test: 0.0798

И здесь Колмогоров-Смирнов победил.

А поэтому вопрос к вам: зачем использовать критерий Манна-Уитни? Лучше попробуйте критерий Колмогорова-Смирнова.

Выводы

Итого, Манн-Уитни: 

  • Это критерий однородности. Все остальные интерпретации нулевой гипотезы неверны.

  • В реальных задачах этот критерий неприменим. Если вы начнете его использовать, то он обнаружит статистически значимый эффект в большем числе случаев, чем у T-test. Только после этого неверно говорить о приросте математического ожидания или медианы в тесте относительно контроля. И именно частые прокрасы критерия Манна-Уитни делают его самым опасным и таким популярным критерием для анализа A/B-тестов.

  • Логарифмирование метрики при этом тоже не работает.

  • Если вы точно знаете, что ваше воздействие сдвинуло вашу выборку или отмасштабировало её на некий параметр, то вы можете использовать Манна-Уитни. Но зачем, если критерий Колмогорова-Смирнова, который мощнее?

  • И главное: в реальности такой ситуации не бывает в бизнесовых задачах. А если вы знаете такие примеры, то будет очень интересно пообщаться с вами.

Если есть вопросы, желание обсудить статью, да и вообще любая реакция, то жду вас в комментариях!

Предыдущая статья: Сколько нужно времени, чтобы переписать объявление?

Комментарии (5)


  1. lea
    17.01.2023 13:31

    Можно взять статистику из t-критерия и использовать её как основу для перестановочного теста. Дёшево, сердито, современно.


  1. adeshere
    17.01.2023 21:25
    +3

    Спасибо за постановку вопроса и за статью! Так приятно, что в наше жуткое время "нажимания кнопок" еще остались люди, готовые сперва протестировать вроде бы общеизвестный (типовой) метод /критерий, и разобраться - насколько он пригоден для данной конкретной задачи, и только потом его применять. Вдвойне здорово, когда они не ленятся еще и написать об этом статью. Надеюсь, что она для многих будет полезна!


  1. thevlad
    18.01.2023 02:31

    Вообще-то даже в википедии в описании метода расписано почему он не будет работать на сформированных вами выборках. В частности "if the responses are assumed to be continuous and the alternative is restricted to a shift in location, i.e., F1(x) = F2(x + δ), we can interpret a significant Mann–Whitney U test as showing a difference in medians. Under this location shift assumption, we can also interpret the Mann–Whitney U test as assessing whether the Hodges–Lehmann estimate of the difference in central tendency between the two populations differs from zero."

    Сделайте честный тест в котором происходит только смещение центра распределения, тогда можно будет о чем-то говорить.

    PS: И по поводу интерпретации p-value https://en.wikipedia.org/wiki/Misuse_of_p-values а то, что вы описали в своих "экспериментах" это вообще кошмар


  1. NNikolay
    18.01.2023 08:12

    Когда я ещё верил в А/Б тестирование, я любил гонять А/А тесты на исторических данных. Так можно протестировать всю систему и метрику тоже не изгаляясь с предположениями о распределениях. Интересно, что у Вас получится на этих трёх метриках.

    Когда смотришь на успешность А/А тестов (ожидаемые 5% может не получиться) и сравниваешь с успешностью А/Б тестов за всё время... то становится грустно.

    Да и 5% - это цифра с потолка. Давно хочу написать как нужно жить, но лень :)


  1. schhhhkat
    18.01.2023 08:54

    Интересно было бы почитать подобный разбор возможных подводных камней в непараметрическом аналоге ANOVA - критерии Краскела-Уоллиса.