Привет, Хабр!
A/B тестирование позволяет принимать обоснованные решения о том, какие функции или изменения наилучшим образом способствуют улучшению пользовательского опыта и повышению конверсии. Однако, несмотря на кажущуюся простоту проведения таких экспериментов, существует ряд подводных камней, которые могут серьезно исказить результаты. Один из самых заметных и опасных — это так называемая проблема peeking или подглядывания. Эта проблема возникает, когда аналитики и маркетологи начинают слишком рано анализировать данные теста, не дожидаясь его завершения. Такое поведение может привести к неверным выводам и решениям, которые, в свою очередь, могут нанести ущерб бизнесу.
В статье рассмотрим, что такое проблема подглядывания в контексте A/B тестирования как ее решить.
Статистические основы и последствия подглядывания
Основной статистический инструмент в A/B тестировании — это p-value, которое используют для определения статистической значимости различий между группами. В идеальном эксперименте значение p-value должно быть рассчитано после завершения теста с заранее определённым размером выборки. Однако при подглядывании результаты проверяются многократно в процессе набора данных, что приводит к повышенной вероятности ошибочного обнаружения статистической значимости, когда на самом деле её нет. Это явление известно как увеличение вероятности ошибки первого рода.
Подглядывание может существенно увеличить p-value, потому что каждый новый "взгляд" на данные увеличивает шанс случайно наткнуться на временное "значимое" различие, которое исчезает по мере продолжения теста. Например, если аналитик проверяет данные дважды, p-value может удвоиться, а при пяти проверках — увеличиться в 3,2 раза, а при 10,000 проверках — увеличиться в двенадцать раз.
На моей практике, даже с использованием методов коррекции, таких как Бонферрони, которые предназначены для контроля за множественными сравнениями и уменьшения ложноположительных результатов, подглядывание всё равно может привести к ошибкам. Эти методы ужесточают критерии статистической значимости, но даже с их помощью неправильное применение процедуры тестирования может привести к неверным выводам.
Важно понимать, что каждая отдельная проверка не является независимой от предыдущих. В результате, чем чаще проверяются данные, тем выше вероятность обнаружения ложных различий из-за случайности, а не из-за наличия реального эффекта.
Решение проблемы
Секвенциального тестирование
Секвенциальное тестирование включает постепенную проверку данных с использованием специально разработанных статистических методов, которые позволяют корректировать пороги значимости на каждом этапе анализа. Пример такого тестирования - применение группового секвенциального метода тестирования, который регулирует важные значения и интервалы доверия в зависимости от количества проведенных анализов.
Метод обычно юзается для контроля над уровнем ошибки первого рода в условиях, когда анализ данных проводится несколько раз в течение эксперимента. Фичей секвенциальных испытаний является возможность остановки теста как только достигнуты предварительно установленные границы эффективности, что позволяет сделать выводы быстрее и с меньшими затратами по сравнению с базовыми A/B тестами.
Приведу пример. Для реализации используем библиотеку statsmodels
:
import numpy as np
import statsmodels.stats.api as sms
# генерация случайных данных
np.random.seed(42)
data = np.random.binomial(1, 0.5, 300)
# настройка секвенциального анализа
alpha = 0.05 # уровень значимости
beta = 0.2 # мощность теста
information_rate = np.linspace(0.1, 1.0, 10) # доли информации для каждой точки анализа
# инициализация секвенциального теста
test = sms.SequentialGroupSequentialDesign(alpha=alpha, beta=beta, k=10)
# процесс тестирования
results = []
for i in range(1, 11):
cum_data = data[:int(30 * i)] # Кумулятивные данные на каждом шаге
result = test.update(np.mean(cum_data), len(cum_data))
results.append(result)
if result['stop']:
break
Данные симулируются как результаты бинарного испытания, где 0.5
- это вероятность успеха. Секвенциальный анализ проводится на десяти этапах, и на каждом шаге проверяется, не достигнут ли критерий для прекращения теста. Если критерий достигнут, тест останавливается.
Байесовский подход
В Байесовском подходе к A/B тестированию используется предварительное распределение вероятностей, которое обновляется с поступлением новых данных, ведущее к постериорному распределению. Этот процесс основывается на теореме Байеса, которая позволяет непрерывно интегрировать новую инфу о поведении юзеров и эффективности интервенций.
Основная фича байесовского подхода заключается в его гибкости и способности корректно обрабатывать изменения, возникающие при подглядывании. В частотных методах, подглядывание может привести к увеличению вероятности ошибки первого рода, потому что каждый просмотр данных трактуется как отдельный тест. В байесовском подходе, наоборот, каждый новый взгляд на данные просто обновляет апостериорные вероятности, что помогает избежать кумулятивного эффекта множественных сравнений
Предположим, есть две версии веб-сайта: A и B. Нужно прверить, увеличивает ли версия B количество кликов по сравнению с версией A. Начинаем с априорного предположения, что обе версии одинаково эффективны:
import numpy as np
# функция для обновления апостериорных вероятностей
def bayes_update(prior, likelihood):
unnormalized_posterior = prior * likelihood
return unnormalized_posterior / unnormalized_posterior.sum()
# генерация данных: 1 - клик, 0 - нет клика
# группа A
clicks_a = np.random.binomial(1, 0.3, 500)
# группа B
clicks_b = np.random.binomial(1, 0.35, 500)
# априорные вероятности: [P(неэффективно), P(эффективно)]
priors = np.array([0.5, 0.5])
# вероятности увидеть данные, если гипотеза верна
likelihoods_a = [1 - clicks_a.mean(), clicks_a.mean()]
likelihoods_b = [1 - clicks_b.mean(), clicks_b.mean()]
# обновление априорных вероятностей для группы A
posterior_a = bayes_update(priors, likelihoods_a)
# обновление априорных вероятностей для группы B
posterior_b = bayes_update(priors, likelihoods_b)
print(f"Апостериорные вероятности для группы A: {posterior_a}")
print(f"Апостериорные вероятности для группы B: {posterior_b}")
Апостериорные вероятности для группы A: [0.72 0.28]
Апостериорные вероятности для группы B: [0.622 0.378]
Создали данные по кликам для групп A и B с помощью биномиального распределения. После начали с равной вероятности того, что версии веб-сайта эффективны или неэффективны. Далее рассчитали как вероятность получить наблюдаемые данные при условии, что гипотеза верна или не верна. С помощью функции bayes_update
обновляем априорные вероятности на основе наблюдаемых данных.
Таким образом, правильное понимание и управление проблемой подглядывания способствует более точному принятию решений на основе данных.
A/B тесты - это один из инструментов, применяемых в аналитике. Больше практических инструментов эксперты OTUS рассматривают в рамках практических онлайн-курсов. Подробнее в каталоге.
RomanSeleznev
У меня возник вопрос: а зачем посреди эксперимента вообще пытаться делать выводы? Это мне напоминает идею 100 раз бросить монету, но уже после 3-х бросков, увидев, что все 3 раза были "орлы", начать верить в то, что и оставшиеся 97 бросков приведут к "орлам".
Есть эксперимент, есть его условия, критерии завершения (время, число испытаний и/или ещё что), есть гипотезы, а значит можно просто дождаться завершения и потом смотреть на отслеживаемые метрики.
Возможно, конечно, я из статьи не уловил мысль, но правда любопытно понять, зачем "подглядывают".