| Title: | Stable Balancing Weights for Covariate Adjustment in Trials |
|---|---|
| Description: | Covariate adjustment for randomized trials using stable balancing weights (SBW), as proposed by Irish, Zubizarreta, and Luedtke (2026) <doi:10.48550/arXiv.2609.01638>. sbw_weights() fits exact-balance SBW for a two-arm study by a closed-form solve with a nonnegative quadratic-programming fallback; sbw_estimate() computes a closed menu of treatment-effect estimands (average treatment effect, relative risk, survival ratio, Mann-Whitney, quantile contrasts) from the fitted weights, with bootstrap confidence intervals. |
| Authors: | Kayla Irish [aut, cre] |
| Maintainer: | Kayla Irish <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.3.0.9000 |
| Built: | 2026-10-16 07:40:41 UTC |
| Source: | https://github.com/kaylairish/sbwadjust |
Bootstraps the standard error of km_ratio_greenwood()'s
log-ratio, then builds either a Wald CI (log scale) or a percentile CI
(log scale). If the point estimate or bootstrap SE come out non-finite,
falls back to the unadjusted KM ratio and sets MC_fail = TRUE; if even
the unadjusted ratio is undefined (a double-degenerate case with no valid
fallback), throws an error rather than returning a nonsense finite
estimate. If the SBW fit or weighted KM ratio fails on an individual
bootstrap resample, the unadjusted KM ratio is used for that resample;
boot_fail_rate reports the fraction of resamples where this happened.
boot_km_ratio( time, status, A, X_subset, t0, B = 1500, alpha = 0.05, ci_method = c("wald", "percentile"), seed = NULL )boot_km_ratio( time, status, A, X_subset, t0, B = 1500, alpha = 0.05, ci_method = c("wald", "percentile"), seed = NULL )
time |
Event/censoring time. |
status |
Event indicator (1 = event, 0 = censored). |
A |
Treatment indicator (0/1). |
X_subset |
Covariate data frame for the full study sample, used to fit the SBW weights. |
t0 |
Time at which to evaluate the survival ratio. |
B |
Number of bootstrap replicates. |
alpha |
Significance level for the confidence interval (default 0.05). |
ci_method |
Either |
seed |
Optional seed set at the start of the bootstrap. |
A list with MC_fail, log_est, est, se_log, ci_log, ci,
boot_fail_rate, boot_n_finite_reps, and SBW clipping diagnostics
(sbw_n_clipped_full, sbw_max_abs_clipped_full,
sbw_n_clipped_boot_mean, sbw_n_clipped_boot_max,
sbw_max_abs_clipped_boot_max).
Tries the unconstrained closed-form solve first for each arm
(get_weights_for_group_neg()); if the result is nonnegative within
floating-point tolerance, zeroes out negligible negative dust and returns
it directly. Otherwise falls back to the nonnegative-QP solve
(get_weights_for_group_nonneg()).
get_sbws_for_study(X_subset, A)get_sbws_for_study(X_subset, A)
X_subset |
Covariate data frame for the full study sample. |
A |
Treatment indicator (0/1) for the full study sample. |
A list with w (numeric vector of weights, one per row of
X_subset), n_clipped, and max_abs_clipped (both 0 if the
closed-form solve was used).
Solves for weights on one treatment-arm's covariate data that exactly
balance its mean to the target X_n, minimizing sum of squared weights,
via the closed-form solution (no nonnegativity constraint).
get_weights_for_group_neg(data, X_n)get_weights_for_group_neg(data, X_n)
data |
Covariate data frame for the group (one treatment arm). |
X_n |
Target covariate mean to balance to. |
Numeric vector of weights, one per row of data.
Solves the same balance objective as get_weights_for_group_neg(), but
constrained to nonnegative weights via quadratic programming.
get_weights_for_group_nonneg(data, X_n)get_weights_for_group_nonneg(data, X_n)
data |
Covariate data frame for the group (one treatment arm). |
X_n |
Target covariate mean to balance to. |
A list with w (numeric vector of weights), n_clipped, and
max_abs_clipped (see .clip_weights()).
Wald CI on the log scale, with se(log S) = se(S) / S for each arm and
se(S) from Greenwood's formula.
km_ratio_greenwood(time, status, A, t0, alpha = 0.05, weights = NULL)km_ratio_greenwood(time, status, A, t0, alpha = 0.05, weights = NULL)
time |
Event/censoring time. |
status |
Event indicator (1 = event, 0 = censored). |
A |
Treatment indicator (0/1; numeric, logical, character, or factor). |
t0 |
Time at which to evaluate the survival ratio. |
alpha |
Significance level for the confidence interval (default 0.05). |
weights |
Optional case weights (e.g. SBW), passed to
|
A list with S0, S1, ratio (= S1(t0)/S0(t0)), log_ratio,
se_log, ci_ratio, and ci_log.
Standardized mean differences (treated vs. control) for each balance covariate, unweighted and SBW-weighted.
## S3 method for class 'sbw_fit' plot(x, ...)## S3 method for class 'sbw_fit' plot(x, ...)
x |
An |
... |
Passed on to the underlying |
x, invisibly.
sbw_fit objectEstimate a treatment effect from a fitted sbw_fit object
sbw_estimate(object, outcome, estimand, ...) ## S3 method for class 'sbw_fit' sbw_estimate( object, outcome, estimand, data = NULL, probs = 0.5, horizon = NULL, B = 1500, alpha = 0.05, ci_method = c("wald", "percentile"), seed = NULL, ... )sbw_estimate(object, outcome, estimand, ...) ## S3 method for class 'sbw_fit' sbw_estimate( object, outcome, estimand, data = NULL, probs = 0.5, horizon = NULL, B = 1500, alpha = 0.05, ci_method = c("wald", "percentile"), seed = NULL, ... )
object |
An |
outcome |
A one-sided-response formula naming the outcome, e.g.
|
estimand |
One of |
... |
Passed to methods. |
data |
Optional data frame holding the outcome, for when the weights
were fit on baseline data before outcomes were available. Its rows must
correspond one-to-one, in the same order, to the data passed to
|
probs |
Vector of probabilities in (0, 1), used when |
horizon |
Time |
B |
Number of bootstrap replicates. |
alpha |
Significance level for the confidence interval. |
ci_method |
Either |
seed |
Optional seed set at the start of the bootstrap. |
The bootstrap resamples participants independently (a nonparametric row bootstrap), which is valid under simple randomization. It does not account for stratified or covariate-adaptive randomization (permuted blocks, minimization, biased coin); under those designs the confidence intervals may be miscalibrated.
For estimand = "survival_ratio", if the SBW point estimate or its
bootstrap standard error is non-finite, the unadjusted Kaplan-Meier
ratio is returned instead, with a warning and mc_fail = TRUE.
An object of class sbw_estimate: a list with estimand;
estimate (named by estimand, or q<p> per quantile); se, the
bootstrap standard error (on the log scale when scale = "log"); ci,
a matrix with one row per estimate (lower, upper) on the estimate's own
scale; scale ("log" for "RR", "quantile_ratio", and
"survival_ratio", otherwise "identity"); alpha; and B. Most
estimands also return boot_fail_rate, the fraction of bootstrap
resamples that failed. "survival_ratio" instead returns horizon,
mc_fail, and detail (the full boot_km_ratio() result, which
includes its own boot_fail_rate).
set.seed(1) n = 100 trial_baseline = data.frame( age = rnorm(n, 50, 10), region = sample(c("N", "S"), n, replace = TRUE), arm = rbinom(n, 1, 0.5) ) # Design stage: fit weights before any outcomes exist. sbw = sbw_weights(~ age + region, data = trial_baseline, treatment = arm) # Analysis stage: outcomes arrive later, one row per participant, same order. trial_outcomes = data.frame(Y = rbinom(n, 1, plogis(-1 + 0.02 * trial_baseline$age))) sbw_estimate(sbw, Y ~ 1, estimand = "RR", data = trial_outcomes, B = 200, seed = 1)set.seed(1) n = 100 trial_baseline = data.frame( age = rnorm(n, 50, 10), region = sample(c("N", "S"), n, replace = TRUE), arm = rbinom(n, 1, 0.5) ) # Design stage: fit weights before any outcomes exist. sbw = sbw_weights(~ age + region, data = trial_baseline, treatment = arm) # Analysis stage: outcomes arrive later, one row per participant, same order. trial_outcomes = data.frame(Y = rbinom(n, 1, plogis(-1 + 0.02 * trial_baseline$age))) sbw_estimate(sbw, Y ~ 1, estimand = "RR", data = trial_outcomes, B = 200, seed = 1)
Formula-based front end to get_sbws_for_study(): balances the covariates
named in balance (factors dummy-coded automatically) to the pooled-arm
mean, exactly (imbalance tolerance fixed at zero), matching the paper's
SBW specification. Intended to be called before unblinding, using only
baseline covariates and the randomization assignment.
sbw_weights(balance, data, treatment)sbw_weights(balance, data, treatment)
balance |
One-sided formula naming the covariates to balance, e.g.
|
data |
Data frame containing the balance covariates and treatment. |
treatment |
Treatment assignment: either a column name in |
An object of class sbw_fit with elements weights, n_clipped,
max_abs_clipped (see get_sbws_for_study()), balance (the formula),
treatment_name, treatment_levels, treatment (recoded 0/1),
X (the balance design matrix), data, n, and call.
set.seed(1) n = 100 trial_baseline = data.frame( age = rnorm(n, 50, 10), region = sample(c("N", "S"), n, replace = TRUE), arm = rbinom(n, 1, 0.5) ) sbw = sbw_weights(~ age + region, data = trial_baseline, treatment = arm) sbw summary(sbw)set.seed(1) n = 100 trial_baseline = data.frame( age = rnorm(n, 50, 10), region = sample(c("N", "S"), n, replace = TRUE), arm = rbinom(n, 1, 0.5) ) sbw = sbw_weights(~ age + region, data = trial_baseline, treatment = arm) sbw summary(sbw)
sbw_fit: balance table and weight diagnosticsSummarize an sbw_fit: balance table and weight diagnostics
## S3 method for class 'sbw_fit' summary(object, ...)## S3 method for class 'sbw_fit' summary(object, ...)
object |
An |
... |
Currently unused. |
An object of class summary.sbw_fit, printed by
print.summary.sbw_fit(), with elements balance (unweighted and
SBW-weighted arm means of each balance column), n_treated, n_control,
and n_zero (units with weight 0, by arm).