# Wrapper Functions of Common Statistical Methods in 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 log odds ratio odds ratio risk ratio risk difference | Yes
`fitCoxph` | Cox PH model | log hazard ratio hazard ratio | Yes
`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:

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

    #> A milestone <final> is registered.

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)
    #>   patient_id  arm enroll_time dropout_time       pfs pfs_event        cep
    #> 1          1  pbo  0.00000000     8.015946  8.015946         0 -0.9347262
    #> 2          2 high  0.03333333   181.907651 59.719535         1  2.0994964
    #> 3          3  low  0.06666667   208.113743  2.093918         1  1.0685059
    #> 4          4  low  0.10000000   183.582186 19.297349         1  1.1374167
    #> 5          5  pbo  0.13333333    93.209441  2.798993         1 -1.0593631
    #> 6          6 high  0.16666667   342.746964  1.801312         1  1.5301519
    #>   cep_readout bep bep_readout biomarker biomarker_readout          x x_readout
    #> 1           0   0           0         1                 0 -1.5830337         0
    #> 2           0   1           0         0                 0 -1.5856812         0
    #> 3           0   0           0         1                 0 -0.4026810         0
    #> 4           0   1           0         0                 0  1.5182975         0
    #> 5           0   1           0         1                 0  0.9951762         0
    #> 6           0   1           0         0                 0  0.1088044         0

    table(locked_data$arm)
    #>
    #> high  low  pbo
    #>  100  100  100

## 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()`.

    ## adjust for covariate x
    fitCoxph(Surv(pfs, pfs_event) ~ arm + x, placebo = 'pbo',
             data = locked_data, alternative = 'less',
             scale = 'hazard ratio')
    #>    arm placebo  estimate            p info         z
    #> 1 high     pbo 0.5446845 4.871544e-05  183 -3.896902
    #> 2  low     pbo 0.7423389 2.289992e-02  184 -1.997233

    fitLogrank(Surv(pfs, pfs_event) ~ arm, placebo = 'pbo',
               data = locked_data, alternative = 'less')
    #>    arm placebo            p info         z
    #> 1 high     pbo 3.965163e-05  183 -3.946496
    #> 2  low     pbo 2.314752e-02  184 -1.992693

    ## more details
    fitLogrank(Surv(pfs, pfs_event) ~ arm, placebo = 'pbo',
               data = locked_data, alternative = 'less', tidy = FALSE)
    #>    arm placebo            p info         z info_pbo info_trt n_pbo n_trt
    #> 1 high     pbo 3.965163e-05  183 -3.946496       94       89   100   100
    #> 2  low     pbo 2.314752e-02  184 -1.992693       94       90   100   100

    ## with strata
    fitLogrank(Surv(pfs, pfs_event) ~ arm + strata(biomarker), placebo = 'pbo',
               data = locked_data, alternative = 'less')
    #>    arm placebo           p info         z
    #> 1 high     pbo 3.86834e-05  183 -3.952414
    #> 2  low     pbo 2.27789e-02  184 -1.999467

    ## 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
    #>    arm placebo   estimate            p info         z
    #> 1 high     pbo -0.6389944 3.371561e-05  177 -3.985172
    #> 2  low     pbo -0.3427958 1.359893e-02  177 -2.208666

## Analyze Continuous Endpoint

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')
    #> NOTE: Results may be misleading due to involvement in interactions
    #> NOTE: Results may be misleading due to involvement in interactions
    #>    arm placebo estimate           p info         z
    #> 1 high     pbo 1.099769 1.34337e-13  200  7.847892
    #> 2  low     pbo 1.372198 0.00000e+00  200 10.453246

    ## marginal model
    fitLinear(cep ~ arm, placebo = 'pbo',
              data = locked_data, alternative = 'greater')
    #>    arm placebo estimate            p info         z
    #> 1 high     pbo 1.107296 7.882583e-14  200  7.929746
    #> 2  low     pbo 1.380094 0.000000e+00  200 10.557089

    ## analyze a sub-group
    fitLinear(cep ~ arm, placebo = 'pbo',
              data = locked_data, alternative = 'greater',
              biomarker == 1) ## define the subgroup
    #>    arm placebo estimate            p info        z
    #> 1 high     pbo 1.054402 1.458040e-08  131 5.906693
    #> 2  low     pbo 1.275959 1.775913e-12  136 7.647631

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

    ## compute regression coefficient of arm
    fitLogistic(bep ~ arm * x + biomarker, placebo = 'pbo',
                data = locked_data, alternative = 'greater',
                scale = 'coefficient')
    #>    arm placebo  estimate            p info        z
    #> 1 high     pbo 1.5890262 8.749865e-05  200 3.752618
    #> 2  low     pbo 0.9166145 2.042494e-02  200 2.045051

    ## compute odds ratio (ATE)
    fitLogistic(bep ~ arm + x*biomarker, placebo = 'pbo',
                data = locked_data, alternative = 'greater',
                scale = 'odds ratio')
    #>    arm placebo estimate            p info        z
    #> 1 high     pbo 4.831764 5.122154e-05  200 3.884731
    #> 2  low     pbo 2.301630 2.284767e-02  200 1.998197

    ## compute risk ratio (ATE)
    fitLogistic(bep ~ arm + x + biomarker, placebo = 'pbo',
                data = locked_data, alternative = 'greater',
                scale = 'risk ratio')
    #>    arm placebo estimate            p info        z
    #> 1 high     pbo 3.319826 0.0001568282  200 3.603752
    #> 2  low     pbo 2.049590 0.0226392707  200 2.002058

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')
    #>    arm placebo  estimate            p info        z
    #> 1 high     pbo 0.2377685 1.236247e-05  200 4.217296
    #> 2  low     pbo 0.1048956 2.106819e-02  200 2.032171

    ## compute risk difference without covariate
    fitLogistic(bep ~ arm, placebo = 'pbo',
                data = locked_data, alternative = 'greater',
                scale = 'risk difference')
    #>    arm placebo estimate            p info        z
    #> 1 high     pbo     0.23 1.864081e-05  200 4.123711
    #> 2  low     pbo     0.11 1.483177e-02  200 2.174554

    ## analyze a sub-group
    fitLogistic(bep ~ arm, placebo = 'pbo',
                data = locked_data, alternative = 'greater',
                scale = 'risk difference',
                x < 2 & biomarker != 1) ## define a subgroup
    #>    arm placebo  estimate          p info         z
    #> 1 high     pbo 0.2083333 0.01669696   68 2.1273151
    #> 2  low     pbo 0.0750000 0.21135590   62 0.8017254

    ## 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)
    #>    arm placebo  estimate          p info         z
    #> 1 high     pbo 0.2083333 0.02161304   68 2.0215189
    #> 2  low     pbo 0.0750000 0.21116074   62 0.8024002