A web dashboard built in Dash for Bayesian Media Mix Modeling (MMM). It fits a Bayesian MMM on one sample territory from the Google Meridian simulated dataset, then explores channel effects, diagnostics, response curves, budget trade-offs, and optimisation in a clean multi-page UI.
![]() |
![]() |
| Overview dashboard | Budget optimiser view |
The app uses PyMC-Marketing to estimate a multidimensional MMM with geometric adstock and logistic saturation on paid media spend, plus controls & seasonality. Inference uses NUTS and posteriors are summarised with ArviZ.
Data: On first run it downloads Google Meridian’s simulated geo_all_channels.csv and caches it under data/. This is synthetic multi-geo weekly data; the app automatically selects the largest territory by revenue (Geo36 in the bundled file) and models that one territory only.
The meridian data has generic names for channels ("Channel1" etc), so these have been mapped to actual channel names like "Video", "Social" etc. for demonstration purposes.
Caching: Fitted inference data is written to data/mmm_idata.nc with a fingerprint file so later launches reload the posterior instead of resampling unless you refit or invalidate the cache.
| Route | Purpose |
|---|---|
| Overview | In-sample KPIs, actual vs. predicted revenue, revenue decomposition, MCMC diagnostics, residual checks and a fast holdout check |
| Contributions | Channel contribution to revenue (posterior uncertainty) |
| Response curves | Marginal response / saturation curves by channel |
| Optimiser | Steady-state budget scenarios with optional channel min/max constraints |
The Options panel in the header lets you adjust sampler settings like requested draws per chain, requested tuning steps per chain, target accept, and trigger a refit. Successful runs persist settings to data/mmm_sampler_config.json.
├── app.py # Dash entrypoint and callback registration
├── callbacks/ # Dash callbacks by feature area
├── components/ # Shared layout helpers, ids, KPI cards, chart theme
├── content/ # Static explanatory copy, including methodology notes
├── dashboard/ # App-level data loading, shared runtime state, and theme settings
├── data/ # Meridian sample data, sampler config, cached posterior
├── figures/ # Plotly figure builders
├── layouts/ # Page and shell layout builders
├── model/ # MMM fitting, posterior summaries, diagnostics, optimiser math
├── pages/ # Dash Pages registration and page entrypoints
├── assets/ # Dash-served CSS and fonts
├── img/ # README screenshots
├── pyproject.toml # Python dependencies and project metadata
└── uv.lock # Locked dependency set for uv
The app is intentionally split by responsibility: model/mmm.py handles fitting and metric calculation, figures/ turns model outputs into Plotly charts, layouts/ builds Dash/Mantine UI, pages/ registers routes with Dash Pages, and callbacks/ wires user interactions.
The Overview page has two different kinds of diagnostics:
- MCMC diagnostics report sampler health and cache-related metadata. Requested sampler settings are shown separately from cached posterior draws per chain.
- Fast holdout check is a lightweight temporal stress test (not a full Bayesian MMM backtest). It fits a ridge surrogate on prior weeks and forecasts short holdout windows then compares the surrogate against simple naive benchmarks.
- Adding more user options to adjust MMM/sampler settings
- Multi-geo / hierarchical geo modelling capability
- Full rolling-origin Bayesian MMM refits for stronger validation
- UI: Dash 4.x, Dash Mantine Components
- Model:
pymc-marketing, nutpie & JAX for sampling throughput - Python: 3.13+ recommended
- Package manager: uv (used for dependency management)
uv sync
uv run app.pyOpen http://127.0.0.1:8050. The first model fit can take a couple of minutes while NUTS runs, subsequent starts should be faster.
This repository is a demo built on Google Meridian simulated data. Don't treat its outputs as business decisions without validating on your own data!
MIT. See LICENSE.

