Проверка результатов 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 тесты для бинарных задач проводить просто (выполнено/не выполнено).

При проверке гипотезы помним, что нулевая гипотеза про отсутствие отличий. Альтернативная гипотеза о значимости различий. Ошибка первого рода происходит, когда мы отклоняем нулевую гипотезу в пользу альтернативной гипотезы, при условии что справедлива нулевая гипотеза. Это та самая альфа 0,05, которая говорит что в одном случае из 20 будет ошибка первого рода. Ошибка второго рода происходит, когда верна альтернативная гипотеза, но было принято решение принять нулевую гипотезу.

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

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

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

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

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

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

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

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

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

Выбросы надо смотреть на 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, то выборка точно нормально распределена. Все, что выше 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, то можно использовать Стьюдента или Бернулли для больших выборок.

Стьюдент. 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

Видим 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, тем выше перевес в пользу альтернативной гипотезы.

Парный 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-критерий работает на основе сравнения средних из фактических наблюдений, в то время как критерий Манна-Уитни использует сравнение рангов (номеров наблюдения в упорядоченной выборке), что позволяет ему быть устойчивым к выбросам.

Используем команду 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) где  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.

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

Не только R

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

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

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

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

26 комментариев

  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) абсолютное медианное отклонение

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

      • 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 для большего кол-ва выборок.

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

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

      Вилкоксон сравнивает не сами значения, а их ранкинг.

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

  8. Anatoly Malanii

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

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

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

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

  9. Андрей

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

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

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

      x <- rnorm(10, 5, 1)
      t.test(x, mu=5)

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

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

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

      var.test(d1, d2)

      А дальше все просто, если 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% больше, чем наблюдения в другой группе. Это не сравнение медиан, а сравнение средних.

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

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

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