Проверка результатов A/B теста

A/B-тесты это основной способ решения споров об интерфейсах в команде. Но часто эти споры решаются неверно, потому что ключевая ошибка при анализе результатов A/B теста это сравнение двух средних, без подбора критерия, оценки выборки. Беглый визуальный анализ отчетов в GA по принципу «где график выше, та версия и лучше» приводит к ошибочным выводам и стоит бизнесу кучу денег. Если до этого момента вы обходились знаниями, что онлайн-калькуляторы должны показать «p < 0.05» и «нормальное распределение похоже на колокол», то в этой статье я постараюсь расширить ваш кругозор.

Все примеры будут продемонстрированы для R-Studio. Общий процесс анализа: экспериментальный дизайн → сырые данные → обработанные данные → выбор статистической модели → суммарная статистика → p value. С экспериментальным дизайном дизайнеры справляются, про остальные шаги давайте говорить подробнее.

Формируем гипотезу

При формировании гипотезы руководствоваться лучше простыми понятиями: цели бизнеса, как эти цели достигаются клиентами и как это можно измерить (Single A/B test или Multi A/B test). Не менее важно понимать, как данные будут собираться и валидироваться, и как будут вноситься изменения по результатам теста. При этом предполагается, что проблема репрезентативности и достаточности объёма выборки решена. По умолчанию в данном уроке мы будем считать, что стандартное отклонение не зависит от размера выборки, так как выборка репрезентативна.

Но будем честны, часто задача будет ставиться по принципу: «у нас упала прибыль, посмотри, че там такое». Или приходит продукт-менеджер или гейм-дизайнер, рассказывает про уже реализованную фичу на проде, и просит узнать эффективность этой фичи. При этом нет информации, на что эта фича была направлена, на каких данных ее исследовать, как ее операционализировать. В случае столь слабо формализованных задач нужно придумывать гипотезы самостоятельно и думать, откуда взять данные для формирования этих гипотез. Помогает консолидация данных из разных источников.

Либо, в качестве источника гипотезы банальный спор менеджеров продукта и проекта, результат коридорного опроса, желание оптимизировать CRO. Я не люблю тестировать фичи, которые очевидно улучшат конверсию, это достаточно бесполезная работа. Лучше тестировать фичи, у которых отдача бизнесу непредсказуема. В таких фичах обычно кроется рост всех ключевых метрик: CTR (Click Through Rate), конверсия, CPA, ROAS, CPI. Должен сказать, что при малом количестве данных очень сложно оценивать небинарные метрики (средний чек, выручка), результаты обычно очень шумные: работает принцип garbage in garbage out. А вот A/B тесты для бинарных задач проводить просто (выполнено/не выполнено).

При проверке гипотезы помним, что нулевая гипотеза про отсутствие отличий. Альтернативная гипотеза о значимости различий. Ошибка первого рода (α, false positive) происходит, когда мы отклоняем нулевую гипотезу в пользу альтернативной гипотезы, при условии что справедлива нулевая гипотеза. H0 считается верной, пока не доказано обратное, и если она отвергается, значит нам пора бежать предпринимать действия. Это та самая альфа 0,05, которая говорит что в одном случае из 20 будет ошибка первого рода. Ошибка второго рода (false negative) происходит, когда верна альтернативная гипотеза, но было принято решение принять нулевую гипотезу. Другими словами, во время дизайна теста мы задаем уровень значимости. который показывает вероятность верности нулевой гипотезы. На основе significance level мы принимаем или отвергаем гипотезу с полученным p-value, это вероятность совершить ошибку первого рода. Вероятность ошибки второго рода это 1 — power, шанс не заметить значимые изменения. Поэтому нет смысла делать дизайна тестов базируясь только на ошибке первого рода. Низкое значение уровня значимости уменьшает шанс совершить ошибку первого рода, но растет шанс совершить ошибку второго рода, поэтому значение p-value подбирается исходя из критичности допуска ошибок. Например, нельзя допустить ошибку второго рода в таком кейсе: не сработать сигнализацией при попытке угона машины, это критично. При работе с калькуляторами вроде evanmiller для расчёта доверительных интервалов разницы мы не учитываем ошибку второго рода. Доверительные интервалы всегда считаются по результатам теста и куда информативнее, чем размер выборки и мощность.

Итак, summary: если человек болен и мы это подтвердили — true positive (TP). Если человек не болен и мы это подтвердили — true negative (TN). Ошибка первого рода — здоровому человеку сказали, что он болен (FP). Или ошибка второго рода, сказали больному человеку что он здоров (FN).

Ошибки будут всегда, и для определения их существенности есть две метрики: полнота и точность (recall и precision), они про количество ошибок первого и второго рода. Метрики между собой конкурируют, поэтому надо учитывать обе метрики по интегральной характеристике.

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

И никогда не забываем про метрики. Если игнорировать LTV (lifetime value или сколько денег принес клиент за свой жизненный цикл) и ROI (return on investment или окупилась ли сумма привлечения клиента), то в краткосрочной перспективе можно хорошо поднять метрики ARPU diary, ARPU month и процент платящих, чем часто пользуются продуктовые менеджеры, бегающие из компании в компанию. Для краткосрочного поднятия ARPU достаточно ввести дополнительные регулярные акции. Но в долгосрочной перспективе такой подход приведет к финансовым проблемам, так как каннибализирует остальные механики привлечения денег, и это будет видно на LTV 6 month. Еще можно выполнить краткосрочный KPI если uplift-нуть CTR одной кнопки, и каннибализировать CTR других кнопок. Или остановить трафик для мобильного приложения, очень краткосрочно подрастет ROI. В общем, любые временные изменения в базовой экономике всегда ведут к временному увеличению метрик с дальнейшей трагической просадкой. Можно привести более наглядный пример: промо-ловушка из продуктовых магазинов. Покупатель думает, что цены растут и доходы снижаются, значит нужно меньше тратить. Продавцы это видят и снижают цены в рамках акций, это увеличивает выручку на короткий промежуток времени. Покупатели привыкают к акциям и идут в магазин целенаправленно в поисках товаров по скидке, продавец вынужден еще раз снижать цену, и далее делать это постоянно. При чем тут тесты? Бизнес после запуска разовой акции видит, что прибыль пошла вверх, трафик увеличился, клиентов стало больше, но вы как аналитик должны сказать, что не смотря на это выгода/маржа/грязный вал в разрезе трех месяцев упали. Это вечная дилема: выполнить план и получить просадку по марже, или не выполнить план, разово увеличить прибыль, но просесть по выручке. Говорите бизнесу про положительную EBITDA, тогда они вас будут слушать

Я буду приводить p-value = 0,05, так как это общепринятое значение. В реальных проектах p-value куда меньше.

 

Запуск A/B теста и сбор данных

Либо вы идете к разработчику и он каким то образом все делает за вас, и при работе с мобилками это основной способ проведения A/B теста. Либо используете GTM или Google Optimize, где разделяем трафик, готовите визуальное представление гипотезы и задаете условия ее отображения (сегменты и тому подобное). В результате будет возможность менять не только цвет и тексты на странице, но и создавать новые функциональные сущности или развивать имеющиеся, а также успешно сегментировать гипотезы еще на этапе запуска. Это дает большое преимущество в условиях ограниченных технических и финансовых ресурсов крупного бизнеса.

Как все происходит: случайно делите весь новый трафик из новичков на тест и контроль. На основании опыта или теории получаем ожидаемый размер эффекта, и от него считаем выборку. Получив результаты, проверяем их на статистическую значимость. Дополнительно, можно провести АAB-тест, это позволит убедиться, что различия только на уровне поведения пользователей, а не на уровне багов системы, изменения погоды и прочих факторов. Аудиторию перед тестом можно раскидывать простым рандомом, а можно престратификацией (CUPED). Если почти все пользователи новые, то CUPED с дополнительной ковариатой.

В идеальном случае после запуска теста через определенное время тест проверяется, и делается переоценка кол-ва дней, нужных для сбора данных. Торопиться при сборе данных не надо, средний цикл оформления банковской услуги это 2 недели, значит, это минимальный срок для сбора данных. Данные влияют на качество валидации гипотез, обязательно проверяем стабильность данных (отсутствие шума), исходя из исторических данных.

Данные не обязательно собирать самостоятельно, основные источники данных это:
1. Данные из собственных приложений, сайтов, расширений и т.д.
2. Снифферинг незашифрованного трафа на крупных узлах обмена данными.
3. Покупка данных сторонних поставщиков — создателей приложений, расширений, рекламных/баннерных сетей, малвари, червей и т.д., как легитимным, так и не очень образом.
4. Покупка данных о посещаемости каких-нибудь ресурсов, которые готовы продать данные.
5. Хантинг людей.

Характеристика и нормализация данных

Предположим, данные готовы, они могут быть в формате csv или Excel. Их может быть много или мало, они могут быть распределены нормально и ненормально. Проверка на нормальность данных нужна, чтобы центральная предельная теорема выполнялась на малых выборках. Если выборки большие и наблюдения независимые, то предположение о нормальном распределении для теста Стьюдента не нужно (т.к. работает центральная предельная теорема). На 1000 пользователях не удастся отследить мелкие изменения (1-2%), но изменения в 20-30% можно. Проще говоря, при большой выборке результаты теста будут точные, при маленькой выборке не факт, что есть смысл проводить A/B тест.

Предположим, что данных мало, значит, нужна проверка о типе распределения данных. Не факт, что удастся отличить равномерное распределение от нормального на очень маленькой выборке. В общем случае, нужно построить график столбчатой диаграммы и посмотреть его форму. Нормальное распределение выглядит как колокол с тремя сигмами с каждой стороны. Правило трех сигм: каждая сигма это одно стандартное отклонение, данные нормально распределены, и 99,7% выборки попадает влево по графику на три сигмы, и вправо по графику на 3 сигмы. Если выборки гомогенные и распределение нормальное, то наш выбор это t-Критерий Стьюдента. Если же по графику видно, что распределение с отклонением на левую сторону графика, то используется хи-квадрат Пирсона. Он хорошо подходит для проверки равномерного налива трафика.

Встречается еще мультимодальное или бимодальное распределение, но чаще после ресемплинга. Если мы говорим про оценку средних, то при достаточном кол-ве наблюдений данные будут распределены нормально. Это большой плюс работы над крупным продуктом, в котором всегда много данных: при любом распределении исходных величин распределение выборочных средних будет стремиться к нормальному. Результат попросту зависит от мощности, т.е. от размера выборки. Идеально нормальных данных быть не может, если под реальной жизнью не понимать результат работы функции rnorm(). Да и данные в нашем случае дискретны и являются набором точек. Поэтому на большой выборке shapiro-wilk всегда будет значимым. А вот на выборках 10-15 значений он всегда незначимый из-за недостаточной мощности. В статистике мощность асимптотически стремится к 1.

Стратификация (выделение суб-группы из выборки и рандомом деллим на 2 группы) опять же не плохо работает для однородности. Сегментация нужна. Хотя бы на уровне киты/планктон и рандомом, сверяясь на A/A-тесте. И даже так выборки могут быть с нестабильным по времени и с дисбалансом. Поэтом важно выбрать подходящий критерий для таких выборок. Ресемплинг поможет для оценки, как и моральная подготовка к перезапуску теста при наличии скачков данных на ретроспективе.

Выбросы надо смотреть на box-plot и удалять ручками, как и дубли. Если мы работает с финансовыми метриками и удалять выбросы невозможно, то применяется правило трех сигм: убрать все значения, которые выходят за три стандартных отклонения и посмотреть, как изменятся наши данные. Но будет большая потеря данных, что может быть критично. Другой способ это метод трансформации по Боксу-Коксу. При этом надо понимать, что удаление выбросов только для применения того или иного критерия—не верный подход. Прагматичнее для начала посмотреть срезы, где явно будут видны различия.

Давайте сгенерируем данные таким способом usersExport <- data.frame(n = 3:90) и построим график boxplot(usersExport). Мы получим практически идеальный график, на котором есть квантили.

Как читать график boxplot: точка или линия соответствуют средней арифметической, эту точку окружает квадрат, его длина соответствует точности оценки генерального параметра. Усы от квадрата соответствуют своей длиной одному из показателей разброса или точности. Для формирования boxplot нужно написать комманду boxplot(имя переменной). Можно для эксперимента создать дырки в данных usersExport <- usersExport[-sample(3:90, 23), ] и посмотреть, как изменится график boxplot.

Вот пример графика с выбросами:

 

Проверить данные на нормальность можно простым взглядом по qqplot(). При больших объемах тесты практически всегда покажут отклонения от нормального распределения. Поэтому, если данные получились очень ненормальные, например у времени, проведенного за смартфоном или финансовых показателей всегда есть ограничение снизу, то нужна нормализация или хотя бы удаление выбросов. Переходя к цифрам, различие в 5% не такое уж и большое. На большой выборке будет совсем близко к 0,05. Кроме того, многие тесты устойчивы к умеренным отклонениям от нормального распределения. Просто даже очень небольшие отклонения от нормальности будут значимы на больших выборках, но это справедливо для всех стат. тестов.

qqplot(rt(a,df=3), x, main="t(3) Q-Q Plot")
abline(0,1)

Мы видим, что R-Studio нарисовал график Q-Q (слева). На таком графике отображаются данные в отсортированном порядке по сравнению с квантилями из стандартного нормального распределения. За исключением выбросов, точки расположены более менее по прямой, хотя и скашиваются. Значит, наши данные искажены. На это также указывает, что точки расположены вдоль линии в середине графика, но отгибаются в конце. Такое поведение характеризует наличие в выборке более высокие значений, чем ожидалось от нормального распределения. Пример нормального распределения показан на графике Q-Q справа.

Еще один пример нормальных данных: вводим команду для генерирования данных x <- rnorm(100). Строим график с линией qqline(x), и добивает гистаграммой hist(x).

Пример ненормальных данных: y <- rgamma(100, 1), затем qqnorm(y); qqline(y), и гистограмма hist(y).

Выбираем победителя

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

Тест на нормальность (Шапиро - Уилка)
Тест на нормальность (Шапиро — Уилка)

Для начала рассмотрим тест Шапиро-Уилка. Критерий Шапиро-Уилка это W-критерий, который также позволяет оценить нормальность. Если W=1, то выборка точно нормально распределена. Если Шапиро-Уилка дает маленький p-value, то значит есть выбросы. Все, что выше 0,75, можно считать нормальным распределением. Для выполнения теста Шапиро-Уилка предназначена функция shapiro.test(x), принимающая на вход выборку x объема не меньше 3 и не больше 5000. Генерируем нормальные данные, x <- rnorm(4600), и используем тест shapiro.test(x). Мы видим следующий текст:

Shapiro-Wilk normality test
data: x
W = 0.99947, p-value = 0.222

Смотрим на p-value = 0.222 и принимаем нулевую гипотезу.

W это значение статистики теста, в данном случае это 0.99947, что считается отличным результатом, т.к. выборка изначально имеет нормально распределенные данные. Чтобы отклонить нулевую гипотезу, p-value должно быть не выше альфы 0,05 (максимум 0,1). Проверим на ненормальных данных:  y <- rgamma(100, 1), shapiro.test(y).

Shapiro-Wilk normality test
data: y
W = 0.9829, p-value < 2.2e-16

P-value < 2.2e-16, что намного меньше 0.05, практически 0. И это при том, что R сообщает только значения p-value выше порога 2.2×10−16. Мы отклоняем нулевую гипотезу. Сначала смотрим, меньше ли 0.05, потом сравниваем средние или медианы. На несимметричных данных медиана значительно лучше отразит центральную тенденцию, чем обычное среднее. Если распределение одной из выборок заметно отличается от нормального, то в качестве центра берется медиана и соответственно, критерий Уилкоксона — Манна-Уитни. Если у данных положительный длинный хвост (прибыль) и ненормальное распределение, то это Манна-Уитни. Если же распределение всех выборок нормальное, то среднее арифметическое это наш выбор, и какой-либо из Стьюдентов.


A/B-тесты подразумевают два набора данных, поэтому тест нужно проводить для обоих выборок. Это не обязательно тест Шапиро-Уилка, это может быть и непараметрический ранговый U-критерий Уилкоксона — Манна-Уитни. Если данные непрерывные, то в простых случаях хорошо работают критерии Уилкоксона — Манна-Уитни/Краскела-Уоллиса, в которых нулевые гипотезы на сравнения распределений и медианы. Распределение 50 на 50 подразумевает использование формулы Бернулли, а может быть и Байесовский многорукий бандит. Если же использовалось сплит-тестирование, то нужно использовать непараметрический дисперсионный анализ — критерий Краскела-Уоллиса. Для сравнения дисперсий хороший вариант Fligner-Killeen и Brown–Forsythe. Рассмотрим с примерами.

Критерий Байеса рекомендуется при большом количестве данных. Идея в получении 500 раз выпадение орла из 1000 бросаний монетки, и считается по формуле C500(1000) * 0.5^(500) * 0.5^(500), т.к. броски друг от друга никак не зависят. У Байаса всегда есть априорное распределение, а значит, нужно иметь свои представления о параметрах исследуемового процесса. Что, в принципе, является основой образа мышления дизайнера. Если по каким то причинам не нравится Байеc, то можно использовать Стьюдента или Бернулли для больших выборок. Если результаты 0 и 1, то точно Бернулли.

Критерий Стьюдента
Критерий Стьюдента

Стьюдент. T-тест или определение t-критерия Стьюдента является простейшим способом проверки точности среднего значения для данных с естественными значениями. Выборки должны быть независимыми (не парными) и нормально распределенными (чем больше результатов, тем ближе распределение к нормальному). Это гарантирует концентрацию плотности значений вокруг среднего значения, что позволяет делать выводы о генеральной совокупности, имея только информацию о выборке. Провести тест легко: x = rnorm(1000000) и y = rnorm(1000000). Команда t.test(x,y). Получаем следующие данные:

data: x and y
t = -1.1696, df = 2e+06, p-value = 0.2422
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
-0.004424298 0.001117388
sample estimates:
mean of x mean of y 
-0.0007571144 0.0008963404

И проверяем:

m = length(x); n = length(y)
t = ( mean(x) - mean(y) ) / sqrt( var(x)/m + var(y)/n ); t

В обоих случаях получаем t = -1.1696, и p-value = 0.2422. Так как мы всегда обеспокоены тем, является ли p больше или меньше 0,05, то при 0.2422 > 0.05 есть основания не отвергать нулевую гипотезу. P-Value это достигнутый уровень значимости (пи-величина)—наименьшая величина уровня значимости, при которой нулевая гипотеза отвергается для данного значения статистики. По P-Value происходит проверки (и отклонения) «нулевой гипотезы». Чем меньше значение Р, найденное для набора результатов, тем меньше вероятность того, что результаты случайны. Результаты считаются «статистически значимыми», когда это значение ниже 0,05 (или 5%). Если удастся довести значение до 0,005, то уже хорошо, сильно уменьшится количество позитивных неправильных результатов. При 0,05 считается нормой 1/3 неправильных выводов. В идеальном мире даже 0,005 должно быть лишь приблизительной наводкой на финальное решение. В физике или при исследовании генов используется 0.0000003. Чем ниже значение p, тем выше перевес в пользу альтернативной гипотезы.

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

Нулевая гипотеза всегда про отсутствие статистической значимости в результатах. Нулевая, так как отрицание это ноль, а согласие это единица.

Парный t-тест имеет уменьшенное количество степеней свободы, но при этом устраняется влияние индивидуальных различий. Нужен для сравнения зависимых выборок, он мощнее непарного t-теста. Зависимые выборки это когда элемент из контрольной группы имеет соответствующий ему элемент во второй группе. Например, при тесте одинаковой главной страницы в сентябре 2018 года и в сентябре 2019, или навыки дизайнеров до и после курсов. при зависимых выборках нас интересует, как изменения в одной группе повлияют на другую.

leftGroup <- c(12, 13, 11, 15, 19, 15, 17)
rightGroup <- c(14, 14, 16, 16, 18, 14, 15)
t.test(leftGroup, rightGroup, var.equal=TRUE)
t.test(leftGroup, rightGroup, paired=TRUE)

Получаем p-value = 0.5648 в первом случае и p-value = 0.4539 во втором. Очевидно, что дискриминационная способность у парного теста больше.

Следующий критерий, Уилкоксона — Манна-Уитни, позволяет протестировать, что результаты случайных наблюдений из одной группы могут быть выше, чем в другой. Это непараметрический критерий, альтернатива t-test. Идея такая: для t-test нужны нормально распределенные данные и его результаты легко интерпретировать, но он чувствителен к выбросам. Если у нас нет информации о нормальности распределения, используем критерий Манна-Уитни, имея ввиду, что он не покажет тонких различий между выборками и его сложнее интерпретировать. Причина в том, что t-критерий работает на основе сравнения средних из фактических наблюдений, в то время как критерий Манна-Уитни использует сравнение рангов (номеров наблюдения в упорядоченной выборке), что позволяет ему быть устойчивым к выбросам. Но при этом критерий Манна-Уитни весьма чувствителен к различию дисперсий, и на больших выборках это становится очень заметно. Поэтому на практике мы можем использовать t-test для оценки средних на большой выборке, так как нормальность обеспечивается «сжиманием» крайних значений по ЦПД (чем больше элементов в выборке, тем меньше дисперсия среднего арифметического). Но при этом помним, что чем больше отклонения от нормальности, тем хуже тест сравнивает выборки. Так что t-test не подойдет для небольших выборок из ненормального распределения, но подойдет для большой выборки из ненормального распределения.

T-test чувствителен к выбросам, и если есть длинный хвост в данных (а он почти всегда есть на реальных финансовых данных), то такой хвост сильно скажется на среднем, и за счет большой дисперсии получим плохой результат работы критерия. Это можно решить за счет техники бакетов, но опять же, нужна большая выборка. Маленькая выборка, и тогда у нас выбор: либо U-критерий Манна-Уитни, считаем p-value внутри дней с поправкой на множественное сравнение. Либо накопительный p-value. Либо bootstrap. Либо CUPED преобразует данные в нормальные, уменьшив отклонения от среднего, и T-Test с поправкой Уэлча.

Используем команду wilcox.test(mpg ~ am, data=mtcars) получаем сообщение, “не могу подсчитать точное p-значение при наличии повторяющихся наблюдений”. Если вас смущает это сообщение, измените команду на wilcox.test(mpg ~ am, data=mtcars, exact=FALSE). Это объяснит программе, что мы все понимаем, и не ждем точного расчета p-value. Получилось W = 42, p-value = 0.001871. Напомню, P-Value должно быть не выше 0,05.

А теперь возьмем два набора наблюдений и протестируем:

a = c(123, 105, 147, 142, 119, 129, 130, 87 ,301, 92, 177, 141, 137, 112, 138, 128, 114, 197, 198, 210, 101, 125, 134, 214, 110, 100, 152, 122, 144, 148 ,153 ,212)
b = c(154, 512, 120 ,131 ,124 ,118 ,178 ,140 ,136, 68, 162, 127, 78 ,106, 133, 655 ,155 ,169 ,199 ,108 ,143, 341 ,121 ,139, 166, 174, 184, 98, 135, 132, 146, 209)
 wilcox.test(a,b)

На выходе получаем:

data: a and b
W = 455, p-value = 0.4507
alternative hypothesis: true location shift is not equal to 0

У нас есть наше любимое P-Value, которое куда больше, чем 0,05. Мы принимаем нулевую гипотезу. W является статистической статистикой Уилкоксона и, как следует из названия, является суммой рангов в одной из двух групп.

Если р < 0,05, то нулевая гипотеза про отсутствие отличий отвергается.

С Бернулли схожая история, берет два вида данных (орел/решка, мальчик/девочка, красное/черное). rbinom(200, size = 1, p = 0.68) где p это шанс получить 1. Грубо говоря, rbinom скажет, сколько будет орлов, если сыграть определенное кол-во раз в монетку. Пример показывает сразу 200 испытаний Бернулли. Посмотрим, как мы можем сравнить два испытания Бернулли, т.к. данные очень похоже на результат бинарного A/B теста:

varA <- rbinom(200, 20, 0.5)
varB <- rbinom(200, 20, 0.25)

И используем hist(varB, probability = TRUE, col = gray(0.9)) для двух наборов данных. На графиках мы видим 200 биномиально распределенных случайных чисел для испытаний размером = 20 и с разной вероятностью возникновения интересующего нас события p = 0,25 и 0,50.

Говоря о типичных финансовых данных, где есть пик-скопление небольших платежей + длинный хвост, и нас не пугает работать с логарифмом от некой велечины, то методом Бокса-Кокса можно транформировать данные в нормально распределенные. Простой логарифм от числа. После этого берем привычный параметрический тест (t-критерий Стьюдента), смотрим p-value и оцениваем уровень значимости для закрытия эксперимента.

Процесс выглядит так: узнаем про выбросы с помощью boxplot, проверяем выборку на нормальность с помощью теста Шапиро Уилко, охарактеризовать распределение с помощью QQnorm, и выбрать метод анализа. Если данные нормальные, используем Бернулли, Гаусса, Стьюдента. Ненормальные (график сглажен по одной из сторон): Хи-квадрат, Байес, Пуассон. Обычно данные выглядят как диапазон от 0 до десятков в кучей выбросов до нескольких сотен, и это распределение Пуассона. Распределение Пуассона применяется в теории массового обслуживания. После этого оцениваем параметры, учитываем их при запуске теста хи-квадрат к выборке и распределению Пуассона, и получаем ответ про правильность гипотезы при распределение Пуассона. Но обо всем по порядку.

Не только R

Сейчас любой аналитик (не важно, UX, бизнес, дашбордист, ресерчер, разработчик) должен уметь в R и Python. Но базовый навык работы в Excel по прежнему помогает быстро решать многие задачи. При работе над продуктом требуются быстрые выводы по популярным метрикам, dau/mau, конверсия в регистрацию, и показатели метрик часто меняются. Распространенная практика это использовать среднее для всех первичных KPI, если распределение нормальное. Это позволяет делать первичные выводы очень быстро. Понять тип распределение можно с помощью стандартного/среднеквадратичного отклонения. в Excel для этого используется функция =сроткл. Формула выглядит как STD=√[(∑(x-x)2)/n], и расшифровывается как корень из суммы квадратов разниц между элементами выборки и средним, деленной на количество элементов в выборке.

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

В Excel еще много замечательных способов визуализировать данные, особенно полезны сводные таблицы. Работают по принципу вирутальной группировки строчек с одинаковыми названиями товаров. Берется группа и считается для нее сумма, очень удобно. Такие таблицы используются для агрегирования данных и получения отчета.

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

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

  1. Andrei Ivashkevich

    Здравствуйте, спасибо за статью. Есть ли способ легко уместить датасет в определенный диапазон?

    • Цветков Максим (Author)

      На примерах: создадим данные

      x <- rweibull(1000,2,66)

      Смотрим на гистаграмму, hist(x), данные не очень то нормальные. В данном искуственном случае требуется предварительно сделать логарифмирование. Или обрезать выбросы, но с пониманием проблематики: в играх выбросы или читеры, им надо уделять особое внимание. При этом нужно понимать, что выбор способа трансформация зависит от типа данных и направления асимметрии.

      Можно усложнить себе задачу:

      x <- x-5
      x <- log10(x)
      x <- na.omit(x)

      Проверим plot(density(x)), данные сместились, теперь нормализация по Боксу-Коксу не сработает.

      Пару раз встречал решение вида g <- runif(x), не надо так делать, это генерация случайных данных. А вот что-нибудь вроде

      finalData <- (x - mean(x)) / sd(x)

      уже ближе к истине. Принцип: значение минус среднее значение и разделить на стандартное отклонение переменной. Результат будет иметь среднее value = 0 и sd = 1. Строим график qqnorm(finalData). Это стандартизация данных, т.к. преобразование данных, поэтому стандартное отклонение 1.

      Можно просто привести все цифры к диапазону от 0 до 1, возможно, вы это и имели ввиду.

      x <- (x - min(x)) / (max(x) - min(x))

      Также, распространенный способ это x[!x %in% boxplot.stats(x)$out], который также не меняет данные, но фильтрует.

  2. Даня

    Здравствуйте. Есть ли варианты выгрузить данные из GA напрямую, без CSV?

    • Цветков Максим (Author)

      Сначала нужно установить правильные библиотеки, install.packages("RGA") и install.packages("devtools"). И подключить library(devtools), library(RGA). Даем доступ к данным в GA authorize(username = "ваш gmail"), вас перебросит в браузер и будет сформирован код, который нужно будет вставить в R. Будет создано окружение .RGAEnv, которое не видно пользователям.

      В скриптах часто нужно прописывать проверку существования объекта:

      x <- NA

      и затем exists("x").

      list_profiles() позволит определить идентификатор профиля, с которым вы планируете работать. И можно потестировать работу командой get_ga(), сделав запрос данных у Core Reporting API, и получить информацию вида users 333/sessions 486/pageviews 1505 по первому найденному profileId. Подробный хелп будет по команде browseVignettes(package = "RGA").

      И финальная команда,

      ga_data <- get_ga(
          profileId = "ga:xxxxxxxx",
          start.date = "2017-04-15",
          end.date = "yesterday",
          metrics = "ga:visits,ga:users", 
          dimensions = "ga:source",
          segment = "",
          filter = "ga:medium==organic"
      )

      Запросы можно формировать сколько угодно сложные, т.к. API очень хорошее.

      ga_data <- get_ga(profileId = "ga:xxxxxxxx", 
                        start.date = "2018-10-08", 
                        end.date = "yesterday",
                        metrics = "ga:sessions", 
                        dimensions = "ga:date",
                        filters = "ga:sessions > 0")

      Работать с регулярными выражениями

      ga_data <- get_ga(profileId = "ga:xxxxxxxx", 
                        start.date = "2016-01-01", 
                        end.date = "yesterday",
                        metrics = "ga:sessions", 
                        dimensions = "ga:keyword",
                        filters = "ga:keyword != (not provided) 
                        && ga:keyword != (not set) 
                        && ga:keyword != (other)",
                        sort = "-ga:sessions")

      При работе с регулярными выражениями можно использовать следующие операторы:
      = — точное соответствие;
      != — отсутствие равенства;
      =@ — содержит подстроку;
      !@ — не содержит подстроку;
      =~ — соответствует регулярному выражению;
      !~ — не соответствует регулярному выражению.

      И потом это все легко визуализируется

      install.packages("ggplot2")
      library(ggplot2)
      ggplot(ga_data, aes(date, sessions)) + geom_line()

      Можно строить сложные графики:

      ga_data <- get_ga(
          profileId = "ga:код_GA",
          start.date = "14daysAgo",
          end.date = "yesterday",
          metrics = "ga:visits,ga:users,ga:sessions,ga:bounces", 
          dimensions = "ga:date,ga:source,ga:medium",
          sort = "-ga:date",
          segment = "",
      )
      ggplot(ga_data, aes(medium, visits)) + geom_line() + geom_boxplot(fill = "green") + ylim(0, 10) + theme_dark(base_size = 14)

      • Артём Клевцов

        Спасибо, я пытался по одной статье делать, не получалось, ваш пример сработал. Буду разбираться дальше. Мне выпало очень много результатов not provided, что это такое?

        • Цветков Максим (Author)

          Большое количество not provided встречается при большом количестве органического трафика. т.к. речь только об органическом трафике, проплаченный трафик Adwords будет показывать слова в PPC.
          Можно с помощью фильтра извлечь все «not provided», это поможет понять, откуда шел трафик.

          Еще можно настроить фильтры таким образом, чтобы было видно, на какие страницы приходят пользователи из not provided.

          В Google Search Console также много ключевых слов, и в нее можно дозагрузить слова из метрики.

  3. Денис Ильин

    Есть ли способ красиво выстроить по возрастающей диапазон дат?

    • Цветков Максим (Author)

      Первое, что приходит на ум:

      newData <- sample(newData)
      colorsSet <- brewer.pal(12, "Paired")
      plot(newData, pch=19, cex=6, col=colorsSet)

      А как украсить, тут довольно субъективный вопрос, что такое красиво. Попробуйте использовать библиотеку для красивых цветов:

      newData <- sample(newData)
      colorsSet <- brewer.pal(12, "Paired")
      plot(newData, pch=19, cex=6, col=colorsSet)

      После того, как выберите подходящие вам цвета, используйте следующую команду. Я добавил немного случайности порядку данных:

      newData <- sample(newData)
      colorsSet <- brewer.pal(12, "Paired")
      plot(newData, pch=19, cex=6, col=colorsSet)

  4. Max Proskin

    Для использования Фишера и Хиквадрата на матрицу данных из двух наблюдений требуется много или мало данных? Если данные из опросника и содержат данные для метрик типа SUS SUM. И как лучше визуализировать такого рода данные?

    • Цветков Максим (Author)

      Сгенерируем матрицу распределения двумерных данных: matrixData = matrix(1:9,800,2). Функция берет вектор и преобразовывает в матрицу с колонками. Предположу, что наблюдения являются независимыми и мы можем смело использовать Хи-квадрат или Фишера. Числа обязаны быть абсолютными и качественными. Возьмем Хи-квадрат для проверки нормальности распределения данных chisq.test(matrixData). RStudio предупредит о возможной некорректности результатов, потому что хи-квадрат плохо работает, если числа небольшие.

      Возьмем другую матрицу данных: matrixData = matrix(1:5,6,2). Запускаем тест Фишера: fisher.test(matrixData). Фишер это F-тест для сравнения дисперсий двух и более генеральных нормально распределенных небольших по объему данных. Не содержит правила, что p-value должно быть фиксированным значением для принятия нулевой гипотезы. Или критерий хи-квадрат Пирсона с коррекцией непрерывности Йейтса chisq.test(matrixData, simulate.p.value = TRUE). Симуляции нужны для нахождения P-value, позволяет не полагаться на приближение хи-квадрата к распределению тестовой статистики, и искать p-value точнее за счёт применения генератора случайных чисел. Видно, что полученный P-value 0.6436 намного больше, чем 0.05, значит, выборка не противоречит нулевой гипотезе.

      Если же нужно сравнить медианы, в статистике существует критерий для медиан Муда, который не очень популярен на рынке из-за большой ошибки второго рода. Но все же пример приведу. Допустим, есть две выборки по использованию продукта в Cеверной и Западной Европах. Из коробки теста Муда в R-Studio нет, но его можно симулировать следующим образом:

      x1 <- 1:30
      x2 <- 1:54
      m <- median(c(x1,x2))
      set1 <- sum (x1>m)
      set2 <- sum (x2>m)
      set01 <- sum(x1<=m)
      set02 <- sum(x2<=m)
      finalData <-matrix(c(set1,set2,set01,set02), nrow=2, ncol=2)
      chisq.test(finalData)

      В приведенном примере P-value = 0.01225, который меньше чем альфа 0.05 (ошибка первого рода), гипотеза о равенстве медиан отвергнута. Значит, использование продукта отличается. Но лучше проверить такие данные критерием Уилкоксона — Манна-Уитни. Он не проверяет разность медиан и работает только с рангами, но проверяет вероятность как часто X < или > Y. При использовании wilcox.test(x1, x2,alternative="two.sided", exact=TRUE, correct=FALSE) получаем p-value = 0.0007761, который намного меньше альфы.

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

      Для визуализации подойдут коррелограммы/хитмапы.

      install.packages("corrplot")
      m <- matrix(seq(-1,1,length.out=9),nrow=3)
      corrplot(m, method = "pie")

      Можно получить очень красивые графики:

      K <- matrix(runif(48),2,24)
      corrplot(K, method = "pie")

  5. Виктор

    Не могу настроить прокси на Mac под R, что ни делал постоянно вываливаются ошибки с недоступностью серверов. Вручную тоже не получается устаноить пакеты. Можете посоветовать что нибудь?

    • Цветков Максим (Author)

      Заходите в Chrome по адресу chrome://net-internals/#proxy, видите ссылку script:http://wpad/wpad.dat , открываете и видите свой прокси.

      В терминале для включения прокси:
      export http_proxy=http://address.ru:8080
      export HTTP_PROXY=http://address.ru:8080

      Можно с именем и паролем:export http_proxy='http://username:password@address.ru:port/'
      Отключить можно командой: export http_proxy=''

      Не будет лишним проверить в System Preferences → Network → LAN → Advanced → Proxies → включена ли галочка Auto Proxy Detection. Command Line не используют эти настройки, но для другого софта пригодится.

      Далее в RStudio: Sys.setenv(http_proxy="http://address.ru:8080")
      Для удаления созданного окружения в R: Sys.unsetenv
      Проверить, что мы насоздавали: Sys.getenv("http_proxy")

      Еще одно решение это установить cntml: brew install cntlm.

  6. Evnegy Shel

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

    • Цветков Максим (Author)

      Простой Shapiro-Wilks-W-Test, самый популярный способ, даже популярнее Хи-квадрата:

      x <- rnorm(450)
      shapiro.test(x)

      Получаем W = 0.99518, p-value = 0.1758. P-value больше чем 0.05 говорит нам, что у нас нормально распределенные данные. Если бы p-value было меньше 0.05, мы бы отвергли нулевую гипотезу. Как и hist(x) в виде колокола. При UX-анализе принято брать уровень значимости 0.05. Если 0.49 и меньше, то различия значимы. Это все характеризует нормальное распределение. Визуально оно очень похоже на T-распределение, которое имеет дополнительный параметр df для степеней свободы (кол-во значений в распределении, свободных для изменения). Если число степеней свободны низкое, то мы получаем слишком высокие/низкие значения. Пограничное значение это df ≈ 30, при котором распределение становится похожим на нормальное.

      x <- seq(-3,3, length=450)
      plot(x, dnorm(x), type="l", col="blue")
      lines(x,dt(x, df=1), col="cyan")
      lines(x,dt(x, df=32), col="red")

      На полученном графике видно, что синяя линия (нормальное распределение) и красная линия (t-распределение с df=32) очень схожи по форме.

      Равномерно распределенные данные можно сгенерировать и сравнить с помощью runif, в котором r обозначает случайное, а unif равномерное.

      y <- runif(450)
      par(mfrow=c(1, 1))
      boxplot(x, y, notch=TRUE, col=(c("gold","darkgreen")))

      Или даже гистограммы, для наглядности:

      Скорее всего, реальные данные не будут лежать в диапазоне от 0 до 1, поэтому потребуется нормализация (в примере диапазон значений от 15 до 100):

      xmin <- 15
      xmax <- 100
      y <- runif(450) * (xmax - xmin) + xmin

      Все вспомогательные значения легко считаются в R:
      sd(y) стандартное отклонение
      range(y) диапазон (разница между максимум и минимумом)
      IQR(y) межквартильный размах (когда есть выбросы)
      quantile(y, c(0.25, 0.75)) квантили
      mad(y) абсолютное медианное отклонение

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

      Отдельно скажу про удаление выбросов: на реальных задачах просто взять и удалить выбросы из набора данных нельзя, особенно в eCommerce. Допускается только убрать те наблюдения, которые оказались за 3 стандартных отклонения от среднего, и только если допускается лишиться кучи данных с китами. Еще можно получить логарифм после преобразования с помощью Бокса-Кокса, но тогда мы получим неестественную метрику. Поэтому используем bootstrap, получаем два распределения и на основе квартильной разницы делаем сравнение.

      • Alexey Sarapulov

        Спасибо за команды для получения данных! а есть такие же для вывода самого малого и самого большого значения?

        • Цветков Максим (Author)

          Давайте создадим какой-нибудь набор данных:

          x <- 1:30
          y <- 0.5 + 0.75 * x + rnorm(x)
          ourData <- lm(y ~ x)

          И выполним команду summary(ourData). В результате получим min, max, mean, median, квантили, стандартные ошибки параметров и уровней значимости:

      • Александр Верен

        Спасибо, а какой диаграммой вот также сравнить много боксплотов с плотностью вероятностей?

        • Цветков Максим (Author)

          Я так понял, вам нужно понять, где больше значений: вокруг медианы или данные сгруппированы вокруг минимума и максимума, а в середине ничего нет. Хорошей альтернативой box-plot служит violin plot, отличаются они тем, что violin plot дополнительно показывает плотность вероятности распределения данных.

          library(ggplot2)
          x<-runif(256,0,1) * 0.65 / 3
          y<-runif(128,0,5)
           
          xbin <- cut(x = x, breaks = seq(0,1,0.1),include.lowest = T,labels=seq(0.05,0.95,0.1) )
          df<-data.frame(x=x,y=y,xbin=xbin)
           
          ggplot( data = df, aes( x = x, y = y, colour = xbin)) + geom_violin(draw_quantiles = c(0.25, 0.5, 0.75)) + geom_point( alpha = 0.5 )

  7. Виктор Микронов

    Если данные распределены ненормально, но результат выдать нужно, то что делать?

    • Цветков Максим (Author)

      Первое это подумать о трансформации данных с через Box–Cox, но это рискованная история. Либо работать непараметрическими критериями на основе рангов: W-критерий Вилкоксона или U-критерий Манна-Уитни). В R за оба отвечает wilcox.test для двух выборок и тест Крускала-Уоллиса kruskal.test для большего кол-ва выборок (дисперсионный анализ). Если при верной нулевой гипотезе вероятность получить высокое значение Н-критерия крайней мала, можно эту гипотезу отклонять. Тест Краскела-Уоллиса, как любой дисперсионный, выдет результат вида р < 0.05 (группы различаются) или р > 0.05 (различий нет), не говоря нам ничего о том, в каких именно группах есть различия.

      Давайте создадим какую-нибудь матрицу:

      x  <- matrix(runif(12*100), ncol=2)
      wilcox.test(x, paired = FALSE, exact = FALSE)

      Вилкоксон сравнивает не сами значения, а их ранкинг. Получаем V = 720600, и вероятность случайно получить такое значение при условии, что нулевая гипотеза верна, не больше 0.05 (p-value = 2.2e-16, а это наименьшее число больше нуля, 2.2e-16 < 0.05). Отклоняем нулевую гипотезу. Ремарка: статистические методы, основанные на рангах, не очень хорошо подсчитывают точные p-value при наличии одинаковых значений в данных. Чуть получше с этим у wilcox_test() из пакета coin, за счет аппроксимации распределения критерия Уилкоксона нормальным распределением.

      install.packages("coin") 
      library(coin)
      testDataRnd <- data.frame( x = c(rnorm(n = 40, mean = 14, sd = 4), rnorm(n = 23, mean = 17, sd = 4)), g = c(rep("a",times = 40), rep("b",times = 23)) )
      keepIt <- wilcox_test(testDataRnd[,1] ~ testDataRnd[,2])
      keepIt
       
      keepIt@statistic@teststatistic

      Последней строкой мы вернули z-value из результатов Манна-Уитни.

      Если нужно сравнить именно средние при ненормальном распределении, то при большом кол-ве данных (больше хотя бы 400 наблюдений) можно и t-критерий Стьюдента, так как в силу центральной предельной теоремы будет примерно нормальное распределение. Либо попросту сравнивать медианы.

  8. Anatoly Malanii

    Как делается графический способ проверки гипотез A/B теста?

    • Цветков Максим (Author)

      Предположим, что данные нормально распределены и среднее является адекватной мерой центральной тенденции. Тогда возникает вопрос, а как можно сравнить два показателя: только подсчитать разницу между ними.

      Если вы провели мультивариативный A/B тест, то для каждого варианта надо посчитать CTR и построить графики. График: 95% доверительный интервал для разности средних и медиан (можно бутстрапом), и смотреть, захватывает ли он 0.

  9. Андрей

    Есть ли способ протестировать разность между двумя значениями медианы?

    • Цветков Максим (Author)

      Да, с помощью t-критерия. Это однонаправленный:

      d1 <- rnorm(12, 5.2, 1)
      d2 <- rnorm(11, 5.1, 1)
      t.test(d1, d1)

      А вот двунаправленный:

      var.test(d1, d2)

      В результате две выборки значительно различаются (р < 0,05). Следует отметить, что в R не «нормальный» t-критерий, а критерий Уэлча, для которого не важно, чтобы дисперсии обоих выборок были одинаковые. Поэтому может подойти F-тест на идентичность обеих дисперсий:

       t.test(d1, d2, var.equal=TRUE)

      А дальше все просто, если p < 0,05, то данные в двух выборках значительно отличаются, поэтому необходимо использовать критерий Уэлча, который я привел выше. Но если p > 0,05, тогда дисперсии примерно равны (но не факт), и может быть применен t-критерий:

       t.test(d1, d2, var.equal=TRUE)

      Если понадобится уменьшить дисперсию, то используйте стратификацию (дробление пользователей на группы, кластеризация для выделения кластеров), использование низкоуровневых метрик (урезание концов), исторических данных (CUPED от Microsoft).

  10. Самюэль Виктор

    Я все же не совсем понимаю, вот у вас в статье тест Шапиро Уилка, у него можно смотреть на p-value, или только на W? И что мне делать с результатами, как понять какой из вариантов a/b теста верный?

    • Цветков Максим (Author)

      Общее правило по прежнему работает:
      ◘ Если результат p> 0.05, значит данные не отличаются от нормального распределения.
      ◘ Если результат р< 0,05, значит данные значительно отличаются от нормального распределения.
      ◘ Для shapiro.test(data) результат вида W = 0.98134, p-value = 0.961 будет близок к нормальному распределению.

      Но если p-value (тестовая статистика) ≤ α: данные не соответствуют нормальному распределению + всегда нужно наносить данные на график для проверки. Графики нужны, так как при большой выборке даже малые отклонения от нормальности дадут о себе знать. Это ответ на вопрос, данные распределены симметрично или есть отложение влево/вправо.

      row1 <- c(29,30,34,34,45,43,33,43,48,23,27,38,32,36,35,34,33)
      row2 <- c(34,32,31,35,55,46,43,45,30,26,27,23,47,37,29,30,60)
      boxplot(data.frame(row1, row2))
      summary(data.frame(row1, row2))
      //для графиков
      hist(row1)
      boxplot(data.frame(row1, row2))
      qqnorm(row1)
      qqline(row1)

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

      Отложения в левую сторону есть, поэтому для определения независимости выборок используется Хи-квадрат.

      chisq.test(data.frame(row1, row2))

      Обычно желаемый результат это чтобы альтернативная гипотеза была верной, в нашем случае p-value = 0.1517 выше уровня значимости 0.05. Значит мы принимаем нулевую гипотезу и заключаем, что две переменные независят друг от друга. Если расценивать два набора данных выше как количество должников за ЖКХ в Восточной Европе и Западной Европе, то принимаем гипотезу H0: среднее количество должников для двух выборок никак не связано.

      Теперь сравним две независимые выборки. Можно использовать t-тесты, они сравнивают средние значения двух групп или одно среднее значение с гипотетическим средним. T-test требует нормального распределения, зависит от df (степеней свободы), и в принципе мы могли бы его использовать: t.test(row1, row2, paired = FALSE); lm(row1 - row2 ~ 1). Но раз мы решили, что наши данные ненормальные, нам нужны непараметрические тесты. Wilcoxon или Quade нам поможет, все равно они эффективнее, чем Стьюдент.

      wilcox.test(row1, row2, paired = TRUE, exact=FALSE)

      Тест противопоставляет row1 и row2, так как они независимые. В результатах смотрим на V = 52.5, p-value = 0.4374, первое это минимальная сумма рангов. Тест проверяет, что наблюдения в одной группе с вероятностью 50% больше, чем наблюдения в другой группе. Это не сравнение медиан, а сравнение средних.

      P-value это накопительная величина, и на реальных проектах мы можем себе позволить ориентироваться на неё только когда она уходит в плато. Поэтому не забываем проверять выбросы в p-value по каждому дню после теста, иначе peeking problem. Есть понятие сходимости, это как раз уходящий в плато p-value. Если количество данных, которые мы выяснили заранее для запуска эксперимента соответствует тому, что получили по факту, и статистика уже дней 5-7 в плато, то эксперимент можно закрывать.

      Если есть вопросы по командам, то help(t.test) и help(wilcox.test) могут помочь.

      • Алексей

        Здравствуйте. Статья выглядит очень полезной и умной, но все же, можно на простом примере, как проверить различия между выборками? Спасибо!

        • Цветков Максим (Author)

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

          dat <- data.frame(landing1 = c(2141, 187, 54),
                            landing2 = c(1875, 201, 65),
                            row.names = c("showed", "bought", "installed"))
          dat_t <- data.frame(t(dat))
          prop.test(dat_t$'bought', dat_t$'showed')

          На выходе:
          prop 1 ⠀⠀⠀⠀ prop 2
          0.08734236⠀⠀ 0.10720000

          Делаем вывод, что конверсия в клики выше у лендинга 2. А p-value = 0.0383, что меньше порогового значения 0.05, значит данные статистически значимы.

          Для расчета размера выборки можно пойти таким путем. Мощность обычно берут как 0.8, при огромной выборке можно и 0.99.

          power.prop.test(p1 = .06, p2 = .08, power = .85)

          А вот размер эффекта не считается, а подбирается из опыта. У нас это .02, (разница между .06 и .08). Чем меньше размер эффекта, тем большая выборка нужна.

          И самое сложное, а именно дизайн теста. Помимо A/B, существуют A/A/B, A/B/n, факторные, multivariate и многие другие. Multivariate test (MVT) нужен для каждое изменение индивидуально, изолированно от других изменений, перемешивая все возможные комбинации изменений. Давайте на примере:
          ● Изменение A: меняем ссылки на подчеркнутые
          ● Изменение B: переделываем кнопки с контурных на залитые
          ● Изменение C: увеличиваем межстрочный интервал
          ● Изменение D: добавляем скелетоны

          Вывод должен быть примерно таким:
          ● Изменение A: +3%
          ● Изменение B: +12%
          ● Изменение C: -20%
          ● Изменение D: +1%

          Классический A/B тест показал бы 3+12-20+1 = -4%, то есть вариант со всеми правками не идет в работу. В результатах MVT должны быть и данные вида A+B= +15%, A+D = +4%, A+B+D = 16% и так далее. Но для достижения статистической значимости каждого результата потребуется тонна трафика.

          Приемлемая альтернатива это factorial design. Мы тестируем множество вариантов, как в примере выше, но не все подряд. И получаем нечто вроде:
          A: Изменение A = +3%
          B: Изменение A + B = +15%
          C: Изменение A + B + C = -17%
          D: Изменение A + B + C + D = -4%

  11. Vyacheslav Egorov

    Такой вопрос. Есть результаты A/B теста, средние очень близки. Но по тестам одна выборка нормальная, а другая нет. как такие результаты интерпретировать?

    • Цветков Максим (Author)

      От теста зависит) сравнивать средние можно, с точки зрения математики. Просто вопрос в том, что вы сравниваете? Количество ролей Николаса Кейджа и смертность от утопления после падения в бассейн, или разные значения ARPU? Для Стьюдента обе выборки должны быть нормально распределены. Можно не заморачиваться, и взять непараметрический ранговый U-критерий Манна-Уитни, если выборки независимы. Основной минус, это нулевая гипотеза сформулирована относительно медианы, а не среднего.

      Либо использовать подход из мира консалтинга: берем ЦПД (должна быть большая выборка), берем из большой выборки несколько выборок поменьше, и их распределение станет нормальным.

  12. Отличная статья! Спасибо! Максим, не подскажете, как считать p-value при расчете bootstrap на мультивариантном тесте?

    • Цветков Максим (Author)

      Каждый вариант сравнивается с каждым, и поправка на множественное сравнение для p-value (в итоге получится меньше, что довольно проблемно). Это из-за того, что надо не просто принять решение о победителе, а еще понять, кто из победителей самый победитель. P-value = 0,05 это ок при A/B, но уже на трех вариантах P-value = 0,025, и выборка понадобится космическая. Поэтому берутся поправки, вроде метода Бонферрони, либо лучше поправку Бенджамини-Хохберга (можно применить далеко не всегда).

  13. Сергей

    Спасибо, Максим, за статью с примерами в коде!

    Пытаюсь разобраться с CUPED и постстратификацией, но не могу понять уменьшается ли дисперсия только для continous метрик.

    Помогают ли эти методы в случае с обычной конверсией в покупку (binomial метрики) ?

    PS Где кнопка подписаться на новые статьи?))

«Взаимодействуя с данным сайтом, вы, как пользователь, автоматически даете согласие согласие на обработку персональных данных» Согласие

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.