Post-run Plots

Purpose

Post-run plots decompose the fitted response into its constituent parts. They answer the question: how much does each predictor contribute to the modelled KPI, and how do those contributions evolve over time?

In the active v1.3 runner, these files are emitted under 30_post_run/ when decomposition output flags are enabled and the fitted model supports decomposition table generation. If decomposition cannot be computed, the runner records deterministic skip rows in 40_diagnostics/artifact_status.csv. For hierarchical models with random-effects formula syntax (|), decomposition can still fail gracefully because stats::model.matrix() may not evaluate the formula against the original data.

Plot catalogue

Filename What it shows Conditions
decomp_predictor_impact.png Total contribution per model term (bar chart) outputs.save_decomp_png: true and successful decomposition tables
decomp_timeseries.png Stacked media channel contribution over time outputs.save_decomp_png: true, successful decomposition tables, and at least one media term

Predictor impact

Filename: decomp_predictor_impact.png

Predictor impact Predictor impact

What it shows

A horizontal bar chart of the total contribution of each model term to the response, computed as the sum of coefficient × design-matrix column across all observations. Terms are sorted by absolute contribution magnitude. The intercept and total rows are excluded.

When it is generated

Generation requires runner_response_decomposition_tables() to return a valid predictor-level summary table and outputs.save_decomp_png: true. That in turn requires that stats::model.matrix() can parse the model formula against the original input data and that the fitted model retains the data needed for decomposition. This generally holds for BLM and pooled models and may succeed for hierarchical models that can be reduced to fixed-effect decomposition tables, but formulas with random-effects syntax can still fail gracefully.

How to interpret it

The bar lengths represent total modelled impact over the data period. Media channels with large positive bars drove the most KPI in the model’s account of the data. Control variables (trend, seasonality, holidays) often dominate in absolute terms because they capture baseline demand — this is expected and does not diminish the media findings.

Negative contributions can arise for terms with negative coefficients (e.g. price sensitivity) or for seasonality harmonics where the net effect over the year partially cancels.

Warning signs

  • A media channel with negative total contribution: Unless the coefficient is intentionally unconstrained (no lower boundary at zero), a negative contribution suggests the model is absorbing noise or confounding through that channel. Review the posterior forest plot and check whether the coefficient’s credible interval excludes zero.
  • Intercept-dominated decomposition (not shown here, but visible in the CSV): If the intercept accounts for >90% of the total, media effects are negligible relative to baseline demand. This may be correct, but it limits the utility of the model for budget allocation.
  • Missing plot: If the decomposition failed (logged as a warning), the model type likely does not support direct model.matrix() decomposition. The CSV companions will also be absent.

Action

Use this plot to prioritise which channels to scrutinise. Cross-reference large contributors with the prior vs posterior plot to confirm they are data-driven rather than prior-driven.

  • decomp_predictor_impact.csv is the corresponding tabular output when decomposition artifacts are enabled.
  • posterior_summary.csv in 30_post_run/ provides the coefficient summary underlying the decomposition.

Decomposition time series

Filename: decomp_timeseries.png

Decomposition time series Decomposition time series

What it shows

A stacked area chart of media channel contributions over time. Each layer represents one media term’s weekly contribution (coefficient × transformed media input). Non-media terms (intercept, controls, seasonality) are excluded to focus the view on the media mix.

When it is generated

The plot is generated alongside the predictor impact chart when outputs.save_decomp_png: true and the decomposition tables include at least one media term.

How to interpret it

The height of each band at a given week represents how much that channel contributed to the modelled response. Seasonal patterns in the stack reflect campaign timing and adstock carry-over. The total height of the stack is the aggregate media contribution — the gap between this and the observed KPI is accounted for by non-media terms and noise.

Warning signs

  • A channel with near-zero contribution throughout: The model assigns negligible effect to that channel. This could be correct (low spend, weak signal) or a sign that multicollinearity is suppressing the estimate.
  • Implausibly large single-channel dominance: If one channel accounts for the vast majority of the media stack, verify the coefficient is plausible and not inflated by collinearity with a correlated channel.
  • Abrupt jumps unrelated to spend changes: Check whether the design matrix term (adstock/saturation output) is well-behaved. Sudden spikes in contribution without corresponding spend changes suggest a data or transform issue.

Action

Compare the relative channel contributions here with the business’s spend allocation. Channels that receive large spend but show small contributions may have diminishing returns or weak effects. This comparison motivates the budget optimisation stage.

  • decomp_timeseries.csv is the corresponding long-format output when decomposition artifacts are enabled.

Cross-references