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.
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:
Step 2 — Group models. For each group
The parameter
-
$\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
Supports gaussian, binomial, and multinomial families.
pip install ptlassoRequires Python ≥ 3.9 and adelie for the underlying Lasso solver, which supports fitting with offsets (unlike scikit-learn).
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# 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)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: |Ŝ|=6The 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)result = model.evaluate(X_test, y_test, groups_test)
# {"pretrain": {"predictions": ..., "score": ...},
# "individual": {"predictions": ..., "score": ...},
# "overall": {"predictions": ..., "score": ...}}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-dictcv = 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")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-modelsPretrainedLasso 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.
Full documentation at thomaslemenestrel.com/ptlasso.
@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}
}MIT