SPB Git

spb/wp29_uqo

Public

# F3-conf-03 — Rolling-origin ridge forecasts of US industrial production growth

# Question

Over 60 rolling origins from 2021-09-01 to 2026-08-01 (n_origins, first_origin, last_origin), does a ridge regression with a fixed penalty (λ = 50, lambda) on 8 standardised predictors (n_features) forecast next month's US industrial production growth (100 × Δln INDPRO, percent per month) better than two baselines: the naive forecast (last value) and the expanding historical mean? I report the out-of-sample RMSE and MAE of each forecast at h = 1, and the ridge's relative RMSE improvement over each baseline.

# Data and exclusions

  • Input: data/ip_predictors.csv. It has 417 rows (n_input_rows) covering 1992-01 to 2026-09, and its SHA-256 matches data/README.md. The full audit is in out/state/data_audit.md.
  • Exclusions (out/exclusions.csv), applied to build the training set at the last origin: (1) months before 1992-03-01 are dropped, as the task specifies (417 → 415); (2) months s with s + 1 > 2026-08-01 are dropped, because their target is not yet available at the origin (415 → 413). That leaves 413 training rows at the last origin (n_analysed). The training window grows with each origin.
  • Data issues (data_issues):
    • neworder_chg is missing for 1992-01 and 1992-02. The analysis never needs those months.
    • The README says 1992-01 is empty for every change series, but ip_growth, oil_chg and permit_chg do have values for 1992-01. I used them as given; only ip_growth for 1992-01 enters the analysis, as a lag.
    • The extreme COVID-19 observations are kept in every training window, since the task fixes the window and gives no outlier rule.
    • The data are the current vintage, and publication lags are ignored.

# Method

All computation is in out/analysis.py. It is deterministic and uses no random numbers (random_seed = null). For each origin t:

  • Naive: the forecast is ip_growth_t.
  • Expanding mean: the forecast is the mean of ip_growth over 1992-04..t.
  • Ridge: training rows are s = 1992-03..t−1. The target is ip_growth_{s+1}. The features are (ip_growth_s, ip_growth_{s−1}, ip_growth_{s−2}, oil_chg_s, fedfunds_s, permit_chg_s, neworder_chg_s, tcu_s).
    • Each feature is standardised with its mean and sample standard deviation (ddof = 1), computed only on that origin's training rows (normalisation_window = "expanding"). The target is centred on its training mean ȳ.
    • Coefficients: b = (Z′Z + 50 I)⁻¹ Z′(y − ȳ), with no intercept penalty.
    • Forecast: ȳ + ((z_t − μ)/σ)·b.
  • Each forecast is scored against ip_growth_{t+1}. Per-origin forecasts are in out/forecasts.csv (180 rows).
  • Verification (out/state/checks.md):
    • A second, independent ridge implementation matches the script to within 5e-11.
    • Replacing all data dated after t with noise leaves every forecast unchanged, so no future information leaks in.
    • Re-running the script gives byte-identical outputs.

# Results

model RMSE (pp/month) MAE (pp/month)
naive 0.8831 (rmse_naive) 0.6990 (mae_naive)
expanding mean 0.5439 (rmse_mean) 0.4293 (mae_mean)
ridge (λ=50) 0.5608 (rmse_ridge) 0.4514 (mae_ridge)
  • Ridge vs naive: RMSE improvement 1 − 0.5608/0.8831 = +0.365 (rel_improvement_vs_naive). The ridge clearly beats the naive forecast.
  • Ridge vs expanding mean: −0.031 (rel_improvement_vs_mean). The ridge is about 3% worse in RMSE than the expanding mean, and also worse in MAE (0.451 vs 0.429). Over this evaluation period it does not beat the expanding-mean baseline.

# Uncertainty

The evaluation uses only 60 one-step errors. I tested equal squared-error loss with Diebold–Mariano tests, using the Harvey–Leybourne–Newbold small-sample correction and Student-t(59) reference:

  • Ridge vs naive: DM = 3.36, p = 0.0014 (dm_stat_ridge_vs_naive, dm_pvalue_ridge_vs_naive). The ridge's advantage over the naive forecast is statistically significant.
  • Ridge vs mean: DM = −0.54, p = 0.59 (dm_stat_ridge_vs_mean, dm_pvalue_ridge_vs_mean). The ridge and the expanding mean cannot be told apart statistically; the 3% RMSE gap is well within sampling noise.

The DM p-values are approximate: the test assumes the loss difference is stationary, and these forecasts come from nested, estimated models.

# Limitations and open issues

  • Not real-time. The data are the current (revised) vintage and publication lags are ignored. INDPRO, TCU and NEWORDER are revised substantially, so the results describe how predictable the revised series is under stylised timing, not how the forecasts would have performed historically in real time.
  • Fixed settings. λ = 50 and the feature set are fixed by the task and were not tuned. Other penalties could give different rankings; that question is outside the task's scope.
  • Short, specific window. The evaluation covers 60 months (2021-09 to 2026-08), a post-COVID period. Every training window contains the extreme 2020 observations, which may affect the standardisation and the coefficients.
  • Documentation discrepancy. The README's statement about empty 1992-01 cells is inconsistent with the file (see Data and exclusions). This has no material effect.
  • Feasibility. The task was completed in full (feasibility = "complete").
Files · 452964ee0fbbaa40fbbed2c87a54ca798ffd6a85
Menu

Theme