Benchmark Validation for Classic and Batched Stepped-Wedge Designs

Amanda Li

Purpose

This vignette reproduces four numerical artifacts from Lin et al. (2026), Power and Sample Size Implications of Delayed Cluster Initiation in Stepped Wedge Trials:

The code is stored under inst/benchmarks/, so it does not add functions to the package namespace. Reference values, tolerances, provenance notes, and documented source discrepancies are stored in machine-readable CSV files.

The benchmark separates manuscript reproduction from validation of the main stepwedgepower engine. The manuscript calculations include continuous LMM approximations and marginal GEE power, whereas stepwedgepower primarily uses simulation with a binomial GLMM. Exact agreement is required only when the same method and parameterization are being reproduced.

Provenance and prespecified tolerances

manifest <- .sp_bench_read_reference(
  "benchmark_manifest.csv", benchmark_dir
)
knitr::kable(manifest)
artifact script reference primary_method optional_packages default_tolerance
Table 2 table2_software_comparison.R reference/table2_reference.csv swCRTdesign and swdpwr calls from the authors’ public code swCRTdesign;swdpwr 0.002 after rounding to 3 decimals
Figure 3 figure3_calendar_delay.R reference/figure3_reference.csv independent matrix inversion under the pooled-variance LMM approximation 1e-10
Table 3 table3_separate_time_grid.R reference/table3_reference.csv swdpwr marginal GEE with logit link and separate-time equivalence swdpwr 0.002 after rounding to 3 decimals
Figure 5 figure5_limited_clusters.R reference/figure5_reference.csv closed-form scaled variance expressions plus independent WLS matrix inversion 1e-12
reference_environment <- .sp_bench_read_reference(
  "reference_environment.csv", benchmark_dir
)
knitr::kable(reference_environment)
item value source
manuscript_title Power and Sample Size Implications of Delayed Cluster Initiation in Stepped Wedge Trials Lin et al. (2026), manuscript title page
manuscript_date 2026-04-20 Lin et al. (2026), manuscript title page
companion_repository https://github.com/tuolin123/BatchedSW Lin et al. (2026), Data Availability Statement
reference_R_version 4.2.2 Companion repository README
external_package_versions Not reported in manuscript or companion repository Recorded as unavailable rather than inferred

The source materials contain several inconsistencies that are retained in the benchmark record rather than silently resolved.

source_notes <- .sp_bench_read_reference(
  "source_discrepancies.csv", benchmark_dir
)
knitr::kable(source_notes)
item manuscript_statement public_code_or_figure benchmark_decision
Figure 3 prevalence Section 5.2.1 states p0 = 0.1 and p1 = 0.3. The public R code uses p0 = 0.1 and p1 = 0.2; the plotted values agree with p1 = 0.2. Use p1 = 0.2 for numerical reproduction and record the discrepancy explicitly.
Figure 3 terminal delay The text and plotted figure include the maximal non-overlap delay, for which calendar and separate time coincide. The public R script as accessed omits the final no-overlap point from each stored power vector. Include delays 0 through the number of periods, so the terminal point is present and equals the no-delay power.
Table 3 wording The caption calls the entries simulated power results. The public code calculates them with swdpwr’s marginal GEE power routine. Describe them as swdpwr GEE power calculations, while retaining the manuscript’s numerical reference values.
Table 2 Scenario 1 swdpwr argument The scenario uses marginal variance 1 for the continuous outcome. The public script supplies sigma = 1, relying on partial argument matching to sigma2. Use the explicit current argument name sigma2 = 1.

Most importantly, the prose for Figure 3 states \(p_1=0.3\), while the public R code and plotted values use \(p_1=0.2\). The numerical reproduction below uses \(p_1=0.2\).

Table 2: software comparison

PASS and the Shiny CRT Calculator are reference-only because they are not invoked by this R package. Calls to swCRTdesign and swdpwr are rerun only when the optional packages are installed and the environment variable STEPWEDGEPOWER_RUN_EXTERNAL=true is set.

table2 <- run_table2_benchmark(
  output_dir = benchmark_output,
  benchmark_dir = benchmark_dir,
  run_external = run_external,
  strict = FALSE
)

table2_display <- table2$validation[, c(
  "scenario", "method", "reference_power", "reproduced_power",
  "absolute_difference", "status", "package_version", "pass"
)]
knitr::kable(table2_display, digits = 3)
scenario method reference_power reproduced_power absolute_difference status package_version pass
1 PASS 0.826 NA NA reference_only NA NA
1 Shiny CRT Calculator 0.829 NA NA reference_only NA NA
1 swCRTdesign 0.826 0.826 0.000 evaluated 4.1 TRUE
1 swdpwr_with_time 0.830 0.830 0.000 evaluated 1.12 TRUE
1 swdpwr_without_time 0.999 0.999 0.000 evaluated 1.12 TRUE
2 PASS 0.811 NA NA reference_only NA NA
2 Shiny CRT Calculator 0.811 NA NA reference_only NA NA
2 swCRTdesign 0.799 0.799 0.000 evaluated 4.1 TRUE
2 swdpwr_with_time 0.809 0.809 0.000 evaluated 1.12 TRUE
2 swdpwr_without_time 0.999 0.999 0.000 evaluated 1.12 TRUE
3 PASS 0.854 NA NA reference_only NA NA
3 Shiny CRT Calculator 0.857 NA NA reference_only NA NA
3 swCRTdesign 0.851 0.851 0.000 evaluated 4.1 TRUE
3 swdpwr_with_time 0.857 0.857 0.000 evaluated 1.12 TRUE
3 swdpwr_without_time 0.998 0.998 0.000 evaluated 1.12 TRUE
4 PASS 0.875 NA NA reference_only NA NA
4 Shiny CRT Calculator 0.875 NA NA reference_only NA NA
4 swCRTdesign 0.868 0.866 0.002 evaluated 4.1 TRUE
4 swdpwr_with_time 0.873 0.873 0.000 evaluated 1.12 TRUE
4 swdpwr_without_time 1.000 1.000 0.000 evaluated 1.12 TRUE

When external execution is disabled, the reference values remain visible and the reproducible rows are marked as skipped.

Figure 3: power under delayed batch initiation

For a single batch with \(s\) steps and \(s+1\) periods, two copies of the within-batch design are placed on a common calendar-time axis. The second batch is shifted by \(d\) periods. The benchmark forms the calendar-time design matrix, the exchangeable covariance matrix, and obtains the treatment variance from

\[ \left(Z^\mathsf{T}V^{-1}Z\right)^{-1}. \]

The pooled binary variance and the normal-approximation power formula match the manuscript computation. The maximal non-overlap delay is included; its power equals the no-delay value under the stated equivalence.

figure3 <- run_figure3_benchmark(
  output_dir = benchmark_output,
  benchmark_dir = benchmark_dir,
  strict = TRUE,
  make_plot = FALSE
)

knitr::kable(
  figure3$results[, c("design", "delay", "variance", "power")],
  digits = 4
)
design delay variance power
2 x 3 0 0.0032 0.4248
2 x 3 1 0.0024 0.5268
2 x 3 2 0.0017 0.6685
2 x 3 3 0.0032 0.4248
3 x 4 0 0.0013 0.7993
3 x 4 1 0.0012 0.8352
3 x 4 2 0.0010 0.8980
3 x 4 3 0.0009 0.9288
3 x 4 4 0.0013 0.7993
4 x 5 0 0.0007 0.9625
4 x 5 1 0.0007 0.9690
4 x 5 2 0.0006 0.9813
4 x 5 3 0.0005 0.9900
4 x 5 4 0.0005 0.9923
4 x 5 5 0.0007 0.9625
.sp_bench_plot_figure3(figure3$results)

knitr::kable(figure3$endpoint_check, digits = 12)
design no_delay_power maximal_delay_power absolute_difference pass
2 x 3 0.4248020 0.4248020 0 TRUE
3 x 4 0.7992751 0.7992751 0 TRUE
4 x 5 0.9624863 0.9624863 0 TRUE

Table 3: separate-time GEE power grid

Table 3 fixes the total sample size at 1,200 and evaluates four combinations of steps, periods, and batches. The public analysis uses swdpwr with a marginal GEE model, logit link, and a very small control-profile increment to include time effects. Although the manuscript caption calls these simulated results, the public code computes analytical GEE power.

table3 <- run_table3_benchmark(
  output_dir = benchmark_output,
  benchmark_dir = benchmark_dir,
  run_external = run_external,
  strict = FALSE
)

table3_display <- table3$validation[, c(
  "design", "p0", "p1", "icc", "reference_power",
  "reproduced_power", "absolute_difference", "status",
  "package_version", "pass"
)]
knitr::kable(table3_display, digits = 3)
design p0 p1 icc reference_power reproduced_power absolute_difference status package_version pass
2 x 3 0.10 0.20 0.01 0.733 0.733 0 evaluated 1.12 TRUE
2 x 3 0.10 0.20 0.10 0.674 0.674 0 evaluated 1.12 TRUE
2 x 3 0.15 0.25 0.01 0.642 0.642 0 evaluated 1.12 TRUE
2 x 3 0.15 0.25 0.10 0.589 0.589 0 evaluated 1.12 TRUE
2 x 3 0.20 0.30 0.01 0.578 0.578 0 evaluated 1.12 TRUE
2 x 3 0.20 0.30 0.10 0.529 0.529 0 evaluated 1.12 TRUE
3 x 4 0.10 0.20 0.01 0.831 0.831 0 evaluated 1.12 TRUE
3 x 4 0.10 0.20 0.10 0.763 0.763 0 evaluated 1.12 TRUE
3 x 4 0.15 0.25 0.01 0.747 0.747 0 evaluated 1.12 TRUE
3 x 4 0.15 0.25 0.10 0.682 0.682 0 evaluated 1.12 TRUE
3 x 4 0.20 0.30 0.01 0.681 0.681 0 evaluated 1.12 TRUE
3 x 4 0.20 0.30 0.10 0.620 0.620 0 evaluated 1.12 TRUE
4 x 5 0.10 0.20 0.01 0.859 0.859 0 evaluated 1.12 TRUE
4 x 5 0.10 0.20 0.10 0.786 0.786 0 evaluated 1.12 TRUE
4 x 5 0.15 0.25 0.01 0.779 0.779 0 evaluated 1.12 TRUE
4 x 5 0.15 0.25 0.10 0.710 0.710 0 evaluated 1.12 TRUE
4 x 5 0.20 0.30 0.01 0.715 0.715 0 evaluated 1.12 TRUE
4 x 5 0.20 0.30 0.10 0.649 0.649 0 evaluated 1.12 TRUE
5 x 6 0.10 0.20 0.01 0.869 0.869 0 evaluated 1.12 TRUE
5 x 6 0.10 0.20 0.10 0.793 0.793 0 evaluated 1.12 TRUE
5 x 6 0.15 0.25 0.01 0.791 0.791 0 evaluated 1.12 TRUE
5 x 6 0.15 0.25 0.10 0.721 0.721 0 evaluated 1.12 TRUE
5 x 6 0.20 0.30 0.01 0.727 0.727 0 evaluated 1.12 TRUE
5 x 6 0.20 0.30 0.10 0.661 0.661 0 evaluated 1.12 TRUE

Figure 5: limited-cluster variance curves

After division by the common multiplicative factor \(2a\), Appendix C gives

\[ \begin{aligned} V_{\mathrm{classic}}(\phi) &= \frac{(1-\phi)(2\phi+1)}{\phi+1},\\ V_{\mathrm{delay\,1}}(\phi) &= \frac{1+\phi}{2},\\ V_{\mathrm{delay\,2}}(\phi) &= 1. \end{aligned} \]

figure5 <- run_figure5_benchmark(
  output_dir = benchmark_output,
  benchmark_dir = benchmark_dir,
  strict = TRUE,
  make_plot = FALSE
)

knitr::kable(figure5$crossing_check, digits = 12)
theoretical_crossing_icc classic_scaled_variance one_unit_delay_scaled_variance absolute_difference pass
0.4472136 0.7236068 0.7236068 0 TRUE

The closed forms are also checked against an independent WLS matrix-inversion calculation for every grid value with \(0 \leq \phi < 1\). The boundary \(\phi=1\) is retained for the analytical curves but excluded from matrix validation because the covariance matrix is singular there.

figure5_matrix_differences <- figure5$validation[
  figure5$validation$matrix_check_applicable,
  c(
    "classic_matrix_absolute_difference",
    "one_unit_delay_matrix_absolute_difference",
    "two_unit_delay_matrix_absolute_difference"
  ),
  drop = FALSE
]
knitr::kable(
  data.frame(
    check = names(figure5_matrix_differences),
    maximum_absolute_difference = vapply(
      figure5_matrix_differences,
      max,
      numeric(1),
      na.rm = TRUE
    )
  ),
  digits = 12
)
check maximum_absolute_difference
classic_matrix_absolute_difference classic_matrix_absolute_difference 0
one_unit_delay_matrix_absolute_difference one_unit_delay_matrix_absolute_difference 0
two_unit_delay_matrix_absolute_difference two_unit_delay_matrix_absolute_difference 0

The classic and one-unit-delay curves cross at \(\phi=1/\sqrt{5}\), approximately 0.4472.

.sp_bench_plot_figure5(figure5$results)

Running the complete suite

The installed benchmark directory contains a single runner:

benchmark_dir <- system.file(
  "benchmarks",
  package = "stepwedgepower"
)

Sys.setenv(
  STEPWEDGEPOWER_RUN_EXTERNAL = "true",
  STEPWEDGEPOWER_BENCHMARK_STRICT = "true",
  STEPWEDGEPOWER_BENCHMARK_OUTPUT =
    file.path(getwd(), "stepwedgepower-benchmark-results")
)

source(file.path(benchmark_dir, "run_all.R"))

The runner writes CSV files, PDF figures, an RDS object, package-version metadata, and sessionInfo(). External package versions are recorded because software updates may alter rounded benchmark values.

Relationship to simulation-based validation

These manuscript benchmarks answer whether the numerical settings and analytical calculations have been reproduced. They do not imply that conditional GLMM power, marginal GEE power, and linear-approximation power should be identical. The package function validate_engine() separately cross-checks the simulation engine against an independently written simulator using the same data-generating and fitted models.

References

Lin T, Bailey BE, Staras S, Bylund C, Lee J-H (2026). Power and Sample Size Implications of Delayed Cluster Initiation in Stepped Wedge Trials. Manuscript dated April 20, 2026.

Companion code: https://github.com/tuolin123/BatchedSW.

Kasza J, Bowden R, Hooper R, Forbes AB (2022). The batched stepped wedge design: a design robust to delays in cluster recruitment. Statistics in Medicine, 41, 3627–3641.