Introduction

The vignette gives a short introduction to the package BayesAdaptSurrogate. We will introduce the main functions of the package and show how to use the package to design a trial with surrogate enpoints.

The package contains 7 functions

library(BayesAdaptSurrogate)
cbind(ls(2))
##      [,1]               
## [1,] "P.value.Bootstrap"
## [2,] "Posterior.Sup"    
## [3,] "Rand.pr"          
## [4,] "Stopping.rule"    
## [5,] "generate.data"    
## [6,] "get.data.matrix"  
## [7,] "sim.trial"
  1. Designing a trial requires the simulation of outcome data and an trial with the outcome data by using the functions generate.data and sim.trial.

  2. Whereas implementing the actual trial requires the functions Posterior.Sup, Stopping.rule and Rand.pr, for computing the (A) posterior probability of a positive treatment effect, (B) early stopping rules for efficacy/toxicity and (C) patient randomization.

Simulating response adaptive trials

To simulat response adaptive trials we first generate a response to treatment under each treatment agent.

Example: We consider a trial with 3 experimental agents and an active standard of care and a total sample size of 120 patient (30 patient per agent under balanced design), and generate 10 datasets.

Step 1: We specify the probability of a response to treatment for the

    1. surrogate enpoint p.interim.1 and
  1. the 2. surrogate endpoint given a positive/negative response at the 1. surrogate enpoint p.interim.2.success, p.interim.2.failure, and

  2. the primary response given a positive/negative response at the 2. surrogate enpoint p.primary.success, p.primary.failure.

## probability of response to treatment at the 1. preliminary outcome
p.interim.1 = c(.3, .45, .3, .3)   

## probability of response to treatment at the 2. preliminary outcome
### given a positive response to treatment at the 1. preliminary outcome
p.interim.2.success = c(.8, .9, .8, .8)
## given a negative response to treatment at the 1. preliminary outcome
p.interim.2.failure = c(.4, .35, .4, .4)

## probability of response to treatment at the primary outcome
### given a positive response to treatment at the 2. preliminary outcome
p.primary.success     = c(0.9, 0.95, 0.9, 0.9)
## given a negative response to treatment at the 2. preliminary outcome
p.primary.failure     = c(.3, .25, .2, .2)

We then generate 10 outcome datasets.

Step 2:

set.seed(1)
Outcome.Data = BayesAdaptSurrogate::generate.data(
  nr.datasets           = 10,      ## only one dataset
  n                     = 120,     ## total sample size of 10 patient
  arrival.rate          = 1,       ## average of one arriving patient per week
  p.interim.1           = p.interim.1,
  p.interim.2.success   = p.interim.2.success,
  p.interim.2.failure   = p.interim.2.failure,
  p.primary.success     = p.primary.success,
  p.primary.failure     = p.primary.failure,
  p.toxicity            = rep(0.2, 4))

For example the second patient in the first dataset would have a response to treatmen 1 for the first surrogate outcome and a negative outcome for the second surrogate and the primary outcome. Whereas under treatment 2 the patient would have a positive response to treatment 2 for all three outcomes.

Outcome.Data[[1]][2:3,]
##           arrival_time Rand_Agent Sur1_A1 Sur1_A2 Sur1_A3 Sur1_A4 Sur2_A1
## patient_2            3         NA       1       1       0       1       0
## patient_3            3         NA       0       1       0       1       0
##           Sur2_A2 Sur2_A3 Sur2_A4 Fin_A1 Fin_A2 Fin_A3 Fin_A4 Tox_A1
## patient_2       1       1       1      0      1      0      1      0
## patient_3       1       0       1      0      1      0      1      0
##           Tox_A2 Tox_A3 Tox_A4
## patient_2      0      0      1
## patient_3      0      0      0

Moreover the average response to treatment by agents can be check to be as specified. For example the mean response to treatment for the 1. surrogate endpoint equals

## avarage response at the 1. surrogate outcome
rbind("TRUE"=p.interim.1, "sample"=round(colMeans(Outcome.Data[[1]] [,(2+1):(2+4)]), 2))
##        Sur1_A1 Sur1_A2 Sur1_A3 Sur1_A4
## TRUE      0.30    0.45    0.30     0.3
## sample    0.24    0.42    0.31     0.3

Step 3: We then use the simulated outcome data to simulate 10 response adaptive trials with the function sim.trial.

Trials = apply(matrix(1:length(Outcome.Data)), 1, function(i) BayesAdaptSurrogate::sim.trial(
 X               = Outcome.Data[[i]],
 active.control   = TRUE,
 Prior11          = c(.1*0.8,.1*.2), ## prior probability
 Prior10          = c(.1*0.3,.1*.7), ## prior probability
 t_SEP_OK         = 4,               ## surrogate endpoint available after 4 week
 t_EP_OK          = 14,              ## primary endpoint available after 4 week
 HRR              = .6,              ## historical response rate
 Delta            = .12,             ## 12% non-inferiority margin
 TOX.rate         = .3,
 TOX.threshold    = .7, 
 PTE.threshold    = .6,
 a                = (1/60)^3,        ## first randomization parameter
 b                = 3,               ## second randomization parameter
 c                = .25,             ## third randomization parameter
 acc.min          = 10))             ## require at least 10 patients per regiment

The output of the function is a list of outcome data and status. The outcome data has the same for as Outcome.Data with the additional second row now containing treatment assignments.

Outcome.Data[[1]][1:5, 1:5]
##           arrival_time Rand_Agent Sur1_A1 Sur1_A2 Sur1_A3
## patient_1            2         NA       1       0       1
## patient_2            3         NA       1       1       0
## patient_3            3         NA       0       1       0
## patient_4            3         NA       0       1       0
## patient_5            4         NA       0       0       0
Trials[[1]]$X[1:5, 1:5]
##           arrival_time Rand_Agent Sur1_A1 Sur1_A2 Sur1_A3
## patient_1            2          2       1       0       1
## patient_2            3          1       1       1       0
## patient_3            3          3       0       1       0
## patient_4            3          4       0       1       0
## patient_5            4          4       0       0       0

Moreover the element STATUS} returns the status of each agents at the end of the trial. STATUS* is a matrix with two rows and one column for each agent, where STATUS[1,a]=0 if agent a is active until the end of the trial. Otherwise if STATUS[1,a]=1 or STATUS[1,a]=2 then the a-th agent was stop for futility or toxicity at time STATUS[2,a]. For example agent 4 was stoped for futility at after 118 weeks.

Trials[[1]]$STATUS
##        Agent1 Agent2 Agent3 Agent4
## Status      0      0      0      1
## Time        0      0      0    118

Implementing the trial

The function sim.trial uses internally the functions get.data.matrix, Posterior.Sup, Stopping.rule, Rand.pr, to compute the (0) sufficient statistics (A) posterior probability of a positive treatment effect, (B) early stopping rules for efficacy/toxicity and (C) patient randomization.

get.data.matrix

The function get.data.matrix takes the data matrix of available outcome data (1./2. surrogate outcome and toxicity outcome data) and computes the sufficient statistics, i.e. the number of observed outcomes and responsers by agent.

(Events = get.data.matrix(Data.matrix  = Trials[[1]]$X, 
                          nr.agents    = 4,   ## number of agents
                          time.current = 100, ## current time of the trial
                          time.early   = 12,  ## time to observed 1.surrogate outcome 
                          time.final   = 40)) ## time to observed 2.surrogate outcome
## , , surrogate
## 
##         agent_1 agent_2 agent_3 agent_4
## Total        23      26      17      15
## Success       5      11       8       4
## 
## , , final.given.sucess
## 
##         agent_1 agent_2 agent_3 agent_4
## Total         4       7       6       3
## Success       2       7       5       2
## 
## , , final.given.failure
## 
##         agent_1 agent_2 agent_3 agent_4
## Total        11      13       6       9
## Success       6       4       3       1
## 
## , , Toxicity
## 
##         agent_1 agent_2 agent_3 agent_4
## Total        23      26      17      15
## Success       4       3       3       2

Posterior.Sup

The function Posterior.Sup computes the posterior probability of a positive treatment effect, where for active.control=TRUE the first agents is taken as the control agent and agente 2 to 4 are treated as experimental agents.

## treat first agent as standard of care
## and compare agent 2-4 to the active standard of care
(PTE1 = Posterior.Sup(Events          = Events,
              HRR                     = NULL,
              active.control          = TRUE,
              Prior.fin.cond.succsess = c(1,1),
              Prior.fin.cond.failure  = c(1,1)))
## [1] 0.603 0.696 0.078

Otherwise agent 1 to 4 is treated as experimental agent and compared to a historical response rate

## treat all agents (1 to 4) as experimental agents
## and compare to historical control
(PTE2 = Posterior.Sup(Events = Events,
              HRR                     = .4, ### historical response rate
              active.control          = FALSE,
              Prior.fin.cond.succsess = c(1,1),
              Prior.fin.cond.failure  = c(1,1)))
## agent_1 agent_2 agent_3 agent_4 
##   0.845   0.957   0.968   0.174

The function Stopping.rule stops agents for futility if the posterior probability of a positive treatment effect falls below a treshold. Similarly an experimental agents is droped for toxicity, if the posterior probability of toxicity frequency being greater than a given maximum toxicity level reaches a given treshold.

As an input of Stopping.rule requires the matrix STATUS, which indicates the current status of each agent, where STATUS[1,a]=0 if agent a is active and STATUS[1,a]=1,2,3 if the a-th agent was stop for futility, toxicity or both futility and toxicity at time STATUS[2,a].

STATUS   = matrix(0, 2, 4)
### with active control 
(STATUS1 = Stopping.rule(STATUS  = STATUS,
              Event.Tox.mat  = Events,
              Pr.PTE         = PTE1,
              TOX.rate       = .5,
              PTE.threshold  = .1,
              TOX.threshold  = .9,
              active.control = TRUE,
              Time           = 40))
##      [,1] [,2] [,3] [,4]
## [1,]    0    0    0    1
## [2,]    0    0    0   40
### without active control
### with active control 
(STATUS2 = Stopping.rule(STATUS  = STATUS,
              Event.Tox.mat  = Events,
              Pr.PTE         = PTE2,
              TOX.rate       = .5,
              PTE.threshold  = .1,
              TOX.threshold  = .9,
              active.control = FALSE,
              Time           = 40))
##      [,1] [,2] [,3] [,4]
## [1,]    0    0    0    0
## [2,]    0    0    0    0

Rand.pr

Finally the function Rand.pr computes the vector of Bayesian adaptive randomizatio probabilities p=(p_1, , p_A) based on the vector of posterior probabilities of a positive treatment effect; and generates iid random treatment assignments from p such that each treatment assignment equals agents a with probability p_a.

For the case of an active control agent random treatment assignments are generated as follows

## patient accrual by agent at week 40
accrual.by.agent = sapply(1:4, function(a) sum(Trials[[1]]$X[,1] <= 40 & Trials[[1]]$X[,2]==a) )

Rand.pr(Pr.PTE           = PTE1,
        n.draws          = 20,                            ## require 20 random numbers
        Observed.events  = sum(Events[1,,-1]),            ## number of observed 2. surrogate outcomes 
        accrual          = accrual.by.agent,              ## patient accrual by agent at week 40
        acc.min          = 10,                            ## require minimum of 10 observed events per agent
        a                = (1/60)^3,                      ## 1. rand parameter
        b                = 3,                             ## 2. rand parameter
        c                = 1,                             ## 3. rand parameter
        active.control   = TRUE,     ## agent 1 is active control
        STATUS           = STATUS1)  ## all arms are active
##  [1] 3 3 3 3 1 1 3 3 1 3 3 3 3 3 3 3 3 3 3 3

For the case of an historical control agent random treatment assignments are generated as follows

Rand.pr(Pr.PTE           = PTE2,
        n.draws          = 20,                            ## require 20 random numbers
        Observed.events  = sum(Events[1,,-1]),            ## number of observed 2. surrogate outcomes 
        accrual          = accrual.by.agent,              ## patient accrual by agent at week 40
        acc.min          = 10,                            ## require minimum of 10 observed events per agent
        a                = (1/60)^3,                      ## 1. rand parameter
        b                = 3,                             ## 2. rand parameter
        c                = NULL,                          ## 3. rand parameter
        active.control   = FALSE,    ## no active control
        STATUS           = STATUS2)  ## agents 1 to 3 are active
##  [1] 3 3 3 3 3 3 2 3 3 3 3 3 3 2 1 3 3 3 3 3