kdiagram.plot.uncertainty.plot_model_drift¶
- kdiagram.plot.uncertainty.plot_model_drift(df, q_cols=None, q10_cols=None, q90_cols=None, horizons=None, color_metric_cols=None, acov='quarter_circle', value_label='Uncertainty Width (Q90 - Q10)', cmap='coolwarm', figsize=(8, 8), title='Model Forecast Drift Over Time', show_grid=True, annotate=True, grid_props=None, savefig=None, dpi=300, ax=None)[source]¶
Visualize forecast drift across prediction horizons.
Renders a polar bar chart depicting how average model uncertainty (or another metric) evolves as the forecast horizon increases. Each bar corresponds to a specific forecast horizon, arranged angularly.
This plot is crucial for diagnosing model degradation over longer lead times, often termed concept drift or model aging [1]. A distinct increase in bar height (radius) for later horizons signals inflating uncertainty or error, potentially indicating a need for model retraining or adjustments to account for changing dynamics. Use this visualization to assess if your model’s reliability holds as you forecast further into the future.
- Parameters:
- df
pandas.DataFrame Input DataFrame containing the necessary quantile columns (or columns specified in
color_metric_cols) for each forecast horizon.- q_cols
list[tuple[str,str]],optional A list where each element is a tuple containing the column names for the lower and upper quantiles for a specific horizon, e.g.,
[('q10_h1', 'q90_h1'), ('q10_h2', 'q90_h2')]. IfNone,q10_colsandq90_colsmust be provided instead. Default isNone.- q10_cols
list[str],optional List of column names representing the lower quantile (e.g., 10th percentile) for each successive horizon. Must be provided if
q_colsisNone. Must have the same length asq90_colsandhorizons(if provided). Default isNone.- q90_cols
list[str],optional List of column names representing the upper quantile (e.g., 90th percentile) for each successive horizon. Must be provided if
q_colsisNone. Must have the same length asq10_colsandhorizons(if provided). Default isNone.- horizons
listofstrorint,optional Labels corresponding to each forecast horizon (plotted on the angular axis). The order must match the order in
q_colsorq10_cols/q90_cols. IfNone, generic labels like “Horizon 1”, “Horizon 2”, … are generated. Default isNone.- color_metric_cols
listofstr,optional If provided, the bars are colored based on the mean value of these columns for each horizon (e.g., provide RMSE columns like
['rmse_h1', 'rmse_h2', ...]). IfNone, bars are colored based on the calculated mean interval width (Q90-Q10). Default isNone.- acov{‘default’, ‘half_circle’, ‘quarter_circle’,
‘eighth_circle’}, optional
Specifies the angular coverage (span) of the plot. Use narrower sectors for fewer horizons. Default is
'quarter_circle'.'default': Full circle (\(2\pi\), 360°).'half_circle': Half circle (\(\pi\), 180°).'quarter_circle': Quarter circle (\(\pi/2\), 90°).'eighth_circle': Eighth circle (\(\pi/4\), 45°).
- value_label
str,optional Label displayed for the radial axis, describing the metric represented by the bar height. Default is
'Uncertainty Width (Q90 - Q10)'.- cmap
str,optional Name of the Matplotlib colormap used to color the bars based on their radial value (or the color_metric_cols value). Default is
'coolwarm'.- figsize
tupleof(float,float),optional Figure dimensions
(width, height)in inches. IfNone, uses the default(8, 8).- title
str,optional Headline text displayed above the plot. Default is
'Model Forecast Drift Over Time'.- show_gridbool,
optional If
True, displays radial and angular grid lines to aid interpretation. Default isTrue.- annotatebool,
optional If
True, displays the numeric value (mean width or mean color metric) on top of each bar. Default isTrue.- grid_props
dict,optional Dictionary of keyword arguments to customize the appearance of the grid lines (passed to
ax.grid()). E.g.,{'linestyle': ':', 'linewidth': 0.5}. Default isNone.- savefig
str,optional Full path and filename to save the plot (e.g., ‘drift.pdf’). If
None, the plot is displayed interactively. Default isNone.
- df
- Returns:
matplotlib.axes.AxesThe polar axes object containing the bar chart, allowing for further customization if desired.
- Raises:
ValueErrorIf required quantile columns (q_cols or both q10_cols and q90_cols) are missing from df, or if the lengths of provided column lists/horizons mismatch.
TypeErrorIf data in the specified columns cannot be processed numerically.
- Parameters:
See also
kdiagram.plot.uncertainty.plot_uncertainty_driftVisualize drift of the uncertainty pattern using rings.
kdiagram.utils.plot.set_axis_gridHelper for grid styling (if used).
Notes
The primary radial value plotted for each horizon \(h\) is the mean interval width calculated across all \(N\) samples:
(1)¶\[\bar{w}_h = \frac{1}{N}\sum_{j=1}^{N} \left( Q_{up, j, h} - Q_{low, j, h} \right)\]If
color_metric_colsis provided, a similar average is calculated for those columns to determine bar color.Radii may be scaled relative to the maximum radius if a restricted angular coverage (
acovis not ‘default’) is used, to better fit the visual sector [2].References
[1]Kouadio, K. L., Liu, R., Loukou, K. G. H., Liu, J., & Liu, W. (2025). Analytics Framework for Interpreting Spatiotemporal Probabilistic Forecasts. International Journal of Forecasting. Manuscript submitted.
[2]Gama, J., Žliobaitė, I., Bifet, A., Pechenizkiy, M., & Bouchachia, A. (2014). A survey on concept drift adaptation. ACM Computing Surveys (CSUR), 46(4), 1-37.
Examples
>>> import pandas as pd >>> import numpy as np >>> from kdiagram.plot.uncertainty import plot_model_drift >>> # Example with synthetic data >>> years = [2023, 2024, 2025, 2026] >>> n_samples=50 >>> df_synth = pd.DataFrame() >>> q10_cols, q90_cols = [], [] >>> for i, year in enumerate(years): ... ql, qu = f'val_{year}_q10', f'val_{year}_q90' ... q10_cols.append(ql); q90_cols.append(qu) ... q10 = np.random.rand(n_samples)*5 + i*0.5 ... q90 = q10 + np.random.rand(n_samples)*2 + 1 + i*0.8 ... df_synth[ql]=q10; df_synth[qu]=q90 >>> ax = plot_model_drift( ... df=df_synth, ... q10_cols=q10_cols, ... q90_cols=q90_cols, ... horizons=years, ... acov='quarter_circle', # Use 90 degree span ... title='Synthetic Model Drift Example' ... )