Был запущен сплит-тест (а/б-тест), направленный на улучшение платежной активности пользователей. Вам дан датасет с транзакциями пользователей до и во время теста в контрольной и тестовых группах.
Нулевая гипотеза Все версии сайта имеют одинаковую вероятность значения показателя платежной активности пользователей (или конверсии в покупки). Другими словами, нет эффекта или нет разницы между версиями.
Альтернативная гипотеза Версии сайта имеют разную вероятность значения показателя платежной активности пользователей (или конверсии в покупки). Между версиями есть статистически значимая разница.
Сравнение групп по метрикам количества и доли платящих. Показатель конверсии (CR) значимо различается в тестовой группе 1 (test1 CR = 28%). Доход от покупок также значимо различается в первой группе (test1), соответственно доход с пользователей, а также доход с платящих пользователей у этой группы самый высокий по сравнению с другими группами (ARPU = 19%, ARPPU = 69%). Кроме сравнения трат пользователей на сервисе, была выдвинута гипотеза о том, что средний чек может не измениться, но может вырасти число людей, которые тратят больше определенного значения. Первая тестовая группа (test1) также показала самый высокое количество пользователей, которые тратят больше верхнего порога суммы (3 квартиль). Иными словами, сумма высоких трат пользователей выше всего в группе test1.
Сравнение групп по транзакционной активности и конверсии в разные типы контента. Тестовая группа 4 (test4) показала значимое увеличение по конверсии в покупки контента dto (33%), но показала результаты ниже ожидаемых по конверсии в подписку (subscription). По показателю конверсии в подписки лучшие результаты (38%) показала контрольная группа (control). Так как подписки представляют собой самый доходный тип контента, то можно предположить что это влияет на то, что у контрольной группы (control) самый высокий показатель среднего чека (38.62 руб.). По показателю распределения среднего чека на количество покупок, была выдвинута гипотеза, что наибольшую ценность для нас представляют пользователи, которые платят нам 2-3 раза, так как помимо подписки они могут дополнительно два-три раза в месяц покупать дополнительный контент.
В сравнении по количеству и доле платящих, а также дохода с пользователей (ARPU, ARPPU), тестовая группа 1 показала наилучшие результаты.
По показателю конверсии в высокомаржинальные типы контента (подписки) лучшие результаты показала контрольная группа (AC = 38.62 руб.)
Перед тем, как делать выводы о результатах тестирования, надо убедиться, что данные о пользователях не содержат ошибок, которые могут исказить наши результаты. Поэтому, начну с вопроса о том, можем ли мы как-то оценить из этих данных равномерность и валидность распределения юзеров по разным группам.
Начнем с первого датасета users, в котором содержится информация о самом сплит-тесте.tag | ts | user_uid | registration_time | conv_ts | |
---|---|---|---|---|---|
Length:696982 | Min. :30132 | Length:696982 | Min. :25034 | Min. :25488 | |
Class :character | 1st Qu.:30135 | Class :character | 1st Qu.:27932 | 1st Qu.:29023 | |
Mode :character | Median :30140 | Mode :character | Median :29151 | Median :29671 | |
NA | Mean :30141 | NA | Mean :28743 | Mean :29312 | |
NA | 3rd Qu.:30146 | NA | 3rd Qu.:29787 | 3rd Qu.:30023 | |
NA | Max. :30159 | NA | Max. :30246 | Max. :30247 | |
NA | NA | NA | NA’s :497 | NA’s :285 |
Теперь удалим дубликаты, без которых не обходится почти ни один датасет. Проверим:
есть ли юзеры, которые попали в несколько тестовых выборок (для таких пользователей результаты будут не валидны, их придется удалить)
есть ли дублирующиеся характеристики, которые мешают интерпретации результатов. Причиной дубликатов в датасете users была колонка времени добавления в тест (ts) - оставляю максимальную отметку (т.е. время последнего добавления).
#удалить юзеров, которые попали в несколько тестов сразу (1461 юзер)
users_stats = users %>% count(tag, user_uid) %>% count(user_uid) %>% filter(n > 1)
users = users %>% filter(!user_uid %in% users_stats$user_uid)
#удалить дублирующиеся значения времени регистрации (ts) юзеров, оставить максимальные знаения ts
users = users %>% group_by(tag, user_uid) %>% top_n(1, ts)
Теперь поищем менее очевидные аномалии. Сравним например, есть ли отрицательная разница между датой добавления в тест и временем регистрации. Время добавления в тест (ts) всегда должно быть больше, чем время регистрации - но есть юзеры, у которых время добавления в тест происходит раньше, чем время регистрации (whaaat?). Я не знаю, как собирались данные, поэтому могу предположить, что это либо системная ошибка, либо ошибка линейной трансформации, либо это сгенерированные данные (:
users$ts_difference = users$ts - users$registration_time
users = users %>% filter(!ts_difference < 0)
Сильных отклонений в средних значениях времени добавления в тест нет, все группы были запущены одновременно.
users %>% group_by(tag) %>% summarise(mean_ts = mean(ts)) %>% kbl() %>% kable_styling()
tag | mean_ts |
---|---|
control | 30141.13 |
test1 | 30141.15 |
test2 | 30141.15 |
test3 | 30141.16 |
test4 | 30141.16 |
Теперь посчитаем пропорцию пользователей в каждой группе. В 5 группах пропорция пользователей составляет ~ 20% от общей выборки, значит пользователи разделены равномерно.
users %>% group_by(tag) %>% summarise(n = n()) %>% mutate(perc = (n/sum(n))*100) %>% kbl() %>% kable_styling()
tag | n | perc |
---|---|---|
control | 138147 | 19.94200 |
test1 | 139245 | 20.10050 |
test2 | 138539 | 19.99859 |
test3 | 138390 | 19.97708 |
test4 | 138423 | 19.98184 |
Посмотрим на распределение внутри групп. Если предположить, что пользователи до теста были взяты случайно, то и значения признаков будут распределены относительно равномерно. Чтобы проверить это предположение, нам нужно соединить оба датасета.
purchases = purchases[!duplicated(purchases),]
joined = left_join(users, purchases, by = "user_uid")
#NA в колонке time, то есть есть юзеры, которые так и не совершили ни одной транзакции во время теста
#91 человек так и не совершили даже первой покупки (NA в conv_ts)
summary(joined) %>% kbl() %>% kable_styling()
tag | ts | user_uid | registration_time | conv_ts | ts_difference | time | consumption_mode | element_uid | price | |
---|---|---|---|---|---|---|---|---|---|---|
Length:1059470 | Min. :30132 | Length:1059470 | Min. :25034 | Min. :25488 | Min. : 0.002 | Min. :30106 | Length:1059470 | Length:1059470 | Min. : 21.1 | |
Class :character | 1st Qu.:30134 | Class :character | 1st Qu.:27725 | 1st Qu.:28887 | 1st Qu.: 370.000 | 1st Qu.:30119 | Class :character | Class :character | 1st Qu.: 26.8 | |
Mode :character | Median :30139 | Mode :character | Median :29041 | Median :29606 | Median :1099.987 | Median :30132 | Mode :character | Mode :character | Median : 32.7 | |
NA | Mean :30140 | NA | Mean :28670 | Mean :29198 | Mean :1469.795 | Mean :30133 | NA | NA | Mean : 36.9 | |
NA | 3rd Qu.:30143 | NA | 3rd Qu.:29769 | 3rd Qu.:30014 | 3rd Qu.:2413.759 | 3rd Qu.:30146 | NA | NA | 3rd Qu.: 44.5 | |
NA | Max. :30159 | NA | Max. :30158 | Max. :30247 | Max. :5104.950 | Max. :30159 | NA | NA | Max. :402.8 | |
NA | NA | NA | NA | NA’s :91 | NA | NA’s :398307 | NA | NA | NA’s :398307 |
#создадим колонку, которая маркирует транзакции до и после добавления в тест
joined$before_after = ifelse(joined$time < joined$ts, "before", "after")
table(joined$before_after) %>% kbl() %>% kable_styling()
Var1 | Freq |
---|---|
after | 276975 |
before | 384188 |
#заменим NA на 0 в колонке с транзакциями и ценами
joined$time[is.na(joined$time)] <- 0
joined$price[is.na(joined$price)] <- 0
#пометим платящих и неплатящих пользователей
joined$converted = ifelse(joined$time == "0", "0", "1")
#сложим их в отдельный датасеты
joined_before = joined %>% filter(before_after == "before")
joined_after = subset(joined, before_after == "after" | time == "0")
Посмотрим, сколько приходится транзакций на группы пользователей до теста. Причина выбора непараметрического теста – признак распределен ненормально, количество покупок можно представить в виде распределения Пуассона. По результатам теста на критерий Краскелла-Уоллиса (p-value > 0.05), транзакционная активность между группами не различается, они выбраны равномерно.
b = joined_before %>% select(tag, user_uid, time) %>% group_by(tag, user_uid) %>% count()
kruskal.test(b$n ~ b$tag)
##
## Kruskal-Wallis rank sum test
##
## data: b$n by b$tag
## Kruskal-Wallis chi-squared = 1.5646, df = 4, p-value = 0.8151
Вторым важным показателем для дальнейших расчетов является сумма, которую потратили пользователи в группах. Тест также показал, что медианное значение чека в группах не различается (p-value > 0.05).
#сколько потратили юзеры по группам
b = joined_before %>% select(tag, user_uid, price) %>% group_by(tag, user_uid) %>% summarise(sum_price = sum(price))
kruskal.test(b$sum_price ~ b$tag)
##
## Kruskal-Wallis rank sum test
##
## data: b$sum_price by b$tag
## Kruskal-Wallis chi-squared = 0.23626, df = 4, p-value = 0.9935
проверить наличие дубликатов и различных аномалий в данных, и удалить, если они затрудняют дальнейший анализ
посчитать пропорцию юзеров (желательно равномерное разделение – для 5 тестовых групп ~ 20% юзеров от генеральной совокупности в каждой группе), а также посмотреть, однородны ли их показатели до начала тестирования
убедиться в том, что юзеры были добавлены в группы тестирования примерно в одно и то же время, и отсечь события, которые происходили с юзерами до отметки начала тестирования
*если не брать в расчет те данные, которые имеются сейчас - можно оценить то, насколько размер выборки позволяет оценить размер эффекта на целевую метрику. Строго говоря, что я не могу оценить, насколько моей выборки достаточно для того, чтобы различия между метриками были статистически значимы. Если мы собираемся применять классические статистические критерии, основанные на частотности, то размер выборки для тестирования и срок сбора данных, достаточный для достижения необходимой величины, определяется заранее.
Какие вы можете сделать выводы? Какая группа показала лучшие результаты?
Цель проведения сплит-тестирования - улучшение платежной активности пользователей. Предположим, было создано 4 разные версии сайта, которые каким-то образом побуждали пользователей платить больше (например, предложения со скидкой или акции).
Платежная активность - понятие растяжимое, поэтому разобьем ее на метрики, через которую мы можем ее измерить.
Когда речь идет о метриках, связанных с монетизацией, обычно имеют в виду следующие:
Общий доход (Gross, Revenue) и средние величины: ARPU, ARPPU, Average Check (AOV)
Количество и долю платящих: Paying Users, Paying Conversion, Количество покупок
Помимо этих базовых метрик можно сравнить другие метрики, косвенно связанные с платежной активностью юзеров:
Как различается вид потребления контента в группах и какая у них конверсия – Selling Content
Тут следует учитывать, что когда мы сравниваем траты пользователей на сервисе, то средний чек может не измениться, но может вырасти число людей, которые тратят больше определенного значения (например, больше среднего значения чека).
#Общее число юзеров в каждой группе (total)
stat = joined_after %>% select(tag, user_uid) %>% unique() %>% group_by(tag) %>% summarise(total = n())
stat$perc = round(stat$total/sum(stat$total)*100, digits = 2)
#Количество юзеров, которые совершили хотя бы 1 транзакцию за время теста и доля от общего количества юзеров в тесте (paying users, conversion rate)
n = joined_after %>% filter(converted == "1") %>% select(tag, user_uid) %>% unique() %>% group_by(tag) %>% summarise(paying_users = n())
stat = left_join(stat, n)
stat$conv_rate = round(stat$paying_users / stat$total*100, digits = 2)
#Юзеры, которые ничего не заплатили
stat$not_paying = stat$total - stat$paying_users
stat %>% kbl() %>% kable_styling()
tag | total | perc | paying_users | conv_rate | not_paying |
---|---|---|---|---|---|
control | 109606 | 19.84 | 29892 | 27.27 | 79714 |
test1 | 110933 | 20.08 | 31316 | 28.23 | 79617 |
test2 | 110548 | 20.01 | 30880 | 27.93 | 79668 |
test3 | 110587 | 20.02 | 30967 | 28.00 | 79620 |
test4 | 110694 | 20.04 | 31006 | 28.01 | 79688 |
С помощью критерия согласия Пирсона проверим, значимы ли различия между группами в количестве платящих пользователей. H0 = наблюдаемые частоты соответствуют ожидаемым (т.е. между ними нет разницы, т.к. они взяты из одной и той же совокупности).Если этот так, то разброс будет относительно небольшим, в пределах случайных колебаний. H1 = наблюдаемые частоты в группах отличаются от ожидаемых.
#составим таблицу сопряженности
chisq = stat %>% select(not_paying, paying_users)
row.names(chisq) <- c("control", "test1", "test2", "test3", "test4")
mychisq = chisq.test(chisq)
mychisq
##
## Pearson's Chi-squared test
##
## data: chisq
## X-squared = 28.76, df = 4, p-value = 8.745e-06
Критерий χ2 превышает критическое значение (p-value < 0.05), значит мы можем опровергнуть H0 – между тестовыми группами и числом платящих пользователей есть зависимость, так как наблюдаемые частоты в группах отличаются от ожидаемых. Необходимо узнать, за счёт каких ячеек это происходит.
Посмотрим на остатки: для ячейки test1 - paying_users наблюдений больше, чем ожидалось в случае, если две переменные были бы не связаны между собой. Обратное происходит в ячейке control - paying users – наблюдений меньше, чем ожидалось при условии несвязанности признаков.
Тестовая группа 1 (test1) показала значимые различия по количеству платящих пользователей.
mosaic(mychisq$observed, shade=TRUE, legend=TRUE)
Несмотря на то, что показатели ARPPU и Average Check сильно коррелируют, я посчитаю их отдельно. Значение среднего чека сильно зависит от количества транзакций, тогда как на ARPPU не влияет количество повторных платежей.
#Revenue
n = joined_after %>% select(tag, price) %>% group_by(tag) %>% summarise(revenue = sum(price))
stat = left_join(stat, n)
#ARPU = Доход за период / Аудитория за период
stat$arpu = round(stat$revenue / stat$total, digits = 2)
#ARPPU = Доход за период / Число платящих пользователей
stat$arppu = round(stat$revenue / stat$paying_users, digits = 2)
#Transactions - число транзакций для каждой группы
n = joined_after %>% select(tag, user_uid, time) %>% filter(time > 0) %>% group_by(tag) %>% summarise(trans = n())
stat = left_join(stat, n)
#Average Check (доход с 1 транзакции)
stat$ac = round(stat$revenue / stat$trans, digits = 2)
#Сколько транзакций приходится на одного платящего юзера - Purchases/Paying
stat$p_rate = round(stat$trans / stat$paying_users, digits = 0)
stat %>% kbl() %>% kable_styling()
tag | total | perc | paying_users | conv_rate | not_paying | revenue | arpu | arppu | trans | ac | p_rate |
---|---|---|---|---|---|---|---|---|---|---|---|
control | 109606 | 19.84 | 29892 | 27.27 | 79714 | 2036158 | 18.58 | 68.12 | 52725 | 38.62 | 2 |
test1 | 110933 | 20.08 | 31316 | 28.23 | 79617 | 2159273 | 19.46 | 68.95 | 56409 | 38.28 | 2 |
test2 | 110548 | 20.01 | 30880 | 27.93 | 79668 | 2140281 | 19.36 | 69.31 | 55734 | 38.40 | 2 |
test3 | 110587 | 20.02 | 30967 | 28.00 | 79620 | 2148500 | 19.43 | 69.38 | 56061 | 38.32 | 2 |
test4 | 110694 | 20.04 | 31006 | 28.01 | 79688 | 2139594 | 19.33 | 69.01 | 56046 | 38.18 | 2 |
Когда мы сравниваем траты пользователей на сервисе, то средний чек может не измениться, но может вырасти число людей, которые тратят больше определенного значения. В качестве такого значения можно взять верхний порог (3 квартиль) - 32.71 единицы (наверное, рублей).
Здесь следует отметить, что можно было бы сравнить такие показатели до и после тестирования для всех групп. Но я не могу этого сделать, потому что мне неизвестно, сколько продолжался тест. То есть количество людей, которые потратили больше или совершили больше транзакций, может зависеть от количества времени на сервисе с момента регистрации. Само собой, показатели для тестовых выборок будут меньше, потому что прошло не так много времени.
#распределение переменной потраченных денег - 3rd Qu. 32.71
summary(joined_after$price)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00 0.00 0.00 15.73 32.71 402.76
Если мы посмотрим на количество людей, которые потратили суммарно 33 рубля, то лучше всего также платит 1 тестовая группа.
joined_after %>% select(tag, user_uid, price) %>% group_by(tag, user_uid) %>% summarise(sum_price = sum(price)) %>% filter(sum_price >= 33) %>% count() %>% kbl() %>% kable_styling()
tag | n |
---|---|
control | 21343 |
test1 | 22775 |
test2 | 22104 |
test3 | 22189 |
test4 | 22414 |
Если мы увеличим сумму до 150 руб., по-прежнему число платящих пользователей больше в первой группе (test1).
joined_after %>% select(tag, user_uid, price) %>% group_by(tag, user_uid) %>% summarise(sum_price = sum(price)) %>% filter(sum_price >= 150) %>% count() %>% kbl() %>% kable_styling()
tag | n |
---|---|
control | 2060 |
test1 | 2353 |
test2 | 2310 |
test3 | 2340 |
test4 | 2235 |
Проверим, значимо ли различие между группами по сумме чека. Распределение показателя суммы платежей похоже на нормальное, но характеризуется большими выбросами. Они важны для анализа, поэтому я решила использовать непараметрические тесты для сравнения медианных значений между группами.
ggplot(joined_after, aes(x=price)) +
geom_histogram(binwidth=5, fill = '#69b3a2', color="#e9ecef", position="identity") +
facet_wrap(~tag) +
scale_x_continuous(limits=c(-5,100)) +
theme_bw() +
xlab("Цена контента, руб.") +
ylab("N") +
ggtitle("Распределение цены контента, которые платят пользователи") +
theme(plot.title = element_text(size=15))
Посмотрим на степень разброса показателя в группах: многие наблюдения находятся за пределами третьего квартиля.
joined_after %>% filter(price < 400) %>% ggplot(aes(x = tag, y = price)) +
geom_boxplot(alpha=0.5, fill = '#69b3a2', outlier.colour='#FF4040') +
theme_bw() +
xlab("Группа тестирования") +
ylab("Сумма чека, руб.") +
ggtitle("Разброс суммы трат по группам") +
theme(plot.title = element_text(size=15))
Хотя все понятно визуально, но применим формальный тест Колмогорова-Смирнова для определения нормальности распределения. Распределение не распределено нормально (p-value < 0.05)
ks.test(joined_after$price, "pnorm")
##
## One-sample Kolmogorov-Smirnov test
##
## data: joined_after$price
## D = 0.5, p-value < 2.2e-16
## alternative hypothesis: two-sided
Результаты теста Левина (Levene Test) позволяют опровергнуть нулевую гипотезу о равенстве дисперсий.
leveneTest(joined_after$price ~ joined_after$tag)
## Levene's Test for Homogeneity of Variance (center = median)
## Df F value Pr(>F)
## group 4 13.159 1.017e-10 ***
## 675277
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Нарисуем распределение по квантилям.
qqnorm(joined_after$price)
qqline(joined_after$price, col= 2)
В принципе, вполне возможно логарифмировать переменную price, чтобы ее распределение стало ближе к нормальному, и тогда применить параметрический тест для сравнения средних в группах (One-way ANOVA). Но я воспользуюсь его непараметрическим аналогом – тестом Краскела-Уоллиса (Kruskal-Wallis).
H0 = группы происходят из идентичных популяций (между ними разницы нет). H1 = хотя бы одна группа происходит из другой популяции (разница есть) Отличается ли сумма, которую платят юзеры, в зависимости от тестовой группы? Согласно критерию Краскела-Уоллиса, различия между группами есть (p-value < 0.05).
kruskal.test(joined_after$price ~ joined_after$tag)
##
## Kruskal-Wallis rank sum test
##
## data: joined_after$price by joined_after$tag
## Kruskal-Wallis chi-squared = 62.386, df = 4, p-value = 9.137e-13
Теперь нужно сделать post hoc анализ, ведь иначе не понятно, какие группы различаются между собой значимо, а какие - нет. Нулевая гипотеза в каждом попарном сравнении состоит в том, что вероятность наблюдения случайного значения в первой группе, которое больше случайного значения во второй группе, равна половине.
dunn_test = dunnTest(joined_after$price,joined_after$tag, method = "bonferroni" )
print(dunn_test,dunn.test.results=TRUE)
## Kruskal-Wallis rank sum test
##
## data: x and g
## Kruskal-Wallis chi-squared = 62.386, df = 4, p-value = 0
##
##
## Comparison of x by g
## (Bonferroni)
## Col Mean-|
## Row Mean | control test1 test2 test3
## ---------+--------------------------------------------
## test1 | -6.682622
## | 0.0000*
## |
## test2 | -6.009460 0.670060
## | 0.0000* 1.0000
## |
## test3 | -6.530384 0.149048 -0.520758
## | 0.0000* 1.0000 1.0000
## |
## test4 | -5.480067 1.207094 0.536006 1.057360
## | 0.0000* 1.0000 1.0000 1.0000
##
## alpha = 0.05
## Reject Ho if p <= alpha
Согласно выдаче, распределение различается между всеми тестовыми группами по сравнению с контрольной (p-value < 0.05). Величины z-статистики (Dunn’s pairwise z test statistic) для каждой сравниваемой пары показывают, что наибольшие различия в распределении мы наблюдаем между первой тестовой группой (test1) и контрольной группой.
Для сравнения, сделаем post hoc анализ другим методом, и посмотрим, различаются ли результаты.
pairwise.wilcox.test(joined_after$price, joined_after$tag,
p.adjust.method = "BH")
##
## Pairwise comparisons using Wilcoxon rank sum test with continuity correction
##
## data: joined_after$price and joined_after$tag
##
## control test1 test2 test3
## test1 2.6e-10 - - -
## test2 5.2e-09 0.69 - -
## test3 2.6e-10 0.89 0.69 -
## test4 8.5e-08 0.42 0.69 0.48
##
## P value adjustment method: BH
По результатам теста Вилкоксона, распределение отличается во всех тестовых группах (p-value < 0.05), но наибольшие различия с контрольной группой у первой и третьей тестовой группы (test1, test3).
Посмотрим, значимо ли различается платежная активность пользователей в группах. Сначала посмотрим на распределение количества транзакций по группам. Само по себе количество транзакций мало о чем может сказать, потому что транзакций может быть много, а доход с одной транзакции может быть небольшим.
#распределение количества покупок
transactions = joined_after %>% select(tag, user_uid, time, price) %>% group_by(tag, user_uid) %>% summarise(n_trans = n(), ac = mean(price))
ggplot(transactions, aes(x=n_trans)) +
geom_histogram(binwidth=1, fill="#69b3a2", color="#e9ecef", position="identity") +
facet_wrap(~tag) +
scale_x_continuous(limits=c(0,10)) +
theme_bw() +
xlab("Количество транзакций") +
ylab("N") +
ggtitle("Распределение количество транзакций пользователей") +
theme(plot.title = element_text(size=15))
Построим на распределение среднего чека как функцию к числу покупок для всех платящих пользователей. Что ценнее: когда юзер платит редко, но много, или часто, но мало? На боксплоте мы видим, что распределение с очень большими выбросами по среднему чеку наблюдается для первых покупок – юзер еще не лоялен, он может купить как годовую подписку и дорогостоящие фильмы, так и попробовать месячную подписку и больше ничего не платить. Начиная со второй покупки медианное значение среднего чека снижается с 40 рублей до 37 рублей, но выбросы распределяются более равномерно со 125 рублей до 65 рублей. Начиная с 4 покупки выбросов практически нет – возможно, эти пользователи просто регулярно берут фильмы в аренду в течении месяца.
Возможно, наибольшую ценность для нас представляют пользователи, которые платят нам 2-3 раза, так как помимо подписки они могут дополнительно два-три раза в месяц покупать фильмы. То есть нам нужно, чтобы пользователи помимо подписки покупали себе еще и дополнительный контент.
t = joined_after %>% group_by(user_uid, time, price) %>% mutate(n_trans = n())
t = t %>% group_by(user_uid) %>% summarise(tr = sum(n_trans), ac = mean(price))
t %>% filter(tr < 8) %>% filter(ac < 400 & ac > 0) %>% ggplot(aes(x = as.factor(tr), y = ac)) +
geom_boxplot(alpha=0.5, fill = '#69b3a2', outlier.colour='#FF4040') +
theme_bw() +
xlab("Число покупок") +
ylab("Средний чек, руб.") +
ggtitle("Распределение среднего чека по числу покупок пользователей") +
theme(plot.title = element_text(size=15))
consumption_mode | av_pr |
---|---|
dto | 3976925 |
rent | 2499837 |
subscription | 4147044 |
NA | 0 |
Var1 | Freq |
---|---|
dto | 20231 |
rent | 17710 |
subscription | 9109 |
Как и предполагалось, эти пользователи по большей части платят за покупку (43%) и аренду единицы контента (38%), а не покупают подписки (19%).
## [1] 42.99894
## [1] 37.64081
## [1] 19.36026
Моя гипотеза - для увеличения платежной активности пользователей можно предлагать им покупку дополнительного контента по акциям в дополнение к уже существующей подписке (например, юзер купил один фильм и ему предлагается второй по сниженной цене).
Посчитаем конверсию в виде количества платящих пользователей для каждого типа контента по группам. Снова воспользуемся критерием хи-квадрат.
cons_mode = joined_after %>% filter(converted == "1") %>% group_by(tag, consumption_mode) %>% summarise(length(unique(user_uid))) %>% rename(n_users = `length(unique(user_uid))`)
chisq1 = spread(cons_mode, key = "consumption_mode", value = "n_users")
chisq1 = chisq1 %>% ungroup() %>% select(-tag)
row.names(chisq1) <- c("control", "test1", "test2", "test3", "test4")
mychisq1 = chisq.test(chisq1)
mychisq1
##
## Pearson's Chi-squared test
##
## data: chisq1
## X-squared = 63.108, df = 8, p-value = 1.141e-10
Посмотрим на остатки: для ячейки control - subscription наблюдений больше ожидаемых, соответственно эта группа показывает лучшие показатели по конверсии в подписку, но в то же время проигрывает по конверсии в платный контент. В группа test4 больше ожидаемых наблюдений в ячейке платного контента (test4 - dto), но меньше ожидаемых в ячейке с подпиской (test4 - dto).
Тестовая группа 4 (test4) показала значимые различия по конверсии в платный тип контента, но показала результаты ниже ожидаемых по конверсии в подпискуdto | rent | subscription | total | dto_p | rent_p | sub_p | tag |
---|---|---|---|---|---|---|---|
11564 | 11425 | 14596 | 37585 | 30.77 | 30.40 | 38.83 | control |
12929 | 11797 | 15032 | 39758 | 32.52 | 29.67 | 37.81 | test1 |
12614 | 11864 | 14771 | 39249 | 32.14 | 30.23 | 37.63 | test2 |
12813 | 11725 | 14983 | 39521 | 32.42 | 29.67 | 37.91 | test3 |
13153 | 11630 | 14711 | 39494 | 33.30 | 29.45 | 37.25 | test4 |
В сравнении по количеству и доле платящих, а также дохода с пользователей (ARPU, ARPPU), тестовая группа 1 показала наилучшие результаты.
По показателю конверсии в высокомаржинальные типы контента (подписки) лучшие результаты показала контрольная группа (AC = 38.62 руб.)
Из существующих данных можно вынести довольно много результатов, но есть детали, которые могли бы поменять суть анализа.
Временные метки Здесь бы пригодились данные о времени окончания тестирования. Тогда можно было бы замерить активность пользователей в когортах и сопоставить показатели транзакционной активности в когортах пользователей внутри тестовых групп. Сейачас я не могу адекватно это сделать, потому что между периодом до тестирования и периодом внутри тестирования могло пройти много времени и событий на самом сервисе.
Результаты по метрикам также могут различаться в зависимости от сезонности и времени активности юзеров – такая информация тоже была бы полезна для коррекции результатов.
Типы подписок и доход
По существующим данным я могу понять только о факте покупки подписки, возможно по стоимости догадаться, что эти подписки чем-то различаются. Но тип подписки (годовая, полугодовая, месячная) может сильно повлиять на LTV, и на подходы к увеличению платежной активности. Например, тем, кто пришел по подписке на один месяц, будет не интересно покупать еще и дополнительный контент, потому что вероятно они могли подписаться только из-за одного сериала. И тогда им стоит предложить продление подписки по выгодным условиям.
Подход к платежной активности пользователей с годовой подпиской может как раз заключаться в том, чтобы стимулировать дополнительные траты на другой контент.
Здесь также не помешали бы данные о том, какие промежутки существуют между транзакциями (по сущетсвующим данным это сделать затруднительно). Тогда можно было бы провести OGA (order gap analysis) — оценить частоту покупок в течении определенного периода времени.
Кроме этого, для анализа могла бы быть важна информация о пробной подписке (Конверсия в пробный период (CR to trial)). Ведь некоторые из тех, кто совершил покупку во время сплит-теста, сделал это как раз по опции пробной подписки, а значит доход с нее в первые несколько недель может быть очень маленьким. Это может сильно искажать наши результаты по Revenue.
Также не хватает данных о маржинальности каждого типа контента. Тогда было бы возможно рассчитать еще одну важную метрику – ARPV с учётом маржинальности.
Спасибо за прочтение!
И напоследок мем про первое знакомство с новыми данными: