Extend the model with effects
Use the existing effect interfaces when ordinary controls and seasonal settings cannot express the required linear-predictor contribution. Custom structure requires a statistical rationale as well as a graph that compiles.
model.add_mu_effect(effect) registers a MuEffect before graph construction.
An effect defines data creation, its contribution and prediction-time data
updates. Built-in interfaces include ControlMuEffect, MediaMuEffect,
FourierEffect, LinearTrendEffect and EventAdditiveEffect.
model.add_events provides a separate event-registration interface. Check each
constructor rather than borrowing an AMMM3 effect signature.
In YAML, effects holds class specifications and extra_vars selects auxiliary
columns to retain as xarray variables. Without extra_vars, an unused DataFrame
column is not automatically retained in the canonical training dataset.
Counterfactual completeness
A media-dependent effect can carry part of the response to changing
channel_data. For incrementality, such an effect must declare an
IncrementalitySpec; otherwise the operation raises rather than silently
omitting the effect. Effects unrelated to media remain in the baseline.
Channel mixing and temporal reach can require joint rather than independent
channel contrasts. A declaration must describe the actual dependency, not merely
suppress validation.
The default optimiser response may also omit media response routed through a
custom effect. Select and verify the complete response variable explicitly.
Effects must implement their own serialisation and any supplementary idata
groups needed to reconstruct them. Test save/load and prediction with changed
inputs, not just in-sample graph construction.
FE and CRE reject custom mean effects. For a fully custom PyMC model, reuse transformations where appropriate, but do not assume MMM’s automatic scaling, persistence, plotting or calibration contracts apply to that new graph.
Retain and serialise an auxiliary control effect
Use a built-in ControlMuEffect when a named auxiliary variable needs its own
coefficient and persisted effect definition. This standalone example retains
promotion_z explicitly, builds the graph and checks effect serialisation; it
does not fit or claim the promotion coefficient is causal.
import numpy as np
import pandas as pd
from pymc_extras.prior import Prior
from ammm.mmm import MMM, GeometricAdstock, LogisticSaturation, ControlMuEffect
from ammm.mmm.data_conversion import to_mmm_dataset
frame = pd.DataFrame({
"date": pd.date_range("2025-01-06", periods=12, freq="W-MON"),
"tv": np.linspace(10, 40, 12),
"promotion_z": [-1.0, 1.0] * 6,
"revenue": np.linspace(50, 70, 12),
})
training = to_mmm_dataset(
frame, date_column="date", channel_columns=["tv"],
target_column="revenue", extra_vars=["promotion_z"],
)
effect = ControlMuEffect(
data_vars=["promotion_z"], prefix="promotion",
prior=Prior("Normal", mu=0, sigma=0.2),
)
rebuilt_effect = ControlMuEffect.from_dict(effect.to_dict())
effect_model = MMM(
date_column="date", target_column="revenue", channel_columns=["tv"],
adstock=GeometricAdstock(l_max=1), saturation=LogisticSaturation(),
)
effect_model.add_mu_effect(rebuilt_effect)
effect_model.build_model(training)
assert "promotion_effect_contribution" in effect_model.model.named_vars
The effect contributes in scaled target units and updates its named data at
prediction time; future canonical datasets must retain promotion_z on the
required date/panel coordinates. Its prior and data-variable declaration are
serialised by to_dict/from_dict
(src/ammm/mmm/additive_effect.py:433, src/ammm/mmm/additive_effect.py:579).
For a fitted model, use the standard save/load route and compare predictions on
changed inputs after loading; the graph-only check above does not exercise
fitted-state persistence (src/ammm/mmm/mmm.py:925).
In a full YAML configuration whose input has the same columns, use:
extra_vars: [promotion_z]
effects:
- class: ammm.mmm.ControlMuEffect
kwargs:
data_vars: [promotion_z]
prefix: promotion
prior:
class: pymc_extras.prior.Prior
args: [Normal]
kwargs: {mu: 0, sigma: 0.2}
The builder converts those extra columns to xarray variables and attaches the
effect before graph construction (src/ammm/mmm/builders/yaml.py:172).
Do not also include promotion_z in control_columns unless you deliberately
want two contributions for the same input and can identify them separately.
Implementation reference at 7cb7f20: src/ammm/mmm/additive_effect.py:341, src/ammm/mmm/spend_reach.py:135.