Всем привет! Меня зовут Дима Лунин, я автор курса по экспериментам в Академии Аналитиков Авито. В текущей статье я хочу "обкатать" материал, который мы рассказываем на нашем курсе, а также поделиться экспертизой по АБ-тестированию с ребятами, которые только начинают свой путь в аналитике, но уже имеют базовые знания в статистике и в проверке статистических гипотез.
Цель статьи — на примере несложного реального кейса полностью разобрать механику A/B-тестирования, объяснить математику и интуицию, которые стоят за каждым шагом при проведении эксперимента. Кроме того, статья снабжена большим количеством кода (все материалы можно найти на гитхабе), который вы сможете потом переиспользовать для себя. Здесь мы затронем только начальные знания по A/B, а про различные продвинутые механики мы поговорим в следующий раз, если статья вам понравится!
Важно: предполагается, что читатель уже хорошо разбирается в том, как проверять различные статистические гипотезы, что такое ошибка первого и второго рода, мощность. Кроме этого надо знать, что такое доверительный интервал, как и в каких случаях работает t-test, а также как валидировать критерий с помощью механики Монте-Карло. Если вы считаете, что у вас тут есть пробелы в знаниях, то в начале советую просмотреть курс по статистике от ААА, лекции 1-5.
Весь текст далее вместе с данными также есть и на гитхабе!
Задача
Представьте, что к вам обратился ваш менеджер продукта с предложением:
Мы изменили описание наших услуг продвижения на сайте и их интерфейс, чтобы сделать их более понятными и привлекательными для пользователей. Мы ожидаем, что это приведет к увеличению спроса на нашу услугу. Как понять, что стало лучше?
Для решения такого типа задач была придумана механика A/B-тестирования. Она состоит из нескольких ключевых шагов:
аудитория сервиса делится на две или более группы: контрольную, в которой не будут вноситься изменения, и тестовую, в которой будет тестироваться новая функция;
собираются данные в каждой из групп в течение некоторого времени;
подводятся итоги теста: показатели в разных группах сравниваются и на их основании определяется, тестируемая гипотеза успешна или нет.
Важно: перечислены не все шаги A/B-тестирования, а лишь ключевые, необходимые для понимания методологии. Далее мы будем их дополнять.
Часть 1. Первый A/B-тест
Первый искусственный A/B-тест.
Рассмотрим теоретический пример, чтобы разобраться, как работает A/B-тест. Определим, какую именно продуктовую гипотезу мы проверяем: рост спроса на услугу продвижения. А зачем это компании? Чтобы увеличить выручку. Допустим, мы хотим оценить рост выручки за неделю.
Когда мы сможем увеличить выручку? В случае, если будет внедрено (или "раскатано") новое изменение, или если его не будет? Чтобы дать точный ответ, нам нужно сравнить выручку в двух параллельных мирах: есть один мир, где внедрено изменение, и другой мир, где его не было.
Определим две выборки в идеальном кейсе:
Тогда, чтобы понять, надо ли катить изменение или нет, нам надо сравнить \sum T^w_i и \sum C^w_i: если сумма T^w больше, то надо катить изменение, а иначе нет. Но в реальности, конечно же, такое невозможно: нельзя сделать 2 мира. Поэтому и была придумана механика АБ-тестирования, в которой всех пользователей делят на 2 группы, но уже в 1 мире.
В таком случае у нас есть две выборки:
тестовая выборка:T_1, T_2,..., T_{K} — это выручка от пользователей за неделю, которые были в группе, где произошло изменение. Далее мы будем обозначать эту выборку буквойT.
контрольная выборка: C_1, C_2,..., C_{M} — это выручка пользователей за неделю из группы, где не было изменений. Эту выборку мы будем обозначать буквой C.
Где размер контрольной + тестовой групп равно N: K + M = N.
Выборки T^w, C^w определены для каждого пользователя, а T, C известны только для какой-то части юзеров, причем у одного пользователя известно либо значение T_i, либо C_i.
Как узнать, действительно ли\sum T^w_i > \sum C^w_i, имея только T и C? Кроме этого, хочется, чтобы наши результаты были валидны не только для текущего множества из N людей, которые будут юзерами сервиса на след неделе, но и для любого другого множества пользователей из той же генеральной совокупности. Например, если в будущем в наш сервис придут новые пользователи.
Здесь на помощь приходит теория вероятности и закон больших чисел (ЗБЧ):
Но ведь: E[Tw]=E[T]; E[Cw]=E[C]E[Tw]=E[T]; E[Cw]=E[C]. А значит:
Поэтому, чтобы определить, какая ситуация лучше — контрольная или тестовая, достаточно сравнить математические ожидания наших выборок в A/B-тесте. Такой подход позволяет нам решить две проблемы, о которых мы говорили ранее.
Теперь мы можем сформулировать нашу продуктовую гипотезу на языке статистики:
Теперь давайте перейдем к практике и посмотрим, как это работает на искусственном примере. Рассмотрим два распределения выручки, которые могли бы возникнуть: без изменения и с ним.
plt.figure(figsize=(10, 5)) plt.title("Распределение выручки в тесте и в контроле.", fontsize=12) plt.plot(x_ticks, control_distribution.pdf(x_ticks), color='green', label='Контроль: нет изменения') plt.plot(x_ticks, test_distribution.pdf(x_ticks), color='orange', label='Тест: изменили услугу') plt.legend(fontsize=12) plt.xlabel('Выручка от пользователя, руб', fontsize=12) plt.ylabel('Плотность распределения', fontsize=12) plt.show()
Мы видим, что рыжее распределение имеет визуально большее математическое ожидание.
Насемплируем возможные выборки в тесте и в контроле.
control = control_distribution.rvs(10000) test = test_distribution.rvs(10000)
Теперь мы можем провести T-test, чтобы проверить нашу гипотезу, и получить результаты. Но до этого нам необходимо зафиксировать уровень значимости (или FPR, false positive rate).
Какая будет ошибка первого рода?
В реальности это всегда не самая тривиальная задача трейдоффа между мощностью и FPR (false positive rate). Если alpha будет очень большой, то вы будете часто детектировать эффект там, где его нет. Вы будете чаще катить потенциально опасные изменения для бизнеса. А если alpha мала, то ваш бизнес будет в застое: вы не будете катить улучшающие бизнес изменения.
В большинстве случаев, если вам надо с чего-то начать, то я предлагаю брать одностороннюю alpha на уровне 2.5%. Это решение основывается на двух факторах:
Такой уровень ошибок приемлем для бизнеса. Это подтверждается опытом большинства компаний, которые используют именно такой FPR и продолжают успешно развиваться.
Обычно такая alpha оказывается достаточно чувствительной для проверки различных гипотез, обеспечивая хорошую мощность для вашего критерия.
Но при этом на практике есть множество кейсов, где выбор альфы 2.5% плох. Например:
нельзя брать большую alpha (где даже 2.5% много), если вы заранее знаете, что ваше изменение маловероятно к чему-то приведет. Например, если вы тестируете рассылку и смотрите в качестве одной из метрик доступность сайта Авито, то брать альфу даже 1% нет смысла. Вы не повлияете рассылками на эту метрику, все прокрасы здесь ложные.
нельзя брать большую alpha, если вы тестируете критический функционал, который потенциально может ухудшить сайт. В этом кейсе вам важна ошибка первого рода.
вы можете изменять alpha в большую сторону, если вы знаете, что у вас наверняка есть положительное изменение. Например, вы запустили маркетинговую рекламную кампанию.
Также важно помнить следующее: односторонняя альфа и двусторонняя альфы отличаются! Например, в нашем случае мы будем использовать дефолтную одностороннюю alpha в 2.5%. Но если мы решим использовать двусторонний критерий, то его alpha увеличится до 5%.
В нашем кейсе мы возьмем одностороннюю альфа равной 5% по 2 причинам:
Метрику выручки сложно прокрасить как мы увидим далее.
Мы уверены, что наше изменение положительно повлияет на услугу продвижения, (например, в реальности такой вывод можно сделать исходя из UX-тестов). Это как раз пример третьего кейса, где альфу можно менять.
Какой критерий выбрать: односторонний или двусторонний?
В нашем конкретном случае мы должны использовать односторонний критерий. Да и на практике ваша гипотеза почти всегда будет односторонней: вас интересует только улучшение (а иногда ухудшение) метрики относительно контрольной группы. В противном случае вы не будете раскатывать изменения.
Однако на практике немногие применяют односторонний критерий, так как он не является универсальным. Это означает, что для разных ситуаций вам потребуется три различных критерия, проверяющих рост, падение или изменение математического ожидания метрики.
Поэтому легче использовать универсальный двусторонний критерий и alpha в 2 раза большую.
Мы видим, что двусторонний p-value<0.1(=0.05⋅2), но мы не понимаем, в какую сторону у нас эффект. Поэтому давайте напишем удобную функцию-обертку над T-test, которая давала бы всю необходимую информацию для двустороннего критерия: pvalue, effect, а также строила бы доверительный интервал.
Это значит, что мы можем отвергнуть гипотезу в пользу H1 на уровне значимости 5% (одностороннем) и внедрять изменения.
Как делить пользователей?
Важно отметить, что для корректной работы T-test необходимо, чтобы тестовые и контрольные группы пользователей были репрезентативны для рыжего и зеленого распределений (в случае изменения и без него). Для этого достаточно поделить пользователей вашего сервиса случайно, что следует из свойств распределения и после небольших махинаций с теорвером. У вас не будет никакого смещения и мат. ожидание тестовых и контрольных выборок будут равны ETw и ECw соответственно.
В таком кейсе вы гарантируете, что ваши выборки не будут смещены относительно генеральной совокупности, а также репрезентативны.
Итоги:
Из этой части мы выяснили:
какую статистическую гипотезу следует использовать в нашем A/B-тесте;
какой критерий использовать на практике;
как разделить пользователей на группы. Случайное разбиение является достаточным.
Теперь мы готовы перейти к A/B-тесту на реальных данных и посмотреть, что у нас получится.
A/B-тест на реальных данных с плохим дизайном
Теперь давайте рассмотрим, как A/B-тестирование работает на практике, используя реальные данные:
статистическая гипотеза: Н0 выручка не изменилась или упала VS Н1 выручка выросла;
сплитование пользователей: мы случайным образом разделим всех пользователей сервиса на две группы: контрольную и тестовую, но в каждой из которых будет по 10% от общего трафика. То есть мы будем проводить наш A/B на 20% пользователей Авито. В контрольной группе мы оставим все как есть, а в тестовой запустим новое описание услуги.
Почему так мало пользователей в эксперименте? Мы опасаемся, что другие A/B-тесты, которые проводятся параллельно, могут повлиять на результаты нашего анализа и сделать их невалидными. Чем меньше размер выборки, тем более вероятно, что вы сможете найти «чистых» пользователей, не находящихся ни в чьем другом эксперименте.
длительность теста: как и в искусственном примере, мы проведем эксперимент в течение одной недели;
анализ результатов: мы воспользуемся t-тестом для получения стат. значимых результатов.
Давайте наконец приступим к анализу реальных данных!
У нас 3 столбца: пользователь, его группа и выручка от него в услугах продвижения.
Посмотрим, как распределена выручка:
test = np.array(exp_df[(exp_df['group'] == 'test')]['promotion_revenue'])
control = np.array(exp_df[(exp_df['group'] == 'control')]['promotion_revenue'])
plt.figure(figsize=(10, 5))
plt.title('Распределение выручки в тесте и в контроле', fontsize=12)
sns.distplot(test, label='test')
sns.distplot(control, label='control')
plt.legend(fontsize=12)
plt.xlabel("Выручка", fontsize=12)
plt.show()
Как видно, у нас есть как мелкие пользователи, так и очень крупные.
test = np.array(exp_df[(exp_df['group'] == 'test')]['promotion_revenue'])
control = np.array(exp_df[(exp_df['group'] == 'control')]['promotion_revenue'])
Давайте проанализируем, как увеличилась выручка на одного пользователя:
В результате теста мы получили p-value больше 5%, что означает, что тест был «серым». Мы не можем точно сказать, стало лучше или хуже.
Как быть в такой ситуации? Мы не можем внедрить изменение, поскольку нет уверенности, что мы не вырастили выручку. В подобных случаях неопытный аналитик сразу говорит, что нельзя внедрять изменение, а хороший аналитик никогда бы не запустил такой тест.
Первое, что необходимо было сделать, это убедиться, что вы действительно могли обнаружить ожидаемое изменение. Давайте вспомним, что такое MDE — минимальный детектируемый эффект:
если MDE очень большой (например, нужно увеличить выручку на 100%), то понятно, почему вы не заметили статистически значимого эффекта$
если MDE небольшой, то есть он меньше ожидаемого вами прироста выручки, то ваш A/B-тест хорошо подготовлен, и нужно искать другие причины, почему он не сработал. Но об этом мы поговорим в другой раз.
Давайте проверим MDE нашего теста. Вспомним, как выглядит формула MDE для t-теста:
где N и M — размеры теста и контроля.
Какую мощность выбрать? Давайте возьмем 80%: если эффект есть, то в четырех из пяти кейсов мы его обнаружим.
Тогда наш MDE в эксперименте будет выглядеть следующим образом:
42 рубля в среднем на человека — это много или мало? Сложно сказать. Давайте посмотрим, как это соотносится со средней выручкой, чтобы понять, насколько эта сумма велика.
MDE_rel = MDE_absolute / np.mean(control)
print(f"Относительный MDE для метрики выручки на пользователя равен {round(MDE_rel * 100, 1)}%")
Относительный MDE для метрики выручки равен 15.8%
Чтобы заметить изменения в выручке, нам нужно было увеличить её в нашем эксперименте на 16% благодаря нововведению. Очевидно, что изменение дизайна в уже существующем сервисе не может привести к таким результатам. Поэтому данный A/B-тест оказался бессмысленным.
А могли ли мы это понять до того, как провели эксперимент?
Предсказание MDE до начала эксперимента
Сегодня мы уже научились определять MDE после завершения эксперимента. Это интересно, но не имеет практического значения. Обнаружить после проведения теста, что он оказался бесполезным, было бы неразумно и привело бы к пустой трате ресурсов и времени компании.
Поэтому важно научиться прогнозировать, будет ли A/B-тест иметь смысл и какой у него будет MDE.
Для этого была разработана следующая методика:
Собирается исторический набор данных, желательно максимально приближенный к запланированному началу реального A/B-тест.
Запускается АА-тест на основе собранных данных с теми же параметрами, что и в предполагаемом эксперименте. Важно отметить, что в АА-тесте нет различий между тестируемой и контрольной группами.
На основе результатов АА-теста рассчитывается MDE, как в реальном A/B-тест.
Практика показывает, что этот метод позволяет с высокой точностью предсказать относительный MDE эксперимента. Давайте рассмотрим его в действии.
1. Сбор датасета
Здесь в датасете есть все 100% пользователей и дополнительные метрики, помимо выручки. А также группировка данных по неделям.
user_id — id пользователя;
week_dt — неделя в году, за которую собирались данные;
promotion_revenue — выручка от услуг продвижения.
Дополнительно:
promotion_listings — количество объявлений, к которым была применена услуга продвижения;
promotion_listers — количество пользователей, купивших услугу продвижения.
Важно:
в данном случае у нас нет колонки group, так как мы пока не поделили пользователей на тест и контроль;
один пользователь встречается в датасете больше, чем 1 раз! Это связано с тем, что юзер может совершать разные покупки в разные недели.
Поэтому давайте создадим функцию-помощник, которая будет группировать данные пользователя за разные недели в 1 строчку, чтобы избавиться от зависимостей в данных. При этом количество объединяемых недель будет параметром функции.
print(f"Абсолютный MDE для метрики выручки на пользователя равен {round(MDE_absolute, 1)} руб.")
print(f"Относительный MDE для метрики выручки на пользователя равен {round(MDE_rel * 100, 1)}%")
Абсолютный MDE для метрики ARPU равен 42.6 руб.
Относительный MDE для метрики ARPU равен 18.5%
Мы видим, что абсолютный MDE и относительный MDE оказались схожи с экспериментальными. Это свидетельствует о том, что наша текущая методика прогнозирования MDE до начала теста работает!
Однако стоит отметить, что в реальной жизни абсолютный MDE, полученный в ходе АА-теста, зачастую отличается от фактического из-за влияния сезонных факторов. Однако относительный MDE, как показывает практика, остается неизменным.
Подводя итог: мы успешно научились предсказывать MDE теста еще до его начала. Это представляет собой один из ключевых элементов в процессе подготовки A/B-теста. Как мы убедились, если бы мы провели эту процедуру заранее в нашем исходном кейсе, то смогли бы избежать плачевных результатов эксперимента.
Вывод всего раздела: некачественный сетап эксперимента — это гарантия того, что A/B-тесты будут серыми и бессмысленными.
Часть 2. Хорошо подготовленный АБ-тест на реальных данных
2.1 Грамотная подготовка теста
Мы научились прогнозировать MDE теста заранее. Но сейчас нам это не помогает: мы лишь поняли, что текущие параметры эксперимента не позволят замерить эффект.
Чтобы избежать подобной ситуации, перед началом теста важно тщательно продумать его параметры. Основываясь на двух приведенных выше примерах, можно выделить несколько ключевых компонентов:
сплитование. Процент пользователей, выделяемый для каждой группы;
длительность теста;
метрика и статистическая гипотеза.
Давайте подробнее рассмотрим, как каждый из этих параметров влияет на MDE.
В дальнейшем мы все время будем делать улучшения в сетапе теста, и надо понимать, насколько стало лучше. Оценивать мы будем по MDE, поэтому сразу же выпишем функцию, которая будет его считать.