Nothing
## ----settings-knitr, include=FALSE--------------------------------------------
library(ggplot2)
knitr::opts_chunk$set(echo = TRUE, message = FALSE, cache = FALSE,
comment = NA,
dev = "png", dpi = 150, fig.asp = 0.618, fig.width = 7, out.width = "85%", fig.align = "center")
options(rmarkdown.html_vignette.check_title = FALSE)
theme_set(theme_bw())
## ----data---------------------------------------------------------------------
library(DoseFinding)
library(ggplot2)
## collect estimates and dosage information in one place
example_estimates <- function() {
## ANOVA mean estimates and ci bounds extracted from fig. 3 of Bays (2020).
## clinicaltrials.gov page already seems to contain values from the dose-response model fit
mn <- c(-0.55, -1.78, -1.95, -3.29, -4.43, -1.14, -2.74, -4.03, -4.47)
lb <- c(-1.56, -3.15, -3.36, -4.85, -5.40, -2.49, -4.10, -5.50, -5.50)
ub <- c( 0.40, -0.30, -0.54, -1.76, -3.48, 0.24, -1.38, -2.65, -3.44)
se <- (ub - lb)/(2*qnorm(0.975)) # approximate standard error
return(list(mu_hat = mn,
daily_dose = c(0, 2.5, 10, 50, 150, 5, 10, 50, 100),
S_hat = diag(se^2),
# keep track of which elements correspond to which regimen:
index = list(placebo = 1, od = 2:5, bid = 6:9)))
}
## restructure estimates for easy plotting with ggplot
tidy_estimates <- function(est) {
se <- sqrt(diag(est$S_hat))
tidy <- data.frame(daily_dose = est$daily_dose, mu_hat = est$mu_hat,
ub = est$mu_hat + qnorm(0.975) * se, lb = est$mu_hat - qnorm(0.975) * se)
tidy <- rbind(tidy[1, ], tidy) # duplicate placebo
tidy$regimen <- c("od", "bid", rep("od", length(est$index$od)), rep("bid", length(est$index$bid)))
return(tidy)
}
plot_estimates <- function(est) {
df <- tidy_estimates(est)
ggplot(df, aes(daily_dose, mu_hat)) + geom_point() +
geom_errorbar(aes(ymin = lb, ymax = ub)) +
facet_wrap(vars(regimen), labeller = label_both) +
xlab("daily dose") + ylab("percent body weight cange") +
labs(title = "ANOVA estimates with 95% confindence intervals")
}
est <- example_estimates()
plot_estimates(est)
## ----candidate_models---------------------------------------------------------
mods <- list(
od = Mods(emax = c(5, 50),
sigEmax = rbind(c(75, 3.5), c(25, 0.7)),
maxEff = -1,
doses = est$daily_dose[c(est$index$placebo, est$index$od)]),
bid = Mods(emax = c(5, 50),
sigEmax = rbind(c(75, 3.5), c(25, 0.7)),
maxEff = -1,
doses=est$daily_dose[c(est$index$placebo, est$index$bid)]))
plotMods(mods$od, superpose = TRUE, xlab = "daily dose")
plotMods(mods$bid, superpose = TRUE, xlab = "daily dose")
## ----contrasts----------------------------------------------------------------
calculate_contrasts <- function(est, mods) {
S_hat <- est$S_hat
i <- est$index
cm_od <- optContr(mods$od, S=S_hat[c(i$placebo, i$od), c(i$placebo, i$od)])$contMat
cm_bid <- optContr(mods$bid, S=S_hat[c(i$placebo, i$bid), c(i$placebo, i$bid)])$contMat
colnames(cm_od) <- paste0("od_", colnames(cm_od))
rownames(cm_od)[-1] <- paste0("od_", rownames(cm_od)[-1])
colnames(cm_bid) <- paste0("bid_", colnames(cm_bid))
rownames(cm_bid)[-1] <- paste0("bid_", rownames(cm_bid)[-1])
# now build a block matrix (contrasts in columns) like this:
# [ row of placebo coefficients od | row of placebo coefficients bid ]
# [----------------------------------+-----------------------------------]
# [ remaining doses' coefficents od | fill with all zeros ]
# [----------------------------------+-----------------------------------]
# [ fill with all zeros | remaining doses' coefficients bid ]
cm_full <- rbind(
"0"=c(cm_od[1,], cm_bid[1,] ),
cbind(cm_od[-1,], matrix(0, nrow(cm_od) - 1, ncol(cm_bid))),
cbind(matrix(0, nrow(cm_bid) - 1, ncol(cm_od)), cm_bid[-1, ] ))
return(cm_full)
}
cont_mat <- calculate_contrasts(est, mods)
print(round(cont_mat, 2))
## ----test---------------------------------------------------------------------
mct_test <- function(cont_mat, est) {
cont_cov <- t(cont_mat) %*% est$S_hat %*% cont_mat
t_stat <- drop(est$mu_hat %*% cont_mat) / sqrt(diag(cont_cov))
# FIXME: calling non-exported function
p <- MCTpval(contMat = cont_mat, corMat = cov2cor(cont_cov),
df=Inf, tStat=t_stat, alternative = "one.sided")
ord <- rev(order(t_stat))
return(data.frame(tStat = t_stat[ord], pVals = p[ord]))
}
mct_test(cont_mat, est)
## ----estimation_1-------------------------------------------------------------
## calculate response under `model` for od/bid with common e0, but separate remaining parameters
## arguments:
## - model: as a string like "emax",
## - i_par: list of vectors named "placebo", "od", "bid", used for indexing `par`
## - par: numeric, model parameter structured as c(e0, pars_od, pars_bid)
## returns: response at placebo, dose_od, dose_bid (in this order)
eval_model_shared_e0 <- function(model, dose_od, dose_bid, par, i_par) {
resp_placebo <- par[1] # e0
resp_od <- do.call(model, append(list(dose_od, par[1]), as.list(par[i_par$od])))
resp_bid <- do.call(model, append(list(dose_bid, par[1]), as.list(par[i_par$bid])))
resp <- c(resp_placebo, resp_od, resp_bid)
return(resp)
}
## ----estimation_2-------------------------------------------------------------
## find sensible starting values for `fit_model_shared_e0` by fitting separate models,
## index: list of vectors named "placebo", "od", "bid", used for indexing `dose`
## bounds: passed through to `fitMod`
calc_start_values <- function(model, full_mu, full_S, dose, index, bounds) {
separate_coefs <- sapply(c("od", "bid"), function(regimen) {
inds <- c(index$placebo, index[[regimen]])
coef(fitMod(dose[inds], full_mu[inds], S = full_S[inds, inds],
type = "general", model = model, bnds = bounds))[-1] # drop e0 estimate
})
## remove names to prevent error in do.call() in eval_model_shared_e0;
## od, bid coefs are in 1st / second column
start <- c(full_mu[1], as.numeric(separate_coefs), use.names=FALSE)
return(start)
}
## fits 'model' to mu_hat with GLS (using S_hat_inv as weight matrix), using a common e0 for od and bid regimens.
## i_reg: list of vectors named "placebo", "od", "bid", used for indexing `dose`
## i_par: passed through to `eval_model_shared_e0`
## dose: numeric with doses for placebo, od, bid
## lower, upper, start: control parameters fro `nlminb`
fit_model_shared_e0 <- function(model, dose, mu_hat, S_hat_inv, lower, upper, start, i_reg, i_par) {
opt_fun <- function(par) { # make use of lexical scope
resp <- eval_model_shared_e0(model, dose[i_reg$od], dose[i_reg$bid], par, i_par)
delta <- resp - mu_hat
return(drop(t(delta) %*% S_hat_inv %*% delta))
}
fit <- nlminb(start, opt_fun, lower = lower, upper = upper)
return(fit)
}
## ----estimation_3-------------------------------------------------------------
## predict population response in each regimen for dose_seq_*
## note: both dose_seq_* vectors should contain a 0 if response at placebo is of interest
one_bootstrap_sample <- function(est, dose_seq_od, dose_seq_bid) {
mu_new <- drop(mvtnorm::rmvnorm(1, est$mu_hat, est$S_hat))
mod_info <- list(list(name = "emax", bounds = rbind(c(0.15, 225)),
i_par = list(od = 2:3, bid = 4:5), n_par_gaic = 5),
list(name = "sigEmax", bounds = rbind(c(0.15, 225), c(0.5, 5)),
i_par = list(od = 2:4, bid = 5:7), n_par_gaic = 7))
fit <- lapply(mod_info, function(m) {
start <- calc_start_values(m$name, mu_new, est$S_hat, est$daily_dose, est$index, m$bounds)
low <- c(-Inf, -Inf, m$bounds[,1]) # no bounds on e0, eMax
up <- c(Inf, Inf, m$bounds[,2])
fit_model_shared_e0(m$name, est$daily_dose, mu_new, solve(est$S_hat), lower = low, upper = up,
start = start, i_reg = est$index, i_par = m$i_par)
})
## calculate gAICs
gaics <- sapply(fit, `[[`, "objective") + 2 * sapply(mod_info, `[[`, "n_par_gaic")
sel <- which.min(gaics)
mod <- mod_info[[sel]]
## drop the placebo element
pred <- eval_model_shared_e0(mod$name, dose_seq_od, dose_seq_bid, fit[[sel]]$par, mod$i_par)[-1]
return(pred)
}
summarize_bootstrap_samples <- function(samples, probs = c(0.025, 0.25, 0.75, 0.975)) {
stopifnot(length(probs) == 4)
med <- apply(samples, 1, median)
quants <- apply(samples, 1, quantile, probs = probs)
bs_df <- as.data.frame(cbind(med, t(quants)))
names(bs_df) <- c("median", "low_out", "low_in", "high_in", "high_out")
return(bs_df)
}
dose_seq_od <- seq(0, 150, length.out = 21) # do include placebo!
dose_seq_bid <- seq(0, 100, length.out = 21)
set.seed(1, kind = "Mersenne-Twister", sample.kind = "Rejection", normal.kind = "Inversion")
reps <- replicate(1000, one_bootstrap_sample(est, dose_seq_od, dose_seq_bid))
bs_sum <- summarize_bootstrap_samples(reps)
bs_sum$daily_dose <- c(dose_seq_od, dose_seq_bid)
bs_sum$regimen <- c(rep("od", length(dose_seq_od)), rep("bid", length(dose_seq_bid)))
ggplot(bs_sum) + geom_ribbon(aes(daily_dose, ymin=low_out, ymax=high_out), alpha = 0.2) +
geom_ribbon(aes(daily_dose, ymin=low_in, ymax=high_in), alpha = 0.2) +
geom_line(aes(daily_dose, median)) +
geom_point(aes(daily_dose, mu_hat), tidy_estimates(est)) +
facet_wrap(vars(regimen), labeller = label_both) +
labs(title = "Bootstrap estimates for population response",
subtitle = "Least squares estimates plus 50% and 95% confidence bands") +
xlab("daily dose") + ylab("percent body weigh change") +
coord_cartesian(ylim = c(-6, 0))
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.