Skip to content

Fix silent NaN forecast from zero-variance lag-subsample in NNS.ARMA - #53

Merged
OVVO-Financial merged 3 commits into
mainfrom
claude/upbeat-pascal-fq49g2
Jun 19, 2026
Merged

Fix silent NaN forecast from zero-variance lag-subsample in NNS.ARMA#53
OVVO-Financial merged 3 commits into
mainfrom
claude/upbeat-pascal-fq49g2

Conversation

@OVVO-Financial

Copy link
Copy Markdown
Owner

Summary

Fixes a silent NaN forecast from NNS.ARMA when a seasonal lag-subsample has zero variance.

In _numeric_seasonal_weights (src/nns/arma.py), the seasonal weight is 1 / (subsample_CV / baseline_CV). When a lag-subsample is perfectly stable (CV = 0) within an otherwise-varying series, that's 1/0 = Inf, and the subsequent weight normalisation collapses to Inf/Inf = NaN. The NaN weight then multiplies a finite estimate, so nns_arma returns an all-NaN forecast for valid, finite input — with no error or warning. It also defeats nns_arma_optim, which selects the period on a finite validation split and only blows up on the final full-series refit.

Reproducer (M4 Hourly series 131, where y[::-168] == [10, 10, 10, 10, 10]):

NNS.nns_arma(variable=y, h=48, seasonal_factor=np.array([168]), method="lin")  # -> all NaN (before)

Fix

Floor the CV ratio so a maximally-seasonal (CV 0) lag gets a large but finite weight instead of Inf:

floor = np.finfo(np.float64).eps
seasonal_weighting = 1.0 / np.maximum(relative, floor)
  • Non-degenerate inputs are unchanged (np.maximum is a no-op for finite relative).
  • A fully constant series (baseline CV 0 → relative NaN) still propagates NaN, matching the R reference and the existing parity test.

Tests

  • Adds test_numeric_seasonal_weights_constant_subsample_is_finite (constant lag-subsample in a varying series ⇒ finite weights summing to 1, finite forecast).
  • tests/invariants/test_arma.py (19) and tests/property/test_arma.py (2) pass; the pre-existing constant-series NaN parity test still passes. Ruff clean.

Note (R side)

The same guard applies to tools/NNS/R/ARMA.R (the is.null(weights) weighting block) — handled separately.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 3 commits June 18, 2026 19:26
Parallel, checkpointed M4 runner with correct sMAPE/MASE/OWA and a
seasonally-adjusted Naive2. Per-series NNS forecast uses M4's declared
seasonality as the nns_seas modulo and the default optim objective.

Validation (30 series/subset): Hourly is strong (OWA ~0.53, near the M4
winners); Daily/Weekly stay ~3-4x worse than naive due to a catastrophic
tail on near-random-walk series. Framing of the final example still TBD;
no README/PR yet. Includes _modonly_probe.py scratch comparison.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxVLKYqazC2uuAW3P3MbAm
Embed the M4 data download (cached in ./m4_data) and surface a CONFIG
block: series count, modulo source, mod_only, optim-vs-plain nns_arma,
explicit training_set, and a custom obj_fn hook. Run, tweak, compare OWA.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxVLKYqazC2uuAW3P3MbAm
_numeric_seasonal_weights divided 1 / (subsample_CV / baseline_CV). When a
seasonal lag-subsample is perfectly stable (CV 0) in an otherwise-varying
series, that is 1/0 = Inf, and the weight normalisation collapses to
Inf/Inf = NaN -- silently producing an all-NaN forecast for valid input
(e.g. M4 Hourly series 131 at period 168).

Floor the CV ratio so a maximally-seasonal lag gets a large but finite
weight. A fully constant series (baseline CV 0 -> relative NaN) still
propagates NaN, matching the R reference. Adds a regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxVLKYqazC2uuAW3P3MbAm
@OVVO-Financial
OVVO-Financial merged commit 5038e89 into main Jun 19, 2026
8 checks passed
@OVVO-Financial
OVVO-Financial deleted the claude/upbeat-pascal-fq49g2 branch June 19, 2026 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants