Skip to content

Commit 33baf19

Browse files
fix: prepare for ggplot2 4.0.0 + budget allocator initials points + robyn_write()'s bug + op's mean points + styler() (#1283)
* recode: comply with ggplot2 4.0.0 * recode: apply styler * fix: #1282 + gg warnings * fix: initResponseUnit NAs * recode: format * fix: initial point in plot over resp curve * fix: points in op always over the curve
1 parent ac773d7 commit 33baf19

File tree

15 files changed

+351
-226
lines changed

15 files changed

+351
-226
lines changed

R/DESCRIPTION

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
Package: Robyn
22
Type: Package
33
Title: Semi-Automated Marketing Mix Modeling (MMM) from Meta Marketing Science
4-
Version: 3.12.0.9008
4+
Version: 3.12.1
55
Authors@R: c(
66
person("Gufeng", "Zhou", , "[email protected]", c("cre", "aut")),
7-
person("Igor", "Skokan", , "[email protected]", c("aut")),
87
person("Bernardo", "Lares", , "[email protected]", c("aut")),
8+
person("Igor", "Skokan", , "[email protected]", c("aut")),
99
person("Leonel", "Sentana", , "[email protected]", c("aut")),
1010
person("Meta Platforms, Inc.", role = c("cph", "fnd")))
1111
Maintainer: Gufeng Zhou <[email protected]>
@@ -17,14 +17,14 @@ Imports:
1717
doRNG,
1818
dplyr,
1919
foreach,
20-
ggplot2,
20+
ggplot2 (>= 3.4.0),
2121
ggridges,
2222
glmnet,
2323
jsonlite,
2424
lares,
2525
lubridate,
2626
nloptr,
27-
patchwork,
27+
patchwork (>= 1.3.1),
2828
prophet,
2929
reticulate,
3030
stringr,

R/R/allocator.R

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,7 @@ robyn_allocator <- function(robyn_object = NULL,
250250
usecase <- paste(usecase, ifelse(!is.null(total_budget), "+ defined_budget", "+ historical_budget"))
251251

252252
# Response values based on date range -> mean spend
253-
initResponseUnit <- NULL
254-
initResponseMargUnit <- NULL
253+
initResponseUnit <- initResponseUnitSim <- initResponseMargUnit <- NULL
255254
hist_carryover <- list()
256255
qa_carryover <- list()
257256
for (i in seq_along(mediaSelectedSorted)) {
@@ -300,11 +299,17 @@ robyn_allocator <- function(robyn_object = NULL,
300299
x_hist_carryover = mean(hist_carryover_temp),
301300
get_sum = FALSE
302301
)
303-
initResponseUnit <- c(initResponseUnit, resp$mean_response) # resp_simulate
302+
initResponseUnit <- c(initResponseUnit, ifelse(
303+
all(mediaSelectedSorted %in% InputCollect$paid_media_spends),
304+
resp$mean_response,
305+
resp_simulate
306+
))
307+
initResponseUnitSim <- c(initResponseUnitSim, resp_simulate)
304308
initResponseMargUnit <- c(initResponseMargUnit, resp_simulate_plus1 - resp_simulate)
305309
}
306310
qa_carryover <- do.call(cbind, qa_carryover) %>% as.data.frame()
307-
names(initResponseUnit) <- names(hist_carryover) <- names(qa_carryover) <- mediaSelectedSorted
311+
names(initResponseUnit) <- names(initResponseUnitSim) <-
312+
names(hist_carryover) <- names(qa_carryover) <- mediaSelectedSorted
308313
# QA adstock: simulated adstock should be identical to model adstock
309314
# qa_carryover_origin <- OutputCollect$mediaVecCollect %>%
310315
# filter(.data$solID == select_model & .data$type == "adstockedMedia") %>%
@@ -591,6 +596,7 @@ robyn_allocator <- function(robyn_object = NULL,
591596
# adstocked_end_date = as.Date(ifelse(adstocked, tail(resp$date, 1), NA), origin = "1970-01-01"),
592597
# adstocked_periods = length(resp$date),
593598
initResponseUnit = initResponseUnit,
599+
initResponseUnitSim = initResponseUnitSim,
594600
initResponseUnitTotal = sum(initResponseUnit),
595601
initResponseMargUnit = initResponseMargUnit,
596602
initResponseTotal = sum(initResponseUnit) * unique(simulation_period),
@@ -655,7 +661,7 @@ robyn_allocator <- function(robyn_object = NULL,
655661
eval_list$levs1 <- levs1
656662

657663
dt_optimOutScurve <- rbind(
658-
select(dt_optimOut, .data$channels, .data$initSpendUnit, .data$initResponseUnit) %>%
664+
select(dt_optimOut, .data$channels, .data$initSpendUnit, .data$initResponseUnitSim) %>%
659665
mutate(x = levs1[1]) %>% as.matrix(),
660666
select(dt_optimOut, .data$channels, .data$optmSpendUnit, .data$optmResponseUnit) %>%
661667
mutate(x = levs1[2]) %>% as.matrix(),

R/R/auxiliary.R

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,16 +97,18 @@ baseline_vars <- function(InputCollect, baseline_level) {
9797

9898
# Calculate dot product
9999
.dot_product <- function(range, proportion) {
100-
mapply(function(proportion) {
101-
c(range %*% c(1 - proportion, proportion))
102-
},
103-
proportion = proportion)
100+
mapply(
101+
function(proportion) {
102+
c(range %*% c(1 - proportion, proportion))
103+
},
104+
proportion = proportion
105+
)
104106
}
105107

106108
# Calculate quantile interval
107109
.qti <- function(x, interval = 0.95) {
108110
check_qti(interval)
109-
int_low <- (1 - interval)/2
111+
int_low <- (1 - interval) / 2
110112
int_up <- 1 - int_low
111113
qt_low <- quantile(x, int_low)
112114
qt_up <- quantile(x, int_up)
@@ -131,13 +133,14 @@ baseline_vars <- function(InputCollect, baseline_level) {
131133
} else {
132134
warning(paste(
133135
"Unable to determine frequency to calculate next logical date.",
134-
"Returning last available date."))
136+
"Returning last available date."
137+
))
135138
return(as.Date(tail(dates, 1)))
136139
}
137-
next_date <- switch(
138-
frequency,
140+
next_date <- switch(frequency,
139141
"daily" = dates[length(dates)] + 1,
140142
"weekly" = dates[length(dates)] + 7,
141-
"monthly" = seq(dates[length(dates)], by = "1 month", length.out = 2)[2])
143+
"monthly" = seq(dates[length(dates)], by = "1 month", length.out = 2)[2]
144+
)
142145
return(as.Date(next_date))
143146
}

R/R/calibration.R

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
#' # "reach 1+" for gamma lower bound and "reach 10+" for gamma upper bound
5252
#' facebook_I_gammas <- c(
5353
#' curve_out[["curve_collect"]][["reach 1+"]][["hill"]][["gamma_best"]],
54-
#' curve_out[["curve_collect"]][["reach 10+"]][["hill"]][["gamma_best"]])
54+
#' curve_out[["curve_collect"]][["reach 10+"]][["hill"]][["gamma_best"]]
55+
#' )
5556
#' print(facebook_I_gammas)
5657
#' }
5758
#' @return List. Class: \code{curve_out}. Contains the results of all trials
@@ -94,7 +95,8 @@ robyn_calibrate <- function(
9495
burn_in_rel,
9596
sim_n,
9697
hp_interval,
97-
quiet)
98+
quiet
99+
)
98100
}
99101

100102
df_curve_plot <- bind_rows(lapply(curve_collect, function(x) x$df_out))
@@ -108,12 +110,14 @@ robyn_calibrate <- function(
108110
x = "cumulative spend",
109111
y = "cumulative reach"
110112
) +
111-
#theme_lares(background = "white")+
112-
#scale_alpha_discrete(range = c(1, 0.2))
113-
scale_colour_discrete(h =c(120,260))
113+
# theme_lares(background = "white")+
114+
# scale_alpha_discrete(range = c(1, 0.2))
115+
scale_colour_discrete(h = c(120, 260))
114116

115-
return(list(curve_collect = curve_collect,
116-
plot_reach_freq = p_rnf))
117+
return(list(
118+
curve_collect = curve_collect,
119+
plot_reach_freq = p_rnf
120+
))
117121
} else {
118122
curve_collect <- robyn_calibrate_single_dim(
119123
df_curve,
@@ -127,7 +131,8 @@ robyn_calibrate <- function(
127131
burn_in_rel,
128132
sim_n,
129133
hp_interval,
130-
quiet)
134+
quiet
135+
)
131136
return(list(curve_collect = curve_collect))
132137
}
133138
}
@@ -314,7 +319,8 @@ robyn_calibrate_single_dim <- function(
314319

315320
## get calibration range for hyparameters
316321
p_alpha <- data.frame(alpha = alpha_collect_converged) %>%
317-
ggplot(aes(x = alpha)) + geom_density(fill = "grey99", color = "grey")
322+
ggplot(aes(x = alpha)) +
323+
geom_density(fill = "grey99", color = "grey")
318324
alpha_den <- .den_interval(p_alpha, hp_interval, best_alpha)
319325

320326
p_gamma <- data.frame(gamma = gamma_collect_converged) %>% ggplot(aes(x = gamma)) +
@@ -330,11 +336,12 @@ robyn_calibrate_single_dim <- function(
330336
# qt_coef_out <- .qti(x = coef_collect_converged, interval = hp_interval)
331337

332338
## plotting & prompting
333-
#coef_response <- max(response_cum_sot) / max(response_sot_scaled)
339+
# coef_response <- max(response_cum_sot) / max(response_sot_scaled)
334340
df_sot_plot <- data.frame(
335341
spend = spend_cum_sot,
336342
response = response_cum_sot,
337-
response_pred = best_pred_response)
343+
response_pred = best_pred_response
344+
)
338345
temp_spend <- seq(0, max(spend_cum_sot), length.out = sim_n)
339346
temp_sat <- best_coef * saturation_hill(x = total_cum_spend, alpha = best_alpha, gamma = best_gamma, x_marginal = temp_spend)[["x_saturated"]]
340347
df_pred_sim_plot <- data.frame(spend = temp_spend, response = temp_sat)
@@ -368,7 +375,7 @@ robyn_calibrate_single_dim <- function(
368375
aes(
369376
x = .data$sim_spend, y = .data$sim_saturation,
370377
color = .data$sim
371-
), size = 2, alpha = 0.2
378+
), linewidth = 2, alpha = 0.2
372379
) +
373380
scale_colour_grey() +
374381
geom_point(
@@ -389,14 +396,14 @@ robyn_calibrate_single_dim <- function(
389396
iterations = unlist(mapply(function(x) seq(x), max_iters_vec, SIMPLIFY = FALSE)),
390397
trials = as.character(unlist(
391398
mapply(function(x, y) rep(x, y),
392-
x = 1:max_trials, y = max_iters_vec
399+
x = 1:max_trials, y = max_iters_vec
393400
)
394401
))
395402
)
396403
p_mse <- df_mse %>%
397404
mutate(trials = factor(.data$trials, levels = seq(max_trials))) %>%
398405
ggplot(aes(x = .data$iterations, y = .data$mse)) +
399-
geom_line(size = 0.2) +
406+
geom_line(linewidth = 0.2) +
400407
facet_grid(.data$trials ~ .) +
401408
labs(
402409
title = paste0(
@@ -415,8 +422,10 @@ robyn_calibrate_single_dim <- function(
415422
p_alpha <- p_alpha +
416423
labs(
417424
title = paste0("Alpha (Hill) density after ", round(burn_in_rel * 100), "% burn-in"),
418-
subtitle = paste0(round(hp_interval*100), "% center density: ", round(alpha_den$interval[1], 4), "-", round(alpha_den$interval[2], 4),
419-
"\nBest alpha: ", round(best_alpha,4))
425+
subtitle = paste0(
426+
round(hp_interval * 100), "% center density: ", round(alpha_den$interval[1], 4), "-", round(alpha_den$interval[2], 4),
427+
"\nBest alpha: ", round(best_alpha, 4)
428+
)
420429
) +
421430
theme_lares(...) +
422431
scale_y_abbr()
@@ -425,8 +434,10 @@ robyn_calibrate_single_dim <- function(
425434
p_gamma <- p_gamma +
426435
labs(
427436
title = paste0("Gamma (Hill) density after ", round(burn_in_rel * 100), "% burn-in"),
428-
subtitle = paste0(round(hp_interval*100), "% center density: ", round(gamma_den$interval[1], 4), "-", round(gamma_den$interval[2], 4),
429-
"\nBest gamma: ", round(best_gamma,4))
437+
subtitle = paste0(
438+
round(hp_interval * 100), "% center density: ", round(gamma_den$interval[1], 4), "-", round(gamma_den$interval[2], 4),
439+
"\nBest gamma: ", round(best_gamma, 4)
440+
)
430441
) +
431442
theme_lares(...) +
432443
scale_y_abbr()
@@ -458,13 +469,15 @@ robyn_calibrate_single_dim <- function(
458469
}
459470

460471
curve_out <- list(
461-
hill = list(alpha_range = c(alpha_den$interval),
462-
alpha_best = best_alpha,
463-
gamma_range = c(gamma_den$interval),
464-
gamma_best = best_gamma,
465-
coef_range = c(coef_den$interval),
466-
coef_best = best_coef,
467-
inflexion_max = total_cum_spend),
472+
hill = list(
473+
alpha_range = c(alpha_den$interval),
474+
alpha_best = best_alpha,
475+
gamma_range = c(gamma_den$interval),
476+
gamma_best = best_gamma,
477+
coef_range = c(coef_den$interval),
478+
coef_best = best_coef,
479+
inflexion_max = total_cum_spend
480+
),
468481
plot = p_lines / p_mse / (p_alpha + p_gamma) +
469482
plot_annotation(
470483
theme = theme_lares(background = "white", ...)
@@ -482,13 +495,15 @@ robyn_calibrate_single_dim <- function(
482495
get_den <- ggplot_build(plot_object)$data[[1]]
483496
# mode_loc <- which.max(get_den$y)
484497
mode_loc <- which.min(abs(get_den$x - best_val))
485-
mode_wing <- sum(get_den$y) * hp_interval /2
498+
mode_wing <- sum(get_den$y) * hp_interval / 2
486499
int_left <- mode_loc - which.min(abs(cumsum(get_den$y[mode_loc:1]) - mode_wing)) + 1
487500
int_left <- ifelse(is.na(int_left) | int_left < 1, 1, int_left)
488-
int_right <- mode_loc + which.min(abs(cumsum(get_den$y[(mode_loc+1):length(get_den$y)]) - mode_wing))
489-
int_right <- ifelse(length(int_right) == 0 , length(get_den$y), int_right)
490-
return(list(interval = c(get_den$x[int_left], get_den$x[int_right]),
491-
mode = get_den$x[mode_loc]))
501+
int_right <- mode_loc + which.min(abs(cumsum(get_den$y[(mode_loc + 1):length(get_den$y)]) - mode_wing))
502+
int_right <- ifelse(length(int_right) == 0, length(get_den$y), int_right)
503+
return(list(
504+
interval = c(get_den$x[int_left], get_den$x[int_right]),
505+
mode = get_den$x[mode_loc]
506+
))
492507
}
493508

494509

R/R/checks.R

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -585,9 +585,11 @@ check_calibration <- function(dt_input, date_var, calibration_input, dayInterval
585585
}
586586
all_media <- c(paid_media_spends, organic_vars)
587587
cal_media <- str_split(calibration_input$channel, "\\+|,|;|\\s")
588-
cal_media_selected <- lapply(cal_media, function(x) sapply(x, function(y) {
589-
ifelse(y %in% c(paid_media_selected, organic_vars), y, paid_media_selected[paid_media_spends == y])
590-
}))
588+
cal_media_selected <- lapply(cal_media, function(x) {
589+
sapply(x, function(y) {
590+
ifelse(y %in% c(paid_media_selected, organic_vars), y, paid_media_selected[paid_media_spends == y])
591+
})
592+
})
591593
calibration_input$channel_selected <- sapply(cal_media_selected, function(x) paste0(x, collapse = "+"))
592594
if (!all(unlist(cal_media) %in% all_media)) {
593595
these <- unique(unlist(cal_media)[which(!unlist(cal_media) %in% all_media)])
@@ -859,14 +861,14 @@ check_allocator <- function(OutputCollect, select_model, paid_media_selected, sc
859861
stop("Input 'scenario' must be one of: ", paste(opts, collapse = ", "))
860862
}
861863
if ((is.null(channel_constr_low) & !is.null(channel_constr_up)) |
862-
(!is.null(channel_constr_low) & is.null(channel_constr_up))) {
864+
(!is.null(channel_constr_low) & is.null(channel_constr_up))) {
863865
stop("channel_constr_low and channel_constr_up must be both provided or both NULL")
864866
} else if (!is.null(channel_constr_low) & !is.null(channel_constr_up)) {
865867
if (any(channel_constr_low < 0)) {
866868
stop("Inputs 'channel_constr_low' must be >= 0")
867869
}
868870
if ((length(channel_constr_low) != 1 && length(channel_constr_low) != length(paid_media_selected)) |
869-
(length(channel_constr_up) != 1 && length(channel_constr_up) != length(paid_media_selected))) {
871+
(length(channel_constr_up) != 1 && length(channel_constr_up) != length(paid_media_selected))) {
870872
stop("'channel_constr_low' and 'channel_constr_up' require either only 1 value or the same length as 'paid_media_selected'")
871873
}
872874
if (any(channel_constr_up < channel_constr_low)) {
@@ -884,10 +886,12 @@ check_metric_type <- function(metric_name, paid_media_spends, paid_media_vars, p
884886
metric_type <- "organic"
885887
metric_name_updated <- metric_name
886888
} else if ((metric_name %in% paid_media_spends && length(metric_name) == 1) |
887-
(metric_name %in% paid_media_vars && length(metric_name) == 1)) {
889+
(metric_name %in% paid_media_vars && length(metric_name) == 1)) {
888890
metric_type <- "paid"
889-
name_loc <- unique(c(which(metric_name == paid_media_spends),
890-
which(metric_name == paid_media_vars)))
891+
name_loc <- unique(c(
892+
which(metric_name == paid_media_spends),
893+
which(metric_name == paid_media_vars)
894+
))
891895
metric_name_updated <- paid_media_selected[name_loc]
892896
} else {
893897
stop(paste(
@@ -898,8 +902,10 @@ check_metric_type <- function(metric_name, paid_media_spends, paid_media_vars, p
898902
paste("\n- organic_vars:", v2t(organic_vars, quotes = FALSE))
899903
))
900904
}
901-
return(list(metric_type = metric_type,
902-
metric_name_updated = metric_name_updated))
905+
return(list(
906+
metric_type = metric_type,
907+
metric_name_updated = metric_name_updated
908+
))
903909
}
904910

905911
check_metric_dates <- function(date_range = NULL, all_dates, dayInterval = NULL, quiet = FALSE, is_allocator = FALSE, ...) {

R/R/clusters.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ errors_scores <- function(df, balance = rep(1, 3), ts_validation = TRUE, ...) {
382382
position = position_nudge(x = -0.02, y = 0.1),
383383
colour = "grey30", size = 3.5
384384
) +
385-
geom_vline(xintercept = 1, linetype = "dashed", size = .5, colour = "grey75") +
385+
geom_vline(xintercept = 1, linetype = "dashed", linewidth = .5, colour = "grey75") +
386386
# scale_fill_viridis_c(option = "D") +
387387
labs(
388388
title = paste("In-Cluster", temp, "& bootstrapped 95% CI"),

R/R/convergence.R

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,6 @@ robyn_converge <- function(OutputModels,
159159
x = ifelse(max(nrmse_win) == 1, "NRMSE", sprintf("NRMSE [Winsorized %s]", paste(nrmse_win, collapse = "-"))),
160160
y = "DECOMP.RSSD",
161161
colour = "Time [s]",
162-
size = "MAPE",
163-
alpha = NULL,
164162
caption = paste(conv_msg, collapse = "\n")
165163
) +
166164
theme_lares(background = "white", )

0 commit comments

Comments
 (0)