Расчет АБ T-тестом

Продолжаем нырять в тему АБ и разбираться как считают тесты в большинстве продуктовых команд, где нет отдельного АБ-департамента.

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

Здесь примеры кода будут на R, но если ты питонист, можешь найти эти темы у меня в ТГ, там версия для Python тоже присутствует.

А теперь про сам тест.

Расчет АБ T-тестом

Статистика для АБ

Классическая механика расчёта АБ-тестов основывается на стат. критериях.

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

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

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

Выбросы — это экстремально большие или маленькие значения в наборе данных, которые могут искажать общие результаты. В АБ с выбросами осторожно.

Статистические тесты — не путать с АБ-тестами. Стат. тесты это инструменты, с помощью которых мы проверяем заранее заданные статистические гипотезы. Их много разных. У каждого стат. теста есть нулевая гипотеза, и задача теста её отклонить или не. Т.е. по простому, стат. тесты позволяют нам делать различные проверки наших данных, например, проверку на нормальность распределения, или гомогенность дисперсий.

ЦПТ (Центральная предельная теорема) — фундамент статистики: “при достаточно большом размере выборки, распределение средних значений выборочных данных будет приближаться к нормальному распределению, независимо от формы распределения исходной популяции.”

P-value — главный термин в контексте АБ, это вероятность получить такие же, или ещё более выраженные отличия, при условии, что верна нулевая гипотеза.

Ошибка первого рода — ситуация, когда мы отклоняем нулевую гипотезу, хотя на самом деле она верна. В АБ это значит что мы радуемся победе тестового варианта, но на самом деле он не выиграл.

Ошибка второго рода — противоположность ошибке первого рода. Мы не отклоняем нулевую гипотезу, хотя на самом деле должны.

Проверка на гомогенность дисперсий

В этой статье мы рассмотрим T-теста Стьюдента / Уэлча.

Нулевая гипотеза Т-теста предполагает, что средние значения переменной в выборках равны. Отклонение гипотезы (p-value < 0.05) это наша желаемая цель.

Я часто подхожу к расчётам через T-тест. Он простой и довольно точный. Из минусов — имеет пару ограничений (но и инструменты для их обхода).

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

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

Бартлетта. Он чувствителен к нормальности распределения;

Левена (Ливиня). Менее чувствителен к нормальности, самый популярный тест;

Флигнера-Килина. Не чувствителен к отклонениям от нормальности и выбросам, подходит для ненормального распределения;

Нулевые гипотезы всех критериев похожи, они предполагают, что дисперсии в выборках равны. Т.е. нам эту гипотезу отклонять не хочется (цель p-value > 0.05).

Но вне зависимости от результатов, мы можем обойти это ограничение. Оно присутствует у Стьюдента, но его нет у Уэлча.

В коде далее df — датасет, metric — метрика, столбец который проверяем, var — категория варианта.

Проверка дисперсий:

library(car) # библиотека с тестом Левена bartlett.test(metric ~ var, df) # тест Бартлетта leveneTest(metric ~ var, df) # тест Левена fligner.test(metric ~ var, df) # тест Флигнера-Килина

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

Долгое время (да и сейчас не редкость) в интернетах активно форсилась позиция, что T-тест ограничивается нормальным распределением выборки. Это не совсем так.

Второе ограничение T-теста подразумевает, что выборочные средние должны быть нормально распределены.

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

Функция для генерации средних:

generate_sample_means <- function(data, sample_size, n_samples) { sample_means <- numeric(n_samples) for (i in 1:n_samples) { sample <- sample(data, size = sample_size, replace = TRUE) sample_means[i] <- mean(sample) } return(sample_means) }

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

Шапиро-Уилка. Проверяет соответствие выборки нормальному распределению.

Колмогорова-Смирнова. Проверяет соответствие выборки заданному распределению, в том числе нормальному.

Тесты:

# тест Шапиро-Уилка shapiro.test(df) # тест Колмогорова-Смирнова на нормальность ks.test(df, "pnorm", mean = mean(df), sd = sd(df))

Но даже если выборочные средние не распределены нормально, мы можем попробовать обойти это ограничение методами выравнивания. Об этом поговорим дальше.

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

Выравнивание распределения

Иногда выборочные средние могут распределиться не нормально, а нам прям очень надо. Эту проблему можно пофиксить

Чтобы попытаться выровнять распределение, мы можем использовать много методов — от простого логарифмирования и стандартизации, до рангового преобразования и трансформации Йео-Джонсона.

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

☝ Главный нюанс, который стоит знать про BCT — он не работает с отрицательными значениями. В коде последней строкой фикс для таких случаев.

🧐 Суть метода, под капотом, заключается в поиске идеального значения суперпараметра лямбда, который определяет как именно трансформировать данные. Лямбда подставляется в формулу к каждому значению в данных. Если лямбда нулевая, то формула логарифмирует значения, если не нулевая — возводит данные в степень лямбды.

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

Примерно так это выглядит:

library(MASS) library(car) # Вычисляем лучшее значение лямбды par(mfrow = c(1, 1)) boxcox_result <- boxcox(df$metric ~ 1, lambda = seq(-3,3,0.1)) # Сохраняем его best_lambda <- boxcox_result$x[which.max(boxcox_result$y)] # Применяем преобразование df$metric_transformed <- car::bcPower(df$metric, best_lambda) # При наличии отрицательных значений, смещаем нашу метрику на минимальную величину +1 df$metric <- df$metric+ abs(min(df$metric)) + 1

T-тест

Вот мы и добрались до самого теста ✨

Он довольно простой. Нулевая гипотеза в обоих вариантах одинаковая: средние значения в двух группах равны. Т.е. нам бы хотелось эту гипотезу отклонить (p-value < 0.05) и показать всем что наш тестовый вариант статистически значимо отличается от контрольного.

# Расчет t-теста Стьюдента t.test(metric ~ var, data = df, var.equal = T) # чтобы переключиться на Уэлча, замени в var.equal T на F

R сам умеет определять, какой тест выбрать, просто не передавай ему параметр var.equal. Пример тут для понимания как это определяется в функции.

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

Главное самому не обмануться — дельты, полученные по итогам теста вообще ничего не прогнозируют. Если ты получаешь стат. значимый рост метрики в +30%, это не значит что раскатив вариант, у вас бизнес вырастет на 30%. Это просто саммари по итогам конкретного теста, в конкретный период, на конкретных юзерах.

А то потом придёт продакт через месяц и будет спрашивать, как так, 30% же было, где они?

Для визуализаций вариантов подходят чарты, которые хорошо показывают разбросы данных, например:

✅ Боксплоты

✅ Диаграмма плотностей

✅ Наложенные гистограммы

✅ График доверительных интервалов

Интерпретация теста

Базово его результат выглядит как-то так:

Two Sample t-test data: metric by var t = -14.679, df = 998, p-value < 2.2e-16 alternative hypothesis: true difference in means between group A and group B is not equal to 0 95 percent confidence interval: -10.869281 -8.305884 sample estimates: mean in group A mean in group B 15.49617 25.08376

🧐 Что тут происходит?

t. Это значение t-критерия, по которому вычисляется p-value. То, что значение отрицательное, означает что среднее группы A меньше среднего B.

df: Степени свободы. Они нужны для расчёта t-критерия.

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

95% доверительный интервал: Доверительный интервал для различия средних значений находится между -10.8 и -8.3, со знаком минус. Т.е. с 95% уверенностью можно сказать, что среднее значение группы B выше среднего значения группы A на величину от 8.3 до 10.8.

Оценки выборочных средних: Среднее значение для группы A составляет 15, а для группы B - 25.

Умение считать T-тест это хороший базовый скилл, которым должны обладать все аналитики, а продуктовые в особенности.

Еще больше про продуктовую аналитику я пишу у себя в ТГ-канале, присоединяйся ;)

2 комментария

Петр, спасибо за работу, теперь я понимаю наглядно что значит уровень senior )
Было бы удобнее воспринимать информацию такого рода, меньшим объемом. Скажите, а на какую аудиторию вы расчитываете ?

1

Ну это скорее базовые вещи, просто структурировано, но спасибо)

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

Я вообще заводил канал больше для себя, побомбить про аналитику, но потом немножко сменил вектор.

Больше пользы, как я считаю, для новичков в сфере, которые уже вышли на работу, но пока не особо понимают а что там делать. Стараюсь сразу показать какие практики хорошие, а какие не очень, чтобы ребята сразу начинали делать нормально, а не набивали кучу ошибок в поисках эффективного флоу. Как это я делал)

1