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:

  • exp(beta_A): A versus Control;
  • exp(beta_B): A+B versus A, the incremental B effect;
  • exp(beta_A + beta_B): A+B versus Control, the total effect.

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")

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.