# 1. Create repo-local library and cache directoriesmkdir -p .Rlib .cache
# 2. Set environment variables (add to .bashrc/.zshrc for persistence)exportR_LIBS_USER="$PWD/.Rlib"exportXDG_CACHE_HOME="$PWD/.cache"# 3. Install DSAMbayes from the local checkoutR_LIBS_USER="$PWD/.Rlib" R -q -e 'install.packages(".", repos = NULL, type = "source")'
This keeps all package libraries and Stan compilation caches inside the repo, avoiding permission issues with system library paths.
Verify the installation
1. Confirm DSAMbayes loads
R_LIBS_USER="$PWD/.Rlib" R -q -e 'library(DSAMbayes); cat("Version:", as.character(utils::packageVersion("DSAMbayes")), "\n")'
Expected: prints Version: 1.2.2 (or current version).
Symptom:install.packages(".", repos = NULL, type = "source") errors.
Actions:
Confirm you are in the repository root directory.
Confirm .Rlib exists and is writable: ls -la .Rlib.
Check for missing system dependencies in the error output.
Stale Stan cache
Symptom: unexpected model behaviour after updating the package.
Actions:
Clear the cache: rm -rf .cache/dsambayes.
Re-run with model.force_recompile: true in your config (or leave default false — v1.2.2 auto-detects stale caches).
Permission issues
Symptom: write failures for library, cache, or run outputs.
Actions:
Ensure .Rlib, .cache, and results/ are writable.
Keep R_LIBS_USER and XDG_CACHE_HOME set in your shell session.
Run all commands from the repository root.
Concepts
What is DSAMbayes?
DSAMbayes is an R package that fits Bayesian marketing mix models (MMM) using Stan. It provides a familiar lm()-style interface for specifying models, adds prior and boundary controls, and delegates estimation to Stan’s Hamiltonian Monte Carlo (HMC) sampler. The result is a full posterior distribution over model parameters — not just point estimates — enabling rigorous uncertainty quantification for media contribution and budget allocation decisions.
Why Bayesian MMM?
Classical OLS regression gives point estimates and confidence intervals that assume the model is correctly specified. In MMM, where media variables are collinear, sample sizes are small (often 100–200 weeks), and the functional form is uncertain, these assumptions are routinely violated.
Bayesian estimation addresses this by:
Regularisation through priors — weakly informative priors stabilise estimates when the data alone cannot separate correlated effects. This is particularly valuable for media channels with overlapping campaign timing.
Hard parameter constraints — boundary constraints (e.g. media coefficients must be non-negative) are enforced directly in the posterior, rather than post-hoc.
Full uncertainty propagation — every downstream output (fitted values, decomposition, budget allocation) carries the full posterior uncertainty, not just a point estimate ± standard error.
Principled model comparison — leave-one-out cross-validation (LOO-CV) via Pareto-smoothed importance sampling provides predictive model comparison without refitting.
Model classes
DSAMbayes supports three model classes, all sharing the same post-fit interface:
BLM (Bayesian Linear Model)
The simplest class. One market, one response variable, one set of predictors. Equivalent to lm() but with Bayesian estimation.
Use when: single-market modelling with sufficient data (typically 100+ weeks).
Hierarchical
Panel data with multiple groups (e.g. markets, regions, brands). Random effects allow each group to deviate from the population average while sharing information across groups (partial pooling).
kpi ~ population_terms + (varying_terms | group)
Use when: multi-market data where you want to borrow strength across markets while allowing market-specific effects.
Pooled
Single-market data where media variables have a nested structure (e.g. campaign > channel > platform). Coefficients are pooled across labelled dimensions.
Decide — optimise_budget() translates estimates into budget allocation recommendations.
Key concepts for practitioners
Priors
A prior distribution encodes what you believe about a parameter before seeing the data. In DSAMbayes:
Default priors are normal(0, 5) — weakly informative, centred at zero.
Informative priors can be set when domain knowledge justifies it (e.g. price_index ~ normal(-0.2, 0.1) if you know price has a small negative effect).
Priors are specified using formula notation: set_prior(model, m_tv ~ normal(0, 10)).
Boundaries
Hard constraints on parameter values. The posterior density is zero outside the boundary:
set_boundary(model, m_tv > 0) forces the TV coefficient to be non-negative.
Default boundaries are unconstrained (-Inf, Inf).
MCMC vs MAP
MCMC (fit()) draws samples from the full posterior distribution. Slower but gives complete uncertainty quantification. Use for final reporting.
MAP (optimise()) finds the single most probable parameter vector. Much faster but gives only a point estimate. Use for rapid iteration during model development.
Response scale
Models can operate on the original KPI scale (identity response) or the log scale:
Identity:kpi ~ ... — coefficients represent unit changes in KPI.
Log-response models require careful back-transformation to the KPI scale. DSAMbayes handles this automatically via fitted_kpi(). See Response Scale Semantics.
Diagnostics
After fitting, DSAMbayes runs a battery of diagnostic checks:
Boundary monitoring — share of draws hitting constraints.
Each check produces a pass, warn, or fail status. See Diagnostics Gates.
The YAML runner
For reproducible, configuration-driven runs, DSAMbayes provides a CLI runner:
Rscript scripts/dsambayes.R run --config config/my_model.yaml
The runner reads a YAML file specifying the data, formula, priors, boundaries, fit settings, diagnostics policy, and optional budget optimisation. It writes structured artefacts (CSVs, plots, model objects) to a timestamped directory under results/. See CLI Usage and Config Schema.
This walkthrough uses the synthetic dataset shipped at data/synthetic_dsam_example_wide_data.csv. It contains weekly observations for a single market with columns for:
First-time Stan compilation takes 1–3 minutes. Subsequent runs use a cached binary. With 2 chains on synthetic data, sampling typically completes in under 2 minutes.
Step 5: Sampler diagnostics
chain_diagnostics(fitted_model)
Metric
Good
Concern
Max Rhat
< 1.01
> 1.05 means chains have not converged
Min ESS (bulk)
> 400
< 200 means too few effective samples
Divergences
0
Any non-zero count warrants investigation
Step 6: Extract the posterior
post<-get_posterior(fitted_model)
post is a tibble with one row per draw containing coef (named coefficient list), yhat (fitted values), noise_sd, r2, rmse, and smape.
For a well-specified MMM on weekly data, in-sample R² above 0.85 is typical.
Step 8: Response decomposition
decomp_tbl<-decomp(fitted_model)head(decomp_tbl)
Shows each term’s contribution (coefficient × design-matrix column) to the predicted KPI at each time point — the foundation for media contribution and ROI reporting.
Step 9: MAP for rapid iteration
During development, use MAP for fast point estimates:
Understanding of random-effects / mixed-model concepts.
Dataset
This walkthrough uses data/synthetic_dsam_example_hierarchical_data.csv — a panel dataset with weekly observations across multiple markets. Key columns:
Response:kpi_value — weekly KPI per market.
Group:market — market identifier.
Media:m_tv, m_search, m_social — media exposure variables.
Controls:trend, seasonality, brand_metric.
Date:date — weekly date index.
library(DSAMbayes)panel_df<-read.csv("data/synthetic_dsam_example_hierarchical_data.csv")table(panel_df$market)# Check group counts
Step 1: Construct the hierarchical model
The (term | group) syntax tells DSAMbayes to fit random effects. Terms inside the parentheses get group-specific deviations from the population mean:
Boundaries apply to the population-level coefficients.
Step 3: (Optional) Add CRE / Mundlak correction
If you suspect that group-level spending patterns are correlated with unobserved market characteristics (e.g. high-spend markets also have higher baseline demand), CRE controls for this:
This adds cre_mean_m_tv, cre_mean_m_search, cre_mean_m_social as fixed effects — the group-level means of each media variable. The within-group coefficients then represent purely temporal variation, controlling for between-group confounding.
Hierarchical models are slower than BLM — expect 10–30 minutes depending on group count and data size. First-time Stan compilation of the hierarchical template adds 2–3 minutes.
Step 5: Check diagnostics
chain_diagnostics(fitted_model)
Pay special attention to Rhat and ESS for sd_* parameters (group-level standard deviations), which are often harder to estimate than population coefficients.
Step 6: Extract the posterior
post<-get_posterior(fitted_model)
For hierarchical models, coefficient draws from get_posterior() return vectors (one value per group) rather than scalars. The population-level (fixed-effect) estimates are averaged across groups.
Step 7: Group-level results
Fitted values and decomposition are returned per group:
# Fitted values — one row per observation, grouped by marketfit_tbl<-fitted(fitted_model)head(fit_tbl)# Decomposition — per-group predictor contributionsdecomp_tbl<-decomp(fitted_model)
Step 8: Budget optimisation (population level)
Budget optimisation uses population-level (fixed-effect) beta draws, not group-specific totals:
# See Budget Optimisation docs for full scenario specificationresult<-optimise_budget(fitted_model,scenario=my_scenario)
Key differences from BLM
Aspect
BLM
Hierarchical
Data structure
Single market
Panel (multiple groups)
Coefficient draws
Scalars
Vectors (one per group)
Fit time
2–5 min
10–30 min
Decomposition
Direct
May fail gracefully for `
Forest/prior-posterior plots
Direct
Group-averaged population estimates
Stan template
bayes_lm_updater_revised.stan
general_hierarchical.stan (templated per group count)
Common pitfalls
Pitfall
Symptom
Fix
Too few groups
Weak partial pooling; group SDs poorly estimated
Need 4+ groups for meaningful hierarchical structure
Run from YAML — reproducible hierarchical runs via the runner
Quickstart (YAML Runner)
Goal
Complete one reproducible DSAMbayes runner execution from validation to artefact inspection, then load the fitted model in R to explore the results interactively.
1–3 minutes on most machines. Subsequent runs use a cached binary and start sampling immediately. If compilation seems stuck, check your C++ toolchain — see Install and Setup.
Do I need to set R_LIBS_USER every time?
Yes, unless you add it to your shell profile (.bashrc, .zshrc, or equivalent). The repo-local .Rlib path keeps DSAMbayes and its dependencies isolated from your system R library.
Can I use renv instead of .Rlib?
Yes. The repository includes a renv.lock file. Run renv::restore() to install exact dependency versions. See the renv section in Install and Setup.
Modelling
How many weeks of data do I need?
There is no hard minimum, but as a rough guide:
BLM: 100+ weeks for a model with 10–15 predictors. Below 80 weeks, most media effects will be prior-driven.
Hierarchical: 80+ weeks per group, ideally with 4+ groups for meaningful partial pooling.
More data is always better. Short series with many predictors will lean heavily on priors.
Should I use identity or log response?
Identity (kpi ~ ...) when the KPI is naturally additive and variance is roughly constant across levels. Coefficients represent absolute unit changes.
Log (log(kpi) ~ ...) when the KPI is strictly positive, variance scales with level, or you want multiplicative (percentage) effects. Common for revenue and sales.
If unsure, fit both and compare diagnostics.
How many MCMC iterations do I need?
The defaults (iter = 2000, warmup = 1000, chains = 4) are a reasonable starting point. Check diagnostics after fitting:
Rhat < 1.01 and ESS > 400 → iterations are sufficient.
Rhat > 1.05 or ESS < 200 → increase iter and warmup (e.g. double both).
For rapid iteration during development, use MAP estimation (optimise()) instead of MCMC.
When should I set boundaries on media coefficients?
Set m_channel > 0 when you are confident that additional media exposure cannot decrease the KPI. This is the most common boundary specification in MMM. Do not set boundaries on control variables (trend, seasonality, price) unless you have a clear structural reason — see the Minimal-Prior Policy.
When should I use CRE (Mundlak)?
Use CRE when fitting a hierarchical model where:
Time-varying regressors (e.g. media spend) have group-level means correlated with unobserved group heterogeneity.
You want to separate within-group (temporal) effects from between-group (cross-sectional) effects.
CRE adds group-mean terms to the population formula. See CRE / Mundlak.
What does scale = TRUE do?
It standardises the response and predictors (centre and divide by SD) before passing them to Stan. This improves sampler efficiency by putting all coefficients on a comparable scale. Post-fit, get_posterior() back-transforms coefficients to the original data scale automatically. Leave it on (the default) unless you have a specific reason to disable it.
Runner and outputs
How long does a typical run take?
Model type
Fit method
Data size
Typical time
BLM
MCMC (2 chains, 2000 iter)
150 weeks
2–5 minutes
BLM
MAP (10 starts)
150 weeks
10–30 seconds
Hierarchical
MCMC (4 chains, 2000 iter)
150 weeks × 5 groups
10–30 minutes
Pooled
MCMC (4 chains, 2000 iter)
150 weeks
5–15 minutes
First-time Stan compilation adds 1–3 minutes.
What is the difference between validate and run?
validate checks config structure, data paths, formula validity, and cross-field constraints — without compiling or fitting Stan models. Use it as a pre-run gate.
run does everything validate does, then compiles, fits, runs diagnostics, and writes artefacts.
Always validate before run when you change config or data.
Where do outputs go?
By default, under results/<timestamp>_<model_name>/ with numbered stage folders. See Output Artefacts for the full contract.
How do I compare two model runs?
Use compare_runs() in R or compare 50_model_selection/loo_summary.csv files manually. See Compare Runs.
Diagnostics
What does “Pareto-k > 0.7” mean?
It means the PSIS-LOO approximation is unreliable for that observation — the observation is highly influential. A few amber (0.5–0.7) points are normal. Red (> 0.7) points warrant investigation. See Model Selection Plots.
My diagnostics say “warn” — should I worry?
It depends on the policy mode:
explore — warnings are expected during development. Continue iterating.
publish — review warnings before sharing results. Most warns are acceptable if you understand the cause.
It generates feasible spend allocations within channel bounds, evaluates each against the posterior, and selects the allocation that maximises the chosen objective (KPI uplift or profit). It is a Monte Carlo search, not an analytical optimiser. See Budget Optimisation.
Can I use budget optimisation with MAP-fitted models?
Yes, but the results will be based on a single point estimate rather than the full posterior distribution. Uncertainty intervals will not be meaningful.