Response Curves

Use response curves to inspect the fitted media transformations directly.

Abacus exposes posterior saturation and adstock curves through both the fitted model and mmm.summary. For decomposition of realised contributions over time, see Contributions and Decomposition.

Sample saturation curves

Use sample_saturation_curve(...) on a fitted PanelMMM:

saturation_curve = mmm.sample_saturation_curve(
    max_value=1.0,
    num_points=100,
    num_samples=500,
    random_state=42,
    original_scale=True,
)

The returned xarray.DataArray contains:

  • the curve axis x
  • channel
  • any panel dims
  • a posterior sample dimension

original_scale=True converts the curve’s y-values to original target units. It does not convert the x-axis. x remains in scaled channel units.

If you want to choose max_value from original channel units, divide by the relevant value from mmm.data.get_channel_scale().

Sample adstock curves

Use sample_adstock_curve(...) to inspect carryover weights:

adstock_curve = mmm.sample_adstock_curve(
    amount=1.0,
    num_samples=500,
    random_state=42,
)

The returned array contains:

  • time since exposure
  • channel
  • any panel dims
  • a posterior sample dimension

The adstock curve is the fitted decay pattern for an impulse of size amount. It does not use an original_scale option because the returned weights are not target-unit contributions.

Runner-generated direct contribution artefacts

If you use the retained pipeline runner, Stage 60_response_curves also writes a forward-pass direct contribution artefact alongside the saturation and adstock transformation curves:

  • forward_pass_contribution_curve.nc
  • forward_pass_contribution_curve_summary.csv
  • forward_pass_contribution_curve.png

This artefact is different from the saturation-only curve:

  • the saturation-only curve shows the fitted saturation transform itself
  • the forward-pass direct contribution curve runs spend through the full fitted model path, including adstock and saturation

The retained Stage 60 forward-pass plot uses one explicit scenario so the curve is interpretable: it rescales the full observed historical spend path from 0% to 200%, then plots total channel spend against total channel contribution in original units. The marker at 100% highlights the fitted total contribution for the observed historical spend path.

Summarise curves as DataFrames

If you want tabular summaries, use mmm.summary:

saturation_df = mmm.summary.saturation_curves(
    hdi_probs=[0.80, 0.94],
    num_points=100,
    num_samples=500,
    random_state=42,
    original_scale=True,
)

adstock_df = mmm.summary.adstock_curves(
    hdi_probs=[0.94],
    amount=1.0,
    num_samples=500,
    random_state=42,
)

These methods return DataFrames with posterior mean, median, and HDI bound columns.

saturation_curves(...) includes an x column. adstock_curves(...) uses time since exposure.

MMMSummaryFactory requirement

Curve summaries need access to both the fitted data and the fitted model transformations.

mmm.summary already satisfies that requirement. If you construct MMMSummaryFactory manually, pass model=mmm:

from abacus.mmm.summary import MMMSummaryFactory

summary = MMMSummaryFactory(mmm.data, model=mmm)
curves = summary.saturation_curves()

If you omit model=mmm, Abacus raises a ValueError.

Plot saturation curves

You can plot sampled curves directly:

curve = mmm.sample_saturation_curve(
    num_points=100,
    random_state=42,
    original_scale=True,
)

figure, axes = mmm.plot.saturation_curves(
    curve=curve,
    original_scale=True,
)

You can also inspect the fitted relationship in the observed data with:

figure, axes = mmm.plot.saturation_scatterplot(original_scale=True)

Example curve output:

Saturation curve example Saturation curve example

Adstock curve example Adstock curve example

Practical guidance

  • Use num_samples to trade off speed against posterior resolution.
  • Use original_scale=True when you want the saturation y-axis in target units.
  • Keep in mind that the saturation x-axis stays on the scaled channel axis.
  • Use the summary methods when you need exportable tables.

Common pitfalls

  • Reading x from saturation curves as original spend units
  • Forgetting to pass model=mmm when manually constructing MMMSummaryFactory
  • Comparing adstock curves across models without matching the amount parameter