Asynchronous Control to A to A+B stepped-wedge designs

library(stepwedgepower)

Design question

Some implementation studies add interventions in stages. A cluster first moves from control to intervention A and later receives B in addition to A:

\[ \text{Control} \longrightarrow A \longrightarrow A+B. \]

The sequences need not add B in the same calendar period. This distinction is important: the design is not a conventional three-arm trial, and there is no B-only condition. The B coefficient therefore represents the incremental add-on effect of B among clusters already receiving A.

An asynchronous schedule

The example below has eight sequences. A and B begin at different times, and the duration of A-only exposure also varies across sequences.

three_state <- sw_multistage_design(
  clusters_per_sequence = rep(6, 8),
  a_start = c(2, 3, 4, 5, 6, 7, 8, 9),
  b_start = c(6, 8, 7, 10, 9, 11, 12, 12),
  n_periods = 12,
  sequence_names = paste0("S", 1:8)
)
three_state
#> <sw_multistage_design>
#>   8 sequences, 12 periods, 48 clusters total
#>   clusters per sequence: 6, 6, 6, 6, 6, 6, 6, 6 
#>   asynchronous cumulative schedule:
#>    Period 1 Period 2 Period 3 Period 4 Period 5 Period 6 Period 7 Period 8
#> S1 Control  A        A        A        A        A+B      A+B      A+B     
#> S2 Control  Control  A        A        A        A        A        A+B     
#> S3 Control  Control  Control  A        A        A        A+B      A+B     
#> S4 Control  Control  Control  Control  A        A        A        A       
#> S5 Control  Control  Control  Control  Control  A        A        A       
#> S6 Control  Control  Control  Control  Control  Control  A        A       
#> S7 Control  Control  Control  Control  Control  Control  Control  A       
#> S8 Control  Control  Control  Control  Control  Control  Control  Control 
#>    Period 9 Period 10 Period 11 Period 12
#> S1 A+B      A+B       A+B       A+B      
#> S2 A+B      A+B       A+B       A+B      
#> S3 A+B      A+B       A+B       A+B      
#> S4 A        A+B       A+B       A+B      
#> S5 A+B      A+B       A+B       A+B      
#> S6 A        A         A+B       A+B      
#> S7 A        A         A         A+B      
#> S8 A        A         A         A+B

The varying A-to-B lags help distinguish the B effect from a treatment-A effect that simply matures with exposure time. The schedule can also be entered as an explicit matrix containing 0 (Control), 1 (A), and 2 (A+B).

Delayed effects

assumptions <- sw_multistage_assumptions(
  baseline_prob = 0.15,
  treatment_or_a = 1.40,
  incremental_or_b = 1.30,
  delay_a = 1,
  delay_b = 1,
  icc = 0.05,
  period_effects = log(seq(1.00, 1.12, length.out = 12)),
  n_per_cluster_period = 30
)
assumptions
#> <sw_multistage_assumptions>
#>   outcome: binary 
#>   A vs Control: OR = 1.400 (log-odds 0.336), delay 1
#>   incremental B: OR = 1.300 (log-odds 0.262), delay 1
#>   total A+B vs Control: OR = 1.820
#>   cluster_sd = 0.416 (latent ICC = 0.050)
#>   baseline probabilities: 0.150 
#>   period effects (log-odds): 0.000, 0.011, 0.022, 0.032, 0.043, 0.053, 0.063, 0.074, 0.084, 0.094, 0.104, 0.113

A delay of one makes the first active period a wash-in period. The effect begins in the second period after the corresponding intervention starts. On the logit scale, the generating model is

\[ \operatorname{logit}(p_{ijt}) = \beta_{0,jt}+\gamma_t+b_i+ \beta_A A^*_{ijt}+\beta_B B^*_{ijt}. \]

The estimands are:

Audit before simulation

audit_multistage_design(
  three_state,
  delay_a = assumptions$delay_a,
  delay_b = assumptions$delay_b
)
#> <sw_multistage_audit>
#>   fixed-effect rank: 14 of 14 (full rank)
#>   Control/A overlap periods: 2, 3, 4, 5, 6, 7, 8 
#>   A/A+B overlap periods: 6, 7, 8, 9, 10, 11 
#>   staggered B starts: yes 
#>   no structural cautions detected

The audit checks fixed-effect rank and identifies whether calendar periods contain concurrent Control/A and A/A+B comparisons. It warns when all sequences add B in one calendar period or after the same duration of A exposure.

Simulate and analyze one trial

trial <- simulate_multistage_swcrt(three_state, assumptions, seed = 123)
head(trial)
#>   cluster_id sequence sequence_idx period   state a_active b_active
#> 1          1       S1            1      1 Control        0        0
#> 2          1       S1            1      2       A        1        0
#> 3          1       S1            1      3       A        1        0
#> 4          1       S1            1      4       A        1        0
#> 5          1       S1            1      5       A        1        0
#> 6          1       S1            1      6     A+B        1        1
#>   a_exposure_time b_exposure_time a_effective b_effective  n events true_prob
#> 1               0               0           0           0 30      5 0.1226229
#> 2               1               0           0           0 30      1 0.1237950
#> 3               2               0           1           0 30      5 0.1666210
#> 4               3               0           1           0 30      5 0.1681008
#> 5               4               0           1           0 30      6 0.1695754
#> 6               5               1           1           0 30      4 0.1710448
#>   true_contribution_a true_contribution_b
#> 1           0.0000000                   0
#> 2           0.0000000                   0
#> 3           0.3364722                   0
#> 4           0.3364722                   0
#> 5           0.3364722                   0
#> 6           0.3364722                   0

The active rollout state and the delayed-effect indicators are both retained. This makes the assumptions auditable rather than hiding wash-in periods inside a single treatment code.

fit <- fit_multistage_model(trial, multiplicity = "holm")
fit$contrasts

The default mixed model contains separate A and B indicators, categorical calendar time, and a cluster random intercept. The total A+B contrast uses the estimated covariance between the A and B coefficients.

Power and failure-aware performance

power_three <- power_multistage_swcrt(
  three_state,
  assumptions,
  nsim = 5000,
  multiplicity = "holm",
  n_cores = 4,
  seed = 2026
)
summary(power_three)
plot(power_three, type = "failure-aware")
power_three$fit_diagnostics
power_three$replicate_diagnostics

The primary fit statuses are hard GLMM error, nonconverged fit, converged fit with a non-finite requested contrast, and successful fit. Singular fit is a separate, non-exclusive property.

The output distinguishes conditional power among usable fits from failure-aware power, which counts a failed or non-estimable analysis as an unsuccessful trial. It also reports Monte Carlo intervals, type-specific contrasts, bias, standard-error calibration, coverage, convergence, and singular fits.

Compare Control to A with Control to A to A+B

Construct a two-state schedule with the same A rollout, clusters, periods, and sample sizes, but no B transition.

two_state <- sw_multistage_design(
  clusters_per_sequence = three_state$clusters_per_sequence,
  a_start = three_state$a_start,
  b_start = rep(Inf, three_state$n_sequences),
  n_periods = three_state$n_periods,
  sequence_names = three_state$sequence_names
)
comparison <- compare_multistage_designs(
  design_two = two_state,
  design_three = three_state,
  assumptions_two = assumptions,
  nsim = 5000,
  multiplicity = "holm",
  n_cores = 4,
  seed = 2026
)
comparison

The A-versus-Control rows can be compared directly under equal resources. The three-state design additionally reports incremental-B power, total A+B power, and the probability that both multiplicity-adjusted component objectives succeed.

Check type I error first

type1 <- type1_multistage_swcrt(
  three_state,
  assumptions,
  nsim = 5000,
  multiplicity = "holm",
  n_cores = 4,
  seed = 22026
)
type1

Power should be interpreted only after confirming that the intended tests have acceptable null rejection rates for the proposed number of clusters and ICC.

mirror server hosted at Truenetwork, Russian Federation.