--- title: "Wrapper Functions of Common Statistical Methods in TrialSimulator" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Wrapper Functions of Common Statistical Methods in TrialSimulator} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, cache.path = 'cache/wrapper/', comment = '#>', dpi = 300, out.width = '100%' ) ``` ```{r setup, echo = FALSE, message = FALSE} 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 | +------------------------+-------------------------+------------------------+------------------------+ ## Example 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: ```{r ioeajfls} ## 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. ```{r eualgha, echo=FALSE} ## 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"`. ```{r ieoajf} controller$run(n = 1, plot_event = FALSE, silent = TRUE) locked_data <- trial$get_locked_data('final') head(locked_data) table(locked_data$arm) ``` ## Analyze Time-to-Event Endpoint 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()`. ```{r ghkaljf} ## 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 ``` ## Analyze Continuous Endpoint We analyze the continuous endpoint `cep` using linear models, with and without covariate adjustment. ```{r saljfh} ## 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 ``` ## Analyze Binary Endpoint 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. ```{r pqeir} ## 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. ```{r uyta} ## 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) ```