| 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)
Options:
-
GeometricAdstock (most common)
from pymc_marketing.mmm import GeometricAdstock
adstock = GeometricAdstock(l_max=<LMAX>)
l_max: Maximum lag (number of periods for decay)
- Models exponential decay of marketing effects
-
NoAdstock (instant impact, no carryover)
from pymc_marketing.mmm import NoAdstock
adstock = NoAdstock(l_max=1)
- Use when effects are immediate with no carryover
Multidimensional Configuration:
adstock = GeometricAdstock(l_max=6).set_dims_for_all_priors(
('country', 'region', 'product_type', 'channel')
)
Saturation Functions
Purpose: Model diminishing returns as marketing spend increases
Options:
- LogisticSaturation (most common)
from pymc_marketing.mmm import LogisticSaturation
saturation = LogisticSaturation()
- Models S-curve response to spending
- Captures diminishing returns
⚠️ Understanding LogisticSaturation Parameters
Formula: saturation_level = (1 - exp(-lam * x)) / (1 + exp(-lam * x))
Where:
x is scaled spend (in [0, 1] range after MaxAbsScaler)
lam (lambda) is the rate of saturation (NOT a spend threshold!)
beta scales the maximum effect
Parameter interpretation:
| Parameter | Higher value means... |
|---|
lam | Faster saturation - channel reaches diminishing returns sooner |
beta | Larger maximum effect - channel contributes more to target |
⚠️ CRITICAL: Lambda is NOT a spend level!
- WRONG interpretation: "Lambda of 45,200 means saturation at $45K spend"
- RIGHT interpretation: "Lambda is dimensionless; higher = faster saturation rate"
Computing actual saturation level at current spend:
lam_mean = mmm.fit_result['saturation_lam'].sel(channel=channel).mean()
scaled_spend = df[channel].mean() / df[channel].max()
- NoSaturation (linear response)
from pymc_marketing.mmm import NoSaturation
saturation = NoSaturation()
- Use when response is perfectly linear
With Hierarchical Priors:
from pymc_extras.prior import Prior, LogNormalPrior
saturation = NoSaturation(
priors={
"beta": LogNormalPrior(
mean=Prior("Normal", mu=1, sigma=2, dims="country"),
std=Prior("Normal", mu=1, sigma=2, dims="region")
)
}
)
Temporal Components
LinearTrend: Piecewise linear trends with learned changepoints
from pymc_marketing.mmm import LinearTrend
from pymc_marketing.mmm.additive_effect import LinearTrendEffect
trend = LinearTrend(
n_changepoints=<N_CHANGEPOINTS>,
include_intercept=<BOOL>,
)
trend_effect = LinearTrendEffect(trend=trend, prefix="trend")
mmm.mu_effects.append(trend_effect)
Mathematical expression: Trend(t) = k + sum(delta_j * I(t > s_j))
k: Base intercept (only if include_intercept=True)
delta_j ~ Laplace(0, b): Change in slope at changepoint j (sparse prior)
s_j: Evenly spaced changepoints from 0 to max(t)
I: Indicator function
Choosing n_changepoints: Use approximately one changepoint per year of data. The Laplace prior on deltas is sparse — unnecessary changepoints shrink toward zero — so slightly overestimating is safe. Underestimating risks missing real slope changes.
WeeklyFourier: Seasonal patterns using Fourier series
from pymc_marketing.mmm.fourier import WeeklyFourier
weekly = WeeklyFourier(n_order=<N_ORDER>)
Mathematical expression: Seasonality(t) = sum[a_n*cos(2*pi*n*t/P) + b_n*sin(2*pi*n*t/P)]
P: Period (e.g., weekly, yearly)
N: Number of Fourier terms (n_order)
- Higher
n_order captures more complex seasonal patterns
Model Building Patterns
Pattern 1: Basic Linear Model (No Transformations)
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import NoAdstock, NoSaturation
linear_model = MMM(
date_column="date_week",
channel_columns=["x1", "x2", "x3", "x4"],
adstock=NoAdstock(l_max=1),
saturation=NoSaturation()
)
linear_model.fit(X=X, y=y)
Use case: Baseline comparison, testing data pipeline
Pattern 2: Standard MMM with Transformations
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
mmm = MMM(
date_column="date_week",
channel_columns=["tv", "digital", "radio"],
adstock=GeometricAdstock(l_max=8),
saturation=LogisticSaturation()
)
mmm.fit(X=X, y=y)
Use case: Standard marketing mix modeling with carryover and saturation
Pattern 3: Multidimensional Hierarchical Model - FOR REGIONAL DATA
Use this pattern when you have panel data with repeated dates per dimension level (e.g., region, market, country).
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
hierarchical_mmm = MMM(
date_column="date_week",
target_column="y",
channel_columns=["tv", "digital", "radio"],
dims=<EXTRA_DIMS>,
adstock=GeometricAdstock(l_max=8),
saturation=LogisticSaturation(),
yearly_seasonality=2,
)
X = df.drop(columns=["y"])
y = df["y"]
hierarchical_mmm.fit(X=X, y=y, nuts_sampler="numpyro")
When to use: Data has a dimension column (e.g., region, market, country) with repeated dates per level.
Key insight: Use dims parameter for panel/multi-region data; omit it (or set to None) for single time series.
Automatic broadcasting: MMM handles broadcasting automatically, allowing parameters of different shapes without manual dimension management.
Pattern 4: GAM with Temporal Components
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation, LinearTrend
from pymc_marketing.mmm.additive_effect import LinearTrendEffect, FourierEffect
from pymc_marketing.mmm.fourier import WeeklyFourier
mmm = MMM(
date_column=<DATE_COL>,
target_column=<TARGET_COL>,
channel_columns=<CHANNEL_COLS>,
adstock=GeometricAdstock(l_max=<L_MAX>),
saturation=LogisticSaturation()
)
trend = LinearTrend(n_changepoints=<N_CHANGEPOINTS>)
weekly = WeeklyFourier(n_order=<N_ORDER>)
mmm.mu_effects.extend([
LinearTrendEffect(trend=trend, prefix="trend"),
FourierEffect(fourier=weekly, prefix="weekly")
])
Use case: Capture both marketing effects AND temporal patterns (trends, seasonality)
Pattern 5: Custom Hierarchical Priors - FOR REGIONAL DATA
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, NoSaturation
from pymc_extras.prior import Prior, LogNormalPrior
saturation = NoSaturation(
priors={
"beta": LogNormalPrior(
mean=Prior("Normal", mu=1, sigma=2, dims="country"),
std=Prior("Normal", mu=1, sigma=2, dims="region")
)
}
)
adstock = GeometricAdstock(l_max=6).set_dims_for_all_priors(
('country', 'region', 'product_type', 'channel')
)
mmm = MMM(
date_column="date_week",
target_column="y",
channel_columns=["tv", "digital", "radio"],
adstock=adstock,
saturation=saturation,
dims=("country", "region", "product_type")
)
Use case: Complex hierarchical structure where parameters vary across multiple dimensions (multi-region data)
Workflow: From Model Building to Inference
Step 1: Build the Model
Single time series (no dims):
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
mmm = MMM(
date_column="date_week",
channel_columns=["tv", "digital", "radio"],
adstock=GeometricAdstock(l_max=8),
saturation=LogisticSaturation()
)
mmm.build_model(X=X, y=y)
Panel / multi-region data (with dims):
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
mmm = MMM(
date_column="date_week",
target_column="y",
channel_columns=["tv", "digital", "radio"],
dims=<EXTRA_DIMS>,
adstock=GeometricAdstock(l_max=8),
saturation=LogisticSaturation()
)
X = df.drop(columns=["y"])
y = df["y"]
mmm.build_model(X=X, y=y)
Step 2: Visualize Model Structure (Optional)
mmm.model.to_graphviz()
Use case: Understand model structure, debug complex models
Step 3: Prior Predictive Checks
prior_pred = mmm.sample_prior_predictive(
X=X,
samples=1000,
random_seed=None
)
Use case: Validate that priors produce reasonable predictions before fitting
Step 4: Fit the Model
sample_kwargs = {
"tune": 800,
"draws": 200,
"chains": 2,
"target_accept": 0.84
}
mmm.fit(X=X, y=y, **sample_kwargs)
Step 5: Posterior Predictive Checks
posterior_pred = mmm.sample_posterior_predictive(
X_pred=X_test,
random_seed=None
)
Step 6: Original Scale Contribution Variables
build_mmm_model() automatically calls mmm.add_original_scale_contribution_variable(var=[...]) which registers *_original_scale pm.Deterministic variables in the model graph. After fitting, the trace will contain variables like channel_contribution_original_scale, intercept_contribution_original_scale, etc. These are required for mmm.plot.* and mmm.summary.* to return results in original (unscaled) units.
If calling mmm.build_model() directly instead of build_mmm_model(), you must also call:
mmm.add_original_scale_contribution_variable(
var=["channel_contribution", "intercept_contribution", "y"]
)
Data Requirements
Required Columns
data = pd.DataFrame({
"date_week": [...],
"y": [...],
"tv": [...],
"digital": [...],
"radio": [...]
})
With Dimensions
data = pd.DataFrame({
"date_week": [...],
"y": [...],
"tv": [...],
"digital": [...],
"radio": [...],
"country": [...],
"region": [...],
"product_type": [...]
})
Advanced Prior Specification
Using Custom Prior Classes
from pymc_extras.prior import Prior, LogNormalPrior, MaskedPrior
beta_prior = LogNormalPrior(
mean=Prior("Normal", mu=1, sigma=2, dims="country"),
std=Prior("Normal", mu=1, sigma=2, dims="region")
)
masked_prior = MaskedPrior(
prior=Prior("HalfNormal", sigma=1),
mask=[True, False, True, False]
)
Setting Dimensions for All Priors
adstock = GeometricAdstock(l_max=6).set_dims_for_all_priors(
('country', 'region', 'product_type', 'channel')
)
Key insight: This method broadcasts dimension structure across all parameters in the component.
Performance Considerations
Sampling Performance
Warning: "As the model grows in size (both in parameters and data), sampling can start to take longer."
Recommendations:
- Start with small models and add complexity incrementally
- Use fewer chains during development (e.g.,
chains=2)
- Reduce draws for exploration (e.g.,
draws=200)
- Use
target_accept=0.84 for better convergence with complex models
- Consider efficient sampling techniques for production models
Model Complexity Trade-offs
Simple models:
- Faster sampling
- Easier to diagnose
- Less flexible
Complex models:
- Slower sampling
- More parameters to diagnose
- Greater flexibility
- Risk of overfitting
Recommended workflow: Start simple, add complexity only when justified by data.
Common Use Cases
Use Case 1: Standard MMM
Scenario: Basic marketing mix modeling with carryover and saturation
Solution: Pattern 2 (Standard MMM with Transformations)
Use Case 2: Multi-Market MMM
Scenario: Model marketing effects across multiple countries/regions
Solution: Pattern 3 (Multidimensional Hierarchical Model)
Use Case 3: MMM with Strong Seasonality
Scenario: Marketing effects + strong weekly/yearly patterns
Solution: Pattern 4 (GAM with Temporal Components)
Use Case 4: Complex Hierarchical Structure
Scenario: Different saturation curves per country, different adstock per product
Solution: Pattern 5 (Custom Hierarchical Priors)
Use Case 5: Custom Bayesian Model
Scenario: Need full control over model specification beyond MMM
Solution: Extend MMM class, add custom effects via mu_effects
Best Practices
1. Start Simple
Begin with basic models before adding complexity:
from pymc_marketing.mmm.multidimensional import MMM
from pymc_marketing.mmm import GeometricAdstock, LogisticSaturation
mmm = MMM(
date_column="date_week",
channel_columns=["tv", "digital"],
adstock=GeometricAdstock(l_max=4),
saturation=LogisticSaturation()
)
mmm.fit(X=X, y=y)
2. Always Check Priors
prior_pred = mmm.sample_prior_predictive(X=X, samples=1000)
import matplotlib.pyplot as plt
plt.hist(prior_pred.prior_predictive["y"].values.flatten(), bins=50)
plt.axvline(y.mean(), color='red', label='Observed mean')
plt.legend()
plt.show()
3. Use Visualization for Debugging
mmm.model.to_graphviz()
4. Incremental Dimension Addition (Multidimensional MMM)
from pymc_marketing.mmm.multidimensional import MMM
mmm_v1 = MMM(..., dims=("country",))
mmm_v2 = MMM(..., dims=("country", "region"))
mmm_v3 = MMM(..., dims=("country", "region", "product"))
5. Validate with Posterior Predictive Checks
posterior_pred = mmm.sample_posterior_predictive(X_pred=X)
plt.scatter(y, posterior_pred.posterior_predictive["y"].mean(dim=["chain", "draw"]))
plt.plot([y.min(), y.max()], [y.min(), y.max()], 'r--')
plt.xlabel("Observed")
plt.ylabel("Predicted")
plt.show()
Common Pitfalls
Pitfall 1: Too Many Dimensions Too Quickly (Multidimensional MMM)
Bad:
mmm = MMM(..., dims=("country", "region", "product", "segment", "channel_type"))
Good:
mmm = MMM(..., dims=("country",))
Pitfall 2: Forgetting to Build Model
Bad:
mmm = MMM(...)
mmm.fit(X=X, y=y)
Good:
mmm = MMM(...)
mmm.build_model(X=X, y=y)
mmm.fit(X=X, y=y)
Pitfall 3: Unrealistic Priors
Bad:
priors = {"intercept": Prior("Normal", mu=0, sigma=10000)}
Good:
prior_pred = mmm.sample_prior_predictive(X=X, samples=1000)
Pitfall 4: Ignoring Sampling Warnings
Bad:
mmm.fit(X=X, y=y)
Good:
mmm.fit(X=X, y=y, target_accept=0.9)
When to Use Which Pattern
| Scenario | Pattern | Key Features |
|---|
| Baseline/testing | Pattern 1 (Linear) | No transformations, fast |
| Standard MMM | Pattern 2 (Standard) | Adstock + saturation |
| Multi-market | Pattern 3 (Hierarchical) | Country/region dimensions |
| Strong seasonality | Pattern 4 (GAM) | Temporal components |
| Complex structure | Pattern 5 (Custom priors) | Full hierarchical control |
Summary
PyMC-Marketing's GAM capabilities enable:
- Flexible progression from simple linear models to complex hierarchical GAMs
- Composable architecture with mix-and-match components
- Automatic broadcasting across dimensions
- Rich temporal modeling with trends and seasonality
- Full Bayesian inference with prior/posterior predictive checks
Key principle: Start simple, add complexity incrementally, always validate with prior/posterior checks.
mmm_lib Function Reference
All mmm_lib functions print their return keys with verbose=True (default). Always use the EXACT key names from the output - never guess or abbreviate.
check_prior_predictive_coverage(mmm, y, hdi_prob=0.95)
Checks if observed data falls within prior predictive HDI. Returns coverage in ORIGINAL scale.
Return keys:
coverage = check_prior_predictive_coverage(mmm, y)
coverage['coverage_percent']
coverage['hdi_lower_mean']
coverage['hdi_upper_mean']
coverage['observed_mean']
coverage['observed_min']
coverage['observed_max']
check_convergence(mmm)
Validates MCMC convergence: R-hat < 1.1, ESS > 200, divergences < 1%.
Return keys:
diagnostics = check_convergence(mmm)
diagnostics['converged']
diagnostics['max_rhat']
diagnostics['min_ess_bulk']
diagnostics['min_ess_tail']
diagnostics['n_divergences']
diagnostics['divergence_pct']
diagnostics['problem_vars_rhat']
diagnostics['low_ess_vars']
ROAS and Contributions (pymc-marketing 0.18.0 built-in API)
Use mmm.summary.roas() instead of custom ROAS functions:
roas = mmm.summary.roas(frequency="all_time")
roas_monthly = mmm.summary.roas(frequency="monthly")
contrib = mmm.summary.contributions(component="channel")
results = mmm.sensitivity.run_sweep(
var_input="channel_data", sweep_values=np.linspace(0.5, 1.5, 11),
var_names="channel_contribution", sweep_type="multiplicative",
)
marginal = mmm.sensitivity.compute_marginal_effects(results)
plot_prior_predictive(mmm, original_scale=True)
IMPORTANT: Always use original_scale=True (the default) so the prior predictive and observed data are on the same scale. Using original_scale=False shows data in normalized [0,1] space.
References