Skip to content

Commit

Permalink
MMM Case Study from PyData Global (#1044)
Browse files Browse the repository at this point in the history
  • Loading branch information
juanitorduz authored Oct 17, 2024
1 parent 429b955 commit 6811395
Show file tree
Hide file tree
Showing 4 changed files with 7,212 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/source/notebooks/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ mmm/mmm_time_varying_media_example
mmm/mmm_components
mmm/mmm_roas
mmm/mmm_time_slice_cross_validation
mmm/mmm_case_study
:::

:::{toctree}
Expand Down
7,174 changes: 7,174 additions & 0 deletions docs/source/notebooks/mmm/mmm_case_study.ipynb

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions pymc_marketing/mmm/mmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,10 @@ def _create_synth_dataset(
) -> pd.DataFrame:
"""Create a synthetic dataset based on the given allocation strategy (Budget) and time granularity.
**Important**: When generating the posterior predicive distribution for the target with the optimized budget,
we are setting the control variables to zero! This is done because in many situations we do not have all the
control variables in the future (e.g. outlier control, special events).
Parameters
----------
df : pd.DataFrame
Expand Down Expand Up @@ -2131,6 +2135,7 @@ def allocate_budget_to_maximize_response(
custom_constraints: dict[str, float] | None = None,
quantile: float = 0.5,
noise_level: float = 0.01,
**minimize_kwargs,
) -> az.InferenceData:
"""Allocate the given budget to maximize the response over a specified time period.
Expand All @@ -2144,6 +2149,10 @@ def allocate_budget_to_maximize_response(
budget, and creates a synthetic dataset based on the optimal allocation. Finally,
it performs posterior predictive sampling on the synthetic dataset.
**Important**: When generating the posterior predicive distribution for the target with the optimized budget,
we are setting the control variables to zero! This is done because in many situations we do not have all the
control variables in the future (e.g. outlier control, special events).
Parameters
----------
budget : float or int
Expand All @@ -2159,6 +2168,10 @@ def allocate_budget_to_maximize_response(
Custom constraints for the optimization. If None, no custom constraints are applied.
quantile : float, optional
The quantile to use for recovering transformation parameters. Default is 0.5.
noise_level : float, optional
The level of noise added to the allocation strategy (by default 1%).
**minimize_kwargs
Additional arguments to pass to the `BudgetOptimizer`.
Returns
-------
Expand All @@ -2170,7 +2183,12 @@ def allocate_budget_to_maximize_response(
ValueError
If the time granularity is not supported.
ValueError
If the noise level is not a float.
"""
if not isinstance(noise_level, float):
raise ValueError("noise_level must be a float")

parameters_mid = self.format_recovered_transformation_parameters(
quantile=quantile
)
Expand All @@ -2188,6 +2206,7 @@ def allocate_budget_to_maximize_response(
total_budget=budget,
budget_bounds=budget_bounds,
custom_constraints=custom_constraints,
**minimize_kwargs,
)

synth_dataset = self._create_synth_dataset(
Expand Down
18 changes: 18 additions & 0 deletions tests/mmm/test_mmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,24 @@ def test_allocate_budget_to_maximize_response(self, mmm_fitted: MMM) -> None:
inference_periods == num_periods
), f"Number of periods in the data {inference_periods} does not match the expected {num_periods}"

def test_allocate_budget_to_maximize_response_bad_noise_level(
self, mmm_fitted: MMM
) -> None:
budget = 2.0
num_periods = 8
time_granularity = "weekly"
budget_bounds = {"channel_1": [0.5, 1.2], "channel_2": [0.5, 1.5]}
noise_level = "bad_noise_level"

with pytest.raises(ValueError, match="noise_level must be a float"):
mmm_fitted.allocate_budget_to_maximize_response(
budget=budget,
time_granularity=time_granularity,
num_periods=num_periods,
budget_bounds=budget_bounds,
noise_level=noise_level,
)

@pytest.mark.parametrize(
argnames="original_scale",
argvalues=[False, True],
Expand Down

0 comments on commit 6811395

Please sign in to comment.