Подглядывание в A/B тестах: как не потерять достоверность данных

95c5fd74aac40bbc73e1196f54ac8688.png

Привет, Хабр!

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 рассматривают в рамках практических онлайн-курсов. Подробнее в каталоге.

© Habrahabr.ru