knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5 )
winratiosim simulates operating characteristics for two-arm clinical trials
with a hierarchical win ratio endpoint. A simulated trial includes three
prioritized outcome layers:
The package was created for the simulation workflow used in Lee (2025), which compares Finkelstein-Schoenfeld permutation variance calculations with the large-sample variance formula discussed by Yu and Ganju.
The main function is winratiosim(). The example below uses only two
simulated trials and a small sample size so that the vignette builds quickly.
Increase nsim and N for a real operating-characteristic study.
library(winratiosim) quick_res <- winratiosim( nsim = 2, N = 20, Randomization.ratio = c(1, 1), alpha.JFM = 0, theta.JFM = 1, lambda_trt = 0.13, lambda_ctl = 0.15, ann.icr_trt = 0.32, ann.icr_ctl = 0.55, xbase_trt = 45, xfinal_trt = 52.5, xbase_ctl = 45, xfinal_ctl = 45, sd.delta.x_trt = 20, sd.delta.x_ctl = 20, censorrate_trt = 0.2, censorrate_ctl = 0.2, nc = 1, seed = 20250518 ) quick_res$df_WR.analysis.summary quick_res$df_sample.size.summary
The returned object is a named list:
names(quick_res)
The most commonly used elements are:
df_FS.analysis.summary: Finkelstein-Schoenfeld statistic, variance,
z-score, and p-value for each simulated trial.df_WR.analysis.summary: win ratio estimates, confidence limits, variance
estimates, and p-values for each simulated trial.df_Total_probability: treatment win, tie, and control win probabilities.df_sample.size.summary: treatment and control sample sizes generated under
the requested randomization ratio.For a one-sided superiority analysis at level 0.025, one common summary is the
proportion of simulated trials with a significant result. Exact binomial
confidence intervals can be calculated with binom.conf.exact().
fs_success <- quick_res$df_FS.analysis.summary$p_value_FS < 0.025 wr_success <- quick_res$df_WR.analysis.summary$LB_R_w > 1 data.frame( Method = c("FS test", "YG win ratio test"), Estimated_power = c( mean(fs_success, na.rm = TRUE), mean(wr_success, na.rm = TRUE) ) ) binom.conf.exact( x = sum(wr_success, na.rm = TRUE), n = sum(!is.na(wr_success)) )
This small example is intended only to show the workflow. Power estimates from two simulations are not scientifically meaningful.
The following code mirrors the larger simulation workflow used for the paper.
It is not evaluated when this vignette is built because nsim = 10000 can take
substantial time.
library(winratiosim) power.design_parameters <- list( nsim = 10000, N = 400, Randomization.ratio = c(1, 1), alpha.JFM = 0, theta.JFM = 1, lambda_trt = 0.13, lambda_ctl = 0.15, ann.icr_trt = 0.32, ann.icr_ctl = 0.55, xbase_trt = 45, xfinal_trt = 45 + 7.5, sd.delta.x_trt = 20, xbase_ctl = 45, xfinal_ctl = 45, sd.delta.x_ctl = 20, censorrate_trt = 0.2, censorrate_ctl = 0.2, nc = 10, seed = 20250518 ) power.sim_res <- do.call(winratiosim, power.design_parameters) Power_binom_CI_one_sided_FS_Permutation <- binom.conf.exact( x = sum(power.sim_res$df_FS.analysis.summary$p_value_FS < 0.025, na.rm = TRUE), n = sum(!is.na(power.sim_res$df_FS.analysis.summary$p_value_FS)) ) Power_binom_CI_one_sided_WR_Ron_Yu <- binom.conf.exact( x = sum(power.sim_res$df_WR.analysis.summary$LB_R_w > 1, na.rm = TRUE), n = sum(!is.na(power.sim_res$df_WR.analysis.summary$LB_R_w)) ) t1e.design_parameters <- list( nsim = power.design_parameters$nsim, N = power.design_parameters$N, Randomization.ratio = power.design_parameters$Randomization.ratio, alpha.JFM = power.design_parameters$alpha.JFM, theta.JFM = power.design_parameters$theta.JFM, lambda_trt = power.design_parameters$lambda_ctl, lambda_ctl = power.design_parameters$lambda_ctl, ann.icr_trt = power.design_parameters$ann.icr_ctl, ann.icr_ctl = power.design_parameters$ann.icr_ctl, xbase_trt = power.design_parameters$xbase_ctl, xfinal_trt = power.design_parameters$xfinal_ctl, sd.delta.x_trt = power.design_parameters$sd.delta.x_trt, xbase_ctl = power.design_parameters$xbase_ctl, xfinal_ctl = power.design_parameters$xfinal_ctl, sd.delta.x_ctl = power.design_parameters$sd.delta.x_ctl, censorrate_trt = power.design_parameters$censorrate_trt, censorrate_ctl = power.design_parameters$censorrate_ctl, nc = power.design_parameters$nc, seed = 20250518 ) t1e.sim_res <- do.call(winratiosim, t1e.design_parameters) t1e_binom_CI_one_sided_FS_Permutation <- binom.conf.exact( x = sum(t1e.sim_res$df_FS.analysis.summary$p_value_FS < 0.025, na.rm = TRUE), n = sum(!is.na(t1e.sim_res$df_FS.analysis.summary$p_value_FS)) ) t1e_binom_CI_one_sided_WR_Ron_Yu <- binom.conf.exact( x = sum(t1e.sim_res$df_WR.analysis.summary$LB_R_w > 1, na.rm = TRUE), n = sum(!is.na(t1e.sim_res$df_WR.analysis.summary$LB_R_w)) ) df.power.type1 <- data.frame( Method = c("FS test", "YG test"), Power = paste( round(c(Power_binom_CI_one_sided_FS_Permutation[1], Power_binom_CI_one_sided_WR_Ron_Yu[1]), 3), "(", round(c(Power_binom_CI_one_sided_FS_Permutation[2], Power_binom_CI_one_sided_WR_Ron_Yu[2]), 3), ", ", round(c(Power_binom_CI_one_sided_FS_Permutation[3], Power_binom_CI_one_sided_WR_Ron_Yu[3]), 3), ")", sep = "" ), Type_I_Error = paste( round(c(t1e_binom_CI_one_sided_FS_Permutation[1], t1e_binom_CI_one_sided_WR_Ron_Yu[1]), 3), "(", round(c(t1e_binom_CI_one_sided_FS_Permutation[2], t1e_binom_CI_one_sided_WR_Ron_Yu[2]), 3), ", ", round(c(t1e_binom_CI_one_sided_FS_Permutation[3], t1e_binom_CI_one_sided_WR_Ron_Yu[3]), 3), ")", sep = "" ) ) df.variance <- data.frame( Median_Variance_under_Power = c( median(power.sim_res$df_WR.analysis.summary$variance_log_R_w_permutation, na.rm = TRUE), median(power.sim_res$df_WR.analysis.summary$Var_logR_w, na.rm = TRUE) ), Median_Variance_under_Type_I_Error = c( median(t1e.sim_res$df_WR.analysis.summary$variance_log_R_w_permutation, na.rm = TRUE), median(t1e.sim_res$df_WR.analysis.summary$Var_logR_w, na.rm = TRUE) ) ) df.combined <- cbind(df.power.type1, round(df.variance, 4)) df.combined median(power.sim_res$df_WR.analysis.summary$R_w, na.rm = TRUE) median(power.sim_res$df_Total_probability[, "Prob_of_tie"], na.rm = TRUE)
When the paper-style workflow above is run with nsim = 10000, N = 400,
nc = 10, and seed = 20250518, the final summary commands produce the
following output. The full simulation is not rerun during vignette building.
df.power.type1 #> Method Power Type_I_Error #> 1 FS test 0.86(0.853, 0.866) 0.024(0.021, 0.028) #> 2 YG test 0.811(0.803, 0.819) 0.018(0.015, 0.021) df.variance #> Median_Variance_under_Power Median_Variance_under_Type_I_Error #> 1 0.01677787 0.01607165 #> 2 0.01969007 0.01882680 median(power.sim_res$df_WR.analysis.summary$R_w, na.rm = TRUE) #> [1] 1.474161 median(power.sim_res$df_Total_probability[, "Prob_of_tie"], na.rm = TRUE) #> [1] 0.191
lambda_trt and lambda_ctl are annual mortality probabilities.ann.icr_trt and ann.icr_ctl are annual recurrent event incidence rates.xbase_* and xfinal_* define the mean continuous outcome change in each
arm.censorrate_* gives the annual censoring probability.nc controls the number of worker processes. Use nc = 1 when debugging.seed makes the simulation reproducible.Lee, S. Y. (2025). A note on the sample size formula for a win ratio endpoint. Statistics in Medicine, 44, e70165. https://doi.org/10.1002/sim.70165
Finkelstein, D. M., and Schoenfeld, D. A. (1999). Combining mortality and longitudinal measures in clinical trials. Statistics in Medicine, 18(11), 1341-1354.
Pocock, S. J., Ariti, C. A., Collier, T. J., and Wang, D. (2012). The win ratio: a new approach to the analysis of composite endpoints in clinical trials based on clinical priorities. European Heart Journal, 33(2), 176-182.
Yu, R. X., and Ganju, J. (2022). Sample size formula for a win ratio endpoint. Statistics in Medicine, 41(6), 950-963.
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.