The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Using winratiosim

Se Yoon Lee

Overview

winratiosim simulates operating characteristics for two-arm clinical trials with a hierarchical win ratio endpoint. A simulated trial includes three prioritized outcome layers:

  1. time to death,
  2. annualized recurrent event count, and
  3. a continuous quality-of-life change score.

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.

Quick start

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                     84

The 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:

Estimating power

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.8418861

This small example is intended only to show the workflow. Power estimates from two simulations are not scientifically meaningful.

Paper-style simulation workflow

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)

Output from the 10,000-simulation run

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

Parameter notes

References

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.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.