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:
| 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, 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.csv10_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:
| Check | Default | Action |
|---|---|---|
| Zero transformed within-unit variation | exact numerical check | Error |
| Rank-deficient transformed within design | exact matrix-rank check | Error |
| Within-variation share | below 0.05 | Warning |
| VIF | above 20 | Warning |
| Condition number | at least 30 | Warning |
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.
| Output | Axes and units |
|---|---|
channel_contribution | chain, draw, date, unit, channel; scaled target |
channel_contribution_original_scale | same axes; original target |
total_media_contribution_original_scale | chain, 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.