deepcausalmmm.postprocess.optimization

Budget Optimization for Marketing Mix Modeling.

This module provides budget optimization capabilities using response curves from DeepCausalMMM. It uses constrained optimization to find the optimal allocation of marketing budget across channels to maximize predicted response.

The optimizer uses Hill equation saturation curves fitted by ResponseCurveFit to predict channel responses at different spend levels and finds the allocation that maximizes total response subject to budget and business constraints.

Functions

optimize_budget_from_curves(budget, ...[, ...])

Convenience function to optimize budget directly from curve parameters DataFrame.

Classes

BudgetOptimizer(budget, channels, ...[, ...])

Optimize marketing budget allocation using response curves.

OptimizationResult(success, allocation, ...)

Result from budget optimization.

class deepcausalmmm.postprocess.optimization.OptimizationResult(success: bool, allocation: Dict[str, float], predicted_response: float, by_channel: DataFrame, message: str = '', method: str = 'trust-constr')[source]

Result from budget optimization.

success

Whether optimization converged successfully

Type:

bool

allocation

Optimal spend allocation by channel

Type:

Dict[str, float]

predicted_response

Total predicted response at optimal allocation

Type:

float

by_channel

Detailed results by channel with spend, response, and ROI

Type:

pd.DataFrame

message

Optimization status message

Type:

str

method

Optimization method used

Type:

str

Examples

>>> result = optimizer.optimize()
>>> if result.success:
...     print(f"Optimal allocation: {result.allocation}")
...     print(f"Expected response: {result.predicted_response:,.0f}")
...     print(result.by_channel)
success: bool
allocation: Dict[str, float]
predicted_response: float
by_channel: DataFrame
message: str = ''
method: str = 'trust-constr'
__init__(success: bool, allocation: Dict[str, float], predicted_response: float, by_channel: DataFrame, message: str = '', method: str = 'trust-constr') None
class deepcausalmmm.postprocess.optimization.BudgetOptimizer(budget: float, channels: List[str], response_curves: Dict[str, Dict], *, num_weeks: int = 52, method: str = 'trust-constr')[source]

Optimize marketing budget allocation using response curves.

Uses constrained optimization (trust-constr, SLSQP, or differential evolution) with Hill transformation curves from ResponseCurveFit to find optimal spend allocation that maximizes total response subject to business constraints.

Parameters:
  • budget (float) – Total budget to allocate across all channels

  • channels (List[str]) – List of channel names to include in optimization

  • response_curves (Dict[str, Dict]) – Response curve parameters by channel from ResponseCurveFit. Each channel dict should contain: ‘top’, ‘bottom’, ‘saturation’, ‘slope’

  • num_weeks (int, default=52) – Number of weeks for planning horizon (annual by default)

  • method (str, default='trust-constr') – Optimization method: ‘trust-constr’, ‘SLSQP’, ‘differential_evolution’, ‘hybrid’

constraints_df

DataFrame with channel-level constraints (lower, upper bounds)

Type:

pd.DataFrame or None

Examples

>>> # After fitting response curves with ResponseCurveFit
>>> curves = {
...     'TV': {'top': 1000000, 'bottom': 0, 'saturation': 50000, 'slope': 1.5},
...     'Search': {'top': 800000, 'bottom': 0, 'saturation': 30000, 'slope': 2.0},
...     'Social': {'top': 600000, 'bottom': 0, 'saturation': 20000, 'slope': 1.8}
... }
>>>
>>> optimizer = BudgetOptimizer(
...     budget=1000000,
...     channels=['TV', 'Search', 'Social'],
...     response_curves=curves,
...     num_weeks=52
... )
>>>
>>> # Optional: Set channel-specific constraints
>>> optimizer.set_constraints({
...     'TV': {'lower': 50000, 'upper': 500000},
...     'Search': {'lower': 100000, 'upper': 400000}
... })
>>>
>>> # Run optimization
>>> result = optimizer.optimize()
>>>
>>> # View results
>>> if result.success:
...     print("Optimal Allocation:")
...     for channel, spend in result.allocation.items():
...         print(f"  {channel}: ${spend:,.0f}")
...     print(f"\nTotal Response: {result.predicted_response:,.0f}")
...     print(f"\nDetailed Results:\n{result.by_channel}")

Notes

The optimizer maximizes total response using the Hill equation:

\[response = bottom + (top - bottom) * \frac{spend^{slope}}{saturation^{slope} + spend^{slope}}\]

Where: - top: Maximum response (saturation level) - bottom: Minimum response (typically 0) - saturation: Spend level at half-maximum response - slope: Steepness of the response curve

The optimization problem is:

\[ \begin{align}\begin{aligned}\max_{x_1, ..., x_n} \sum_{i=1}^{n} response_i(x_i)\\s.t. \sum_{i=1}^{n} x_i = budget\\lower_i \leq x_i \leq upper_i \quad \forall i\end{aligned}\end{align} \]
__init__(budget: float, channels: List[str], response_curves: Dict[str, Dict], *, num_weeks: int = 52, method: str = 'trust-constr')[source]

Initialize BudgetOptimizer with budget, channels, and response curves.

constraints_df: DataFrame | None
set_constraints(constraints: Dict[str, Dict[str, float]]) None[source]

Set spend constraints for channels.

Parameters:

constraints (Dict[str, Dict[str, float]]) – Channel constraints: {‘channel’: {‘lower’: min_spend, ‘upper’: max_spend}}

Examples

>>> optimizer.set_constraints({
...     'TV': {'lower': 50000, 'upper': 500000},
...     'Search': {'lower': 100000, 'upper': 400000},
...     'Social': {'lower': 25000, 'upper': 300000}
... })

Notes

  • Channels not specified in constraints get default bounds: [0, budget]

  • Upper bounds are automatically capped at total budget

  • If lower > upper, lower is reset to 0

  • Upper bounds cannot be 0 (would make channel unusable)

optimize() OptimizationResult[source]

Run optimization to find optimal budget allocation.

Returns:

Optimization results including allocation, predicted response, and details

Return type:

OptimizationResult

Examples

>>> result = optimizer.optimize()
>>> if result.success:
...     print("Optimization successful!")
...     print(f"Predicted response: {result.predicted_response:,.0f}")
...     for channel, spend in result.allocation.items():
...         roi = result.by_channel[result.by_channel['channel']==channel]['roi'].iloc[0]
...         print(f"{channel}: ${spend:,.0f} (ROI: {roi:.2f})")
... else:
...     print(f"Optimization failed: {result.message}")
compare_scenarios(scenarios: Dict[str, Dict[str, float]]) DataFrame[source]

Compare different budget allocation scenarios.

Parameters:

scenarios (Dict[str, Dict[str, float]]) – Dictionary of scenarios: {‘scenario_name’: {‘channel’: spend, …}}

Returns:

Comparison of scenarios with predicted responses and ROIs

Return type:

pd.DataFrame

Examples

>>> scenarios = {
...     'Current': {'TV': 400000, 'Search': 350000, 'Social': 250000},
...     'Optimized': result.allocation,
...     'Heavy TV': {'TV': 600000, 'Search': 250000, 'Social': 150000}
... }
>>> comparison = optimizer.compare_scenarios(scenarios)
>>> print(comparison)
deepcausalmmm.postprocess.optimization.optimize_budget_from_curves(budget: float, curve_params: DataFrame, *, channel_col: str = 'channel', num_weeks: int = 52, constraints: Dict[str, Dict[str, float]] | None = None, method: str = 'trust-constr') OptimizationResult[source]

Convenience function to optimize budget directly from curve parameters DataFrame.

This function is useful when you have response curve parameters in a DataFrame (e.g., from ResponseCurveFit fitted on multiple channels) and want to quickly run optimization without manually setting up the BudgetOptimizer.

Parameters:
  • budget (float) – Total budget to allocate

  • curve_params (pd.DataFrame) – DataFrame with response curve parameters. Required columns: channel, top, bottom, saturation, slope

  • channel_col (str, default='channel') – Name of the channel column in curve_params

  • num_weeks (int, default=52) – Number of weeks for planning horizon

  • constraints (Dict[str, Dict[str, float]], optional) – Channel-specific constraints: {‘channel’: {‘lower’: min, ‘upper’: max}}

  • method (str, default='trust-constr') – Optimization method

Returns:

Optimization results

Return type:

OptimizationResult

Examples

>>> # After fitting curves for multiple channels
>>> curves_df = pd.DataFrame({
...     'channel': ['TV', 'Search', 'Social'],
...     'top': [1000000, 800000, 600000],
...     'bottom': [0, 0, 0],
...     'saturation': [50000, 30000, 20000],
...     'slope': [1.5, 2.0, 1.8]
... })
>>>
>>> result = optimize_budget_from_curves(
...     budget=1000000,
...     curve_params=curves_df,
...     constraints={'TV': {'lower': 100000, 'upper': 600000}}
... )
>>> print(result.allocation)