Porting from grpreg¶
grpreg (Patrick Breheny) is the canonical R package for group
penalized regression — group lasso, group MCP, group SCAD, and
group bridge. If you’re moving group-penalty workflows from R to
Python, this is your guide.
skein’s group MCP / SCAD path implements the same Local Linear
Approximation outer loop that grpreg::grpreg uses, but the inner
solver is parallelized across groups via Rayon. For overlapping
groups (which grpreg doesn’t natively support), see the M6
roadmap.
The three top-line translations¶
|
|
|---|---|
|
|
|
|
|
|
R:
library(grpreg)
fit <- cv.grpreg(X, y, group, penalty = "grMCP", gamma = 3, nfolds = 10)
beta <- coef(fit)[-1]
alpha <- coef(fit)[1]
Python:
import skein_glm
fit = skein_glm.GroupMCPPathCV(groups=g, gamma=3.0, cv=10).fit(X, y)
beta = fit.coef_
alpha = fit.intercept_
Group specification¶
Both libraries accept a length-p integer vector mapping each
feature to its group:
# R: factor or integer; group levels needn't be contiguous.
group <- c(1, 1, 2, 2, 2, 3, 3)
# Python: numpy int64 array; same semantics. Labels can be any
# integers; skein normalizes them internally to contiguous group
# indices.
import numpy as np
g = np.array([1, 1, 2, 2, 2, 3, 3], dtype=np.int64)
For “5 features per group, 10 groups”:
g = np.repeat(np.arange(10), 5) # [0, 0, 0, 0, 0, 1, 1, ..., 9, 9]
For singleton groups (each feature its own group, recovering scalar behavior):
g = np.arange(p)
Penalty map¶
|
|
|---|---|
|
|
|
|
|
(M3.7 wiring; M2.7 surrogate exists) |
|
|
|
(not in v0.1) |
|
(M6 roadmap) |
For sparse-group penalties:
|
|
|---|---|
(not in |
|
(M-step from cMCP) |
|
Family map¶
|
|
|---|---|
|
|
|
|
|
|
|
|
Per-argument map¶
|
|
Notes |
|---|---|---|
|
|
Same. |
|
|
Cox: |
|
|
int label vector. |
|
(choose estimator class) |
See penalty map. |
|
(choose family-prefixed class) |
See family map. |
|
|
Same numerical meaning for MCP. |
|
|
For sparse-group only; not in plain group. |
|
|
numpy array. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Per-group penalty weights. Defaults to |
|
|
Group multiplier — important default difference¶
grpreg::grpreg defaults group.multiplier = sqrt(group sizes) —
the canonical group-size correction that prevents larger groups
from being more aggressively penalized. skein does not apply
this correction by default. If you’re matching grpreg numerics,
pass it explicitly:
import numpy as np
g = np.array([0, 0, 1, 1, 1, 2]) # groups of sizes 2, 3, 1
group_sizes = np.bincount(g)
weights = np.sqrt(group_sizes.astype(float)) # [√2, √3, 1]
fit = skein_glm.GroupLassoPathRegressor(
groups=g, weights=weights, n_lambdas=50,
).fit(X, y)
This is also the default weights= you’d want for
GroupMCPPathRegressor, SparseGroupLasso*, etc. when you want
grpreg-compatible behavior.
The reason skein defaults to ones rather than sqrt(|g|): the
correction is an opinionated choice (good for groups of features
representing the same “thing”, arguably wrong when a single large
group is meaningfully one feature), so we leave it explicit. The
M5.x adaptive weights work will eventually expose a one-shot
helper for the canonical correction.
Workflow translations¶
Group MCP + CV¶
# R
fit <- cv.grpreg(X, y, group, penalty = "grMCP", gamma = 3, nfolds = 10)
beta <- coef(fit)[-1]
print(predict(fit, type = "groups")) # which groups are active
# Python
fit = skein_glm.GroupMCPPathCV(groups=g, gamma=3.0, cv=10).fit(X, y)
beta = fit.coef_
intercept = fit.intercept_
# Which groups are active (any non-zero coef in the group)?
group_ids = np.unique(g)
active_groups = [
int(gid) for gid in group_ids
if np.any(beta[g == gid] != 0)
]
Logistic + group lasso¶
# R
fit <- grpreg(X, y, group, family = "binomial", penalty = "grLasso")
prob <- predict(fit, X_new, type = "response", lambda = 0.05)
# Python
fit = skein_glm.LogisticGroupLassoPathRegressor(
groups=g, n_lambdas=100,
).fit(X, y_bin)
# At a specific λ:
idx = np.argmin(np.abs(fit.lambdas_ - 0.05))
prob_at_0p05 = fit.predict_proba(X_new)[:, idx]
Sparse-group lasso (skein-only)¶
# Mixed across- and within-group sparsity.
fit = skein_glm.SparseGroupLassoPathRegressor(
groups=g, alpha=0.5, n_lambdas=50,
).fit(X, y)
alpha=1 recovers scalar lasso; alpha=0 recovers group lasso;
intermediate values give “some groups are partially active.”
Survival with group penalty¶
# R
fit <- grpsurv(X, y = Surv(time, event), group, penalty = "grMCP")
# Python
fit = skein_glm.CoxGroupMCPPathRegressor(
groups=g, gamma=3.0, n_lambdas=50,
).fit(X, time, event)
risk = fit.predict(X_new)
Things grpreg does that skein doesn’t yet¶
Group bridge (
gBridge): M6 roadmap.Group exponential lasso (
gel): not in v0.1.returnX = TRUE(return the standardized X used internally): not in v0.1, but you can recover this fromStandardized<D>if needed.
Things skein does that grpreg doesn’t¶
Sparse-group penalties (convex and nonconvex).
grpreghas composite MCP (cMCP) which is related but not the same as sparse-group MCP.Rayon-parallel block CD across groups.
grpregis single- threaded.Three weight axes (per-sample, per-feature, per-group) composable.
scipy.sparse.csc_matrixdesign matrices native.Memory-mapped / chunked design matrices for very large
n.The dual extension surface for prototyping custom group penalties.
See also¶
Porting from glmnet — for scalar-penalty users.
Porting from ncvreg — for nonconvex scalar users.
Concepts: Penalties — group, sparse- group, and the LLA outer loop.
Concepts: Weights — the per-group weight axis in detail.