Skip to content

Commit

Permalink
feat: keep custom exported data from JSON when recreating model & use…
Browse files Browse the repository at this point in the history
… raw_data if available + update docs (#930)
  • Loading branch information
laresbernardo authored Mar 1, 2024
1 parent 2fb757c commit cf7dae7
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 51 deletions.
4 changes: 2 additions & 2 deletions R/DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Package: Robyn
Type: Package
Title: Semi-Automated Marketing Mix Modeling (MMM) from Meta Marketing Science
Version: 3.10.6.9000
Version: 3.10.6.9001
Authors@R: c(
person("Gufeng", "Zhou", , "[email protected]", c("cre","aut")),
person("Bernardo", "Lares", , "[email protected]", c("aut")),
person("Leonel", "Sentana", , "[email protected]", c("aut")),
person("Igor", "Skokan", , "[email protected]", c("aut")),
person("Bernardo", "Lares", , "[email protected]", c("aut")),
person("Meta Platforms, Inc.", role = c("cph", "fnd")))
Maintainer: Gufeng Zhou <[email protected]>
Description: Semi-Automated Marketing Mix Modeling (MMM) aiming to reduce human bias by means of ridge regression and evolutionary algorithms, enables actionable decision making providing a budget allocation and diminishing returns curves and allows ground-truth calibration to account for causation.
Expand Down
4 changes: 2 additions & 2 deletions R/R/clusters.R
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,10 @@ errors_scores <- function(df, balance = rep(1, 3), ts_validation = TRUE, ...) {
top_sols %>%
left_join(real_rois, by = c("solID" = "real_solID")) %>%
mutate(label = sprintf("[%s.%s]\n%s", .data$cluster, .data$rank, .data$solID)) %>%
tidyr::gather("media", "roi", contains(all_media)) %>%
tidyr::gather("media", "perf", contains(all_media)) %>%
filter(grepl("real_", .data$media)) %>%
mutate(media = gsub("real_", "", .data$media)) %>%
ggplot(aes(x = reorder(.data$media, .data$roi), y = .data$roi)) +
ggplot(aes(x = reorder(.data$media, .data$perf), y = .data$perf)) +
facet_grid(.data$label ~ .) +
geom_col() +
coord_flip() +
Expand Down
14 changes: 11 additions & 3 deletions R/R/inputs.R
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,10 @@
#' Check "Guide for calibration source" section.
#' @param InputCollect Default to NULL. \code{robyn_inputs}'s output when
#' \code{hyperparameters} are not yet set.
#' @param json_file Character. JSON file to import previously exported inputs
#' (needs \code{dt_input} and \code{dt_holidays} parameters too).
#' @param json_file Character. JSON file to import previously exported inputs or
#' recreate a model. To generate this file, use \code{robyn_write()}.
#' If you didn't export your data in the json file as "raw_data",
#' \code{dt_input} must be provided; \code{dt_holidays} input is optional.
#' @param ... Additional parameters passed to \code{prophet} functions.
#' @examples
#' # Using dummy simulated data
Expand Down Expand Up @@ -177,7 +179,13 @@ robyn_inputs <- function(dt_input = NULL,
### Use case 3: running robyn_inputs() with json_file
if (!is.null(json_file)) {
json <- robyn_read(json_file, step = 1, ...)
if (is.null(dt_input)) stop("Must provide 'dt_input' input; 'dt_holidays' input optional")
if (is.null(dt_input)) {
if ("raw_data" %in% names(json[["Extras"]])) {
dt_input <- json[["Extras"]]$raw_data
} else {
stop("Must provide 'dt_input' input; 'dt_holidays' input optional")
}
}
if (!is.null(hyperparameters)) {
warning("Replaced hyperparameters input with json_file's fixed hyperparameters values")
}
Expand Down
37 changes: 26 additions & 11 deletions R/R/json.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
####################################################################
#' Import and Export Robyn JSON files
#'
#' \code{robyn_write()} generates JSON files with all the information
#' \code{robyn_write()} generates light JSON files with all the information
#' required to replicate Robyn models. Depending on user inputs, there are
#' 3 use cases: only the inputs data, input data + modeling results data,
#' and input data, modeling results + specifics of a single selected model.
Expand All @@ -20,11 +20,12 @@
#' @param dir Character. Existing directory to export JSON file to.
#' @param pareto_df Dataframe. Save all pareto solutions to json file.
#' @param ... Additional parameters to export into a custom Extras element.
#' If you wish to include the data to recreate a model, add
#' \code{raw_data = InputCollect$dt_input}.
#' @examples
#' \dontrun{
#' InputCollectJSON <- robyn_inputs(
#' dt_input = Robyn::dt_simulated_weekly,
#' dt_holidays = Robyn::dt_prophet_holidays,
#' json_file = "~/Desktop/RobynModel-1_29_12.json"
#' )
#' print(InputCollectJSON)
Expand Down Expand Up @@ -87,10 +88,20 @@ robyn_write <- function(InputCollect,
stopifnot(select_model %in% OutputCollect$allSolutions)
outputs <- list()
outputs$select_model <- select_model
outputs$summary <- filter(OutputCollect$xDecompAgg, .data$solID == select_model) %>%
df <- filter(OutputCollect$xDecompAgg, .data$solID == select_model)
perf_metric <- ifelse(InputCollect$dep_var_type == "revenue", "ROAS", "CPA")
outputs$performance <- df %>%
filter(.data$rn %in% InputCollect$paid_media_spends) %>%
group_by(.data$solID) %>%
summarise(metric = perf_metric,
performance = ifelse(
perf_metric == "ROAS",
sum(.data$xDecompAgg) / sum(.data$total_spend),
sum(.data$total_spend) / sum(.data$xDecompAgg)), .groups = "drop")
outputs$summary <- df %>%
mutate(
metric = ifelse(InputCollect$dep_var_type == "revenue", "ROI", "CPA"),
performance = ifelse(.data$metric == "ROI", .data$roi_total, .data$cpa_total)
metric = perf_metric,
performance = ifelse(.data$metric == "ROAS", .data$roi_total, .data$cpa_total)
) %>%
select(
variable = .data$rn, coef = .data$coef,
Expand Down Expand Up @@ -158,7 +169,7 @@ robyn_write <- function(InputCollect,
#' @param x \code{robyn_read()} or \code{robyn_write()} output.
#' @export
print.robyn_write <- function(x, ...) {
val <- isTRUE(x$ExportedModel$ts_validation)
val <- any(c(x$ExportedModel$ts_validation, x$ModelsCollect$ts_validation))
print(glued(
"
Exported directory: {x$ExportedModel$plot_folder}
Expand All @@ -173,7 +184,10 @@ print.robyn_write <- function(x, ...) {
))
errors <- x$ExportedModel$errors
print(glued(
"\n\nModel's Performance and Errors:\n {errors}",
"\n\nModel's Performance and Errors:\n {performance}{errors}",
performance = ifelse("performance" %in% names(x$ExportedModel), sprintf(
"Total Model %s = %s\n ",
x$ExportedModel$performance$metric, signif(x$ExportedModel$performance$performance, 4)), ""),
errors = paste(
sprintf(
"Adj.R2 (%s): %s",
Expand All @@ -190,7 +204,7 @@ print.robyn_write <- function(x, ...) {

print(x$ExportedModel$summary %>%
select(-contains("boot"), -contains("ci_")) %>%
dplyr::rename_at("performance", list(~ ifelse(x$InputCollect$dep_var_type == "revenue", "ROI", "CPA"))) %>%
dplyr::rename_at("performance", list(~ ifelse(x$InputCollect$dep_var_type == "revenue", "ROAS", "CPA"))) %>%
mutate(decompPer = formatNum(100 * .data$decompPer, pos = "%")) %>%
dplyr::mutate_if(is.numeric, function(x) ifelse(!is.infinite(x), x, 0)) %>%
dplyr::mutate_if(is.numeric, function(x) formatNum(x, 4, abbr = TRUE)) %>%
Expand Down Expand Up @@ -219,8 +233,8 @@ print.robyn_write <- function(x, ...) {

#' @rdname robyn_write
#' @aliases robyn_write
#' @param json_file Character. JSON file name to read and import as list.
#' @param step Integer. 1 for import only and 2 for import and ouput.
#' @param json_file Character. JSON file name to read and import.
#' @param step Integer. 1 for import only and 2 for import and output.
#' @export
robyn_read <- function(json_file = NULL, step = 1, quiet = FALSE, ...) {
if (!is.null(json_file)) {
Expand Down Expand Up @@ -335,7 +349,8 @@ robyn_recreate <- function(json_file, quiet = FALSE, ...) {
}
return(invisible(list(
InputCollect = InputCollect,
OutputCollect = OutputCollect
OutputCollect = OutputCollect,
Extras = json[["Extras"]]
)))
}

Expand Down
13 changes: 11 additions & 2 deletions R/R/plots.R
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,15 @@ robyn_onepagers <- function(
round(plotMediaShareLoop$mape[1], 4), NA
)
train_size <- round(plotMediaShareLoop$train_size[1], 4)
type <- ifelse(InputCollect$dep_var_type == "conversion", "CPA", "ROAS")
perf <- filter(OutputCollect$xDecompAgg, .data$solID == sid) %>%
filter(.data$rn %in% InputCollect$paid_media_spends) %>%
group_by(.data$solID) %>%
summarise(performance = ifelse(
type == "ROAS",
sum(.data$xDecompAgg) / sum(.data$total_spend),
sum(.data$total_spend) / sum(.data$xDecompAgg))) %>%
pull(.data$performance) %>% signif(., 3)
if (val) {
errors <- sprintf(
paste(
Expand All @@ -355,7 +364,6 @@ robyn_onepagers <- function(
plotMediaShareLoopLine <- temp[[sid]]$plot1data$plotMediaShareLoopLine
ySecScale <- temp[[sid]]$plot1data$ySecScale
plotMediaShareLoopBar$variable <- stringr::str_to_title(gsub("_", " ", plotMediaShareLoopBar$variable))
type <- ifelse(InputCollect$dep_var_type == "conversion", "CPA", "ROAS")
plotMediaShareLoopLine$type_colour <- type_colour <- "#03396C"
names(type_colour) <- "type_colour"
p1 <- ggplot(plotMediaShareLoopBar, aes(x = .data$rn, y = .data$value, fill = .data$variable)) +
Expand Down Expand Up @@ -625,6 +633,7 @@ robyn_onepagers <- function(
calc <- ifelse(type == "ROAS",
"Total ROAS = sum of response / sum of spend",
"Total CPA = sum of spend / sum of response")
calc <- paste(c(calc, perf), collapse = " = ")
onepagerCaption <- paste0(
"*", calc, " in modeling window ", paste0(window, collapse = ":"),
"\n", onepagerCaption)
Expand Down Expand Up @@ -1386,7 +1395,7 @@ refresh_plots_json <- function(OutputCollectRF, json_file, export = TRUE, ...) {
labs(
title = paste(
"Model refresh: Decomposition & Paid Media",
ifelse(chainData[[1]]$InputCollect$dep_var_type == "revenue", "ROI", "CPA")
ifelse(chainData[[1]]$InputCollect$dep_var_type == "revenue", "ROAS", "CPA")
),
subtitle = paste(
"Baseline includes intercept and all prophet vars:",
Expand Down
6 changes: 4 additions & 2 deletions R/man/robyn_allocator.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions R/man/robyn_inputs.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions R/man/robyn_mmm.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions R/man/robyn_refresh.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions R/man/robyn_response.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions R/man/robyn_run.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions R/man/robyn_train.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions R/man/robyn_write.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit cf7dae7

Please sign in to comment.