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.
Choose an Estimator - Compare the released
time-series, FE, and CRE contracts and choose the identifying variation that
matches the modelling question.
Fixed-effects Estimator - The released
one-unit FE contract, its within-design checks, and its limits.
The released one-unit CRE contract, its transformed summary basis,
estimability checks, and interpretation limits.
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. Named
FE and CRE presets use specialised likelihoods and restrictions; read
Choose an Estimator before applying this general
component description to them.
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.
This table describes the ordinary PanelMMM component surface. The released
FE and CRE presets deliberately reject several optional components and
downstream operations. Their estimator pages are authoritative for those
boundaries.
What target_type changes
target_type is semantic metadata, not a different likelihood family.
It affects downstream reporting such as the default efficiency metric label:
"revenue" -> ROAS
"conversion" -> CPA
It does not change the fitted functional form on its own.
Read Choose an Estimator before choosing an
aggregate time-series, FE, or CRE contract.
Adstock and Saturation
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.
You can do the same for transform priors and additive effects.
Legacy Mundlak adjustment and panel dimensions
use_mundlak_cre=True only makes sense when you have at least one panel dim.
Abacus enforces that. This low-level surface is not the named CRE estimator
preset, and its coefficients do not by themselves identify confounding or
establish causal effects.
When enabled, Abacus builds extra correlated-random-effects terms from training
period means:
channel_mundlak_contribution
control_mundlak_contribution
mundlak_contribution
These terms live on the panel coordinates defined by dims.
Custom HSGP dims
If you use a custom SoftPlusHSGP for time-varying effects, its dims must be
compatible with the panel structure.
Examples:
no extra dims: ("date",) or ("date", "channel") for media
dims=("geo",): ("date", "geo") or ("date", "geo", "channel") for media
Calibration lets you add external evidence to a built PanelMMM.
Both calibration methods are unavailable for the named FE, CRE and
release-gated RE presets; they raise EstimatorOperationError. Check
Choose an Estimator before
preparing calibration data. A built graph alone does not establish support.
Abacus currently supports two retained calibration paths:
lift-test measurements through add_lift_test_measurements(...)
cost-per-target calibration through add_cost_per_target_calibration(...)
General rule
Both calibration methods operate on a built model, not a bare constructor.
only the supported calibration methods above are available in YAML
dist is not supported in YAML yet for add_lift_test_measurements
other calibration actions should be applied through the Python API until they
have explicit YAML support
Choose the right calibration path
Use lift tests when you have measured incremental response data for a specific
spend change.
Use cost-per-target calibration when you want the fitted channel contribution to
stay consistent with observed cost efficiency.
For a supported estimator, you can use either or both after building the model.
Common pitfalls
Adding calibration before build_model(...)
Forgetting to add channel_contribution_original_scale before
cost-per-target calibration
Omitting required dims columns from calibration data
Assuming YAML supports every Python calibration argument; dist does not
currently round-trip through YAML
Next steps
Read Model Fitting for the fit workflow once the
model has been fully specified.
Read Save and Load if you plan to keep
calibrated models on disk.
Correlated-random-effects Estimator
The released cre preset fits a one-unit correlated-random-effects (CRE)
marketing-mix model. It combines shared media and control slopes with a
Gaussian random unit intercept and an explicit adjustment for association
between persistent unit differences and the declared predictors.
Released contract
estimator:type:creunit:geo
The v1 surface has these deliberate limits:
Component
Released CRE behaviour
Unit dimensions
Exactly one categorical unit column
Unit effects
Gaussian random intercept, integrated out exactly
Media and control slopes
Shared across units
Adstock and saturation
Shared geometric adstock followed by logistic saturation
CRE media summaries
Centred unit means of the transformed exposure basis
CRE control summaries
Standardised centred unit means of eligible time-varying controls
Residual scale
Shared across units
Common time effects and seasonality
Not supported
Custom additive effects and holidays
Not supported
Historical and manual scenarios
Supported for the complete fitted-unit panel; fitted CRE summaries remain frozen
Calibration and fixed-budget optimisation
Not supported
Use the bundled starting point at data/demo/geo_cre/config.yml.
Run it from the repository root:
python3 runme.py --demo geo_cre
Statistical meaning
For unit i and date t, CRE augments the shared-slope level equation with
unit summaries of the declared regressors. The media summaries are computed
from the fitted adstock-and-saturation exposure basis. They are not raw-spend
means. Eligible control summaries are standardised unit means.
The residual unit intercept is integrated out. The likelihood therefore uses
one exact Gaussian covariance block per unit. Pointwise log likelihood is
unit-block marginal evidence, not an observation-level or future-date score.
The adjustment relaxes the naive random-effects mean-independence restriction
only with respect to the declared summary basis. It does not establish causal
identification or address omitted time-varying confounding, measurement error,
reverse causality, or response-function misspecification.
Data and estimability requirements
The dataset must be a balanced unit-date Cartesian product with one row per
unit and date. It must contain enough units to estimate the active centred
media and control summaries while retaining at least two residual between-unit
degrees of freedom.
Abacus rejects:
non-finite predictors or targets;
media with no within-unit temporal variation;
an exactly rank-deficient transformed between-summary design; and
insufficient between-unit residual degrees of freedom.
It warns about low transformed within-unit variation, high variance inflation
factors, and a high condition number. These thresholds are configurable under
estimator.estimability. A pass means that the implemented screen found no
declared defect. It is not proof of global, posterior-wide, or causal
identification.
After fitting, inspect posterior convergence, effective sample size, sampler
pathologies, prior sensitivity, the post-fit summary-basis diagnostics, and
predictive checks. A CRE coefficient interval containing zero does not prove
that a simpler random-effects model is adequate.
Pipeline evidence
A structured CRE run records:
the resolved estimator contract in
00_run_metadata/estimator_summary.txt and
00_run_metadata/estimator_manifest.yaml;
the raw structural screen in
10_pre_diagnostics/cre_structural_estimability.json;
the transformed reference-basis screen in
10_pre_diagnostics/cre_reference_estimability.json and
10_pre_diagnostics/cre_reference_estimability_features.csv;
the bounded posterior-draw screen in
20_model_fit/cre_postfit_estimability.json; and
the CRE adjustment and reconciliation outputs under 40_decomposition.
The run manifest is the machine-readable index of these files. A completed
pipeline only means that every required stage ran. Review the diagnostic status
before interpreting the posterior.
Prediction and persistence
Prediction is conditional on the fitted unit history. Every prediction request
must supply all fitted units. Row order may vary, but unseen units and
fitted-unit subsets are rejected. Save and load preserve the fitted CRE summary
state and validate its unit coordinates before prediction.
Historical and manual scenarios use the same fitted-unit restriction. Manual
spend changes the nonlinear media-response path, but Abacus does not recompute
the fitted training-period Mundlak media or control summaries. This preserves
the fitted CRE adjustment rather than redefining confounding context from the
planned spend. Scenario outputs are posterior media-contribution estimates,
not total-outcome forecasts or causal effects.
Use the YAML and Python examples under data/demo/geo_cre/. Fixed-budget
optimisation remains outside the released CRE scenario contract.
Evidence boundary
The release verifies the declared graph, configuration restrictions,
estimability evidence, fitted-unit prediction contract and persistence path.
It does not promise a fixed point-estimate accuracy, causal validity, or general
robustness for arbitrary data. Treat each fitted model as a separate statistical
assessment.
For a direct comparison with FE and the aggregate time-series preset, see
Choose an Estimator.
Choose an Estimator
PanelMMM provides three released named estimator presets:
time_series for one aggregate time series
fe for a one-unit fixed-effects panel
cre for a one-unit correlated-random-effects panel
The re declaration is typed but release-gated. It is not a runnable
estimator.
Release support matrix
Preset
Status in 3.1.1
Data contract
Identifying variation
Important operation boundary
time_series
Released
One aggregate observation per date
Aggregate temporal variation
Uses the supported ordinary PanelMMM workflow
fe
Released
One balanced unit-date panel
Within-unit temporal variation
Prediction and historical/manual scenarios require all fitted units; calibration and fixed-budget optimisation are unavailable
cre
Released
One balanced unit-date panel
Within-unit variation conditional on the declared transformed between-unit summary adjustment
Prediction and historical/manual scenarios require all fitted units and frozen fitted CRE summaries; calibration and fixed-budget optimisation are unavailable
re
Gated; not released
Declaration only
Not applicable until release
Graph construction fails closed with EstimatorReleaseGateError
The support status is a software release boundary, not evidence that a preset
is appropriate for a particular dataset or causal question.
Compare the released presets
Question
time_series
fe
cre
Data structure
One observation per date
Balanced unit-date panel
Balanced unit-date panel
Unit effects
Not applicable
Absorbed unit intercepts
Gaussian random unit intercept
Identifying variation for shared slopes
Aggregate temporal variation
Within-unit temporal variation
Within-unit temporal variation, conditional on the declared between-unit summary adjustment
Media slopes
Shared
Shared across units
Shared across units
Adstock and saturation
Shared
Shared across units
Shared across units
Persistent unit differences
Not represented
Removed from the slope likelihood
Modelled through the random intercept and declared CRE summaries
Common categorical time effects
Not an estimator option
Not supported
Not supported
Calibration
Available through the ordinary PanelMMM surface
Not supported
Not supported
Historical and manual scenarios
Supported
Supported for all fitted units
Supported for all fitted units with frozen fitted CRE summaries
Fixed-budget optimisation
Supported
Not supported
Not supported
All three presets combine likelihood information with the declared priors.
None of them turns observational marketing data into a causal design.
Use the time-series preset
Use time_series when the modelling unit is one aggregate market, brand, or
business series and each date occurs once.
estimator:type:time_series
Do not use it to disguise panel observations as independent aggregate rows.
Aggregate the data deliberately or use a panel estimator.
Use FE
Use fe when the target question concerns changes within units over time and
you want time-invariant unit characteristics removed from the shared-slope
likelihood.
estimator:type:feunit:geo
FE is appropriate only when the transformed media and controls have enough
within-unit temporal variation. It cannot estimate coefficients for predictors
that are constant within every unit. It also does not remove time-varying
confounding or common shocks.
Use cre when persistent unit differences may be associated with the declared
predictors and you need an explicit within-between panel specification.
estimator:type:creunit:geo
CRE augments the shared-slope random-intercept model with centred unit summaries.
Media summaries use the fitted adstock-and-saturation exposure basis rather
than raw-spend means. The adjustment is limited to the declared basis. It does
not correct arbitrary omitted confounding.
CRE needs both usable within-unit media variation and enough independent
between-unit information for its active summaries. Prediction is limited to
the complete set of fitted units.
The presets answer different statistical questions. Do not select one only
because it has the best in-sample fit, lowest information criterion, or the
most favourable media coefficient.
Before fitting:
State the unit and time structure of the business question.
State which persistent and time-varying confounding paths remain plausible.
Check whether the proposed identifying variation exists after media
transformation.
Choose the estimator contract and priors before inspecting the preferred
result.
After fitting, inspect the estimator-specific estimability evidence, MCMC
diagnostics, posterior predictive checks, prior sensitivity, and any
predeclared holdout evidence supported by that estimator. A passed screen means
that Abacus did not detect the specified defect. It is not proof of causal or
global identification.
The demo sampling settings are evidence-oriented and may take time. Command-line
overrides can reduce the budget for an installation or workflow check. Do not
interpret a reduced run as final statistical evidence.
Fixed-effects Estimator
The released fe preset fits a one-unit fixed-effects marketing-mix model.
It absorbs a separate intercept for each unit and identifies shared media and
control effects from temporal changes within each unit.
For units i and dates t, the level formulation is:
Abacus fits the equivalent exact within-unit orthonormal-contrast likelihood.
The unit intercepts alpha_i are not regularised parameters in the graph.
Consequently, persistent differences between units do not identify the shared
media coefficients.
Released contract
estimator:type:feunit:geo
The released surface has these deliberate limits:
Component
Released FE behaviour
Unit dimensions
Exactly one categorical unit column
Unit effects
Absorbed fixed intercepts
Media and control slopes
Shared across units
Adstock and saturation parameters
Shared across units
Residual scale
Shared across units
Common time effects
Not supported
Annual seasonality and custom additive effects
Not supported
Time-varying intercepts or media
Not supported
Budget optimisation and calibration
Not supported
Use the bundled starting point at
data/demo/geo_fe/config.yml. It contains only settings supported by this
contract.
Run it from the repository root:
python3 runme.py --demo geo_fe
Data requirements
The dataset must be balanced on the declared unit and date columns: every unit
must have the same dates, and each unit-date pair must occur once. It must have
at least two units and two dates.
Each channel and control must vary within at least one fitted unit. A predictor
that is constant within every unit cannot be estimated by FE and causes a
pre-fit error. The target must also have non-zero within-unit variation.
This is not the same as adding geo controls to a pooled model. FE discards
between-geo level variation from the likelihood for the shared slope
coefficients.
Estimability screen
Before the main graph is created, Abacus evaluates media after the configured
adstock and saturation transforms at a fixed-seed PyMC initial point. It saves:
The zero-variation and rank checks remain errors. Do not suppress a warning by
changing a threshold without recording why the underlying design remains fit
for purpose.
The screen is a reference-design diagnostic. It cannot prove identification
for every posterior draw because adstock and saturation parameters are
estimated. A pass is necessary for the released graph, not sufficient evidence
for a causal or decision claim.
Interpretation
For a media channel to be identified, it needs meaningful within-unit temporal
variation after the configured transforms. National media that is identical in
every geography can still vary over time, but it can be difficult to separate
from common shocks. The initial FE contract does not add time fixed effects, so
it does not claim to solve that problem.
FE removes time-invariant unit characteristics. It does not solve time-varying
endogeneity, anticipation, simultaneous promotions, or measurement error. Use
the preflight report, posterior diagnostics, predictive checks, prior
sensitivity, and an explicit causal design before making attribution or budget
decisions.
What comes next
RE and CRE are separate estimator contracts. CRE is released under its own
transformed-summary and prediction boundary; RE remains unavailable.