Skip to content

Commit ac773d7

Browse files
fix: mean response and performance values standardized across functions (#1280)
1 parent d37dfd8 commit ac773d7

File tree

5 files changed

+77
-46
lines changed

5 files changed

+77
-46
lines changed

R/DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: Robyn
22
Type: Package
33
Title: Semi-Automated Marketing Mix Modeling (MMM) from Meta Marketing Science
4-
Version: 3.12.0.9007
4+
Version: 3.12.0.9008
55
Authors@R: c(
66
person("Gufeng", "Zhou", , "[email protected]", c("cre", "aut")),
77
person("Igor", "Skokan", , "[email protected]", c("aut")),

R/R/allocator.R

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,11 @@ robyn_allocator <- function(robyn_object = NULL,
133133

134134
## set local variables, sort & prompt
135135
# paid_media_spends <- InputCollect$paid_media_spends
136-
paid_media_selected <- if ("paid_media_selected" %in% names(InputCollect))
137-
InputCollect$paid_media_selected else InputCollect$paid_media_spends
136+
paid_media_selected <- if ("paid_media_selected" %in% names(InputCollect)) {
137+
InputCollect$paid_media_selected
138+
} else {
139+
InputCollect$paid_media_spends
140+
}
138141
dep_var_type <- InputCollect$dep_var_type
139142
if (is.null(select_model) && length(OutputCollect$allSolutions == 1)) {
140143
select_model <- OutputCollect$allSolutions
@@ -145,7 +148,7 @@ robyn_allocator <- function(robyn_object = NULL,
145148
mediaSelectedSorted <- paid_media_selected[media_order]
146149

147150
## Checks and constraints
148-
if ("max_historical_response" %in% scenario) scenario <- "max_response" #legacy
151+
if ("max_historical_response" %in% scenario) scenario <- "max_response" # legacy
149152
check_allocator(
150153
OutputCollect, select_model, paid_media_selected, scenario,
151154
channel_constr_low, channel_constr_up, constr_mode
@@ -164,27 +167,35 @@ robyn_allocator <- function(robyn_object = NULL,
164167
}
165168
if (length(channel_constr_low) == 1) channel_constr_low <- rep(channel_constr_low, length(paid_media_selected))
166169
if (length(channel_constr_up) == 1) channel_constr_up <- rep(channel_constr_up, length(paid_media_selected))
167-
#check_allocator_constrains(channel_constr_low, channel_constr_up)
170+
# check_allocator_constrains(channel_constr_low, channel_constr_up)
168171
names(channel_constr_low) <- names(channel_constr_up) <- paid_media_selected
169172
channelConstrLowSorted <- channel_constr_low[mediaSelectedSorted]
170173
channelConstrUpSorted <- channel_constr_up[mediaSelectedSorted]
171174

172175
## get hill parameters and coefs
173176
dt_hyppar_sorted <- OutputCollect$resultHypParam %>%
174177
filter(.data$solID == select_model) %>%
175-
select(c(hyper_names(InputCollect$adstock, mediaSelectedSorted),
176-
paste0(mediaSelectedSorted, "_inflexion"),
177-
paste0(mediaSelectedSorted, "_inflation"))) %>%
178+
select(c(
179+
hyper_names(InputCollect$adstock, mediaSelectedSorted),
180+
paste0(mediaSelectedSorted, "_inflexion"),
181+
paste0(mediaSelectedSorted, "_inflation")
182+
)) %>%
178183
select(sort(colnames(.)))
179184
dt_coef_sorted <- OutputCollect$xDecompAgg %>%
180185
filter(.data$solID == select_model & .data$rn %in% mediaSelectedSorted) %>%
181186
select("rn", "coef") %>%
182187
arrange(.data$rn)
183188
non_zero_coef_sorted <- dt_coef_sorted$coef > 0
184189
names(non_zero_coef_sorted) <- dt_coef_sorted$rn
185-
alphas <- dt_hyppar_sorted %>% select(contains("alphas")) %>% unlist
186-
inflexions <- dt_hyppar_sorted %>% select(contains("inflexion")) %>% unlist
187-
inflations <- dt_hyppar_sorted %>% select(contains("inflation")) %>% unlist
190+
alphas <- dt_hyppar_sorted %>%
191+
select(contains("alphas")) %>%
192+
unlist()
193+
inflexions <- dt_hyppar_sorted %>%
194+
select(contains("inflexion")) %>%
195+
unlist()
196+
inflations <- dt_hyppar_sorted %>%
197+
select(contains("inflation")) %>%
198+
unlist()
188199
coefs_sorted <- dt_coef_sorted$coef
189200
names(coefs_sorted) <- dt_coef_sorted$rn
190201

@@ -289,7 +300,7 @@ robyn_allocator <- function(robyn_object = NULL,
289300
x_hist_carryover = mean(hist_carryover_temp),
290301
get_sum = FALSE
291302
)
292-
initResponseUnit <- c(initResponseUnit, resp_simulate)
303+
initResponseUnit <- c(initResponseUnit, resp$mean_response) # resp_simulate
293304
initResponseMargUnit <- c(initResponseMargUnit, resp_simulate_plus1 - resp_simulate)
294305
}
295306
qa_carryover <- do.call(cbind, qa_carryover) %>% as.data.frame()

R/R/json.R

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,29 +89,38 @@ robyn_write <- function(InputCollect,
8989
stopifnot(select_model %in% OutputCollect$allSolutions)
9090
outputs <- list()
9191
outputs$select_model <- select_model
92-
df <- filter(OutputCollect$xDecompAgg, .data$solID == select_model)
92+
sp <- select(InputCollect$dt_mod, c("ds", InputCollect$paid_media_spends))
93+
df <- filter(OutputCollect$mediaVecCollect, .data$solID %in% select_model, .data$type == "decompMedia")
9394
perf_metric <- ifelse(InputCollect$dep_var_type == "revenue", "ROAS", "CPA")
94-
outputs$performance <- df %>%
95-
filter(.data$rn %in% InputCollect$paid_media_spends) %>%
96-
group_by(.data$solID) %>%
97-
summarise(
95+
performance <- left_join(
96+
tidyr::gather(dplyr::summarize_all(select(sp, InputCollect$paid_media_spends), sum), "channel", "spend"),
97+
tidyr::gather(dplyr::summarize_all(select(df, InputCollect$paid_media_spends), sum), "channel", "response"),
98+
by = "channel"
99+
) %>%
100+
dplyr::rowwise() %>%
101+
mutate(
98102
metric = perf_metric,
99103
performance = ifelse(
100104
perf_metric == "ROAS",
101-
sum(.data$xDecompAgg) / sum(.data$total_spend),
102-
sum(.data$total_spend) / sum(.data$xDecompAgg)
103-
), .groups = "drop"
105+
.data$response / .data$spend,
106+
.data$spend / .data$response
107+
)
104108
)
105-
outputs$summary <- df %>%
106-
mutate(
107-
metric = perf_metric,
108-
performance = ifelse(.data$metric == "ROAS", .data$roi_total, .data$cpa_total)
109-
) %>%
109+
outputs$performance <- performance %>%
110+
group_by(solID = select_model, .data$metric) %>%
111+
dplyr::summarize_if(is.numeric, sum) %>%
112+
mutate(solID = select_model)
113+
outputs$summary <- filter(OutputCollect$xDecompAgg, .data$solID == select_model) %>%
114+
left_join(performance, by = c("rn" = "channel")) %>%
110115
select(
111116
variable = .data$rn, coef = .data$coef,
112117
decompPer = .data$xDecompPerc, decompAgg = .data$xDecompAggRF,
113-
.data$performance, .data$mean_response, .data$mean_spend,
118+
.data$performance, "mean_response" = .data$response, "mean_spend" = .data$spend,
114119
contains("boot_mean"), contains("ci_")
120+
) %>%
121+
mutate(
122+
mean_response = .data$mean_response / InputCollect$totalObservations,
123+
mean_spend = .data$mean_spend / InputCollect$totalObservations
115124
)
116125
outputs$errors <- filter(OutputCollect$resultHypParam, .data$solID == select_model) %>%
117126
select(starts_with("rsq_"), starts_with("nrmse"), .data$decomp.rssd, .data$mape)

R/R/pareto.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ robyn_pareto <- function(InputCollect, OutputModels,
175175
)
176176
list_response <- list(
177177
dt_resp = data.frame(
178-
mean_response = get_resp$mean_response_total,
178+
mean_response = get_resp$mean_response,
179179
mean_spend_adstocked = get_resp$mean_input_immediate + get_resp$mean_input_carryover,
180180
mean_carryover = get_resp$mean_input_carryover,
181181
rn = get_media_name,

R/R/response.R

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,12 @@ robyn_response <- function(InputCollect = NULL,
132132
if (is.null(dt_hyppar)) dt_hyppar <- OutputCollect$resultHypParam
133133
if (is.null(dt_coef)) dt_coef <- OutputCollect$xDecompAgg
134134
} else {
135-
# Get pre-filled values
136-
if (is.null(dt_hyppar)) dt_hyppar <- OutputCollect$resultHypParam
137-
if (is.null(dt_coef)) dt_coef <- OutputCollect$xDecompAgg
138-
if (any(is.null(dt_hyppar), is.null(dt_coef), is.null(InputCollect), is.null(OutputCollect))) {
139-
stop("When 'json_file' is not provided, 'InputCollect' & 'OutputCollect' must be provided")
140-
}
135+
# Get pre-filled values
136+
if (is.null(dt_hyppar)) dt_hyppar <- OutputCollect$resultHypParam
137+
if (is.null(dt_coef)) dt_coef <- OutputCollect$xDecompAgg
138+
if (any(is.null(dt_hyppar), is.null(dt_coef), is.null(InputCollect), is.null(OutputCollect))) {
139+
stop("When 'json_file' is not provided, 'InputCollect' & 'OutputCollect' must be provided")
140+
}
141141
}
142142

143143
if ("selectID" %in% names(OutputCollect)) {
@@ -202,23 +202,26 @@ robyn_response <- function(InputCollect = NULL,
202202
hist_transform <- transform_decomp(
203203
all_values = all_values,
204204
adstock, theta, shape, scale, alpha, gamma,
205-
window_loc, coeff, metric_loc = ds_list$metric_loc)
205+
window_loc, coeff, metric_loc = ds_list$metric_loc
206+
)
206207
dt_line <- data.frame(
207208
metric = hist_transform$input_total[window_loc],
208209
response = hist_transform$response_total,
209-
channel = metric_name_updated)
210+
channel = metric_name_updated
211+
)
210212
dt_point <- data.frame(
211213
mean_input_immediate = hist_transform$mean_input_immediate,
212214
mean_input_carryover = hist_transform$mean_input_carryover,
213215
mean_input_total = hist_transform$mean_input_immediate + hist_transform$mean_input_carryover,
214216
mean_response_immediate = hist_transform$mean_response_total - hist_transform$mean_response_carryover,
215217
mean_response_carryover = hist_transform$mean_response_carryover,
216218
mean_response_total = hist_transform$mean_response_total
217-
)
219+
)
218220
if (!is.null(date_range)) {
219221
dt_point_sim <- data.frame(
220222
input = hist_transform$sim_mean_spend + hist_transform$sim_mean_carryover,
221-
output = hist_transform$sim_mean_response)
223+
output = hist_transform$sim_mean_response
224+
)
222225
}
223226

224227
## Simulated transformation
@@ -227,10 +230,12 @@ robyn_response <- function(InputCollect = NULL,
227230
all_values = all_values_updated,
228231
adstock, theta, shape, scale, alpha, gamma,
229232
window_loc, coeff, metric_loc = ds_list$metric_loc,
230-
calibrate_inflexion = hist_transform$inflexion)
233+
calibrate_inflexion = hist_transform$inflexion
234+
)
231235
dt_point_sim <- data.frame(
232236
input = hist_transform_sim$sim_mean_spend + hist_transform_sim$sim_mean_carryover,
233-
output = hist_transform_sim$sim_mean_response)
237+
output = hist_transform_sim$sim_mean_response
238+
)
234239
}
235240

236241
## Plot optimal response
@@ -239,17 +244,20 @@ robyn_response <- function(InputCollect = NULL,
239244
geom_point(
240245
data = dt_point,
241246
aes(x = .data$mean_input_total, y = .data$mean_response_total),
242-
size = 3, color = "grey") +
247+
size = 3, color = "grey"
248+
) +
243249
labs(
244250
title = paste(
245251
"Saturation curve of", metric_type$metric_type,
246252
"media:", metric_type$metric_name_updated
247253
),
248-
subtitle = sprintf(paste(
249-
"Response: %s @ mean input %s",
250-
"Response: %s @ mean input carryover %s",
251-
"Response: %s @ mean input immediate %s",
252-
sep = "\n"),
254+
subtitle = sprintf(
255+
paste(
256+
"Response: %s @ mean input %s",
257+
"Response: %s @ mean input carryover %s",
258+
"Response: %s @ mean input immediate %s",
259+
sep = "\n"
260+
),
253261
num_abbr(dt_point$mean_response_total),
254262
num_abbr(dt_point$mean_input_total),
255263
num_abbr(dt_point$mean_response_carryover),
@@ -294,6 +302,7 @@ robyn_response <- function(InputCollect = NULL,
294302
mean_input_carryover = hist_transform$mean_input_carryover,
295303
mean_response_total = hist_transform$mean_response_total,
296304
mean_response_carryover = hist_transform$mean_response_carryover,
305+
mean_response = hist_transform$mean_response,
297306
sim_mean_spend = sim_mean_spend,
298307
sim_mean_carryover = sim_mean_carryover,
299308
sim_mean_response = sim_mean_response,
@@ -327,7 +336,7 @@ which_usecase <- function(metric_value, date_range) {
327336
}
328337

329338
transform_decomp <- function(all_values, adstock, theta, shape, scale, alpha, gamma,
330-
window_loc, coeff, metric_loc, calibrate_inflexion = NULL) {
339+
window_loc, coeff, metric_loc, calibrate_inflexion = NULL) {
331340
## adstock
332341
x_list <- transform_adstock(x = all_values, adstock, theta, shape, scale)
333342
input_total <- x_list$x_decayed
@@ -348,6 +357,7 @@ transform_decomp <- function(all_values, adstock, theta, shape, scale, alpha, ga
348357
## simulate mean response of all_values periods
349358
mean_input_immediate <- mean(input_immediate[window_loc])
350359
mean_input_carryover <- mean(input_carryover_rw)
360+
mean_response <- mean(saturated_total$x_saturated[window_loc] * coeff)
351361
mean_response_total <- fx_objective(
352362
x = mean_input_immediate,
353363
coeff = coeff,
@@ -392,6 +402,7 @@ transform_decomp <- function(all_values, adstock, theta, shape, scale, alpha, ga
392402
mean_input_immediate = mean_input_immediate,
393403
mean_input_carryover = mean_input_carryover,
394404
mean_response_total = mean_response_total,
405+
mean_response = mean_response,
395406
mean_response_carryover = mean_response_carryover,
396407
sim_mean_spend = sim_mean_spend,
397408
sim_mean_carryover = sim_mean_carryover,

0 commit comments

Comments
 (0)