Source code for impulso.plotting._historical_decomposition

"""Historical decomposition plotting."""

from typing import TYPE_CHECKING

import matplotlib.pyplot as plt
from matplotlib.figure import Figure

if TYPE_CHECKING:
    from impulso.results import HistoricalDecompositionResult


[docs] def plot_historical_decomposition( result: "HistoricalDecompositionResult", figsize: tuple[float, float] | None = None, ) -> Figure: """Plot historical decomposition as stacked bar charts. One panel per response variable, showing the propagated contribution of each structural shock over time, with the posterior median of the total deviation from the deterministic baseline overlaid as a line. The line is the median of the per-draw sum over shocks, so it matches `data - result.baseline()` exactly; because median-of-sum differs from sum-of-medians, it need not exactly top the median bars. Shock labels come from the result's `shock` coordinate, so partially-identified decompositions (with an `unidentified_remainder` column) render correctly. Args: result: HistoricalDecompositionResult. figsize: Figure size. Defaults to (12, 3 * n_vars). Returns: Matplotlib Figure. """ med = result.median() deviation = result.deviation() shock_names = result.shock_names n_vars = len(result.var_names) T = len(med.index) if figsize is None: figsize = (12, 3 * n_vars) fig, axes = plt.subplots(n_vars, 1, figsize=figsize, sharex=True) if n_vars == 1: axes = [axes] fig.suptitle("Historical Decomposition") time_idx = range(T) for i, resp in enumerate(result.var_names): panel = med[resp].values # (T, n_shocks) bottom_pos = None bottom_neg = None for j, shock in enumerate(shock_names): vals = panel[:, j] pos = vals.clip(min=0) neg = vals.clip(max=0) if bottom_pos is None: axes[i].bar(time_idx, pos, width=1.0, label=shock, alpha=0.8) axes[i].bar(time_idx, neg, width=1.0, alpha=0.8, color=f"C{j}") bottom_pos = pos.copy() bottom_neg = neg.copy() else: axes[i].bar(time_idx, pos, width=1.0, bottom=bottom_pos, label=shock, alpha=0.8) axes[i].bar( time_idx, neg, width=1.0, bottom=bottom_neg, alpha=0.8, color=f"C{j}", ) bottom_pos += pos bottom_neg += neg axes[i].plot( time_idx, deviation[resp].values, color="black", linewidth=1.1, label="deviation from baseline", ) axes[i].set_ylabel(resp) axes[i].axhline(0, color="0.5", linewidth=0.5, linestyle="--") if i == 0: axes[i].legend(fontsize=8, loc="upper right") axes[-1].set_xlabel("Time") fig.tight_layout() return fig