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
#> R_w logR_w variance_log_R_w_permutation LB_R_w_95p_permutation
#> 1 1.281250 0.2478362 0.3694346 0.3892732
#> 2 2.052632 0.7191227 0.5670003 0.4691907
#> UB_R_w_95p_permutation Var_logR_w UB_R_w LB_R_w p_value_R_w
#> 1 4.217094 0.4612333 4.849879 0.3384830 0.3575835
#> 2 8.979924 0.6020799 9.392968 0.4485586 0.1770208
quick_res$df_sample.size.summary
#> N N_trt N_ctl N_comparison_win_ratio
#> 1 20 11 9 99
#> 2 20 6 14 84The returned object is a named list:
names(quick_res)
#> [1] "df_FS.analysis.summary" "df_WR.analysis.summary" "df_sample.size.summary"
#> [4] "df_Total_probability" "df_Total_count"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)
)
)
#> Method Estimated_power
#> 1 FS test 0
#> 2 YG win ratio test 0
binom.conf.exact(
x = sum(wr_success, na.rm = TRUE),
n = sum(!is.na(wr_success))
)
#> PointEst Lower Upper
#> 0.0000000 0.0000000 0.8418861This 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.191lambda_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.