Pretrained Lasso: a two-step procedure for sparse linear models with grouped samples.
Project description
ptlasso
Python implementation of the Pretrained Lasso — a two-step procedure for fitting sparse linear models when samples belong to distinct groups, leveraging shared structure across groups via pretraining.
Use ptlasso when your dataset has natural groups (cohorts, sites, populations) and you want group-specific models that still borrow strength from a shared signal — outperforming both a single pooled model and fully independent per-group models.
Craig, E., Pilanci, M., Le Menestrel, T., Narasimhan, B., Rivas, M. A., Gullaksen, S. E., ... & Tibshirani, R. (2025). Pretraining and the lasso. Journal of the Royal Statistical Society Series B: Statistical Methodology, qkaf050.
How it works
Standard group-specific Lasso models are fit independently per group, ignoring shared signal. The Pretrained Lasso fits in two steps:
Step 1 — Overall model. Fit a Lasso on all samples to capture shared structure:
$$\hat{\beta}^{\text{overall}} = \arg\min_\beta \frac{1}{2n}|y - X\beta|^2 + \lambda|\beta|_1$$
Step 2 — Group models. For each group $k$, fit a Lasso with an offset equal to $\alpha$ times the overall model's linear predictor:
$$\hat{\beta}^{(k)} = \arg\min_\beta \frac{1}{2n_k}|y^{(k)} - \underbrace{\alpha \cdot X^{(k)}\hat{\beta}^{\text{overall}}}_{\text{offset}} - X^{(k)}\beta|^2 + \lambda_k|\beta|_1$$
The parameter $\alpha \in [0, 1]$ controls the pretraining strength:
- $\alpha = 0$: group models explain residuals from the overall model (maximum pretraining)
- $\alpha = 1$: independent per-group models (no pretraining)
- $\alpha \in (0, 1)$: group models are anchored to the overall fit
Final prediction for group $k$: $\hat{y}^{(k)} = \alpha \cdot X^{(k)}\hat{\beta}^{\text{overall}} + X^{(k)}\hat{\beta}^{(k)}$
Supports gaussian, binomial, and multinomial families.
Installation
pip install ptlasso
Requires Python ≥ 3.9 and adelie for the underlying Lasso solver, which supports fitting with offsets (unlike scikit-learn).
Quick start
import numpy as np
from ptlasso import PretrainedLasso, PretrainedLassoCV
rng = np.random.default_rng(42)
n, p, k = 300, 100, 3
X = rng.standard_normal((n, p))
groups = rng.integers(0, k, size=n)
beta = np.zeros(p)
beta[:5] = [2, -1.5, 1, -0.8, 0.5]
y = X @ beta + 0.5 * rng.standard_normal(n)
# Fixed alpha
model = PretrainedLasso(alpha=0.5)
model.fit(X, y, groups)
print(model)
PretrainedLasso(alpha=0.5, family='gaussian', overall_lambda='lambda.1se', fit_intercept=True, lmda_path_size=100)
family : gaussian
n_features : 100
n_groups : 3
overall |Ŝ| : |Ŝ| = 6 / 100 [0, 1, 2, 3, 4, ...]
pretrain |Ŝ| : 0: |Ŝ|=36, 1: |Ŝ|=42, 2: |Ŝ|=19
overall λ : lambda.1se (idx 34)
print("R²:", model.score(X, y, groups))
# R²: 0.9827
# Cross-validate over alpha
cv = PretrainedLassoCV(alphas=[0.0, 0.25, 0.5, 0.75, 1.0])
cv.fit(X, y, groups)
print("Best alpha:", cv.alpha_)
# Best alpha: 0.0
Families
# Binary classification
model = PretrainedLasso(alpha=0.5, family="binomial")
model.fit(X, y_binary, groups)
probs = model.predict(X, groups) # shape (n,), P(y=1)
# Multi-class classification (integer labels 0..K-1)
model = PretrainedLasso(alpha=0.5, family="multinomial")
model.fit(X, y_multiclass, groups)
probs = model.predict(X, groups) # shape (n, K)
Feature names and group labels
Both fit() methods accept human-readable names. pandas DataFrames are supported natively — column names are picked up automatically.
import pandas as pd
X_df = pd.DataFrame(X, columns=[f"gene_{i}" for i in range(p)])
group_labels = {0: "control", 1: "treated_A", 2: "treated_B"}
model = PretrainedLasso(alpha=0.5)
model.fit(X_df, y, groups, group_labels=group_labels)
# overall |Ŝ| : |Ŝ| = 5 / 100 [gene_0, gene_1, gene_2, gene_3, gene_4]
# pretrain |Ŝ| : control: |Ŝ|=5, treated_A: |Ŝ|=4, treated_B: |Ŝ|=6
Inspecting the support
The support is the set of non-zero variables selected by the model.
from ptlasso import (
get_overall_support,
get_pretrain_support,
get_pretrain_support_split,
get_individual_support,
)
get_overall_support(model) # features from the overall model
get_pretrain_support(model) # union across pretrained group models
get_pretrain_support(model, common_only=True) # features selected by >50% of groups
get_pretrain_support(model, groups=[0, 1]) # restrict to specific groups
get_individual_support(model) # features from no-pretraining baselines
common, indiv = get_pretrain_support_split(model)
# common : features from the overall model (stage 1)
# indiv : additional features picked up by group models (stage 2)
Evaluating all sub-models at once
result = model.evaluate(X_test, y_test, groups_test)
# {"pretrain": {"predictions": ..., "score": ...},
# "individual": {"predictions": ..., "score": ...},
# "overall": {"predictions": ..., "score": ...}}
Retrieving coefficients
coefs = model.get_coef() # all sub-models
coefs["overall"] # {"coef": ndarray, "intercept": ndarray}
coefs["pretrain"]["control"] # {"coef": ndarray, "intercept": ndarray}
coefs["individual"]["treated_A"]
model.get_coef(model="pretrain") # just pretrain sub-dict
CV details
cv = PretrainedLassoCV(
alphas=[0.0, 0.25, 0.5, 0.75, 1.0],
n_folds=10,
alphahat_choice="overall", # or "mean" (unweighted mean of per-group CV errors)
family="gaussian",
overall_lambda="lambda.1se", # or "lambda.min"
foldid=my_foldid, # optional: custom integer fold assignments
)
cv.fit(X, y, groups)
cv.alpha_ # globally best alpha
cv.varying_alphahat_ # {group: best_alpha} per group
cv.cv_results_ # {alpha: mean CV loss}
cv.cv_results_se_ # {alpha: SE of CV loss}
cv.cv_results_per_group_ # {alpha: {group: mean CV loss}}
cv.cv_results_mean_ # {alpha: unweighted mean of per-group losses}
cv.cv_results_wtd_mean_ # {alpha: size-weighted mean of per-group losses}
cv.cv_results_individual_ # CV loss for individual (no-pretraining) baseline
cv.cv_results_overall_ # CV loss for overall model baseline
cv.best_estimator_ # PretrainedLasso fitted with alpha_
cv.all_estimators_ # {alpha: PretrainedLasso} for varying-alpha prediction
# Predict using each group's own best alpha
cv.predict(X, groups, alphatype="varying")
cv.evaluate(X, y, groups, alphatype="varying")
Plotting
from ptlasso import plot_cv, plot_paths
plot_cv(cv) # CV loss curve over alpha with ±1 SE band
plot_paths(model) # regularisation paths for all sub-models
Saving and loading models
PretrainedLasso and PretrainedLassoCV can be serialised with joblib:
import joblib
joblib.dump(model, "model.pkl")
model = joblib.load("model.pkl")
Avoid
pickledirectly — the underlying adelie solver stores C++ objects that are not natively picklable. ptlasso handles this transparently through joblib. Usingpickledirectly will raise aTypeError.
API reference
Full documentation at thomaslemenestrel.com/ptlasso.
Citation
@article{craig2025pretraining,
title = {Pretraining and the lasso},
author = {Craig, Erin and Pilanci, Mert and Le Menestrel, Thomas and Narasimhan, Balasubramanian and Rivas, Manuel A. and Gullaksen, Stein-Erik and Tibshirani, Robert},
journal = {Journal of the Royal Statistical Society Series B: Statistical Methodology},
pages = {qkaf050},
year = {2025}
}
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ptlasso-0.8.0.tar.gz.
File metadata
- Download URL: ptlasso-0.8.0.tar.gz
- Upload date:
- Size: 40.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
920478ee4fcb543ba86b6192eecf5b6855d97697d4e58ce73edf97fc3a08cad3
|
|
| MD5 |
74ed00ab05004a28b1cc43679bb6b0a3
|
|
| BLAKE2b-256 |
dbe3bfe55ed2ee5a4ad722283dd8f554b31b936d72377d4518e4c45bc1848c2a
|
Provenance
The following attestation bundles were made for ptlasso-0.8.0.tar.gz:
Publisher:
publish.yml on thomaslemenestrel/ptlasso
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ptlasso-0.8.0.tar.gz -
Subject digest:
920478ee4fcb543ba86b6192eecf5b6855d97697d4e58ce73edf97fc3a08cad3 - Sigstore transparency entry: 1618766205
- Sigstore integration time:
-
Permalink:
thomaslemenestrel/ptlasso@ed6da142e41817d4e8ca5c3df70a436aa32f5506 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/thomaslemenestrel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ed6da142e41817d4e8ca5c3df70a436aa32f5506 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ptlasso-0.8.0-py3-none-any.whl.
File metadata
- Download URL: ptlasso-0.8.0-py3-none-any.whl
- Upload date:
- Size: 38.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6b907429f723717ed1fe892071c520f1b243dadfbc262c6f8ea099d38e74f515
|
|
| MD5 |
73643d8a087dd24ae8c15bf027864171
|
|
| BLAKE2b-256 |
22cf209121a2695bf8a1e974b0f30d58233f439a9ae0f941a3dfc494e86dc88b
|
Provenance
The following attestation bundles were made for ptlasso-0.8.0-py3-none-any.whl:
Publisher:
publish.yml on thomaslemenestrel/ptlasso
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ptlasso-0.8.0-py3-none-any.whl -
Subject digest:
6b907429f723717ed1fe892071c520f1b243dadfbc262c6f8ea099d38e74f515 - Sigstore transparency entry: 1618766368
- Sigstore integration time:
-
Permalink:
thomaslemenestrel/ptlasso@ed6da142e41817d4e8ca5c3df70a436aa32f5506 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/thomaslemenestrel
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ed6da142e41817d4e8ca5c3df70a436aa32f5506 -
Trigger Event:
push
-
Statement type: