Abacus Driver: Budget Optimisation (opt.py)¶
This module provides high-level functions to perform marketing budget optimisation using a fitted Abacus Marketing Mix Model (MMM). It leverages the model’s understanding of channel response curves to suggest an optimal allocation of a total budget across channels to maximise expected contribution.
Functions
optimize_marketing_budget
def optimize_marketing_budget(
model,
data: pd.DataFrame,
config: dict,
results_dir: str,
total_budget: Optional[float] = None,
frequency: str = 'M',
n_time_periods: int = 1
) -> None:
Orchestrates the marketing budget optimisation process.
This function performs the following steps:
Aggregates historical spending data (
data) based on the specifiedfrequency.Calculates the
total_budgetbased on historical means if not explicitly provided.Determines dynamic lower and upper bounds for each channel’s budget based on historical spend patterns (e.g., +/- 20% of the mean spend over
n_time_periods).Estimates the parameters of the channel saturation curves (currently using the ‘sigmoid’ method via
model.compute_channel_curve_optimization_parameters_original_scale).Calls the model’s optimisation method (
model.optimize_channel_budget_for_maximum_contribution) to find the budget allocation that maximises total expected contribution, subject to thetotal_budgetand calculatedbudget_bounds.Calculates the expected contribution from the initial (historical mean) budget allocation for comparison.
Generates and saves a plot comparing the initial and optimal budget allocations and their corresponding contributions using
model.plot_budget_scenarios.Saves the numerical results (initial vs. optimal budget and contribution per channel, plus totals and estimated lift) to a CSV file (
optimization_results.csv) in theresults_dir.
Parameters:
model: An instance of a fitted Abacus MMM model (e.g.,abacus.core.mmm_model.DelayedSaturatedMMM) possessing the required optimisation and plotting methods.data(pd.DataFrame): DataFrame containing historical marketing data, including a date column (specified implicitly or viaconfig) and spend columns for each channel (specified inconfig['media']).config(dict): Configuration dictionary, expected to contain a'media'key which is a list of dictionaries, each specifying a'spend_col'for a channel.results_dir(str): Path to the directory where the output plot (budget_optimisation.png) and CSV file (optimization_results.csv) will be saved.total_budget(Optional[float], optional): The total budget to allocate across channels. IfNone(default), the total budget is calculated based on the mean historical spend indataaggregated byfrequencyovern_time_periods.frequency(str, optional): The frequency for aggregating historical spend data (‘W’ for weekly, ‘M’ for monthly, ‘Q’ for quarterly). Defaults to'M'.n_time_periods(int, optional): The number of future time periods the optimisation pertains to (used for scaling the total budget and bounds). Defaults to1.
Returns:
None: The function saves output files to disk and prints confirmation messages.
Notes:
This function relies heavily on methods assumed to be present in the passed
modelobject, specifically those related to estimating saturation parameters (compute_channel_curve_optimization_parameters_original_scale), performing the budget optimisation (optimize_channel_budget_for_maximum_contribution), and plotting the results (plot_budget_scenarios). These methods are typically inherited via mixins likeOptimizationMixinandScenarioPlottingMixin.The budget bounds are currently hardcoded to +/- 20% of the historical mean spend.
The saturation curve estimation currently defaults to the ‘sigmoid’ method.
get_lower_and_upper_bounds
def get_lower_and_upper_bounds(
media: pd.DataFrame,
n_time_periods: int,
lower_pct: np.ndarray,
upper_pct: np.ndarray
) -> tuple[np.ndarray, np.ndarray]:
Calculates lower and upper budget bounds for each channel based on historical spend and percentage deviations.
Parameters:
media(pd.DataFrame): DataFrame of historical media spend (channels as columns, time as index).n_time_periods(int): Number of future periods to scale the bounds for.lower_pct(np.ndarray): Array of percentages (0 to 1) defining the maximum decrease from the mean spend for the lower bound (per channel).upper_pct(np.ndarray): Array of percentages (0 to 1) defining the maximum increase from the mean spend for the upper bound (per channel).
Returns:
tuple[np.ndarray, np.ndarray]: A tuple containing two NumPy arrays: (lower_bounds,upper_bounds) for each channel over the specifiedn_time_periods.
calculate_total_budget
def calculate_total_budget(
mean_costs: Union[np.ndarray, pd.Series],
n_time_periods: int
) -> float:
Calculates the total budget based on mean channel costs and the number of time periods.
Parameters:
mean_costs(np.ndarray | pd.Series): Array or Series containing the mean cost per time period for each channel.n_time_periods(int): The number of time periods to calculate the total budget for.
Returns:
float: The calculated total budget.