Frequently asked questions
Use these answers to choose a supported workflow and interpret its evidence.
They describe source commit 7cb7f20b357f23ee1683670953c7dd0092fa2084, reviewed
on 1 October 2026; linked guides provide the longer explanations.
Data and specification
How much data do I need?
There is no universal number of weeks that makes channel effects identifiable.
Assess independent variation in transformed exposure, spend support and variation
remaining after controls and baseline terms; repeated rows of the same spending
pattern cannot resolve every attribution problem.
FE and CRE enforce particular design requirements, which are narrower than
statistical adequacy, so use the identification guide
alongside those checks (src/ammm/mmm/fixed_effects.py:261,
src/ammm/mmm/correlated_random_effects.py:358).
What do adstock and saturation change?
Adstock describes carryover across model periods, while saturation describes
the response to the transformed input.
The GeometricAdstock component defaults to normalised trailing weights, and
ordinary MMM defaults to applying adstock before saturation; reversing that
order changes the response assumption (src/ammm/mmm/components/adstock.py:88,
src/ammm/mmm/mmm.py:436).
Choose the lag horizon and shape from a defensible mechanism, then assess their
implications using the specification guide.
Must my model be additive, or can I use a log link?
Ordinary MMM supports an identity link and an experimental log link, whose
default likelihoods are Normal and LogNormal respectively
(src/ammm/mmm/link.py:560, src/ammm/mmm/link.py:622).
A log link does not automatically create a constant-elasticity log-log model,
because the specified media transformations still determine the predictor.
Its default media contrasts use the conditional median, and the retained YAML
runner rejects log-link models at response preparation, so use the Python
interface with the response contract
in view (src/ammm/mmm/link.py:661, src/ammm/pipeline/stages/core.py:130).
What does HSGP mean for a time-varying model?
A Hilbert-space Gaussian process (HSGP) provides a finite basis approximation
for a smooth model component.
Ordinary MMM accepts boolean switches or HSGP objects for time-varying intercept
and media effects, and its boolean defaults configure 200 basis functions
(src/ammm/mmm/mmm.py:392, src/ammm/mmm/mmm.py:1388).
Basis size, domain and smoothness assumptions affect the approximation, so
inspect time variation
and attribution sensitivity rather than treating extra flexibility as evidence
of a separate media effect (src/ammm/mmm/hsgp.py:64, src/ammm/mmm/tvp.py:183).
Should I choose ordinary MMM, FE or CRE?
Choose the estimator from the statistical question and required operations.
Ordinary MMM(dims=("geo",)) uses configured parameter dimensions and priors,
whereas FE fits within-unit contrasts and CRE integrates a Gaussian unit
intercept with transformed-history adjustments (src/ammm/mmm/mmm.py:1375,
src/ammm/mmm/fixed_effects.py:377, src/ammm/mmm/correlated_random_effects.py:422).
The specialised estimators require balanced fitted-unit panels and reject
calibration and fixed-budget optimisation, so check the FE
and CRE contracts before selecting one
(src/ammm/mmm/fixed_effects.py:204, src/ammm/mmm/fixed_effects.py:632,
src/ammm/mmm/correlated_random_effects.py:311,
src/ammm/mmm/correlated_random_effects.py:746).
Does CRE perform a Mundlak specification test?
The CRE interface fits a Bayesian adjustment; it does not expose a classical
joint Wald-test decision about random-effects adequacy.
Its gamma_media_cre and eligible gamma_control_cre parameters multiply
declared unit summaries, so posterior intervals concern those conditional
associations (src/ammm/mmm/correlated_random_effects.py:255,
src/ammm/mmm/correlated_random_effects.py:484).
An interval containing zero does not establish mean independence, and an
interval excluding zero does not measure all confounding; inspect the summary
basis, priors and CRE limitations.
Does AMMM4 scale my data and clean missing observations?
Ordinary MMM scales channels and target but requires you to resolve missing or
invalid observations explicitly.
Its default uses signed maxima, controls are not automatically standardised,
and finite values with complete labelled observations are required
(src/ammm/mmm/_mmm_init.py:167, src/ammm/mmm/_mmm_graph.py:298,
src/ammm/mmm/data_conversion.py:189).
Read the scaling contract and
input contract, including the requirement
that a named target Series match target_column
(src/ammm/mmm/_mmm_graph.py:42).
Priors and checking
What can a prior tell me about a channel’s sign?
A positive-support prior fixes the permitted sign before the observations are
used, so a positive posterior is not independent evidence against a negative effect.
For example, LogisticSaturation defaults to a HalfNormal amplitude prior
(src/ammm/mmm/components/saturation.py:249).
Choose scales in the model’s units and report provenance, practical effect
thresholds and sensitivity, following the prior specification guide.
What should I inspect before fitting?
Inspect whether prior predictive outcomes have plausible levels, variability
and support under the proposed specification.
In the Python quickstart, the registered original-scale draws are available as
model.idata["prior"]["y_original_scale"], whereas the returned prior dataset
contains the observed variable y; the return extracts the prior_predictive
group (src/ammm/model_builder.py:673).
Supply real targets when building and review the quickstart
with the lifecycle rules,
because a prior-only placeholder requires rebuilding before fitting
(src/ammm/mmm/mmm.py:1709).
What do posterior predictive checks establish?
Posterior predictive checks assess whether the fitted model can reproduce
features of observations relevant to your task.
Use retained predictive draws to inspect levels, extremes, residual structure
and subgroup patterns; explicitly set combined=False when separate chain and
draw axes are needed (src/ammm/mmm/mmm.py:2299).
An in-sample match does not establish future predictive performance or causal
attribution, so follow inference and prediction
with an appropriate independent evaluation.
Does creating prior-sensitivity scenarios establish robustness?
Creating scenarios records alternative configurations without fitting them.
Stage 75 fits alternatives only when fit_scenarios: true, whereas the default
is false (src/ammm/prior_sensitivity/config.py:52,
src/ammm/pipeline/stages/core.py:584).
Review fit diagnostics and changes in decision quantities across defensible
alternatives, because stability under a selected set does not prove
identification; an input sweep at retained posterior draws answers a different
question, as the prior-sensitivity guide explains.
Inference and diagnostics
Which sampling diagnostics should I check?
Check divergences, R-hat, bulk and tail effective sample size, and Monte Carlo
precision for the quantities you intend to report.
For example, the bundled demo policy warns at R-hat at least 1.01, fails at
1.05, and fails for any retained divergence; these are screening rules rather
than guarantees (data/demo/diagnostic_gates.yml:9,
src/ammm/mmm/diagnostic_gates.py:507).
Investigate the cause of failures using the inference guide
and inspect the resolved policy rather than treating a fixed draw count as acceptance.
Can I choose the best model from LOO or WAIC alone?
LOO and WAIC support a specified predictive comparison when outcomes, scales
and scored observation units are compatible.
FE scores within contrasts, whereas CRE uses collapsed unit likelihoods, so
their raw scores cannot simply be ranked against ordinary level-observation
scores (src/ammm/mmm/fixed_effects.py:377,
src/ammm/mmm/correlated_random_effects.py:542).
Inspect uncertainty and importance-sampling warnings, and use
time-respecting prediction
for a future-period question; predictive preference does not establish causal attribution.
What does a blocked-tail holdout tell me?
A blocked-tail holdout assesses conditional prediction on later dates from a
fresh fit using earlier observations.
For an uncalibrated aggregate MCMC model, the runner uses observed holdout
inputs and supplies factual training history for adstock
(src/ammm/pipeline/stages/validation.py:27,
src/ammm/mmm/blocked_holdout.py:76).
The runner rejects enabled holdout validation with non-empty calibration before
loading data. Remove calibration for evaluation, or disable validation for a
calibrated fit (src/ammm/pipeline/stages/validation.py:28, working tree after
7cb7f20).
Interpret error and coverage under the holdout design,
because observed future inputs and repeated model selection limit what this
evaluation establishes.
Do failed diagnostic gates stop the runner or the AI advisor?
A diagnostic fail can coexist with a completed runner invocation, because
the diagnostic stage writes its result and the runner continues unless a stage
raises an exception (src/ammm/pipeline/stages/core.py:684,
src/ammm/pipeline/runner.py:215).
The final advisor skips a provider call when its deterministic rules return
do_not_use, which is a separate decision from process completion
(src/ammm/pipeline/stages/ai_advisor.py:181).
Read the diagnostic report and
retained stage states, because neither a
completed directory nor a skipped check establishes model acceptance.
Interpretation and ROAS
Why can similar fits give different baseline and media contributions?
The likelihood observes the combined predictor, so different component
allocations can produce similar fitted outcomes.
Ordinary MMM combines transformed media with the configured intercept,
controls and effects before constructing the likelihood
(src/ammm/mmm/_mmm_graph.py:147, src/ammm/mmm/_mmm_graph.py:182).
Compare attribution under defensible baseline and prior choices, using the
specification and
identification guides, rather than selecting
the allocation that tells the preferred story.
Does a narrow posterior mean the effect is identified?
A narrow posterior can reflect restrictive assumptions even when observations
do not distinguish the alternatives relevant to a decision.
AMMM4’s FE and CRE design screens check specific reference designs, so passing
them does not establish nonlinear or causal identification across the posterior
(src/ammm/mmm/fixed_effects.py:261,
src/ammm/mmm/correlated_random_effects.py:358).
Assess prior and specification sensitivity and, when channel-specific evidence
is insufficient, narrow the claim or seek additional variation, as described in
the identification guide.
How do ROAS and marginal ROAS differ?
ROAS summarises return relative to spend, whereas marginal ROAS concerns the
response to an additional spend change at a stated operating point.
model.summary.roas() defaults to elementwise calculations; the incrementality
interface instead evaluates spend contrasts, and its marginal method defaults
to a 1% finite perturbation (src/ammm/mmm/summary/_factory_contributions.py:67,
src/ammm/mmm/incrementality.py:1423).
For monetary interpretation, require monetary spend and the intended outcome
units, and report aggregation, history, carryover and posterior uncertainty
using the returns guide.
Neither ratio supplies a causal identification argument by itself.
Calibration and evidence
When can I describe a contribution as a causal effect?
A causal interpretation requires a defined intervention and a defensible
identification argument for that population, horizon and outcome.
AMMM4’s incrementality calculations compare model predictions under factual
and changed spend, so they inherit the fitted response assumptions
(src/ammm/mmm/incrementality.py:271).
Review assignment, adjustment, support, measurement and spillovers using the
identification guide; good sampling and
predictive diagnostics answer different questions.
How does a lift test enter the model?
A supported lift test adds a measurement likelihood for a static saturation
contrast before fitting.
The ordinary-MMM method scales x, delta_x, delta_y and sigma, uses the
configured channel and panel labels, and defaults to a Gamma measurement
distribution; it rejects the log link (src/ammm/mmm/_mmm_calibration.py:102).
Match the study to that contrast and its uncertainty, because this API does
not replay arbitrary treatment and control exposure histories; follow the
calibration guide.
What must I prepare for cost-per-target calibration?
Build the ordinary MMM and register channel_contribution with
add_original_scale_contribution_variable before adding cost-per-target calibration.
The method requires channel_contribution_original_scale, monetary spend on
matching training coordinates and calibration rows with the selected target
column and uncertainty (src/ammm/mmm/_mmm_calibration.py:171).
Use target_per_cost=True only when the reciprocal estimand is intended, and
review the calibration contract
before interpreting the supplied ratio.
Optimisation
When is a budget optimisation result ready to act on?
Act only after the model, proposed intervention and operational constraints
have adequate evidence for the decision.
The default optimiser uses local SLSQP and a posterior-mean utility of the
selected response, so solver success does not establish a global optimum or
beneficial intervention (src/ammm/mmm/budget_optimizer.py:297,
src/ammm/mmm/budget_optimizer.py:431).
Check feasibility independently, compare feasible starts, examine posterior
differences from the current plan and review extrapolation and causal assumptions
using the optimisation guide.
Are scenario budgets and Python allocation budgets in the same units?
Manual recipe allocations specify total horizon spend, whereas the Python
response interface distributes a per-period allocation across the selected dates.
The recipe converts its total into a per-period allocation before evaluation,
so copying the same number between interfaces changes the intended spend
(src/ammm/mmm/scenarios.py:105, src/ammm/mmm/scenarios.py:413).
Keep dates, factual carry-in and trailing carryover consistent when comparing
plans, following scenario recipes
and the Python example.
Operations and migration
Can I migrate a saved model without refitting?
Use the originating release for an unsupported saved format, or reconstruct
the specification and refit from the original inputs.
The AMMM4 loader requires persistence format 1, canonical training data and
replayable calibration records; disabling identity checks does not bypass the
persistence validation (src/ammm/mmm/persistence.py:62,
src/ammm/mmm/persistence.py:79, src/ammm/mmm/mmm.py:970).
Do not assume AMMM3 classes or saved models are interchangeable; use the
AMMM4 comparison and
save/load contract
to plan and verify reconstruction.
What does the AI advisor send outside my environment?
When live review is enabled and permitted by the local rules, the request
contains selected configuration and diagnostic evidence with aliased channel
and control labels, rather than raw outcome series
(src/ammm/ai/evidence.py:84, src/ammm/ai/evidence.py:161,
src/ammm/ai/evidence.py:511).
OpenRouter requests set data-collection denial and zero-data-retention routing
parameters; these request controls do not establish a provider-wide governance
guarantee (src/ammm/ai/client.py:173).
Inspect the retained evidence before enabling a provider, and keep local
parameter lookup files distinct from the provider evidence, following the
advisor guide
(src/ammm/pipeline/stages/ai_advisor.py:168).
What does the AI advisor cost, and how do I avoid provider calls?
The code does not establish a fixed monetary price for a live review; check
the selected provider’s current pricing and returned usage before budgeting.
The implementation requests at most 3,000 output tokens and records returned
usage, but that output limit is not a currency spending cap
(src/ammm/ai/advisor.py:174, src/ammm/pipeline/stages/ai_advisor.py:315).
Set llm_enabled: false for local review or use --no-ai-advisor to disable
both advisor stages, because llm_enabled defaults to true inside an enabled
advisor block (src/ammm/pipeline/config.py:47, runme.py:102).
See the advisor configuration for credentials and
retained invocation status.
Does a successful dry run validate my quick-run overrides?
A dry run validates the source configuration and builds the main graph; it
does not apply the sampler, quick-mode or stage-disable overrides.
The CLI takes the dry-run branch before constructing the overridden run
configuration, although it forwards an explicit holiday-file override
(runme.py:163).
Use the YAML runner guide to distinguish
graph validation from an actual run, then inspect the latter’s resolved
configuration and manifest.
What does the FE prior predictive plot show?
FE prior predictions use within-unit contrasts because that is the fitting
likelihood. The repaired plot compares them with observed contrasts and labels
the axis accordingly; posterior predictions reconstruct outcome levels
(src/ammm/mmm/plotting/diagnostics.py:206, working tree after 7cb7f20).
The FE runner and its dependent
retained scenarios completed a reduced-draw
workflow check, but the fit failed diagnostic gates. A successful execution
therefore does not qualify the posterior for decisions.