plots.preprocessing_plots

plots.preprocessing_plots

Figures for the spotforecast2-safe data-preparation artifacts.

Companion plots for the preprocessing stages of spotforecast2_safe: the target-corruption heal step (plot_corruption_spans visualizes a TargetCorruptionReport next to the frames before and after healing) and the gap-imputation step (plot_imputation_weights visualizes the filled frame and weight series returned by get_missing_weights).

All functions follow the stateless conventions of spotforecast2.plots.evaluation: they return a matplotlib.figure.Figure, never call plt.show(), and do not mutate matplotlib.rcParams — styling and figure lifecycle stay with the caller (set matplotlib.use("Agg") before importing pyplot in headless environments).

Functions

Name Description
plot_corruption_spans Two-panel before/after view of a target-corruption heal.
plot_imputation_weights Two-panel view of a gap fill and its companion sample weights.

plot_corruption_spans

plots.preprocessing_plots.plot_corruption_spans(
    data_before,
    data_after,
    target,
    spans,
    *,
    reference=None,
    zoom=None,
    span_end_extension='1h',
    target_color=None,
    reference_color=None,
    span_color='0.5',
    span_alpha=0.12,
    before_label='corrupted series',
    after_label='healed series',
    reference_label='reference series',
    linewidth=0.9,
    ylabel='',
    legend_loc='lower left',
    figsize=(6.3, 3.6),
)

Two-panel before/after view of a target-corruption heal.

The top panel shows the corrupted series (and, when reference is given, the reference series the deviation rule compares against), the bottom panel the same column after the policy ran. The flagged spans of the TargetCorruptionReport are shaded in both panels.

TargetCorruptionReport.spans records each span as a pair of hour-floored ISO strings in which the end names the START of the last flagged hour. The default span_end_extension of one hour therefore closes the interval so the shading covers the full flagged stretch. Spans are clipped to the zoom window and dropped when they fall entirely outside it, so a distant span never widens the x-axis.

Parameters

Name Type Description Default
data_before pd.DataFrame Frame before healing. Must contain target (and reference when given). required
data_after pd.DataFrame Frame after healing. Must contain target. required
target str Name of the healed column. required
spans Sequence[tuple[str, str]] Flagged spans as (start, end) pairs of timestamps or ISO strings, typically report.spans. required
reference str | None Optional reference column of data_before, drawn in the top panel. None
zoom Optional (start, end) pair restricting both panels to a window around the episode. None
span_end_extension str | pd.Timedelta Amount added to each span end before shading. The default “1h” matches the report contract above. '1h'
target_color str | None Line color of the target series in both panels. None leaves the choice to matplotlib’s color cycle. None
reference_color str | None Line color of the reference series. None
span_color str Fill color of the shaded spans. '0.5'
span_alpha float Alpha of the shaded spans. 0.12
before_label str Legend label of the top-panel target line. 'corrupted series'
after_label str Legend label of the bottom-panel target line. 'healed series'
reference_label str Legend label of the reference line. 'reference series'
linewidth float Width of all series lines. 0.9
ylabel str Y-axis label, applied to both panels. ''
legend_loc str loc argument forwarded to both legends. 'lower left'
figsize tuple[float, float] Figure size of the two-panel figure. (6.3, 3.6)

Returns

Name Type Description
Figure A matplotlib.figure.Figure with two axes sharing the x-axis.

Raises

Name Type Description
ValueError If a frame is empty, if target (or reference) is missing from its frame, or if a span is not a pair.

Examples

import matplotlib
matplotlib.use("Agg")  # non-interactive backend for doc rendering
import numpy as np
import pandas as pd

from spotforecast2.plots.preprocessing_plots import plot_corruption_spans

idx = pd.date_range("2025-01-01", periods=192, freq="15min", tz="UTC")
rng = np.random.default_rng(3)
before = pd.DataFrame({"Actual Load": 10.0 + rng.normal(0.0, 0.05, 192)}, index=idx)
after = before.copy()
flagged = idx[(idx >= "2025-01-01 12:00") & (idx < "2025-01-01 15:00")]
after.loc[flagged, "Actual Load"] = float("nan")
spans = [("2025-01-01T12:00", "2025-01-01T14:00")]

fig = plot_corruption_spans(before, after, "Actual Load", spans)
assert len(fig.axes) == 2
assert len(fig.axes[0].patches) == 1 and len(fig.axes[1].patches) == 1
print("plot_corruption_spans: 1 span shaded in both panels")
plot_corruption_spans: 1 span shaded in both panels

plot_imputation_weights

plots.preprocessing_plots.plot_imputation_weights(
    data_gapped,
    data_filled,
    weights,
    target,
    *,
    zoom=None,
    bridge_slots=1,
    series_color=None,
    fill_color=None,
    weight_color=None,
    series_label='series with gap',
    fill_label='filled values',
    weight_label='sample weight',
    linewidth=0.9,
    fill_linewidth=1.4,
    ylabel='',
    weight_ylabel='weight',
    weight_ylim=(-0.05, 1.05),
    legend_loc='lower left',
    figsize=(6.3, 3.6),
)

Two-panel view of a gap fill and its companion sample weights.

Visualizes the pair returned by spotforecast2_safe.preprocessing.imputation.get_missing_weights: the top panel shows the gapped series with the filled values drawn over every gap, the bottom panel the weight series as a step plot, which is zero inside a gap and for the trailing window after it.

Each filled segment is extended by bridge_slots positions on both sides so the fill connects visually to the surrounding series.

Parameters

Name Type Description Default
data_gapped pd.DataFrame Frame before imputation; gap slots hold NaN. required
data_filled pd.DataFrame Frame after imputation, same index as data_gapped. required
weights pd.Series Weight series aligned with the frames, e.g. the second return value of get_missing_weights. required
target str Name of the filled column. required
zoom Optional (start, end) pair restricting both panels to a window around the gap. None
bridge_slots int Positions added on each side of a filled segment. 1
series_color str | None Line color of the gapped series. None leaves the choice to matplotlib’s color cycle. None
fill_color str | None Line color of the filled segments. None
weight_color str | None Line color of the weight step plot. None
series_label str Legend label of the gapped series. 'series with gap'
fill_label str Legend label of the filled segments. 'filled values'
weight_label str Legend label of the weight series. 'sample weight'
linewidth float Width of the series and weight lines. 0.9
fill_linewidth float Width of the filled segments. 1.4
ylabel str Y-axis label of the top panel. ''
weight_ylabel str Y-axis label of the bottom panel. 'weight'
weight_ylim tuple[float, float] Y-limits of the bottom panel. (-0.05, 1.05)
legend_loc str loc argument forwarded to both legends. 'lower left'
figsize tuple[float, float] Figure size of the two-panel figure. (6.3, 3.6)

Returns

Name Type Description
Figure A matplotlib.figure.Figure with two axes sharing the x-axis.

Raises

Name Type Description
ValueError If a frame is empty, if target is missing from either frame, or if the frames and weights do not share the same index.

Examples

import matplotlib
matplotlib.use("Agg")  # non-interactive backend for doc rendering
import numpy as np
import pandas as pd

from spotforecast2.plots.preprocessing_plots import plot_imputation_weights

idx = pd.date_range("2025-01-01", periods=192, freq="15min", tz="UTC")
rng = np.random.default_rng(5)
gapped = pd.DataFrame({"Actual Load": 10.0 + rng.normal(0.0, 0.05, 192)}, index=idx)
gapped.iloc[60:84, 0] = float("nan")
filled = gapped.ffill()
weights = pd.Series(1.0, index=idx)
weights.iloc[60:120] = 0.0

fig = plot_imputation_weights(gapped, filled, weights, "Actual Load")
assert len(fig.axes) == 2
assert len(fig.axes[0].lines) == 2  # series + one filled segment
print("plot_imputation_weights: 1 gap bridged")
plot_imputation_weights: 1 gap bridged