alsgls package¶
Lightweight low-rank-plus-diagonal GLS/SUR estimation via ALS.
- class alsgls.ALSGLS(*, rank='auto', rank_candidates=None, cv_folds=5, cv_random_state=None, lam_B=0.001, max_sweeps=12, rel_tol=1e-06, d_floor=1e-08, cg_maxit=800, cg_tol=3e-07)[source]¶
Bases:
objectScikit-learn style estimator for low-rank GLS via ALS.
- Parameters:
- score(Xs, Y)[source]¶
Return the negative Gaussian NLL per row of
Yunder the fitted Sigma.Higher is better, as scikit-learn’s
scoreprotocol requires. This scores the whole fitted covariance, not just the conditional mean, so it is not the negative MSE; usealsgls.msefor that.
- covariance(method='kh')[source]¶
Covariance of the coefficients by the named method.
"kh"(default) is the Kackar-Harville corrected covariance, which accounts forSigmabeing estimated;"plugin"is the df-rescaled plug-in that linearmodels, systemfit and Stata report. Measured on the test suite’s 4-equation, rank-1 fixture, the reported standard error is 0.85 (plug-in) and 0.89 (kh) of the actual spread atn = 20, and 0.94 / 0.96 atn = 40. Neither is calibrated atn = 20; for that usebootstrap().- Parameters:
method (str) –
"kh"or"plugin".- Returns:
The
(p_total, p_total)covariance.- Raises:
RuntimeError – If the training designs were not stored.
- Return type:
ndarray
- property cov_params_: ndarray¶
The default coefficient covariance,
covariance("kh").
- bootstrap(B=999, method='parametric', seed=None)[source]¶
Bootstrap the whole fit and return studentised (bootstrap-t) inference.
Each replicate refits
F,Dandbetaon resampled data, which is what captures the part of the sampling variance the plug-incov_params_cannot: thatSigmais estimated. The calibrated object is the percentile-t interval, not the bootstrap standard error; seeBootstrapResults.- Parameters:
B (int) – Number of replicates.
999makes0.05 * (B + 1)an integer, which is what a 95% percentile interval wants;199is enough if only the standard errors are of interest.method (str) –
"parametric"draws errors from the fittedF F' + diag(D);"wild"multiplies each residual row by a Rademacher sign;"residual"resamples whole residual rows. The last two make no distributional assumption.seed (int | None) – Seed for the replicate stream; the same seed gives the same replicates.
- Returns:
The bootstrap distribution and the inference built on it.
- Return type:
- predict_interval(Xs, alpha=0.05, return_type='prediction')[source]¶
Compute prediction or confidence intervals for new observations.
- Parameters:
Xs (Sequence[Any]) – List of design matrices [X_0, …, X_{K-1}] for new observations.
alpha (float) – Significance level (default 0.05 for 95% intervals).
return_type (str) – “prediction” for prediction intervals (includes residual variance), “confidence” for confidence intervals (variance of E[y|X] only).
- Returns:
{“mean”: (N, K), “lower”: (N, K), “upper”: (N, K)}
- Return type:
- Raises:
ValueError – If
return_typeis neither"prediction"nor"confidence", or the design matrices do not match the fitted model in count or in number of columns.
- class alsgls.ALSGLSSystem(system, *, rank='auto', lam_B=0.001, max_sweeps=12, rel_tol=1e-06, d_floor=1e-08, cg_maxit=800, cg_tol=3e-07)[source]¶
Bases:
objectStatsmodels-style system container for ALS GLS fitting.
- Parameters:
- class alsgls.ALSGLSSystemResults(model, estimator)[source]¶
Bases:
objectLightweight results container mimicking
statsmodelsoutputs.- Parameters:
model (ALSGLSSystem)
estimator (ALSGLS)
- covariance(method='kh')[source]¶
Covariance of the coefficients by the named method.
"kh"(default) is the Kackar-Harville corrected covariance, which accounts forSigmabeing estimated;"plugin"is the df-rescaled plug-in that linearmodels, systemfit and Stata report. Measured on the test suite’s 4-equation, rank-1 fixture, the reported standard error is 0.85 (plug-in) and 0.89 (kh) of the actual spread atn = 20, and 0.94 / 0.96 atn = 40. Neither is calibrated atn = 20; for that usebootstrap().- Parameters:
method (str) –
"kh"or"plugin".- Returns:
The
(p_total, p_total)covariance.- Return type:
ndarray
- property cov_params: ndarray¶
The default coefficient covariance,
covariance("kh").
- property bse: ndarray¶
Standard errors of parameter estimates (sqrt of diagonal of cov_params).
- property tvalues: ndarray¶
β = 0.
- Type:
t-statistics for H₀
- property pvalues: ndarray¶
β = 0.
- Type:
Two-sided p-values for H₀
- conf_int(alpha=0.05)[source]¶
Confidence intervals for parameters.
- Parameters:
alpha (float) – Significance level (default 0.05 for 95% CI)
- Returns:
(n_params, 2) array with lower and upper bounds
- Return type:
ci
- get_prediction(exog=None, alpha=0.05)[source]¶
Get prediction results with standard errors and intervals.
- Parameters:
- Returns:
Object with predicted_mean, se_mean, se_obs, and interval methods.
- Return type:
- Raises:
ValueError – If design matrices are not supplied for every equation, or one has the wrong number of columns.
- bootstrap(B=999, method='parametric', seed=None)[source]¶
Bootstrap the whole fit and return studentised (bootstrap-t) inference.
Each replicate refits
F,Dandbetaon resampled data, which is what captures the part of the sampling variance the plug-incov_paramscannot: thatSigmais estimated. The calibrated object is the percentile-t interval, not the bootstrap standard error; seeBootstrapResults.- Parameters:
B (int) – Number of replicates.
999makes0.05 * (B + 1)an integer, which is what a 95% percentile interval wants;199is enough if only the standard errors are of interest.method (str) –
"parametric"draws errors from the fittedF F' + diag(D);"wild"multiplies each residual row by a Rademacher sign;"residual"resamples whole residual rows. The last two make no distributional assumption.seed (int | None) – Seed for the replicate stream; the same seed gives the same replicates.
- Returns:
The bootstrap distribution and the inference built on it.
- Return type:
- class alsgls.BootstrapResults(params, se_plugin, estimates, tstats, method, seed, df)[source]¶
Bases:
objectBootstrap distribution of the coefficients and the inference built on it.
- Parameters:
- params: ndarray¶
The parent fit’s coefficients, length
p_total.
- se_plugin: ndarray¶
The parent fit’s plug-in standard errors.
- estimates: ndarray¶
Coefficients from each replicate,
(B, p_total).
- tstats: ndarray¶
Studentised deviations
(beta(b) - beta_hat) / se(b),(B, p_total).
- property bse: ndarray¶
the spread of
betaacross replicates.Better than the plug-in, but still biased down (Freedman and Peters measured 20-30% in their design), because each replicate is generated from a
Sigma_hatthat is itself too small. The interval, not this number, is the calibrated object.- Type:
Bootstrap standard errors
- property tvalues: ndarray¶
params / se_plugin, the statistic the bootstrap-t p-value refers to.
- conf_int(alpha=0.05)[source]¶
Percentile-t (bootstrap-t) confidence intervals.
[beta_hat - t*_{1-alpha/2} se, beta_hat - t*_{alpha/2} se]with the quantiles taken from the studentised replicates andsethe parent plug-in. Asymmetric when the studentised distribution is.- Parameters:
alpha (float) – Significance level.
- Returns:
(p_total, 2)array of lower and upper bounds.- Return type:
ndarray
- property pvalues: ndarray¶
Two-sided bootstrap-t p-values for
H0: beta = 0.The share of replicates whose studentised deviation is at least as large in absolute value as the observed
t, with the+1continuity adjustment soBreplicates cannot report zero.
- class alsgls.PredictionResults(predicted_mean, se_mean, se_obs, _df, _alpha_default=0.05)[source]¶
Bases:
objectContainer for prediction results with intervals.
- Parameters:
- predicted_mean: ndarray¶
- se_mean: ndarray¶
- se_obs: ndarray¶
- alsgls.als_gls(Xs, Y, k, lam_B=0.001, sweeps=8, d_floor=1e-08, cg_maxit=800, cg_tol=3e-07, *, rel_tol=1e-06, init_D=None)[source]¶
Alternating GLS with a low-rank-plus-diagonal covariance.
Alternates two exact steps until the likelihood stops falling:
betaby matrix-free conjugate gradients at the currentSigma, thenSigmaby the closed-form factor-analysis update in_sigma_step(). Woodbury is used throughout and noK x Kmatrix is formed.- Parameters:
Xs (list[ndarray]) – One design matrix per equation, each
(N, p_j). The equations may have different numbers of regressors.Y (ndarray) – Responses,
(N, K), one column per equation.k (int) – Rank of the latent factor block. The covariance is modelled as
F F^T + DwithFof shape(K, k), sokcontrols how much cross-equation dependence is shared;k << Kis the point.lam_B (float) – Ridge penalty on the coefficients
beta, expressed relative to the mean initial residual variance rather than in absolute units. An absolute penalty would make the fit depend on the units ofY:X' Sigma^-1 Xscales as1/s^2underY -> sYwhile a fixedlam_Bdoes not, so atlam_B = 1e-3scalingYby1e4moved every coefficient by 100%. Dividing by the variance scale makes the penalty transform correctly and the whole fit equivariant.sweeps (int) – Maximum number of alternating passes over
betaandSigma.d_floor (float) – Floor on each diagonal variance, as a fraction of the mean initial residual variance rather than an absolute variance, for the same reason
lam_Bis relative: the trueDscales ass^2underY -> sY, so an absolute floor binds on every entry onceYis small enough.cg_maxit (int) – Iteration cap for the conjugate-gradient solve in the beta step.
cg_tol (float) – Relative residual tolerance for that solve.
rel_tol (float) – Relative decrease in the negative log-likelihood below which the sweeps stop early.
init_D (ndarray | None) – Starting diagonal variances for the first Sigma step, length
K. Defaults to the residual variances of the initial fit. A bootstrap replicate passes the parent fit’sDhere: the replicate’s answer is close to the parent’s, so starting from it skips most of the inner alternation. OnlyDis needed, because the Sigma step recomputesFfromDin closed form.
- Returns:
infoincludesp_list,cg(the final beta solve),nll_trace(per sweep, non-increasing, and equal tonll_per_rowat the returned parameters),nll_beta_trace(post-beta, per sweep),sigma_iters(inner alternation counts), andvar_ref/lam_B_eff(the variance scale the relative penalties were resolved against, and the resulting absolute ridge).- Return type:
B_list, F, D, mem_MB_est, info
- Raises:
ValueError – If an argument is outside its domain, if
XsorYholds a non-finite entry, or if their shapes disagree.np.linalg.LinAlgError – If a Cholesky factorisation of the Woodbury core fails, which means the current Sigma is not positive definite.
- alsgls.mse(Y, Yhat)[source]¶
Mean squared error between two matrices.
- Parameters:
Y (ndarray)
Yhat (ndarray)
- Return type:
- alsgls.nll_per_row(R, F, D)[source]¶
Negative log-likelihood per row for a residual matrix.
Computed for
Runder Σ = F F^T + diag(D) with Gaussian errors.- Parameters:
R (ndarray) – Residual matrix,
(N, K).F (ndarray) – Factor loadings,
(K, k).D (ndarray) – Diagonal variances, length
K.
- Returns:
0.5 * [ tr(R Σ^{-1} R^T)/N + log det(Σ) + K log(2π) ] where N is the number of rows in R.
- Return type:
- alsgls.select_rank_bic(Xs, Y, k_candidates=None, **als_kwargs)[source]¶
Select rank by minimizing BIC (Bayesian Information Criterion).
BIC(k) = 2 * N * nll_per_row + n_params * log(N)
which is the usual
-2 * loglik + n_params * log(N), sinceN * nll_per_rowis the negative log-likelihood of the sample. The value used to be reported at half this, so it was not comparable with thebicattribute of statsmodels or any other package; the rank chosen is unchanged, because halving is monotone.n_paramscounts the free parameters of the whole fitted model: theK*kfactor loadings inFless thek*(k-1)/2orthogonal rotationsF -> F Qthat leaveF F^Tunchanged and so are not identified, theKdiagonal variances inD, and thesum(p_j)regression coefficients, sincenll_per_rowis evaluated at the fittedbetaand a BIC has to charge for it. This is the standard factor-analysis count; R’sfactanalreports the complementarydf = ((K-k)^2 - K - k) / 2.A rank whose fit raised carries the message in its
errorkey.The count used to be
K*(k+1) + k, which neither subtracted the rotational redundancy nor charged forbeta. Only the first term varies withk, so on the fixtures tested the selected rank is unchanged; the reported value was wrong either way.- Parameters:
- Returns:
The rank with minimum BIC. results: Per-rank dicts of ‘k’, ‘nll’, ‘bic’, ‘n_params’, ‘converged’.
- Return type:
best_k
- Raises:
RuntimeError – If no candidate rank produced a usable fit.
- alsgls.select_rank_cv(Xs, Y, k_candidates=None, n_folds=5, random_state=None, **als_kwargs)[source]¶
Select rank by k-fold cross-validation on validation NLL.
- Parameters:
Xs (list[ndarray]) – Design matrices for each equation.
Y (ndarray) – Response matrix.
k_candidates (list[int] | None) – Candidate ranks to evaluate. Defaults to range(1, min(K//2, 12)+1).
n_folds (int) – Number of cross-validation folds.
random_state (int | Generator | None) – Random state for reproducible fold splits.
**als_kwargs (Any) – Additional arguments passed to als_gls().
- Returns:
The rank with minimum mean CV NLL. results: Per-rank results containing ‘k’, ‘cv_nll’, ‘cv_std’, ‘fold_nlls’.
- Return type:
best_k
- Raises:
ValueError – If
n_foldsis below 2 or exceeds the number of rows.RuntimeError – If no candidate rank produced a usable fit.
- alsgls.simulate_gls(N_tr, N_te, p_list, k, seed=0)[source]¶
Simulate a generalized least squares (GLS) dataset.
This variant allows each response equation to have its own number of features as specified by
p_list.- Parameters:
- Returns:
per-equation feature matrices with
X_tr[j]of shape(N_tr, p_list[j])andX_te[j]of shape(N_te, p_list[j]), and responses of shape(N_tr, K)and(N_te, K)whereK = len(p_list).- Return type:
A tuple
(X_tr, Y_tr, X_te, Y_te)
Notes
Randomness is controlled via
numpy.random.default_rng(seed); pass a differentseedfor different simulations.Examples
>>> from alsgls import simulate_gls >>> p_list = [3, 5, 2] >>> Xtr, Ytr, Xte, Yte = simulate_gls(100, 20, p_list, k=2, seed=0)
- alsgls.simulate_sur(N_tr, N_te, K, p, k, seed=0)[source]¶
Simulate a Seemingly Unrelated Regression (SUR) dataset.
- Parameters:
- Returns:
lists of per-equation feature matrices of shape
(N_tr, p)and(N_te, p), and response matrices of shape(N_tr, K)and(N_te, K).- Return type:
A tuple
(X_tr, Y_tr, X_te, Y_te)
Notes
Randomness is controlled via
numpy.random.default_rng(seed); pass a differentseedfor different simulations.Examples
>>> from alsgls import simulate_sur >>> Xtr, Ytr, Xte, Yte = simulate_sur(100, 20, K=3, p=5, k=2, seed=42)
Submodules¶
alsgls.als module¶
Alternating-least-squares solver for low-rank-plus-diagonal GLS.
- alsgls.als.als_gls(Xs, Y, k, lam_B=0.001, sweeps=8, d_floor=1e-08, cg_maxit=800, cg_tol=3e-07, *, rel_tol=1e-06, init_D=None)[source]¶
Alternating GLS with a low-rank-plus-diagonal covariance.
Alternates two exact steps until the likelihood stops falling:
betaby matrix-free conjugate gradients at the currentSigma, thenSigmaby the closed-form factor-analysis update in_sigma_step(). Woodbury is used throughout and noK x Kmatrix is formed.- Parameters:
Xs (list[ndarray]) – One design matrix per equation, each
(N, p_j). The equations may have different numbers of regressors.Y (ndarray) – Responses,
(N, K), one column per equation.k (int) – Rank of the latent factor block. The covariance is modelled as
F F^T + DwithFof shape(K, k), sokcontrols how much cross-equation dependence is shared;k << Kis the point.lam_B (float) – Ridge penalty on the coefficients
beta, expressed relative to the mean initial residual variance rather than in absolute units. An absolute penalty would make the fit depend on the units ofY:X' Sigma^-1 Xscales as1/s^2underY -> sYwhile a fixedlam_Bdoes not, so atlam_B = 1e-3scalingYby1e4moved every coefficient by 100%. Dividing by the variance scale makes the penalty transform correctly and the whole fit equivariant.sweeps (int) – Maximum number of alternating passes over
betaandSigma.d_floor (float) – Floor on each diagonal variance, as a fraction of the mean initial residual variance rather than an absolute variance, for the same reason
lam_Bis relative: the trueDscales ass^2underY -> sY, so an absolute floor binds on every entry onceYis small enough.cg_maxit (int) – Iteration cap for the conjugate-gradient solve in the beta step.
cg_tol (float) – Relative residual tolerance for that solve.
rel_tol (float) – Relative decrease in the negative log-likelihood below which the sweeps stop early.
init_D (ndarray | None) – Starting diagonal variances for the first Sigma step, length
K. Defaults to the residual variances of the initial fit. A bootstrap replicate passes the parent fit’sDhere: the replicate’s answer is close to the parent’s, so starting from it skips most of the inner alternation. OnlyDis needed, because the Sigma step recomputesFfromDin closed form.
- Returns:
infoincludesp_list,cg(the final beta solve),nll_trace(per sweep, non-increasing, and equal tonll_per_rowat the returned parameters),nll_beta_trace(post-beta, per sweep),sigma_iters(inner alternation counts), andvar_ref/lam_B_eff(the variance scale the relative penalties were resolved against, and the resulting absolute ridge).- Return type:
B_list, F, D, mem_MB_est, info
- Raises:
ValueError – If an argument is outside its domain, if
XsorYholds a non-finite entry, or if their shapes disagree.np.linalg.LinAlgError – If a Cholesky factorisation of the Woodbury core fails, which means the current Sigma is not positive definite.
alsgls.ops module¶
Woodbury-based linear algebra kernels for the ALS-GLS solver.
- alsgls.ops.woodbury_chol(F, D)[source]¶
Return (Dinv, C_chol) with the Cholesky factor of C = I + F^T D^{-1} F.
Intended for numerically stable downstream solves that avoid forming C^{-1} explicitly.
- Parameters:
F (ndarray)
D (ndarray)
- Return type:
tuple[ndarray, ndarray]
- alsgls.ops.apply_siginv_to_matrix(M, F, D, *, Dinv=None, C_chol)[source]¶
Right-multiply an (NxK) matrix M by Σ^{-1} using Woodbury.
Σ = F F^T + diag(D), which is never formed densely.
Uses numerically stable Cholesky factorization approach.
- Parameters:
M (ndarray) – (NxK) matrix to right-multiply by Σ^{-1}
F (ndarray) – (Kxk) factor loadings matrix
D (ndarray) – (K,) diagonal noise variances
Dinv (ndarray | None) – Pre-computed 1/D. If None, computed from D.
C_chol (ndarray) – Cholesky factor of C = I + F^T D^{-1} F
- Returns:
M @ Σ^{-1}
- Return type:
np.ndarray
- alsgls.ops.stack_B_list(B_list)[source]¶
Stack list of (p_jx1) blocks into a flat vector.
- Parameters:
B_list (list[ndarray])
- Return type:
ndarray
- alsgls.ops.cg_solve(operator_mv, b, x0=None, maxit=500, tol=1e-07, M_pre=None)[source]¶
Conjugate gradient for SPD operator A (matrix-free).
- Parameters:
operator_mv (Callable[[np.ndarray], np.ndarray]) – Function that returns A @ x for a given x.
b (np.ndarray) – Right-hand side.
x0 (np.ndarray | None) – Initial guess.
maxit (int) – Maximum CG iterations.
tol (float) – Relative residual tolerance.
M_pre (Callable[[np.ndarray], np.ndarray] | None) – Preconditioner application: returns M^{-1} @ r.
- Returns:
Approximate solution. info: Iterations and final residual norm.
- Return type:
x
- Raises:
ValueError – If the operator or the preconditioner turns out not to be positive definite, which conjugate gradients requires.
- alsgls.ops.siginv_diag(F, Dinv, C_chol)[source]¶
Compute the diagonal of Σ^{-1} without forming the inverse.
Σ^{-1} = D^{-1} - D^{-1} F C^{-1} F^T D^{-1}, evaluated from
Dinvand the Cholesky factor ofC.- Parameters:
F (ndarray) – Factor loadings,
(K, k).Dinv (ndarray) – Reciprocal of the diagonal variances, length
K.C_chol (ndarray) – Cholesky factor of the
k x kWoodbury core.
- Returns:
The diagonal entries of Σ^{-1}.
- Return type:
diag_Sinv
- alsgls.ops.apply_siginv_F(F, Dinv, C_chol)[source]¶
Compute Σ^{-1} @ F efficiently using Woodbury.
Σ^{-1} @ F = D^{-1} F - D^{-1} F C^{-1} F^T D^{-1} F
- Parameters:
F (ndarray) – Factor loadings
Dinv (ndarray) – Inverse diagonal
C_chol (ndarray) – Cholesky factor of C = I + F^T D^{-1} F
- Returns:
Σ^{-1} @ F
- Return type:
SinvF
- class alsgls.ops.GramBlocks(Xs)[source]¶
Bases:
objectThe cross-products
X_j' X_llaid out for fast block assembly.Every precision-weighted quantity in the SUR system is a block matrix whose
(j, l)block is a scalar timesX_j' X_l. Rather than keep the K^2 blocks as a list and loop over them per assembly – which at K = 100 and 500 assemblies is five million Python-level block writes, and was the whole cost of the Kackar-Harville correction – the blocks are tiled once into a(p_total, p_total)template, and an assembly is one gather and one elementwise product.- Parameters:
Xs (Sequence[np.ndarray])
- template¶
(p_total, p_total)with block(j, l)equal toX_j' X_l.
- eq_of_row¶
Equation index of each row of the stacked coefficient vector.
- p_list¶
Number of regressors per equation.
- alsgls.ops.gram_blocks(Xs)[source]¶
Prepare
GramBlocksfor the design.- Parameters:
Xs (Sequence[np.ndarray]) – One design matrix per equation.
- Returns:
The tiled cross-products.
- Return type:
- alsgls.ops.assemble_blocks(M, gram)[source]¶
X' (M (x) I_n) Xfor the block-diagonal SUR design and aK x KM.Its
(j, l)block isM[j, l] * X_j' X_l. WithM = Sigma^-1this is the GLS normal matrix; withM = Sigma^-1 (dSigma/dtheta) Sigma^-1it is a Kackar-HarvillePmatrix, and so on.- Parameters:
M (ndarray) – The
K x Kweighting matrix.gram (GramBlocks) – Output of
gram_blocks().
- Returns:
The
(p_total, p_total)assembled matrix.- Return type:
ndarray
- alsgls.ops.df_rescaled(F, D, n, p_list)[source]¶
Rescale a fitted low-rank covariance for the residual degrees of freedom.
Sigma_hatis estimated from residuals of a fitted model, so its entries are biased toward zero by the fitting; the correction every SUR package applies isSigma_ij * n / sqrt((n - p_i)(n - p_j))(linearmodelsdebiased=True, R systemfitmethodResidCov="geomean", Statasureg, dfk). That elementwise scaling isdiag(sqrt(c)) Sigma diag(sqrt(c))withc_i = n / (n - p_i), and since- diag(sqrt(c)) (F F’ + diag(D)) diag(sqrt(c))
= (diag(sqrt(c)) F)(diag(sqrt(c)) F)’ + diag(c * D),
it preserves the low-rank-plus-diagonal structure exactly. Scale row
iofFbysqrt(c_i)andD_ibyc_i.This corrects the bias in
Sigma_hatitself. It does nothing about the variance ofSigma_hat, which is the larger part of the finite-sample shortfall in the plug-in standard errors; seeBootstrapResults.- Parameters:
- Returns:
The rescaled
(F, D).- Raises:
ValueError – If any equation has no residual degrees of freedom.
- Return type:
tuple[np.ndarray, np.ndarray]
- alsgls.ops.compute_XtSigmaInvX(Xs, F, D, lam_B=0.0)[source]¶
Compute (X’Σ⁻¹X + λI) using the Woodbury identity.
For GLS with Σ = FF’ + diag(D), we need the precision-weighted design matrix cross-product for computing coefficient standard errors:
Var(β̂) = (X’Σ⁻¹X + λI)⁻¹
This is exact only when
lam_Bis 0. For a ridge estimator the variance is the sandwichA⁻¹ (X'Σ⁻¹X) A⁻¹withA = X'Σ⁻¹X + λI, and the form above understates it; at the defaultlam_B = 1e-3the difference is negligible, but it grows with the penalty. Σ is also treated as known rather than estimated, which is the usual feasible-GLS convention.Using Woodbury: Σ⁻¹ = D⁻¹ - D⁻¹F C⁻¹ F’D⁻¹ where C = I + F’D⁻¹F
- The block structure gives:
[X’Σ⁻¹X]_{jl} = X_j’ [Σ⁻¹]_{jl} X_l
- Parameters:
- Returns:
(p_total, p_total) matrix where p_total = sum(p_j)
- Return type:
XtSinvX
- alsgls.ops.compute_prediction_variance(Xs, F, D, cov_params, include_residual=True)[source]¶
Compute prediction variances for new observations.
For each observation i and equation j: - Var(E[y_j|X]) = X_j[i,:] @ Cov(β̂_j) @ X_j[i,:] - Var(y_j|X) = Var(E[y_j|X]) + Σ_jj where Σ_jj = ||F[j,:]||² + D[j]
- Parameters:
Xs (Sequence[np.ndarray]) – List of design matrices [X_0, …, X_{K-1}] where X_j is (N_new, p_j)
F (np.ndarray) – (K, k) factor loadings matrix
D (np.ndarray) – (K,) diagonal noise variances
cov_params (np.ndarray) – (p_total, p_total) covariance matrix of parameter estimates
include_residual (bool) – If True, add Σ_jj (residual variance) for prediction intervals. If False, return only the variance of the mean prediction (confidence intervals).
- Returns:
(N_new, K) array of prediction variances
- Return type:
var_pred
- Raises:
ValueError – If
Xsis empty.
alsgls.api module¶
High-level estimator APIs for ALS-based GLS fitting.
- alsgls.api.COV_METHODS = ('kh', 'plugin')¶
Covariance estimators
covariance()accepts.
- class alsgls.api.ALSGLS(*, rank='auto', rank_candidates=None, cv_folds=5, cv_random_state=None, lam_B=0.001, max_sweeps=12, rel_tol=1e-06, d_floor=1e-08, cg_maxit=800, cg_tol=3e-07)[source]¶
Bases:
objectScikit-learn style estimator for low-rank GLS via ALS.
- Parameters:
- score(Xs, Y)[source]¶
Return the negative Gaussian NLL per row of
Yunder the fitted Sigma.Higher is better, as scikit-learn’s
scoreprotocol requires. This scores the whole fitted covariance, not just the conditional mean, so it is not the negative MSE; usealsgls.msefor that.
- covariance(method='kh')[source]¶
Covariance of the coefficients by the named method.
"kh"(default) is the Kackar-Harville corrected covariance, which accounts forSigmabeing estimated;"plugin"is the df-rescaled plug-in that linearmodels, systemfit and Stata report. Measured on the test suite’s 4-equation, rank-1 fixture, the reported standard error is 0.85 (plug-in) and 0.89 (kh) of the actual spread atn = 20, and 0.94 / 0.96 atn = 40. Neither is calibrated atn = 20; for that usebootstrap().- Parameters:
method (str) –
"kh"or"plugin".- Returns:
The
(p_total, p_total)covariance.- Raises:
RuntimeError – If the training designs were not stored.
- Return type:
ndarray
- property cov_params_: ndarray¶
The default coefficient covariance,
covariance("kh").
- bootstrap(B=999, method='parametric', seed=None)[source]¶
Bootstrap the whole fit and return studentised (bootstrap-t) inference.
Each replicate refits
F,Dandbetaon resampled data, which is what captures the part of the sampling variance the plug-incov_params_cannot: thatSigmais estimated. The calibrated object is the percentile-t interval, not the bootstrap standard error; seeBootstrapResults.- Parameters:
B (int) – Number of replicates.
999makes0.05 * (B + 1)an integer, which is what a 95% percentile interval wants;199is enough if only the standard errors are of interest.method (str) –
"parametric"draws errors from the fittedF F' + diag(D);"wild"multiplies each residual row by a Rademacher sign;"residual"resamples whole residual rows. The last two make no distributional assumption.seed (int | None) – Seed for the replicate stream; the same seed gives the same replicates.
- Returns:
The bootstrap distribution and the inference built on it.
- Return type:
- predict_interval(Xs, alpha=0.05, return_type='prediction')[source]¶
Compute prediction or confidence intervals for new observations.
- Parameters:
Xs (Sequence[Any]) – List of design matrices [X_0, …, X_{K-1}] for new observations.
alpha (float) – Significance level (default 0.05 for 95% intervals).
return_type (str) – “prediction” for prediction intervals (includes residual variance), “confidence” for confidence intervals (variance of E[y|X] only).
- Returns:
{“mean”: (N, K), “lower”: (N, K), “upper”: (N, K)}
- Return type:
- Raises:
ValueError – If
return_typeis neither"prediction"nor"confidence", or the design matrices do not match the fitted model in count or in number of columns.
- class alsgls.api.PredictionResults(predicted_mean, se_mean, se_obs, _df, _alpha_default=0.05)[source]¶
Bases:
objectContainer for prediction results with intervals.
- Parameters:
- predicted_mean: ndarray¶
- se_mean: ndarray¶
- se_obs: ndarray¶
- class alsgls.api.ALSGLSSystem(system, *, rank='auto', lam_B=0.001, max_sweeps=12, rel_tol=1e-06, d_floor=1e-08, cg_maxit=800, cg_tol=3e-07)[source]¶
Bases:
objectStatsmodels-style system container for ALS GLS fitting.
- Parameters:
- class alsgls.api.ALSGLSSystemResults(model, estimator)[source]¶
Bases:
objectLightweight results container mimicking
statsmodelsoutputs.- Parameters:
model (ALSGLSSystem)
estimator (ALSGLS)
- covariance(method='kh')[source]¶
Covariance of the coefficients by the named method.
"kh"(default) is the Kackar-Harville corrected covariance, which accounts forSigmabeing estimated;"plugin"is the df-rescaled plug-in that linearmodels, systemfit and Stata report. Measured on the test suite’s 4-equation, rank-1 fixture, the reported standard error is 0.85 (plug-in) and 0.89 (kh) of the actual spread atn = 20, and 0.94 / 0.96 atn = 40. Neither is calibrated atn = 20; for that usebootstrap().- Parameters:
method (str) –
"kh"or"plugin".- Returns:
The
(p_total, p_total)covariance.- Return type:
ndarray
- property cov_params: ndarray¶
The default coefficient covariance,
covariance("kh").
- property bse: ndarray¶
Standard errors of parameter estimates (sqrt of diagonal of cov_params).
- property tvalues: ndarray¶
β = 0.
- Type:
t-statistics for H₀
- property pvalues: ndarray¶
β = 0.
- Type:
Two-sided p-values for H₀
- conf_int(alpha=0.05)[source]¶
Confidence intervals for parameters.
- Parameters:
alpha (float) – Significance level (default 0.05 for 95% CI)
- Returns:
(n_params, 2) array with lower and upper bounds
- Return type:
ci
- get_prediction(exog=None, alpha=0.05)[source]¶
Get prediction results with standard errors and intervals.
- Parameters:
- Returns:
Object with predicted_mean, se_mean, se_obs, and interval methods.
- Return type:
- Raises:
ValueError – If design matrices are not supplied for every equation, or one has the wrong number of columns.
- bootstrap(B=999, method='parametric', seed=None)[source]¶
Bootstrap the whole fit and return studentised (bootstrap-t) inference.
Each replicate refits
F,Dandbetaon resampled data, which is what captures the part of the sampling variance the plug-incov_paramscannot: thatSigmais estimated. The calibrated object is the percentile-t interval, not the bootstrap standard error; seeBootstrapResults.- Parameters:
B (int) – Number of replicates.
999makes0.05 * (B + 1)an integer, which is what a 95% percentile interval wants;199is enough if only the standard errors are of interest.method (str) –
"parametric"draws errors from the fittedF F' + diag(D);"wild"multiplies each residual row by a Rademacher sign;"residual"resamples whole residual rows. The last two make no distributional assumption.seed (int | None) – Seed for the replicate stream; the same seed gives the same replicates.
- Returns:
The bootstrap distribution and the inference built on it.
- Return type:
alsgls.sim module¶
Synthetic data generators for GLS/SUR experiments and tests.
- alsgls.sim.simulate_sur(N_tr, N_te, K, p, k, seed=0)[source]¶
Simulate a Seemingly Unrelated Regression (SUR) dataset.
- Parameters:
- Returns:
lists of per-equation feature matrices of shape
(N_tr, p)and(N_te, p), and response matrices of shape(N_tr, K)and(N_te, K).- Return type:
A tuple
(X_tr, Y_tr, X_te, Y_te)
Notes
Randomness is controlled via
numpy.random.default_rng(seed); pass a differentseedfor different simulations.Examples
>>> from alsgls import simulate_sur >>> Xtr, Ytr, Xte, Yte = simulate_sur(100, 20, K=3, p=5, k=2, seed=42)
- alsgls.sim.simulate_gls(N_tr, N_te, p_list, k, seed=0)[source]¶
Simulate a generalized least squares (GLS) dataset.
This variant allows each response equation to have its own number of features as specified by
p_list.- Parameters:
- Returns:
per-equation feature matrices with
X_tr[j]of shape(N_tr, p_list[j])andX_te[j]of shape(N_te, p_list[j]), and responses of shape(N_tr, K)and(N_te, K)whereK = len(p_list).- Return type:
A tuple
(X_tr, Y_tr, X_te, Y_te)
Notes
Randomness is controlled via
numpy.random.default_rng(seed); pass a differentseedfor different simulations.Examples
>>> from alsgls import simulate_gls >>> p_list = [3, 5, 2] >>> Xtr, Ytr, Xte, Yte = simulate_gls(100, 20, p_list, k=2, seed=0)
alsgls.lsqr_gls module¶
High-accuracy GLS solves via LSQR/LSMR without squaring the condition number.
This module exposes utilities for solving the weighted least-squares problem
min_beta (y - X beta)^T Σ^{-1} (y - X beta)
in the common “low-rank plus diagonal” setting used across the package. The
implementation avoids the explicit normal equations that the in-package CG
routine currently relies on and instead wraps the design matrix inside a
scipy.sparse.linalg.LinearOperator so that the LSQR/LSMR Krylov solvers can
be used directly. In ill-conditioned designs this provides noticeably better
convergence and is less sensitive to round-off.
The implementation follows the write-up in the project documentation and is careful to avoid forming dense KxK matrices except for a skinny SVD of the Woodbury core.
- class alsgls.lsqr_gls.GLSLinearOperator(*args, **kwargs)[source]¶
Bases:
LinearOperatorLinear operator representing
A = W Xfor LSQR/LSMR.- Parameters:
X_dot (Callable[[np.ndarray], np.ndarray])
X_Tdot (Callable[[np.ndarray], np.ndarray])
W (WoodburyWeight)
N (int)
K (int)
P (int)
- class alsgls.lsqr_gls.WoodburyWeight(d, F, d_floor=1e-12, sv_tol=1e-12)[source]¶
Bases:
objectRow-wise operator
WsatisfyingW.T @ W = Σ^{-1}.- Parameters:
d (ndarray | Sequence[float]) – Diagonal of
DinΣ = D + F F^T.F (ndarray | Sequence[float] | None) – Optional factor loadings. If
Noneor empty thenΣis purely diagonal and the action reduces to simple scaling byD^{-1/2}.d_floor (float) – Lower bound applied element-wise to
dto avoid singularities.sv_tol (float) – Relative tolerance used to trim tiny singular values when computing the skinny SVD of
U = D^{-1/2} F.
- alsgls.lsqr_gls.make_block_design_ops(X_blocks)[source]¶
Build
X_dot/X_Tdotcallbacks for SUR-style block designs.- Parameters:
X_blocks (Sequence[ndarray])
- alsgls.lsqr_gls.solve_gls_weighted(X_dot, X_Tdot, y, d, F, *, method='lsmr', atol=1e-10, btol=1e-10, conlim=100000000.0, maxiter=None, verbose=False)[source]¶
Solve
argmin_beta || W (X beta - y) ||_2via LSQR or LSMR.The design is provided through matrix-free callbacks
X_dotandX_Tdotmatching the interfaces used throughout the rest of thealsglspackage. The solver works directly with the GLS geometry and therefore avoids squaring the condition number ofX.- Parameters:
X_dot (Callable[[ndarray], ndarray]) – Callback applying the stacked design to a coefficient vector.
X_Tdot (Callable[[ndarray], ndarray]) – Callback applying its transpose to a residual matrix.
y (ndarray) – Responses,
(N, K).d (ndarray | Sequence[float]) – Diagonal of
DinSigma = D + F F^T.F (ndarray | Sequence[float] | None) – Factor loadings, or None when
Sigmais purely diagonal.method (str) – Krylov solver to use,
"lsmr"or"lsqr".atol (float) – Absolute stopping tolerance passed to the solver.
btol (float) – Relative stopping tolerance passed to the solver.
conlim (float) – Condition-number limit at which the solver gives up.
maxiter (int | None) – Iteration cap, or None for the solver’s own default.
verbose (bool) – Print solver progress.
- Returns:
The concatenated coefficient vector. info: Diagnostics returned by the underlying Krylov solver.
- Return type:
beta
- Raises:
ValueError – If
methodis neither"lsmr"nor"lsqr".
alsgls.rank_selection module¶
Rank selection methods for ALS-GLS: BIC and cross-validation.
- alsgls.rank_selection.select_rank_bic(Xs, Y, k_candidates=None, **als_kwargs)[source]¶
Select rank by minimizing BIC (Bayesian Information Criterion).
BIC(k) = 2 * N * nll_per_row + n_params * log(N)
which is the usual
-2 * loglik + n_params * log(N), sinceN * nll_per_rowis the negative log-likelihood of the sample. The value used to be reported at half this, so it was not comparable with thebicattribute of statsmodels or any other package; the rank chosen is unchanged, because halving is monotone.n_paramscounts the free parameters of the whole fitted model: theK*kfactor loadings inFless thek*(k-1)/2orthogonal rotationsF -> F Qthat leaveF F^Tunchanged and so are not identified, theKdiagonal variances inD, and thesum(p_j)regression coefficients, sincenll_per_rowis evaluated at the fittedbetaand a BIC has to charge for it. This is the standard factor-analysis count; R’sfactanalreports the complementarydf = ((K-k)^2 - K - k) / 2.A rank whose fit raised carries the message in its
errorkey.The count used to be
K*(k+1) + k, which neither subtracted the rotational redundancy nor charged forbeta. Only the first term varies withk, so on the fixtures tested the selected rank is unchanged; the reported value was wrong either way.- Parameters:
- Returns:
The rank with minimum BIC. results: Per-rank dicts of ‘k’, ‘nll’, ‘bic’, ‘n_params’, ‘converged’.
- Return type:
best_k
- Raises:
RuntimeError – If no candidate rank produced a usable fit.
- alsgls.rank_selection.select_rank_cv(Xs, Y, k_candidates=None, n_folds=5, random_state=None, **als_kwargs)[source]¶
Select rank by k-fold cross-validation on validation NLL.
- Parameters:
Xs (list[ndarray]) – Design matrices for each equation.
Y (ndarray) – Response matrix.
k_candidates (list[int] | None) – Candidate ranks to evaluate. Defaults to range(1, min(K//2, 12)+1).
n_folds (int) – Number of cross-validation folds.
random_state (int | Generator | None) – Random state for reproducible fold splits.
**als_kwargs (Any) – Additional arguments passed to als_gls().
- Returns:
The rank with minimum mean CV NLL. results: Per-rank results containing ‘k’, ‘cv_nll’, ‘cv_std’, ‘fold_nlls’.
- Return type:
best_k
- Raises:
ValueError – If
n_foldsis below 2 or exceeds the number of rows.RuntimeError – If no candidate rank produced a usable fit.
alsgls.metrics module¶
Fit metrics for low-rank-plus-diagonal GLS models.
- alsgls.metrics.mse(Y, Yhat)[source]¶
Mean squared error between two matrices.
- Parameters:
Y (ndarray)
Yhat (ndarray)
- Return type:
- alsgls.metrics.nll_per_row(R, F, D)[source]¶
Negative log-likelihood per row for a residual matrix.
Computed for
Runder Σ = F F^T + diag(D) with Gaussian errors.- Parameters:
R (ndarray) – Residual matrix,
(N, K).F (ndarray) – Factor loadings,
(K, k).D (ndarray) – Diagonal variances, length
K.
- Returns:
0.5 * [ tr(R Σ^{-1} R^T)/N + log det(Σ) + K log(2π) ] where N is the number of rows in R.
- Return type: