kdiagram.plot.anomaly.plot_anomaly_profile

kdiagram.plot.anomaly.plot_anomaly_profile(df, actual_col, q_low_col, q_up_col, *, window_size=21, theta_bins=72, title=None, figsize=(9.0, 9.0), cmap='plasma', colors=None, alpha=0.8, acov='default', show_grid=True, grid_props=None, ax=None, savefig=None, jitter=0.85, max_flares_per_bin=None, flare_scale='sqrt', flare_clip=None, flare_linewidth=1.4, ring_height=0.06, ring_alpha=0.95, legend_anchor=(1.35, 1.04), **kwargs)[source]

Visualize anomaly severity as a polar profile or “fiery ring”.

This figure emphasizes readability for papers. It encodes clustered anomaly density as a colored ring, and shows each failed sample as a short “flare” growing inward or outward. The design avoids overlap by angular binning and in–bin dodging.

Parameters:
dfpandas.DataFrame

Input table that holds the observed series and the two prediction bounds. Missing rows are dropped.

actual_colstr

Column name of the observed values.

q_low_colstr

Column name of the lower prediction bound.

q_up_colstr

Column name of the upper prediction bound.

window_sizeint, default=21

Window length used to compute the local anomaly density. Odd values are recommended for symmetric windows.

theta_binsint, default=72

Number of angular bins used for the density ring and to group flares for anti-overlap dodging.

titlestr, optional

Figure title. If not given, a title including the CAS score is used.

figsizetuple of float, default=(9.0, 9.0)

Figure size in inches.

cmapstr, default=’plasma’

Colormap used for the density ring.

colorslist of str, optional

Two colors for the flares (over, under). If not given, defaults are chosen.

alphafloat, default=0.8

Global alpha for flare lines.

acov{‘default’,’half_circle’,’quarter_circle’,

‘eighth_circle’}, default=’default’

Angular coverage preset. This controls the polar span.

show_gridbool, default=True

If True, show a light polar grid.

grid_propsdict, optional

Keyword arguments forwarded to the grid styling helper.

axmatplotlib.axes.Axes, optional

Existing polar axes. If None, a new figure is created.

savefigstr, optional

Path to save the figure. If None, the figure is shown.

jitterfloat, default=0.85

Fraction of bin width used to spread flares within each bin to reduce overlap. Clipped to [0, 1].

max_flares_per_binint, optional

If given, at most this many flares are drawn per bin. The largest magnitudes are kept.

flare_scale{‘linear’,’sqrt’,’log’}, default=’sqrt’

Transform applied to anomaly magnitude before mapping to flare length. Use ‘sqrt’ or ‘log’ to tame outliers.

flare_clipfloat, optional

Maximum flare length after scaling. If None, no clipping is applied.

flare_linewidthfloat, default=1.4

Line width of the flares.

ring_heightfloat, default=0.06

Radial thickness of the density ring.

ring_alphafloat, default=0.95

Alpha value for the density ring.

legend_anchortuple of float, default=(1.35, 1.04)

Anchor for the legend box (axes coordinates).

Returns:
axmatplotlib.axes.Axes or None

The polar axes with the plot. Returns None if no anomaly is detected after preprocessing.

Parameters:
Return type:

Axes | None

See also

plot_anomaly_severity

Polar scatter of anomaly points.

plot_anomaly_glyphs

Glyph-based variant with richer marks.

clustered_anomaly_severity

Metric used by this plot.

Notes

Visual mapping.

  • Angle \(\varepsilon\): encodes sample position (index order).

  • Ring color: mean local anomaly density within each angular bin. Hot colors indicate clustered failures.

  • Flares: one per failed sample.

    • Length: anomaly magnitude (scaled and optionally clipped).

    • Direction: type. Outward = over-prediction. Inward = under-prediction.

Anomalies.

A point is an anomaly when the observed value lies outside the prediction interval. Density is computed with a moving window of length window_size and then averaged within bins.

Styling and overlap control. Angular binning plus in-bin jitter reduce overlap. Use theta_bins to raise angular resolution, jitter to control spread, max_flares_per_bin to cap clutter, and flare_scale or flare_clip to keep lengths balanced.

References

[1]

Kouadio, K. L., et al. 2025. CAS: Cluster-Aware Scoring for Probabilistic Forecasts. in review.

[2]

Gneiting, T., and Raftery, A. E. 2007. Strictly proper scoring rules, prediction, and estimation. JASA, 102(477), 359–378.

Examples

>>> import numpy as np
>>> import pandas as pd
>>> from kdiagram.plot.anomaly import plot_anomaly_profile
>>> rng = np.random.default_rng(30)
>>> n = 500
>>> base = np.sin(np.linspace(0, 6*np.pi, n)) * 10 + 20
>>> qlow = base - 5
>>> qup = base + 5
>>> y = base.copy()
>>> y[100:130] += rng.uniform(6, 12, 30)   # over
>>> y[300:330] -= rng.uniform(6, 12, 30)   # under
>>> df = pd.DataFrame({'actual': y, 'q10': qlow, 'q90': qup})
>>> ax = plot_anomaly_profile(
...     df,
...     actual_col='actual',
...     q_low_col='q10',
...     q_up_col='q90',
...     window_size=31,
...     theta_bins=96,
...     jitter=0.9,
... )
>>> _ = ax.figure  # keep handle for saving outside