Budget allocator: how marketing budget allocation works with response curves

A budget allocator moves money to wherever the next dollar buys the most. The maths is the easy part; the hard part is knowing which curves and constraints to trust.

Mike Taylor By Mike Taylor · Updated · 15 min read

A budget allocator is the part of a marketing mix model that takes each channel’s estimated response curve, a total budget and your constraints, and finds the split that maximizes total incremental revenue (or conversions). It works on one principle: keep moving money from the channel where the next dollar returns least to the channel where it returns most, until those marginal returns are equal or a constraint gets in the way. That’s all marketing budget allocation really is once you have curves, and it’s why the allocator is usually the screen that executives want to see first and the screen analysts trust least.

I’ve built marketing mix models for companies like Monzo, Booking.com and The AA, and in almost every project the allocator output was the moment the conversation got real. A decomposition chart is interesting, but a table that says “take $40,000 out of search and put it into video” is something people have to act on, argue about, or explain to finance. The maths behind that table is surprisingly simple, while the judgment about whether to believe it is not, and most of this page is about the second part. I’ll show you how the allocation works, how Robyn and Meridian implement it, and where it tends to go wrong.

What a budget allocator actually does

Every allocator, whether it’s an open-source library, a vendor dashboard or a spreadsheet, needs four things. It needs a response curve for each channel (the incremental outcome the model expects at each level of spend, usually an adstocked saturation curve), a total budget or a target efficiency, a set of constraints (floors, caps, and limits on how far any channel may move from where it is now), and an objective, such as total incremental revenue, conversions or contribution. Given those, it searches for the spend levels that score best on the objective without breaking a constraint.

What it does not do is learn anything new. The allocator doesn’t look at your data again; it takes the marketing mix model’s curves as given and does arithmetic on them. That’s why I describe it to clients as a calculator sitting on top of the model, not a separate source of insight. If a channel’s curve is wrong, the allocator will confidently recommend the wrong amount for it, and if the curves are uncertain, the allocator will usually ignore that uncertainty unless you make it pay attention.

The output is normally a table of current and proposed spend by channel, the expected change in total response, and some efficiency numbers at each point: average ROAS (incremental revenue divided by spend) and, in better tools, marginal ROAS (the return on the next dollar). Media mix optimization, marketing mix optimization and budget optimization are all names for the same exercise, although vendors sometimes use “optimization” to mean anything from this kind of curve-based search to automated bid management inside a platform.

The equal-marginal-return principle

Imagine you have two channels and a fixed budget, and the next $1,000 in search would bring in $1,600 of incremental revenue while the next $1,000 in social would bring in about $2,500. If you move $1,000 from search to social, you lose roughly $1,600 and gain roughly $2,500, so total revenue goes up by around $900 without spending a cent more. You’d keep doing that until it stops working, and it stops working when the two marginal returns meet, because saturation means search’s next dollar gets more valuable as you take money out and social’s gets less valuable as you add it. At that point no move between the two can raise the total, so you’ve found the optimum.

Those numbers aren’t made up for the example: they’re the marginal returns at current spend in the course’s invented Northstar case, where search revenue follows 1000x/(100+x)1000x/(100+x) and social follows 1200x/(140+x)1200x/(140+x) (in £k), with £150k in search and £120k in social. The slope of the search curve at £150k is 1000×100/2502=1.61000 \times 100 / 250^2 = 1.6, and the slope of social at £120k is 1200×140/2602≈2.491200 \times 140 / 260^2 \approx 2.49. Solving for the split where the two slopes are equal, with the same £270k total, gives roughly £122k in search and £148k in social, which adds about £12.5k of modeled revenue. That’s very close to the course’s “balanced” plan of £120k and £150k, and it’s a long way from the dramatic reshuffles allocators often suggest.

In general terms, if channel ii has response curve Ri(xi)R_i(x_i) and the budget is BB, the allocator is choosing spend levels to maximize ∑iRi(xi)\sum_i R_i(x_i), subject to ∑ixi=B\sum_i x_i = B and a floor and cap for each channel, Li≤xi≤UiL_i \le x_i \le U_i. When the curves are concave, the solution has every unconstrained channel at the same marginal return, Ri′(xi)=λR_i'(x_i) = \lambda, with channels below that level pushed down to their floor and channels above it pushed up to their cap. The common value λ\lambda is the marginal return of the whole budget: it tells you what one more dollar of total budget would be worth if you spread it optimally, which is the number you’d compare with your breakeven ROAS to decide whether the budget itself should be bigger or smaller.

Notice what this principle doesn’t say. It doesn’t say every channel should end up with the same average ROAS, and it doesn’t say the channel with the best average ROAS should get more money. At the Northstar optimum, search still averages about £4.50 of revenue per £1 and social about £4.17, yet both have the same marginal return of roughly £2.03, because what matters is the slope where you are now, not the average over everything you’ve already spent. A channel with a lower average can deserve more money, and a channel with a higher average can deserve less. The diminishing returns brief and the response curves brief cover the shape side of this in more detail.

Try the budget allocator with your own curves

The allocator below uses the same greedy idea that real tools use. Each channel starts at its lowest allowed spend, then the remaining budget is handed out in small steps, each one going to whichever channel would turn it into the most incremental revenue. If your curves come from a model, type in its parameters; if not, the defaults are a reasonable place to build intuition.

Interactive
Allocate a budget across three channels

Edit each channel's response curve and limits, set the budget, and the allocator moves money one small step at a time to wherever the next dollar buys the most incremental revenue.

Search method

Current vs optimized spend ($k)
Response curves, current (gray) and optimized (color) points
Incremental revenue, current
–
Incremental revenue, optimized
–
Gain from reallocating
–
With no limits at all
–
At the optimized allocation
All amounts are $k for one planning period. Curves are invented: incremental revenue = a × x^s / (x^s + k^s), so s = 1 gives the a·x/(k+x) shape used in the course's Northstar case (search and social defaults follow that case; online video is an extra S-shaped channel). The allocator is greedy: it treats the curves as known and certain, which a real model never earns.

With the defaults, the allocator takes about $32k out of search and moves it into social and video, adding around $23k of incremental revenue on the same budget. Search and social end up with the same marginal ROAS (about 2.10), which is the equal-marginal-return principle at work, but video hits its +30 percent change limit while its next dollar would still return about $3.40. That’s exactly what you should expect to see in a real plan. Look at the “Limit” column: when a channel is “at cap” or “at floor”, its marginal ROAS will differ from the free channels, and that gap is the price of the constraint. Then drag the change limit all the way to “no limit” and watch where the money goes. The “With no limits at all” figure is always the biggest number on the screen, and it’s also the one I’d trust least, for reasons I’ll come to.

The search method toggle matters for the S-shaped video channel (shape above 1). “Next step only” compares only the very next step, so a channel whose curve starts flat can look worthless at zero and never get its first dollar, even though a bigger investment would pay. “Look-ahead” compares the average return of every jump from the current position, which is close to how Meridian’s optimizer searches its grid, and it handles S-curves much better. Neither method guarantees the global optimum when curves aren’t concave, which is a warning Meridian’s own code prints.

Why average ROAS gives the wrong answer

The most common budget mistake I see isn’t a modeling error at all: it’s ranking channels by average ROAS and moving money towards the top of the table. Average ROAS answers “how much did all of this channel’s spend return?”, which is useful for judging whether the channel was worth having, but it says nothing directly about what the next dollar will do. A channel can have a high average because its first few thousand dollars were very efficient and still be deep into saturation now, while another channel with a mediocre average can be sitting on the steep part of its curve.

Attribution makes this worse, because platform-reported ROAS isn’t even incremental. If you rank channels by last-click ROAS, branded search usually comes out on top because it’s catching people who were going to buy anyway, and an allocator built on those numbers will happily pour money into the channel with the least real headroom. I cover the gap between reported and incremental numbers on the incrementality page, and the difference between average and marginal returns on the marginal ROAS page. For allocation purposes the rule is short: rank on incremental, and decide on marginal.

Constraints are part of the decision

The way I put it in the course is that “the mathematical optimum must still be deliverable.” An allocator that recommends tripling YouTube spend next month hasn’t helped if the creative team can only produce two new videos a quarter, and a plan that cuts a channel below a contractual minimum is really a contract negotiation that nobody has had yet. Constraints aren’t inconvenient details to bolt on after you show the unconstrained result: they’re part of the problem definition, and they should be in the optimization from the start.

The constraints that come up most often are spend floors (minimum presence in a channel, a sponsorship or upfront TV commitment), caps (inventory or audience size, delivery capacity, a platform that won’t take more money at a sane CPM), change limits relative to current spend, and business rules such as a minimum share of budget for brand activity. It helps to separate hard limits from preferences. A hard limit is something you physically or contractually can’t break; a preference is something the team would like but would trade off if the numbers were compelling. I’d write each constraint down with its owner and the date it could change, so the analyst and the operating team read it the same way.

Constraints also change what the output means. When a channel ends up at its cap with a marginal ROAS well above the others, the allocator is telling you the value of relaxing that cap. That’s often the most useful sentence in the whole analysis, something like “if we could deliver another $20k in video, the model expects it to return about $2 per dollar, against about $1.40 for the next dollar in search.” The same goes for the objective. Maximizing incremental revenue, maximizing contribution after advertising, hitting a target ROAS and hitting a new-customer target can each produce a different plan, and an allocator only answers the objective you give it.

How Robyn’s budget allocator works

Robyn, Meta’s open-source MMM package in R, exposes its allocator as robyn_allocator(), and it runs on one selected model from the Pareto front (so one set of curves, with no uncertainty attached). It offers two scenarios. The max_response scenario holds total spend fixed, either at the historical spend in the date_range you choose or at a total_budget you supply, and finds the split with the highest total response. The target_efficiency scenario takes a target ROAS or CPA (defaulting to 80 percent of the initial ROAS or 120 percent of the initial CPA) and finds how much you could spend, and where, while still hitting it. An older scenario, max_historical_response, is now mapped to max_response, and max_response_expected_spend is deprecated.

Constraints are set per channel with channel_constr_low and channel_constr_up, expressed as multiples of each channel’s historical average spend over the date range, so channel_constr_low = 0.7 means “no less than 70 percent of what this channel usually spends.” In the current code the defaults for max_response are 0.5 and 2, and the documentation warns against exaggerating upper bounds far above historical levels. A neat touch is channel_constr_multiplier (default 3), which reruns the optimization with a wider version of your bounds so the one-pager can show what a bolder plan would have done. Under the hood it uses the nloptr package, with an augmented Lagrangian wrapper around SLSQP by default (optim_algo = "SLSQP_AUGLAG") or MMA as an alternative, and it reports marginal response by evaluating each curve at the optimized spend plus one unit. I walk through running it on the Robyn page.

How Meridian’s budget optimizer works

Google’s Meridian, the Bayesian MMM in Python, does this through BudgetOptimizer in its analysis.optimizer module, and calling optimize() with no arguments runs a fixed-budget scenario at historical spend. You can switch to a flexible budget with fixed_budget=False and either a target_roi (applied to total marketing ROI) or a target_mroi (applied channel by channel, so spend grows until each channel’s marginal ROI falls to the target, unless its spend range stops it). Spend bounds are set with spend_constraint_lower and spend_constraint_upper as fractions of each channel’s current allocation: the defaults are 0.3 for a fixed budget, which means 70 to 130 percent of current spend, and 1 for a flexible budget, meaning anywhere from zero to double.

The search itself is a greedy grid search. Meridian builds a grid of spend levels for each channel, computes the mean incremental outcome at each level using the posterior by default (use_posterior=True), then repeatedly makes the move with the highest return per extra dollar until the budget is used up, much like the look-ahead mode in the widget above. For reach and frequency channels it also uses the optimal frequency by default (use_optimal_frequency=True). The code warns that the method assumes concave response curves and may miss the global optimum if a channel’s Hill curve is S-shaped. So even though Meridian is Bayesian, the optimizer allocates on the posterior mean curve; the uncertainty shows up in what you report around the plan, not in the search. There’s more on fitting and reading it on the Google Meridian page.

Robyn robyn_allocator()Meridian BudgetOptimizer.optimize()
Fixed-budget scenariomax_responsefixed_budget=True (default)
Flexible-budget scenariotarget_efficiency (target ROAS or CPA)target_roi (total) or target_mroi (per channel)
Spend boundschannel_constr_low / _up, multiples of historical average spendspend_constraint_lower / _upper, fractions of current allocation
Default bounds (fixed budget)0.5x to 2x70% to 130%
Search methodnloptr: SLSQP or MMA inside an augmented LagrangianGreedy search over a spend grid
Curves usedOne selected modelPosterior mean incremental outcome

Why optimizers love extrapolating

An optimizer is built to find the best-looking number, and the best-looking number is very often in a region the model knows little about. Suppose a channel has mostly spent between $20k and $80k a week. The model’s curve between those levels is pinned down by data; the curve at $250k is pinned down mostly by the functional form and the priors. If that distant part of the curve happens to bend gently, the allocator will see a channel with lots of headroom and send money there, and nothing in the arithmetic will tell it that this headroom is an assumption rather than an observation. Statisticians call a related effect the optimizer’s curse: when you pick the option with the highest estimated value, you also tend to pick the option whose estimate is most inflated by error.

The practical defense is the step limit. Robyn’s and Meridian’s default bounds both keep the plan within a band around current spend, and I’d usually go tighter than the defaults for anything that’s going to be executed, not just presented. I ask for the observed spend range to be drawn on every planning curve, so the room can see whether the proposed point sits inside the historical data or beyond it, and I’d treat a recommendation that only works in the extrapolated zone as a hypothesis to test, not a plan. The MMM scenario planning page has more on stress-testing a proposal across alternative curve shapes.

Observed spend rangeCurrentOptimizer’s pickEqually plausible curveSpendIncremental revenue
The same spend recommendation can sit inside the evidence or far beyond it. The curve is solid where the channel has historical spend and dashed where it is extrapolated. Source: Vexpower illustration.

The same logic argues for staging. If the model says video is under-invested, a 25 percent increase run as a measured test, with a geo experiment or a holdout to check the result, tells you whether the curve is right before you bet the quarter on it. That test result can then go back into the model as calibration, which tightens the curve exactly where the next decision needs it.

Allocating when the curves are uncertain

A point-estimate allocator treats each curve as a single line, but a Bayesian model gives you thousands of plausible curves, and a frequentist model gives you a confidence band around each one. That uncertainty matters more for allocation than people expect, because a reallocation depends on the difference between two channels’ marginal returns, and the uncertainty in a difference is usually larger than the uncertainty in either estimate. Two channels whose average returns are $3.00 and $2.80 might rank the other way round in a third of the posterior draws.

There are a few ways to make allocation respect that. The simplest is to take the proposed plan and evaluate it against every posterior draw (or every plausible scenario), then report the share of draws in which it beats the current plan and the size of the loss when it doesn’t. A more conservative version optimizes a lower percentile of the outcome instead of the mean, which naturally pulls the plan back towards well-evidenced spend levels. In the Northstar case the course doesn’t give a posterior; it gives three stress scenarios for social’s response (0.7, 1.0 and 1.3 times the central curve), and moving £30k from search to social changes contribution after advertising, at a 40 percent margin, by about −£3.1k, +£4.9k and +£12.9k respectively. That’s a small expected gain with a real chance of a small loss, which is a perfectly good reason to run the shift as a bounded test rather than to refuse it or to go further.

I’d much rather show a range than a point estimate here. A sentence like “the model expects about $12k more revenue, and the plan beats the current split in about 70 percent of plausible scenarios” gives finance something to weigh, whereas “the optimal plan delivers $1.17m” invites false precision. The Bayesian MMM page covers where those posterior draws come from.

Turning the allocation into a budget decision

The allocator’s output is an input to a decision, not the decision. When I review a proposed reallocation, I want to see the current plan, a feasible alternative and the constraints that bind; the expected gain and its range; the marginal ROAS of each channel at the proposed point, compared with breakeven; and a review date with the evidence that will be used to judge it. The course frames the recommendation as one sentence you should be able to complete: “Move ___ from ___ to ___, because ___, subject to ___; review it after ___ using ___ evidence.” If you can’t fill in the “because” with something about the next dollar rather than the past average, the analysis isn’t finished.

If you want to practise this end to end, the Vexpower MMM course has a budget room built on these Northstar curves and finishes with a decision memo, and the old budget optimization brief covers the basic response-curve logic. For presenting the result, the communicating MMM results page has a memo template. The allocator does the arithmetic in milliseconds; the part worth spending your time on is deciding which curves, constraints and ranges deserve to go into it.

Frequently asked questions

What is a budget allocator in marketing mix modeling?

A budget allocator takes the response curve the model estimated for each channel, a total budget and a set of constraints, and searches for the split that maximizes total incremental revenue or conversions. Robyn calls it robyn_allocator and Google's Meridian calls it the BudgetOptimizer. The output is a proposed allocation plus the expected response, which is only as good as the curves behind it.

How do you allocate a marketing budget across channels?

Estimate a response curve for each channel, then move money from channels where the next dollar returns least to channels where it returns most, until the marginal returns are equal or a constraint stops you. Average ROAS is the wrong guide because it describes all the money already spent, not the next dollar. Finish by checking the plan against operating constraints and the uncertainty in each curve.

What is the equal marginal return principle?

With concave response curves and a fixed budget, total response is highest when every channel that is not stuck at a floor or cap has the same marginal return. If one channel's next dollar returns more than another's, moving a dollar between them raises the total, so the optimum is the point where no such move is left.

What is the difference between Robyn's max_response and target_efficiency scenarios?

max_response holds total spend fixed (the historical spend in the chosen date range or a total_budget you supply) and finds the split with the highest total response. target_efficiency instead takes a target ROAS or CPA and finds how much you can spend, and where, while still hitting that target.

Why do budget optimizers recommend such big changes?

An optimizer treats the estimated curves as true, so it pushes money into whichever channel's curve looks least saturated, often far beyond the spend levels the model ever saw. That region is mostly assumption. Change limits, such as Meridian's default of 30 percent either side of current spend for a fixed budget, keep the plan close to the evidence.

References

  1. Robyn source code: robyn_allocator() documentation (allocator.R) Scenario names, channel_constr_low/up defaults, channel_constr_multiplier, SLSQP_AUGLAG and MMA_AUGLAG, marginal response at spend + 1.
  2. Robyn documentation: Analysts' guide to MMM, budget allocator Max response and target efficiency scenarios; nloptr gradient-based optimization.
  3. Meridian user guide: Budget optimization scenarios Fixed budget default, flexible budget with target_roi (total level) or target_mroi (channel level), spend_constraint defaults.
  4. Meridian API reference: BudgetOptimizer
  5. Meridian source code: analysis/optimizer.py Greedy grid search over mean incremental outcome; warning when Hill curves aren't concave; use_posterior and use_optimal_frequency defaults.
  6. Meridian: ROI, mROI and response curves
  7. Vexpower MMM course: Northstar decision case and the lessons Make a budget decision, Set operating constraints, Reason with uncertainty Invented response curves and planning evidence.
Mike Taylor
Mike Taylor

I've built marketing mix models for Monzo Bank, Booking.com, Time Out, The AA and others, advised Google, Appsflyer and Recast on MMM, and teach marketing mix modeling on LinkedIn Learning. Before that I co-founded a 50-person growth marketing agency. I write the Vexpower MMM course.

Keep reading

Learn MMM by doing itThe Vexpower course walks through thirty lessons on one invented brand, with simulators, Python labs and a decision case you can defend.
See the course