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.

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.