statspai.selection¶
selection ¶
Variable selection tools.
stepwise: Stepwise regression with AIC/BIC/p-value criterialasso_select: LASSO-based variable selection with coordinate descentbest_subset: exact best-subset selection by branch and boundglmnet: lasso / ridge / elastic net with the conventions of Rglmnet(penalty path, penalty factors,lambda.min/lambda.1se)shrinkage: ridge / lasso / principal-components prediction, tuned by cross-validation
GlmnetResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:glmnet.
Attributes:
| Name | Type | Description |
|---|---|---|
params |
Series
|
Coefficients at the selected penalty, on the scale of the data. |
intercept |
float
|
|
penalty |
float
|
The selected |
lambda_min, lambda_1se |
float
|
The penalty with the smallest cross-validated error, and the
largest one within a standard error of it ( |
path |
DataFrame
|
One row per penalty: |
coefficients |
DataFrame
|
Coefficients along the path, one row per penalty, |
cv |
DataFrame or None
|
|
Examples:
>>> import numpy as np, pandas as pd, statspai as sp
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(200, 8))
>>> df = pd.DataFrame(X, columns=[f"x{j}" for j in range(8)])
>>> df["y"] = 2 * X[:, 0] - X[:, 1] + rng.normal(size=200)
>>> fit = sp.glmnet(df, "y", [f"x{j}" for j in range(8)], seed=0)
>>> isinstance(fit, sp.GlmnetResult)
True
>>> bool(fit.lambda_1se >= fit.lambda_min)
True
>>> fit.predict(df.head(3)).shape
(3,)
ShrinkageResult
dataclass
¶
Bases: ResultProtocolMixin
Fit returned by :func:statspai.shrinkage.
Attributes:
| Name | Type | Description |
|---|---|---|
method |
str
|
|
params |
Series
|
Coefficients of the standardised predictors. |
intercept |
float
|
Mean of the outcome on the estimation sample. |
penalty |
float or None
|
The ridge / lasso penalty of the reported fit. |
n_components |
int or None
|
Number of principal components of the reported fit. |
cv |
DataFrame or None
|
One row per candidate tuning value: the value, the cross-validated mean squared prediction error and its square root. |
cv_rmspe |
float or None
|
Cross-validated root MSPE of the reported fit. |
rmspe_in |
float
|
In-sample root mean squared error (no degrees-of-freedom correction). |
n_obs, n_predictors, n_nonzero |
int
|
|
Examples:
>>> import numpy as np, pandas as pd, statspai as sp
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(120, 8))
>>> df = pd.DataFrame(X, columns=[f"x{j}" for j in range(8)])
>>> df["y"] = X[:, 0] - X[:, 1] + rng.normal(size=120)
>>> fit = sp.shrinkage(df, "y", [f"x{j}" for j in range(8)], method="ridge")
>>> type(fit).__name__
'ShrinkageResult'
>>> fit.predict(df.head(2)).shape
(2,)
>>> bool(fit.cv_rmspe > 0 and fit.penalty in set(fit.cv["penalty"]))
True
SelectionResult
dataclass
¶
Bases: ResultProtocolMixin
Result container for variable selection procedures.
Attributes:
| Name | Type | Description |
|---|---|---|
selected |
list[str]
|
Variable names retained in the final model. |
dropped |
list[str]
|
Variable names excluded from the final model. |
history |
DataFrame
|
Step-by-step log of the selection procedure. |
final_model |
dict
|
Summary statistics for the final model (R², adj-R², AIC, BIC, n, k). |
method |
str
|
Selection method used ( |
coefficients |
dict | None
|
Coefficient estimates from the final model (name -> value). |
lasso_path |
dict | None
|
For LASSO: |
Examples:
>>> import statspai as sp
>>> df = sp.cps_wage()
>>> res = sp.stepwise(df, y="log_wage",
... x=["female", "education", "experience"],
... method="forward", criterion="aic", verbose=False)
>>> isinstance(res, sp.SelectionResult)
True
>>> sorted(res.final_model.keys())
['adj_r_squared', 'aic', 'bic', 'k', 'n', 'r_squared']
plot ¶
Plot selection diagnostics.
For stepwise: criterion value at each step. For LASSO: coefficient path across lambda values.
Returns:
| Type | Description |
|---|---|
(fig, ax) : matplotlib Figure and Axes
|
|
glmnet ¶
glmnet(data: DataFrame, y: str, x: Sequence[str], *, alpha: float = 1.0, lambda_: Union[None, float, Sequence[float]] = None, family: str = 'gaussian', penalty_factor: Optional[Sequence[float]] = None, cv: bool = True, n_folds: int = 10, foldid: Union[None, str, Sequence[int]] = None, rule: str = 'min', n_lambda: int = 100, lambda_min_ratio: Optional[float] = None, standardize: bool = True, intercept: bool = True, seed: Optional[int] = None, tol: float = 1e-18, max_iter: int = 100000) -> GlmnetResult
Lasso, ridge and elastic net with the conventions of R glmnet.
Minimises, over the intercept and the coefficients,
-loglik / n + lambda * sum_j v_j (alpha |b_j| + (1 - alpha) b_j^2 / 2)
(for a Gaussian outcome -loglik / n is RSS / (2 n)) along a
decreasing path of lambda, and chooses lambda by K-fold
cross-validation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
Rows with a missing outcome or predictor are dropped. |
required |
y
|
str
|
Outcome. For |
required |
x
|
sequence of str
|
Predictors, in their original units. |
required |
alpha
|
float
|
Mixing weight: 1 is the lasso, 0 the ridge. |
1.0
|
lambda_
|
float or sequence of float
|
Penalties to fit. |
None
|
family
|
('gaussian', 'binomial')
|
|
'gaussian'
|
penalty_factor
|
sequence of float
|
Multiplier |
None
|
cv
|
bool
|
Cross-validate. With |
True
|
n_folds
|
int
|
|
10
|
foldid
|
sequence of int or str
|
Fold label of each row (or a column holding it). Without it rows
are assigned at random with |
None
|
rule
|
('min', '1se')
|
Which penalty |
'min'
|
n_lambda
|
int
|
|
100
|
lambda_min_ratio
|
float
|
Default |
None
|
standardize
|
bool
|
Scale predictors to unit standard deviation (divisor |
True
|
intercept
|
bool
|
|
True
|
seed
|
int
|
|
None
|
tol
|
float
|
Convergence threshold of coordinate descent, on the largest
squared coefficient change (standardised units). Much tighter than
|
1e-18
|
max_iter
|
int
|
|
100000
|
Returns:
| Type | Description |
|---|---|
GlmnetResult
|
|
Notes
Conventions shared with glmnet:
- Predictors are scaled by the standard deviation with divisor
n. - A Gaussian outcome is scaled to unit variance before the penalty is
applied and
lambdais reported on the original scale. The lasso part does not notice; the ridge part does, so in original units the ridge term islambda / sd(y) * (1 - alpha) / 2 * b^2. This is whyglmnet(alpha = 0)is not the textbook ridge at the samelambda. - The first point of a path built here is the fit with an infinite
penalty, and the path stops early once the deviance explained no
longer improves. For the ridge, where no finite penalty sets the
coefficients to zero, the first point is therefore the null model
(coefficients of order 1e-36, all counted in
df) although it carries a finite label. - Cross-validation refits the whole path, standardisation included,
on each training set. With a path built here every training set
builds its own and is read at the full-sample penalties by linear
interpolation in
lambda; withlambda_supplied every training set is fitted at exactly those penalties. The error is the mean squared error (Gaussian) or the binomial deviance with probabilities held in[1e-5, 1 - 1e-5], averaged within fold;cvsdis the standard error of the fold means, weighted by fold size.
:func:statspai.shrinkage is the other penalised regression in the
package: ridge, lasso and principal components with the penalty stated
on the residual sum of squares, for comparing predictors by
cross-validated error. Use this function when the penalty has to mean
what it means in glmnet, or for the elastic net, a binomial
outcome, penalty factors, or lambda.1se.
Inference after selection is a different problem: see
:func:statspai.rlasso_effect and :func:statspai.dml.
Examples:
>>> import numpy as np, pandas as pd, statspai as sp
>>> rng = np.random.default_rng(0)
>>> X = rng.normal(size=(200, 8))
>>> cols = [f"x{j}" for j in range(8)]
>>> df = pd.DataFrame(X, columns=cols)
>>> df["y"] = 2 * X[:, 0] - X[:, 1] + rng.normal(size=200)
>>> fit = sp.glmnet(df, "y", cols, alpha=0.5, seed=0)
>>> bool(fit.params["x0"] > 1.5)
True
>>> sparse = fit.coef("lambda.1se")
>>> bool((sparse != 0).sum() <= (fit.coef("lambda.min") != 0).sum())
True
References
[@friedman2010regularization], [@zou2005regularization], [@zou2006adaptive]
lasso_select ¶
lasso_select(data: DataFrame, y: str, x: list[str], method: Literal['cv', 'bic', 'aic'] = 'cv', n_folds: int = 10, n_lambda: int = 100, eps: float = 0.001, max_iter: int = 1000, tol: float = 1e-06, verbose: bool = True, seed: int = 42) -> SelectionResult
LASSO-based variable selection.
Solves min (1/2n)||y - Xβ||² + λ||β||₁ via coordinate descent. Selects λ by K-fold cross-validation, BIC, or AIC.
With method="cv" every training fold is centred and scaled on its
own rows and the error is pooled over observations. On the same folds
and penalties this is the cross-validated error of sp.glmnet
(lasso_path["cv_mse"]). In a small sample that error curve can be
nearly flat, and the selected λ then changes with the fold assignment;
compare a few values of seed before reading much into the list of
selected variables. sp.glmnet also reports lambda.1se.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
y
|
str
|
Dependent variable column. |
required |
x
|
list[str]
|
Candidate independent variables. |
required |
method
|
('cv', 'bic', 'aic')
|
How to choose the regularisation parameter λ. |
"cv"
|
n_folds
|
int
|
Number of cross-validation folds (only for |
10
|
n_lambda
|
int
|
Number of λ values in the grid. |
100
|
eps
|
float
|
Ratio of lambda_min / lambda_max. |
0.001
|
max_iter
|
int
|
Maximum coordinate descent iterations per λ. |
1000
|
tol
|
float
|
Convergence tolerance. |
1e-06
|
verbose
|
bool
|
Print progress. |
True
|
seed
|
int
|
Random seed for CV fold assignment. |
42
|
Returns:
| Type | Description |
|---|---|
SelectionResult
|
|
Examples: