SPB Git

spb/wp29_uqo

Public

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

# Question

Does a fixed-penalty ridge regression (λ = 50, key lambda) on eight standardised monthly predictors (n_features = 8) forecast next month's US industrial production growth (100 × Δln INDPRO, percent per month) better than the naive (last-value) forecast and the expanding historical mean? There are n_origins = 60 rolling origins, from first_origin = 2021-09-01 to last_origin = 2026-08-01, all at horizon h = 1.

# Data and exclusions

  • Input: data/ip_predictors.csv, 417 data rows (n_input_rows), 1992-01 to 2026-09, contiguous months, no duplicates. The SHA-256 matches the README (sha256_input).
  • Missing values: only neworder_chg for 1992-01 and 1992-02. These months come before the training window, so nothing was imputed.
  • Exclusions (out/exclusions.csv): two steps take 417 rows down to 413 training rows at the last origin (n_analysed = 413). Step 1 drops months before 1992-03-01, as the spec requires (2 rows). Step 2 drops feature months whose target would be dated after the last origin (2 rows).
  • Data issues are listed in data_issues:
    • The README says 1992-01 is empty for every change series. In fact ip_growth, oil_chg and permit_chg have values for that month. I used them as given; only ip_growth for 1992-01 enters, as the 2-month lag for s = 1992-03.
    • The training windows contain extreme values (2020-04 = −14.1, 2008-09 = −4.5). I kept them because the specification is fixed.
    • The data are current vintage and publication lags are ignored.

# Method

All computations are in out/analysis.py, which is deterministic and uses no randomness (random_seed = null).

Baselines.

  • Naive: the forecast is ip_growth of month t.
  • Expanding mean: the forecast is the mean of ip_growth over 1992-04..t.

Ridge model.

  • Training rows: months s with 1992-03 ≤ s ≤ t−1, target ip_growth_{s+1}.
  • Features: ip_growth at s, s−1 and s−2, plus oil_chg, fedfunds, permit_chg, neworder_chg and tcu at s.
  • At every origin the features are standardised using the training-window mean and sample SD (ddof = 1), and the target is centred on its training mean ȳ (normalisation_window = "expanding").
  • Coefficients: b = (Z′Z + 50 I)⁻¹ Z′(y − ȳ). Forecast: ȳ + z̃_t·b.
  • ȳ is the same quantity as the expanding-mean forecast. The ridge forecast is therefore the mean forecast plus a shrunken, predictor-based adjustment.

Evaluation. RMSE and MAE over the 60 forecasts. Actuals are ip_growth at t+1 in the current vintage.

# 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 0.5608 (rmse_ridge) 0.4514 (mae_ridge)
  • Ridge vs naive: ridge improves RMSE by 36.5% (rel_improvement_vs_naive = 0.3649).
  • Ridge vs expanding mean: ridge is 3.1% worse in RMSE (rel_improvement_vs_mean = −0.0310) and also worse in MAE.
  • Overall: ridge clearly beats the naive forecast but does not beat the expanding historical mean. The stronger of the two baselines is the mean (baseline_best_rmse_model).

# Uncertainty

I ran Diebold–Mariano tests on the squared-error differential, with the Harvey–Leybourne–Newbold small-sample correction and a t₅₉ reference distribution. For h = 1 no HAC lags are used.

  • Naive vs ridge: DM = 3.36 (dm_stat_naive_vs_ridge), p = 0.0014 (dm_pvalue_naive_vs_ridge). Ridge is significantly more accurate than naive.
  • Mean vs ridge: DM = −0.54 (dm_stat_mean_vs_ridge), p = 0.59 (dm_pvalue_mean_vs_ridge). There is no significant difference; the mean's point advantage is within sampling noise.

With only 60 forecasts, the RMSE differences are imprecise. Ridge and the mean share the same intercept, so the comparison is nested-like, and a standard DM test can be conservative in that setting.

# Limitations and open issues

  • Data vintage: the data are the current (revised) vintage and publication lags are ignored. The results describe how predictable the revised series is under stylised timing, not real-time forecast performance.
  • Fixed specification: λ = 50 and the feature set are fixed by the task and were not tuned. A different λ could change the ranking against the mean; I did not explore this because the task does not ask for it.
  • Outliers: the COVID-era outliers (2020) in the training windows are not down-weighted, and they may distort the ridge coefficients.
  • Sample size: the evaluation sample is short (60 months) and covers a single regime (2021–2026).
  • README discrepancy: the README does not match the data on which 1992-01 cells are empty (see Data and exclusions). The effect is negligible, since only one lag value enters.
  • Verification steps are recorded in out/state/checks.md. They include an independent re-implementation run with all future data corrupted, which reproduced every forecast and so rules out look-ahead.
Files · main
Menu

Theme