
Fractional Weighted Bootstrap for SOPs
Source:vignettes/fractional-weighted-bootstrap-sops.Rmd
fractional-weighted-bootstrap-sops.RmdThe ordinary patient bootstrap
(vignette("mvn-vs-bootstrap-orm-sops")) draws patients with
replacement, so each resample leaves out about a third of the patients
and counts others several times. In small samples or subgroups, a
resample can then lack patients in a rare state, and the model cannot be
refitted, or a subgroup can be missing altogether.
The fractional weighted bootstrap (FWB) avoids this. Instead of resampling, it keeps every patient in every replicate and gives each patient a random positive weight, drawn from an exponential distribution and scaled to have mean 1. For each replicate it
- refits the transition model, applying each patient’s weight to all of that patient’s rows; and
- averages the patient-level state occupancy probabilities (SOPs) using the same weights.
Like the ordinary bootstrap, the FWB reflects both uncertainty in the model coefficients and which patients happened to be sampled.
This article uses the FWB to estimate SOPs within subgroups defined by the patients’ starting state, and to compare time benefit between arms in these subgroups.
Fit the model
We simulate 160 patients over 10 days with
sim_markov("actt2"), which uses the eight-state NIAID scale
described in vignette("full-po-sops"). We record each
patient’s starting state, the value of yprev on day 1, in a
new column starting_state for use as a subgroup.
library(mostr)
library(ggplot2)
library(dplyr)
library(rms)
N_PATIENTS <- 160
FOLLOW_UP <- 10
N_BOOT <- 30
SEED <- 20260606
STATE_LEVELS <- as.character(1:8)
ABSORB <- "8"
raw_data <- sim_markov(
"actt2",
n_patients = N_PATIENTS,
follow_up_time = FOLLOW_UP,
treatment_effect = -0.03,
seed = SEED
)
model_data <- prepare_markov_data(
raw_data,
absorbing_state = as.integer(ABSORB),
factor_previous = TRUE
) |>
group_by(id) |>
mutate(starting_state = first(yprev)) |>
ungroup()
fit <- orm_markov(
y ~ tx + rcs(time, 4) + yprev,
data = model_data,
id_var = "id",
opt_method = "LM",
scale = TRUE,
maxit = 100
)Define SOPs by subgroup
by = "starting_state" asks avg_sops() to
average the patient-level SOPs separately within each starting state,
after setting every patient to each treatment value. Because the model
was fitted with orm_markov(), the full longitudinal data
needed for refitting and each patient’s starting row are stored with the
fit, so neither needs to be passed again.
sop_by_start <- avg_sops(
fit,
variables = "tx",
by = "starting_state",
times = seq_len(FOLLOW_UP),
y_levels = fit$yunique,
absorb = ABSORB
)
head(sop_by_start)#> time state tx starting_state estimate
#> 1 1 1 0 4 0.001631675
#> 2 2 1 0 4 0.091350360
#> 3 3 1 0 4 0.179681107
#> 4 4 1 0 4 0.260056964
#> 5 5 1 0 4 0.339022020
#> 6 6 1 0 4 0.420290648
Run the fractional weighted bootstrap
method = "fwb" selects the FWB. Within each replicate,
the subgroup averages use the same patient weights as the refit. We use
only 30 replicates to keep this example fast; a real analysis needs many
more, typically 1,000 or more.
set.seed(SEED)
sop_fwb <- inferences(
sop_by_start,
method = "fwb",
n_draws = N_BOOT,
workers = 1,
return_draws = TRUE
)Each panel shows one starting state. Subgroups with few patients have wider intervals.
plot_sops(
sop_fwb,
facet_var = "starting_state",
linetype_var = "tx"
)
Compare time benefit by subgroup
Time benefit summarizes the whole ordinal scale in one number: on
each day, the probability that a patient would be in a better state
under treatment than under control, minus the probability of a worse
state, summed over days. See
vignette("many-levels-previous-state-spline") for a fuller
explanation. With by = "starting_state", it is reported
separately for each starting state.
tb_fwb <- avg_comparisons(
fit,
times = seq_len(FOLLOW_UP),
variables = list(tx = c(0, 1)),
estimand = "time_benefit",
absorb = ABSORB,
by = "starting_state"
) |>
inferences(
method = "fwb",
n_draws = N_BOOT,
workers = 1,
return_draws = TRUE
)
plot_comparisons(tb_fwb, color_var = "starting_state")