Вы работаете интернет-сервисе по продаже билетов в кино и на концерты. Посетители заходят на сайт сервиса, читают рецензии, смотрят расписание кинотеатров, выбирают понравившийся сеанс и покупают билеты.
Маркетологи сервиса просят вас помочь им оптимизировать рекламные расходы. У вас в распоряжении есть данные о посещениях сервиса, заказах и рекламных расходах.
library(dbplyr)
library(dplyr)
library(tidyr)
library(lubridate)
library(zoo)
library(ggplot2)
setwd("~/mipt-course/pavlov")
costs_df <- read.csv(file = 'data/costs.csv', header = T, sep = ",")
visits_df <- read.csv(file = 'data/visits_log.csv', header = T, sep = ",")
orders_df <- read.csv(file = 'data/orders_log.csv', header = T, sep = ",")
costs_df$dt <- strptime(costs_df$dt, "%Y-%m-%d")
visits_df$Start.Ts <- strptime(visits_df$Start.Ts, "%d.%m.%Y %H:%M")
visits_df$End.Ts <- strptime(visits_df$End.Ts, "%d.%m.%Y %H:%M")
orders_df$Buy.Ts <- strptime(orders_df$Buy.Ts, "%Y-%m-%d %H:%M:%S")
colnames(costs_df) <- c("Source.Id", "Date", "Costs")
DAU:
dau_df <- visits_df %>%
mutate(date = as.Date(Start.Ts, "%Y-%m-%d")) %>%
group_by(date) %>%
summarise(DAU = n_distinct(Uid))
mean(dau_df$DAU)
## [1] 907.9918
WAU
wau_df <- visits_df %>%
mutate(date = as.Date(Start.Ts, "%Y-%m-%d")) %>%
mutate(year_week = format(date, "%Y-%W")) %>%
group_by(year_week) %>%
summarise(WAU = n_distinct(Uid))
mean(wau_df$WAU)
## [1] 5716.245
MAU:
mau_df <- visits_df %>%
mutate(date = as.Date(Start.Ts, "%Y-%m-%d")) %>%
mutate(year_month = format(date, "%Y-%m")) %>%
group_by(year_month) %>%
summarise(MAU = n_distinct(Uid))
mean(mau_df$MAU)
## [1] 23228.42
mean_sessions_count_df <- visits_df %>%
mutate(date = as.Date(Start.Ts, "%Y-%m-%d")) %>%
group_by(Uid, date) %>%
summarise(count = n()) %>%
group_by(date) %>%
summarise(mean_sessions_count = mean(count))
## `summarise()` has grouped output by 'Uid'. You can override using the `.groups`
## argument.
mean(mean_sessions_count_df$mean_sessions_count)
## [1] 1.07959
avg_session_length_df <- visits_df %>%
mutate(session_length = End.Ts - Start.Ts) %>%
mutate(date = as.Date(Start.Ts, "%Y-%m-%d")) %>%
group_by(date) %>%
summarise(avg_session_length = mean(session_length))
mean(avg_session_length_df$avg_session_length)
## Time difference of 623.0082 secs
get_mode_in_sec <- function(x) {
a <- table(x)
ans <- paste(c(as.numeric(names(a)[a == max(a)]), "sec"), collapse = " ")
return(ans)
}
first_visits_df <- visits_df %>%
select(Uid, Start.Ts) %>%
group_by(Uid) %>%
summarise(first_visit.Ts = min(Start.Ts))
first_purchase_df <- orders_df %>%
select(Uid, Buy.Ts) %>%
group_by(Uid) %>%
summarise(first_purchase.Ts = min(Buy.Ts))
visit_first_purchase_df <- first_visits_df %>%
inner_join(first_purchase_df, by = "Uid") %>%
mutate(time_to_purchase = first_purchase.Ts - first_visit.Ts)
get_mode_in_sec(visit_first_purchase_df$time_to_purchase)
## [1] "60 sec"
Первое приближение. Бывают сессии, когда покупка происходит раньше начала сессии.
purchase_per_visit <- function(x, y) {
a = length(which(!is.na(visits_orders_df$Revenue)))
b = length(which(is.na(visits_orders_df$Revenue)))
return(a / (a + b))
}
visits_with_orders_df <- visits_df %>%
left_join(orders_df, by = "Uid") %>%
filter(Buy.Ts <= End.Ts) %>%
filter(Buy.Ts >= Start.Ts)
visits_without_orders_df <- visits_df %>%
left_join(orders_df, by = "Uid") %>%
filter(is.na(Buy.Ts))
visits_orders_df <- rbind(visits_with_orders_df, visits_without_orders_df)
purchase_per_visit(visits_orders_df)
## [1] 0.1180179
first_purchase_df <- orders_df %>%
select(Uid, Buy.Ts) %>%
group_by(Uid) %>%
summarise(first_purchase.Ts = min(Buy.Ts))
six_months_in_sec <- 365.25 / 2 * 24 * 60 * 60
TBU_first_6_months_df <- first_purchase_df %>%
left_join(orders_df, by = "Uid") %>%
mutate(time_delta = Buy.Ts - first_purchase.Ts) %>%
filter(time_delta < six_months_in_sec) %>%
group_by(Uid) %>%
summarise(TBU_first_6_months = n())
mean(TBU_first_6_months_df$TBU_first_6_months)
## [1] 1.332804
Первое приближение. Бывают сессии, когда покупка происходит раньше начала сессии.
visits_x_orders_df <- visits_df %>%
inner_join(orders_df, by = "Uid") %>%
filter(Buy.Ts <= End.Ts) %>%
filter(Buy.Ts >= Start.Ts)
mean(visits_x_orders_df$Revenue)
## [1] 4.664603
first_purchase_df <- orders_df %>%
select(Uid, Buy.Ts) %>%
group_by(Uid) %>%
summarise(first_purchase.Ts = min(Buy.Ts))
six_months_in_sec <- 365.25 / 2 * 24 * 60 * 60
first_purchase_df %>%
inner_join(orders_df, by = "Uid") %>%
mutate(Date = as.Date(Buy.Ts)) %>%
mutate(Time_delta = Buy.Ts - first_purchase.Ts) %>%
mutate(Epoch = as.integer(floor(Time_delta / 2592000)) + 1) %>%
filter(Epoch <= 6) %>%
group_by(Epoch) %>%
summarise(Avg_check = mean(Revenue))
## # A tibble: 6 × 2
## Epoch Avg_check
## <dbl> <dbl>
## 1 1 4.31
## 2 2 6.55
## 3 3 14.6
## 4 4 10.6
## 5 5 7.85
## 6 6 8.74
sum(costs_df$Costs)
## [1] 329131.6
costs_per_source_df <- costs_df %>%
group_by(Source.Id) %>%
summarise(Costs_per_Source.Id = sum(Costs))
costs_per_source_df
## # A tibble: 7 × 2
## Source.Id Costs_per_Source.Id
## <int> <dbl>
## 1 1 20833.
## 2 2 42806.
## 3 3 141322.
## 4 4 61074.
## 5 5 51757.
## 6 9 5517.
## 7 10 5822.
visits_with_orders_df <- visits_df %>%
left_join(orders_df, by = "Uid") %>%
filter(Buy.Ts <= End.Ts) %>%
filter(Buy.Ts >= Start.Ts)
visits_with_orders_Revenue_per_Source.Id_df <- visits_with_orders_df %>%
group_by(Source.Id) %>%
summarise(Revenue_per_Source.Id = sum(Revenue))
visits_with_orders_Revenue_per_Source.Id_df %>%
left_join(costs_per_source_df) %>%
mutate(Profit = Revenue_per_Source.Id - Costs_per_Source.Id)
## Joining, by = "Source.Id"
## # A tibble: 8 × 4
## Source.Id Revenue_per_Source.Id Costs_per_Source.Id Profit
## <int> <dbl> <dbl> <dbl>
## 1 1 27949. 20833. 7115.
## 2 2 33171. 42806. -9635.
## 3 3 29767. 141322. -111555.
## 4 4 34027. 61074. -27047.
## 5 5 32955. 51757. -18802.
## 6 7 1.22 NA NA
## 7 9 2688. 5517. -2829.
## 8 10 2979. 5822. -2844.
Расходы на маркетинг в неделю:
costs_per_source_df <- costs_df %>%
mutate(yw = format(Date, "%Y-%W")) %>%
group_by(Source.Id, yw) %>%
summarise(Costs_per_Source.Id = sum(Costs))
## `summarise()` has grouped output by 'Source.Id'. You can override using the
## `.groups` argument.
costs_source <- costs_per_source_df %>%
pivot_wider(names_from = yw, values_from = Costs_per_Source.Id)
costs_source
## # A tibble: 7 × 54
## # Groups: Source.Id [7]
## Source.Id `2017-22` `2017-23` `2017-24` `2017-25` `2017-26` `2017-27`
## <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 229. 300. 253. 156. 221. 239.
## 2 2 421. 653. 359. 506. 594. 515.
## 3 3 1250. 2480. 1401. 1537. 1447. 1965.
## 4 4 357. 958. 763. 981. 660. 654.
## 5 5 315. 806. 562. 529. 524. 654.
## 6 9 42.9 71.4 66.9 57.2 60.3 67.4
## 7 10 53.7 105. 57.8 56.0 56.3 63.8
## # … with 47 more variables: `2017-28` <dbl>, `2017-29` <dbl>, `2017-30` <dbl>,
## # `2017-31` <dbl>, `2017-32` <dbl>, `2017-33` <dbl>, `2017-34` <dbl>,
## # `2017-35` <dbl>, `2017-36` <dbl>, `2017-37` <dbl>, `2017-38` <dbl>,
## # `2017-39` <dbl>, `2017-40` <dbl>, `2017-41` <dbl>, `2017-42` <dbl>,
## # `2017-43` <dbl>, `2017-44` <dbl>, `2017-45` <dbl>, `2017-46` <dbl>,
## # `2017-47` <dbl>, `2017-48` <dbl>, `2017-49` <dbl>, `2017-50` <dbl>,
## # `2017-51` <dbl>, `2017-52` <dbl>, `2018-01` <dbl>, `2018-02` <dbl>, …
Прибыль за неделю:
visits_with_orders_df <- visits_df %>%
left_join(orders_df, by = "Uid") %>%
filter(Buy.Ts <= End.Ts) %>%
filter(Buy.Ts >= Start.Ts) %>%
mutate(yw = format(Buy.Ts, "%Y-%W"))
visits_with_orders_Revenue_per_Source.Id_df <- visits_with_orders_df %>%
group_by(Source.Id, yw) %>%
summarise(Revenue_per_Source.Id = sum(Revenue))
## `summarise()` has grouped output by 'Source.Id'. You can override using the
## `.groups` argument.
revenue_source <- visits_with_orders_Revenue_per_Source.Id_df %>%
pivot_wider(names_from = yw, values_from = Revenue_per_Source.Id)
revenue_source
## # A tibble: 8 × 54
## # Groups: Source.Id [8]
## Source.Id `2017-22` `2017-23` `2017-24` `2017-25` `2017-26` `2017-27`
## <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 244. 355. 205. 237. 216. 319.
## 2 2 91.1 301. 670. 92.7 201. 177.
## 3 3 196. 616. 229. 309. 376. 426.
## 4 4 119. 327. 357. 304. 429. 408.
## 5 5 169. 515. 232. 370. 588. 340.
## 6 7 NA NA NA NA NA NA
## 7 9 28.8 33.3 21.9 29.9 21.0 45.4
## 8 10 20.7 84.4 34.4 23.5 12.8 19.8
## # … with 47 more variables: `2017-28` <dbl>, `2017-29` <dbl>, `2017-30` <dbl>,
## # `2017-31` <dbl>, `2017-32` <dbl>, `2017-33` <dbl>, `2017-34` <dbl>,
## # `2017-35` <dbl>, `2017-36` <dbl>, `2017-37` <dbl>, `2017-38` <dbl>,
## # `2017-39` <dbl>, `2017-40` <dbl>, `2017-41` <dbl>, `2017-42` <dbl>,
## # `2017-43` <dbl>, `2017-44` <dbl>, `2017-45` <dbl>, `2017-46` <dbl>,
## # `2017-47` <dbl>, `2017-48` <dbl>, `2017-49` <dbl>, `2017-50` <dbl>,
## # `2017-51` <dbl>, `2017-52` <dbl>, `2018-01` <dbl>, `2018-02` <dbl>, …
Видим, что на момент последней недели окупаются 1, 2, 10 каналы.
Для наглядности построим графики, срвним недельные затраты на маркетинг и прибыль за неделю в каждом из каналов.
compare_dd <- visits_with_orders_Revenue_per_Source.Id_df %>%
left_join(costs_per_source_df) %>%
gather(key="Type", value="Value", 3:4)
## Joining, by = c("Source.Id", "yw")
ggplot(data = compare_dd,
mapping = aes(x = yw, y = Value)) +
geom_line(aes(colour = Type, group = Type)) +
facet_grid(rows = vars(Source.Id), scales = "free_y")
## geom_path: Each group consists of only one observation. Do you need to adjust
## the group aesthetic?
Выводы:
1. Первый канал. Добиться большей стабильности работы первого канала, например за счет увеличения объемов продаж. Видно, что была попытка увеличения затрат на маркетинг, это привело к хорошему росту прибыли. Нужно увеличить инвестиции до 500 в неделю.
2. Второй канал. Продолжить развивать второй канал, постараться оптимизировать затраты на маркетинг. Затем снова попробовать увеличить инвестиции в этот канал (видно, что у продаж хороший отклик на рост инвестиций).
3. Третий канал нужно заморозить. Видно, что была попытка увеличить инвестиции в этот канал, но продажи не выросли.
4. Четвертый канал. Ситуация частично похожа на второй канал: есть рост продаж при росте затрат на маркетинг, но получить прибыль не получилось. В этом канале нужно временно сокартить затраты и заняться разработкой новой маркетинговой стратегии, основанной на опыте этого канала.
5. Пятый канал нужно заморозить. Была попытка увеличивать затрты на маркетинг, но прибыль быстро упала после увеличения бюджета.
6. Девятый и десятый канал не генерируют большого убытка, но при этом добавляют разнообразия в продажи. С этими каналами в будущем нужно провести эксперименты с увеличением бюджета до 250.