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"
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.
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.
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
the 2. surrogate endpoint given a positive/negative response at the 1. surrogate enpoint p.interim.2.success, p.interim.2.failure, and
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
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.
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
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
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