A/B тест
Накратко: A/B тестът сравнява два варианта върху случайно разделени посетители и проверява дали разликата в конверсията е истинска, или е шум.
Проблем
„Новата кошница по-добра ли е?“
Екипът направи нова страница на кошницата: по-кратка, с по-малко полета. Не знаем дали е по-добра. Затова две седмици всяко посещение на сайта на случаен принцип вижда или старата (old), или новата (new) кошница. Колоната ordered е 1, ако посещението завършва с поръчка:
Това е A/B тест: две версии, случайно разделени посетители, една мярка. Мярката тук е конверсия: делът на посещенията, които завършват с поръчка. Старата кошница има 3,10%, новата 3,72%.
Разликата е 0,62 процентни пункта: разлика между два процента. Това е около една пета повече поръчки. Но дали не е шум?
Идеята
Шум ли е разликата?
Това е въпросът от урока за хипотезите. Нулевата хипотеза: кошницата няма значение. Ако е вярна, етикетите old и new са без значение и можеш да ги разбъркаш. Колко често разбърканите данни дават разлика поне 0,62 пункта?
В 2 000 разбърквания най-голямата разлика е 0,76 пункта, но само 6 пъти тя е поне колкото истинската. p е 0,003. Разлика като тази рядко идва от шума.
По-точно, за любопитните
ordered е 0 или 1, затова средната на колоната е точно делът на поръчките. Разбъркването тук е с цикъл: таблицата има 28 176 реда и 2 000 разбърквания наведнъж в една матрица биха заели много памет в браузъра.
Пробвай
На всяко устройство ли?
Подобрението на телефон ли идва, или на компютър? Запиши в rates конверсията в проценти за всяко устройство (редове) и вариант (колони), а в lift разликата new − old за всяко устройство в пунктове.
Решение
import pandas as pd
visits = pd.read_csv('ab_visits.csv')
rates = visits.pivot_table(index='device', columns='variant', values='ordered', aggfunc='mean') * 100
lift = rates['new'] - rates['old']
print(rates.round(2))
print(lift.round(2))visits.pivot_table(index='device', columns='variant', values='ordered', aggfunc='mean') * 100 дава таблица 2 × 2. На компютър конверсията е 4,20% при старата и 5,26% при новата, на телефон 2,51% и 2,90%. Подобрението е на двете устройства: 1,05 и 0,39 пункта. С колоната revenue щеше да смяташ оборота на посещение, а не конверсията.
Идеята
По-бързият път: chi2_contingency
За два дяла има готова проверка. Първо pd.crosstab брои двойките: колко посещения от всеки вариант са с поръчка и без. После scipy.stats.chi2_contingency казва колко рядко е такава таблица, ако вариантът няма значение:
p е 0,0046, около 0,005. Разбъркването даде 0,003: двата пътя казват едно и също. Малката разлика е, защото 2 000 разбърквания мерят p грубо (6 случая), а chi2_contingency при таблица 2 × 2 прави малка поправка, която леко вдига p.
Идеята
Колко е подобрението?
p казва само „едва ли е шум“. Управителят пита друго: с колко е по-добра новата кошница? За това ти трябва интервал. Както в урока за интервалите, теглиш с връщане нови извадки от всяка група и гледаш как се мени разликата:
Подобрението е 0,62 пункта, а 95% интервал е от 0,21 до 1,04 пункта. Нулата е извън интервала: това е същото „едва ли е шум“. Но интервалът казва и колко: може да е малко над 0,2, може да е над 1 пункт.
По-точно, за любопитните
Тук повторенията са 2 000, а не 10 000, за да върви бързо: всяко тегли по 14 000 реда. По формулата интервалът на разлика между два дяла е разликата ± 1,96 × √(p₁(1 − p₁)/n₁ + p₂(1 − p₂)/n₂). Дава почти същото:
Идеята
Колко посещения ти трябват
Колко дълго да върви тестът? Решаваш го преди да започнеш, от две числа: конверсията досега (3,1%) и най-малкото подобрение, което има смисъл за теб (0,6 пункта). Има правило на палеца за 80% шанс да видиш подобрението при праг 0,05:
посещения на група ≈ 16 × p × (1 − p) / подобрение²
На група трябват 13 351 посещения, общо 26 701. Шансът тестът да хване истинско подобрение се казва мощност (power). Можеш да го провериш със симулация: измисляш 2 000 теста с конверсия 3,1% и 3,7% и броиш колко пъти p излиза под 0,05. rng.binomial(n, 0.031) дава колко поръчки идват от n посещения, всяко с шанс 3,1%:
С 2 000 посещения на група тестът хваща подобрението само в 18% от случаите, с 5 000 в 37%. С 13 351 е 78%, близо до 80%-те от правилото. Малкият тест не казва „няма разлика“: той не вижда.
По-точно, за любопитните
p_value е проверката на два дяла по формула (z-тест). Тя работи върху цели масиви наведнъж, затова 2 000 теста отнемат миг. За таблица 2 × 2 дава почти същото като chi2_contingency. Числото 16 в правилото идва от прага 0,05 и мощност 80%: (1,96 + 0,84)² × 2 ≈ 16.
Пробвай
По-голямо подобрение
Продуктовият екип казва: „Интересува ни само подобрение от поне 1 пункт.“ Колко посещения на група трябват по правилото? Запиши ги в per_group като цяло число.
Решение
base = 0.031
lift = 0.01
per_group = round(16 * base * (1 - base) / lift ** 2)
print(per_group)round(16 * 0.031 * (1 - 0.031) / 0.01 ** 2) дава 4 806 на група. Подобрението е 0,01, дял, а не 1. По-голямо подобрение се вижда с по-малко данни: 1 пункт иска около 2,8 пъти по-малко посещения от 0,6 пункта, защото (1 / 0,6)² ≈ 2,8.
Идеята
Не надничай преди края
Изкушението е да гледаш резултата всеки ден. Ето p, сметнато от всички посещения до съответния ден:
На третия ден p е 0,053, на четвъртия 0,077, на петия 0,041. Ако беше спрял на петия ден, щеше да обявиш победа. Тук новата кошница наистина е по-добра, но това се вижда от целия тест. Самото правило „спирам, щом p падне под 0,05“ е грешката.
Защо? Провери го с A/A тест: двете групи виждат едно и също, и разлика няма. Симулираме 1 000 такива теста по 14 дни и гледаме p всеки ден:
Ако гледаш само в края, фалшивите „победи“ са 4,9%, колкото обещава прагът 0,05. Ако гледаш всеки ден и спираш при първото p под 0,05, те са 21,7%: четири пъти повече. Шумът има 14 шанса да слезе под линията.
Идеята
Много метрики, много сегменти
Новата кошница дава повече поръчки. А по-големи ли са те? Сравни сумата на поръчка:
26,30 € при новата и 26,71 € при старата, p = 0,73. Няма доказателство, че кошницата мени сумата. Това не е „доказахме, че са равни“: малка разлика може да има, но този тест не я вижда.
Сега си представи отчет с 20 метрики: поръчки, сума, връщания, време на страницата… Всяка метрика без истинска разлика има 5% шанс да излезе с p под 0,05. Колко често поне една „печели“?
Една метрика „печели“ в 5,1% от случаите, както се очаква. Но поне една от 20 „печели“ в 64,1% от тестовете. Същото е с сегментите: телефон, компютър, нови клиенти, всеки ден от седмицата. Затова главната метрика се избира преди теста. Случайна „победа“ в 20-тата метрика е идея за следващ тест, не извод.
Идеята
Защо тестът казва „причинява“
Защо вярваш, че кошницата причинява повече поръчки, а не само върви с тях? Защото вариантът е даден на случаен принцип. Случайността изравнява всичко, което не мериш. Ето устройствата в двете групи:
И в двете групи компютрите са 35%, а телефоните 65%. Същото важи за часа, деня, навиците на хората: групите се различават само по кошницата. Затова разликата в поръчките идва от нея.
Когато данните не са от тест, това не е така. В следващия урок един банер изглежда, че вдига поръчките, а на всяко устройство поотделно не ги вдига.
Упражнение
Доклад за управителя
Управителят пита: „Носи ли новата кошница повече пари на посещение?“ Запиши в per_visit средния оборот на посещение за всеки вариант (всички посещения, и тези без поръчка), а в p p-стойността на сравнението с scipy.stats.ttest_ind.
Решение
import pandas as pd
visits = pd.read_csv('ab_visits.csv')
from scipy import stats
per_visit = visits.groupby('variant')['revenue'].mean()
new = visits.loc[visits['variant'] == 'new', 'revenue']
old = visits.loc[visits['variant'] == 'old', 'revenue']
p = stats.ttest_ind(new, old).pvalue
print(per_visit.round(2))
print(round(p, 3))visits.groupby('variant')['revenue'].mean() дава 0,98 € на посещение при новата и 0,83 € при старата кошница, а ttest_ind дава p ≈ 0,032. Посещенията без поръчка носят 0 € и влизат в средната. Ако смяташ само поръчките, получаваш 26,30 € и 26,71 € и p = 0,73: друг въпрос. Изводът за доклада: новата кошница носи повече пари, защото носи повече поръчки, а не по-големи.
Ако го планираше отначало: колко дни трябваше да върви тестът? Посещенията на ден са средно len(visits) / 14. Запиши в per_group посещенията на група по правилото (подобрение 0,6 пункта при 3,1%), в per_day посещенията на ден, а в days дните, закръглени нагоре.
Решение
import math
import pandas as pd
visits = pd.read_csv('ab_visits.csv')
base = 0.031
lift = 0.006
per_group = 16 * base * (1 - base) / lift ** 2
per_day = len(visits) / 14
days = math.ceil(2 * per_group / per_day)
print(round(per_day), days)На група трябват около 13 351 посещения, а групите са две: общо около 26 701. На ден идват около 2 013 посещения, затова дните са math.ceil(26701 / 2013) = 14. Тестът е бил планиран точно толкова. С per_group / per_day щяха да излязат 7 дни: колкото за едната група.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Първата графика · Извадка и популация · Доверителен интервал · Проверка на хипотеза
- Продължава в: Корелация и причина
- Задачи по темата: Статистика за решения