Why MMM, and why now
Every model you have built so far - heuristics, Markov, Shapley, GA4 DDA - needs to follow a person across touches. That means a user identity, and in 2026 that identity leaks through Safari, Firefox, ATT and SKAN aggregation. Marketing Mix Modeling throws the person away on purpose. It works on aggregate weekly spend - one row per week per channel, two to three years deep - and asks a top-down question: as spend moved, how did revenue move? No identity means nothing to break. That is why MMM, a technique from the 1960s, came roaring back.
Adstock: an ad keeps working after it runs 7 min live
Run a CTV burst this week and sales lift for weeks. That lag is adstock (carryover): the effect of spend decays across subsequent weeks rather than vanishing when the campaign ends. If you feed raw weekly spend into a regression you assume the effect dies the instant the week closes - which is false for every brand channel. Adstock spreads this week's spend forward before the model ever sees it.
LiveGeometric vs Weibull adstock3 min▶
Two ways to shape the decay, from simple to flexible:
- Geometric decay - one number per channel, theta, the fraction of last week's effect that carries into this week. High theta (0.7) = slow-burning brand media like CTV; low theta (0.1) = fast performance media like paid search. This is the workhorse and what you will code today.
- Weibull adstock - two parameters (shape and scale) that let the peak effect land after week zero and the tail bend how you like. More faithful for channels whose impact builds before it fades, at the cost of harder fitting. Meta Robyn uses Weibull; PyMC-Marketing offers both.
A brand fed raw (un-adstocked) spend into a regression and concluded CTV "did nothing" - because the sales it drove showed up two and three weeks later, credited to whatever ran then. Adding a theta of 0.65 to CTV moved its estimated contribution from near-zero to the third-largest channel. Same data, one transform, opposite decision.
Self-studyWhy carryover is not the same as saturation2 min read▶
Keep these two separate in your head - they are the two independent transforms of MMM and they answer different questions:
- Adstock is about time: where does this week's effect land - now, or spread across the next several weeks?
- Saturation is about amount: for a given week's effect, does doubling the spend double the response? (No - that is Part 2.)
In a real MMM pipeline you apply them in order: adstock first (spread spend over time), then saturation (bend the response). Get the order backwards and the curves fight each other.
Saturation: the tenth dollar buys less than the first 6 min live
No channel scales forever. The first $10k into paid social reaches your hottest prospects; the hundredth reaches people who would have bought anyway or never will. That bend is saturation (diminishing returns), and it is modeled with an S-shaped or concave curve - the Hill function, Michaelis-Menten, or a simple log transform. This curve is not a footnote: it is the budget-response curve, the thing that tells you where the next dollar should go.
LiveHill, Michaelis-Menten, or log?3 min▶
Three common shapes, same idea (response rises then flattens):
| Transform | Shape | When to reach for it |
|---|---|---|
| Hill | Flexible S-curve (half-point + slope) | Default in Robyn & Meridian; can be concave or S-shaped |
| Michaelis-Menten | Concave, one saturation constant | Simple diminishing returns, no S-bend needed |
| Log / power | Always concave | Quick on-ramp; least faithful at the extremes |
The Hill curve wins in practice because its two parameters (a half-saturation point and a slope) let it be gently concave for search and sharply S-shaped for a channel that needs a threshold of spend before anything happens.
Self-studyThe full MMM equation in one line2 min read▶
Putting the pieces together, a channel's contribution to weekly revenue is roughly:
revenue_week = base + Σ_channel [ coef · saturate( adstock( spend ) ) ] + seasonality + noise
Read it right to left: take raw weekly spend, apply adstock (spread over time), apply saturation (bend for diminishing returns), multiply by a learned coefficient, sum across channels, add a baseline and seasonality. Everything today builds toward fitting exactly this - just with a simple linear estimator instead of a full Bayesian one.
Robyn vs Meridian vs PyMC-Marketing 5 min live
You could hand-roll everything (and today you partly will, to understand it), but production MMM runs on one of three tools. The dividing line is point estimates vs Bayesian uncertainty: does the tool hand you one number per channel, or a whole distribution with a credible interval?
LiveThe three tools, and why Bayesian uncertainty matters3 min▶
| Tool | Language / engine | What you get |
|---|---|---|
| Meta Robyn | R · ridge regression + Nevergrad evolutionary search | Hill saturation + Weibull adstock; point estimates, many candidate models |
| Google Meridian | Python · TensorFlow-Probability, Bayesian MCMC | Estimates adstock + saturation inside the model; every parameter has an uncertainty interval; ingests reach & frequency; calibrate with experiment priors |
| PyMC-Marketing | Python · PyMC, Bayesian | Full Bayesian MMM, geometric & Weibull adstock, Hill/logistic saturation, priors you can set |
Robyn gives you a point estimate - "paid social contributed $1.2M". Meridian and PyMC-Marketing give you a distribution - "paid social contributed $1.2M, 90% credible interval $0.9M to $1.5M". For a budget decision that moves millions, the interval is the honest answer. It stops you from reallocating on noise.
Google deprecated LightweightMMM in January 2025 and pointed everyone to Meridian, its Bayesian successor. If you inherit an old LightweightMMM pipeline, the migration target is Meridian - do not build new work on the deprecated library.
Self-studyBayesian priors as the calibration hook2 min read▶
The reason the Bayesian tools matter for this whole course: because every parameter is a distribution with a prior, you can inject outside knowledge as that prior. Ran a geo-lift experiment that measured paid social's true incremental effect? Feed that as a prior on paid social's coefficient and the MMM is now calibrated to a causal result instead of pure correlation. That is exactly the loop Session 9 builds. Robyn's point estimates cannot receive a prior the same way - another reason the field tilted Bayesian.
Prep spend_weekly + apply geometric adstock ★ 8 min · run it
Load Lumen's aggregate MMM table, pivot it to a weekly spend matrix, and write the geometric adstock function by hand. Notice there is not a single customer_id in sight - this is the privacy-resilient part.
See the pivot. wide is a matrix of weeks by channels - the entire input to an MMM. Two to three years of these rows is all it needs. No journeys, no identity resolution, nothing a browser can revoke.
Read the loop. Each week adds this week's spend plus theta times last week's carried effect. That single recurrence is geometric adstock - the whole thing.
Compare the columns. Print CTV next to paid search and watch CTV's adstocked value stay elevated for weeks after a spike while paid search snaps back. That is the theta difference made visible.
Hill saturation + fit a simple MMM ★ 8 min · run it
Apply a Hill saturation transform on top of the adstocked spend, then fit a simple ridge regression of Lumen's weekly revenue on the transformed channels. This is a real (if minimal) MMM.
The Hill function. At low spend the response climbs fast; as spend passes half, each extra dollar returns less. Change slope to 2.0 and the curve becomes S-shaped (a spend threshold before liftoff).
Order matters. Adstock ran first (Build-along 1), saturation second, regression third - the canonical MMM pipeline. Swap the first two and the transforms fight.
Read the coefficients. contrib ranks channels by their fitted contribution to weekly revenue. On Lumen this typically lifts paid social and CTV above where last-touch ever put them - because MMM finally credits the carryover and the demand they create.
A minimal ridge MMM like this is a teaching model, not a board deck. Production MMM adds seasonality controls, price and promo variables, a baseline term, and - critically - uncertainty. But the shape of the argument is identical: transform spend, regress on the KPI, read the contributions. You just built the skeleton the real thing hangs on.
Response curve → a budget-optimization read ★ 7 min · run it
The saturation curve for one channel, turned into the sentence a media planner actually wants: where is the next dollar worth the most? Then the note on why you would graduate this to Meridian or PyMC-Marketing.
Build the curve. Push a grid of hypothetical spends through the exact same Hill transform and coefficient. The output is the response curve you saw in Part 2 - now a real array you can plot.
Find the knee. The marginal (slope) tells you where returns flatten. Below the knee, add budget; past it, you are buying the flat part of the curve - reallocate.
The budget read. Compare current spend to the knee for every channel and you have the first draft of a reallocation: pull from the saturated channels, push into the ones still climbing.
This week ◐ 45 min total
- Sweep the theta. Re-run Build-along 1 for CTV with theta = 0.3, 0.5, 0.7 and plot the adstocked series. Feel how carryover reshapes the input before the model ever sees it.
- Bend the Hill. Re-run Build-along 2 with slope = 0.8, 1.3, and 2.5 for one channel and plot all three response curves on one axis - concave vs S-shaped.
- Read every knee. Extend Build-along 3 to loop over all nine channels and print current-spend-vs-knee for each. Which channels look saturated on Lumen?
- Optional: install
pymc-marketingand fit itsMMMclass on the samespend_weeklydata - your first Bayesian MMM with real credible intervals. Compare its channel ranking to your ridge model.
Three questions before you go 🎯 ◐ 90 seconds
1 · Adstock in an MMM models...
Adstock is about time: this week's spend keeps working (decaying) over following weeks. Geometric adstock uses one theta per channel - high for slow-burn CTV, low for fast paid search.
2 · Saturation (the Hill curve) captures the fact that...
Saturation is about amount. The Hill / Michaelis-Menten / log curve bends over as spend grows, and its slope (the marginal return) is exactly what you optimize a budget against.
3 · You inherit a LightweightMMM pipeline in 2026. The right move is...
Google deprecated LightweightMMM in January 2025 in favour of Meridian, its Bayesian successor - every parameter gets a credible interval and you can calibrate with experiment priors. Do not build new work on the deprecated library.
What this session covers
This session distills the MMM foundations - adstock, saturation, and the modern Bayesian toolchain - into a build-it-yourself first pass on Lumen's aggregate data. Full model internals and vendor docs stay with their official sources.