sensitivity_analysis#

Counterfactual sweeps for Marketing Mix Models (MMM).

Quick start#

SensitivityAnalysis can work with any PyMC model whose response depends on an input pm.Data tensor. The functional relationship between inputs and the response can be arbitrary (e.g., adstock, saturation, splines, custom PyTensor ops). You only need to provide:

  • the name of the input data variable (var_input, e.g., "channel_data"),

  • the name of the deterministic/response variable you want to analyze (var_names, e.g., "channel_contribution"),

  • a grid of sweep_values, and the sweep type.

Example#

The example below uses an arbitrary saturation transformation, but any PyTensor-compatible transformation will work the same way.

import numpy as np
import pymc as pm
from pymc_marketing.mmm.sensitivity_analysis import SensitivityAnalysis


def saturation(x, alpha, lam):
    return (alpha * x) / (x + lam)


coords = {"date": np.arange(52), "channel": list("abcde")}
with pm.Model(coords=coords) as m:
    X = pm.Data(
        "channel_data",
        np.random.Gamma(13, 12, size=(52, 5)),
        dims=("date", "channel"),
    )
    alpha = pm.Gamma("alpha", 1, 1, dims="channel")
    lam = pm.Gamma("lam", 1, 1, dims="channel")
    contrib = pm.Deterministic(
        "channel_contribution", saturation(X, alpha, lam), dims=("date", "channel")
    )
    mu = pm.Normal("intercept", 0.0, 1.0) + contrib.sum(axis=-1)
    pm.Normal("likelihood", mu=mu, sigma=1.0, dims=("date",))
    idata = pm.sample(1000, tune=500, chains=2, target_accept=0.95, random_seed=42)

sa = SensitivityAnalysis(m, idata)
sweeps = np.linspace(0.2, 3.0, 8)
# Jacobian-based marginal effects with respect to the input; shape: (sample, sweep, channel)
result = sa.run_sweep(
    sweep_values=sweeps,
    var_input="channel_data",
    var_names="channel_contribution",
    sweep_type="multiplicative",
)
# Optional (backwards-compatibility): returns `result` unchanged because `run_sweep`
# already yields marginal effects. Kept to avoid breaking older code.
me = SensitivityAnalysis.compute_marginal_effects(result, sweeps)

Notes#

  • Arbitrary models: As long as the response graph depends (directly or indirectly) on the pm.Data provided via var_input, and you pass the name of the deterministic/response via var_names, the class builds the Jacobian and evaluates it across sweeps automatically.

  • Multi-dimensional inputs: If var_input has dims like (date, country, channel), the output shape is (sample, sweep, country, channel). You can subset with var_names_filter={"country": ["usa"], "channel": ["a", "b"]}.

  • Sweep types: "multiplicative", "additive", and "absolute" are supported.

  • To persist results, pass extend_idata=True to store them under idata.sensitivity_analysis.

Classes

SensitivityAnalysis(pymc_model, idata[, dims])

SensitivityAnalysis class is used to perform counterfactual analysis on MMM's.