Incrementality.compute_incremental_contribution#

Incrementality.compute_incremental_contribution(frequency, start_date=None, end_date=None, include_carryover=True, num_samples=None, random_state=None, counterfactual_spend_factor=0.0, central_tendency='median')[source]#

Compute incremental channel contributions using counterfactual analysis.

Core incrementality function. Compares the model’s prediction under actual spend with its prediction under a counterfactual spend scenario, properly accounting for adstock carryover. Results are always returned in the original scale of the target variable, with the model’s link function applied – see the module docstring for the full mathematical formulation and for what per-channel increments do and do not mean under a multiplicative (log-link) model.

Parameters:
frequency{“original”, “weekly”, “monthly”, “quarterly”, “yearly”, “all_time”}

Time aggregation frequency. "original" uses data’s native frequency. "all_time" returns a single value across the entire period.

start_datestr or pd.Timestamp, optional

Start date for evaluation window. If None, uses start of fitted data.

end_datestr or pd.Timestamp, optional

End date for evaluation window. If None, uses end of fitted data.

include_carryoverbool, default=True

Include adstock carryover effects. When True, prepends l_max observations before the period to capture historical effects carrying into the evaluation period, and extends the evaluation window by l_max periods to capture trailing adstock effects from spend during the period.

num_samplesint or None, optional

Number of posterior samples to use. If None, all samples are used. If less than total available (chain × draw), a random subset is drawn.

random_stateRandomState or Generator or None, optional

Random state for reproducible subsampling. Only used when num_samples is not None.

counterfactual_spend_factorfloat, default=0.0

Multiplicative factor α applied to channel spend in the counterfactual scenario.

  • 0.0 (default): Zeroes out channel spend → total incremental contribution (classic on/off counterfactual).

  • 1.01: Scales spend to 101% of actual → marginal incremental contribution (response to a 1 % spend increase).

  • Any value ≥ 0: Supported. Values > 1 measure the upside of more spend; values in (0, 1) measure the cost of less spend.

Note that α intervenes on spend, which is not the same as removing a channel’s effect. The two coincide only when the saturation maps zero spend to zero contribution (as LogSaturation does). With a saturation whose value at zero spend is non-zero, or with time_varying_media scaling the contribution, 0.0 still leaves a residual channel effect in the response. To remove a component’s effect outright, use compute_counterfactual_contributions_dataset().

central_tendency{“median”, “mean”}, default=”median”

Central tendency of the predictions being differenced. Only meaningful for non-linear links: under link="log" the model’s response-scale prediction \(\exp(\mu)\,s\) is the median of the LogNormal likelihood, and "mean" rescales it by \(\exp(\sigma^2 / 2)\) to give an increment on the conditional-mean scale. Ignored under link="identity", where the Normal mean and median coincide.

Returns:
xr.DataArray

Incremental contributions in original scale with dimensions:

  • (chain, draw, date, channel, *custom_dims) when frequency != "all_time"

  • (chain, draw, channel, *custom_dims) when frequency == "all_time"

For models with hierarchical dimensions like dims=("country",), output has shape (chain, draw, date, channel, country).

Sign convention: The result is always Y(perturbed) Y(actual) when α > 1 and Y(actual) Y(counterfactual) when α < 1 (including 0). Both total and marginal incrementality are therefore positive for channels with a positive effect.

Estimand: each channel’s number is a unilateral intervention – what changes when that channel’s spend is scaled by α, with the others at actual spend. At α = 0 that is the leave-one-out question. The numbers sum to the total only when the response is additive in the channels; see compute_joint_incremental_contribution().

Raises:
ValueError

If frequency is invalid, period dates are outside fitted data range, counterfactual_spend_factor is negative, or central_tendency is not one of {"median", "mean"}. Also raised if a mu_effect declares fewer carryover lags, or a narrower evaluation_mode, than a spend counterfactual was measured to need (a declaration narrower than what was measured is refused rather than silently overridden); if the model produces non-finite predictions (NaN or infinity) for some posterior draw, which usually means a transform is dividing zero by zero; or if a post-fit mutation of an auxiliary date-indexed input is detected (MMM.sample_posterior_predictive(..., clone_model=False) or a direct pm.set_data(...) call after fitting). See spend_reach for the full story on each.

NotImplementedError

If the model’s link function has no IncrementalReducer, or a mu_effect that depends on channel spend has not opted in via incrementality_spec(). Also raised if the accounted nodes (channel_contribution plus the resolved effects) do not reproduce the full move in the linear predictor, which means some path from spend to the response is unattributed; see assert_increment_is_complete().

Warns:
UserWarning

If a spend counterfactual’s reach could not be measured because no interior date could be probed, the evaluation falls back to evaluating every period on the full date axis instead of a window, which is correct but slower, and the completeness check above is skipped for lack of anything to compare it against. See measure().

See also

compute_joint_incremental_contribution

All channels perturbed together, for a total rather than a split.

References

Google MMM Paper: https://storage.googleapis.com/gweb-research2023-media/pubtools/3806.pdf

Examples

Compute quarterly incremental contributions:

incremental = mmm.incrementality.compute_incremental_contribution(
    frequency="quarterly",
    start_date="2024-01-01",
    end_date="2024-12-31",
)

Mean contribution per channel per quarter:

incremental.mean(dim=["chain", "draw"])

Total annual contribution (all_time):

annual = mmm.incrementality.compute_incremental_contribution(
    frequency="all_time",
    start_date="2024-01-01",
    end_date="2024-12-31",
)

Quarterly marginal incrementality (1 % spend increase):

marginal = mmm.incrementality.compute_incremental_contribution(
    frequency="quarterly",
    counterfactual_spend_factor=1.01,
)