additive_effect#
Additive effects for the multidimensional Marketing Mix Model.
Example of a custom additive effect#
Custom negative-effect component (added as a MuEffect)
import numpy as np
import pandas as pd
import pymc as pm
from pymc_extras.prior import create_dim_handler
# A simple custom effect that penalizes certain dates/segments with a
# negative-only coefficient. This is not a "control" in the MMM sense, so
# give it a different name/prefix to avoid clashing with built-in controls.
class PenaltyEffect:
'''Example MuEffect that applies a negative coefficient to a user-specified pattern.
'''
def __init__(self, name: str, penalty_provider):
self.name = name
self.penalty_provider = penalty_provider
def create_data(self, mmm):
# Produce penalty values aligned with model dates (and optional extra dims)
dates = pd.to_datetime(mmm.model.coords["date"])
penalty = self.penalty_provider(dates)
pm.Data(f"{self.name}_penalty", penalty, dims=("date", *mmm.dims))
def create_effect(self, mmm):
model = mmm.model
penalty = model[f"{self.name}_penalty"] # dims: (date, *mmm.dims)
# Negative-only coefficient per extra dims, broadcast over date
coef = pm.TruncatedNormal(f"{self.name}_coef", mu=-0.5, sigma=-0.05, lower=-1.0, upper=0.0, dims=mmm.dims)
dim_handler = create_dim_handler(("date", *mmm.dims))
effect = pm.Deterministic(
f"{self.name}_effect_contribution",
dim_handler(coef, mmm.dims) * penalty,
dims=("date", *mmm.dims),
)
return effect # Must have dims ("date", *mmm.dims)
def set_data(self, mmm, model, X):
# Update to future dates during posterior predictive
dates = pd.to_datetime(model.coords["date"])
penalty = self.penalty_provider(dates)
pm.set_data({f"{self.name}_penalty": penalty}, model=model)
Usage
-----
# Example weekend penalty (Sat/Sun = 1, else 0), applied per geo if present
weekend_penalty = PenaltyEffect(
name="brand_penalty",
penalty_provider=lambda dates: pd.Series(dates)
.dt.dayofweek.isin([5, 6])
.astype(float)
.to_numpy()[:, None] # if mmm.dims == ("geo",), broadcast over geo
)
# Build your MMM as usual (with channels, etc.), then add the effect before build/fit:
# mmm = MMM(...)
# mmm.mu_effects.append(weekend_penalty)
# mmm.build_model(X, y)
# mmm.fit(X, y, ...)
# At prediction time, the effect updates itself via set_data.
How it works#
Mu effects follow a simple protocol:
create_data(mmm)
,create_effect(mmm)
, andset_data(mmm, model, X)
.During
MMM.build_model(...)
, each effect’screate_data
is called first to introduce any neededpm.Data
. Thencreate_effect
must return a tensor with dims (“date”, *mmm.dims) that is added additively to the model mean.During posterior predictive,
set_data
is called with the cloned PyMC model and the new coordinates; update anypm.Data
you created usingpm.set_data
.
Tips for custom components#
Use unique variable prefixes to avoid name clashes with built-in pieces like controls. Do not call your component “control”; choose a distinct name/prefix.
Follow the patterns used by the provided effects in this module (e.g.,
FourierEffect
,LinearTrendEffect
,EventAdditiveEffect
): - Increate_data
, derive and register any required inputs into the model. - Increate_effect
, construct PyTensor expressions and return a contributionwith dims (“date”, *mmm.dims). If you need broadcasting, use
pymc_extras.prior.create_dim_handler
as shown above.In
set_data
, update the data variables when dates/dims change.
Classes
|
Event effect class for the MMM. |
|
Fourier seasonality additive effect for MMM. |
|
Wrapper for LinearTrend to use with MMM's MuEffect protocol. |
|
Protocol MMM. |
|
Protocol for arbitrary additive mu effect. |