| name | Informative Priors for MMM |
| description | Expert guide on calculating and setting informative priors for PyMC-Marketing MMM models based on data characteristics and domain knowledge. Use when configuring priors for intercept, channel effects, or adstock parameters. |
Guide: Calculating Informative Priors for MMM
This guide describes how to calculate tighter, more informative priors for PyMC-Marketing MMM based on data characteristics.
Important: The default wide priors (intercept: Normal(mu=0, sigma=2)) often lead to better-calibrated posteriors. Only use informative priors when you have strong domain knowledge or specific requirements. Overly tight priors can lead to overconfident (underdispersed) posteriors.
When to Use Informative Priors
- You have domain knowledge about expected ROAS ranges, decay rates, or effect sizes
- You have results from previous studies or models to inform your beliefs
- Business constraints require certain parameter ranges
- You're doing sensitivity analysis to understand prior impact
Step 1: Calculate Scaled Statistics
PyMC-Marketing uses MaxAbsScaler for target and channels (divides by max absolute value).
y_scaled_max = 1.0
y_scaled_mean = y.mean() / y.max()
y_scaled_min = y.min() / y.max()
print(f"Scaled y statistics:")
print(f" min: {y_scaled_min:.3f}")
print(f" mean: {y_scaled_mean:.3f}")
print(f" max: {y_scaled_max:.3f}")
Step 2: Reason About Intercept Prior
The intercept represents predicted y when all channels = 0 (no marketing spend).
intercept_mu = y_scaled_mean * 0.8
intercept_sigma = 0.2
print(f"Intercept prior: Normal(mu={intercept_mu:.2f}, sigma={intercept_sigma})")
Step 3: Reason About Channel Effect Priors
saturation_beta represents the effect size per channel in scaled space.
n_channels = len(channel_columns)
expected_total_channel_contribution = 1 - intercept_mu
expected_per_channel = expected_total_channel_contribution / n_channels
beta_sigma = expected_per_channel * 2.5
print(f"Channel beta prior: HalfNormal(sigma={beta_sigma:.2f})")
print(f" Expected per-channel contribution: ~{expected_per_channel:.2f}")
Step 4: Set model_config
from pymc_extras.prior import Prior
model_config = {
"intercept": Prior("Normal", mu=intercept_mu, sigma=intercept_sigma),
"saturation_beta": Prior("HalfNormal", sigma=beta_sigma),
"adstock_alpha": Prior("Beta", alpha=2, beta=3),
}
mmm = MMM(
date_column=date_column,
channel_columns=channel_columns,
adstock=GeometricAdstock(l_max=l_max),
saturation=LogisticSaturation(),
model_config=model_config,
)
Step 5: Validate with Prior Predictive Checks
After setting informative priors, always validate:
mmm.build_model(X=X, y=y)
mmm.sample_prior_predictive(X=X, samples=1000, random_seed=None
from mmm_lib import check_prior_predictive_coverage
coverage = check_prior_predictive_coverage(mmm, y, hdi_prob=0.94)
Adjusting Based on Prior Predictive
| Observation | Action |
|---|
| HDI too wide (>10x observed range) | Decrease sigma values |
| HDI too narrow (<2x observed range) | Increase sigma values |
| Too many negative predictions (>10%) | Increase intercept mu or use HalfNormal |
| Coverage too low (<50%) | Loosen priors (increase sigmas) |
Example: Full Workflow
import pandas as pd
import numpy as np
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
from pymc_extras.prior import Prior
df = pd.read_parquet('cleaned_data.parquet')
y = df[target_column]
channel_columns = ['tv', 'digital', 'radio']
y_scaled_mean = y.mean() / y.max()
print(f"Scaled y mean: {y_scaled_mean:.3f}")
intercept_mu = y_scaled_mean * 0.75
intercept_sigma = 0.3
n_channels = len(channel_columns)
expected_per_channel = (1 - intercept_mu) / n_channels
beta_sigma = expected_per_channel * 2
model_config = {
"intercept": Prior("Normal", mu=intercept_mu, sigma=intercept_sigma),
"saturation_beta": Prior("HalfNormal", sigma=beta_sigma),
"adstock_alpha": Prior("Beta", alpha=2, beta=3),
}
print(f"Model config:")
print(f" intercept: Normal(mu={intercept_mu:.2f}, sigma={intercept_sigma})")
print(f" saturation_beta: HalfNormal(sigma={beta_sigma:.2f})")
print(f" adstock_alpha: Beta(alpha=2, beta=3)")
mmm = MMM(
date_column='date',
channel_columns=channel_columns,
adstock=GeometricAdstock(l_max=8),
saturation=LogisticSaturation(),
model_config=model_config,
)
X = df[['date'] + channel_columns]
mmm.build_model(X=X, y=y)
mmm.sample_prior_predictive(X=X, samples=1000, random_seed=None
Warning: Posterior Calibration
Informative priors that are too tight can cause underdispersed posteriors (overconfident uncertainty estimates). Signs of this:
- 94% HDI coverage << 94% (e.g., 50-60%)
- Posterior intervals that don't contain true values
If you observe this, loosen your priors by increasing sigma values, or revert to defaults.
Additional Prior Types
Beta Distribution for Bounded Parameters
For parameters that must be between 0 and 1 (like adstock decay):
model_config = {
"adstock_alpha": Prior("Beta", alpha=2, beta=3),
}
HalfNormal for Positive Parameters
For strictly positive parameters:
model_config = {
"saturation_beta": Prior("HalfNormal", sigma=0.5),
"sigma": Prior("HalfNormal", sigma=1.0),
}
LogNormal for Positive Parameters with Heavy Tails
When you expect occasional large values:
model_config = {
"saturation_lam": Prior("LogNormal", mu=0, sigma=1),
}
Domain Knowledge Integration
From Historical Data
If you have previous MMM results:
previous_intercept_mean = 0.65
previous_intercept_std = 0.1
model_config = {
"intercept": Prior("Normal",
mu=previous_intercept_mean,
sigma=previous_intercept_std * 2),
}
From Industry Benchmarks
If you have industry benchmarks for ROAS or effect sizes:
model_config = {
"saturation_beta": Prior("TruncatedNormal",
mu=0.1,
sigma=0.05,
lower=0),
}
From Business Constraints
If business logic requires certain constraints:
model_config = {
"intercept": Prior("TruncatedNormal",
mu=0.7,
sigma=0.1,
lower=0.5),
}
When to Use This Skill
- Setting up initial priors for a new MMM
- Adjusting priors based on prior predictive checks
- Incorporating domain knowledge into the model
- Debugging overly wide or narrow posterior distributions
- Sensitivity analysis to understand prior impact on results