Media Mix Modeling with PyMC-Marketing. Use when building MMMs, specifying adstock/saturation transformations, setting priors, fitting multidimensional (geo-level) models, computing channel contributions, ROAS, running budget optimization, calibrating with lift tests, or performing sensitivity analysis. Covers the MMM class, GeometricAdstock, LogisticSaturation, BudgetOptimizerWrapper, and ArviZ diagnostics for marketing models.
Media Mix Modeling with PyMC-Marketing. Use when building MMMs, specifying adstock/saturation transformations, setting priors, fitting multidimensional (geo-level) models, computing channel contributions, ROAS, running budget optimization, calibrating with lift tests, or performing sensitivity analysis. Covers the MMM class, GeometricAdstock, LogisticSaturation, BudgetOptimizerWrapper, and ArviZ diagnostics for marketing models.
Media Mix Modeling with PyMC-Marketing
Bayesian Media Mix Modeling workflow using the PyMC-Marketing MMM class.
PyMC prerequisite: This skill assumes familiarity with PyMC's core modeling API (coords/dims, priors, MCMC diagnostics, HSGP). For foundational patterns, see the pymc-modeling skill.
LLMs understand Bayesian inference, MCMC, and hierarchical models in general. But getting from those concepts to a correctly specified, well-diagnosed, and actionable PyMC-Marketing MMM requires domain-specific knowledge: which Prior to use for saturation beta informed by spend shares, how dims=("geo",) activates multidimensional partial pooling, why the final model must be fit on the full dataset (time-slice CV is only for stability assessment), how add_lift_test_measurements() resolves causal identification, and how BudgetOptimizerWrapper translates posterior uncertainty into optimal allocations.
This skill encodes those patterns. Without it, an LLM might hold out test data for the final fit (wrong -- use all data, validate with time-slice CV), use flat priors on saturation parameters (causes divergences), skip add_original_scale_contribution_variable (then contributions are on scaled space), or call BudgetOptimizer directly instead of BudgetOptimizerWrapper (misses geo-level allocation).
Quick Start
import arviz as az
import numpy as np
import pandas as pd
from pymc_extras.prior import Prior
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_marketing.mmm.mmm import MMM
# Load data
data_df = pd.read_csv("data.csv", parse_dates=["date"])
X = data_df.drop(columns=["y"])
y = data_df["y"]
# Specify model
mmm = MMM(
date_column="date",
channel_columns=["tv", "radio", "social"],
target_column="y",
adstock=GeometricAdstock(l_max=6),
saturation=LogisticSaturation(),
yearly_seasonality=5,
)
# Build and fit on FULL dataset
mmm.build_model(X, y)
mmm.fit(X=X, y=y, nuts_sampler="nutpie", target_accept=0.9, random_seed=42)
mmm.sample_posterior_predictive(X=X, random_seed=42)
See references/model_specification.md for full constructor reference, hierarchical prior patterns (partial/full/no pooling), and prior predictive checks.
Data Preparation
Data must have a date column, one or more channel (spend) columns, and a target column. For multidimensional models, include a geo/region column and provide data in long format.
Critical: The final model is always fit on the full dataset. Train/test splits are only used for model stability assessment via time-slice cross-validation.
See references/media_deep_dive.md for ROAS computation, incremental analysis, saturation/adstock curves, sensitivity analysis, and contribution share plots.
Time-Varying Parameters
The MMM class supports GP-based time-varying intercept and time-varying media multiplier via Hilbert Space Gaussian Processes (HSGP):
Use TVP when residuals show irregular, non-repeating temporal variation not explained by seasonality, trend, or controls. The GP is primarily useful for in-sample decomposition; it reverts to the prior mean out of sample.
When the MMM class cannot express your model structure (non-standard hierarchies, spline baselines, custom likelihoods), build a custom model by combining PyMC-Marketing components with plain PyMC:
import pymc as pm
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
with pm.Model(coords=coords) as custom_mmm:
channel_data_ = pm.Data("channel_data", channel_scaled, dims=("date", "geo", "channel"))
adstocked = adstock.apply(channel_data_, core_dim="date")
channel_contribution = saturation.apply(adstocked, core_dim="date")
# ... add intercept, controls, seasonality, likelihood
Custom models gain full flexibility but lose built-in scaling, plotting, budget optimization, lift test integration, and save/load.
See references/custom_model.md for standalone component usage, hierarchical prior patterns, spline-based intercepts, custom events, and a complete geo-hierarchical example.
Lift tests resolve causal identification when channels are correlated:
# After build_model, before fit
mmm.build_model(X, y)
mmm.add_lift_test_measurements(df_lift_test)
mmm.fit(X=X, y=y, nuts_sampler="nutpie", ...)
The lift test DataFrame requires columns: channel, x, delta_x, delta_y, sigma (plus geo for geo-level).
When time_varying_media=True, include date so each lift measurement maps to the correct media_temporal_latent_multiplier time coordinate.
See references/liftest_calibration.md for data format, calibrated vs uncalibrated comparison, geo-level patterns, and sigma estimation.
Saving, Loading, and YAML Specification
# Save / load fitted model
mmm.save("mmm_model.nc", engine="h5netcdf")
loaded_mmm = MMM.load("mmm_model.nc")
# Build model from a YAML specificationfrom pymc_marketing.mmm.builders.yaml import build_mmm_from_yaml
mmm = build_mmm_from_yaml("model_spec.yaml", X=X, y=y)
The YAML builder (build_mmm_from_yaml) enables declarative model specification -- useful for reproducible experiments, TimeSliceCrossValidator integration (via yaml_path), and MLflow tracking.
Incremental Analysis and Summary
mmm.incrementality
Counterfactual analysis with proper adstock carryover handling. Preferred approach for ROAS/CAC computation: