Привет, с вами команда аналитиков “Пятёрочки” X5 Tech. Как вы уже знаете, мы активно внедряем решение AppMetrica для мобильной аналитики. В AppMetrica есть модуль для проведения A/B тестов на приложении – называется Varioqub, который является, в том числе, платформой для A/B-тестов в Яндексе. Varioqub грозится стать одним из основных инструментов для проведения тестов в рамках бизнеса на территории России и СНГ, при этом не только на приложениях, но и на вебсайтах. Поэтому было бы полезно знать, как он работает, учитывая, что под капотом данная A/B-тестилка использует такой статистический критерий как Mann-Whitney. Если вы хотите понимать способ подсчёта результатов ваших тестов и иметь их интерпретацию лучше, чем “сумму рангов”, то эта статья для вас.

Мы начнём с Mann-Whitney, по ходу разберём ещё два критерия, таких как Probability Index и ранговый тест Wilcoxon, которые помогут нам всё расставить по полочкам. Дальше мы коснёмся важных замечаний относительно этого теста, рассмотрим, как это решает Varioqub, и перейдём к самому Varioqub.

Часть 1. Mann-Whitney это не то, что вы думаете

Под капотом у Varioqub в качестве статистического теста используется непараметрический тест Mann-Whitney. Этот тест окутан рядом заблуждений, которые сводятся к путанице в определении проверяемой нулевой гипотезы, что, как следствие, даёт сложность интерпретации: “Сумма рангов стат. значима, а поэтому…” 

Так как цель этой статьи – ваше понимание, мы будем стараться максимально раскрыть в ней все формулы и подсчёты.

Mann-Whitney – это не про сумму рангов

Формально, конечно, там есть сумма рангов, она присутствует в формуле теста, которая не является изначальной. Вот эта формула:

U_{Y, X} =  R_2 - \frac{n_2(n_2+1)}{2} = n_1n_2+\frac{n_1(n_1+1)}{2} - R_1

где: 

R2 – это сумма рангов второй выборки (X);

R1 – это сумма рангов первой выборки (Y);

n1 – размер первой выборки X;

n2 – размер второй выборки Y.

– это то, во что тест переродился для простоты подсчёта и стал сложным для интерпретации, растеряв всю простоту своего содержания. Чтобы вернуться к содержанию, давайте попробуем пофантазировать, как бы этот тест мог бы быть придуман.

Варианты сравнения выборок

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

Hidden text

Это весь набор знаний, который будет нужен и вам, чтобы понять суть этого теста

При этом вы ничего не знаете из мат. стата: ни как считать среднее, ни дисперсию. Разве что имеете представление, что такое распределение.

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

Пускай наш набор – это непрерывные значения от 0 до 10, тогда подвыборка X и Y могут быть следующими:

  • X = [1.0, 2.0, 3.0]

  • Y = [1.5, 2.5, 3.5]

Итого, мы видим, что а) выборки отличаются б) но при этом они из одного мешка в) немного умудрённые опытом жизни, понимаем, что X и Y могли бы быть другими по составу элементов, например, [3.5, 4.5, 5.5] для Y.

И вот вам говорят: придумай, используя свои знания, такое правило, применяя которое к двум выборкам, ты бы мог в какой-то момент усомниться, что А и B – из одного набора. При этом так, чтобы это можно было делать для любых А и B без привязки к конкретной популяции (в нашем случае популяция от 0 до 10). На этой части вы можете сами остановиться и подумать: используя только сравнение и сложение, вывести правило.

Правило попарного сравнения

Вывели его профессор Генри Манн и его студент, Уитни. Оно оказалось следующим:

  1. Сделаем поэлементное сравнение.

    • Если элемент из А больше элемента из B, тогда пускай мы запишем результат этого сравнения как 1. Пример: элемент = 2 из A больше элемента = 1 из B ? Да, записывает 1.

    • Если элемент из А меньше, тогда 0.

    • Если элемент из А = элементу B, тогда ½

  2. Сложим результаты всех поэлементных сравнений, получив некую сумму. Эта сумма называется Mann-Whitney U-статистика или просто U-статистика.

U_{XY} = \sum_{i=1}^{n}\sum_{j=1}^{m}S(X_{i},Y_{j})S(X,Y)=\begin{cases}  & \text{1,   }  X_{i} > Y_{j} \\   &  {\text{1/2, }  X_{i} = Y_{j}} \\  & \text{0,   }  X_{i} < Y_{j} \\ \end{cases}\displaystyle

Где:

(X, Y) – это сравнение выборки X c Y;

Xi, Yi – это элементы выборок X и Y:

Рассмотрим пример:

Значение X \ Значение Y

1.5

2.5

3.5

1

1> 1.5? = 0

S(X,Y) = 0

S(X,Y) = 0

S(X,Y) = 0

0

2

S(X,Y) = 1

S(X,Y) = 0

S(X,Y) = 0

1

3

S(X,Y) = 1

S(X,Y) = 1

S(X,Y) = 0

2

U-stat (сумма) =

0+1+2=3

Hidden text

Или в иной форме:

Значение

Подсчет

1

(1 > 1.5) ? = 0

(1 > 2.5) ? = 0

(1 > 3.5) ? = 0

0+0+0= 0

0

2

(2 > 1.5) ? = 1

(2 > 2.5) ? = 0

(2 > 3.5) ? = 0

1+0+0= 1

1

3

(3 > 1.5) ? = 1

(3 > 2.5) ? = 1

(3 > 3.5) ? = 0

1+1+0 = 2

2

U-stat =

0+1+2=3

При этом, так как можно сравнить элементы из X c Y (X,Y) и, наоборот, Y c X (Y,X), что даст разные значения U-статистики, то из этих двух значений выбирается меньшее:

U = min(U_{(X,Y)}, U_{(Y,X)})
Hidden text

Убедимся, что сравнение Y c X даёт другое значение U-статистики:

Значение X \ Значение Y

1

2

3

1.5

1.5 > 1? = 1

S(X,Y) = 0

S(X,Y) = 0

S(X,Y) = 0

1

2.5

S(X,Y) = 1

S(X,Y) = 1

S(X,Y) = 0

2

3.5

S(X,Y) = 1

S(X,Y) = 1

S(X,Y) = 1

3

U-stat=

1+2+3=6

Отсюда мы должны взять U-stat = min(3, 6) = 3

Чем интересна запись результата сравнений вида: если A > B больше, тогда запишем 1, если меньше - 0, а равно = ½ ?

Во-первых, это своего рода нормализация: что 100 > 10, что 1000 > 50, результат один и он равен 1. А потому что набор:

  • [1.0, 2.0, 3.0]

  • [1.5, 2.5, 3.5]

что

  • [10.0, 20.0, 30.0]

  • [15.0, 25.0, 35.0]

Будут давать одну и ту же U-сумму. Как и любые сравнения двух выборок размером 3 с аналогичным взаиморасположением элементов. Нормализация даёт унификацию в плане будущей оценки распределения для H0 (об этом чуть ниже) для разных комбинацией размеров выборок, напоминающую этим же принципом аналогичную t-таблицу.

Во-вторых, как следствие, это нечувствительность к выбросам: статистику волнует расположение элементов относительно друг друга. Но у этого есть и цена: теряется магнитуда, степень различия 100-10 = 90, 1000-50 = 950, а именно различие такого рода и может волновать, особенно когда речь идёт о деньгах.

Ещё один момент, который стоит сразу подсветить. Как мы уже говорили ранее, используется другая формула

Hidden text
U_{Y, X} =  R_2 - \frac{n_2(n_2+1)}{2} = n_1n_2+\frac{n_1(n_1+1)}{2} - R_1

Почему это так и зачем, мы обязательно выясним.

Распределение нулевой гипотезы Mann-Whitney

Будучи немного знакомым со случайностью и зная, что выборки принципиально ничем не отличимы, так как взяты из одного набора, мы констатируем: эта сумма – результат случайности. Но где одна случайная сумма, там и другая. Какие ещё значения могут быть?

Для этого нам нужно повторить процедуру отбора в А и B большое количество раз, скажем, 100500 раз. Для примера мы возьмём выборки 3x3 из того же множества, что и раньше (популяция от 0 до 10), пускай они будут иными по составу, значит иным будет и результаты сравнения, то есть и суммы U. Короче говоря, применим столько же раз правило сравнение и получим 100500 сумм, отображаем полученный список сумм в гистограмму – готово! Мы получили распределение для выборок по (далее - 3 против 3 или 3x3) –  распределение нулевой гипотезы Mann-Whitney (его статистики).

H0, или нулевая гипотеза (для контекста)

Так как с тем, что проверяет Mann-Whitney есть путаница, а точнее, какую именно нулевую гипотезу он тестирует, то вспомним, что такое нулевая гипотеза вообще. Нулевая гипотеза в плане формулировки – это предположение о выборке, точнее, её параметрах / форме распределения и пр. Под гипотезу подбирается соответствующий тест, который мог бы её проверить. 

В контексте статистики выбранного теста нулевая гипотеза выражена через распределение статистики теста/критерия, обусловленное случайностью – самим случайным отбором в А и B. Если вам так будет проще, то представьте 100500 классических A/B, где группе B вы каждый раз давали treatment в виде плацебо. Вы ожидаете, что эффект от воздействия будет нулевой, однако это далеко не всегда так, бывают и очень экстремальные значения, но они – результат случайности, а не treatment’a. Отсюда между воздействием – treatment’ом – и результатом нет причинно-следственной связи, связь нулевая, сама случайность отбора даёт разброс значений. Поэтому когда говорят “при условии верности' нулевой гипотезы”, то говорят именно о распределении статистики теста из-за случайности. 

Hidden text

' В реальности это практически никогда не так, эффект скорее всего есть, но он может быть настолько незначительным, что мы а) не сумели его обнаружить в виду ошибки 2-го рода б) в нем нет практической значимости.

Важно понимать, что в рамках эксперимента всё начинается с продуктовой гипотезы. Какой? Тем, кто придумал на базе исследований некое улучшение, фичу, конечно, хочется говорить о том, что она даст результат лучше, поэтому и гипотеза у них о различиях. В науке же доказать различие невозможно', можно только опровергнуть с достаточной уверенностью отсутствие различий '', а потому нулевая гипотеза такова: разницы/различий нет, или в других словах – одно равно другому. И уже от этой гипотезы формулируем альтернативную, которую коротко обозначим как “не равны”.

Hidden text

' Разве что бутстрап позволяет сделать оценку различий и приземлять нолик, как ожидаемое значение при отсутствии различий, на свое распределение всех возможных разниц.

'' Так как у нас есть распределение статистики теста под этой случай, t-распределение самое известное

Далее, самое важное,  мы конкретизируем, через что мы это будем понимать, то есть как можно понять, что стало лучше/хуже? Через какой-то параметр распределения?  Например, будет ли это сдвиг средней? А может, не параметр распределения, а само распределение? Например, различие в форме распределения и так далее. От этого напрямую зависит постановка нашей нулевой гипотезы и критерий, который может её проверить. Например, если мы говорим про разницу средних, то наша нулевая гипотеза про то, что нет разницы средних. Для этой нулевой гипотезы есть соответствующий критерий – t-test, t-статистика которого имеет своё распределение при верности конкретно этой нулевой гипотезы.

Иными словами, прежде чем проверять статистическую значимость, нужно определиться с гипотезой, которую мы проверяем. А уже после выбора гипотезы выбирать подходящий для неё тест. Обычно у каждого теста есть описание, какую гипотезу он проверяет, но если есть какие-то трудности в понимании, то попробуйте посмотреть формулу – она может стать ключом к тому, чтобы понять нулевую гипотезу, которую способен проверить тест. 

Нулевая гипотеза Mann-Whitney

Вернёмся к распределению статистики Mann-Whitney: определим для всех выборок классический уровень значимости в 0.05.

критические значения при alpha=0.05 для U-статистики, в зависимости от размера выборок
критические значения при alpha=0.05 для U-статистики, в зависимости от размера выборок

Пустые клетки в таблице с уровнем значимости 0.05 свидетельствуют о том, что для 3x3 нет критических значений. Почему? 

А давайте увидим это! Возьмём из генеральной совокупности выборки X, Y, подсчитаем U-статистики (X,Y), (Y,X), выберем наименьшую, повторим 100 500 раз, отобразим гистограмму, выборки по 3 элемента в каждой (3 по 3, 3x3):

Вероятность получить 0 для 3x3 = 0.1055, то есть у нас не просто нет значений для альфа = 0.05, но и даже для альфы = 0.1!

Hidden text

А вот у t-test’a такого затруднения нет, можно хоть 2x2. Другое дело, что, - упрощенное требование, - генеральные таких выборок должны быть нормальными (напомним следствия, более строгие условия: нормальные средние, выборочные дисперсии распределены согласно Хи-Квадрату), иначе нельзя оперировать t-распределением для оценки дельты выборочных средних.

Возможные значения самой статистики

Рассмотрим три предельных примера:

  1. Все элементы А < B. Тогда сумма статистики будет наименьшей у А и равна 0: стат. значимо.

  2. Все элементы А > B. Тогда сумма статистики будет наименьшей у B и равна 0: стат. значимо.

  3. Все элементы A примерно равны B, попеременно будет 0, 1, редко ½. Сумма статистики будет не стат. значимой.

Тогда согласно пункту 3 распределение нулевой гипотезы складывается из того, что А и B равны “по сути”. Не в рамках среднего или медианы, а в рамках взаимного расположения элементов. То есть элементы выборки А распределены аналогично элементам выборки В. Иными словами, равны их распределения. Это и есть нулевая гипотеза этого теста.

Альтернативной гипотезой тогда было бы утверждение: распределения выборок не равны.

Можно дать и немного другую интерпретацию для теста: раз результатом нашего попарного сравнения элементов А против элементов B даётся маркер старшинства (0, ½, 1), тогда логично заключить, что нулевая гипотеза – это про то, что “структура старшинства в группе А равна B”. 

Правило совпадения вероятностей: Probabilistic Index

Что ещё мы могли бы оценить, раз у нас есть операция сравнения? Например, две вероятности:

  • Вероятность №1, что случайный элемент из X больше случайного элемента из Y.

  • Вероятность №2, что случайный элемент из X больше случайного элемента из Y.

Рассмотрим пример:

  • X = [1, 2, 3]

  • Y = [1, 2, 3]

Если мы попарно сравним элементы из X с Y, то вероятность P(X > Y) = ⅓.

Значение A

Подсчет

Результат

1

(1 > 1) ?

0

1

(1 > 2) ?

0

1

(1 > 3) ?

0

2

(2 > 1) ?

1

2

(2 > 2) ?

0

2

(2 > 3) ?

0

3

(3 > 1) ?

1

3

(3 > 2) ?

1

3

(3 > 3) ?

0

3/9 = 1/3

Такую же вероятность мы получим из сравнения Y c X. Итого для одинаковых по сути выборок P(X > Y) = P(Y > X).

Интересно то, что в той же английской Wiki эта и есть та нулевая гипотеза, которую проверяет Mann-Whitney:

is a nonparametric test of the null hypothesis that, for randomly selected values X and Y from two populations, the probability of X being greater than Y is equal to the probability of Y being greater than X

или, если перевести:

непараметрический тест, который проверяет нулевую гипотезу, что для случайно выбранных измерений “x” и “y” из двух популяции (X и Y), вероятность, что “x” будет больше “y” равна вероятности, что “y” будет больше “x”

P(X > Y) = P(Y > X)

Однако с этим сложно согласиться – такое определение не следует из определения статистики напрямую: оно в принципе не берёт в расчёт вероятность, она больше оперирует именно взаимным расположением элементов выборок.

Гипотеза P(X > Y) = P(Y > X) характерна для такого подхода в статистике как Probabilistic Index. При этом в нём неожиданным образом есть место для статистики Mann-Whitney. Рассмотрим, где они пересекаются.

Probabilistic Index

Рассмотрим случай для непрерывных величин X = [1.0, 2.0, 3.0], Y = [1.2, 2.4, 3.2]. В силу непрерывности измерений P(X = Y) – практически невозможный исход.

Значит, у нас есть только вероятность:

  • P(X > Y) = n,

следствие:

  • P(Y > X) = 1-n 

Возможные варианты для вероятности P(Y > X):

  • P(Y > X) = 0.5

  • P(Y > X) > 0.5

  • P(Y < X) < 0.5

А сама эта P(Y > X) и есть Probabilistic Index (PI): вероятность, что измерение (=отклик от воздействия) объекта из Y будет больше, чем объекта из X.

Алгоритм подсчёта оценки PI

1) Попарно сравниванием все элементы из Y c X:

  • Множество всех пар – это n_X * n_Y = прямое/декартово произведение (Cross Join).

2) Считаем, сколько раз элементы Y были лучше, чем X.

3) Делим п.2 на кол-во всех пар = P(Y > X).

Пример:

  • X = [1.0, 2,0, 3,0]

  • Y = [1.2, 2.4, 3.2]

Значение (Y)

Подсчет

Результат

1.2

(1.2 > 1.0) ?

1

1.2

(1.2 > 2.0) ?

0

1.2

(1.2 > 3.0) ?

0

2.4

(2.4 > 1.0) ?

1

2.4

(2.4 > 2.0) ?

1

2.4

(2.4 > 3.0) ?

0

3.2

(3.2 > 1.0) ?

1

3.2

(3.2 > 2.0) ?

1

3.2

(3.2 > 3.0) ?

1

Probabilistic Index Y > X = 

6/9 = ⅔ = 0.66

При этом распределение нулевой гипотезы – P(X>Y) = P(Y>X) – которое мы по классике получаем, беря по 100500 раз выборки из одной генеральной, считая этот индекс, будет таким:

Mann-Whitney и Probabilistic Index

U_{XY} = \sum_{i=1}^{n}\sum_{j=1}^{m}S(X_{i},Y_{j})S(X,Y)=\begin{cases}  & \text{1,   }  X_{i} > Y_{j} \\  & \text{0,   }  X_{i} < Y_{j} \\ \end{cases}\displaystyle
  • Случай равенства измерения из X с измерением из Y практически исключается в силу непрерывности: этот исход маловероятен. Даже при его наступлении мы, по сути, ничего не внесём нового в логику Prob. Score.

  • Согласно определению, у U-статистики есть следующее свойство: сумма всех единиц и нулей – это все пары, когда X > Y, то есть точно как в п. 2 из Алгоритма Prob. Index.

Когда мы попарно сравниваем все элементы из X c Y, у нас автоматически есть всё множество пар. И поделив сумму единиц и нулей на всё множество пар, мы и получим PI (X > Y)

 PI(X>Y)=\frac{1}{n_{x}*n_{y}}\sum_{i=1}^{n}\sum_{j=1}^{m}S(X_{i}, Y_{j})=\frac{1}{n_{x}*n_{y}}*U_{XY}

Отсюда определение из английской Wiki относится только к Probabilistic Index. Умножив обе части уравнения на n_X и n_Y, мы получим:

U_{XY}=PI(X>Y)*n_{x}*n_{y}

Таким образом, необходимое уточнение определения Mann-Whitney из Wiki такое: Mann-Whitney – это непараметрический тест, который проверяет нулевую гипотезу, что для случайно выбранных измерений “x” и “y” из X и Y, вероятность, что “x” будет больше “y” равна вероятности, что “y” будет больше “x”, с учётом всех возможных пар. При этом, чем меньше выборки равны между собой, тем выше, - внимательно, -  P(X > Y) или P(X < Y) попасть на край распределения нулевой гипотезы для PI с учётом размера выборки, для примера края обозначены красным:

То есть по сути, как и Mann-Whitney, так и PI смотрят на распределения как таковые: тест проверяет неоднородность, а PI смотрит на вероятность, которая будет сильно отличаться от 0.5 при смещённости одной выборки относительно другой, и находится где-то на краю распределения. 

Ещё раз обратим внимание, что при том, что PI и статистика Mann-Whitney взаимосвязаны, используются они по-разному. В Mann-Whitney мы выбираем наименьшую U-статистику и сверяем её значение с распределением нулевой гипотезы, а для PI мы сравниванием статистики между собой.

Effect Size Mann-Whitney

Effect size – магнитуда (величина) разницы между A и B. Алгоритм подсчёта эффекта простой и, по сути, это и есть Probabilistic Index. 

Пример подсчёта:

  • X = [1, 2, 4]

  • Y = [3, 5, 6]

Попарное сравнение

(3 > 1) ? = 1

(3 > 2) ? = 1

(3 > 4) ? = 0

(5 > 1) ? = 1

(5 > 2) ? = 1

(5 > 4) ? = 1

(6 > 1) ? = 1

(6 > 2) ? = 1

(6 > 4) ? = 1

8/9 = 0.88

В 88% случаев B > A. Очевидная проблема такого effect size в том, что её сложно перевести в “деньги”. Та же дельта t-test’a легко даёт понимание прироста, скажем, среднего чека. А вот с вероятностью B > A сложнее: выше в 88% случаев средний чек и лучше, а насколько лучше в деньгах – понимания нет.

И, кажется, от этого можно отмахнуться, но бизнесу хочется конкретики: “сколько будет составлять прирост в деньгах”. Отсюда уже одно это может быть веским возражением против Mann-Whitney, когда вполне допустимо использовать t-test. Простой вероятности недостаточно.

Часть 2. Mann-Whitney: история превращения в интерпретируемое “нечто”

Сейчас в учебниках даётся чаще всего совершенно иная формула для подсчёта теста.

U_{1} = \frac{n_{1}*(n_{1}+1)}{2} + n_{1}*n_{2} - R_{1}

Давайте разберёмся, как она получилась, начиная с конца правой стороны уравнения – с R1, ранга.

Ранг

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

R(X_{j}) = \sum_{i=1, j \neq  i}^{m}S(X_{j}, X_{i}) + 1 S(X_{j}, X_{i}) =\begin{cases}  & \text{1,   }  X_{i} > X_{j} \\   & \text{1/2, }  X_{i} = X_{j} \\  & \text{0,   }  X_{i} < X_{j} \\ \end{cases}

Формула нам уже знакомая.

Приведём пару примеров:

  • Выборка [1, 3] – значения отсюда и далее считать непрерывными, целочисленная запись нужна для облегчения примера подсчёта (читаемости):

Значение

Подсчет

Ранг

1

(1 > 3) ? = 0

0+1 = 1

1

3

(3 > 1) ? = 1

1+1 = 2

2

  • Выборка [1, 1]

Значение

Подсчет

Ранг

1

(1 > 1) ? = 1/2

1/2+1 = 1.5

1.5

1

(1 > 1) ? = 1/2

1/2+1 = 1.5

1.5

Ранг двух выборок

В случае двух выборок ранг элемента подсчитывается в объединённой выборке. Рассмотрим формулу для ранга элемента из выборки Y:

R_{u}(Y_{j}) = \sum_{i=1}^{m}S(Y_{j}, X_{i}) + \sum_{i=1, j \neq  i}^{n}S(Y_{j}, Y_{i}) + 1

То есть мы просто рассчитываем расположение элемента, но сначала в выборке Y, а затем в выборке X, и получаем итоговый порядковый номер в объединённой выборке.

Формула теперь просто учитывает две выборки и не более.

Разберём на примере. Пускай у нас будут две выборки: 

  • X = [1.0, 3.0, 5.0, 7.0]

  • Y = [2.5, 4.5, 6.5, 8.5]

 X = [1.0, 3.0, 5.0, 7.0]

Значение

Подсчет

Ранг

1.0

(1.0 > 2.5) ? = 0

(1.0 > 4.5) ? = 0

(1.0 > 6.5) ? = 0

(1.0 > 8.5) ? = 0

(1.0 > 3.0) ? = 0

(1.0 > 5.0) ? = 0

(1.0 > 7.0) ? = 0

0+0+0+0+0+0+0+1 = 1

1

3.0

(3.0 > 2.5) ? = 1

(3.0 > 4.5) ? = 0

(3.0 > 6.5) ? = 0

(3.0 > 8.5) ? = 0

(3.0 > 1.0) ? = 1

(3.0 > 5.0) ? = 0

(3.0 > 7.0) ? = 0

1+0+0+1+0+1 = 3

3

5.0

(5.0 > 2.5) ? = 1

(5.0 > 4.5) ? = 1

(5.0 > 6.5) ? = 0

(5.0 > 8.5) ? = 0

(5.0 > 1.0) ? = 1

(5.0 > 3.0) ? = 1

(5.0 > 7.0) ? = 0

1+1+0+0+1+1+0+1 = 5

5

7.0

(7.0 > 2.5) ? = 1

(7.0 > 4.5) ? = 1

(7.0 > 6.5) ? = 1

(7.0 > 8.5) ? = 0

(7.0 > 1.0) ? = 1

(7.0 > 3.0) ? = 1

(7.0 > 5.0) ? = 1

1+1+1+0+1+1+1+1 = 7

7

Y = [2.5, 4.5, 6.5, 8.5]

Значение

Подсчет

Ранг

2.5

(2.5 > 1.0) ? = 1

(2.5 > 3.0) ? = 0

(2.5 > 5.0) ? = 0

(2.5 > 7.0) ? = 0

(2.5 > 4.5) ? = 0

(2.5 > 6.5) ? = 0

(2.5 > 8.5) ? = 0

1+0+0+0+0+0+0+1 = 2

2

4.5

(4.5 > 1.0) ? = 1

(4.5 > 3.0) ? = 1

(4.5 > 5.0) ? = 0

(4.5 > 7.0) ? = 0

(4.5 > 2.5) ? = 1

(4.5 > 6.5) ? = 0

(4.5 > 8.5) ? = 0

1+1+0+0+1+0+0+1 = 4

4

6.5

(6.5 > 1.0) ? = 1

(6.5 > 3.0) ? = 1

(6.5 > 5.0) ? = 1

(6.5 > 7.0) ? = 0

(6.5 > 2.5) ? = 1

(6.5 > 4.5) ? = 1

(6.5 > 8.5) ? = 0

1+1+1+1+1+1 = 6

6

8.5

(8.5 > 1.0) ? = 1

(8.5 > 3.0) ? = 1

(8.5 > 5.0) ? = 1

(8.5 > 7.0) ? = 1

(8.5 > 2.5) ? = 1

(8.5 > 4.5) ? = 1

(8.5 > 6.5) ? = 1

1+1+1+1+1+1+1+1 = 8

8

  1. Легко увидеть, что элементы примерно равных групп выстроены в ряд раз через раз – вместе в отсортированном виде = [1.0, 2.5, 3.0, 4.5, 5.0, 6.5, 7.0, 8.5].

При этом ранги: [1, 2, 3, 4, 5, 6, 7, 8]

В пределе у одинаковых групп у всех элементов один и тот же ранг.

  1. Если бы Y была строго больше X:

тут нам придётся увеличить размер групп и для простоты примера взять немного другие значения:

X = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]

Y = [7.0, 8.0, 9.0, 10.0, 11.0, 12.0]

Все значения B были бы справа: 

[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0].

Ранги = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] – тогда сумма рангов (наименьшая – 1+2+3+4+5+6) будет по тесту Wilcoxon стат. значимой (альфа = 0.05).

Приведены радикальные случаи, чтобы “на глаз” вы могли увидеть, есть ли разница между X и Y или нет.

А причём тут Wilcoxon и ранги? W-Статистика Wilcoxon’a

Mann-Whitney часто называют ещё как Wilcoxon-Mann-Whitney тестом. Это неспроста.

Hidden text
U_{Y, X} =  R_2 - \frac{n_2(n_2+1)}{2} = n_1n_2+\frac{n_1(n_1+1)}{2} - R_1

R_1, например, это и есть ни что иное как статистика Wilcoxon’a:

W_{1} = \sum_{i=1}^{n}R(Y_{i})

У нас по каждой выборке получается две суммы рангов, и также, как в случае c U статистикой, мы выбираем меньшее значение:

W = min(W_{1}, W_{2})

Согласитесь, она на самом деле очень похожа U-статистику: 

  1. ранг элемента определяется близкому к U-статистике правилу (да в целом, ранг результат сравнения);

  2. из двух сумм рангов выбирается наименьшая, как и в случае с U-статистикой.

H0 гипотеза Wilcoxon-Mann-Whitney в рамках рангов следующая:

X и Y распределены одинаково.

Последнее, но самое важное: именно этот тест – Wilcoxon – про сумму рангов, а Mann-Whitney в ином формульном представлении может задействовать сумму для подсчёта U-статистики! Вот и давайте посмотрим, как именно Mann-Whitney это делает.

Вывод классической формулы, испортившей интерпретацию

Итак, у нас есть всё необходимое для вывода формулы, далее нужно запастись терпением. У нас есть четыре разобранные формулы:

(1) \space U_{XY} = \sum_{i=1}^{n}\sum_{j=1}^{m}S(X_{i},Y_{j})(2) \space R(Y_{j}) = \sum_{i=1, j \neq i}^{m}S(Y_{i}, Y_{j}) + 1(3) \space R_{u}(Y_{j}) = \sum_{i=1}^{m}S(Y_{j}, X_{i}) + \sum_{i=1, j \neq  i}^{n}S(Y_{j}, Y_{i}) + 1

Ru (Yj) - - это ранг элемента j из Y при объединении выборок X и Y

(4) \space W = min(\sum_{j=1}^{n}R_{u}(X_{j}), \sum_{j=1}^{m}R_{u}(Y_{j}))

Пусть:

 \sum_{j=1}^{n}R_{u}(X_{j}) > \sum_{j=1}^{m}R_{u}(Y_{j})

Тогда перепишем (4)

(4) \space W = min(\sum_{j=1}^{n}R_{u}(X_{j}), \sum_{j=1}^{m}R_{u}(Y_{j})) = \sum_{j=1}^{m}R_{u}(Y_{j})(5) \space W_{Y} = \sum_{j=1}^{m}R_{u}(Y_{j})

Поставим в (5) формулу (3)

(5) \space W_Y = \sum_{j=1}^{m}R_{u}(Y_{j}) = \sum_{j=1}^{m}( \sum_{i=1}^{n}S(Y_{j}, X_{i}) + \sum_{i=1, j \neq  i}^{m}S(Y_{j}, Y_{i}) + 1) == \sum_{j=1}^{m} \sum_{i=1}^{n}S(Y_{j}, X_{i}) + \sum_{j=1}^{m}(\sum_{i=1, j \neq  i}^{m}S(Y_{j}, Y_{i}) + 1)

Обратим внимание, что:

\sum_{j=1}^{m} \sum_{i=1}^{n}S(Y_{j}, X _{i})=U_{Y, X}

Тогда (5) перепишем как:

(5) \space W_{y} =U_{Y, X} + \sum_{j=1}^{m}(\sum_{i=1, j \neq  i}^{m}S(Y_{j}, Y_{i}) + 1)

Далее смотрим на оставшееся выражение в скобках, потом смотрим на (2) - тоже самое! Переписываем (5)

(5) \space W_{Y}= U_{Y, X} + \sum_{j=1}^{m}R_{y}(Y_{j})

Осталось разобраться с суммой рангов по Yi:

(6) \space \sum_{j=1}^{m}R_{y}(Y_{j})

Пример, выборка = [1, 2]

Значение

Подсчет

Ранг

1

(1 > 2) ? = 0

0 + 1 = 1

1

2

(2 > 1) ? = 1

1 + 1 = 2

2

Дальше нам нужно суммировать 1 и 2.

У нас же выборка от 1 до n. Если вспомнить начала высшей математики, то несложно вывести, что сумма рангов выборки с элементами от 1 до n равна:

Hidden text

Один из вариантов доказательств, смотреть можно без звука

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

1 + 2 + … + n - 1 + n = \frac{n(n+1)}{2}\sum_{j=1}^{m}R_{y}(Y_{j}) = \frac{n(n+1)}{2}

Поставляем (6) в (5):

(7) \space W_{Y}= U_{Y, X} +  \frac{n(n+1)}{2}

Выразим U

(8) \space U_{Y, X} = \space W_{Y}-\frac{n(n+1)}{2}

Мы почти у цели, еще чуть-чуть. Рассмотрим объединение U выборок X и Y, тогда кол-во элементов в U:

N = n_{1}+n_{2}n_{1} = X, n_{2} = Y(8) \space \sum_{i=1}R_{u}(U_{i}) = \frac{N(N+1)}{2}

При этом (8) еще и:

 (8) \space \sum_{i=1}^{N}R_{u}(U_{i}) =  \sum_{j=1}^{n}R_{u}(X_{j}) + \sum_{j=1}^{m}R_{u}(Y_{j}) = R_{u}(X) + R_{u}(Y)

Пусть:

R_{u}(X), R_{u}(Y) = R_{1}, R_{2}

Тогда:

R_{1} + R_{2} = \frac{N(N+1)}{2} = \frac{(n_1+n_2)(n_1+n_2+1)}{2} ==\frac{n_1^2+n_1n_2+n_1+n_1n_2+n_2^2+n_2}{2}==\frac{n_1^2+n_1+2n_1n_2+n_2^2+n_2}{2}==\frac{n_1(n_1+1)}{2}+n_1n_2+\frac{n_2(n_2+1)}{2}

Тогда, проделав пару перестановок:

(9) R_2 - \frac{n_2(n_2+1)}{2} = n_1n_2+\frac{n_1(n_1+1)}{2} - R_1

Отметим про (5) следующее:

(5) \space W_{Y} = \sum_{j=1}^{m}R_{u}(Y_{j}) = R_2

И вернемся к (8) и подставим (5):

(8) \space U_{Y, X} = \space W_{Y}-\frac{n(n+1)}{2}(10) \space U_{Y, X} = R_2 - \frac{n_2(n_2+1)}{2}

Вспоминаем про (9):

U_{Y, X} =  R_2 - \frac{n_2(n_2+1)}{2} = n_1n_2+\frac{n_1(n_1+1)}{2} - R_1

Готово! Обратите внимание: хоть мы теперь и можем подсчитать U-статистику, используя ранги, но формула использует не только их – отсюда утверждение, что эта сумма рангов ошибочна. Сумма рангов – это именно W-Статистика Wilcoxon’a. Здесь же, как и в случае с PI, это сумма рангов с учётом размера выборок.

Вероятный смысл считать через ранги

Предположительный ответ на вопрос “А зачем?” такой: так проще и быстрее считать. Если вы попробуете от руки подсчитать результат теста и тем, и другим способом, вы обнаружите, что через ранги это делается эффективнее: скорость больше и меньше вероятность запутаться, сравнивая поэлементно измерения. И обычно то, что эффективнее, и приживается в массах. Но в случае с Mann-Whitney это сыграло с ним злую шутку в виде сложности интерпретации в контексте суммы рангов.

Другими словами, предлагаемая в ряде учебников формула, это более простой способ считать U-статистику, но весь смысл этой статистики лежит именно в этой формуле, которая в свою очередь связана с набор правил сравнения:

U_{XY} = \sum_{i=1}^{n}\sum_{j=1}^{m}S(X_{i},Y_{j})S(X,Y)=\begin{cases}  & \text{1,   }  X_{i} > Y_{j} \\   &  {\text{1/2, }  X_{i} = Y_{j}} \\  & \text{0,   }  X_{i} < Y_{j} \\ \end{cases}\displaystyle

Часть 3. Mann-Whitney не бесполезен

Разбор двух кейсов

Теперь, разобравшись с тем, как конкретно работает Mann-Whitney, как он сравнивает выборки, поговорим о критике в его адрес. Год назад компания Авито выпустила классную статью “Mann-Whitney – самый главный враг A/B” (Дима, пиши еще).

Я согласен с рядом утверждений, таких как:

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

  • Он не может быть заменой t-test для проверки поставленной нулевой гипотезы о равенстве средних (что, согласно содержанию самой U-статистики, так и есть.)

  • Если есть более подходящий тест, то не надо применять этот. 

Отдельно вынесу справедливое утверждение: “Мы не знаем, как именно повлиял наш эффект”.

Но все-таки дам несколько комментариев по двум кейсам из этой статьи.

1. Пример с двумя равномерными выборками:

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

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

“Про эти два распределения мы знаем, что у них равны средние и медианы, и что они симметричны относительно 0. Кроме того, вероятность, что сгенерированное значение в первой выборке будет больше значения во второй выборке, равна 1/2.”

Анализ отклонения поставленной нулевой гипотезы P(Treatment > Control) = ½ верен: Mann-Whitney ошибается чаще, чем установленный порог. Но при этом важно отметить, что сам кейс подобран такой, где при равенстве двух параметров и одного свойства двух распределений, он (кейс) попадает в исключение из ситуаций. Разная дисперсия, а, как следствие, и разные распределения при той же одинаковой медиане часто дают такой результат.

Также отмечено про то, что тест в рамках данного кейса больше подходит для обнаружения неоднородности распределений, а в кейсе распределения не равны. То есть Mann-Whitney-то видит, что что-то не так на входе, давая стат. значимый результат в 11% случаев против 4.1% у t-test’a. Эти значения не только ложноположительные для поставленной гипотезы P(T > C) = ½, но и валидные для обнаружения чистого различия, “расположение элементов выборок относительно друг друга”, тут это различие в распределении Mann-Whitney и схватывает.

То есть различие есть, его не видит t-test, но видит Mann-Whitney с той оговоркой, что делает он это не очень хорошо только в 11% случаев, но видит, тем не менее. В целом, перед тем, как сравнивать результаты теста, стоит смотреть на распределения: визуальный анализ уже может натолкнуть на мысль сравнить средние. Если нас интересует только распределение средних, то и не надо выбирать тесты, которые сравнивают распределения данных. 

При этом, учитывая, что “мы не знаем, как именно повлиял наш эффект”, то использование Mann-Whitney даже в приведённом кейсе имеет место для попытки обнаружить эффект как таковой в двух случаях: 

  1. Когда мы хотим проверить две нулевые гипотезы сразу в рамках маленьких выборок, когда известно, что они взяты из нормального распределения. Ничто, кроме вероятности допустить ошибку 1-го рода хотя бы 1 раз (9.75%), не мешает вам в тесте проверить две нулевые гипотезы:

  • H0_1: “Среднее выборки А не отличается от среднего выборки B”.

  • H0_2: “Выборка А и выборка B имеют одинаковые распределения”.

При больших выборках, как и сказано в статье Авито, для H0_2 лучше использовать критерий Колмогорова-Смирнова. На малых выборках ему трудновато (малые – это в районе и 1000 в том числе).

  1. И особый случай: когда взяты малые выборки А и B, а мы не уверены ни в нормальности распределения средних, ни в том, что выборочные дисперсии распределены согласно закону Хи-Квадрат (такое определенно точно под силу малым выборкам из нормального распределения), а потому проведённый t-test возможно не будет приземлять полученную статистику на распределение Стьюдента. Единственное, несколько замечаний:

  • Ещё раз: вы будете проверять совершенно иную гипотезу в отличие от t-test’a! 

  • Пороги для значимости: Mann-Whitney не любит совсем малые выборки: в сумме n двух выборок для альфы = 0,05 (см. таблицу крит. значений для этого теста выше) должно быть >= 8.

Первый случай в силу больших данных как будто не актуален: мы будем использовать Колмогорова-Смирнова. 

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

И всё же: если это всё из разряда исключений, то какой смысл использовать Mann-Whitney как базиса для своей A/B-платформы? Для ответа на этот вопрос нам надо сначала разобрать причину неудачи этого теста во 2-ом кейсе про выручку ребят из Авито:


2. Пример с кейсом выдачи скидки

“Сразу зафиксируем проверяемую гипотезу о равенстве средних: H0 — средний чек не изменился или упал vs. H1 — средний чек вырос.”

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

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

    • В тесте стало 55% платящих пользователей (45% нулей - прим.).

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

Количество платящих пользователей увеличилось, но ARPPU упал. Наша ключевая метрика — выручка.”

Этот кейс не так прост, как кажется. Стоит сразу отметить, что поставленную гипотезу Mann-Whitney в принципе не способен проверить, а потому фраза “может ошибаться в этой задаче” не совсем корректна. Подсчёт симуляции не вызвал вопросов, Mann-Whitney действительно даёт больше ложноположительных прокрасов, но именно в рамках заданной гипотезы. При этом если гипотезу переформулировать про однородность, тогда всё будет в порядке.

Интересен вопрос: почему тест Mann-Whitney показывает то, что показывает? Ответ лежит в нулях при экспоненциальном распределении: 

  1. Cлишком большое повторяющее значений с обеих сторон неизбежно в формуле дают слишком частое ½ в ряде сравнений, что естественным образом уже приближает U-статистику к середине распределения.

  1. В контроле было 50% нулей против 45% у теста из-за CR в покупку. То есть согласно определению U-статистики, у контроля часто сравнение давало 0, статистика ещё больше смещается – с середины на его левый край.  До значимости рукой подать.

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

{\text{1/2, }  X_{i} = Y_{j}}

Модифицируем пример, где контроль и тестовая группа были уже взяты из соответствующих из популяций: контроль из популяции со средним 3.5, а тестовая группа – со средним 3.3. Так как мы делаем это случайно, то ноль должен браться с той же вероятностью, что и прочие значения.

Hidden text
# Подключим библиотеки
import scipy.stats as sps
from tqdm.notebook import tqdm # tqdm – библиотека для визуализации прогресса в цикле
from statsmodels.stats.proportion import proportion_confint
import numpy as np

#H0 и HA
pop_sz = 1000000
exp_3_3 = sps.expon(loc=0, scale=3.3).rvs(pop_sz) #3.3
exp_3_5 = sps.expon(loc=0, scale=3.5).rvs(pop_sz) #3.5

# Заводим счетчики количества отвергнутых гипотез для Манна-Уитни и для t-test
mann_bad_cnt = 0
ttest_bad_cnt = 0
sz = 10000

for i in tqdm(range(sz)):

    #берем выборки
    test = np.random.choice(exp_3_3, size=1000)
    control = np.random.choice(exp_3_5, size=1000)

    # Проверяем гипотезу
    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-Whitney 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-Whitney LESS power: 0.0031, [0.0022, 0.0044]

  • T-test LESS power: 0.0016, [0.001, 0.0026]

При этом вполне справедливо замечание, что мы не можем не учитывать эти нули: воздействие на этого пользователя было? Было. Он решил не платить, его измерение равно нулю.

Можно ли это преодолеть и исправить ситуацию? Можно. 

Varioqub. Что под капотом

Своё решение предложил Яндекс через модуль Varioqub – это не просто инструмент для А/B тестирования: помимо сплитования и подсчёта результатов по заданным метрикам это ещё и remote config, который в потенциале позволяет персонализировать приложение для каждого сегмента пользователей. 

Перед подсчётом результатов с применением Mann-Whitney Varioqub осуществляет бакетизацию выборок. Что это такое?

Бакетизация

Пользователей выборок А и B распределяют по бакетам (от англ. “по корзинам”), подгруппам: Varioqub, согласно данным из конференции Aha ‘23 использует от 20 до 100 бакетов для каждой группы. Бакеты можно сделать, например, через встроенную функцию Python -  hash:

  1. Задаём желаемое количество подгрупп, бакетов, допустим, 100.

  2. Каждый userid пользователя получает хэш через hash(userid): 

  • у userid = ‘111’

  • hash(111) = ‘-7842065448116747663’

  1. Применяем операцию “остаток от деления”, %: (-7842065448116747663)%100 = 37 – это и есть бакет для userid ‘111’

  2. По каждому бакету считается интересующая нас метрика: для CTR, например, среднее CTR по бакету.

Если данные независимы, то у нас получается распределение средних А и B на базе бакетов, которые можно сравнить через тест.

Шаг с 1 по 3:

Шаг 4:

Визуализация бакетов:

Эффекты бакетизации

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

Экономия места

Память не резиновая и такое преобразование, когда у вас A/B тесты на промышленной основе, позволяет экономить ресурсы как в рамках подсчёта данных, так и в рамках хранения результатов: вместо данных по А и B группам – данные по бакетам.

Корректировка данных для теста

В кейсе №2 от Авито мы можем перейти от разных распределений, где есть не только повторы значений, но и где нулей у контроля больше, чем у тестовой группы к распределению средних согласно Центральной Предельной теореме. Если чуть переформулировать её под бакетизацию, то средние значения бакетов из любого распределения, даже если оно ненормальное, будут иметь приблизительно нормальное распределение при достаточно большом объёме выборки. И приближение к нормальному распределению будет более точным с увеличением размера выборки.

Обратите внимание: повторяющиеся нули почти убраны!

Теперь при симуляции у Mann-Whitney процент ложноположительных срабатываний при том же размере выборок (1000) и количестве бакетов в 100 значительно упал, но по-прежнему выше заданного предела:

Hidden text
#модификат кейса: бакетизация
import random
import pandas as pd
import scipy.stats as sps
from tqdm import tqdm
from statsmodels.stats.proportion import proportion_confint
import warnings
warnings.filterwarnings("ignore")

#Измени меня
a_b_size = 1000
buckets = 50

mann_bad_cnt = 0
ttest_bad_cnt = 0

for i in tqdm(range(int(a_b_size/2))):
  start_id = 10000

  userid = random.sample(range(start_id, start_id+a_b_size), a_b_size)
  userid = [str(i) for i in userid]

  data = {'userid': userid}

  userid = pd.DataFrame(data)
  l = len(userid.index) // 2
  userid.loc[:l - 1, 'group'] = 'A'
  userid.loc[l:, 'group'] = 'B'

  control = userid[userid['group']=='A']
  test = userid[userid['group']=='B']

  sample_size = int(a_b_size/2)
  #H0 и #HA
  test_zero_array = sps.bernoulli(p=0.55).rvs(sample_size)
  control_zero_array = sps.bernoulli(p=0.5).rvs(sample_size)
  test['value'] = sps.expon(loc=0, scale=6).rvs(sample_size) * test_zero_array # ET = 3.3
  control['value'] = sps.expon(loc=0, scale=7).rvs(sample_size) * control_zero_array # EC = 3.5

  #бакетизация
  test['bucket'] = pd.util.hash_pandas_object(test['userid'])%buckets
  control['bucket'] = pd.util.hash_pandas_object(control['userid'])%buckets

  #средние бакетов
  test['mean'] = test.groupby('bucket')['value'].transform('mean');
  control['mean'] = control.groupby('bucket')['value'].transform('mean');

  #тесты
  mann_pvalue = sps.mannwhitneyu(control['mean'], test['mean'], alternative='less').pvalue
  ttest_pvalue = sps.ttest_ind(control['mean'], test['mean'], 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 = int(a_b_size/2), alpha=0.05, method='wilson')
left_ttest_power, right_ttest_power = proportion_confint(count = ttest_bad_cnt, nobs = int(a_b_size/2), alpha=0.05, method='wilson')
# Выводим результаты
print(f"Mann-Whitney LESS power: {round(mann_bad_cnt / int(a_b_size/2), 4)}, [{round(left_mann_power, 4)}, {round(right_mann_power, 4)}]")
print(f"T-test LESS power: {round(ttest_bad_cnt / int(a_b_size/2), 4)}, [{round(left_ttest_power, 4)}, {round(right_ttest_power, 4)}]")

  • Mann-Whitney LESS power: 0.137, [0.1171, 0.1597]

При этом если размер выборок увеличивается (например, до 2000), то даже при 50 бакетах ложноположительных результатов стало не более заданного уровня альфы (но t-test всё равно робастнее, да).

  • Mann-Whitney LESS power: 0.0043, [0.032, 0.0058]

  • T-test LESS power: 0.0036, [0.0028, 0.0052]

Часто, но не всегда – нормализация данных

Нормальность тут не важна для теста, она важна как раз для возможности наглядно увидеть, о чём спрашивало Авито:

“Тестируемая фича полностью сдвигает выборку на некий коэффициент theta или масштабирует выборку на некий параметр theta (theta > 0)”.

Hidden text

Вообще, бакетизация повторяет Центральную Предельную Теорему: предел распределения бакетизированных средних/сумм - нормальное распределение.

Вот таким образом вполне наглядно мы будем более уверены, что у нас именно такой эффект от воздействия, что, как следствие, делает “критерий Манна-Уитни применимым, и он будет верно оценивать направление сдвига математического ожидания”.

При этом она характеризует центральную тенденцию: смещение центра как такового. В рамках нормальности данных сдвиг или его отсутствие хорошо видно!

Возможное возражение состоит в том, что: здесь же сдвиг не выборки, а среднее бакетизированных средних!  Поэтому в этом виде идёт проверка сдвига центральной тенденции.

Почему не t-test после бакетизации

Как мы и разобрали, бакетизированный Mann-Whitney в Кейсе №1 становится куда как чувствительнее, при этом на порядок работая лучше своей небакетной версии в Кейсе 2. Отсюда вместо двух тестов – один на однородность, другой на смещение центральной тенденции, то есть у нас получается один тест на оба случая (хоть и для Кейса 2 t-test робастнее).

При этом, как и говорились в параграфе про Effect Size, отсутствие интервала для дельты – не очень хорошо для поставки результатов бизнесу, это можно будет преодолеть через сырые данные по тестам из этого инструмента. Насколько мне известно, это будет возможным. Это же даст возможность подсчитать тесты так, как каждый считает нужным, используя Varioqub скорее как сплитовалку и remote config.

Varioqub. UI

Если говорить о UI Varioqub, - который что в AppMetrica, что в Я.Метрика однородны, - то предусмотрены стандартные шаги: гибкая настройка таргетинга, флаги для версии A/B/…, выбор метрик (до 10 штук), запуск. Классическая последовательность без неожиданностей. Единственное, о чем следует дать несколько комментариев, это текущие возможности по метрикам и калькулятору размера выборки

Метрики

На данный момент (весна 2024) можно собирать конверсии из названия событий и параметров. Единственное текущее ограничение это выбор нескольких параметров одного и того же уровня. То есть если ваше событие описывается полностью через параметры на первом уровне вложенности, то создать конверсию не выйдет. Например, схема события на красную кнопку на экране главная такая:

  • Event_name: tap

    • object: button

    • button_name: red_button

    • screen_name: main

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

Калькулятор

Он находится сейчас справа вверху UI. Обычно, доступный для теста размер выборки за период, как и сам период, известны, нужно подсчитать MDE, что калькулятор делает, тут все ок. Но возникает два вопроса:

  1. Редко, но все же такое случается, что нам может понадобиться рассчитать именно размер выборки на базе желаемого эффекта (uplift’a). Калькулятор на данный момент такого не может.

  2. На базе какой формулы калькулятор считает MDE?

Ответ на второй вопрос будет решением и первого: используется классическая формула для размера выборки для t-test’a. Да, Varioqub использует Mann-Whitney, но если предположить сдвиг исключительно среднего и подсчитать размер выборки через симуляцию для Mann-Whitney, то этот размер выборки будет приблизительно сходиться к тем, что дает классическая формула. Поэтому либо используйте формулу, либо симуляцию.

Из скриншота вы также можете видеть, что в калькуляторе сейчас нет возможности указать альфу и мощность; в него зашиты классические альфа = 0.05, мощность = 0.8. Отсюда вам поможет либо снова формула, либо симуляция, по-другому пока никак, но у ребят из Varioqub в планах улучшение калькулятора.

Далее мы можем UI оставим в стороне и обратим внимание на ряд других моментов для приложения.

SDK VarioQub для App

Ребята в плане UI не стали придумывать велосипед, и это правильно. Шаги стандартные: гибкая настройка таргетинга, флаги для версии A/B/.., выбор метрик (до 10 штук), запуск. Классическая последовательность без неожиданностей, а потому UI оставим в стороне и осветим ряд других моментов.

Remote config

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

Во-вторых: раньше для A/B надо было отдавать на скачивания два разных приложения через магазин приложения, теперь делать этого не нужно. В рамках remote config можно настраивать флаги-пары "ключ-значение". 

Например, кнопке логина можно дать флаг (ключ) цвет, registration_button_color и обозначить текущий цвет, пускай он будет белым:

  • white

Допустим, дизайнеры сделали альтернативную – красную – и она доступна для загрузки, её значение:

  • red

Тогда если значение флага registration_button_color = white, у всех пользователей будет кнопка белая. Если registration_button_color = red, то красная.

Другой пример: флаг того, что включена какая-то  настройка, например, показ приветственного баннера, welcome_banner: 

  • active 

  • non-active

Если active, то показывается, если non-active – нет.

В рамках A/B вы можете сделать для А флаг welcome_banner = active, для B = non-active. Это называется клиентский опыт – то, что будет у пользователей в контроле/тесте.

При этом можно настраивать сегменты, для которых будет эксперимент:

  • Платформа (Android/iOS)

  • Версия OS

  • Язык телефона

  • Регион

  • Версия приложения 

  • Клиентские параметры (пол, возраст и пр.)

Разный клиентский опыт

Для каждого сегмента пользователей может быть своё представление приложения, особенно актуально это может быть после проведения A/B. Пускай мы проверили два A/B.

  1. В 1-ом мы тестировали цвет кнопки registration_button_color = red, он и победил, но при анализе сегментов по осям увидели, что для iOS red работает значимо хуже.

Поэтому мы сделали так:  для iOS флаг registration_button_color = white, а для Android = red.

  1. Во 2-ом тесте была проверка гипотезы, что появление welcome_banner скажется позитивно. Так и получилось, но казалось, что в рамках сегмента “мужчины” результаты были значимо хуже.

Сделали для женщин welcome_banner = active, для мужчин welcome_banner = non-active.

Итого у нас получается 4 версии приложения, которые будут активны одновременно:

Пол/Платформа

iOS

Android

Ж

registration_button_color = white

welcome_banner = active

registration_button_color = red

welcome_banner = active

M

registration_button_color = white

welcome_banner = non-active

registration_button_color = red

welcome_banner = non-active

Эта идея не нова и сама по себе уже активно развивается как на App, как и на веб.  Но здорово, что Varioqub – это не только A/B, но и конфиги вашего приложения.

Вместо финала

Вот мы и разобрали, надеюсь, всё от А до Я, по Mann-Whitney и то, как он реализован в Varioqub. Также проговорили из-за необходисти про test Wilcoxon и о подходе в статистике – Probabilistic Index.

Тесту Mann-Whitney есть место, но его использование, как и использование всех прочих тестов, должно быть осознанным, как минимум стоит помнить, что он проверяет иную гипотезу, нежели t-test. В случае Varioqub’a Mann-Whitney был выбран для промышленной А/Б-тестилки, исходя из оценки соотношения затрат и результата. 

Мы надеемся, что после нашей статьи вы, как экспериментаторы, будете лучше понимать, как работает под капотом Mann-Whitney и Varioqub в частности, что позволит вам обдумать, насколько качественно взвешиваются результаты ваших тестов, надо ли вам идти в своё решение или достаточно использовать этот модуль.

Благодарности

Это статья появилась на свет благодаря большому количеству человек.

Спасибо большое за тщательное ревью:

- ребятам-аналитикам из X5 Tech: Антону Денисову, Никите Суркову, Николаю Назарову, Дмитрию Чернышеву, Ивану Щербаку, Илье Веккерле и др.

- ребятам из Яндекса, отвечающим за AppMetrica и Varioqub:

“Рады, что наш инструмент вдохновил партнёров на такой глубокий и детальный разбор методов тестирования. Наш большой опыт исследований и экспериментов показывает, как важно выбрать достаточно чувствительный и в то же время максимально устойчивый статистический критерий. Для этого в Varioqub в качестве основного в дополнение к методу Байеса мы используем ещё и тест Mann-Whitney с бакетизацией. Этот тест выиграл многолетнюю конкуренцию с другими методами благодаря своей универсальности и робастности”.

Максим Белоусов
Менеджер Varioqub

- редакторам из X5 Tech и Яндекса – Анастасии Федоровой и Анне Антоновой

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