Lab

Media mix model & budget optimizer

A media mix model fitted to two years of weekly spend and revenue reads each channel's response curve and shows where the next euro of media budget earns most. Change the budget or the limits and the plan recomputes.

Adjust inputs ↓

Optimizer

Where the next euro should go

–As % of weekly revenue
–Media ROI, now → plan

NowPlan
ChannelNow / wkPlan / wkChangeNext € returns (at plan)

Model fit

Does the model track revenue?

Curves are chosen on the first 91 weeks. The last 13 weeks are held out and predicted blind.

–R² (train)
–MAPE (train)
–Revenue from media
Holdout weeks

Decomposition

What each channel added

Incremental revenue per channel over the full period. The plan is built on marginal ROI, what the next euro returns at today's spend.

–Baseline revenue
–Media-driven revenue
–Media ROI
ROI = incremental revenue per euro of spend. Half-sat = weekly spend where the curve reaches half its ceiling.
ChannelAvg / wkRevenueROIMarginal ROIDecayHalf-sat

Response curves

Where each channel flattens

Parameter recovery

Does the model recover the known curves?

The synthetic data comes from known curves, so this table checks the fit against the answer. Real data has no answer key, so calibrate the model with geo tests.

ChannelTrue ROIFitted ROIErrorDecay true / fitWithin 20%

    How it works
    Method
    Regression MMM with geometric adstock, Hill saturation, trend and Fourier seasonality. Budget plan by marginal-return water-filling.
    Built from
    My incrementality and measurement work: mix models, geo tests and budget planning for paid channels.
    Data
    Synthetic, generated from a seeded model with known curves. Not client data. Or your own CSV, which never leaves the page.
    Runtime
    Plain JavaScript in your browser. No libraries, no server.

    The model

    Each channel's weekly spend goes through two transforms. Adstock carries part of this week's effect into later weeks (a decay of 0.6 means 60% carries over). The Hill curve then makes returns shrink as spend grows. Revenue is modelled as baseline (intercept, linear trend, three yearly Fourier terms) plus the transformed channels.

    For each channel the fit searches 216 curve shapes (9 decays × 8 half-saturation points × 3 steepness values), one channel at a time, until nothing improves. Every candidate gets a ridge regression with media coefficients held non-negative. Weak priors on the curve shape stop the search chasing noise. Curves are chosen on 91 weeks; the last 13 are the holdout. Coefficients are then refit on all weeks for the decomposition and the plan.

    The optimizer uses each channel's steady-state curve. It adds budget in small steps to whichever channel returns most for the next euro, then swaps budget between channels while that still pays. An exact grid search runs alongside, because S-shaped curves can trap a greedy step. On the same budget, the plan never scores worse than today's split.

    In production

    Weekly spend, revenue and controls (price, promos, holidays) come from the warehouse, modelled in dbt on BigQuery or Snowflake. The model runs as a Bayesian MMM such as PyMC-Marketing or Meridian, which puts a credible interval on every estimate. Geo-lift results become priors for the channels they cover (see the geo-lift demo). The plan lands in the planning sheet with ranges, and gets re-tested with the next experiment. The sentence above the plan is a template filled from the model output.

    Limits

    • It's correlational. If spend follows demand (search in Q4), the model can mistake demand for media effect.
    • It needs spend variation. A channel that never moves, or moves with another, can't be separated. The Collinear scenario shows this.
    • Steady-state curves ignore flighting effects: a burst can beat the same budget spread evenly.
    • Plans far outside the observed spend range are extrapolation. Keep the per-channel limit tight, and calibrate with experiments.