Executive Summary
This analysis implements a comprehensive Marketing Mix Model (MMM)
for pharmaceutical marketing channels using:
- Panel regression with fixed/random effects
- Adstock transformation to capture carryover
effects
- ROI and marginal ROI calculations from regression
coefficients
- Two optimization scenarios: Fixed budget
reallocation and unconstrained budget expansion
1. Setup and Package Loading
# Install and load required packages
required_packages <- c("dplyr", "tidyr", "scales", "gt", "plotly", "plm", "lmtest")
suppressPackageStartupMessages({
for(pkg in required_packages) {
if(!require(pkg, character.only = TRUE, quietly = TRUE)) {
install.packages(pkg, quiet = TRUE)
library(pkg, character.only = TRUE, quietly = TRUE)
}
}
})
# Verify plm is loaded
if(!"plm" %in% loadedNamespaces()) {
stop("Package 'plm' failed to load. Please install manually: install.packages('plm')")
}
set.seed(123)
2. Data Generation
2.1 Generate Panel Data
generate_mmm_data <- function(n_periods = 52, n_regions = 20) {
# Create panel structure: regions over time (increased to 20 regions)
regions <- paste0("Region_", LETTERS[1:n_regions])
data <- expand.grid(
Week = 1:n_periods,
Region = regions
)
# Get number of rows for random generation
n_rows <- nrow(data)
# Generate marketing spend by channel
data <- data %>%
mutate(
# Regional fixed effects
region_effect = as.numeric(factor(Region)) * 50000,
# Marketing channels with realistic spending patterns
Digital_Spend = rnorm(n_rows, 80000, 15000) + region_effect * 0.3,
TV_Spend = rnorm(n_rows, 150000, 25000) + region_effect * 0.5,
Print_Spend = rnorm(n_rows, 40000, 8000) + region_effect * 0.2,
Sales_Rep_Spend = rnorm(n_rows, 120000, 20000) + region_effect * 0.4,
Events_Spend = rnorm(n_rows, 60000, 12000) + region_effect * 0.25,
# Control variables
Seasonality = sin(2 * pi * Week / 52) * 100000 + 500000,
Competitor_Spend = rnorm(n_rows, 200000, 30000),
Price = rnorm(n_rows, 100, 10)
) %>%
# Ensure non-negative spending
mutate(across(ends_with("_Spend"), ~pmax(., 0)))
return(data)
}
# Generate base data
mmm_data <- generate_mmm_data(n_periods = 52, n_regions = 20)
cat("Data dimensions:", nrow(mmm_data), "rows x", ncol(mmm_data), "columns\n")
## Data dimensions: 1040 rows x 11 columns
cat("Time periods:", length(unique(mmm_data$Week)), "weeks\n")
## Time periods: 52 weeks
cat("Regions:", length(unique(mmm_data$Region)), "regions\n")
## Regions: 20 regions
2.3 Generate Revenue
# Generate revenue based on adstocked variables with realistic effects
mmm_data <- mmm_data %>%
mutate(
# True data generating process
Revenue = 200000 +
# Adstocked channel effects (coefficients represent true ROI components)
0.85 * Digital_Spend_Adstock +
0.65 * TV_Spend_Adstock +
1.10 * Print_Spend_Adstock +
0.75 * Sales_Rep_Spend_Adstock +
0.55 * Events_Spend_Adstock +
# Control variables
0.8 * Seasonality +
-0.3 * Competitor_Spend +
2000 * Price +
# Regional fixed effects
region_effect +
# Random error
rnorm(nrow(.), 0, 50000)
) %>%
mutate(Revenue = pmax(Revenue, 0))
cat("Revenue summary:\n")
## Revenue summary:
summary(mmm_data$Revenue)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1258001 2548738 3472784 3492740 4458112 5601914
3. Panel Regression Models
cat("\n========================================")
##
## ========================================
cat("\n MARKETING MIX MODEL ESTIMATION")
##
## MARKETING MIX MODEL ESTIMATION
cat("\n========================================\n")
##
## ========================================
# Prepare panel data object
mmm_data$Region <- as.character(mmm_data$Region)
mmm_data$Week <- as.integer(mmm_data$Week)
panel_data <- plm::pdata.frame(mmm_data, index = c("Region", "Week"))
# Model formula
formula_pooled <- Revenue ~ Digital_Spend_Adstock + TV_Spend_Adstock +
Print_Spend_Adstock + Sales_Rep_Spend_Adstock + Events_Spend_Adstock +
Seasonality + Competitor_Spend + Price
# Model 1: Pooled OLS (baseline)
model_pooled <- plm::plm(formula_pooled, data = panel_data, model = "pooling")
# Model 2: Fixed Effects Model
model_fe <- plm::plm(formula_pooled, data = panel_data, model = "within")
# Model 3: Random Effects Model
model_re <- tryCatch({
plm::plm(formula_pooled, data = panel_data, model = "random", random.method = "swar")
}, error = function(e) {
message("Swamy-Arora method failed, using alternative random effects estimation")
plm::plm(formula_pooled, data = panel_data, model = "random", random.method = "amemiya")
})
# Model 4: Mixed Effects Model
cat("\n--- Estimating Mixed Effects Model ---\n")
##
## --- Estimating Mixed Effects Model ---
model_mixed <- tryCatch({
plm::pvcm(Revenue ~ Digital_Spend_Adstock + TV_Spend_Adstock +
Print_Spend_Adstock + Sales_Rep_Spend_Adstock + Events_Spend_Adstock +
Seasonality + Competitor_Spend + Price,
data = panel_data,
model = "random",
effect = "individual")
}, error = function(e) {
message("Mixed effects model failed, will use fixed or random effects instead")
NULL
})
# Hausman test
hausman_test <- tryCatch({
plm::phtest(model_fe, model_re)
}, error = function(e) {
message("Hausman test could not be computed, defaulting to Fixed Effects model")
list(p.value = 0.01, statistic = NA, method = "Manual selection")
})
cat("\n--- Hausman Test (Fixed vs Random Effects) ---\n")
##
## --- Hausman Test (Fixed vs Random Effects) ---
print(hausman_test)
##
## Hausman Test
##
## data: formula_pooled
## chisq = 632.7, df = 8, p-value < 2.2e-16
## alternative hypothesis: one model is inconsistent
# Select best model
if(hausman_test$p.value < 0.05) {
best_model <- model_fe
model_type <- "Fixed Effects"
cat("\n*** Fixed Effects Model Selected (p < 0.05) ***\n")
} else {
best_model <- model_re
model_type <- "Random Effects"
cat("\n*** Random Effects Model Selected (p >= 0.05) ***\n")
}
##
## *** Fixed Effects Model Selected (p < 0.05) ***
3.1 Model Summary Table
# Extract model summary
model_summary_data <- summary(best_model)
# Extract coefficients table
coef_table <- as.data.frame(coef(model_summary_data))
coef_table$Variable <- rownames(coef_table)
rownames(coef_table) <- NULL
coef_table <- coef_table %>%
select(Variable, Estimate, `Std. Error`, `t-value`, `Pr(>|t|)`)
# Create gt table
gt_model_summary <- coef_table %>%
gt() %>%
tab_header(
title = paste(model_type, "Model: Regression Coefficients"),
subtitle = "Marketing Mix Model with Adstock Transformation"
) %>%
fmt_number(
columns = c(Estimate, `Std. Error`, `t-value`, `Pr(>|t|)`),
decimals = 4
) %>%
cols_label(
Variable = "Variable",
Estimate = "Coefficient",
`Std. Error` = "Std. Error",
`t-value` = "t-statistic",
`Pr(>|t|)` = "p-value"
) %>%
data_color(
columns = `Pr(>|t|)`,
colors = scales::col_numeric(
palette = c("#006400", "#90EE90", "#FFD700", "#FF6B6B"),
domain = c(0, 0.1),
reverse = TRUE
)
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_body(columns = everything(), rows = `Pr(>|t|)` < 0.05)
) %>%
tab_style(
style = cell_text(weight = "bold"),
locations = cells_column_labels()
) %>%
tab_source_note(
source_note = paste0(
"Model: ", model_type, " | R² = ", sprintf("%.4f", model_summary_data$r.squared[1])
)
)
gt_model_summary
| Fixed Effects Model: Regression Coefficients |
| Marketing Mix Model with Adstock Transformation |
| Variable |
Coefficient |
Std. Error |
t-statistic |
p-value |
| Digital_Spend_Adstock |
0.8621 |
0.0993 |
8.6785 |
0.0000 |
| TV_Spend_Adstock |
0.6502 |
0.0264 |
24.5867 |
0.0000 |
| Print_Spend_Adstock |
1.0468 |
0.1512 |
6.9216 |
0.0000 |
| Sales_Rep_Spend_Adstock |
0.7564 |
0.0572 |
13.2171 |
0.0000 |
| Events_Spend_Adstock |
0.5430 |
0.1168 |
4.6495 |
0.0000 |
| Seasonality |
0.8165 |
0.0231 |
35.2858 |
0.0000 |
| Competitor_Spend |
−0.3000 |
0.0528 |
−5.6831 |
0.0000 |
| Price |
1,911.4950 |
168.3908 |
11.3515 |
0.0000 |
| Model: Fixed Effects | R² = 0.9560 |
3.2 Model Comparison
model_comparison <- data.frame(
Model = c("Pooled OLS", "Fixed Effects", "Random Effects",
if(!is.null(model_mixed)) "Mixed Effects" else NULL, "Selected Model"),
R_Squared = c(
summary(model_pooled)$r.squared[1],
summary(model_fe)$r.squared[1],
summary(model_re)$r.squared[1],
if(!is.null(model_mixed)) summary(model_mixed)$r.squared[1] else NULL,
summary(best_model)$r.squared[1]
),
Adj_R_Squared = c(
summary(model_pooled)$r.squared[2],
summary(model_fe)$r.squared[2],
summary(model_re)$r.squared[2],
if(!is.null(model_mixed)) summary(model_mixed)$r.squared[2] else NULL,
summary(best_model)$r.squared[2]
),
N_Obs = c(
nobs(model_pooled), nobs(model_fe), nobs(model_re),
if(!is.null(model_mixed)) nobs(model_mixed) else NULL, nobs(best_model)
)
)
gt_models <- model_comparison %>%
gt() %>%
tab_header(title = "Panel Regression Model Comparison") %>%
fmt_number(columns = c(R_Squared, Adj_R_Squared), decimals = 4) %>%
fmt_number(columns = N_Obs, decimals = 0) %>%
tab_style(
style = cell_fill(color = "#E8F4F8"),
locations = cells_body(rows = Model == "Selected Model")
)
gt_models
| Panel Regression Model Comparison |
| Model |
R_Squared |
Adj_R_Squared |
N_Obs |
| Pooled OLS |
0.9966 |
0.9966 |
1,040 |
| Fixed Effects |
0.9560 |
0.9548 |
1,040 |
| Random Effects |
0.9956 |
0.9956 |
1,040 |
| Selected Model |
0.9560 |
0.9548 |
1,040 |
4. ROI and mROI Calculations
# Extract coefficients
coefficients <- coef(best_model)
channel_coefs <- coefficients[grep("_Spend_Adstock", names(coefficients))]
# Calculate totals
spend_summary <- mmm_data %>%
summarise(
Total_Digital_Spend = sum(Digital_Spend),
Total_TV_Spend = sum(TV_Spend),
Total_Print_Spend = sum(Print_Spend),
Total_Sales_Rep_Spend = sum(Sales_Rep_Spend),
Total_Events_Spend = sum(Events_Spend),
Total_Digital_Adstock = sum(Digital_Spend_Adstock),
Total_TV_Adstock = sum(TV_Spend_Adstock),
Total_Print_Adstock = sum(Print_Spend_Adstock),
Total_Sales_Rep_Adstock = sum(Sales_Rep_Spend_Adstock),
Total_Events_Adstock = sum(Events_Spend_Adstock)
)
# Create ROI dataframe
roi_data <- data.frame(
Channel = c("Digital", "TV", "Print", "Sales_Rep", "Events"),
Coefficient = c(
channel_coefs["Digital_Spend_Adstock"],
channel_coefs["TV_Spend_Adstock"],
channel_coefs["Print_Spend_Adstock"],
channel_coefs["Sales_Rep_Spend_Adstock"],
channel_coefs["Events_Spend_Adstock"]
),
Total_Spend = c(
spend_summary$Total_Digital_Spend,
spend_summary$Total_TV_Spend,
spend_summary$Total_Print_Spend,
spend_summary$Total_Sales_Rep_Spend,
spend_summary$Total_Events_Spend
),
Total_Adstock = c(
spend_summary$Total_Digital_Adstock,
spend_summary$Total_TV_Adstock,
spend_summary$Total_Print_Adstock,
spend_summary$Total_Sales_Rep_Adstock,
spend_summary$Total_Events_Adstock
),
Decay_Rate = decay_rates
)
rownames(roi_data) <- NULL
roi_data <- roi_data %>%
mutate(
Total_Contribution = Coefficient * Total_Adstock,
ROI = Total_Contribution / Total_Spend,
mROI_Immediate = Coefficient,
mROI_Steady_State = Coefficient / (1 - Decay_Rate),
Efficiency = ROI * (1 + Decay_Rate)
) %>%
arrange(desc(ROI))
4.1 ROI and mROI Table
gt_roi <- roi_data %>%
select(Channel, Total_Spend, Total_Contribution, ROI,
mROI_Immediate, mROI_Steady_State, Decay_Rate) %>%
gt() %>%
tab_header(
title = "Marketing Mix Model: ROI & Marginal ROI Analysis",
subtitle = "Based on Panel Regression with Adstock Transformation"
) %>%
fmt_currency(columns = c(Total_Spend, Total_Contribution), decimals = 0) %>%
fmt_number(columns = c(ROI, mROI_Immediate, mROI_Steady_State), decimals = 3) %>%
fmt_number(columns = Decay_Rate, decimals = 2) %>%
data_color(
columns = ROI,
colors = scales::col_numeric(
palette = c("#FF6B6B", "#FFD93D", "#6BCF7F", "#4D96FF"),
domain = NULL
)
)
gt_roi
| Marketing Mix Model: ROI & Marginal ROI Analysis |
| Based on Panel Regression with Adstock Transformation |
| Channel |
Total_Spend |
Total_Contribution |
ROI |
mROI_Immediate |
mROI_Steady_State |
Decay_Rate |
| TV |
$429,749,589 |
$889,599,331 |
2.070 |
0.650 |
2.167 |
0.70 |
| Print |
$150,589,946 |
$309,150,484 |
2.053 |
1.047 |
2.094 |
0.50 |
| Sales_Rep |
$342,961,943 |
$629,862,865 |
1.837 |
0.756 |
1.891 |
0.60 |
| Digital |
$247,390,231 |
$302,196,735 |
1.222 |
0.862 |
1.232 |
0.30 |
| Events |
$198,776,067 |
$177,577,485 |
0.893 |
0.543 |
0.905 |
0.40 |
5. Budget Optimization
5.1 Scenario 1: Fixed Budget
total_budget <- sum(roi_data$Total_Spend)
optimal_allocation_fixed <- roi_data %>%
mutate(
Weight = mROI_Steady_State / sum(mROI_Steady_State),
Optimal_Budget = Weight * total_budget,
Current_Budget = Total_Spend,
Budget_Change = Optimal_Budget - Current_Budget,
Change_Pct = (Budget_Change / Current_Budget) * 100
) %>%
select(Channel, Current_Budget, Optimal_Budget, Budget_Change,
Change_Pct, ROI, mROI_Steady_State)
gt_allocation_fixed <- optimal_allocation_fixed %>%
gt() %>%
tab_header(
title = "SCENARIO 1: Fixed Budget Allocation",
subtitle = "Budget reallocation within current total spend"
) %>%
fmt_currency(columns = c(Current_Budget, Optimal_Budget, Budget_Change), decimals = 0) %>%
fmt_number(columns = c(Change_Pct, ROI, mROI_Steady_State), decimals = 2) %>%
data_color(
columns = Change_Pct,
colors = scales::col_numeric(
palette = c("#FF6B6B", "#FFFFFF", "#6BCF7F"),
domain = c(-100, 100)
)
)
gt_allocation_fixed
| SCENARIO 1: Fixed Budget Allocation |
| Budget reallocation within current total spend |
| Channel |
Current_Budget |
Optimal_Budget |
Budget_Change |
Change_Pct |
ROI |
mROI_Steady_State |
| TV |
$429,749,589 |
$358,096,024 |
−$71,653,565 |
−16.67 |
2.07 |
2.17 |
| Print |
$150,589,946 |
$345,916,033 |
$195,326,087 |
129.71 |
2.05 |
2.09 |
| Sales_Rep |
$342,961,943 |
$312,446,323 |
−$30,515,620 |
−8.90 |
1.84 |
1.89 |
| Digital |
$247,390,231 |
$203,490,478 |
−$43,899,753 |
−17.75 |
1.22 |
1.23 |
| Events |
$198,776,067 |
$149,518,918 |
−$49,257,149 |
−24.78 |
0.89 |
0.90 |
5.2 Scenario 2: Unconstrained Budget
# Function to calculate optimal spend
calculate_optimal_spend <- function(current_spend, current_contribution, elasticity = 0.7, target_mroi = 1.0) {
current_mroi <- current_contribution / current_spend
if(elasticity == 1) {
if(current_mroi > target_mroi) {
return(current_spend * 10)
} else {
return(current_spend)
}
}
spend_ratio <- (target_mroi / (elasticity * current_mroi))^(1/(elasticity - 1))
optimal_spend <- current_spend * spend_ratio
optimal_spend <- min(optimal_spend, current_spend * 5)
return(optimal_spend)
}
# Calculate unconstrained optimal budget
optimal_allocation_unconstrained <- roi_data %>%
mutate(
Current_Budget = Total_Spend,
Current_mROI = Total_Contribution / Total_Spend,
Elasticity = 0.7,
Optimal_Budget_Unconstrained = mapply(
calculate_optimal_spend,
current_spend = Total_Spend,
current_contribution = Total_Contribution,
elasticity = Elasticity,
target_mroi = 1.0
),
Spend_Ratio = Optimal_Budget_Unconstrained / Total_Spend,
Optimal_Contribution = Total_Contribution * (Spend_Ratio^Elasticity),
Budget_Change = Optimal_Budget_Unconstrained - Current_Budget,
Budget_Change_Pct = (Budget_Change / Current_Budget) * 100,
Contribution_Change = Optimal_Contribution - Total_Contribution,
Incremental_ROI = Contribution_Change / Budget_Change
) %>%
select(Channel, Current_Budget, Current_mROI, Optimal_Budget_Unconstrained,
Budget_Change, Budget_Change_Pct, Contribution_Change, Incremental_ROI)
gt_allocation_unconstrained <- optimal_allocation_unconstrained %>%
gt() %>%
tab_header(
title = "SCENARIO 2: Unconstrained Budget Allocation",
subtitle = "Expand spending until marginal ROI equals 1.0"
) %>%
fmt_currency(columns = c(Current_Budget, Optimal_Budget_Unconstrained,
Budget_Change, Contribution_Change), decimals = 0) %>%
fmt_number(columns = c(Current_mROI, Budget_Change_Pct, Incremental_ROI), decimals = 3)
gt_allocation_unconstrained
| SCENARIO 2: Unconstrained Budget Allocation |
| Expand spending until marginal ROI equals 1.0 |
| Channel |
Current_Budget |
Current_mROI |
Optimal_Budget_Unconstrained |
Budget_Change |
Budget_Change_Pct |
Contribution_Change |
Incremental_ROI |
| TV |
$429,749,589 |
2.070 |
$1,479,581,400 |
$1,049,831,812 |
244.289 |
$1,224,088,383 |
1.166 |
| Print |
$150,589,946 |
2.053 |
$504,315,853 |
$353,725,907 |
234.893 |
$411,300,735 |
1.163 |
| Sales_Rep |
$342,961,943 |
1.837 |
$792,330,379 |
$449,368,435 |
131.026 |
$502,037,676 |
1.117 |
| Digital |
$247,390,231 |
1.222 |
$146,802,489 |
−$100,587,742 |
−40.660 |
−$92,478,893 |
0.919 |
| Events |
$198,776,067 |
0.893 |
$41,569,064 |
−$157,207,002 |
−79.087 |
−$118,193,107 |
0.752 |
# Store for compatibility
optimal_allocation <- optimal_allocation_fixed
6. Visualizations
# Add optimal budget for plotting
roi_data_plot <- roi_data %>%
left_join(optimal_allocation_fixed %>% select(Channel, Optimal_Budget), by = "Channel")
6.1 ROI vs mROI Bubble Chart
p1 <- plot_ly(roi_data_plot,
x = ~ROI, y = ~mROI_Steady_State, text = ~Channel,
type = 'scatter', mode = 'markers+text',
marker = list(size = ~sqrt(Total_Spend)/100, color = ~Decay_Rate,
colorscale = 'Viridis', showscale = TRUE,
colorbar = list(title = "Decay Rate"),
line = list(color = 'white', width = 2)),
textposition = 'top center',
textfont = list(size = 12, color = 'black'),
hoverinfo = 'text',
hovertext = ~paste0('<b>', Channel, '</b><br>',
'ROI: ', round(ROI, 3), '<br>',
'mROI (SS): ', round(mROI_Steady_State, 3), '<br>',
'Current Spend: $', formatC(Total_Spend, format = "f", digits = 0, big.mark = ","), '<br>',
'Optimal Budget: $', formatC(Optimal_Budget, format = "f", digits = 0, big.mark = ","))) %>%
layout(title = list(text = "ROI vs Marginal ROI (Steady State)"),
xaxis = list(title = "ROI", zeroline = TRUE),
yaxis = list(title = "mROI (Steady State)", zeroline = TRUE),
plot_bgcolor = 'white', paper_bgcolor = 'white')
p1
6.2 Channel Contributions
p2 <- plot_ly(roi_data %>% arrange(desc(Total_Contribution)),
x = ~Total_Contribution, y = ~reorder(Channel, Total_Contribution),
type = 'bar',
marker = list(color = ~ROI,
colorscale = list(c(0, "#FF6B6B"), c(1, "#6BCF7F")),
showscale = TRUE)) %>%
layout(title = "Total Revenue Contribution by Channel",
xaxis = list(title = "Revenue Contribution ($)"),
yaxis = list(title = ""))
p2
6.3 Budget Allocation Comparison
p4 <- plot_ly(optimal_allocation_fixed, y = ~Channel) %>%
add_trace(x = ~Current_Budget, name = 'Current', type = 'bar', orientation = 'h',
marker = list(color = '#FFA07A')) %>%
add_trace(x = ~Optimal_Budget, name = 'Optimal', type = 'bar', orientation = 'h',
marker = list(color = '#6BCF7F')) %>%
layout(title = "SCENARIO 1: Budget Reallocation",
xaxis = list(title = "Budget ($)"),
barmode = 'group')
p4
6.4 Unconstrained Budget Expansion
p6 <- plot_ly(optimal_allocation_unconstrained, y = ~Channel) %>%
add_trace(x = ~Current_Budget, name = 'Current', type = 'bar', orientation = 'h',
marker = list(color = '#FFA07A')) %>%
add_trace(x = ~Optimal_Budget_Unconstrained, name = 'Optimal (Unconstrained)',
type = 'bar', orientation = 'h', marker = list(color = '#4D96FF')) %>%
layout(title = "SCENARIO 2: Budget Expansion",
xaxis = list(title = "Budget ($)"),
barmode = 'group')
p6
7. Key Insights
best_roi_channel <- roi_data$Channel[which.max(roi_data$ROI)]
best_mroi_channel <- roi_data$Channel[which.max(roi_data$mROI_Steady_State)]
cat("\n========================================")
##
## ========================================
cat("\n KEY INSIGHTS")
##
## KEY INSIGHTS
cat("\n========================================\n")
##
## ========================================
cat(sprintf("\n1. Model: %s (R² = %.4f)\n", model_type, summary(best_model)$r.squared[1]))
##
## 1. Model: Fixed Effects (R² = 0.9560)
cat(sprintf("\n2. Best ROI Channel: %s (%.3f)\n", best_roi_channel, max(roi_data$ROI)))
##
## 2. Best ROI Channel: TV (2.070)
cat(sprintf("\n3. Highest mROI: %s (%.3f)\n", best_mroi_channel, max(roi_data$mROI_Steady_State)))
##
## 3. Highest mROI: TV (2.167)
cat(sprintf("\n4. Total Budget: $%s\n", formatC(total_budget, format = "f", digits = 0, big.mark = ",")))
##
## 4. Total Budget: $1,369,467,776
cat(sprintf("\n5. Total Contribution: $%s\n", formatC(sum(roi_data$Total_Contribution), format = "f", digits = 0, big.mark = ",")))
##
## 5. Total Contribution: $2,308,386,900
cat(sprintf("\n6. Portfolio ROI: %.3f\n", sum(roi_data$Total_Contribution) / total_budget))
##
## 6. Portfolio ROI: 1.686
cat("\n========================================\n")
##
## ========================================
Session Info
sessionInfo()
## R version 4.5.1 (2025-06-13)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 22.04.5 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.10.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0 LAPACK version 3.10.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## time zone: America/New_York
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] lmtest_0.9-40 zoo_1.8-14 plm_2.6-7 plotly_4.11.0 ggplot2_4.0.0
## [6] gt_1.1.0 scales_1.4.0 tidyr_1.3.1 dplyr_1.1.4
##
## loaded via a namespace (and not attached):
## [1] sandwich_3.1-1 sass_0.4.10 generics_0.1.4 xml2_1.4.0
## [5] lattice_0.22-5 digest_0.6.37 magrittr_2.0.4 evaluate_1.0.5
## [9] grid_4.5.1 RColorBrewer_1.1-3 fastmap_1.2.0 jsonlite_2.0.0
## [13] Formula_1.2-5 httr_1.4.7 purrr_1.1.0 crosstalk_1.2.2
## [17] viridisLite_0.4.2 lazyeval_0.2.2 jquerylib_0.1.4 Rdpack_2.6.4
## [21] cli_3.6.5 rlang_1.1.6 rbibutils_2.4 miscTools_0.6-28
## [25] withr_3.0.2 cachem_1.1.0 yaml_2.3.10 parallel_4.5.1
## [29] tools_4.5.1 bdsmatrix_1.3-7 maxLik_1.5-2.1 vctrs_0.6.5
## [33] R6_2.6.1 lifecycle_1.0.4 fs_1.6.6 htmlwidgets_1.6.4
## [37] MASS_7.3-65 pkgconfig_2.0.3 pillar_1.11.1 bslib_0.9.0
## [41] gtable_0.3.6 Rcpp_1.1.0 glue_1.8.0 data.table_1.17.8
## [45] collapse_2.1.5 xfun_0.53 tibble_3.3.0 tidyselect_1.2.1
## [49] rstudioapi_0.17.1 knitr_1.50 farver_2.1.2 nlme_3.1-168
## [53] htmltools_0.5.8.1 rmarkdown_2.30 compiler_4.5.1 S7_0.2.0