Фрея · риск-менеджер необанка «Монетка»
В 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 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.