This section explains how PanelMMM is defined: the model structure, media
transforms, priors, panel dimensions, optional time variation, and calibration
hooks.
Pages
Model Overview - The actual PanelMMM mean structure,
scaled-space formulation, and optional components.
Adstock and Saturation - Supported media
transforms, their priors, and the adstock_first composition order.
Priors and Configuration - Default prior keys,
model_config, transform-prior overrides, and directional control priors.
Time-Varying Parameters - How
time_varying_intercept and time_varying_media use SoftPlusHSGP.
Seasonality and Trends - Built-in yearly
seasonality plus custom Fourier, trend, and event effects.
Panel Dimensions - How dims change the shape of the
data and parameters.
Calibration - Lift-test and cost-per-target calibration for
a built model.
Subsections of Model Specification
Model Overview
PanelMMM is an additive Bayesian marketing mix model built in PyMC. This page
describes the model structure that Abacus actually builds.
For input layout, see Data Preparation. For
individual configuration surfaces, see the other pages in this section.
Core structure
At fit time, Abacus builds the model mean in scaled target space as:
mu =
intercept_contribution
+ sum(channel_contribution over channel)
+ sum(control_contribution over control), if control_columns are configured
+ mundlak_contribution, if use_mundlak_cre=True
+ yearly_seasonality_contribution, if yearly_seasonality is enabled
+ any additional mu_effects
The observed target is then attached through the configured likelihood
distribution with mu=mu.
What is scaled and what is not
Before the PyMC graph is built:
channel data is scaled according to Scaling.channel
the target is scaled according to Scaling.target
controls are not scaled automatically
That means media and target priors operate on the model scale, not directly on
the original business units. For the scaling surface, see
Scaling and Preprocessing.
Model components
Component
Built when
Shape
intercept_contribution
Always
effectively ("date", *dims) in the model mean
channel_contribution
Always
("date", *dims, "channel")
control_contribution
control_columns is set
("date", *dims, "control")
mundlak_contribution
use_mundlak_cre=True
dims
yearly_seasonality_contribution
yearly_seasonality is set
("date", *dims)
Additional additive effects
You add entries to mu_effects
("date", *dims)
Abacus also adds total_media_contribution_original_scale automatically as a
deterministic on the original target scale.
Media path
Each channel column goes through the configured media transform path:
scale channel input
apply adstock and saturation through forward_pass(...)
optionally apply a time-varying media multiplier
contribute the result through channel_contribution
Use controls for non-media regressors such as price, macro indicators, or
competitor measures. Controls are configured with control_columns and use
gamma_control priors from model_config.
Panel dimensions
dims adds extra indexing axes such as geo, brand, or market.
With dims=("geo",), the model is indexed over date and geo. With
dims=("geo", "brand"), it is indexed over date, geo, and brand.
Abacus does not automatically add hierarchical pooling just because dims is
set. By default, parameters are indexed over the configured panel coordinates.
If you want hierarchical shrinkage across those coordinates, encode it in the
priors you pass to transforms or model_config.
PanelMMM requires one adstock transform and one saturation transform. Abacus
applies them inside the model graph rather than as a fixed preprocessing step.
Transform priors also appear in model_config under prefixed variable names.
For example:
adstock_alpha
adstock_lam
adstock_k
saturation_lam
saturation_beta
That means you can override transform priors centrally through the top-level priors
if you prefer. See Priors and Configuration.
Choose the composition order
adstock_first is part of the model specification, not a plotting choice.
The current public YAML schema does not expose adstock_first; it uses the
library default. If you need to change the composition order, use the Python
API.
Use adstock_first=True when you want the model to interpret carryover before
diminishing returns. Use False when you want each period’s spend to saturate
before the carryover step.
The code path is explicit:
True -> saturation(adstock(x))
False -> adstock(saturation(x))
Common pitfalls
Forgetting that l_max is required for adstock classes
Assuming dims automatically change transform priors even when you have
already set explicit incompatible dims on the transform
Using adstock_first=False without a substantive reason
Treating transform priors as if they were on original business units rather
than the model scale
The builder appends these effects before calling build_model(...).
Choosing between built-in and custom seasonality
Use yearly_seasonality when you need a compact built-in annual effect.
Use FourierEffect when you need:
weekly seasonality
monthly seasonality
multiple seasonal effects together
custom Fourier prefixes or priors
Common pitfalls
Adding effects after the model has already been built
Using event effect dims that do not include the required prefix
Treating yearly_seasonality and a custom yearly Fourier effect as if they
were separate concepts when they are both additive seasonal terms
Next steps
Read Time-Varying Parameters if you want trend
or media behaviour to vary smoothly over time.
Read Calibration if you want to constrain the specification
with external measurements.
Priors and Configuration
Abacus uses model_config to control priors on the underlying PyMC variables.
Transform priors can be configured either on the transform objects themselves or
through their prefixed variable names in model_config.
Abacus parses these mappings into runtime Prior or HSGPKwargs objects.
Transform priors and prefixed names
Transform parameters appear in the model under prefixed variable names.
Examples:
adstock alpha -> adstock_alpha
saturation lam -> saturation_lam
saturation beta -> saturation_beta
So you can override transform priors in either of these ways:
pass priors={...} to the transform object
override the prefixed variable in model_config
Use one style consistently within a project if you want the configuration to be
easy to read.
Directional control priors
Controls are the right place for exogenous drivers whose effect may be
negative, such as competitor spend, competitor price pressure, or supply-side
disruptions. By default, control coefficients remain unrestricted.
The current public YAML schema does not expose control_impacts or
control_sign_policy. If you need directional control settings today, use the
Python API for that part of the specification.
Constraints for directional controls
When control_impacts is configured, Abacus expects:
gamma_control and gamma_control_mundlak to be Normal or
TruncatedNormal
scalar numeric mu and sigma values for those priors
the prior dims to include "control"
If you violate those assumptions, model build fails with a validation error.
Time-varying configuration keys
When you enable a boolean time-varying effect, Abacus uses these model_config
keys:
intercept_tvp_config
media_tvp_config
Those keys can be:
an HSGPKwargs instance
a dict with HSGPKwargs fields
a dict in SoftPlusHSGP.parameterize_from_data(...) style, such as
{"ls_lower": 1, "ls_upper": 10}
dims tells PanelMMM which extra categorical axes exist alongside date.
With no extra dims, the model is indexed by:
date
channel
optionally control
With dims=("geo",), the model is indexed by:
date
geo
channel
optionally control
With dims=("geo", "brand"), it is indexed by:
date
geo
brand
channel
optionally control
What changes inside the model
Setting dims changes the coordinates and parameter shapes used in the PyMC
graph.
Quantity
No extra dims
dims=("geo",)
channel_data
("date", "channel")
("date", "geo", "channel")
target_data
("date",)
("date", "geo")
channel_contribution
("date", "channel")
("date", "geo", "channel")
control_contribution
("date", "control")
("date", "geo", "control")
intercept prior dims by default
()
("geo",)
Reserved names
Do not use these names in dims:
date
channel
control
fourier_mode
Abacus rejects them because they are reserved for internal coordinates.
dims does not imply automatic pooling
This is the most important modelling point.
By default, dims gives you parameters indexed by the panel coordinates, but
not automatic hierarchical shrinkage across those coordinates.
For example:
the default intercept prior is Normal(..., dims=dims)
transform priors default to (*dims, "channel")
control coefficients default to (*dims, "control")
Those defaults create per-slice parameters. If you want hierarchical pooling
across geo, brand, or another dimension, you need to encode that in the
priors you supply.