oal() estimates a propensity score by the outcome-adaptive lasso (OAL) of
Shortreed and Ertefaie (2017) or its generalized elastic-net form (GOAL) of
Balde, Yang and Lefebvre (2023): an adaptive lasso on the treatment
(propensity score) log-likelihood whose penalty weights come from an
outcome regression, so that covariates unrelated to the outcome –
instruments and noise variables – are excluded from the propensity score
model. Tuning parameters are selected by the papers' weighted absolute mean
difference (wAMD) balance criterion. The result is deliberately modest: a
numeric score vector designed to be handed to MatchIt::matchit() as
distance, to WeightIt::weightit() as ps, or to psAve::psave() as an
appended candidate.
Usage
oal(
formula,
data,
outcome,
method = c("oal", "goal"),
estimand = c("ATE", "ATT"),
family = gaussian(),
lambda = c(-10, -5, -2, -1, -0.75, -0.5, -0.25, 0.25, 0.49),
gamma = NULL,
gamma.factor = 2,
lambda2 = c(0, 10^c(-2, -1.5, -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 1)),
outcome.coef = NULL,
refit = FALSE,
clip = c(0.01, 0.99),
keep.path = TRUE,
keep.fits = FALSE,
verbose = FALSE,
...
)Arguments
- formula
A two-sided formula
treat ~ x1 + x2 + ..., exactly as inMatchIt::matchit(). The right-hand side defines the propensity score covariates; the same covariates (plus the treatment) form the outcome model that supplies the adaptive penalty weights.- data
A data frame containing the variables in
formulaandoutcome. Complete cases in all used variables are REQUIRED; any missing value is an error naming the offending variables, never a silent row drop.- outcome
A one-sided formula
~ ynaming the outcome variable (exactly one variable). Required unlessoutcome.coefis supplied, in which case it is unused. A two-sided formula is an error: OAL derives its penalty weights from an outcome model on the same covariates as the propensity score model, so there is no separate outcome design to specify.- method
"oal"(default): the outcome-adaptive lasso;"goal": the generalized outcome-adaptive lasso, which adds an elastic-net \(\lambda_2 \sum_j \alpha_j^2\) term (grouping effect and numerical stability under correlated covariates and near-positivity violations).- estimand
"ATE"(default) or"ATT"; determines the inverse-probability weights used inside the wAMD criterion and returned inweights. The ATE default is Shortreed and Ertefaie's wAMD weighting and deliberately differs frompsAve::psave()'s ATT default.- family
The outcome (weight) model family:
gaussian()(default; the specification validated by the papers' simulations) orbinomial().binomial()emits a one-time warning: binomial outcome models are beyond the simulations validated by Shortreed and Ertefaie (2017); GLM theory per Balde (2025).- lambda
Numeric vector of EXPONENTS \(\delta\); the penalties actually applied are \(\lambda_n = n^{\delta}\). The default is Shortreed and Ertefaie's grid
c(-10, -5, -2, -1, -0.75, -0.5, -0.25, 0.25, 0.49). A guard warns whenmax(lambda) > 3: such values look like raw penalty values, not exponents (convert a raw grid viadelta = log(lambda_n)/log(n)).- gamma
NULL(default) pairs \(\gamma\) with each \(\delta\) as \(\gamma = 2(\code{gamma.factor} - \delta + 1)\) (the Shortreed-Ertefaie/rejoinder convention); a single number (e.g.2.5) fixes \(\gamma\) and crosses it with the wholelambdagrid (the Schnitzer-style fixed-\(\gamma\) mode). No vector form is accepted.- gamma.factor
The convergence factor
gcfin the pairing formula (default2); ignored whengammais given.- lambda2
Numeric grid of elastic-net ridge constants for
method = "goal"only (supplying it otherwise is an error). Defaultc(0, 10^c(-2, -1.5, -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 1))– the author's published grid (Balde 2025 supplement, "taken from Zou and Hastie (2005)"), verified against the official GOAL code on 2026-07-02;0is always a member and nests plain OAL exactly.lambda2is selected jointly with(lambda, gamma)by the wAMD.- outcome.coef
The extension hook: a NAMED numeric vector of outcome coefficients \(b_j\) over the standardized model-matrix columns (names must exactly cover the columns), overriding the internal outcome model. The values are interpreted on the standardized scale of the internal design matrix – adaptive weights are scale-dependent. All values must be finite and not all zero. Zeros ARE allowed and mean a hard drop: \(|0|^{-\gamma} = \infty\) and glmnet natively converts an infinite penalty factor to an exclusion. No epsilon or cap is ever applied. Using this hook flips the provenance label to "screening use only" (see Details) and sets
info$gamma.mode = "user-coef". Ifoutcomeis also supplied, it is still validated and the outcome-leakage guard still runs (the outcome variable may not appear among the PS covariates); ifoutcomeis omitted, the package cannot detect outcome leakage informula– ensuring the coefficients were derived without post-treatment or outcome information is then entirely the caller's responsibility.- refit
FALSE(default): the propensity score comes from the penalized fit itself (the Shortreed-Ertefaie/rejoinder convention).TRUE: at every grid point an unpenalized logistic regression is refit on the selected covariates, and both the propensity score and the wAMD come from the refit (the Schnitzer et al. 2025 variant, adapted from a longitudinal setting); per-grid-point convergence is recorded inpath$refit.convergedand any non-convergence triggers one warning – never a silent fallback.- clip
Length-2 numeric: propensity scores are clipped to
[clip[1], clip[2]]BEFORE the wAMD IPW weights are formed and in the returnedps(defaultc(0.01, 0.99), equal topsAve::psave()'s default so that psave's re-clipping is a no-op). The default is a Shortreed-Ertefaie-lineage safety choice; Balde's official reference code runs UNCLIPPED weights (1/e,1/(1 - e)with no truncation). To reproduce that behavior, effectively disable clipping withclip = c(1e-12, 1 - 1e-12).- keep.path
If
TRUE(default), the full per-grid-point results are stored inpath,sets, andcoef.path.- keep.fits
If
TRUE, the fitted glmnet paths, the outcome model, and (withrefit = TRUE) the selected refit are retained infits. DefaultFALSE.- verbose
If
TRUE, progress messages report the grid evaluation and the selected point.- ...
Reserved for future use; supplying unused arguments triggers a warning.
Value
An object of class "oal": a list with components
psnumeric(n), named by
rownames(data): the propensity score, clipped toclip, strictly in (0, 1) – the deliverable.weightsnumeric(n): the IPW weights at
estimandimplied byps.coefficientsnamed numeric(p+1): selected propensity score coefficients on the ORIGINAL covariate scale (intercept first).
coefficients.stdthe same on the standardized scale actually optimized.
selecteddata frame, one row per model-matrix column:
term,outcome.coef(signed \(b_j\), standardized scale),penalty.factor,coef(original scale),selected(logical), androle("retained"/"excluded").lambdanamed numeric:
delta,lambda.n(\(= n^\delta\); \(= (n + p)^\delta\) on GOAL grid points with \(\lambda_2 > 0\)),gamma,lambda2(NAformethod = "oal") at the selected point.criterion,criterion.value"wamd"and its value at the selection.pathdata frame (or
NULLwithoutkeep.path), one row per grid point in grid order:lambda2,delta,lambda.n,gamma,s(the exact glmnetsused),wamd,n.selected,refit.converged(NAunlessrefit),selected.setslist (or
NULL): the selected variable set at every grid point.coef.path(p+1) x G matrix (or
NULL): propensity score coefficients across the grid, original scale.outcome.coefnamed numeric(p): the \(b_j\) actually used (internal or user), standardized scale.
outcome.modelcompact list: coefficient table,
family, and alabel("lm(y ~ A + X)","glm(y ~ A + X, binomial)", or"user-supplied"); the full fit object only underkeep.fits.balancedata frame per ORIGINAL covariate column:
smd.un,smd.wt,ks.un,ks.wtvia cobalt – identical layout topsAve::psave()$balance.provenancethe fixed provenance label, printed first by
print()/summary().treatinteger(n) 0/1 treatment as coerced.
covsn x p numeric original-scale model matrix with
attr(, "bin.vars").estimand,method,refit,clipas resolved.
formula,data,terms,xlevelsstored to power
oal_match(),oal_weight()andpredict.oal().fitslist (or
NULL):glmnet(one path per \((\lambda_2, \gamma)\)),outcome,refit– iffkeep.fits.infolist:
n,n.treated,p,grid.size,family,gamma.mode("paired"/"fixed"/"user-coef"),center,scale,contrasts,glmnet.version,oalasso.version.callthe matched call.
Details
Objective and exact glmnet evaluation
OAL solves, on the TOTAL log-likelihood scale,
$$\hat\alpha = \arg\min_\alpha\; -\ell(\alpha; A, X_s)
+ \lambda_n \sum_j |b_j|^{-\gamma} |\alpha_j|,$$
where \(\ell\) is the binomial log-likelihood of treatment on the
standardized covariates and \(b_j\) are the outcome-model coefficients.
glmnet minimizes the per-observation loss and internally rescales
penalty factors to sum to the number of variables, so each grid point is
evaluated exactly at
$$s = \overline{\mathrm{pen}} \cdot \lambda_n / n, \qquad
\mathrm{pen}_j = |b_j|^{-\gamma},$$
via coef(fit, s = s, exact = TRUE, x = , y = , penalty.factor = ), which
re-solves at that penalty (the KKT-verified recipe of the
Shortreed/Ertefaie rejoinder code). When outcome.coef contains zeros,
some \(\mathrm{pen}_j = \infty\): glmnet converts those to exclusions and
internally resets their factor to 1 before the sum-to-nvars rescale, so
the constant generalizes to
\(s = (\sum_{j: \mathrm{finite}} \mathrm{pen}_j + \#\{\mathrm{pen}_j =
\infty\})/p \cdot \lambda_n / n\) – identical to
\(\overline{\mathrm{pen}}\,\lambda_n/n\) when all factors are finite.
This correction is always on; it is what makes the \(n^\delta\) grid
carry its published meaning.
Standardization protocol (fixed, not an argument)
The model matrix X <- model.matrix(formula, data)[, -1] (default
treatment contrasts) is standardized ONCE, Xs <- scale(X, TRUE, TRUE),
all columns including dummies (the rejoinder convention). The outcome model
AND glmnet are both fit on this SAME Xs with
glmnet(standardize = FALSE). Per-subset scaling is structurally
impossible. Coefficients are back-transformed for output:
\(\alpha_j = \alpha^{std}_j / s_j\) and
\(\alpha_0 = \alpha^{std}_0 - \sum_j \alpha^{std}_j c_j / s_j\), with
centers \(c\) and scales \(s\) stored in info.
Weight (outcome) model and the degenerate-beta policy
By default \(b\) comes from lm(y ~ A + Xs) (or
glm(y ~ A + Xs, binomial)) on the FULL sample, both arms pooled – the
Shortreed-Ertefaie/rejoinder convention (not control-arm-only). Only the
covariate coefficients are used; the treatment coefficient is discarded and
the treatment is never penalized anywhere. Any NA or exactly zero
internal coefficient is an ERROR naming the columns: OLS betas are almost
surely nonzero, so an exact zero or NA signals rank deficiency or
aliasing that would silently corrupt the penalty-scale correction. Remove
the collinear columns or supply outcome.coef.
Tuning grid and the \(\gamma\) pairing
lambda supplies exponents \(\delta\) with
\(\lambda_n = n^{\delta}\), n = nrow(model.matrix) always. With
gamma = NULL each \(\delta\) is paired with
\(\gamma = 2(\mathrm{gcf} - \delta + 1)\) (default
\(\mathrm{gcf} = 2\)), the formula derivable from
\(\lambda_n\, n^{\gamma/2 - 1} = n^{\mathrm{gcf}}\); there are no
\(\lambda \times \gamma\) cross-products in paired mode. A scalar
gamma is crossed with all \(\delta\) (Schnitzer's \(\gamma = 2.5\)
is reachable this way; convert their raw \(\lambda\) grid via
\(\delta = \log \lambda_n / \log n\)).
GOAL
For \(\lambda_2 > 0\) the elastic-net term is solved by the Zou-Hastie
augmentation: \(X_{aug} = \mathrm{rbind}(X_s, \sqrt{\lambda_2} I_p)\)
with \(p\) pseudo-responses 0, followed by rescaling ALL coefficients
INCLUDING the intercept by \((1 + \lambda_2)\) (Balde 2025 supplement:
\(PS = \mathrm{expit}(\mathrm{cbind}(1, X)\,(1 + \lambda_2)\,\hat\alpha)\));
the propensity score uses the original \(n\) rows only. On augmented
grid points the raw penalty constant is
\(\lambda_n = (n + q)^{\delta}\) with \(q = p\) augmentation rows
(Balde's adaptive.lasso(lambda = n.q^(il)), n.q = n + q), and the
penalty-scale constant uses \(1/(n + q)\) in place of \(1/n\), i.e.
\(s = \overline{\mathrm{pen}} (n + q)^{\delta} / (n + q)\).
\(\lambda_2 = 0\) grid points run the plain-OAL code path (no
augmentation, \(\lambda_n = n^{\delta}\)), so method = "goal" with
lambda2 = 0 nests method = "oal" exactly. (Balde's script literally
augments with \(q\) zero rows and the \((n+q)^\delta\) base even at
\(\lambda_2 = 0\); the zero rows still shift the intercept, so exact
nesting is deliberately preferred here.) \(\lambda_2\) is selected
jointly with \((\delta, \gamma)\) by the wAMD: the flat first-minimum
over the \(\lambda_2\)-outer \(\times\) \(\delta\)-inner grid,
which is equivalent to Balde's nested rule (per-\(\lambda_2\) minima
over \((\delta, \gamma)\), then the first minimum over
\(\lambda_2\)).
wAMD criterion and selection
Every grid point is scored by oal_wamd(): propensity scores clipped to
clip first (note that Balde's reference implementation is unclipped;
see the clip argument), IPW weights at estimand, per-column weighted
absolute mean differences on the standardized matrix summed with weights
\(|b_j|\) over ALL columns (including excluded ones). The selected grid point is the
FIRST minimum (relative tolerance 1e-9) in the grid order
\(\lambda_2\) ascending (0 first) outer \(\times\) \(\delta\)
ascending inner – ties therefore prefer plain OAL and then the smallest
penalty. The wAMD is a balance heuristic: no published proof guarantees it
lands \(\lambda_n\) inside the window required by the theory
(\(\lambda_n/\sqrt{n} \to 0\),
\(\lambda_n n^{\gamma/2 - 1} \to \infty\), \(\gamma > 1\)), and it
inherits the outcome model's misspecification vulnerability.
Why exclude instruments?
Covariates that predict treatment but not outcome should be excluded from a propensity score model: they increase the variance of the effect estimate without decreasing bias (Brookhart et al. 2006) and amplify the bias from any unmeasured confounding (Myers et al. 2011). OAL's penalty implements exactly this exclusion.
Provenance labels
print() and summary() always print a provenance line first:
"OAL (Shortreed & Ertefaie 2017, doi:10.1111/biom.12679)" or
"GOAL (Balde, Yang & Lefebvre 2023, doi:10.1111/biom.13683)" with a
clause stating whether the lambda2 grid is the author's published grid
(Balde 2025 supplement) or user-specified. Supplying outcome.coef appends
"user-supplied outcome coefficients — screening use only; no oracle property"; family = binomial() and refit = TRUE append their own
flags.
Handoff contract
$ps is a numeric vector of length n, named by rownames(data),
strictly inside (0, 1) after clipping – it satisfies
psAve::psave(ps.append = ), MatchIt::matchit(distance = ), and
WeightIt::weightit(ps = ) by construction.
There is no seed argument: the whole pipeline is deterministic.
References
Shortreed SM, Ertefaie A (2017). Outcome-adaptive lasso: variable selection for causal inference. Biometrics, 73(4), 1111-1122. doi:10.1111/biom.12679
Balde I, Yang Y, Lefebvre G (2023). Reader reaction to "Outcome-adaptive lasso: variable selection for causal inference" by Shortreed and Ertefaie (2017). Biometrics, 79(1), 514-520. doi:10.1111/biom.13683
Jones B, Ertefaie A, Shortreed SM (2023). Rejoinder to reader reaction "On the use of the outcome-adaptive lasso for propensity score estimation". Biometrics, 79(1), 521-525. doi:10.1111/biom.13681
Balde I (2025). Oracle properties of the generalized outcome-adaptive lasso. Statistics & Probability Letters. doi:10.1016/j.spl.2025.110379
Zou H (2006). The adaptive lasso and its oracle properties. Journal of the American Statistical Association, 101(476), 1418-1429. doi:10.1198/016214506000000735
Brookhart MA, Schneeweiss S, Rothman KJ, Glynn RJ, Avorn J, Sturmer T (2006). Variable selection for propensity score models. American Journal of Epidemiology, 163(12), 1149-1156. doi:10.1093/aje/kwj149
Myers JA, Rassen JA, Gagne JJ, Huybrechts KF, Schneeweiss S, Rothman KJ, Joffe MM, Glynn RJ (2011). Effects of adjusting for instrumental variables on bias and precision of effect estimates. American Journal of Epidemiology, 174(11), 1213-1222. doi:10.1093/aje/kwr364
Schnitzer ME, Talbot D, Liu Y, Berger C, Wang G, O'Loughlin J, Sylvestre M-P, Ertefaie A (2025). Outcome-adaptive LASSO for longitudinal data. Statistics in Medicine (arXiv:2410.08283).
Examples
data("lalonde", package = "MatchIt")
## 1) Matching
fit <- oal(treat ~ age + educ + race + married + nodegree + re74 + re75,
data = lalonde, outcome = ~ re78)
fit
#> OAL (Shortreed & Ertefaie 2017, doi:10.1111/biom.12679)
#> An oal object (outcome-adaptive lasso propensity score)
#> - estimand: ATE; refit: FALSE (penalized fit)
#> - sample: 614 units (185 treated, 429 control); 8 covariate column(s)
#> - selected: delta = -10 (lambda_n = 1.31e-28), gamma = 26
#> - wAMD at the selection: 877
#>
#> Retained (outcome-related):
#> age (|b| = 128)
#> educ (|b| = 1062)
#> racehispan (|b| = 560)
#> racewhite (|b| = 621)
#> married (|b| = 201)
#> nodegree (|b| = 126)
#> re74 (|b| = 1920)
#> re75 (|b| = 763)
#> Excluded (outcome-unrelated):
#> (none)
#>
#> Next:
#> MatchIt::matchit(treat ~ age + educ + race + married + nodegree + re74 + re75, data = lalonde, distance = x$ps)
#> or: oal_match(x)
#> WeightIt::weightit(treat ~ age + educ + race + married + nodegree + re74 + re75, data = lalonde, ps = x$ps, estimand = "ATE")
#> or: oal_weight(x)
#> ($ps is numeric, named by rownames(lalonde), strictly inside (0, 1);
#> it also satisfies psAve::psave(ps.append = cbind(oal = x$ps)).)
m <- oal_match(fit) # = MatchIt::matchit(f, lalonde, distance = fit$ps)
## 2) Weighting (requires WeightIt)
# w <- oal_weight(fit) # = WeightIt::weightit(f, lalonde, ps = fit$ps, "ATE")
## 3) psAve composition (requires psAve); cbind() labels the candidate
## while keeping the row names aligned with rownames(data)
# ma <- psAve::psave(treat ~ age + educ + race + married + nodegree +
# re74 + re75,
# data = lalonde, outcome = ~ re78,
# ps.append = cbind(oal = fit$ps))
