Skip to content

Outlier Dummies Strategy

This document explains how forecast_realtime handles outlier (point) dummies — one-off indicator variables that neutralise the influence of an exceptional observation (e.g. a COVID quarter) on the fitted model. For the broader forecasting design see forecasting_strategy.md; for the API quick-start see the Home; for the package reference see the API Reference.

These are point dummies for outliers only — not seasonal dummies and not multi-period regime/step dummies. A dummy is 1 on a single date and 0 everywhere else.

Why outlier dummies

A single extreme observation can distort an estimated relationship: in OLS it drags the fitted line; in a scaled, regularised model it also inflates the standardisation statistics. Adding an unpenalised point dummy for that date absorbs the observation, so the remaining coefficients are estimated as if the outlier were not there. For a pure point dummy in plain OLS this is exactly equivalent to dropping that row, but the dummy approach generalises to the scaled/regularised case and keeps a coefficient for every column.

API

Dummies are supplied at fit/forecast time (not in the model constructor), mirroring the y_lags / X_lags pattern. The dummies argument is accepted by:

  • ForecastModel.fit(..., dummies=...)
  • RealTimeModel.forecast(..., dummies=...) — passed through to model.fit() at every vintage.

The value is either:

  • A list of dates — each becomes a column named by its period:
model.fit(y, X=X, dummies=["2020-06-30"])
# → column "D_2020Q2" (quarterly), "D_2020M6" (monthly),
#   "D_2020" (annual), or "D_<ISO date>" as a fallback.
  • A dict {name: date} — for a custom column name:
model.fit(y, X=X, dummies={"covid": "2020-06-30"})
# → column "covid"

The column name for the list form is derived from the date and the inferred frequency of the index, so quarterly/monthly/annual data are labelled appropriately.

How it works

  • The dummy columns are built deterministically from the DatetimeIndex (1.0 on the dummy date, 0.0 elsewhere). Because they are regenerated from the index at both fit and forecast time, they need no imputation — this is the key advantage over passing an outlier indicator as an ordinary X regressor.
  • Dummies are concatenated to the design matrix before the formula is applied, so a formula's right-hand side can select or drop dummies by name (y ~ x1 + D_2020M6 keeps only that dummy; y ~ . or no formula keeps all).
  • Decomposition is automatic: because dummies are ordinary design columns, the forecast decomposition picks them up as components with no extra work.

All-zero dummy dropping (real-time safety)

In a real-time vintage loop a requested dummy date may fall outside the data available at a given vintage (still in the forecast horizon, or before the sample starts). That would create an all-zero column — a meaningless zero coefficient and a singular design for solve-based models.

To prevent this, fit() drops any dummy column that is all-zero over the fit window and records the survivors on self._dummy_cols. forecast() then filters the rebuilt dummies to those same survivors, so the fit and forecast designs stay aligned.

Regularised models: unpenalised dummies and target lags (FWL)

For ForecastRidge, ForecastLasso, and ForecastElasticNet, dummies are always unpenalised and remain in their original units. Generated target lags are unpenalised by default; set penalise_ar=True to shrink them. Only exact target-lag columns generated by y_lags qualify.

FWL handles dummies and exempt target lags alongside penalised regressors. Its cv argument accepts an integer or a scikit-learn-compatible splitter. Each fold learns its projection and optional scaling from the training rows only, then scores held-out design rows with the unweighted mean fold MSE in the original target units. These are not recursive multi-step backtests, so choose a splitter that matches the forecasting task.

An AR-only FWL design skips CV and sets alpha_ (Ridge) or best_alpha (Lasso and ElasticNet) to None. Ordinary non-FWL fixed-alpha fits are unchanged. For Ridge, the default is 100 log-spaced values from 1e-4 to 1e2 in alpha_scaling units in both CV paths; explicit alphas override it.

For the automatic FWL Lasso and ElasticNet grid, see Cross-validation for regularised models.

Important note

Calling model._forecast_decomp(X=raw_X) directly bypasses dummy augmentation (the raw X lacks the dummy column) and raises a "Missing regressors" error. Always go through forecast(decomp=True), which augments the design with the dummies first.