- 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**
```python
# This defeats the purpose of MMM!
mmm = MMM(
channel_columns=["tv", "digital", "radio"],
dims=<EXTRA_DIMS>,
adstock=GeometricAdstock(l_max=8), # Single alpha shared by all channels
saturation=LogisticSaturation(), # Single lambda shared by all channels
)
```
**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:**
```python
# Verify channel-specific parameters exist
# Parameter names: adstock_alpha, saturation_lam, saturation_beta
print(mmm.fit_result['adstock_alpha'].dims) # Should be ('chain', 'draw', 'channel')
print(mmm.fit_result['saturation_lam'].dims) # Should be ('chain', 'draw', 'channel')
print(mmm.fit_result['saturation_beta'].dims) # Should be ('chain', 'draw', 'channel')
# Check shapes - should have n_channels in the last dimension
print(mmm.fit_result['adstock_alpha'].shape) # e.g., (4, 2000, 3) for 4 chains, 2000 draws, 3 channels
# If using dims=<EXTRA_DIMS>, shapes should be (chain, draw, channel, *<EXTRA_DIMS>)
# NOT just (chain, draw) with a single scalar value!
```
## ⛔⛔⛔ CRITICAL: You MUST Configure Priors with dims - Default Does NOT Work!
**❌ WRONG - Creates USELESS models (parameters same for all dimension levels):**
```python
mmm = MMM(
dims=<EXTRA_DIMS>,
adstock=GeometricAdstock(l_max=12), # NO priors! All dim levels share same alpha!
saturation=LogisticSaturation(), # NO priors! All dim levels share same params!
)
```
**The default `GeometricAdstock(l_max=12)` without `priors=` does NOT create dimension-specific parameters!**
**✅ CORRECT - Configure priors with dims:**
```python
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.**
```python
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
# Fully pooled: dims="channel" only (no extra dimension)
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.**
```python
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
# Unpooled: dims includes both channel AND the extra dimension
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.**
```python
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
# Hierarchical: hyperparameters have dims="channel", final param has dims=("channel", <DIM>)
adstock = GeometricAdstock(
priors={
"alpha": Prior(
"Beta",
alpha=Prior("Gamma", mu=2, sigma=1, dims="channel"), # Shared across dimension levels
beta=Prior("Gamma", mu=5, sigma=2, dims="channel"), # Shared across dimension levels
dims=("channel", <DIM>), # But dimension-specific values
)
},
l_max=8
)
saturation = LogisticSaturation(
priors={
# Lambda: fully pooled (channel efficiency assumed similar across dimension levels)
"lam": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims="channel"),
# Beta: hierarchical (max impact varies by dimension but channels share structure)
"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, # Non-centered helps MCMC convergence
),
}
)
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.**
```python
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
# Adstock: unpooled (memory effects can vary significantly by market)
adstock = GeometricAdstock(
priors={"alpha": Prior("Beta", alpha=<ALPHA>, beta=<BETA>, dims=("channel", <DIM>))},
l_max=<LMAX>,
)
# Saturation: mixed
saturation = LogisticSaturation(
priors={
# Lambda (channel efficiency): pooled - assume similar efficiency across markets
"lam": Prior("Gamma", mu=<MU>, sigma=<SIGMA>, dims="channel"),
# Beta (max impact): unpooled - market size/potential varies
"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:**
1. Start with **fully pooled** or **mixed pooling** (Strategy 1 or 4)
2. Fit model, check convergence, validate results
3. If you have enough data and see evidence of dimension-level variation, try **unpooled** (Strategy 2)
4. 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:**
```python
# After fitting
print("Adstock alpha dims:", mmm.fit_result['adstock_alpha'].dims)
print("Adstock alpha shape:", mmm.fit_result['adstock_alpha'].shape)
# Expected for 3 channels, 5 dimension levels:
# Fully pooled: ('chain', 'draw', 'channel') → shape (N_CHAINS, TOTAL_DRAWS, 3)
# Unpooled: ('chain', 'draw', 'channel', <DIM>) → shape (N_CHAINS, TOTAL_DRAWS, 3, 5)
# Hierarchical: ('chain', 'draw', 'channel', <DIM>) → shape (N_CHAINS, TOTAL_DRAWS, 3, 5)
# If you see shape (N_CHAINS, TOTAL_DRAWS) with no channel/extra dimension, something is WRONG!
```
## 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:
1. **Simple Linear Regression**
- Automatic scaling and preprocessing
- Basic channel effects
2. **Linear MMM with Transformations**
- Adstock transformations (carryover effects)
- Saturation transformations (diminishing returns)
3. **Multidimensional Hierarchical Models**
- Country/region/product dimensions
- Dimension-specific parameters
- Automatic broadcasting across dimensions
4. **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)
Ver no GitHub