Chapter 02 of 10
Carryover and saturation
Adstock then Hill, in that order, normalized so the decay parameter cannot quietly rescale the coefficient it multiplies.
Media enters the model through two transforms applied in a fixed order: carryover first, then diminishing returns. The order is a modelling claim, not a convention. Saturating each week in isolation and then accumulating would understate the concavity a large flight actually runs into, because the flight’s own weeks would never be summed before the curve bent.
carried[g,t,m] = sum_l w[l,m] * spend_scaled[g,t-l,m] / sum_l w[l,m] delta[g,t,m] = hill(carried[g,t,m]) - hill(0)
Adstock, normalized
Weights are divided by their own sum. Without that normalization, raising the decay parameter inflates the whole transformed series, the coefficient shrinks to compensate, and the two become a ridge that NUTS explores badly. Worse for this engine specifically: an ROI prior would stop meaning the same thing at different decay rates, and the entire elicitation story in chapter 03 would be built on a moving unit.
geometric w[l] = alpha ** l delayed_geometric w[l] = alpha ** ((l - theta) ** 2) weibull_cdf w[l] = exp(-((l / scale) ** shape))
Geometric decay is monotone from the first period, which cannot express a brand response that builds for a few weeks before fading. delayed_geometric peaks at lag theta; the Weibull survival form holds the response up before dropping it when shape > 1 and decays fast with a long tail when shape < 1.
source · engine/mmm/transforms.py
Hill saturation
Half-saturation ec is expressed in scaled media units, so ec = 1means the channel is at 50% saturation at its median historical spend. That is what makes the prior in chapter 01 interpretable without knowing the client’s budget. With slope = 1this reduces to the Michaelis–Menten form.
hill(u) = (u + eps) ** s / ((u + eps) ** s + ec ** s)
Subtracting hill(0) matters more than it looks. The epsilon lifts the curve off the floor by around 1e-12, so without the subtraction a channel with zero spend would contribute a tiny positive amount in every geo and period — and those amounts, summed over a five-year panel, are what stop a decomposition from reconciling.
Where the flighting assumption bites