BacktestResult#
- class cultivars.forecast.backtest.BacktestResult(*, names, horizons, origins, realized, point, paths, scheme, window, step)[source]
Bases:
_SummaryMixinThe aligned record of a rolling-origin forecasting exercise.
One row per evaluation origin, one slab per horizon, one column per series: what was forecast, what happened, and, for a density forecaster, the predictive draws behind the forecast. Everything downstream, a density score, a calibration record, a Diebold-Mariano or Clark-West comparison, a model confidence set, reads from these arrays, so the alignment the comparison tests cannot verify is guaranteed here by construction: origin \(t\)’s \(h\)-step forecast and the outcome at
origins[t] + h - 1are stored in the same cell,\[\texttt{point}[t, h - 1, j] = \hat y_{\,o_t + h - 1 \mid o_t,\, j}, \qquad \texttt{realized}[t, h - 1, j] = y_{\,o_t + h - 1,\, j},\]with \(o_t\) the number of observations the \(t\)-th estimation saw. Every loss the record computes is a function of those two arrays and, for the density losses, the draws, and is returned one value per origin so that a comparison test receives a series it can difference against another backtest’s.
- Variables:
names – Series labels.
horizons – Longest horizon; every horizon
1 .. horizonsis kept.origins –
(T,)observations available at each origin, so the first forecast target is roworigins[t]of the sample.realized –
(T, H, k)outcomes.point –
(T, H, k)point forecasts – the predictive mean for a density forecaster.paths –
(T, S, H, k)predictive draws, orNonefor a point forecaster.scheme –
"expanding"or"rolling".window – Observations each estimation used under a rolling scheme; the first origin’s count under an expanding one.
step – Origins between successive evaluations.
Note
The record is the same type for a point and a density forecaster; the difference is whether
pathsisNone, whichis_densityreads. Every method that needs draws says so in its error rather than returning a degenerate value, so a point backtest handed to a calibration tool fails at the call that needs the draws, with the remedy named. Losses are negatively oriented throughout: smaller is better, including the log score, which is the negative log predictive density.See also
Backtest– the harness that produces this record.ForecastComparisonandclark_west()– the comparison testslosses()feeds.Calibration– the calibration recordrecord()feeds.model_confidence_set()– the set of models the losses cannot separate.
References
Diebold, F. X., & Mariano, R. S. (1995). Comparing predictive accuracy. Journal of Business & Economic Statistics, 13(3), 253-263.
Gneiting, T., & Raftery, A. E. (2007). Strictly proper scoring rules, prediction, and estimation. Journal of the American Statistical Association, 102(477), 359-378.
Tashman, L. J. (2000). Out-of-sample tests of forecasting accuracy: An analysis and review. International Journal of Forecasting, 16(4), 437-450.
Example
A point backtest of a VAR, expanding from 100 observations, and the aligned loss series a comparison test would take:
>>> import numpy as np >>> from cultivars.multivariate.reduced_form.vector_autoregression import VAR >>> rng = np.random.default_rng(0) >>> y = np.zeros((160, 2)) >>> for t in range(1, 160): ... y[t] = 0.5 * y[t - 1] + rng.standard_normal(2) >>> record = Backtest(y, lambda w: VAR(w, order=1).fit(), horizons=2, start=100).run() >>> record.n_origins, record.horizons, record.k_endog, record.is_density (59, 2, 2, False) >>> record.origins[:3], record.realized.shape, record.paths is None (array([100, 101, 102]), (59, 2, 2), True) >>> record.losses("squared", horizon=2, name="y1").shape (59,) >>> record.rmse.round(2) array([[1.06, 1.19], [1.21, 1.21]])
A density backtest records draws and unlocks the density losses:
>>> from cultivars.multivariate.large_dim.bayesian import BVAR >>> fit = lambda w: BVAR(w, order=1).fit(n_draws=100, seed=0) >>> density = Backtest(y, fit, horizons=2, start=120, step=5).run() >>> density.n_origins, density.n_draws, density.paths.shape (8, 100, (8, 100, 2, 2)) >>> density.losses("crps", horizon=1).shape, density.record(2)[0].shape ((8, 2), (8, 100, 2))
Private methods#
|
|
|
The slab index of a one-based horizon, after checking it exists. |
|
The column index of a series label, after checking it exists. |
Build the structured summary: one row per horizon and series. |