| name | PyMC-Marketing MMM |
| description | Expert on PyMC-Marketing's Marketing Mix Model (MMM) framework including adstock transformations, saturation functions, hierarchical models, and GAM components. Use for MMM modeling, prior configuration, or pymc-marketing API questions. |
PyMC-Marketing GAM Options and Advanced Model Architectures
Overview
PyMC-Marketing extends beyond traditional Marketing Mix Modeling to support custom Bayesian Generalized Additive Models (GAMs) with flexible architectures for complex probabilistic inference. This skill covers advanced modeling patterns, multidimensional hierarchical structures, and custom model components.
MMM Import
Use from pymc_marketing.mmm.multidimensional import MMM. This handles both single time series (dims=None) and panel data with dims=(<DIM>,).
CRITICAL: The dims value MUST match the exact column name from your dataframe. Inspect the data columns first, then use the actual column name. Do NOT assume a column name — always verify it exists in the data.
WARNING: from pymc_marketing.mmm import MMM is DEPRECATED and will break save/load. Always use from pymc_marketing.mmm.multidimensional import MMM.
CRITICAL: Channel-Specific Parameters vs Dimensional Hierarchy
These are TWO DIFFERENT things - don't confuse them!
| Concept | What It Controls | Example |
|---|
dims parameter | Hierarchical structure across data dimensions | Different baseline per region, pooled learning across regions |
| Channel-specific parameters | Per-channel adstock (alpha) and saturation (lambda) | TV has slower decay than Digital |
WRONG: Single alpha/lambda shared across ALL channels
mmm = MMM(
channel_columns=["tv", "digital", "radio"],
dims=<EXTRA_DIMS>,
adstock=GeometricAdstock(l_max=8),
saturation=LogisticSaturation(),
)
CORRECT: PyMC-Marketing gives each channel its own parameters by default
When you specify channel_columns=["tv", "digital", "radio"], PyMC-Marketing automatically creates:
alpha[tv], alpha[digital], alpha[radio] (3 separate adstock decay rates)
lam[tv], lam[digital], lam[radio] (3 separate saturation parameters)
beta_channel[tv], beta_channel[digital], beta_channel[radio] (3 separate effect sizes)
The dims parameter adds ADDITIONAL hierarchy on top of this. For example with dims=<EXTRA_DIMS>:
alpha[tv, dim_val_a], alpha[tv, dim_val_b], alpha[digital, dim_val_a], etc. (per channel AND per extra dimension)
Key insight: If you only see a SINGLE alpha and SINGLE lam in your trace plots (not arrays), something is wrong with your model configuration!
After fitting, ALWAYS verify you have the right parameter shapes:
print(mmm.fit_result['adstock_alpha'].dims)
print(mmm.fit_result['saturation_lam'].dims)
print(mmm.fit_result['saturation_beta'].dims)
print(mmm.fit_result['adstock_alpha'].shape)
⛔⛔⛔ CRITICAL: You MUST Configure Priors with dims - Default Does NOT Work!
❌ WRONG - Creates USELESS models (parameters same for all dimension levels):
mmm = MMM(
dims=<EXTRA_DIMS>,
adstock=GeometricAdstock(l_max=12),
saturation=LogisticSaturation(),
)
The default GeometricAdstock(l_max=12) without priors= does NOT create dimension-specific parameters!
✅ CORRECT - Configure priors with dims:
from pymc_extras.prior import Prior
adstock = GeometricAdstock(
priors={"alpha": Prior("Beta", alpha=<ALPHA>, beta=<BETA>, dims=("channel", <DIM>))},
l_max=12
)
saturation = LogisticSaturation(
priors={
"lam": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims=("channel", <DIM>)),
"beta": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims=("channel", <DIM>)),
}
)
mmm = MMM(dims=<EXTRA_DIMS>, adstock=adstock, saturation=saturation)
Parameter Pooling Strategies for Multidimensional MMM
When using MMM with dimensions like dims=<EXTRA_DIMS>, you MUST configure how parameters vary across dimensions. There are three strategies:
Strategy 1: Fully Pooled (Shared across all dimension levels)
Same parameter for all dimension levels - one value per channel, shared everywhere.
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
adstock = GeometricAdstock(
priors={"alpha": Prior("Beta", alpha=<VALUE>, beta=<VALUE>, dims=("channel",))},
l_max=8
)
saturation = LogisticSaturation(
priors={
"lam": Prior("Gamma", mu=<VALUE>, sigma=<VALUE>, dims=("channel",)),
"beta": Prior("Gamma", mu=<VALUE>, sigma=<VALUE>, dims=("channel",)),
}
)
mmm = MMM(
date_column="date",
target_column="sales",
channel_columns=["tv", "radio", "digital"],
dims=<EXTRA_DIMS>,
adstock=adstock,
saturation=saturation,
)
Use when:
- Limited data per dimension level
- You believe channel effects are truly the same across all dimension levels
- Starting simple
Result: 3 alpha values (one per channel), shared across all dimension levels.
Strategy 2: Unpooled (Independent per dimension-channel)
Separate parameter for every dimension-channel combination - no information sharing.
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
adstock = GeometricAdstock(
priors={"alpha": Prior("Beta", alpha=<ALPHA>, beta=<BETA>, dims=("channel", <DIM>))},
l_max=8
)
saturation = LogisticSaturation(
priors={
"lam": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims=("channel", <DIM>)),
"beta": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims=("channel", <DIM>)),
}
)
mmm = MMM(
date_column="date",
target_column="sales",
channel_columns=["tv", "radio", "digital"],
dims=<EXTRA_DIMS>,
adstock=adstock,
saturation=saturation,
)
Use when:
- Lots of data per dimension level (50+ observations per level recommended)
- You believe effects truly vary by market
- Markets are very different (e.g., different countries with different media landscapes)
Result: 3 channels × N dimension levels = 3N alpha values, each estimated independently.
Strategy 3: Hierarchical / Partial Pooling (RECOMMENDED)
Dimension levels share information through channel-level hyperparameters, but still get dimension-specific estimates.
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
adstock = GeometricAdstock(
priors={
"alpha": Prior(
"Beta",
alpha=Prior("Gamma", mu=2, sigma=1, dims="channel"),
beta=Prior("Gamma", mu=5, sigma=2, dims="channel"),
dims=("channel", <DIM>),
)
},
l_max=8
)
saturation = LogisticSaturation(
priors={
"lam": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims="channel"),
"beta": Prior(
"Normal",
mu=Prior("Gamma", mu=0.25, sigma=0.10, dims="channel"),
sigma=Prior("Exponential", scale=0.10, dims="channel"),
dims=("channel", <DIM>),
centered=False,
),
}
)
mmm = MMM(
date_column="date",
target_column="sales",
channel_columns=["tv", "radio", "digital"],
dims=<EXTRA_DIMS>,
adstock=adstock,
saturation=saturation,
)
Use when:
- Moderate data per dimension level
- You want dimension levels to "borrow strength" from each other
- Markets are related but not identical (e.g., different US states)
Key insight: The hierarchical prior allows TV in geo_a to inform TV in geo_b (through shared hyperparameters), while TV never influences radio (independent channel effects).
Strategy 4: Mixed Pooling (Practical Default)
Mix different strategies for different parameters based on domain knowledge.
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
adstock = GeometricAdstock(
priors={"alpha": Prior("Beta", alpha=<ALPHA>, beta=<BETA>, dims=("channel", <DIM>))},
l_max=<LMAX>,
)
saturation = LogisticSaturation(
priors={
"lam": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims="channel"),
"beta": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims=("channel", <DIM>)),
}
)
mmm = MMM(
date_column="date",
target_column="sales",
channel_columns=["tv", "radio", "digital"],
dims=<EXTRA_DIMS>,
adstock=adstock,
saturation=saturation,
)
This is often the most practical starting point:
- Adstock alpha varies by dimension (different media consumption patterns)
- Lambda pooled (channel response shape similar across markets)
- Beta varies by dimension (different market sizes)
Best Practice: Start Simple, Add Complexity
From the PyMC-Marketing documentation:
"The choice is primarily driven by computational considerations. Partial pooling is generally a more reasonable assumption but it can make the model slower to estimate, more complicated to debug, and more difficult to reason about."
Recommended progression:
- Start with fully pooled or mixed pooling (Strategy 1 or 4)
- Fit model, check convergence, validate results
- If you have enough data and see evidence of dimension-level variation, try unpooled (Strategy 2)
- Only use hierarchical (Strategy 3) if you need information sharing AND have convergence issues with unpooled
Verifying Parameter Shapes After Fitting
ALWAYS check that you got the dimensionality you expected:
print("Adstock alpha dims:", mmm.fit_result['adstock_alpha'].dims)
print("Adstock alpha shape:", mmm.fit_result['adstock_alpha'].shape)
Key Concept: MMM as a GAM Framework
PyMC-Marketing is not only a framework for marketing optimization but also a general-purpose engine for building interpretable Bayesian GAMs. The architecture enables seamless transitions from standard MMM to fully specified graphical models capturing richer causal relationships.
Core Capabilities
1. Flexible Architecture Progression
The framework supports progression from simple to complex models:
-
Simple Linear Regression
- Automatic scaling and preprocessing
- Basic channel effects
-
Linear MMM with Transformations
- Adstock transformations (carryover effects)
- Saturation transformations (diminishing returns)
-
Multidimensional Hierarchical Models
- Country/region/product dimensions
- Dimension-specific parameters
- Automatic broadcasting across dimensions
-
Custom Bayesian GAMs
- Temporal components (trends, seasonality)
- Custom additive effects
- Fully specified graphical models
2. Composable Components
All components can be mixed and matched:
- Adstock transformations
- Saturation functions
- Temporal effects
- Hierarchical priors
- Multiple dimensions
Model Components in Detail
Adstock Transformations
Purpose: Model how marketing impact decays over time (carryover effects)