How do I optimise my marketing budget for next year?
A visual walk-through of Marketing Mix Modeling (MMM) with Google's Meridian — how a CMO's hardest budget question gets answered with data.
Every CMO eventually faces the same deceptively simple question from the CFO: "If I give you the same budget next year, can you make it work harder — and where should the money go?" It sounds like a spreadsheet exercise. It isn't. Answering it well means untangling which channels actually caused sales versus which merely took credit for them.
That used to be the job of last-click attribution and multi-touch attribution (MTA). But with third-party cookies fading, mobile identifiers restricted (Apple's ATT, Google's Privacy Sandbox), and walled gardens reporting their own homework, user-level tracking no longer sees the whole picture. This is exactly why Marketing Mix Modeling (MMM) — a privacy-safe, aggregate approach that predates cookies — is having a major revival.
Below, I answer the budget question end-to-end for a fictional brand, using Google Meridian, Google's open-source Bayesian MMM library. The numbers are illustrative, but the method is exactly what I'd run in production.
tldr;
By modelling two years of weekly data, our fictional brand discovers that Meta and programmatic display are saturated (extra dollars barely move the needle) while TikTok and YouTube are underfunded (every extra dollar returns well above average). Reallocating ~20% of the media budget — without spending a cent more — is projected to lift incremental revenue by +11%.
Meet "Kirana" — a fictional D2C brand
Kirana Co. is a made-up direct-to-consumer skincare brand selling online across Southeast Asia. It spends about US$6M a year on media across eight channels, from brand-building TV down to lower-funnel search. The CMO wants next year's plan to be defensible to the board.
Six steps, one loop
MMM isn't a one-off report — it's a loop you refresh as new data arrives. Here's the whole journey before we zoom into each step:
flowchart LR A["1 · Frame the
decision"] --> B["2 · Acquire &
shape data"] B --> C["3 · Model the
two forces"] C --> D["4 · Configure in
Meridian"] D --> E["5 · Validate
& trust"] E --> F["6 · Read results
& optimise"] F -.->|"refresh quarterly
with new data"| B F ==> G(["Budget
decision"])
Frame the decision before touching data
The most common way MMM projects fail is skipping this step. Before any modelling, we pin down three things:
- The KPI: what are we optimising? For Kirana, it's weekly online revenue.
- The decision: how to split a fixed ~$6M across eight channels for the next four quarters.
- The constraints: real-world guardrails — e.g. "don't cut brand/TV below 15%", "TikTok can't more than double", contract minimums on out-of-home.
A model that ignores constraints produces answers no one can execute. Naming them now makes the final recommendation actionable.
Step 2Acquire & shape the data
MMM works on aggregate, time-series data — no personal data, no cookies, no user IDs. That's the privacy superpower. We assemble two years of weekly rows combining three ingredients:
| Ingredient | Examples | Why it matters |
|---|---|---|
| Media | Spend & impressions per channel (TV, YouTube, Meta, TikTok, Search, Display, OOH, Affiliates) | The levers we control and want to value |
| The outcome | Weekly online revenue (the KPI) | What we're trying to explain and grow |
| Controls | Price/discounts, seasonality & holidays, competitor activity, macro trend | So credit isn't wrongly given to ads during a sale or festive spike |
Meridian can also model at the geo level (e.g. per country or region) rather than one national line. More geographies means far more signal for the model to learn from — the statistical equivalent of running many parallel experiments. Kirana feeds in six markets.
Rule of thumb
Aim for 2–3 years of weekly data and enough spend variation across channels. If a channel's budget never changes, the model can't learn what happens when it does.
The two forces every MMM must capture
Advertising doesn't convert like a vending machine. Two real-world effects make the spend-to-sales relationship non-linear, and modelling them is what separates a credible MMM from a naïve regression.
flowchart LR S["Media spend
this week"] --> AD["Adstock
(carryover)
a TV ad seen today
still drives sales
next week"] AD --> SAT["Saturation
(diminishing returns)
the 2nd million spent
does less than
the 1st"] SAT --> INC["Incremental
revenue"]
Adstock — advertising has a memory
A brand campaign you see on Monday can still nudge a purchase the following week. Adstock captures this carryover, decaying over time. TV and YouTube have long memories; a search ad's effect is almost immediate.
Saturation — dollars hit a ceiling
The first $100k on a channel reaches the most receptive audience. The next $100k reaches slightly less receptive people, and so on — returns flatten. Saturation (modelled with an S-shaped/Hill curve) is the concept behind budget optimisation: the goal is to spend on each channel only up to the point where the next dollar still pays off.
Step 4Configure the model in Meridian
Meridian is Bayesian, which matters for one very practical reason: it lets us start from what we already believe (a prior) and let the data update it. For a marketer, the priors are just structured common sense — "we're fairly sure search returns more than display" — expressed as numbers.
Here's the essence of the setup (lightly simplified):
# 1 · Tell Meridian which columns are media, KPI, and controls
spec = ModelSpec(
kpi_type="revenue",
media=["tv", "youtube", "meta", "tiktok",
"search", "display", "ooh", "affiliates"],
controls=["price_index", "holiday", "competitor_spend"],
)
Naming the levers (media), the goal (revenue), and the noise to control for.
# 2 · Encode prior beliefs about ROI — and calibrate with real experiments
priors = PriorDistribution(
roi_m="lognormal", # ROI can't be negative
# A recent TikTok lift test measured ~3.4x ROI — anchor the prior to it
tiktok_roi_mean=3.4,
)
mmm = Meridian(input_data=data, model_spec=spec, prior=priors)
mmm.sample_posterior() # the model learns from the data
The single highest-leverage move in MMM: calibrating priors with incrementality experiments (lift tests, geo tests). It grounds the model in measured truth, not just correlation.
Step 5Validate — can we trust it?
A model that hasn't been checked is just an opinion with decimal places. Before presenting anything, we ask:
- Does it fit reality? Overlay the model's predicted revenue on actual revenue. A close fit on held-out weeks (data the model never saw) is the real test — not just the weeks it trained on.
- Are the results stable and sensible? Because it's Bayesian, Meridian gives every number a range, not a single figure. "TikTok ROI is 3.4x ± 0.5" is honest; a lone "3.4x" hides the uncertainty.
- Do the priors and the data agree? If the data violently disagrees with a prior, that's a signal to investigate — often a data-quality issue.
The honesty principle
MMM is directional, not surgical. It's excellent for "shift budget from A to B" decisions and poor for "prove this one ad drove exactly $4,213". Presenting ranges keeps the conversation credible.
Read the results
Now the payoff. Meridian decomposes revenue into a baseline (sales that would happen with no media — brand equity, organic, repeat) and each channel's incremental contribution. For Kirana, media drives ~45% of revenue; the rest is baseline.
But the number that drives the decision isn't average ROI — it's marginal ROI (mROI): what the next dollar returns. A channel can have great average ROI yet be saturated at the margin.
| Channel | Spend share | Avg ROI | Marginal ROI | Verdict |
|---|---|---|---|---|
| Search (non-brand) | 10% | 4.2x | 2.1x | Efficient, near optimal |
| TikTok | 8% | 3.5x | 3.1x | Underfunded — grow |
| YouTube | 12% | 2.8x | 2.4x | Headroom — grow |
| TV | 22% | 1.9x | 1.3x | Hold (long carryover) |
| Meta | 26% | 2.1x | 0.9x | Saturated — trim |
| Display | 12% | 1.1x | 0.5x | Over-invested — cut |
| OOH | 10% | 1.0x | 0.8x | Trim to contract floor |
Illustrative figures for the fictional brand. Note how Meta has a healthy average ROI (2.1x) but a poor marginal ROI (0.9x) — the tell-tale sign of saturation.
The optimisationTurn insight into a budget
Here's the elegant part. Meridian builds a response curve for every channel (spend → revenue, with saturation baked in). The optimiser then reshuffles the budget to equalise marginal ROI across channels — the mathematical definition of an optimal mix — while respecting the constraints from Step 1.
flowchart TD RC["Response curve
per channel"] --> OPT{{"Optimiser:
move $1 to wherever
it earns the most,
repeat"}} CON["Constraints
(floors, caps,
fixed budget)"] --> OPT OPT --> ALLOC["Recommended
allocation"] ALLOC --> CHK{"Marginal ROI
equal across
channels?"} CHK -->|"no"| OPT CHK -->|"yes"| DONE(["Optimal budget"])
# Ask Meridian for the best split of the SAME total budget
optimizer = BudgetOptimizer(mmm)
result = optimizer.optimize(
fixed_budget=True, # same $6M, just allocated smarter
constraints=guardrails, # floors & caps from Step 1
)
result.plot_response_curves() # current vs. recommended spend per channel
One call turns the model into a board-ready recommendation.
Before vs. after — same $6M, smarter split
| Channel | Current | Recommended | Change |
|---|---|---|---|
| TikTok | 8% | 18% | +10 |
| YouTube | 12% | 17% | +5 |
| Search (non-brand) | 10% | 13% | +3 |
| OOH | 10% | 9% | −1 |
| TV | 22% | 18% | −4 |
| Display | 12% | 6% | −6 |
| Meta | 26% | 19% | −7 |
| Total | 100% | 100% | $6M |
So — how do we optimise next year's budget?
For Kirana, the recommendation to the board is concrete and defensible:
- Shift ~$1.2M out of saturated Meta and low-return display into TikTok, YouTube, and non-brand search — the channels with the strongest marginal returns.
- Hold TV despite its lower average ROI: its long adstock keeps the baseline healthy, and cutting it too far would quietly erode future organic sales.
- Expect ~+11% incremental revenue from the same spend — with the model's uncertainty range shown, not hidden.
And if the CFO instead asks "what if I give you 20% more?", the same response curves answer that too: they show exactly where the next dollar should go before returns flatten — turning budget-setting into a scenario conversation rather than a negotiation.
Doing it responsiblyMMM is one leg of a three-legged stool
MMM is powerful but shouldn't be run in isolation. Best-in-class measurement triangulates three methods, each covering the others' blind spots:
flowchart TD MMM["MMM
the big-picture,
privacy-safe view
of every channel"] --> U(["Unified,
trusted
measurement"]) EXP["Incrementality
experiments
ground-truth ROI
to calibrate MMM"] --> U ATT["Attribution /
analytics
fast, granular,
day-to-day tactics"] --> U
Refresh the model quarterly, keep running lift tests to re-calibrate, and treat every output as a range. That discipline is what turns a clever model into a trusted decision tool.
What this case study demonstrates
Translating a hard MarTech topic into a decision a CMO can act on: framing the business question, understanding modern measurement (MMM, incrementality, attribution) in a post-cookie world, working hands-on with Google Meridian, and — most importantly — communicating it clearly to non-technical stakeholders. That bridge between deep technical measurement and executive decision-making is exactly the value I bring to customer success and MarTech teams.