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.
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\).
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.
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 |
| 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 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 |
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.
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.
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.
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.