Adstock and Saturation

PanelMMM requires one adstock transform and one saturation transform. Abacus applies them inside the model graph rather than as a fixed preprocessing step.

For the econometrics framing of these transforms, see Adstock and Saturation for Econometricians.

How the transform path works

Abacus combines the two transforms through forward_pass(...):

  • if adstock_first=True, the order is adstock then saturation
  • if adstock_first=False, the order is saturation then adstock

The transformed result becomes channel_contribution on the model scale.

Adstock options

PanelMMM accepts any AdstockTransformation. The built-in options include:

Class Key parameter priors by default Notes
GeometricAdstock alpha ~ Beta(1, 3) Standard geometric carryover
BinomialAdstock alpha ~ Beta(1, 3) Alternative finite-lag carryover
DelayedAdstock alpha ~ Beta(1, 3), theta ~ HalfNormal(1) Allows a delayed peak
WeibullPDFAdstock lam ~ Gamma(mu=2, sigma=1), k ~ Gamma(mu=3, sigma=1) Flexible PDF-shaped carryover
WeibullCDFAdstock lam ~ Gamma(mu=2, sigma=1), k ~ Gamma(mu=3, sigma=1) Flexible CDF-shaped carryover

All adstock transforms also take:

  • l_max: maximum lag
  • normalize: whether the carryover weights are normalised
  • mode: convolution mode

Saturation options

PanelMMM accepts any SaturationTransformation. Common built-ins include:

Class Key parameter priors by default Notes
LogisticSaturation lam ~ Gamma(alpha=3, beta=1), beta ~ HalfNormal(2) Default retained choice
MichaelisMentenSaturation alpha ~ Gamma(mu=2, sigma=1), lam ~ HalfNormal(1) Common diminishing-returns form
HillSaturation slope ~ HalfNormal(1.5), kappa ~ HalfNormal(1.5), beta ~ HalfNormal(1.5) Flexible Hill curve
HillSaturationSigmoid sigma ~ HalfNormal(1.5), beta ~ HalfNormal(1.5), lam ~ HalfNormal(1.5) Sigmoid Hill variant
RootSaturation alpha ~ Beta(alpha=1, beta=2), beta ~ Gamma(mu=1, sigma=1) Square-root style curvature
TanhSaturation b ~ HalfNormal(1), c ~ HalfNormal(1) Hyperbolic tangent form
TanhSaturationBaselined x0, gain, r, beta all HalfNormal(1) Baselined tanh form

Default prior dims

When you pass a transform to PanelMMM, Abacus assigns default prior dims for any transform prior that does not already have explicit dims:

  • adstock priors default to (*dims, "channel")
  • saturation priors default to (*dims, "channel")

If you want a different structure, set the prior dims explicitly on the transform.

Configure transforms in Python

from abacus.mmm import GeometricAdstock, LogisticSaturation
from abacus.mmm.panel import PanelMMM

mmm = PanelMMM(
    date_column="date",
    channel_columns=["tv", "search"],
    target_column="sales",
    adstock=GeometricAdstock(l_max=8),
    saturation=LogisticSaturation(),
    adstock_first=True,
)

Example with more customised transforms:

from pymc_extras.prior import Prior

from abacus.mmm import MichaelisMentenSaturation, WeibullCDFAdstock

adstock = WeibullCDFAdstock(
    l_max=12,
    priors={
        "lam": Prior("Gamma", mu=2, sigma=1, dims=("geo", "channel")),
        "k": Prior("Gamma", mu=3, sigma=1, dims=("geo", "channel")),
    },
)

saturation = MichaelisMentenSaturation(
    priors={
        "alpha": Prior("Gamma", mu=2, sigma=1, dims=("geo", "channel")),
        "lam": Prior("HalfNormal", sigma=1, dims="geo"),
    }
)

Configure transforms in YAML

data:
  date_column: date

target:
  column: sales
  type: revenue

media:
  channels: [tv, search]
  adstock:
    type: geometric
    l_max: 8
  saturation:
    type: logistic

Override transform priors through priors

Transform priors also appear in model_config under prefixed variable names. For example:

  • adstock_alpha
  • adstock_lam
  • adstock_k
  • saturation_lam
  • saturation_beta

That means you can override transform priors centrally through the top-level priors if you prefer. See Priors and Configuration.

Choose the composition order

adstock_first is part of the model specification, not a plotting choice.

The current public YAML schema does not expose adstock_first; it uses the library default. If you need to change the composition order, use the Python API.

Use adstock_first=True when you want the model to interpret carryover before diminishing returns. Use False when you want each period’s spend to saturate before the carryover step.

The code path is explicit:

  • True -> saturation(adstock(x))
  • False -> adstock(saturation(x))

Common pitfalls

  • Forgetting that l_max is required for adstock classes
  • Assuming dims automatically change transform priors even when you have already set explicit incompatible dims on the transform
  • Using adstock_first=False without a substantive reason
  • Treating transform priors as if they were on original business units rather than the model scale

Next steps