Фреймворк для дизайна A/B-теста

Сегодня мы рассмотрим простой базовый фреймворк для дизайна сплит-теста, который можно удобно использовать продуктовым аналитикам в своей работе. Разберем использование этого фреймворка, его теоретическую и математическую основу, и также поговорим о продуктовых аспектах заведения A/B-тестов — когда продакту и аналитику заводить A/B-тест не нужно. Вам понадобятся: представления о продуктовых метриках, знания python, первичные представления о математической статистике и чуточку воображения.

Варианты созрели для сравнения…

Варианты созрели для сравнения…

Содержание

С чего начать работу над дизайном теста

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

Обсудив с вашим продуктовым менеджером проблемы и цели и убедившись, что проведение теста необходимо, перейдите к формализации тестируемых гипотез, созданию макетов и MVP тестируемых вариантов представления продукта для пользователя и техническим ограничениям, описание которых должно присутствовать в документации на дизайн A/B.

Технические ограничения могут исходить из:

  • потребностей продукта: выбраны корректные сегменты аудитории по соцдему, географии, используемым платформам (iOS, android или web), версиям приложения и т.д.,

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

  • математических предпосылок и возможностей разработки: рассчитанные необходимые объемы выборок достяжимы, дизайн-макеты и MVP соответствуют тестируемым гипотезам, проведена проверка корректности системы логирования и системы сплитования.

Общий алгоритм действий

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

  1. Сформулируйте гипотезы в общем виде, исходя из продуктовой цели и переложите их на более математически точную формулировку.Например, изменение модели ранжирования статей в поиске приложения улучшает пользовательский опыт в приложении засчет улучшения полезности выдачи, а именно увеличивает: конверсию в переход на чтение статьи и timespent пользователя на странице статей в приложении, уменьшает число в поддержку жалоб на нерелевантные результаты поиска.

  2. Выберите метрики, у вас должны быть:
    core-метрика или метрики — 1–2 ключевые метрики, на основе которых Вы в первую очередь будете принимать решение по результатам теста, например, конверсия и AOV (средний чек) или RPU, или CPU и GMV и так далее. Это должны быть метрики, которые подтверждают, или опровергают сформулированные в пункте 1 гипотезы;
    warning-метрика: метрика, при выходе (росте или падении) которой за определенный установленный порог и сохранении этого роста или падения на протяжении заранее выбранного времени тест отключается, например: при уменьшении GMV в тестовой группе на 5+% и сохранении этого падения на протяжении 2-х дней тест отключается до его окончания;
    proxy-метрика или метрики — это метрики, которые коллинеарны core-метрике или core-метрикам и косвенно служат подтверждению изменения core-метрик в том или ином направлении. Рroxy-метрики необходимы в случае, если core-метрики недостаточно чувствительны и не позволяют за разумное время определиться с результатами по тесту (или набрать необходимый объем выборок для core-метрик в принципе невозможно), тогда решение по тесту придется принимаются на основе proxy-метрик.

  3. Зафиксируйте значения alpha-уровня, мощности.Определитесь со значениями уровня статистической значимости alpha и мощности для ошибок I рода и II рода: стандартно выбирают alpha = 0,05 и (1-beta) = 80%.

  4. Выберите значения ожидаемого лифта и MDE для заданной метрики или метрик, где лифт — это значение ожидаемой разницы (прироста или падения) метрики в тестовой группе относительно контрольной, выбранное на основе экспертных мнений или рассчитанное на основе ретроспективных данных. На основе лифта рассчитайте размер эффекта, также именуемого Cohen«s d, и зафиксируйте MDE (минимально детектируемый эффект) — его минимально достяжимое значение. Формулы для этих показателей представлены в следующем разделе — Quick Guide по математике.

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

  6. Рассчитайте время, необходимое для проведения теста. Исходя из полученных в предыдущем пункте значений размеров групп определите период проведения теста для накопления данных;

  7. Убедитесь в корректно настроке логирования.Проверьте наличие логов и полноту и качество логирования до старта теста и трекайте показатели и качество логирования в процессе тестирования новой версии. Для этого настройте базовые дашборды, отчеты или витрины, которые обеспечат оперативный доступ к необходимым показателям и мониторинг прогресса тестирования (избегайте эффекта подглядывания и дождитесь накопления необходимого объема выборки)

  8. Настройте оперативный мониторинг Warning-метрики и alerting по её выходам за установленный порог, threshold, что позволит в краткий период времени узнать о резком или долговременном падении или скачке Warning-показателя и при необходимости своевременно отключить тест.

  9. Проведите проверку корректности системы сплитования и адекватности выбранных статистических критериев, выбрав две идентичные контрольные группы и проведя предварительный набор A/A-тестов с расчетом p-value, которое в случае корректности сплитования и выбора критериев будет распределено равномерно, чтобы заранее убедиться в корректности системы сплитования.

Запускайте тест, Вы готовы.

Quick Guide по математике, лежащей в основе A/B-тестирования

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

Нулевая и альтернативная гипотезы

Нулевая и альтернативная гипотезы

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

Таблица. Типы ошибок при тестировании гипотез и характер их возникновения

Таблица. Типы ошибок при тестировании гипотез и характер их возникновения

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

150b7b834f21b240c3e638985f8ba851.png

alpha — это уровень статистической значимости, или вероятность ошибки I рода, то есть вероятность отклонить нулевую гипотезу, когда она верна («ложная тревога»), beta — это вероятность ошибки II рода, то есть вероятность принять нулевую гипотезу, когда она неверна (пропустить цель). Визуально соотношение alpha и beta можно представить на гистрограмме:

Соотношение alpha, beta и размера эффекта Cohen’s d

Соотношение alpha, beta и размера эффекта Cohen«s d

Размер эффекта, как можно видеть на гистограмме, также зависит от уровня статистической значимости, выбранной мощности и дисперсии выборки, а значит её размеров. Таким образом размер эффекта можно вычислить исходя из заданных на старте расчетов параметров alpha, (1-beta) и межгрупповой дисперсии, или фактически ожидаемого лифта (прироста или падения) в метрике, и вычислить MDE по формуле:

Метрики разницы-лифта между группами, размера эффекта и MDE в тесте

Метрики разницы-лифта между группами, размера эффекта и MDE в тесте

Как можно видеть на графике гистограмм, вычисление MDE фактически сводится к вычислению разницы средних двух распределений метрики в двух группах при заданных alpha и beta при критическом уровне p-value = alpha. Исходя из этого, в качестве методики расчета можно выбрать симуляцию проведения большого числа A/A-тестов методом Монте-Карло на исторических данных, затем построить распределение статистики разницы средних в симуляциях (фактически аналог Эфроновского подсчета p-value) и выбрать на основе этого распределения минимально необходимый размер выборки, который будет равен числу пользователей в выборке симуляции.

Почему это так: если в выборке N пользователей на A/A-тесте группы показали текущее распределение размеров эффекта и вычисленный на его основе MDE, то при большем числе пользователей в группах они тем более его покажут, то есть MDE корректный.

Распределения p-value для решения о принятии или отклонении нулевой гипотезы

Распределения p-value для решения о принятии или отклонении нулевой гипотезы

Принцип вычисления MDE и принятия решений на основе Эфроновского подсчета p-value показан на гистограмме: если при двусторонней гипотезе распределение статистики не касается нуля в процентилях [0; alpha*0.5] и [100-alpha*0.5;100], то принимается нулевая гипотеза.

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

Зачем же в принципе нужно рассчитывать размеры выборок? Чтобы избежать ложного срабатывания критерия на недостаточном объеме данных, необходимо рассчитать объемы групп, меньше которых статистическая значимость не может быть достигнута и значение p-valuealpha следует признавать случайным — это частая ошибка, вызванная подглядыванием за p-value — на протяжении теста с накоплением данных p-value может «гулять» вокруг alpha, пока не выйдет на плато. Примеры на практике:

Изменение p-value в течении теста — материал из статьи Uchi.ru

Изменение p-value в течении теста — материал из статьи Uchi.ru

Проверка корректности системы сплитования и адекватности используемого критерия также происходит на основе проведения большого числа A/A-тестов, например, 100 или 1000. Если сплитование или критерий работают корректно, число A/A-тестов с принятой по правилу p-value ≤ alpha альтернативной гипотезой не должно превышать alpha-уровень и, соответственно, p-value должно быть распределено равномерно, примеры корректного и некорректного распределения p-value в тестах:

Сплитование и критерий работают корректно: p-value на 200 A/A-тестах для метрики распределен равномерно

Сплитование и критерий работают корректно: p-value на 200 A/A-тестах для метрики распределен равномерно

Сплитование и критерий работают некорректно: p-value на 200 A/A-тестах для метрики, имеющей выброс, распределен ближе нормально

Сплитование и критерий работают некорректно: p-value на 200 A/A-тестах для метрики, имеющей выброс, распределен ближе нормально

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

Расчет необходимого объема выборки

Процедура расчета необходимого объема выборки основана на симуляции методом Монте-Карло, в рамках которой происходит последовательная генерация идентичных выборок в режиме A/A теста путем выбора из генеральной совокупности наблюдений случайным образом с возвращением.

Этот метод называется bootstrap, при большом объеме данных и недостаточно производительности машин или кластера для расчетов его можно заменить на метод бакетирования или саббакетирования.

def bootstrap(test, control, iterations = 1000):
  iterations = iterations
  size = min(len(test), len(control))
  t_boots = [random.choices(test, weights=None, k=size) for _ in range(iterations)]
  c_boots = [random.choices(control, weights=None, k=size) for _ in range(iterations)]
  return t_boots, c_boots

Для ratio-метрик метод будет иметь чуть измененный вид:

def bootstrap_ratio(test, control, iterations = 1000):
  iterations = iterations
  t_boots = [random.choices(test, weights=None, k=len(test)) for _ in range(iterations)]
  c_boots = [random.choices(control, weights=None, k=len(control)) for _ in range(iterations)]
  return t_boots, c_boots

Зададим функции агрегации и расчета статистики для сравнения разницы метрик в группах:

### 1. Функции аггрегации
def agg_sum(array):
  return [sum(i) for i in array]

def agg_avg(array):
  return [sum(i)/len(i) for i in array]

def agg_median(array):
  return [np.median(i) for i in array]


### 2. Функции расчета статистики
def diff(test, control):
  return [test_i - control_i for (test_i,control_i) in zip(test,control)]

def compare(test, control):
  return [test_i/control_i for (test_i,control_i) in zip(test,control)]

def perc_compare(test, control):
  return [(test_i/control_i-1)*100 for (test_i,control_i) in zip(test,control)]

У нас есть некоторый объем аудитории, рассчитанный за выбранны период времени — например, месяц — разобьём её на 100 бакетов случайным образом. Для расчета минимально необходимой выборки для проведения теста мы будем генерировать подвыборки разного объема, соответствующего 5%, 10%, 15% и так далее до 50% MAU и смотреть за изменением показателя минимального лифта и MDE:

# Процент из генеральной совокупности
slices = [5,10,20,30,40,50]
minimum_lift = []
mde = []
n_items = []
users = []

for i in slices:
  test = data[data['bucket_id']=i) &\
                  (df['bucket_id']<2*i)][metric].to_list()
  t_list, c_list = bootstrap(test, control)

  # Разница средних
  result = diff(agg_avg(t_list), agg_avg(c_list))
  minimum_lift.append((np.quantile(result, 0.975) - np.quantile(result, 0.025))/2)
  mde.append( minimum_lift/np.std(c_list))  
  n_items.append(i)
  users.append(min(len(test), len(control)))
 
pd.DataFrame({'minimum_lift': minimum_lift, 'mde': mde,
              'n_items': n_items, 'users': users})

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

2243489160e39a303b4cc0bcaee4cc97.png

Убедимся, что в симуляции A/A-теста действительно не наблюдаются статистически начимые изменения, для этого визуализируем результат на итерации с 10%:

plt.figure(figsize=(12,5))
plt.hist(result, bins=50)
plt.vlines([np.quantile(result,q=0.975),np.quantile(result,q=0.025),
            np.quantile(result,q=0.5)],ymin=0,ymax=50,
            linestyle='--', color='black')
plt.show()

Распределение метрики разницы средних в A/A-симуляции

Распределение метрики разницы средних в A/A-симуляции

Видим, как и ожидали, что распределение Эфроновской статистики проходит через ноль.

Сведем расчеты в единую функцию bootstrap:

def bootstrap(test, control, func_agg, func_diff, 
              ratio=False, n_iterations=1000):
    function_agg = func_agg
    function_diff = func_diff
    
    size = min(len(test), len(control))
    t_list = []
    c_list = []
    np.random.seed(seed=42)
    
    for _ in range(n_iterations):
        if ratio:
            test_agg = np.random.choice(a=test, size=len(test), replace=True)
            control_agg = np.random.choice(a=control, size=len(control), replace=True)
        else:
            test_agg = np.random.choice(a=test, size=size, replace=True)
            control_agg = np.random.choice(a=control, size=size, replace=True)
        t_list.append(function_agg(test_agg))
        c_list.append(function_agg(control_agg))
        
    t_list = np.array(t_list)
    c_list = np.array(c_list)
    result = function_diff(t_list, c_list)
    
    ci_all = np.quantile(result, 0.025).round(2), 
             np.quantile(result, 0.975).round(2)
    minimum_lift = (np.quantile(result, 0.975) - np.quantile(result, 0.025))/2
    mde = minimum_lift/np.std(c_list)
    users = min(len(test), len(control))

    return ci_all, minimum_lift, mde, result, users

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

Расчет необходимого времени работы теста

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

_, _, _, _, users = bootstrap(test, control, agg_avg, diff, 
                              ratio=False, n_iterations=1000)
mau = data_monthly['user_id'].nunique()
wau = data_weekly['user_id'].nunique()
days_weeks = users/wau*7
days_m = users/mau*30
print(f'Period from {min_days} to {max_days} days needed'.format(
                                           min_days=min(days_weeks, days_m), 
                                           max_days=min(days_weeks, days_m)))

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

В качестве послесловия

Рассмотренный выше фреймворк довольно полно описывает процесс дизайна A/B-теста, универсален и охватывает большую часть аспектов построения грамотного процесса тестирования, не включая только вопросы тестирования множественных гипотез. Приведенный в статье метод определения объемов выборок применим для разных типов статистик. Надеюсь, материал был для вас полезен и интересен, и поможет на практике. 
За вдохновение на работу над статьей и методами, разработанными в ней, особенно хочется отметить моего замечательного руководителя Дениса Самойлова из Яндекс.Доставки и мою коллегу Динару Исхакову, а также потрясающих экспертов в сфере split-тестирования Вита Черемисинова и Искандера Мирмахмадова. Всем Data Cheers :)

Полезная литература

  1. https://expf.ru/ab_course

  2. https://www.geo.fu-berlin.de/en/v/soga-py/Basics-of-statistics/Hypothesis-Tests/Introduction-to-Hypothesis-Testing/Critical-Value-and-the-p-Value-Approach/index.html

  3. https://habr.com/ru/companies/uchi_ru/articles/500918/

  4. https://www.microsoft.com/en-us/research/group/experimentation-platform-exp/articles/p-values-for-your-p-values-validating-metric-trustworthiness-by-simulated-a-a-tests/

© Habrahabr.ru