Guide: Performing Budget Optimisation
This guide explains how to use the budget optimisation features within the ammm library.
Overview
Section titled “Overview”Budget optimisation aims to find the allocation of a total marketing budget across different channels that is predicted to maximise the overall contribution (e.g., sales, conversions), based on the fitted MMM and the estimated response curves for each channel. The core principle behind this is to allocate spend to channels where the marginal ROI (mROI)—the return from the next dollar spent—is highest, until the mROIs equalize across channels or budget constraints are met.
Under a fixed total budget, AMMM’s optimiser maximises total expected contribution:
\max_{\{S_i\}} \sum_i C_i(S_i) \quad \text{subject to} \quad \sum_i S_i = BThis is equivalent to maximising overall ROI for that fixed budget (total_contribution / total_spend).
Prerequisites
Section titled “Prerequisites”- A fitted MMM model
- Response curve parameters for each channel (e.g.
(alpha, lam)for thesigmoidcurve)
How AMMM gets response-curve parameters
Section titled “How AMMM gets response-curve parameters”AMMM estimates per-channel response curves via a two-step approximation:
- Compute per-channel contributions from the fitted Bayesian MMM (often using posterior means).
- Fit a simple saturating curve (sigmoid or Michaelis–Menten) to the
(spend, contribution)relationship usingscipy.optimize.curve_fit.
In code, the typical entrypoint is the model mixin method:
sigmoid_params = driver.model.compute_channel_curve_optimization_parameters_original_scale(method="sigmoid")Then optimisation uses these parameters to allocate a fixed total budget.
Runnable Example
Section titled “Runnable Example”This example demonstrates how to run the budget optimiser in a self-contained way.
In practice, you estimate the response curve parameters from a fitted MMM (for example by using the optimisation helper methods on the model).
import core.opt as opt
print("Running a self-contained optimisation example...")
# Channels to allocate budget acrosschannels = ["channel_1", "channel_2", "channel_3"]
# Example sigmoid response parameters for each channel: (alpha, lam)# In practice, you estimate these from a fitted MMM.parameters = { "channel_1": (50_000.0, 1.0e-5), "channel_2": (60_000.0, 8.0e-6), "channel_3": (40_000.0, 1.2e-5),}
total_budget = 100_000.0
# Optional per-channel min/max boundsbudget_ranges = { "channel_1": (0.0, 60_000.0), "channel_2": (0.0, 70_000.0), "channel_3": (0.0, 50_000.0),}
results = opt.budget_allocator( method="sigmoid", total_budget=total_budget, channels=channels, parameters=parameters, budget_ranges=budget_ranges,)
print("Optimisation results:")print(results)
# The output DataFrame shows the estimated contribution and the optimal budget# for each channel, as well as the total.Interpreting the Results
Section titled “Interpreting the Results”The output of the example script will be a DataFrame that looks something like this (values will vary):
estimated_contribution optimal_budgetchannel_1 25000.00 30000.00 channel_2 35000.00 40000.00 … total 150000.00 160000.00
optimal_budget: This column shows the suggested budget allocation after optimisation. Compare this to the historical spend to see the recommended shifts.estimated_contribution: This column shows the estimated contribution from each channel based on the optimal budget.
Key Considerations
Section titled “Key Considerations”- Approximation: The optimisation relies on the accuracy of the fitted parametric response curves (e.g., sigmoid). These are approximations of the true, non-parametric response curves from the Bayesian model.
- Total Budget: The optimisation allocates a fixed total budget. It suggests how to best spend that amount, not whether to increase or decrease the total budget.
- Constraints: If you provide
budget_ranges, the optimizer will respect them. This is useful for incorporating business constraints. - Model Accuracy: The optimisation is only as good as the underlying MMM fit. Ensure your main model diagnostics are satisfactory before trusting the optimization results.
Solver Details
Section titled “Solver Details”Single-period optimisation uses SciPy’s constrained optimiser:
scipy.optimize.minimize(..., method="SLSQP")- equality constraint:
Σ S_i = B - bounds: per-channel min/max
For multi-period planning, see the dedicated guide: Multi-Period Optimization.