Latest Results
arima: rotate xreg onto the singular directions, as R does
R's arima rotates a multi-column xreg by `S$v` before fitting, to decorrelate
the regressors. numpy's svd returns `vt`, which is that matrix transposed, so
translating `S$v` as `vt` rotates by the wrong matrix.
It is self-consistent -- the fit and the two undo steps all use the same
matrix, so an orthogonal reparameterisation cancels out and coefficients and
standard errors are unaffected (checked against R on a correlated two-regressor
ARIMA(1,0,0): coefficients agree to 7 decimals, standard errors to 6, both
before and after). What is lost is the entire point of the rotation. On a
near-collinear design the intended basis leaves the columns orthogonal, with
norms equal to the singular values; `vt` leaves the largest off-diagonal of the
Gram matrix at 70.06 against 3e-14, and column norms unrelated to the spectrum.
Fitting in a better-conditioned basis changes what the optimizer can reach, so
ARIMA(2,0,2)(1,0,1)[12] on the issue 649 series now scores 505.8834 instead of
being rejected. R scores it 505.8942, so the pinned `inf` recorded a deviation;
the expectation moves with it.
This also makes the singular values describe the rotated columns, which is what
a rank check on the xreg would need.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YXn1fursQmqpMazreQccZjmoralez:fix/xreg-svd-rotation arima: keep a singular Hessian from failing the whole model search
auto_arima_f raised "No suitable ARIMA model found" whenever the xreg was
rank-deficient -- two constant columns, which is what a static feature becomes
on a per-series model, and what generate_series feeds the dask/spark flow
tests. Every candidate carries the same xreg, so every candidate was rejected.
np.linalg.inv does not raise on a numerically singular matrix; it returns a
covariance with negative diagonal entries, whose square root is NaN, which is
exactly what checkarima rejects on. Main never saw this because scipy's BFGS
hess_inv is positive definite by construction, so the degeneracy stayed hidden
until the Hessian became a real one.
Decompose instead and read the spectrum. approx_hess3's central differences
resolve the Hessian to about sqrt(eps), so eigenvalues under that are
indistinguishable from zero: one below -tol is a genuine saddle and still
yields NaN, while the flat ones are directions collinear xreg leaves
unidentified and are dropped, inverting over the identified subspace. A
well-conditioned Hessian keeps every direction and the result is the plain
inverse, so the standard errors pinned against R are untouched.
This is a guard, not a cure -- a single constant regressor is equally
unidentified and stays undetected, since the design matrix is full rank and
only the likelihood is flat. Dropping collinear regressors up front is filed
separately.
eigh also raises LinAlgError on a Hessian holding NaN, which the previous code
only caught for inv.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YXn1fursQmqpMazreQccZ Latest Branches
0%
+80%
jmoralez:migrate-to-scikit-build 0%
jmoralez:fix/xreg-svd-rotation © 2026 CodSpeed Technology