knitr::opts_chunk$set( collapse = TRUE, cache.path = 'cache/wrapper/', comment = '#>', dpi = 300, out.width = '100%' )
library(dplyr) library(TrialSimulator)
The TrialSimulator
package provides a unified set of wrapper functions that encapsulate statistical methods commonly used in clinical trial simulations. These functions facilitate model fitting, treatment comparisons, and covariate adjustments within a standardized interface.
When multiple active treatment arms are present, each wrapper function automatically performs pairwise comparisons between each active arm and the designated reference (e.g., placebo, control, or standard-of-care). All wrapper functions share a consistent syntax and output structure. Most of them support model specification via an R formula interface, and covariate adjustment is available where appropriate.
Pairwise average treatment effects (ATEs) are estimated using the emmeans
package under the hood. All tests are one-sided, and the ellipsis (...
) argument can be used to define data subsets, enabling flexible analyses such as those needed in enrichment designs.
Below is a summary of the available wrapper functions included in this vignette, along with their corresponding statistical methods, output metrics, and support for covariate adjustment.
+------------------------+-------------------------+------------------------+------------------------+
| Function | Method | Statistics in Outputs | Covariate adjustment |
+:=======================+:========================+:=======================+:=======================+
| fitLinear
| Linear model | ATE | Yes |
+------------------------+-------------------------+------------------------+------------------------+
| fitLogistic
| Logistic model | regression coefficient | Yes |
| | | | |
| | | log odds ratio | |
| | | | |
| | | odds ratio | |
| | | | |
| | | risk ratio | |
| | | | |
| | | risk difference | |
+------------------------+-------------------------+------------------------+------------------------+
| fitCoxph
| Cox PH model | log hazard ratio | Yes |
| | | | |
| | | hazard ratio | |
+------------------------+-------------------------+------------------------+------------------------+
| fitLogrank
| logrank test | | No but supports strata |
+------------------------+-------------------------+------------------------+------------------------+
| fitFarringtonManning
| Farrington-Manning test | | No |
+------------------------+-------------------------+------------------------+------------------------+
To demonstrate the usage of the wrapper functions, we simulate a hypothetical three-arm trial with one control (pbo
) and two active doses (low
and high
). The trial includes a continuous covariate x
, three endpoint types (time-to-event, continuous, and binary), and a binary biomarker used to define subgroups.
The placebo arm is constructed as follows:
## time-to-event endpoint pfs <- endpoint(name = 'pfs', type = 'tte', generator = rexp, rate = .07) ## continuous endpoint cep <- endpoint(name = 'cep', type = 'non-tte', readout = c(cep = 0), generator = rnorm) ## binary endpoint bep <- endpoint(name = 'bep', type = 'non-tte', readout = c(bep = 0), generator = rbinom, size = 1, prob = .1) ## biomarker bm <- endpoint(name = 'biomarker', type = 'non-tte', readout = c(biomarker = 0), generator = rbinom, size = 1, prob = .7) ## covariate covar <- endpoint(name = 'x', type = 'non-tte', readout = c(x = 0), generator = rnorm) pbo <- arm(name = 'pbo') pbo$add_endpoints(pfs, cep, bep, bm, covar)
For brevity, the code for the low and high dose arms and the trial definition are hidden in this vignette. Refer to the source of this vignette for full code. A single milestone for final analysis is defined, with an empty action doNothing.
This is because we will explicitly request locked data outside of the action function when demonstrating the wrapper functions.
## time-to-event endpoint pfs <- endpoint(name = 'pfs', type = 'tte', generator = rexp, rate = .06) ## continuous endpoint cep <- endpoint(name = 'cep', type = 'non-tte', readout = c(cep = 0), generator = rnorm, mean = 1.2) ## binary endpoint bep <- endpoint(name = 'bep', type = 'non-tte', readout = c(bep = 0), generator = rbinom, size = 1, prob = .2) ## biomarker bm <- endpoint(name = 'biomarker', type = 'non-tte', readout = c(biomarker = 0), generator = rbinom, size = 1, prob = .7) ## covariate covar <- endpoint(name = 'x', type = 'non-tte', readout = c(x = 0), generator = rnorm) low <- arm(name = 'low') low$add_endpoints(pfs, cep, bep, bm, covar) ## time-to-event endpoint pfs <- endpoint(name = 'pfs', type = 'tte', generator = rexp, rate = .04) ## continuous endpoint cep <- endpoint(name = 'cep', type = 'non-tte', readout = c(cep = 0), generator = rnorm, mean = 1.3) ## binary endpoint bep <- endpoint(name = 'bep', type = 'non-tte', readout = c(bep = 0), generator = rbinom, size = 1, prob = .35) ## biomarker bm <- endpoint(name = 'biomarker', type = 'non-tte', readout = c(biomarker = 0), generator = rbinom, size = 1, prob = .7) ## covariate covar <- endpoint(name = 'x', type = 'non-tte', readout = c(x = 0), generator = rnorm) high <- arm(name = 'high') high$add_endpoints(pfs, cep, bep, bm, covar) accrual_rate <- data.frame(end_time = c(10, Inf), piecewise_rate = c(30, 50)) trial <- trial( name = 'Trial-3415', n_patients = 300, seed = 1727811904, duration = 1000, enroller = StaggeredRecruiter, accrual_rate = accrual_rate, dropout = rexp, rate = -log(1 - 0.1)/18, ## 10% by month 18 silent = TRUE ) trial$add_arms(sample_ratio = c(1, 1, 1), pbo, low, high) final <- milestone(name = 'final', action = doNothing, when = calendarTime(1000)) listener <- listener() listener$add_milestones(final) controller <- controller(trial, listener)
Now we execute the trial. After simulation, locked data can be retrieved using the get_locked_data()
method with the milestone name "final"
.
controller$run(n = 1, plot_event = FALSE, silent = TRUE) locked_data <- trial$get_locked_data('final') head(locked_data) table(locked_data$arm)
We begin by analyzing the time-to-event endpoint pfs
using both a Cox proportional hazards model and a log-rank test. When performing analysis on a subset defined via the ...
argument, the syntax must be compatible with that of dplyr::filter()
.
## adjust for covariate x fitCoxph(Surv(pfs, pfs_event) ~ arm + x, placebo = 'pbo', data = locked_data, alternative = 'less', scale = 'hazard ratio') fitLogrank(Surv(pfs, pfs_event) ~ arm, placebo = 'pbo', data = locked_data, alternative = 'less') ## more details fitLogrank(Surv(pfs, pfs_event) ~ arm, placebo = 'pbo', data = locked_data, alternative = 'less', tidy = FALSE) ## with strata fitLogrank(Surv(pfs, pfs_event) ~ arm + strata(biomarker), placebo = 'pbo', data = locked_data, alternative = 'less') ## analyze a subset fitCoxph(Surv(pfs, pfs_event) ~ arm + strata(biomarker), placebo = 'pbo', data = locked_data, alternative = 'less', scale = 'log hazard ratio', x > -2 & x < 3) ## define a subset
We analyze the continuous endpoint cep
using linear models, with and without covariate adjustment.
## ATE accounting for covariate x fitLinear(cep ~ arm * x, placebo = 'pbo', data = locked_data, alternative = 'greater') ## marginal model fitLinear(cep ~ arm, placebo = 'pbo', data = locked_data, alternative = 'greater') ## analyze a sub-group fitLinear(cep ~ arm, placebo = 'pbo', data = locked_data, alternative = 'greater', biomarker == 1) ## define the subgroup
We analyze the binary endpoint bep
using logistic regression. Multiple estimands (e.g., odds ratio, risk ratio, risk difference) can be computed by specifying the scale
argument.
## compute regression coefficient of arm fitLogistic(bep ~ arm * x + biomarker, placebo = 'pbo', data = locked_data, alternative = 'greater', scale = 'coefficient') ## compute odds ratio (ATE) fitLogistic(bep ~ arm + x*biomarker, placebo = 'pbo', data = locked_data, alternative = 'greater', scale = 'odds ratio') ## compute risk ratio (ATE) fitLogistic(bep ~ arm + x + biomarker, placebo = 'pbo', data = locked_data, alternative = 'greater', scale = 'risk ratio')
The risk difference can also be estimated using logistic regression or the Farrington-Manning test. Note that the latter does not support covariate adjustment.
## compute risk difference (ATE) fitLogistic(bep ~ arm + x * biomarker, placebo = 'pbo', data = locked_data, alternative = 'greater', scale = 'risk difference') ## compute risk difference without covariate fitLogistic(bep ~ arm, placebo = 'pbo', data = locked_data, alternative = 'greater', scale = 'risk difference') ## analyze a sub-group fitLogistic(bep ~ arm, placebo = 'pbo', data = locked_data, alternative = 'greater', scale = 'risk difference', x < 2 & biomarker != 1) ## define a subgroup ## analyze the same sub-group using the FM test, ## same estimate but different p-values fitFarringtonManning(endpoint = 'bep', placebo = 'pbo', data = locked_data, alternative = 'greater', x < 2 & biomarker != 1)
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.