Skip to contents

Define one or multiple endpoints. This is a user-friendly wrapper for the class constructor Endpoint$new. Users who are not familiar with the concept of classes may consider using this wrapper directly. wrapper if

Usage

endpoint(name, type = c("tte", "non-tte"), readout = NULL, generator, ...)

Arguments

name

character vector. Name(s) of endpoint(s)

type

character vector. Type(s) of endpoint(s). It supports "tte" for time-to-event endpoints, and "non-tte" for all other types of endpoints (e.g., continous, binary, categorical, or repeated measurement. TrialSimulator will do some verification if an endpoint is of type "tte". However, no special manipulation is done for non-tte endpoints.

readout

numeric vector with name to be the non-tte endpoint(s). readout should be specified for every non-tte endpoint. For example, c(endpoint1 = 6, endpoint2 = 3). If all endpoints are tte, readout can be NULL.

generator

a RNG function. Its first argument must be `n`, number of patients. It must return a data frame of `n` rows. It support all built-in random number generators in stats, e.g., stats::rnorm, stats::rexp, etc. that with n as the argument for number of observations. generator could be any custom functions as long as (1) its first argument is n; and (2) it returns a vector of length n or a data frame of n rows. Custom random number generator can return data of more than one endpoint. This is useful when users need to simulate correlated endpoints. The column names of returned data frame should match to name exactly. If an endpoint is of type "tte", the custom generator should also return a column as event indicator. For example, if "pfs" is "tte", then custom generator should return at least two columns "pfs" and "pfs_event". Usually pfs_event can be all 1s if no censoring. Censoring can be specified later when defining the Trial through argument dropout. See ?Trial. Note that if covariates, e.g., biomarker, subgroup, are needed in generating and analyzing trial data, they can be defined as Endpoint as well.

...

optional arguments for generator.

Examples

set.seed(12345)
## Example 1. Generate a time-to-event endpoint.
## Two columns are returned, one for time, one for event (1/0, 0 for
## A built-in RNG function is used to handle piecewise constant exponential
## distribution
risk <- data.frame(
  end_time = c(1, 10, 26.0, 52.0),
  piecewise_risk = c(1, 1.01, 0.381, 0.150) * exp(-3.01)
)

pfs <- endpoint(name = 'pfs', type='tte',
generator = PiecewiseConstantExponentialRNG,
risk = risk, endpoint_name = 'pfs')
pfs$get_generator()
#> generator :
#> $risk
#>   end_time piecewise_risk
#> 1        1    0.049291679
#> 2       10    0.049784596
#> 3       26    0.018780130
#> 4       52    0.007393752
#> 
#> $endpoint_name
#> [1] "pfs"
#> 

## Example 2. Generate continuous and binary endpoints using R's built-in
## RNG functions, e.g. rnorm, rexp, rbinom, etc.
ep1 <- endpoint(
         name = 'cd4', type = 'non-tte', generator = rnorm, readout = c(cd4=1),
         mean = 1.2)
ep2 <- endpoint(
         name = 'resp_time', type = 'non-tte', generator = rexp, readout = c(resp_time=0),
         rate = 4.5)
ep3 <- endpoint(
         name = 'orr', type = 'non-tte', readout = c(orr=3), generator = rbinom,
         size = 1, prob = .4)

mean(ep1$get_generator()(1e4)[, 1]) # compared to 1.2
#> [1] 1.199141
sd(ep1$get_generator()(1e4)[, 1]) # compared to 1.0
#> [1] 0.9865558

log(2) / median(ep2$get_generator()(1e4)[, 1]) # compared to 4.5
#> [1] 4.554779

mean(ep3$get_generator()(1e4)[, 1]) # compared to 0.4
#> [1] 0.3961

## print summary reports for endpoint objects in console
# ep1
# ep2
# ep3

## An example of piecewise constant exponential random number generator
## Baseline hazards are piecewise constant
## Hazard ratios are piecewise constant, resulting a delayed effect.

run <- TRUE

if (!requireNamespace("survminer", quietly = TRUE)) {
  run <- FALSE
  message("Please install 'survminer' to run this example.")
}

if (!requireNamespace("survival", quietly = TRUE)) {
  run <- FALSE
  message("Please install 'survival' to run this example.")
}

if(run){
risk1 <- risk
ep1 <- endpoint(
  name = 'pfs', type='tte',
  generator = PiecewiseConstantExponentialRNG,
  risk=risk1, endpoint_name = 'pfs')

risk2 <- risk1
risk2$hazard_ratio <- c(1, 1, .6, .4)
ep2 <- endpoint(
  name = 'pfs', type='tte',
  generator = PiecewiseConstantExponentialRNG,
  risk=risk2, endpoint_name = 'pfs')

n <- 1000
tte <- rbind(ep1$get_generator()(n), ep2$get_generator()(n))
arm <- rep(0:1, each = n)
dat <- data.frame(tte, arm)
sfit <- survival::survfit(
  survival::Surv(time = pfs, event = pfs_event) ~ arm, dat)

survminer::ggsurvplot(sfit,
           data = dat,
           pval = TRUE,  # Show p-value
           conf.int = TRUE,  # Show confidence intervals
           risk.table = TRUE,  # Add risk table
           palette = c("blue", "red"))

## print summary reports for endpoint objects in console
# ep1
# ep2

}