The OncoBayes2
package provides flexible functions for Bayesian meta-analytic modeling of the incidence of Dose Limiting Toxicities (DLTs) by dose level, under treatment regimes involving any number of combination partners. Such models may be used to support dose-escalation decisions and estimation of the Maximum Tolerated Dose (MTD) in adaptive Bayesian dose-escalation designs.
The package supports incorporation of historical data through a Meta-Analytic-Combined (MAC) framework [1], which stratifies these heterogeneous sources of information through a hierarchical model. Additionally, it allows the use of EXchangeable/Non-EXchangeable (EX/NEX) priors to manage the amount of information-sharing across subgroups of historical and/or concurrent sources of data.
Consider the application described in Section 3.2 of [1], in which the risk of DLT is to be studied as a function of dose for two drugs, drug A and drug B. Historical information on the toxicity profiles of these two drugs is available from single agent trials trial_A
and trial_B
. The historical data for this example is available in an internal data set.
group_id | DosesAdm1 | DosesAdm2 | Npat | Ntox |
---|---|---|---|---|
trial_A | 3.0 | 0.0 | 3 | 0 |
trial_A | 4.5 | 0.0 | 3 | 0 |
trial_A | 6.0 | 0.0 | 6 | 0 |
trial_A | 8.0 | 0.0 | 3 | 2 |
trial_B | 0.0 | 33.3 | 3 | 0 |
trial_B | 0.0 | 50.0 | 3 | 0 |
trial_B | 0.0 | 100.0 | 4 | 0 |
trial_B | 0.0 | 200.0 | 9 | 0 |
trial_B | 0.0 | 400.0 | 15 | 0 |
trial_B | 0.0 | 800.0 | 20 | 2 |
trial_B | 0.0 | 1120.0 | 17 | 4 |
The objective is to aid dosing and dose-escalation decisions in a future trial, trial_AB
, in which the drugs will be combined. Additionally, another investigator-initiated trial IIT
will study the same combination concurrently. Note that these as-yet-unobserved sources of data are included in the input data as unobserved factor levels. This mechanism allows us to specify a joint meta-analytic prior for all four sources of historical and concurrent data.
## [1] "trial_A" "trial_B" "IIT" "trial_AB"
To fit the hierarchical model described in [4], one makes a call to the function blrm_exnex
, as below.
# Design parameters ---------------------
dref <- c(3, 960)
num_comp <- 2 # two investigational drugs
num_inter <- 1 # one drug-drug interaction needs to be modeled
num_groups <- nlevels(hist_combo2$group_id) # four groups of data
num_strata <- 1 # no stratification needed
# Model fit -----------------------------
blrmfit <- blrm_exnex(
cbind(Ntox, Npat - Ntox) ~
1 + I(log(DosesAdm1 / dref[1])) |
1 + I(log(DosesAdm2 / dref[2])) |
0 + I(DosesAdm1/dref[1] *DosesAdm2/dref[2]) |
group_id,
data = hist_combo2,
prior_EX_mu_mean_comp = matrix(
c(logit(0.1), 0, # hyper-mean of (intercept, log-slope) for drug A
logit(0.1), 0), # hyper-mean of (intercept, log-slope) for drug B
nrow = num_comp,
ncol = 2,
byrow = TRUE
),
prior_EX_mu_sd_comp = matrix(
c(3.33, 1, # hyper-sd of mean mu for (intercept, log-slope) for drug B
3.33, 1), # hyper-sd of mean mu for (intercept, log-slope) for drug B
nrow = num_comp,
ncol = 2,
byrow = TRUE
),
prior_EX_tau_mean_comp = matrix(
c(log(0.25), log(0.125),
log(0.25), log(0.125)),
nrow = num_comp,
ncol = 2,
byrow = TRUE
),
prior_EX_tau_sd_comp = matrix(
c(log(4) / 1.96, log(4) / 1.96,
log(4) / 1.96, log(4) / 1.96),
nrow = num_comp,
ncol = 2,
byrow = TRUE
),
prior_EX_mu_mean_inter = 0,
prior_EX_mu_sd_inter = 1.121,
prior_EX_tau_mean_inter = matrix(log(0.125), nrow = num_inter, ncol = num_strata),
prior_EX_tau_sd_inter = matrix(log(4) / 1.96, nrow = num_inter, ncol = num_strata),
prior_is_EXNEX_comp = rep(FALSE, num_comp),
prior_is_EXNEX_inter = rep(FALSE, num_inter),
prior_EX_prob_comp = matrix(1, nrow = num_groups, ncol = num_comp),
prior_EX_prob_inter = matrix(1, nrow = num_groups, ncol = num_inter),
prior_tau_dist = 1
)
The function blrm_exnex
returns an object from which numerical and graphical posterior summaries can be extracted using OncoBayes2 functions. We recommend making use of the methods described below.
The function prior_summary
provides a facility for printing, in a readable format, a summary of the prior specification.
The main target of inference is generally the probability of DLT at a selection of possible doses. In order to obtain this inference, one needs to specify the covariate levels of interest (which need not be present in the observed data).
In this case, we are interested in predicitons for trial_AB
, with the possible combination doses of drugs A and B below.
newdata <- expand.grid(
group_id = c("trial_AB"),
DosesAdm1 = c(0, 3, 4.5, 6),
DosesAdm2 = c(0, 400, 600, 800),
stringsAsFactors = FALSE
)
newdata$group_id <- factor(newdata$group_id, levels(hist_combo2$group_id))
Note it is important that the factor levels associated with newdata$group_id
be consistent with the levels of the grouping factor used when calling blrm_exnex
. The stringsAsFactors = FALSE
argument above ensures that the variable will initially be treated as a character. The next line re-converts it to a factor with the correct set of four levels. Alternativley, we can create the group_id
column directly as a factor
column within newdata
. The code below results in the same newdata
in a more compact form.
newdata <- expand.grid(
group_id = factor(c("trial_AB"), levels(hist_combo2$group_id)),
DosesAdm1 = c(0, 3, 4.5, 6),
DosesAdm2 = c(0, 400, 600, 800)
)
Posterior summary statistics for the DLT rates at these provisional doses can be extracted from the blrmfit
object using the summary
method.
summ_stats <- summary(blrmfit,
newdata = newdata,
prob = 0.95,
interval_prob = c(0, 0.16, 0.33, 1))
kable(cbind(newdata, summ_stats), digits = 3)
group_id | DosesAdm1 | DosesAdm2 | mean | sd | 2.5% | 50% | 97.5% | (0,0.16] | (0.16,0.33] | (0.33,1] |
---|---|---|---|---|---|---|---|---|---|---|
trial_AB | 0.0 | 0 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 1.000 | 0.000 | 0.000 |
trial_AB | 3.0 | 0 | 0.056 | 0.070 | 0.000 | 0.031 | 0.249 | 0.924 | 0.067 | 0.009 |
trial_AB | 4.5 | 0 | 0.098 | 0.093 | 0.005 | 0.072 | 0.346 | 0.812 | 0.158 | 0.030 |
trial_AB | 6.0 | 0 | 0.166 | 0.139 | 0.013 | 0.129 | 0.537 | 0.598 | 0.294 | 0.108 |
trial_AB | 0.0 | 400 | 0.033 | 0.039 | 0.000 | 0.020 | 0.126 | 0.988 | 0.010 | 0.002 |
trial_AB | 3.0 | 400 | 0.093 | 0.090 | 0.005 | 0.065 | 0.333 | 0.829 | 0.146 | 0.026 |
trial_AB | 4.5 | 400 | 0.143 | 0.129 | 0.010 | 0.103 | 0.501 | 0.675 | 0.234 | 0.091 |
trial_AB | 6.0 | 400 | 0.221 | 0.193 | 0.014 | 0.161 | 0.710 | 0.499 | 0.266 | 0.236 |
trial_AB | 0.0 | 600 | 0.061 | 0.055 | 0.003 | 0.048 | 0.190 | 0.954 | 0.040 | 0.005 |
trial_AB | 3.0 | 600 | 0.130 | 0.119 | 0.010 | 0.092 | 0.467 | 0.716 | 0.210 | 0.074 |
trial_AB | 4.5 | 600 | 0.189 | 0.176 | 0.011 | 0.130 | 0.665 | 0.571 | 0.248 | 0.181 |
trial_AB | 6.0 | 600 | 0.271 | 0.245 | 0.010 | 0.193 | 0.860 | 0.450 | 0.222 | 0.328 |
trial_AB | 0.0 | 800 | 0.104 | 0.073 | 0.017 | 0.089 | 0.278 | 0.844 | 0.141 | 0.014 |
trial_AB | 3.0 | 800 | 0.185 | 0.162 | 0.015 | 0.135 | 0.623 | 0.568 | 0.269 | 0.164 |
trial_AB | 4.5 | 800 | 0.250 | 0.229 | 0.010 | 0.174 | 0.824 | 0.478 | 0.235 | 0.286 |
trial_AB | 6.0 | 800 | 0.327 | 0.293 | 0.006 | 0.232 | 0.943 | 0.410 | 0.185 | 0.405 |
Such summaries may be used to assess which combination doses have acceptable risk of toxicity. For example, according the escalation with overdose control (EWOC) design criteria [3], any doses for which the last column does not exceed 25% are eligible for enrollment.
Before trial_AB
is initiated, it may be of interest to test how this model responds in various scenarios for the initial combination cohort(s).
This can be done easily in OncoBayes2 by updating the initial model fit with additional rows of hypothetical data. In the code below, we explore 3 possible outcomes for an initial cohort enrolled at 3 mg Drug A + 400 mg Drug B, and review the model’s inference at adjacent doses.
# set up two scenarios at the starting dose level
# store them as data frames in a named list
scenarios <- expand.grid(
group_id = factor("trial_AB", levels(hist_combo2$group_id)),
DosesAdm1 = 3,
DosesAdm2 = 400,
Npat = 3,
Ntox = 1:2
) %>% split(1:2) %>% setNames(paste(1:2, "DLTs"))
candidate_doses = expand.grid(
group_id = factor("trial_AB", levels(hist_combo2$group_id)),
DosesAdm1 = c(3, 4.5),
DosesAdm2 = 400
)
scenario_inference <- lapply(scenarios, function(scen_row){
# refit the model with each scenario's additional data
scenario_data <- rbind(hist_combo2, scen_row)
scenario_fit <- update(blrmfit, data = scenario_data)
# summarize posterior at candidate doses
scenario_summ <- summary(scenario_fit,
newdata = candidate_doses,
interval_prob = c(0, 0.16, 0.33, 1))
cbind(candidate_doses, scenario_summ)
})
group_id | DosesAdm1 | DosesAdm2 | mean | sd | 2.5% | 50% | 97.5% | (0,0.16] | (0.16,0.33] | (0.33,1] |
---|---|---|---|---|---|---|---|---|---|---|
trial_AB | 3.0 | 400 | 0.147 | 0.110 | 0.019 | 0.119 | 0.438 | 0.653 | 0.270 | 0.076 |
trial_AB | 4.5 | 400 | 0.217 | 0.155 | 0.030 | 0.177 | 0.611 | 0.446 | 0.354 | 0.200 |
group_id | DosesAdm1 | DosesAdm2 | mean | sd | 2.5% | 50% | 97.5% | (0,0.16] | (0.16,0.33] | (0.33,1] |
---|---|---|---|---|---|---|---|---|---|---|
trial_AB | 3.0 | 400 | 0.256 | 0.155 | 0.051 | 0.223 | 0.639 | 0.306 | 0.438 | 0.257 |
trial_AB | 4.5 | 400 | 0.348 | 0.194 | 0.068 | 0.315 | 0.784 | 0.180 | 0.347 | 0.473 |
In the example of [1], at the time of completion of trial_AB
, the complete historical and concurrent data are as follows.
group_id | DosesAdm1 | DosesAdm2 | Npat | Ntox |
---|---|---|---|---|
trial_A | 3.0 | 0.0 | 3 | 0 |
trial_A | 4.5 | 0.0 | 6 | 0 |
trial_A | 6.0 | 0.0 | 11 | 0 |
trial_A | 8.0 | 0.0 | 3 | 2 |
trial_B | 0.0 | 33.3 | 3 | 0 |
trial_B | 0.0 | 50.0 | 3 | 0 |
trial_B | 0.0 | 100.0 | 4 | 0 |
trial_B | 0.0 | 200.0 | 9 | 0 |
trial_B | 0.0 | 400.0 | 15 | 0 |
trial_B | 0.0 | 800.0 | 20 | 2 |
trial_B | 0.0 | 1120.0 | 17 | 4 |
IIT | 3.0 | 400.0 | 3 | 0 |
IIT | 3.0 | 800.0 | 7 | 5 |
IIT | 4.5 | 400.0 | 3 | 0 |
IIT | 6.0 | 400.0 | 6 | 0 |
IIT | 6.0 | 600.0 | 3 | 2 |
trial_AB | 3.0 | 400.0 | 3 | 0 |
trial_AB | 3.0 | 800.0 | 6 | 2 |
trial_AB | 4.5 | 600.0 | 10 | 2 |
trial_AB | 6.0 | 400.0 | 10 | 3 |
Numerous toxicities were observed in the concurrent IIT
study. Through the MAC framework, these data can influence the model summaries for trial_AB
.
final_fit <- update(blrmfit, data = codata_combo2)
summ <- summary(final_fit, newdata, prob = c(0.5, 0.95), interval_prob = c(0,0.33,1))
final_summ_stats <- cbind(newdata, summ) %>%
mutate(EWOC=1*`(0.33,1]`<=0.25)
ggplot(final_summ_stats,
aes(x=factor(DosesAdm2), colour=EWOC)) +
facet_wrap(~DosesAdm1, labeller=label_both) +
scale_y_continuous(breaks=c(0, 0.16, 0.33, 0.4, 0.6, 0.8, 1.0)) +
coord_cartesian(ylim=c(0,0.8)) +
geom_hline(yintercept = c(0.16, 0.33),
linetype = "dotted") +
geom_pointrange(aes(y=`50%`, ymin=`2.5%`, ymax=`97.5%`)) +
geom_linerange(aes(ymin=`25%`, ymax=`75%`), size=1.5) +
ggtitle("DLT Probability", "Shown is the median (dot), 50% CrI (thick line) and 95% CrI (thin line)") +
ylab(NULL) +
xlab("Dose Drug B [mg]")
It can be insightful to consider the continuous representation of the model as shown below using the modern tidybayes
package. The EWOC criterion fails to be fullfilled once the upper end of the 50% CrI crosses the critical 33% threshold probability since then the 75% quantile of the distribution exceeds the 33% thresholds such that more than 25% of probability lies beyond 33%.
library(tidybayes)
expand.grid(group_id=factor("trial_AB", levels=levels(codata_combo2$group_id)),
DosesAdm2=exp(seq(log(100),log(800),length=100)), DosesAdm1=c(0, 3, 4.5, 6)) %>%
add_draws(posterior_linpred(final_fit, newdata = ., transform=TRUE)) %>%
median_qi(.width=c(0.5, 0.95)) %>%
ggplot(aes(y = .value, x = DosesAdm2)) +
scale_x_log10(breaks=c(100,200,400,600,800)) +
facet_wrap(~DosesAdm1, labeller=label_both) +
scale_y_continuous(breaks=c(0, 0.16, 0.33, 0.4, 0.6, 0.8, 1.0)) +
geom_lineribbon() +
scale_fill_brewer() +
coord_cartesian(ylim=c(0,0.8)) +
geom_hline(yintercept = c(0.16, 0.33),
linetype = "dotted") +
ggtitle("DLT Probability", "Shown is the median (line), 50% CrI (dark) and 95% CrI (light)") +
ylab(NULL) +
xlab("Dose Drug B [mg]")
[1] Neuenschwander, B., Roychoudhury, S., & Schmidli, H. (2016). On the use of co-data in clinical trials. Statistics in Biopharmaceutical Research, 8(3), 345-354.
[2] Neuenschwander, B., Wandel, S., Roychoudhury, S., & Bailey, S. (2016). Robust exchangeability designs for early phase clinical trials with multiple strata. Pharmaceutical statistics, 15(2), 123-134.
[3] Neuenschwander, B., Branson, M., & Gsponer, T. (2008). Critical aspects of the Bayesian approach to phase I cancer trials. Statistics in medicine, 27(13), 2420-2439.
[4] Neuenschwander, B., Matano, A., Tang, Z., Roychoudhury, S., Wandel, S. Bailey, Stuart. (2014). A Bayesian Industry Approach to Phase I Combination Trials in Oncology. In Statistical methods in drug combination studies (Vol. 69). CRC Press.
## R version 3.5.3 (2019-03-11)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 16.04.6 LTS
##
## Matrix products: default
## BLAS: /usr/lib/libblas/libblas.so.3.6.0
## LAPACK: /usr/lib/lapack/liblapack.so.3.6.0
##
## locale:
## [1] C
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] tidybayes_1.1.0 dplyr_0.8.3 ggplot2_3.2.0 knitr_1.22
## [5] RBesT_1.4-0 OncoBayes2_0.4-4 Rcpp_1.0.1
##
## loaded via a namespace (and not attached):
## [1] rstan_2.18.2 ggstance_0.3.2
## [3] tidyselect_0.2.5 xfun_0.6
## [5] purrr_0.2.5 lattice_0.20-38
## [7] colorspace_1.3-2 htmltools_0.3.6
## [9] stats4_3.5.3 loo_2.0.0
## [11] yaml_2.2.0 rlang_0.4.0
## [13] pkgbuild_1.0.2 pillar_1.4.2
## [15] glue_1.3.1 withr_2.1.2
## [17] RColorBrewer_1.1-2 matrixStats_0.54.0
## [19] plyr_1.8.4 stringr_1.4.0
## [21] munsell_0.5.0 gtable_0.2.0
## [23] mvtnorm_1.0-8 coda_0.19-2
## [25] codetools_0.2-16 evaluate_0.13
## [27] forcats_0.3.0 inline_0.3.15
## [29] callr_3.1.0 ps_1.2.1
## [31] parallel_3.5.3 bayesplot_1.6.0
## [33] highr_0.8 rstantools_1.5.1
## [35] arrayhelpers_1.0-20160527 scales_1.0.0
## [37] backports_1.1.3 checkmate_1.8.5
## [39] StanHeaders_2.18.0-1 abind_1.4-5
## [41] gridExtra_2.3 svUnit_0.7-12
## [43] digest_0.6.18 stringi_1.4.3
## [45] processx_3.2.1 grid_3.5.3
## [47] cli_1.0.1 tools_3.5.3
## [49] magrittr_1.5 lazyeval_0.2.1
## [51] tibble_2.1.3 Formula_1.2-3
## [53] crayon_1.3.4 tidyr_0.8.2
## [55] pkgconfig_2.0.2 prettyunits_1.0.2
## [57] ggridges_0.5.1 assertthat_0.2.1
## [59] rmarkdown_1.11 R6_2.4.0
## [61] compiler_3.5.3