Был запущен сплит-тест (а/б-тест), направленный на улучшение платежной активности пользователей. Вам дан датасет с транзакциями пользователей до и во время теста в контрольной и тестовых группах.

Основные гипотезы для анализа результатов сплит-теста

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

Альтернативная гипотеза Версии сайта имеют разную вероятность значения показателя платежной активности пользователей (или конверсии в покупки). Между версиями есть статистически значимая разница.

Результаты:

  1. Сравнение групп по метрикам количества и доли платящих. Показатель конверсии (CR) значимо различается в тестовой группе 1 (test1 CR = 28%). Доход от покупок также значимо различается в первой группе (test1), соответственно доход с пользователей, а также доход с платящих пользователей у этой группы самый высокий по сравнению с другими группами (ARPU = 19%, ARPPU = 69%). Кроме сравнения трат пользователей на сервисе, была выдвинута гипотеза о том, что средний чек может не измениться, но может вырасти число людей, которые тратят больше определенного значения. Первая тестовая группа (test1) также показала самый высокое количество пользователей, которые тратят больше верхнего порога суммы (3 квартиль). Иными словами, сумма высоких трат пользователей выше всего в группе test1.

  2. Сравнение групп по транзакционной активности и конверсии в разные типы контента. Тестовая группа 4 (test4) показала значимое увеличение по конверсии в покупки контента dto (33%), но показала результаты ниже ожидаемых по конверсии в подписку (subscription). По показателю конверсии в подписки лучшие результаты (38%) показала контрольная группа (control). Так как подписки представляют собой самый доходный тип контента, то можно предположить что это влияет на то, что у контрольной группы (control) самый высокий показатель среднего чека (38.62 руб.). По показателю распределения среднего чека на количество покупок, была выдвинута гипотеза, что наибольшую ценность для нас представляют пользователи, которые платят нам 2-3 раза, так как помимо подписки они могут дополнительно два-три раза в месяц покупать дополнительный контент.

Summary:

  • В сравнении по количеству и доле платящих, а также дохода с пользователей (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

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

  1. есть ли юзеры, которые попали в несколько тестовых выборок (для таких пользователей результаты будут не валидны, их придется удалить)

  2. есть ли дублирующиеся характеристики, которые мешают интерпретации результатов. Причиной дубликатов в датасете 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

Summary о том, как оценить выборку:

  • проверить наличие дубликатов и различных аномалий в данных, и удалить, если они затрудняют дальнейший анализ

  • посчитать пропорцию юзеров (желательно равномерное разделение – для 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)) 

Какой тип контента приносит нам больше всего прибыли? Доход выше у подписной модели потребления (SVOD revenue), но у нее есть один недостаток - при покупке подписки пользователи не всегда мотивированы покупать что-то еще.
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

Summary:

  • В сравнении по количеству и доле платящих, а также дохода с пользователей (ARPU, ARPPU), тестовая группа 1 показала наилучшие результаты.

  • По показателю конверсии в высокомаржинальные типы контента (подписки) лучшие результаты показала контрольная группа (AC = 38.62 руб.)

Если не ограничиваться теми данными, которые приведены в рамках этого задания, что ещё вы бы посчитали для оценки результатов групп?

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

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

Результаты по метрикам также могут различаться в зависимости от сезонности и времени активности юзеров – такая информация тоже была бы полезна для коррекции результатов.

Типы подписок и доход

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

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

Здесь также не помешали бы данные о том, какие промежутки существуют между транзакциями (по сущетсвующим данным это сделать затруднительно). Тогда можно было бы провести OGA (order gap analysis) — оценить частоту покупок в течении определенного периода времени.

Кроме этого, для анализа могла бы быть важна информация о пробной подписке (Конверсия в пробный период (CR to trial)). Ведь некоторые из тех, кто совершил покупку во время сплит-теста, сделал это как раз по опции пробной подписки, а значит доход с нее в первые несколько недель может быть очень маленьким. Это может сильно искажать наши результаты по Revenue.

Также не хватает данных о маржинальности каждого типа контента. Тогда было бы возможно рассчитать еще одну важную метрику – ARPV с учётом маржинальности.

Спасибо за прочтение!

И напоследок мем про первое знакомство с новыми данными: