Classic and Batched Stepped-Wedge Designs

Amanda Li

A batched stepped-wedge design (BSWD) allows clusters to begin trial participation in batches with staggered initiation times. Consequently, some cluster-periods are outside the observation window. A dash means that no outcome is observed; it is not a Control observation.

Batch labels may be supplied explicitly or inferred from first observed calendar periods. Version 0.4.1 distinguishes calendar time, shared time on trial, and separate batch-specific time.

Exact SWD and BSWD schedules

library(stepwedgepower)

swd <- sw_batched_design(
  c(5L, 5L),
  rbind(
    `Group 1` = c("0", "1", "1+2", "1+2"),
    `Group 2` = c("0", "0", "1", "1+2")
  ),
  batch = c("Batch 1", "Batch 1")
)

bswd <- sw_batched_design(
  c(2L, 2L, 3L, 3L),
  rbind(
    `Group 1` = c("0", "1", "1+2", "1+2", "-"),
    `Group 2` = c("0", "0", "1", "1+2", "-"),
    `Group 3` = c("-", "0", "1", "1+2", "1+2"),
    `Group 4` = c("-", "0", "0", "1", "1+2")
  ),
  batch = c("Batch 1", "Batch 1", "Batch 2", "Batch 2")
)

swd
#> <sw_batched_design>
#>   type: classic SWD; 1 batch(es), 2 sequence(s), 4 calendar period(s)
#>    batch start delay_from_first gap_from_previous sequences clusters
#>  Batch 1     1                0                 0         2       10
#>   observed cluster-periods: 40 of 40
#>         Period 1 Period 2 Period 3 Period 4
#> Group 1 Control  A        A+B      A+B     
#> Group 2 Control  Control  A        A+B
bswd
#> <sw_batched_design>
#>   type: BSWD; 2 batch(es), 4 sequence(s), 5 calendar period(s)
#>    batch start delay_from_first gap_from_previous sequences clusters
#>  Batch 1     1                0                 0         2        4
#>  Batch 2     2                1                 1         2        6
#>   observed cluster-periods: 40 of 50
#>         Period 1 Period 2 Period 3 Period 4 Period 5
#> Group 1 Control  A        A+B      A+B      -       
#> Group 2 Control  Control  A        A+B      -       
#> Group 3 -        Control  A        A+B      A+B     
#> Group 4 -        Control  Control  A        A+B

The BSWD has two four-period batches, with the second batch starting one calendar period later. The object records calendar time and time on trial.

bswd$batch_start_period
#> Batch 1 Batch 2 
#>       1       2
bswd$batch_delay
#> Batch 1 Batch 2 
#>       0       1
bswd$batch_gap_from_previous
#> Batch 1 Batch 2 
#>       0       1
bswd$time_on_trial
#>         Period 1 Period 2 Period 3 Period 4 Period 5
#> Group 1        1        2        3        4        5
#> Group 2        1        2        3        4        5
#> Group 3       NA        1        2        3        4
#> Group 4       NA        1        2        3        4

Assumptions and resources

Because B is not delivered alone, its component coefficient is the incremental A+B-versus-A effect when the interaction is omitted.

assumptions <- sw_batched_assumptions(
  baseline_prob = 0.15,
  treatment_or_a = 1.35,
  treatment_or_b = 1.25,
  interaction_mode = "none",
  icc = 0.05,
  n_per_cluster_period = 25L,
  time_model = "calendar",
  time_effects = log(seq(1.00, 1.08, length.out = 5))
)

rbind(
  SWD = component_resource_summary(swd, assumptions),
  BSWD = component_resource_summary(bswd, assumptions)
)
#>      n_sequences n_clusters n_periods calendar_sequence_periods
#> SWD            2         10         4                         8
#> BSWD           4         10         5                        20
#>      observed_sequence_periods missing_sequence_periods
#> SWD                          8                        0
#> BSWD                        16                        4
#>      calendar_cluster_periods observed_cluster_periods missing_cluster_periods
#> SWD                        40                       40                       0
#> BSWD                       50                       40                      10
#>      observed_fraction total_individual_observations
#> SWD                1.0                          1000
#> BSWD               0.8                          1000

Both designs contain 40 observed clinic-periods and 1,000 observations. The BSWD spans 50 possible clinic-periods, of which 10 are structurally unobserved.

Audit the three time models

contrasts <- c("A_vs_control", "AB_vs_A", "AB_vs_control")

calendar_audit <- audit_batched_design(
  bswd, assumptions, "calendar", contrasts, include_interaction = FALSE
)
trial_time_audit <- audit_batched_design(
  bswd, assumptions, "time_on_trial", contrasts,
  include_interaction = FALSE
)
separate_audit <- audit_batched_design(
  bswd, assumptions, "separate", contrasts, include_interaction = FALSE
)

calendar_audit
#> <sw_batched_audit>
#>   time model: calendar 
#>   Calendar-period effects are shared across batches; this assumes a common secular trend on the calendar scale.
#>   fixed-effect rank: 7 of 7 (full)
#>   observed cluster-periods: 40 of 50; structurally unobserved: 10
#>   batches:
#>    batch start_period delay_from_first gap_from_previous end_period sequences
#>  Batch 1            1                0                 0          4         2
#>  Batch 2            2                1                 1          5         2
#>  clusters
#>         4
#>         6
#>   requested contrasts:
#>       contrast          label estimable
#>   A_vs_control   A vs Control      TRUE
#>        AB_vs_A       A+B vs A      TRUE
#>  AB_vs_control A+B vs Control      TRUE
#>   no structural cautions detected
trial_time_audit
#> <sw_batched_audit>
#>   time model: time_on_trial 
#>   Time-on-trial effects are shared across batches and indexed from each batch's initiation.
#>   fixed-effect rank: 6 of 6 (full)
#>   observed cluster-periods: 40 of 50; structurally unobserved: 10
#>   batches:
#>    batch start_period delay_from_first gap_from_previous end_period sequences
#>  Batch 1            1                0                 0          4         2
#>  Batch 2            2                1                 1          5         2
#>  clusters
#>         4
#>         6
#>   requested contrasts:
#>       contrast          label estimable
#>   A_vs_control   A vs Control      TRUE
#>        AB_vs_A       A+B vs A      TRUE
#>  AB_vs_control A+B vs Control      TRUE
#>   no structural cautions detected
separate_audit
#> <sw_batched_audit>
#>   time model: separate 
#>   Each batch has its own categorical time-on-trial profile, allowing batch-specific secular effects.
#>   fixed-effect rank: 10 of 10 (full)
#>   observed cluster-periods: 40 of 50; structurally unobserved: 10
#>   batches:
#>    batch start_period delay_from_first gap_from_previous end_period sequences
#>  Batch 1            1                0                 0          4         2
#>  Batch 2            2                1                 1          5         2
#>  clusters
#>         4
#>         6
#>   requested contrasts:
#>       contrast          label estimable
#>   A_vs_control   A vs Control      TRUE
#>        AB_vs_A       A+B vs A      TRUE
#>  AB_vs_control A+B vs Control      TRUE
#>   no structural cautions detected

Simulate and fit one trial

one_trial <- simulate_batched_swcrt(bswd, assumptions, seed = 123)
one_fit <- fit_batched_model(
  one_trial,
  time_model = "calendar",
  contrasts = contrasts,
  include_interaction = FALSE,
  multiplicity = "holm"
)
one_fit$contrasts
#>        contrast          label  estimate std_error  z_value    p_value
#> 1  A_vs_control   A vs Control 0.4891446 0.3301932 1.481389 0.13850294
#> 2       AB_vs_A       A+B vs A 0.3579656 0.2996250 1.194712 0.23219962
#> 3 AB_vs_control A+B vs Control 0.8471101 0.4787949 1.769255 0.07685134
#>      conf_low conf_high odds_ratio p_adjusted reject reject_adjusted
#> 1 -0.15802220 1.1363114   1.630921  0.1385029  FALSE           FALSE
#> 2 -0.22928867 0.9452198   1.430416         NA  FALSE              NA
#> 3 -0.09131052 1.7855308   2.332895         NA  FALSE              NA

Power comparisons

comparison <- compare_swd_bswd(
  swd, bswd, assumptions,
  nsim = 5000,
  contrasts = contrasts,
  include_interaction = FALSE,
  multiplicity = "holm",
  multiplicity_family = contrasts,
  nAGQ = 1,
  n_cores = 4,
  seed = 2026,
  warn_on_design = FALSE
)

comparison$resource_check
comparison$comparison
comparison$fit_diagnostics

model_comparison <- compare_batched_time_models(
  bswd, assumptions,
  nsim = 5000,
  contrasts = contrasts,
  include_interaction = FALSE,
  multiplicity = "holm",
  multiplicity_family = contrasts,
  nAGQ = 1,
  n_cores = 4,
  seed = 2026,
  warn_on_design = FALSE
)
model_comparison$comparison

The simulation output retains conditional and failure-aware power, Monte Carlo uncertainty, bias, coverage, hard GLMM errors, nonconvergence, non-finite contrasts, singular fits, and successful fits.