Раздел 42 · Аналитика данных с нуля

Статистика A/B на Python: p-value, t-тест Уэлча и бутстрэп

middle~10 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Фрея · риск-менеджер необанка «Монетка»

В SQL вы досчитали z до 1,935 и сказали «на грани». На грани чего? Мне нужна вероятность. И про частоту походов в кафе: вы обещали отдельный тест для средних.

В уроке про A/B-тест на SQL мы проверили, честно ли устроен тест кешбэка, и упёрлись в две вещи, которых в SQL нет: таблицы нормального распределения и тестов для средних. В Python они есть в scipy. Сегодня закрываем долг: p-value для доли, t-тест для частоты и бутстрэп для медианы.

Данные одни на весь урок. В cafe_clients.csv по строке на клиента теста, клиенты из обеих групп сразу уже исключены: группа, сколько раз клиент заплатил в кафе с 1 апреля и сколько там потратил. Таблицу собрал запрос из урока 54, а в pandas она читается одной строкой:

import numpy as np
import pandas as pd
from scipy import stats

clients = pd.read_csv("cafe_clients.csv")
clients.groupby("test_group")["cafe_visits"].agg(["count", "mean"])
            count      mean
test_group
control       994  3.434608
test          976  4.063525

От z к p-value

z = 1,935 значит, что разница долей равна 1,935 стандартной ошибки. Насколько это редко, если кешбэк ничего не делает? Ответ даёт p-value: вероятность случайно получить такую разницу или больше.

Если эффекта нет, z ведёт себя как стандартное нормальное распределение. Доля хвоста правее z это stats.norm.sf(z), от английского survival function:

z = 1.935
stats.norm.sf(z)            # 0.0265: один хвост
2 * stats.norm.sf(abs(z))   # 0.0530: два хвоста

Хвостов берём два. До старта мы не знали, в какую сторону сдвинется метрика: кешбэк мог и отпугнуть клиентов. Тест, который заранее ловит отклонения в обе стороны, называют двусторонним, и его p-value это сумма двух хвостов: 0,053. Порог в 5% мы не прошли: без эффекта такая разница случайно получается в 5,3% случаев. Вывод из SQL-урока не меняется, теперь у «на грани» есть число.

Средние: t-тест Уэлча

Частота походов: в тесте 4,06 оплаты в кафе на клиента, в контроле 3,43, на 18% больше. Для разницы средних берут t-тест. Идея та же, что у z: разница средних, делённая на стандартную ошибку. Только ошибку теперь считают по разбросу самих данных.

test = clients.loc[clients["test_group"] == "test", "cafe_visits"]
control = clients.loc[clients["test_group"] == "control", "cafe_visits"]

result = stats.ttest_ind(test, control, equal_var=False)
result.statistic, result.pvalue, result.df
# (4.124, 3.87e-05, 1956.4)

t = 4,12: разница больше четырёх стандартных ошибок. p-value 0,00004: случайно такое бывает примерно в 4 тестах из 100 000. Частота походов выросла, и это не шум.

equal_var=False включает вариант Уэлча. Без него получится классический t-тест Стьюдента, который считает разброс в группах одинаковым. Здесь группы почти равны по размеру, и разница мизерная: t = 4,126 против 4,124. Но если в тесте 10% трафика, а в контроле 90%, и разброс у них разный, тест Стьюдента начинает ошибаться. Тест Уэлча в таком случае не ломается, поэтому его и берут по умолчанию.

Для больших групп t-тест и z-тест для средних почти совпадают. В финальном боссе мы посчитаем такой тест для трат в кафе прямо в SQL, по формуле.

урок из курса

Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.