Fixed-effects Estimator

The released fe preset fits a fixed-effects marketing-mix panel with one categorical unit dimension, such as geo, and multiple units. 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:

y_it = alpha_i + f(media_it; theta) + controls_it * gamma + error_it.

ammm 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.

The sections above and the released contract below describe unit-only FE. Optional common category effects have a separate experimental contract and evidence boundary.

Released contract

estimator:
  type: fe
  unit: geo

The released unit-only surface has these deliberate limits:

ComponentReleased FE behaviour
Unit dimensionsExactly one categorical unit column
Unit effectsAbsorbed fixed intercepts
Media and control slopesShared across units
Adstock and saturation parametersShared across units
Residual scaleShared across units
Common time effectsNot supported
Annual seasonality and custom additive effectsNot supported
Time-varying intercepts or mediaNot supported
Budget optimisation and calibrationNot 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, ammm evaluates media after the configured adstock and saturation transforms at a fixed-seed PyMC initial point. It saves:

  • 10_pre_diagnostics/fixed_effects_estimability.csv
  • 10_pre_diagnostics/fixed_effects_estimability.json

The report records the reference basis, within-variation share, VIF, condition number, rank, and the thresholds used. Its default policy is:

CheckDefaultAction
Zero transformed within-unit variationexact numerical checkError
Rank-deficient transformed within designexact matrix-rank checkError
Within-variation sharebelow 0.05Warning
VIFabove 20Warning
Condition numberat least 30Warning

You may version a stricter or looser warning policy in the estimator block:

estimator:
  type: fe
  unit: geo
  estimability:
    within_variation_share_warning: 0.10
    max_vif_warning: 10
    condition_number_warning: 20

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.

A channel that passes the zero-variation check with a low within-variation share is accepted, and its coefficient is then shaped by the prior and the functional form rather than by the data. See Always-on and low-variance channels for the case the gate does not catch, the screens to run before fitting, and the designs that change the answer.

What comes next

RE and CRE are separate estimator contracts. CRE is released under its own transformed-summary and prediction boundary; RE remains unavailable.

For a direct comparison, see Choose an Estimator.

Experimental common categorical time effects

Common category effects were introduced in 3.3.1 as an experimental FE extension and remain experimental in ammm 3.3.2. Numerical and bounded integration checks do not establish repeated-data bias, interval calibration or general robustness.

E1 recorded five divergences in its delayed-adstock case. After the numerical stability fix, the separate N3-S1 check passed on the same dataset with zero divergences. E1 remains failed. The 3.3.1 release decision accepted the combined evidence for experimental use despite E1’s original all-cells-pass rule. Distribution is through the private GitHub repository under the unchanged proprietary licence. Q1 statistical qualification remains unexecuted. The released unit-only FE contract above remains the baseline. Categorical CRE is unsupported and named RE remains gated.

Read Categorical FE evidence and release limits for the historical development checks, retained mismatches and permitted claims. The 3.3.2 namespace rename does not rerun or relabel that evidence; older fitted artefacts require their original runtime.

estimator:
  type: fe
  unit: geo
  time_effects:
    column: period

period supplies equality groups, not calendar instructions. A month label and a year-month label define different models. Observed labels must be homogeneous strings or signed int64 integers, with no missing values or type coercion. String identity includes whitespace and case. Pandas categorical ordering and unused levels are ignored. Dates must be timezone-naive and are normalised to nanoseconds. Every date must have the same label across all fitted units. Training needs at least two observed categories.

For N units, T dates and G observed categories, the model is y_it = alpha_i + lambda_g(t) + m_it(theta) + epsilon_it. Reference coding sets the first chronological category effect to zero, includes each unit intercept, and puts a flat prior on the q = N + G - 1 identified nuisance coefficients. The exact error-contrast Normal likelihood has r = NT - N - G + 1 orthonormal dimensions and normalisation -r/2 * log(2*pi*sigma²). The same factorised Helmert operator C acts on both target and transformed mean. Sigma is the individual-error standard deviation. Profiling a level dummy regression gives a different sigma-dependent normalisation and is not this likelihood.

The target scale is the factual training RMS s_y = ||C y_raw|| / sqrt(r). Priors are conditional on that fitted preprocessing; they are not a fully generative prior for unscaled outcomes. Original-target media amplitudes, control coefficients and sigma multiply scaled values by s_y. Linear raw-spend slopes also divide by the fitted channel scale. Ordinary FE retains its existing within-contrast maximum-absolute target scale.

The reference screen evaluates configured transforms at pymc_initial_point(seed=0). It rejects numerically unresolved projected features, deficient joint slope rank and r-p < 1, where p counts reference media/control features. This last restriction is a support limit, not a residual-df calculation including all nonlinear parameters. National media constant within each category is absorbed; national variation within a repeated category may remain. Adstock can change that result, so the screen uses transformed media. Raw within-unit variation is still required.

The numerical-zero threshold is 64*eps_float64*max(NT,q,1) relative to each feature’s own scaled norm. Rank uses normalised projected columns and threshold 64*eps_float64*max(r,p)*largest_singular_value. Extremely large absorbed levels can exhaust numerical precision; such inputs fail explicitly. The retained version-one report uses uncentred projected VIF, correlations and condition number. Unavailable or overflowed metrics are JSON null with reasons. These checks establish only reference-design properties, not global nonlinear or causal identification.

Level posterior prediction requires supplied known categories and all fitted units. One future date with one known category is valid. An existing fitted date cannot be relabelled; unseen categories have no extrapolated baseline. Future reuse of a known label assumes its fitted effect still applies. Conditional nuisance draws always use factual retained training residuals. They have the joint covariance implied by the flat-prior dummy design; shared unit/category uncertainty is retained across forecast rows. Independent observation noise is added afterwards.

OutputAxes and units
channel_contributionchain, draw, date, unit, channel; scaled target
channel_contribution_original_scalesame axes; original target
total_media_contribution_original_scalechain, draw; returned-window total
y (or the configured output variable)chain, draw, date, unit; scaled level observations

The observation wrapper converts scaled levels to target units once. Training y remains in labelled unit_time_contrast space, including any original-scale training deterministic. Prior predictive observations are contrasts: the flat nuisance prior has no proper prior level-predictive law. Pointwise LOO, WAIC and Pareto-k are unavailable because leaving out a basis-dependent contrast is not leaving out a date/unit observation. Retained contrast log densities remain available for inspection; other diagnostics continue with an explicit criteria-unavailable note.

Media-only prediction, response curves, manual scenarios and historical incrementality do not require future categories or nuisance draws. Synthetic scenario frames omit the category column, including when integer category 0 was fitted. A response wrapper without category inputs cannot request level observations; call PanelMMM.sample_posterior_predictive with a complete frame instead. Carryover uses factual training media and totals count only returned dates. Incrementality uses paired factual-minus-counterfactual media contributions and preserves its model-implied estimand.

Saved models retain canonical fe_time_effects_state version 1 and factual fit_data; prediction and loading validate category mapping, posterior axes, scales and fitted coordinates. Loading needs no external input file. Calibration, fixed-budget optimisation, generic level decomposition, annual seasonality, custom additive effects and time-varying effects remain gated. No public category attribution is provided.