F3-conf-03 — Rolling-origin ridge forecasts of US industrial production growth
Question
Does a fixed-penalty ridge regression (λ = 50; lambda) on eight standardised predictors forecast next-month US industrial production growth (100 × Δln INDPRO) better than the naive (last value) forecast and the expanding historical mean? I evaluated this over 60 rolling origins (n_origins), from first_origin = 2021-09-01 to last_origin = 2026-08-01, at horizon h = 1. The estimands are each model's out-of-sample RMSE and MAE, and the ridge model's relative RMSE improvement over each baseline.
Data and exclusions
The input is data/ip_predictors.csv: 417 rows (n_input_rows), monthly from 1992-01 to 2026-09. It is current-vintage FRED data, and its SHA-256 matches the README. There are no duplicate or missing months. The only empty cells are neworder_chg for 1992-01 and 1992-02. All data problems and how I handled them are listed in data_issues:
- The README says the 1992-01 cells of every change series are empty. In the file, three of them (
ip_growth,oil_chg,permit_chg) have values. I used the values as given. - The extreme COVID-19 months (2020-03 to 2020-06) are kept in every training window.
- The data are revised (current vintage), and publication lags are ignored.
Exclusion ledger (out/exclusions.csv), counted at the last origin: start with 417 rows. Dropping months before 1992-03-01 (the training start set in the specification) leaves 415. Dropping months whose target would fall after the origin leaves 413 training rows (n_analysed).
Method
- Naive forecast:
ip_growthat the origin t. - Mean forecast: the arithmetic mean of
ip_growthfrom 1992-04 to t. - Ridge model: trained on rows s from 1992-03 to t−1, with target
ip_growthat s+1.- Features (
n_features= 8):ip_growthat s, s−1 and s−2, plusoil_chg,fedfunds,permit_chg,neworder_chgandtcu. - Each feature is standardised with the mean and the sample standard deviation (ddof = 1) of that origin's training rows. These statistics are recomputed at every origin (
normalisation_window= "expanding"). - The target is centred on its training mean ȳ, and b = (Z′Z + 50 I)⁻¹ Z′(y − ȳ). The intercept is not penalised.
- Forecast = ȳ + z̃_t·b, where z̃_t is the origin's feature vector standardised with the same statistics.
- Features (
- Evaluation: errors are forecast − actual over the 60 target months, 2021-10 to 2026-09.
- No randomness is involved (
random_seed= null).
Results
| model | RMSE (pp/month) | MAE (pp/month) |
|---|---|---|
| naive | 0.883 (rmse_naive) |
0.699 (mae_naive) |
| expanding mean | 0.544 (rmse_mean) |
0.429 (mae_mean) |
| ridge λ=50 | 0.561 (rmse_ridge) |
0.451 (mae_ridge) |
- Ridge vs naive: ridge reduces RMSE by 36.5% (
rel_improvement_vs_naive= 0.365). - Ridge vs expanding mean: ridge does not beat the mean. Its RMSE is 3.1% higher (
rel_improvement_vs_mean= −0.031), and its MAE is also higher (mae_ridgevsmae_mean).
The individual forecasts are in out/forecasts.csv: 180 rows, 60 per model.
Uncertainty
I ran Diebold–Mariano tests on squared-error loss for h = 1, with the Harvey–Leybourne–Newbold correction and a t(59) reference distribution:
- Ridge vs naive: DM = 3.36, p = 0.0014 (
dm_stat_vs_naive,dm_pvalue_vs_naive). The ridge model's advantage over the naive forecast is statistically significant. - Ridge vs mean: DM = −0.54, p = 0.59 (
dm_stat_vs_mean,dm_pvalue_vs_mean). The two forecasts' accuracy cannot be told apart statistically.
With only 60 forecast errors, the RMSE estimates are imprecise. I did not compute confidence intervals for the RMSEs themselves.
Limitations and open issues
- Not real-time: the data are current-vintage (revised) INDPRO, TCU and NEWORDER, and publication lags are ignored. These are pseudo-out-of-sample results, not real-time forecast performance.
- Fixed specification: λ is fixed at 50 and not tuned, and the model is linear. Every training window includes the COVID-19 extremes, which may distort the coefficients. The specification defines the windows without exclusions, so I kept them.
- Narrow evaluation: the window is short (60 months) and covers a single period, 2021–2026.
- README discrepancy: the README's statement about empty 1992-01 cells does not match the file (see
data_issues). - Feasibility: the task was completed in full (
feasibility= "complete").