Nothing
#' @include Data-methods.R
#' @include Design-class.R
#' @include McmcOptions-class.R
#' @include Rules-methods.R
#' @include Simulations-class.R
#' @include helpers.R
#' @include mcmc.R
NULL
# simulate ----
## Design ----
#' Simulate outcomes from a CRM design
#'
#' @description `r lifecycle::badge("stable")`
#'
#' @param object the [`Design`] object we want to simulate data from
#' @param nsim (`count`)\cr the number of simulations (default: 1)
#' @param seed see [set_seed()]
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the
#' true probability (vector) for toxicity. Additional arguments can be supplied
#' in `args`.
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The
#' column names correspond to the argument names, the rows to the values of the
#' arguments. The rows are appropriately recycled in the `nsim`
#' simulations. In order to produce outcomes from the posterior predictive
#' distribution, e.g, pass an `object` that contains the data observed so
#' far, `truth` contains the `prob` function from the model in
#' `object`, and `args` contains posterior samples from the model.
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs
#' in this patient.
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`],
#' giving the MCMC options for each evaluation in the trial. By default,
#' the standard options are used
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the
#' clusters of the computer? (not default)
#' @param nCores (`count`)\cr how many cores should be used for parallel computing?
#' Defaults to the number of cores on the machine, maximum 5.
#' @param ... not used
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the
#' vector of posterior MTD samples. Each list element must therefore accept
#' one and only one argument, which is a numeric vector, and return a number.
#'
#' @return an object of class [`Simulations`]
#'
#' @example examples/design-method-simulate-Design.R
#' @export
#' @importFrom parallel detectCores
setMethod(
f = "simulate",
signature = signature(
object = "Design",
nsim = "ANY",
seed = "ANY"
),
definition = function(
object,
nsim = 1L,
seed = NULL,
truth,
args = NULL,
firstSeparate = FALSE,
mcmcOptions = McmcOptions(),
parallel = FALSE,
nCores = min(parallel::detectCores(), 5),
derive = list(),
...
) {
nsim <- as.integer(nsim)
assert_function(truth)
assert_flag(firstSeparate)
assert_count(nsim, positive = TRUE)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
rng_state <- set_seed(seed)
sim_seeds <- sample.int(n = 2147483647, size = as.integer(nsim))
run_sim <- function(iter_sim) {
set.seed(sim_seeds[iter_sim])
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
data <- object@data
prob_placebo <- NULL
cohort_size_placebo <- NULL
if (data@placebo) {
prob_placebo <- h_this_truth(
object@data@doseGrid[1],
current_args,
truth
)
}
should_stop <- FALSE
dose <- object@startingDose
while (!should_stop) {
prob <- h_this_truth(dose, current_args, truth)
cohort_size <- size(object@cohort_size, dose = dose, data = data)
if (data@placebo) {
cohort_size_placebo <- size(
object@pl_cohort_size,
dose = dose,
data = data
)
} else {
cohort_size_placebo <- NULL
}
data <- h_determine_dlts(
data = data,
dose = dose,
prob = prob,
prob_placebo = prob_placebo,
cohort_size = cohort_size,
cohort_size_placebo = cohort_size_placebo,
dose_grid = object@data@doseGrid[1],
first_separate = firstSeparate
)
dose_limit <- maxDose(object@increments, data = data)
samples <- mcmc(
data = data,
model = object@model,
options = mcmcOptions
)
dose <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = samples,
model = object@model,
data = data
)$value
should_stop <- stopTrial(
object@stopping,
dose = dose,
samples = samples,
model = object@model,
data = data
)
stopit_results <- h_unpack_stopit(should_stop)
}
fit_model <- fit(object = samples, model = object@model, data = data)
target_dose_samples <- dose(
mean(object@nextBest@target),
model = object@model,
samples = samples
)
additional_stats <- lapply(derive, function(f) f(target_dose_samples))
list(
data = data,
dose = dose,
fit = subset(fit_model, select = c(middle, lower, upper)),
stop = attr(should_stop, "message"),
report_results = stopit_results,
additional_stats = additional_stats
)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"firstSeparate",
"truth",
"object",
"mcmcOptions"
),
parallel = parallel,
n_cores = nCores
)
simulations_output <- h_simulations_output_format(result_list)
Simulations(
data = simulations_output$dataList,
doses = simulations_output$recommendedDoses,
fit = simulations_output$fitList,
stop_report = simulations_output$stop_matrix,
stop_reasons = simulations_output$stopReasons,
additional_stats = simulations_output$additional_stats,
seed = rng_state
)
}
)
## RuleDesign ----
#' Simulate outcomes from a rule-based design
#'
#' @description `r lifecycle::badge("stable")`
#'
#' @param object the [`RuleDesign`] object we want to simulate data from
#' @param nsim (`count`)\cr the number of simulations (default: 1)
#' @param seed see [set_seed()]
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the
#' true probability (vector) for toxicity. Additional arguments can be supplied
#' in `args`.
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The
#' column names correspond to the argument names, the rows to the values of the
#' arguments. The rows are appropriately recycled in the `nsim`
#' simulations.
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the
#' clusters of the computer? (not default)
#' @param nCores (`count`)\cr how many cores should be used for parallel computing?
#' Defaults to the number of cores on the machine, maximum 5.
#' @param ... not used
#'
#' @return an object of class [`GeneralSimulations`]
#'
#' @example examples/design-method-simulate-RuleDesign.R
#' @export
setMethod(
f = "simulate",
signature = signature(
object = "RuleDesign",
nsim = "ANY",
seed = "ANY"
),
definition = function(
object,
nsim = 1L,
seed = NULL,
truth,
args = NULL,
parallel = FALSE,
nCores = min(parallel::detectCores(), 5L),
...
) {
nsim <- as.integer(nsim)
assert_function(truth)
assert_count(nsim, positive = TRUE)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
assert_class(object, "RuleDesign")
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
rng_state <- set_seed(seed)
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim))
run_sim <- function(iter_sim) {
set.seed(sim_seeds[iter_sim])
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
truth_with_args <- function(dose) {
do.call(truth, c(dose, current_args))
}
data <- object@data
should_stop <- FALSE
dose <- object@startingDose
while (!should_stop) {
prob <- truth_with_args(dose)
cohort_size <- size(object@cohort_size, dose = dose, data = data)
dlts <- rbinom(n = cohort_size, size = 1L, prob = prob)
data <- update(object = data, x = dose, y = dlts)
outcome <- nextBest(object@nextBest, data = data)
dose <- outcome$value
should_stop <- outcome$stopHere
}
list(data = data, dose = dose)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"truth",
"object"
),
parallel = parallel,
n_cores = nCores
)
data_list <- lapply(result_list, "[[", "data")
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose"))
GeneralSimulations(
data = data_list,
doses = recommended_doses,
seed = rng_state
)
}
)
## DualDesign ----
#' Simulate outcomes from a dual-endpoint design
#'
#' @description `r lifecycle::badge("stable")`
#'
#' @param object the [`DualDesign`] object we want to simulate data from
#' @param nsim (`count`)\cr the number of simulations (default: 1)
#' @param seed see [set_seed()]
#' @param trueTox (`function`)\cr a function which takes as input a dose (vector) and returns the
#' true probability (vector) for toxicity. Additional arguments can be supplied
#' in `args`.
#' @param trueBiomarker (`function`)\cr a function which takes as input a dose (vector) and
#' returns the true biomarker level (vector). Additional arguments can be
#' supplied in `args`.
#' @param args (`data.frame`)\cr data frame with arguments for the `trueTox` and
#' `trueBiomarker` function. The column names correspond to the argument
#' names, the rows to the values of the arguments. The rows are appropriately
#' recycled in the `nsim` simulations.
#' @param sigma2W (`number`)\cr variance for the biomarker measurements
#' @param rho (`number`)\cr correlation between toxicity and biomarker measurements (default: 0)
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs
#' in this patient.
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`],
#' giving the MCMC options for each evaluation in the trial. By default,
#' the standard options are used
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the
#' clusters of the computer? (not default)
#' @param nCores (`count`)\cr how many cores should be used for parallel computing?
#' Defaults to the number of cores on the machine, maximum 5.
#' @param ... not used
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the
#' vector of posterior MTD samples. Each list element must therefore accept
#' one and only one argument, which is a numeric vector, and return a number.
#'
#' @return an object of class [`DualSimulations`]
#'
#' @example examples/design-method-simulate-DualDesign.R
#' @importFrom mvtnorm rmvnorm
#' @export
setMethod(
f = "simulate",
signature = signature(object = "DualDesign"),
definition = function(
object,
nsim = 1L,
seed = NULL,
trueTox,
trueBiomarker,
args = NULL,
sigma2W,
rho = 0,
firstSeparate = FALSE,
mcmcOptions = McmcOptions(),
parallel = FALSE,
nCores = min(parallel::detectCores(), 5),
derive = list(),
...
) {
nsim <- as.integer(nsim)
assert_function(trueTox)
assert_function(trueBiomarker)
assert_number(sigma2W, lower = 0)
assert_number(rho, lower = -1, upper = 1)
assert_flag(firstSeparate)
assert_count(nsim, positive = TRUE)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
assert_class(object, "DualDesign")
assert_list(derive)
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
tox_arg_names <- names(formals(trueTox))[-1]
biomarker_arg_names <- names(formals(trueBiomarker))[-1]
covariance_matrix <- matrix(
c(
sigma2W,
sqrt(sigma2W) * rho,
sqrt(sigma2W) * rho,
1
),
nrow = 2,
byrow = TRUE
)
rng_state <- set_seed(seed)
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim))
run_sim <- function(iter_sim) {
set.seed(sim_seeds[iter_sim])
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
tox_with_args <- function(dose) {
do.call(
trueTox,
c(dose, as.list(current_args)[tox_arg_names])
)
}
biomarker_with_args <- function(dose) {
do.call(
trueBiomarker,
c(dose, as.list(current_args)[biomarker_arg_names])
)
}
data <- object@data
should_stop <- FALSE
dose <- object@startingDose
prob_placebo <- NULL
mean_z_placebo <- NULL
mean_biomarker_placebo <- NULL
if (data@placebo) {
prob_placebo <- tox_with_args(object@data@doseGrid[1])
mean_z_placebo <- qlogis(prob_placebo)
mean_biomarker_placebo <- biomarker_with_args(object@data@doseGrid[1])
}
while (!should_stop) {
prob <- tox_with_args(dose)
mean_z <- qlogis(prob)
mean_biomarker <- biomarker_with_args(dose)
cohort_size <- size(object@cohort_size, dose = dose, data = data)
cohort_size_placebo <- NULL
if (data@placebo) {
cohort_size_placebo <- size(
object@pl_cohort_size,
dose = dose,
data = data
)
}
response_data <- if (firstSeparate && (cohort_size > 1L)) {
first_patient <- mvtnorm::rmvnorm(
n = 1,
mean = c(mean_biomarker, mean_z),
sigma = covariance_matrix
)
first_patient_placebo <- NULL
if (data@placebo && (cohort_size_placebo > 0L)) {
first_patient_placebo <- mvtnorm::rmvnorm(
n = 1,
mean = c(mean_biomarker_placebo, mean_z_placebo),
sigma = covariance_matrix
)
}
if (first_patient[, 2] < 0) {
remaining_patients <- mvtnorm::rmvnorm(
n = cohort_size - 1,
mean = c(mean_biomarker, mean_z),
sigma = covariance_matrix
)
first_patient <- rbind(first_patient, remaining_patients)
if (data@placebo && (cohort_size_placebo > 0L)) {
remaining_patients_placebo <- mvtnorm::rmvnorm(
n = cohort_size_placebo,
mean = c(mean_biomarker_placebo, mean_z_placebo),
sigma = covariance_matrix
)
first_patient_placebo <- rbind(
first_patient_placebo,
remaining_patients_placebo
)
}
}
if (data@placebo && (cohort_size_placebo > 0L)) {
list(active = first_patient, placebo = first_patient_placebo)
} else {
list(active = first_patient)
}
} else {
active_responses <- mvtnorm::rmvnorm(
n = cohort_size,
mean = c(mean_biomarker, mean_z),
sigma = covariance_matrix
)
placebo_responses <- NULL
if (data@placebo && (cohort_size_placebo > 0L)) {
placebo_responses <- mvtnorm::rmvnorm(
n = cohort_size_placebo,
mean = c(mean_biomarker_placebo, mean_z_placebo),
sigma = covariance_matrix
)
}
if (data@placebo && (cohort_size_placebo > 0L)) {
list(active = active_responses, placebo = placebo_responses)
} else {
list(active = active_responses)
}
}
biomarkers <- response_data$active[, 1]
dlts <- as.integer(response_data$active[, 2] > 0)
if (data@placebo && (cohort_size_placebo > 0L)) {
biomarkers_placebo <- response_data$placebo[, 1]
dlts_placebo <- as.integer(response_data$placebo[, 2] > 0)
data <- update(
object = data,
x = object@data@doseGrid[1],
y = dlts_placebo,
w = biomarkers_placebo,
check = FALSE
)
data <- update(
object = data,
x = dose,
y = dlts,
w = biomarkers,
new_cohort = FALSE
)
} else {
data <- update(
object = data,
x = dose,
y = dlts,
w = biomarkers
)
}
dose_limit <- maxDose(object@increments, data = data)
samples <- mcmc(
data = data,
model = object@model,
options = mcmcOptions
)
dose <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = samples,
model = object@model,
data = data
)$value
should_stop <- stopTrial(
object@stopping,
dose = dose,
samples = samples,
model = object@model,
data = data
)
stopit_results <- h_unpack_stopit(should_stop)
}
fit_model <- fit(object = samples, model = object@model, data = data)
target_dose_samples <- dose(
mean(object@nextBest@target),
model = object@model,
samples = samples
)
additional_stats <- lapply(derive, function(f) f(target_dose_samples))
list(
data = data,
dose = dose,
fitTox = subset(fit_model, select = c(middle, lower, upper)),
fit_biomarker = subset(
fit_model,
select = c(middleBiomarker, lowerBiomarker, upperBiomarker)
),
rho_est = median(samples@data$rho),
sigma2w_est = median(1 / samples@data$precW),
stop = attr(should_stop, "message"),
additional_stats = additional_stats,
report_results = stopit_results
)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"firstSeparate",
"trueTox",
"trueBiomarker",
"covariance_matrix",
"object",
"mcmcOptions"
),
parallel = parallel,
n_cores = nCores
)
data_list <- lapply(result_list, "[[", "data")
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose"))
rho_estimates <- as.numeric(sapply(result_list, "[[", "rho_est"))
sigma2w_estimates <- as.numeric(sapply(result_list, "[[", "sigma2w_est"))
fit_tox_list <- lapply(result_list, "[[", "fitTox")
fit_biomarker_list <- lapply(result_list, "[[", "fit_biomarker")
stop_reasons <- lapply(result_list, "[[", "stop")
stop_results <- lapply(result_list, "[[", "report_results")
stop_report <- as.matrix(do.call(rbind, stop_results))
additional_stats <- lapply(result_list, "[[", "additional_stats")
DualSimulations(
data = data_list,
doses = recommended_doses,
rho_est = rho_estimates,
sigma2w_est = sigma2w_estimates,
fit = fit_tox_list,
fit_biomarker = fit_biomarker_list,
stop_report = stop_report,
stop_reasons = stop_reasons,
additional_stats = additional_stats,
seed = rng_state
)
}
)
## TDsamplesDesign ----
#' Simulate dose escalation procedure using DLE responses only with DLE samples
#'
#' @description `r lifecycle::badge("stable")`
#'
#' This is a method to simulate dose escalation procedure only using the DLE responses.
#' This is a method based on the [`TDsamplesDesign`] where model used are of
#' [`ModelTox`] class object DLE samples are also used.
#'
#' @param object the [`TDsamplesDesign`] object we want to simulate the data from
#' @param nsim (`count`)\cr the number of simulations (default: 1)
#' @param seed see [set_seed()]
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the true probability
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`.
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The
#' column names correspond to the argument names, the rows to the values of the
#' arguments. The rows are appropriately recycled in the `nsim`
#' simulations. In order to produce outcomes from the posterior predictive
#' distribution, e.g, pass an `object` that contains the data observed so
#' far, `truth` contains the `prob` function from the model in
#' `object`, and `args` contains posterior samples from the model.
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs
#' in this patient.
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`],
#' giving the MCMC options for each evaluation in the trial. By default,
#' the standard options are used
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the
#' clusters of the computer? (not default)
#' @param nCores (`count`)\cr how many cores should be used for parallel computing?
#' Defaults to the number of cores on the machine, maximum 5.
#' @param ... not used
#'
#' @return an object of class [`PseudoSimulations`]
#'
#' @example examples/design-method-simulateTDsamplesDesign.R
#' @export
setMethod(
f = "simulate",
signature = signature(
object = "TDsamplesDesign",
nsim = "ANY",
seed = "ANY"
),
definition = function(
object,
nsim = 1L,
seed = NULL,
truth,
args = NULL,
firstSeparate = FALSE,
mcmcOptions = McmcOptions(),
parallel = FALSE,
nCores = min(parallel::detectCores(), 5L),
...
) {
nsim <- as.integer(nsim)
assert_function(truth)
assert_flag(firstSeparate)
assert_count(nsim, positive = TRUE)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
assert_class(object, "TDsamplesDesign")
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
rng_state <- set_seed(seed)
# Keep original seed generation for snapshot test compatibility
sim_seeds <- sample(x = seq_len(1e5), size = nsim)
run_sim <- function(iter_sim) {
set.seed(sim_seeds[iter_sim])
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
truth_with_args <- function(dose) {
do.call(truth, c(dose, current_args))
}
data <- object@data
prob_placebo <- NULL
if (data@placebo) {
prob_placebo <- truth_with_args(object@data@doseGrid[1])
}
should_stop <- FALSE
dose <- object@startingDose
while (!should_stop) {
prob <- truth_with_args(dose)
cohort_size <- size(object@cohort_size, dose = dose, data = data)
cohort_size_placebo <- NULL
if (data@placebo) {
cohort_size_placebo <- size(
object@pl_cohort_size,
dose = dose,
data = data
)
}
dlts <- if (firstSeparate && (cohort_size > 1L)) {
first_dlt <- rbinom(n = 1L, size = 1L, prob = prob)
dlts_placebo_first <- NULL
if (data@placebo && (cohort_size_placebo > 0L)) {
dlts_placebo_first <- rbinom(n = 1L, size = 1L, prob = prob_placebo)
}
if (first_dlt == 0) {
remaining_dlts <- rbinom(
n = cohort_size - 1L,
size = 1L,
prob = prob
)
c(first_dlt, remaining_dlts)
} else {
first_dlt
}
} else {
rbinom(n = cohort_size, size = 1L, prob = prob)
}
dlts_placebo <- NULL
if (data@placebo && (cohort_size_placebo > 0L)) {
if (firstSeparate && (cohort_size > 1L) && length(dlts) == 1) {
dlts_placebo <- dlts_placebo_first
} else {
dlts_placebo <- if (firstSeparate && (cohort_size > 1L)) {
c(
dlts_placebo_first,
rbinom(
n = cohort_size_placebo,
size = 1L,
prob = prob_placebo
)
)
} else {
rbinom(n = cohort_size_placebo, size = 1L, prob = prob_placebo)
}
}
}
if (data@placebo && (cohort_size_placebo > 0L)) {
data <- update(object = data, x = dose, y = dlts)
data <- update(
object = data,
x = object@data@doseGrid[1],
y = dlts_placebo,
new_cohort = FALSE
)
} else {
data <- update(object = data, x = dose, y = dlts)
}
model <- update(object@model, data = data)
dose_limit <- maxDose(object@increments, data = data)
samples <- mcmc(data = data, model = model, options = mcmcOptions)
next_best_result <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = samples,
model = model,
data = data,
in_sim = TRUE
)
dose <- next_best_result$next_dose_drt
td_target_during_trial <- next_best_result$dose_target_drt
td_target_end_of_trial <- next_best_result$dose_target_eot
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot
ci_tdeot <- list(
lower = next_best_result$ci_dose_target_eot[1],
upper = next_best_result$ci_dose_target_eot[2]
)
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot
should_stop <- stopTrial(
object@stopping,
dose = dose,
samples = samples,
model = model,
data = data
)
stopit_results <- h_unpack_stopit(should_stop)
}
fit_model <- fit(object = samples, model = model, data = data)
list(
data = data,
dose = dose,
TDtargetDuringTrial = td_target_during_trial,
TDtargetEndOfTrial = td_target_end_of_trial,
TDtargetEndOfTrialatdoseGrid = td_target_end_of_trial_at_dose_grid,
TDtargetDuringTrialatdoseGrid = dose,
CITDEOT = ci_tdeot,
ratioTDEOT = ratio_tdeot,
fit = subset(fit_model, select = c(middle, lower, upper)),
stop = attr(should_stop, "message"),
report_results = stopit_results
)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"firstSeparate",
"truth",
"object",
"mcmcOptions"
),
parallel = parallel,
n_cores = nCores
)
data_list <- lapply(result_list, "[[", "data")
td_target_during_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrial"
))
td_target_end_of_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrial"
))
td_target_during_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrialatdoseGrid"
))
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrialatdoseGrid"
))
recommended_doses <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrialatdoseGrid"
))
ci_list <- lapply(result_list, "[[", "CITDEOT")
ratio_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT"))
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT")
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT"))
fit_list <- lapply(result_list, "[[", "fit")
stop_reasons <- lapply(result_list, "[[", "stop")
stop_results <- lapply(result_list, "[[", "report_results")
stop_report <- as.matrix(do.call(rbind, stop_results))
PseudoSimulations(
data = data_list,
doses = recommended_doses,
fit = fit_list,
final_td_target_during_trial_estimates = td_target_during_trial_list,
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list,
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list,
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list,
final_cis = ci_list,
final_ratios = ratio_list,
final_tdeot_cis = ci_tdeot_list,
final_tdeot_ratios = ratio_tdeot_list,
stop_reasons = stop_reasons,
stop_report = stop_report,
seed = rng_state
)
}
)
## TDDesign ----
#' Simulate dose escalation procedure using DLE responses only without samples
#'
#' @description `r lifecycle::badge("stable")`
#'
#' This is a method to simulate dose escalation procedure only using the DLE responses.
#' This is a method based on the [`TDDesign`] where model used are of
#' [`ModelTox`] class object and no samples are involved.
#'
#' @param object the [`TDDesign`] object we want to simulate the data from
#' @param nsim (`count`)\cr the number of simulations (default: 1)
#' @param seed see [set_seed()]
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the true probability
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`.
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The
#' column names correspond to the argument names, the rows to the values of the
#' arguments. The rows are appropriately recycled in the `nsim`
#' simulations. In order to produce outcomes from the posterior predictive
#' distribution, e.g, pass an `object` that contains the data observed so
#' far, `truth` contains the `prob` function from the model in
#' `object`, and `args` contains posterior samples from the model.
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs
#' in this patient.
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the
#' clusters of the computer? (not default)
#' @param nCores (`count`)\cr how many cores should be used for parallel computing?
#' Defaults to the number of cores on the machine, maximum 5.
#' @param ... not used
#'
#' @return an object of class [`PseudoSimulations`]
#'
#' @example examples/design-method-simulateTDDesign.R
#' @export
setMethod(
f = "simulate",
signature = signature(
object = "TDDesign",
nsim = "ANY",
seed = "ANY"
),
definition = function(
object,
nsim = 1L,
seed = NULL,
truth,
args = NULL,
firstSeparate = FALSE,
parallel = FALSE,
nCores = min(parallel::detectCores(), 5L),
...
) {
nsim <- as.integer(nsim)
assert_function(truth)
assert_flag(firstSeparate)
assert_count(nsim, positive = TRUE)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
assert_class(object, "TDDesign")
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
rng_state <- set_seed(seed)
# Keep original seed generation for snapshot test compatibility
sim_seeds <- sample(x = seq_len(1e5), size = nsim)
run_sim <- function(iter_sim) {
set.seed(sim_seeds[iter_sim])
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
truth_with_args <- function(dose) {
do.call(truth, c(dose, current_args))
}
data <- object@data
prob_placebo <- NULL
if (data@placebo) {
prob_placebo <- truth_with_args(object@data@doseGrid[1])
}
should_stop <- FALSE
dose <- object@startingDose
while (!should_stop) {
prob <- truth_with_args(dose)
cohort_size <- size(object@cohort_size, dose = dose, data = data)
cohort_size_placebo <- NULL
if (data@placebo) {
cohort_size_placebo <- size(
object@pl_cohort_size,
dose = dose,
data = data
)
}
dlts <- if (firstSeparate && (cohort_size > 1L)) {
first_dlt <- rbinom(n = 1L, size = 1L, prob = prob)
dlts_placebo_first <- NULL
if (data@placebo && (cohort_size_placebo > 0L)) {
dlts_placebo_first <- rbinom(n = 1L, size = 1L, prob = prob_placebo)
}
if (first_dlt == 0) {
remaining_dlts <- rbinom(
n = cohort_size - 1L,
size = 1L,
prob = prob
)
c(first_dlt, remaining_dlts)
} else {
first_dlt
}
} else {
rbinom(n = cohort_size, size = 1L, prob = prob)
}
dlts_placebo <- NULL
if (data@placebo && (cohort_size_placebo > 0L)) {
if (firstSeparate && (cohort_size > 1L) && length(dlts) == 1) {
dlts_placebo <- dlts_placebo_first
} else {
dlts_placebo <- rbinom(
n = cohort_size_placebo,
size = 1L,
prob = prob_placebo
)
}
}
if (data@placebo && (cohort_size_placebo > 0L)) {
data <- update(
object = data,
x = object@data@doseGrid[1],
y = dlts_placebo
)
data <- update(
object = data,
x = dose,
y = dlts,
new_cohort = FALSE
)
} else {
data <- update(object = data, x = dose, y = dlts)
}
model <- update(object@model, data = data)
dose_limit <- maxDose(object@increments, data = data)
next_best_result <- nextBest(
object@nextBest,
doselimit = dose_limit,
model = model,
data = data,
in_sim = TRUE
)
dose <- next_best_result$next_dose_drt
td_target_during_trial <- next_best_result$dose_target_drt
td_target_end_of_trial <- next_best_result$dose_target_eot
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot
ci_tdeot <- list(
lower = next_best_result$ci_dose_target_eot[1],
upper = next_best_result$ci_dose_target_eot[2]
)
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot
should_stop <- stopTrial(
object@stopping,
dose = dose,
model = model,
data = data
)
stopit_results <- h_unpack_stopit(should_stop)
}
prob_fun <- probFunction(
model,
phi1 = model@phi1,
phi2 = model@phi2
)
fit_model <- list(
phi1 = model@phi1,
phi2 = model@phi2,
probDLE = prob_fun(object@data@doseGrid)
)
list(
data = data,
dose = dose,
TDtargetDuringTrial = td_target_during_trial,
TDtargetEndOfTrial = td_target_end_of_trial,
TDtargetEndOfTrialatdoseGrid = td_target_end_of_trial_at_dose_grid,
TDtargetDuringTrialatdoseGrid = dose,
CITDEOT = ci_tdeot,
ratioTDEOT = ratio_tdeot,
fit = fit_model,
stop = attr(should_stop, "message"),
report_results = stopit_results
)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"firstSeparate",
"truth",
"object"
),
parallel = parallel,
n_cores = nCores
)
data_list <- lapply(result_list, "[[", "data")
td_target_during_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrial"
))
td_target_end_of_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrial"
))
td_target_during_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrialatdoseGrid"
))
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrialatdoseGrid"
))
recommended_doses <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrialatdoseGrid"
))
ci_list <- lapply(result_list, "[[", "CITDEOT")
ratio_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT"))
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT")
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT"))
fit_list <- lapply(result_list, "[[", "fit")
stop_reasons <- lapply(result_list, "[[", "stop")
stop_results <- lapply(result_list, "[[", "report_results")
stop_report <- as.matrix(do.call(rbind, stop_results))
PseudoSimulations(
data = data_list,
doses = recommended_doses,
fit = fit_list,
final_td_target_during_trial_estimates = td_target_during_trial_list,
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list,
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list,
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list,
final_cis = ci_list,
final_ratios = ratio_list,
final_tdeot_cis = ci_tdeot_list,
final_tdeot_ratios = ratio_tdeot_list,
stop_reasons = stop_reasons,
stop_report = stop_report,
seed = rng_state
)
}
)
## DualResponsesDesign ----
#' Simulate dose escalation procedure using both DLE and efficacy responses without samples
#'
#' @description `r lifecycle::badge("stable")`
#'
#' This is a method to simulate dose escalation procedure using both DLE and efficacy responses.
#' This is a method based on the [`DualResponsesDesign`] where DLE model used are of
#' [`ModelTox`] class object and efficacy model used are of [`ModelEff`]
#' class object. In addition, no DLE and efficacy samples are involved or generated in the simulation
#' process.
#'
#' @param object the [`DualResponsesDesign`] object we want to simulate the data from
#' @param nsim (`count`)\cr the number of simulations (default: 1)
#' @param seed see [set_seed()]
#' @param trueDLE (`function`)\cr a function which takes as input a dose (vector) and returns the true probability
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`.
#' @param trueEff (`function`)\cr a function which takes as input a dose (vector) and returns the expected efficacy
#' responses (vector). Additional arguments can be supplied in `args`.
#' @param trueNu (`number`)\cr the precision, the inverse of the variance of the efficacy responses
#' @param args (`data.frame`)\cr data frame with arguments for the `trueDLE` and
#' `trueEff` function. The column names correspond to the argument
#' names, the rows to the values of the arguments. The rows are appropriately
#' recycled in the `nsim` simulations.
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs
#' in this patient.
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the
#' clusters of the computer? (not default)
#' @param nCores (`count`)\cr how many cores should be used for parallel computing?
#' Defaults to the number of cores on the machine, maximum 5.
#' @param ... not used
#'
#' @return an object of class [`PseudoDualSimulations`]
#'
#' @example examples/design-method-simulateDualResponsesDesign.R
#' @export
setMethod(
f = "simulate",
signature = signature(
object = "DualResponsesDesign",
nsim = "ANY",
seed = "ANY"
),
definition = function(
object,
nsim = 1L,
seed = NULL,
trueDLE,
trueEff,
trueNu,
args = NULL,
firstSeparate = FALSE,
parallel = FALSE,
nCores = min(parallel::detectCores(), 5L),
...
) {
nsim <- as.integer(nsim)
assert_function(trueDLE)
assert_function(trueEff)
assert_true(trueNu > 0)
assert_flag(firstSeparate)
assert_count(nsim, positive = TRUE)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
assert_class(object, "DualResponsesDesign")
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
dle_arg_names <- names(formals(trueDLE))[-1]
eff_arg_names <- names(formals(trueEff))[-1]
rng_state <- set_seed(seed)
# Keep original seed generation for snapshot test compatibility
sim_seeds <- sample(x = seq_len(1e5), size = nsim)
run_sim <- function(iter_sim) {
set.seed(sim_seeds[iter_sim])
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
dle_with_args <- function(dose) {
do.call(
trueDLE,
c(dose, as.list(current_args)[dle_arg_names])
)
}
eff_with_args <- function(dose) {
do.call(
trueEff,
c(dose, as.list(current_args)[eff_arg_names])
)
}
data <- object@data
sigma2 <- 1 / trueNu
prob_placebo <- NULL
mean_eff_placebo <- NULL
if (data@placebo) {
prob_placebo <- dle_with_args(object@data@doseGrid[1])
mean_eff_placebo <- eff_with_args(object@data@doseGrid[1])
}
should_stop <- FALSE
dose <- object@startingDose
while (!should_stop) {
prob <- dle_with_args(dose)
mean_eff <- eff_with_args(dose)
cohort_size <- size(object@cohort_size, dose = dose, data = data)
cohort_size_placebo <- NULL
if (data@placebo) {
cohort_size_placebo <- size(
object@pl_cohort_size,
dose = dose,
data = data
)
}
if (firstSeparate && (cohort_size > 1L)) {
dlts <- rbinom(n = 1L, size = 1L, prob = prob)
eff_responses <- rnorm(n = 1L, mean = mean_eff, sd = sqrt(sigma2))
dlts_placebo <- NULL
eff_responses_placebo <- NULL
if (data@placebo && (cohort_size_placebo > 0L)) {
dlts_placebo <- rbinom(n = 1L, size = 1L, prob = prob_placebo)
eff_responses_placebo <- rnorm(
n = 1L,
mean = mean_eff_placebo,
sd = sqrt(sigma2)
)
}
if (dlts == 0) {
remaining_dlts <- rbinom(
n = cohort_size - 1L,
size = 1L,
prob = prob
)
remaining_eff <- rnorm(
n = cohort_size - 1L,
mean = mean_eff,
sd = sqrt(sigma2)
)
dlts <- c(dlts, remaining_dlts)
eff_responses <- c(eff_responses, remaining_eff)
if (data@placebo && (cohort_size_placebo > 0L)) {
remaining_dlts_placebo <- rbinom(
n = cohort_size_placebo,
size = 1L,
prob = prob_placebo
)
remaining_eff_placebo <- rnorm(
n = cohort_size_placebo,
mean = mean_eff_placebo,
sd = sqrt(sigma2)
)
dlts_placebo <- c(dlts_placebo, remaining_dlts_placebo)
eff_responses_placebo <- c(
eff_responses_placebo,
remaining_eff_placebo
)
}
}
} else {
dlts <- rbinom(n = cohort_size, size = 1L, prob = prob)
eff_responses <- rnorm(
n = cohort_size,
mean = mean_eff,
sd = sqrt(sigma2)
)
dlts_placebo <- NULL
eff_responses_placebo <- NULL
if (data@placebo && (cohort_size_placebo > 0L)) {
dlts_placebo <- rbinom(
n = cohort_size_placebo,
size = 1L,
prob = prob_placebo
)
eff_responses_placebo <- rnorm(
n = cohort_size_placebo,
mean = mean_eff_placebo,
sd = sqrt(sigma2)
)
}
}
if (data@placebo && (cohort_size_placebo > 0L)) {
data <- update(
object = data,
x = object@data@doseGrid[1],
y = dlts_placebo,
w = eff_responses_placebo,
check = FALSE
)
data <- update(
object = data,
x = dose,
y = dlts,
w = eff_responses,
new_cohort = FALSE
)
} else {
data <- update(object = data, x = dose, y = dlts, w = eff_responses)
}
dle_model <- update(object = object@model, data = data)
eff_model <- update(object = object@eff_model, data = data)
eff_nu <- eff_model@nu
eff_sigma2 <- if (eff_model@use_fixed) {
1 / eff_nu
} else {
1 / (as.numeric(eff_nu["a"] / eff_nu["b"]))
}
dose_limit <- maxDose(object@increments, data = data)
next_best_result <- nextBest(
object@nextBest,
doselimit = dose_limit,
model = dle_model,
data = data,
model_eff = eff_model,
in_sim = TRUE
)
dose <- next_best_result$next_dose
td_target_during_trial <- next_best_result$dose_target_drt
td_target_during_trial_at_dose_grid <- next_best_result$next_dose_drt
td_target_end_of_trial <- next_best_result$dose_target_eot
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot
gstar <- next_best_result$dose_max_gain
gstar_at_dose_grid <- next_best_result$next_dose_max_gain
recommend <- min(
td_target_end_of_trial_at_dose_grid,
gstar_at_dose_grid
)
ci_tdeot <- list(
lower = next_best_result$ci_dose_target_eot[1],
upper = next_best_result$ci_dose_target_eot[2]
)
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot
ci_gstar <- list(
lower = next_best_result$ci_dose_max_gain[1],
upper = next_best_result$ci_dose_max_gain[2]
)
ratio_gstar <- next_best_result$ci_ratio_dose_max_gain
optimal_dose <- min(gstar, td_target_end_of_trial)
if (optimal_dose == gstar) {
ratio <- ratio_gstar
ci <- ci_gstar
} else {
ratio <- ratio_tdeot
ci <- ci_tdeot
}
should_stop <- stopTrial(
object@stopping,
dose = dose,
model = dle_model,
data = data,
Effmodel = eff_model
)
stopit_results <- h_unpack_stopit(should_stop)
}
prob_fun <- probFunction(
dle_model,
phi1 = dle_model@phi1,
phi2 = dle_model@phi2
)
dle_fit <- list(
phi1 = dle_model@phi1,
phi2 = dle_model@phi2,
probDLE = prob_fun(object@data@doseGrid)
)
eff_fun <- efficacyFunction(
eff_model,
theta1 = eff_model@theta1,
theta2 = eff_model@theta2
)
eff_fit <- list(
theta1 = eff_model@theta1,
theta2 = eff_model@theta2,
ExpEff = eff_fun(object@data@doseGrid)
)
list(
data = data,
dose = dose,
TDtargetDuringTrial = td_target_during_trial,
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid,
TDtargetEndOfTrial = td_target_end_of_trial,
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid,
Gstar = gstar,
GstarAtDoseGrid = gstar_at_dose_grid,
Recommend = recommend,
OptimalDose = optimal_dose,
OptimalDoseAtDoseGrid = recommend,
ratio = ratio,
CI = ci,
ratioGstar = ratio_gstar,
CIGstar = ci_gstar,
ratioTDEOT = ratio_tdeot,
CITDEOT = ci_tdeot,
fitDLE = dle_fit,
fitEff = eff_fit,
sigma2est = eff_sigma2,
stop = attr(should_stop, "message"),
report_results = stopit_results
)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"firstSeparate",
"trueDLE",
"trueEff",
"trueNu",
"object"
),
parallel = parallel,
n_cores = nCores
)
data_list <- lapply(result_list, "[[", "data")
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend"))
td_target_during_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrial"
))
td_target_end_of_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrial"
))
td_target_during_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrialAtDoseGrid"
))
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrialAtDoseGrid"
))
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar"))
gstar_at_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"GstarAtDoseGrid"
))
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose"))
optimal_dose_at_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"Recommend"
))
ci_list <- lapply(result_list, "[[", "CI")
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio"))
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT")
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT"))
ci_gstar_list <- lapply(result_list, "[[", "CIGstar")
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar"))
fit_dle_list <- lapply(result_list, "[[", "fitDLE")
fit_eff_list <- lapply(result_list, "[[", "fitEff")
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est"))
stop_reasons <- lapply(result_list, "[[", "stop")
stop_results <- lapply(result_list, "[[", "report_results")
stop_report <- as.matrix(do.call(rbind, stop_results))
PseudoDualSimulations(
data = data_list,
doses = recommended_doses,
final_td_target_during_trial_estimates = td_target_during_trial_list,
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list,
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list,
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list,
final_cis = ci_list,
final_ratios = ratio_list,
final_gstar_estimates = gstar_list,
final_gstar_at_dose_grid = gstar_at_dose_grid_list,
final_gstar_cis = ci_gstar_list,
final_gstar_ratios = ratio_gstar_list,
final_tdeot_cis = ci_tdeot_list,
final_tdeot_ratios = ratio_tdeot_list,
final_optimal_dose = optimal_dose_list,
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list,
fit = fit_dle_list,
fit_eff = fit_eff_list,
sigma2_est = sigma2_estimates,
stop_reasons = stop_reasons,
stop_report = stop_report,
seed = rng_state
)
}
)
## DualResponsesSamplesDesign ----
### h_simulate_flexi ----
h_simulate_flexi <- function(
object,
nsim = 1L,
seed = NULL,
trueDLE,
trueEff,
trueNu = NULL,
trueSigma2 = NULL,
trueSigma2betaW = NULL,
args = NULL,
firstSeparate = FALSE,
mcmcOptions = McmcOptions(),
parallel = FALSE,
nCores = min(parallel::detectCores(), 5L),
...
) {
stopifnot(
trueSigma2 > 0,
trueSigma2betaW > 0,
is.numeric(trueEff),
length(trueEff) == length(object@data@doseGrid)
)
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
# Get argument names (excluding the first one which is the dose)
dle_arg_names <- names(formals(trueDLE))[-1]
# Seed handling
rng_state <- set_seed(seed)
# Generate individual seeds for simulation runs
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim))
# Function to run a single simulation with index "iter_sim"
run_sim <- function(iter_sim) {
# Set the seed for this run
set.seed(sim_seeds[iter_sim])
# Get current arguments (appropriately recycled)
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
# DLE truth function with current arguments
dle_with_args <- function(dose) {
do.call(
trueDLE,
# First argument: the dose
c(
dose,
# Following arguments
current_args
)
)
}
# Efficacy truth function (fixed for EffFlexi)
eff_truth <- trueEff
# Start with the provided data
data <- object@data
# Trial control variables
should_stop <- FALSE
dose <- object@startingDose
dose_pl <- object@data@doseGrid[1]
# Start with specified variance parameters
sigma2 <- trueSigma2
sigma2_beta_w <- trueSigma2betaW
# Main simulation loop
while (!should_stop) {
# Calculate probabilities and outcomes at current dose
dle_prob <- dle_with_args(dose)
dle_prob_pl <- dle_with_args(dose_pl)
dose_index <- which(dose == data@doseGrid)
mean_eff <- eff_truth[dose_index]
mean_eff_pl <- eff_truth[1]
# Determine cohort size
cohort_size <- size(object@cohort_size, dose = dose, data = data)
if (data@placebo) {
placebo_size <- size(
object@pl_cohort_size,
dose = dose,
data = data
)
}
## simulate DLTs: depends on whether we
## separate the first patient or not.
if (firstSeparate && (cohort_size > 1L)) {
## dose the first patient
dlts <- rbinom(
n = 1L,
size = 1L,
prob = dle_prob
)
if (data@placebo && (placebo_size > 0L)) {
dlts_pl <- rbinom(
n = 1L,
size = 1L,
prob = dle_prob_pl
)
}
eff_responses <- rnorm(
n = 1L,
mean = mean_eff,
sd = sqrt(trueSigma2)
)
if (data@placebo && (placebo_size > 0L)) {
eff_responses_pl <- rnorm(
n = 1L,
mean = mean_eff_pl,
sd = sqrt(trueSigma2)
)
}
# If no DLT in first patient, enroll remaining patients
if (dlts == 0) {
dlts <- c(
dlts,
rbinom(
n = cohort_size - 1L,
size = 1L,
prob = dle_prob
)
)
eff_responses <- c(
eff_responses,
rnorm(
n = cohort_size - 1L,
mean = mean_eff,
sd = sqrt(trueSigma2)
)
)
if (data@placebo && (placebo_size > 0L)) {
dlts_pl <- c(
dlts_pl,
rbinom(
n = placebo_size,
size = 1L,
prob = dle_prob_pl
)
)
eff_responses_pl <- c(
eff_responses_pl,
rnorm(
n = placebo_size,
mean = mean_eff_pl,
sd = sqrt(trueSigma2)
)
)
}
}
} else {
# Dose all patients directly
dlts <- rbinom(
n = cohort_size,
size = 1L,
prob = dle_prob
)
eff_responses <- rnorm(
n = cohort_size,
mean = mean_eff,
sd = sqrt(trueSigma2)
)
if (data@placebo && (placebo_size > 0L)) {
dlts_pl <- rbinom(
n = placebo_size,
size = 1L,
prob = dle_prob_pl
)
eff_responses_pl <- rnorm(
n = placebo_size,
mean = mean_eff_pl,
sd = sqrt(trueSigma2)
)
}
}
## update the data with this placebo (if any) cohort and then with active dose
if (data@placebo && (placebo_size > 0L)) {
data <- update(
object = data,
x = object@data@doseGrid[1],
y = dlts_pl,
w = eff_responses_pl,
check = FALSE
)
## update the data with active dose
data <- update(
object = data,
x = dose,
y = dlts,
w = eff_responses,
new_cohort = FALSE
)
} else {
## update the data with this cohort
data <- update(
object = data,
x = dose,
y = dlts,
w = eff_responses
)
}
# Update models with new data
dle_model <- update(
object = object@model,
data = data
)
eff_model <- update(
object = object@eff_model,
data = data
)
# Calculate dose limit
dose_limit <- maxDose(object@increments, data = data)
# Generate MCMC samples from both models
dle_samples <- mcmc(
data = data,
model = dle_model,
options = mcmcOptions
)
eff_samples <- mcmc(
data = data,
model = eff_model,
options = mcmcOptions
)
# Update variance estimates from MCMC samples
sigma2 <- mean(eff_samples@data$sigma2W)
sigma2_beta_w <- mean(eff_samples@data$sigma2betaW)
# Calculate next best dose
next_bd <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = dle_samples,
model = dle_model,
model_eff = eff_model,
samples_eff = eff_samples,
data = data,
in_sim = TRUE
)
# Extract dose recommendations
dose <- next_bd$next_dose
td_target_during_trial <- next_bd$dose_target_drt
td_target_during_trial_at_dose_grid <- next_bd$next_dose_drt
td_target_end_of_trial <- next_bd$dose_target_eot
td_target_end_of_trial_at_dose_grid <- next_bd$next_dose_eot
gstar <- next_bd$dose_max_gain
gstar_at_dose_grid <- next_bd$next_dose_max_gain
recommend <- min(
td_target_end_of_trial_at_dose_grid,
gstar_at_dose_grid
)
# Calculate 95% confidence intervals and ratios
ci_tdeot <- list(
lower = next_bd$ci_dose_target_eot[1],
upper = next_bd$ci_dose_target_eot[2]
)
ratio_tdeot <- next_bd$ci_ratio_dose_target_eot
ci_gstar <- list(
lower = next_bd$ci_dose_max_gain[1],
upper = next_bd$ci_dose_max_gain[2]
)
ratio_gstar <- next_bd$ci_ratio_dose_max_gain
# Find the optimal dose
optimal_dose <- min(gstar, td_target_end_of_trial)
if (optimal_dose == gstar) {
ratio <- ratio_gstar
ci <- ci_gstar
} else {
ratio <- ratio_tdeot
ci <- ci_tdeot
}
# Evaluate stopping rules
should_stop <- stopTrial(
object@stopping,
dose = dose,
samples = dle_samples,
model = dle_model,
data = data,
TDderive = object@nextBest@derive,
Effmodel = eff_model,
Effsamples = eff_samples,
Gstarderive = object@nextBest@mg_derive
)
stop_results <- h_unpack_stopit(should_stop)
}
# Calculate final model fits
dle_fit <- fit(
object = dle_samples,
model = dle_model,
data = data
)
eff_fit <- fit(
object = eff_samples,
model = eff_model,
data = data
)
# Return simulation results
list(
data = data,
dose = dose,
TDtargetDuringTrial = td_target_during_trial,
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid,
TDtargetEndOfTrial = td_target_end_of_trial,
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid,
Gstar = gstar,
GstarAtDoseGrid = gstar_at_dose_grid,
Recommend = recommend,
OptimalDose = optimal_dose,
OptimalDoseAtDoseGrid = recommend,
ratio = ratio,
CI = ci,
ratioGstar = ratio_gstar,
CIGstar = ci_gstar,
ratioTDEOT = ratio_tdeot,
CITDEOT = ci_tdeot,
fitDLE = subset(dle_fit, select = c(middle, lower, upper)),
fitEff = subset(eff_fit, select = c(middle, lower, upper)),
sigma2est = sigma2,
sigma2betaWest = sigma2_beta_w,
stop = attr(should_stop, "message"),
report_results = stop_results
)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"firstSeparate",
"trueDLE",
"trueEff",
"trueSigma2",
"trueSigma2betaW",
"object",
"mcmcOptions"
),
parallel = parallel,
n_cores = nCores
)
# Process simulation results
data_list <- lapply(result_list, "[[", "data")
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend"))
# Extract model fits and variance estimates
fit_dle_list <- lapply(result_list, "[[", "fitDLE")
fit_eff_list <- lapply(result_list, "[[", "fitEff")
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est"))
sigma2_beta_w_estimates <- as.numeric(sapply(
result_list,
"[[",
"sigma2betaWest"
))
# Extract TD target estimates
td_target_during_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrial"
))
td_target_end_of_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrial"
))
td_target_during_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrialAtDoseGrid"
))
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrialAtDoseGrid"
))
# Extract Gstar and optimal dose estimates
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar"))
gstar_at_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"GstarAtDoseGrid"
))
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose"))
optimal_dose_at_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"Recommend"
))
# Extract confidence intervals and ratios
ci_list <- lapply(result_list, "[[", "CI")
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio"))
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT")
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT"))
ci_gstar_list <- lapply(result_list, "[[", "CIGstar")
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar"))
# Extract stopping information
stop_reasons <- lapply(result_list, "[[", "stop")
stop_results <- lapply(result_list, "[[", "report_results")
stop_report <- as.matrix(do.call(rbind, stop_results))
# Return simulation results
PseudoDualFlexiSimulations(
data = data_list,
doses = recommended_doses,
final_td_target_during_trial_estimates = td_target_during_trial_list,
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list,
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list,
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list,
final_cis = ci_list,
final_ratios = ratio_list,
final_gstar_estimates = gstar_list,
final_gstar_at_dose_grid = gstar_at_dose_grid_list,
final_gstar_cis = ci_gstar_list,
final_gstar_ratios = ratio_gstar_list,
final_tdeot_cis = ci_tdeot_list,
final_tdeot_ratios = ratio_tdeot_list,
final_optimal_dose = optimal_dose_list,
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list,
fit = fit_dle_list,
fit_eff = fit_eff_list,
sigma2_est = sigma2_estimates,
sigma2_beta_w_est = sigma2_beta_w_estimates,
stop_reasons = stop_reasons,
stop_report = stop_report,
seed = rng_state
)
}
### h_simulate_nonflexi ----
h_simulate_nonflexi <- function(
object,
nsim = 1L,
seed = NULL,
trueDLE,
trueEff,
trueNu = NULL,
trueSigma2 = NULL,
trueSigma2betaW = NULL,
args = NULL,
firstSeparate = FALSE,
mcmcOptions = McmcOptions(),
parallel = FALSE,
nCores = min(parallel::detectCores(), 5L),
...
) {
stopifnot(
trueNu > 0,
is.function(trueEff)
)
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
# Get argument names (excluding the first one which is the dose)
dle_arg_names <- names(formals(trueDLE))[-1]
eff_arg_names <- names(formals(trueEff))[-1]
# Seed handling
rng_state <- set_seed(seed)
# Generate individual seeds for simulation runs
sim_seeds <- sample(x = seq_len(1e5), size = nsim)
# Function to run a single simulation with index "iter_sim"
run_sim <- function(iter_sim) {
# Set the seed for this run
set.seed(sim_seeds[iter_sim])
# Get current arguments (appropriately recycled)
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
# DLE truth function with current arguments
dle_with_args <- function(dose) {
do.call(
trueDLE,
# First argument: the dose
c(
dose,
# Following arguments: take only those that
# are required by the DLE function
as.list(current_args)[dle_arg_names]
)
)
}
# Efficacy truth function with current arguments
eff_with_args <- function(dose) {
do.call(
trueEff,
# First argument: the dose
c(
dose,
# Following arguments: take only those that
# are required by the Eff function
as.list(current_args)[eff_arg_names]
)
)
}
# Find true sigma2 to generate responses
true_sigma2 <- 1 / trueNu
# Start with the provided data
data <- object@data
# Trial control variables
should_stop <- FALSE
dose <- object@startingDose
# Main simulation loop
while (!should_stop) {
# Calculate probabilities and outcomes at current dose
dle_prob <- dle_with_args(dose)
mean_eff <- eff_with_args(dose)
# Determine cohort size
cohort_size <- size(object@cohort_size, dose = dose, data = data)
if (data@placebo) {
placebo_size <- size(
object@pl_cohort_size,
dose = dose,
data = data
)
dose_pl <- data@doseGrid[1]
dle_prob_pl <- dle_with_args(dose_pl)
mean_eff_pl <- eff_with_args(dose_pl)
}
## simulate DLTs: depends on whether we
## separate the first patient or not.
if (firstSeparate && (cohort_size > 1L)) {
# Dose the first patient
dlts <- rbinom(
n = 1L,
size = 1L,
prob = dle_prob
)
if (data@placebo && (placebo_size > 0L)) {
dlts_pl <- rbinom(
n = 1L,
size = 1L,
prob = dle_prob_pl
)
}
eff_responses <- rnorm(
n = 1L,
mean = mean_eff,
sd = sqrt(true_sigma2)
)
if (data@placebo && (placebo_size > 0L)) {
eff_responses_pl <- rnorm(
n = 1L,
mean = mean_eff_pl,
sd = sqrt(true_sigma2)
)
}
# If there is no DLT, enroll the remaining patients
if (dlts == 0) {
dlts <- c(
dlts,
rbinom(
n = cohort_size - 1L,
size = 1L,
prob = dle_prob
)
)
eff_responses <- c(
eff_responses,
rnorm(
n = cohort_size - 1L,
mean = mean_eff,
sd = sqrt(true_sigma2)
)
)
if (data@placebo && (placebo_size > 0L)) {
dlts_pl <- c(
dlts_pl,
rbinom(
n = placebo_size,
size = 1L,
prob = dle_prob_pl
)
)
eff_responses_pl <- c(
mean_eff_pl,
rnorm(
n = placebo_size,
mean = mean_eff_pl,
sd = sqrt(true_sigma2)
)
)
}
}
} else {
# Directly dose all patients
dlts <- rbinom(
n = cohort_size,
size = 1L,
prob = dle_prob
)
eff_responses <- rnorm(
n = cohort_size,
mean = mean_eff,
sd = sqrt(true_sigma2)
)
if (data@placebo && (placebo_size > 0L)) {
dlts_pl <- rbinom(
n = placebo_size,
size = 1L,
prob = dle_prob_pl
)
eff_responses_pl <- rnorm(
n = placebo_size,
mean = mean_eff_pl,
sd = sqrt(true_sigma2)
)
}
}
## update the data with this placebo (if any) cohort and then with active dose
if (data@placebo && (placebo_size > 0L)) {
data <- update(
object = data,
x = object@data@doseGrid[1],
y = dlts_pl,
w = eff_responses_pl,
check = FALSE
)
# Update the data with active dose
data <- update(
object = data,
x = dose,
y = dlts,
w = eff_responses,
new_cohort = FALSE
)
} else {
# Update the data with this cohort
data <- update(
object = data,
x = dose,
y = dlts,
w = eff_responses
)
}
# Update models with new data
dle_model <- update(
object = object@model,
data = data
)
eff_model <- update(
object = object@eff_model,
data = data
)
nu <- eff_model@nu
dle_samples <- mcmc(
data = data,
model = dle_model,
options = mcmcOptions
)
eff_samples <- mcmc(
data = data,
model = eff_model,
options = mcmcOptions
)
sigma2 <- if (eff_model@use_fixed) {
1 / nu
} else {
1 / (as.numeric(nu["a"] / nu["b"]))
}
# Calculate dose limit
dose_limit <- maxDose(object@increments, data = data)
# Calculate next best dose
next_bd <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = dle_samples,
model = dle_model,
data = data,
model_eff = eff_model,
samples_eff = eff_samples,
in_sim = TRUE
)
# Extract dose recommendations
dose <- next_bd$next_dose
td_target_during_trial <- next_bd$dose_target_drt
td_target_during_trial_at_dose_grid <- next_bd$next_dose_drt
td_target_end_of_trial <- next_bd$dose_target_eot
td_target_end_of_trial_at_dose_grid <- next_bd$next_dose_eot
gstar <- next_bd$dose_max_gain
gstar_at_dose_grid <- next_bd$next_dose_max_gain
recommend <- min(
td_target_end_of_trial_at_dose_grid,
gstar_at_dose_grid
)
# Calculate 95% confidence intervals and ratios
ci_tdeot <- list(
lower = next_bd$ci_dose_target_eot[1],
upper = next_bd$ci_dose_target_eot[2]
)
ratio_tdeot <- next_bd$ci_ratio_dose_target_eot
ci_gstar <- list(
lower = next_bd$ci_dose_max_gain[1],
upper = next_bd$ci_dose_max_gain[2]
)
ratio_gstar <- next_bd$ci_ratio_dose_max_gain
# Find the optimal dose
optimal_dose <- min(gstar, td_target_end_of_trial)
if (optimal_dose == gstar) {
ratio <- ratio_gstar
ci <- ci_gstar
} else {
ratio <- ratio_tdeot
ci <- ci_tdeot
}
# Evaluate stopping rules
should_stop <- stopTrial(
object@stopping,
dose = dose,
samples = dle_samples,
model = dle_model,
data = data,
TDderive = object@nextBest@derive,
Effmodel = eff_model,
Effsamples = eff_samples,
Gstarderive = object@nextBest@mg_derive
)
stop_results <- h_unpack_stopit(should_stop)
}
# Calculate final model fits
dle_fit <- fit(
object = dle_samples,
model = dle_model,
data = data
)
eff_fit <- fit(
object = eff_samples,
model = eff_model,
data = data
)
# Return simulation results
list(
data = data,
dose = dose,
TDtargetDuringTrial = td_target_during_trial,
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid,
TDtargetEndOfTrial = td_target_end_of_trial,
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid,
Gstar = gstar,
GstarAtDoseGrid = gstar_at_dose_grid,
Recommend = recommend,
OptimalDose = optimal_dose,
OptimalDoseAtDoseGrid = recommend,
ratio = ratio,
CI = ci,
ratioGstar = ratio_gstar,
CIGstar = ci_gstar,
ratioTDEOT = ratio_tdeot,
CITDEOT = ci_tdeot,
fitDLE = subset(dle_fit, select = c(middle, lower, upper)),
fitEff = subset(eff_fit, select = c(middle, lower, upper)),
sigma2est = sigma2,
stop = attr(
should_stop,
"message"
),
report_results = stop_results
)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"firstSeparate",
"trueDLE",
"trueEff",
"trueNu",
"object"
),
parallel = parallel,
n_cores = nCores
)
# Process simulation results
data_list <- lapply(result_list, "[[", "data")
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend"))
# Extract TD target estimates
td_target_during_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrial"
))
td_target_end_of_trial_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrial"
))
td_target_during_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetDuringTrialAtDoseGrid"
))
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"TDtargetEndOfTrialAtDoseGrid"
))
# Extract Gstar and optimal dose estimates
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar"))
gstar_at_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"GstarAtDoseGrid"
))
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose"))
optimal_dose_at_dose_grid_list <- as.numeric(sapply(
result_list,
"[[",
"Recommend"
))
# Extract confidence intervals and ratios
ci_list <- lapply(result_list, "[[", "CI")
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio"))
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT")
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT"))
ci_gstar_list <- lapply(result_list, "[[", "CIGstar")
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar"))
# Extract model fits and variance estimates
fit_dle_list <- lapply(result_list, "[[", "fitDLE")
fit_eff_list <- lapply(result_list, "[[", "fitEff")
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est"))
# Extract stopping information
stop_reasons <- lapply(result_list, "[[", "stop")
stop_results <- lapply(result_list, "[[", "report_results")
stop_report <- as.matrix(do.call(rbind, stop_results))
# Return simulation results
PseudoDualSimulations(
data = data_list,
doses = recommended_doses,
final_td_target_during_trial_estimates = td_target_during_trial_list,
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list,
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list,
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list,
final_cis = ci_list,
final_ratios = ratio_list,
final_gstar_estimates = gstar_list,
final_gstar_at_dose_grid = gstar_at_dose_grid_list,
final_gstar_cis = ci_gstar_list,
final_gstar_ratios = ratio_gstar_list,
final_tdeot_cis = ci_tdeot_list,
final_tdeot_ratios = ratio_tdeot_list,
final_optimal_dose = optimal_dose_list,
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list,
fit = fit_dle_list,
fit_eff = fit_eff_list,
sigma2_est = sigma2_estimates,
stop_reasons = stop_reasons,
stop_report = stop_report,
seed = rng_state
)
}
### method definition ----
#' Simulate dose escalation procedure using DLE and efficacy responses with samples
#'
#' @description `r lifecycle::badge("stable")`
#'
#' This is a method to simulate dose escalation procedure using both DLE and efficacy responses.
#' This is a method based on the [`DualResponsesSamplesDesign`] where DLE model used are of
#' [`ModelTox`] class object and efficacy model used are of [`ModelEff`]
#' class object (special case is [`EffFlexi`] class model object).
#' In addition, DLE and efficacy samples are involved or generated in the simulation
#' process.
#'
#' @param object the [`DualResponsesSamplesDesign`] object we want to
#' simulate the data from
#' @param nsim (`count`)\cr the number of simulations (default: 1)
#' @param seed see [set_seed()]
#' @param trueDLE (`function`)\cr a function which takes as input a dose (vector) and returns the true probability
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`.
#' @param trueEff (`function`)\cr a function which takes as input a dose (vector) and returns the expected
#' efficacy responses (vector). Additional arguments can be supplied in `args`.
#' @param trueNu (`number`)\cr (not with [`EffFlexi`]) the precision, the inverse of the
#' variance of the efficacy responses
#' @param trueSigma2 (`number`)\cr (only with [`EffFlexi`]) the true variance of the efficacy
#' responses which must be a single positive scalar.
#' @param trueSigma2betaW (`number`)\cr (only with [`EffFlexi`]) the true variance for the
#' random walk model used for smoothing. This must be a single positive scalar.
#' @param args (`data.frame`)\cr data frame with arguments for the `trueDLE` and
#' `trueEff` function. The column names correspond to the argument
#' names, the rows to the values of the arguments. The rows are appropriately
#' recycled in the `nsim` simulations.
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs
#' in this patient.
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`],
#' giving the MCMC options for each evaluation in the trial. By default,
#' the standard options are used
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the
#' clusters of the computer? (not default)
#' @param nCores (`count`)\cr how many cores should be used for parallel computing?
#' Defaults to the number of cores on the machine, maximum 5.
#' @param ... not used
#'
#' @return an object of class [`PseudoDualSimulations`] or
#' [`PseudoDualFlexiSimulations`]
#'
#' @example examples/design-method-simulateDualResponsesSamplesDesign.R
#' @export
setMethod(
f = "simulate",
signature = signature(
object = "DualResponsesSamplesDesign",
nsim = "ANY",
seed = "ANY"
),
definition = function(
object,
nsim = 1L,
seed = NULL,
trueDLE,
trueEff,
trueNu = NULL,
trueSigma2 = NULL,
trueSigma2betaW = NULL,
args = NULL,
firstSeparate = FALSE,
mcmcOptions = McmcOptions(),
parallel = FALSE,
nCores = min(parallel::detectCores(), 5L),
...
) {
# Common checks and validations
assert_function(trueDLE)
assert_flag(firstSeparate)
assert_count(nsim, positive = TRUE)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
# Check if special case applies
is_flexi <- is(object@eff_model, "EffFlexi")
# Dispatch to appropriate helper based on model type
if (is_flexi) {
h_simulate_flexi(
object = object,
nsim = nsim,
seed = seed,
trueDLE = trueDLE,
trueEff = trueEff,
trueSigma2 = trueSigma2,
trueSigma2betaW = trueSigma2betaW,
args = args,
firstSeparate = firstSeparate,
mcmcOptions = mcmcOptions,
parallel = parallel,
nCores = nCores
)
} else {
h_simulate_nonflexi(
object = object,
nsim = nsim,
seed = seed,
trueDLE = trueDLE,
trueEff = trueEff,
trueNu = trueNu,
args = args,
firstSeparate = firstSeparate,
mcmcOptions = mcmcOptions,
parallel = parallel,
nCores = nCores
)
}
}
)
## DADesign ----
#' Simulate outcomes from a time-to-DLT augmented CRM design
#'
#' @description `r lifecycle::badge("stable")`
#'
#' This method simulates dose escalation trials using time-to-DLT data,
#' where the timing of dose-limiting toxicities is explicitly modeled.
#'
#' @param object the [`DADesign`] object we want to simulate data from
#' @param nsim (`count`)\cr the number of simulations (default: 1)
#' @param seed see [set_seed()]
#' @param truthTox (`function`)\cr a function which takes as input a dose (vector) and returns the
#' true probability (vector) for toxicity and the time DLT occurs. Additional
#' arguments can be supplied in `args`.
#' @param truthSurv (`function`)\cr a CDF which takes as input a time (vector) and returns
#' the true cumulative probability (vector) that the DLT would occur conditioning on the patient
#' has DLTs.
#' @param trueTmax (`number` or `NULL`)\cr the true maximum time at which DLTs can occur.
#' Note that this must be larger than `Tmax` from the `object`'s base data, which is
#' the length of the DLT window, i.e. until which time DLTs are officially declared
#' as such and used in the trial.
#' @param args (`data.frame`)\cr data frame with arguments for the `truthTox` function. The
#' column names correspond to the argument names, the rows to the values of the
#' arguments. The rows are appropriately recycled in the `nsim`
#' simulations. In order to produce outcomes from the posterior predictive
#' distribution, e.g, pass an `object` that contains the data observed so
#' far, `truthTox` contains the `prob` function from the model in
#' `object`, and `args` contains posterior samples from the model.
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs
#' in this patient.
#' @param deescalate (`flag`)\cr allow deescalation when a DLT occurs in cohorts with lower dose
#' level? (default: TRUE)
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`],
#' giving the MCMC options for each evaluation in the trial. By default,
#' the standard options are used.
#' @param DA (`flag`)\cr use dose-adaptation rules? (default: TRUE)
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the
#' clusters of the computer? (not default)
#' @param nCores (`count`)\cr how many cores should be used for parallel computing?
#' Defaults to the number of cores on the machine, maximum 5.
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the
#' vector of posterior MTD samples. Each list element must therefore accept
#' one and only one argument, which is a numeric vector, and return a number.
#' @param ... not used
#'
#' @return an object of class [`Simulations`]
#'
#' @example examples/design-method-simulate-DADesign.R
#' @export
setMethod(
f = "simulate",
signature = signature(
object = "DADesign",
nsim = "ANY",
seed = "ANY"
),
definition = function(
object,
nsim = 1L,
seed = NULL,
truthTox,
truthSurv,
trueTmax = NULL,
args = NULL,
firstSeparate = FALSE,
deescalate = TRUE,
mcmcOptions = McmcOptions(),
DA = TRUE,
parallel = FALSE,
nCores = min(parallel::detectCores(), 5),
derive = list(),
...
) {
# Validate inputs
assert_function(truthTox)
assert_function(truthSurv)
assert_flag(firstSeparate)
assert_count(nsim, positive = TRUE)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
args <- as.data.frame(args)
n_args <- max(nrow(args), 1L)
# Seed handling
rng_state <- set_seed(seed)
# Generate individual seeds for simulation runs
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim))
# Define inverse function for DLT survival generation.
inverse <- function(f, lower = -100, upper = 100) {
function(y) {
uniroot((function(x) f(x) - y), lower = lower, upper = upper)[1]$root
}
}
# Get DLT window length.
data <- object@data
t_max <- data@Tmax
if (is.null(trueTmax)) {
trueTmax <- t_max
} else if (trueTmax < t_max) {
warning("trueTmax < Tmax! trueTmax is set to Tmax")
trueTmax <- t_max
}
# Calculate the inverse function of survival to DLT CDF.
inverse_truth_surv <- inverse(truthSurv, 0, trueTmax)
# Generate random survival times for DLT data.
# Returns t_max when no DLT occurs.
generate_surv_times <- function(
dlt,
t_max,
inverse_surv = inverse_truth_surv
) {
surv_times <- rep(-100, length(dlt))
if (sum(dlt == 0) > 0) {
surv_times[dlt == 0] <- t_max
}
if (sum(dlt == 1) > 0) {
surv_times[dlt == 1] <- unlist(lapply(
runif(sum(dlt == 1), 0, 1),
inverse_surv
))
}
# Ensure results are always positive.
surv_times[surv_times <= 0] <- 0.05
surv_times
}
# Check if follow-up requirements are fulfilled for opening next cohort.
ready_to_open <- function(day, window, surv_times) {
cohort_size <- length(surv_times)
# Calculate when patients start.
start_time <- apply(
rbind(surv_times[-cohort_size], window$patientGap[-1]),
2,
min
)
# Calculate relative time for each patient on the specified day.
individual_check <- day - cumsum(c(0, start_time))
# Ensure minimum is 0.
individual_check[individual_check < 0] <- 0
follow_up <- apply(rbind(surv_times, individual_check), 2, min)
all(
(follow_up -
apply(rbind(window$patientFollow, surv_times), 2, min)) >=
0
) &
(max(follow_up) >= min(window$patientFollowMin, max(surv_times)))
}
# Determine when to open the next cohort.
# Assumes sufficient patients are available for immediate enrollment.
next_open <- function(window, surv_times) {
cohort_size <- length(surv_times)
window$patientGap <- window$patientGap[1:cohort_size]
# If DLT happens before end of DLT window, next cohort opens earlier.
start_time <- apply(
rbind(surv_times[-cohort_size], window$patientGap[-1]),
2,
min
)
# Duration until all DLT windows finished.
max_time <- max(surv_times + cumsum(c(0, start_time)))
requirements_met <- sapply(1:max_time, function(i) {
ready_to_open(i, window, surv_times)
})
if (sum(requirements_met) > 0) {
# Earliest time that requirements are met.
time <- min(c(1:max_time)[requirements_met])
} else {
time <- max_time
}
time
}
# Function to run a single simulation.
run_sim <- function(iter_sim) {
# Set the seed for this run.
set.seed(sim_seeds[iter_sim])
# Get current arguments (appropriately recycled).
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
# Truth function with current arguments.
truth_with_args <- function(dose) {
do.call(
truthTox,
c(
dose,
current_args
)
)
}
# Start with the provided data.
data <- object@data
# Handle placebo if present.
if (data@placebo) {
prob_pl <- truth_with_args(object@data@doseGrid[1])
}
# Trial control variables.
should_stop <- FALSE
trial_time <- 0
# Initialize observed DLT data.
observed_dlts <- data@y
observed_surv <- data@u
observed_t0 <- data@t0
# Initialize with starting dose.
dose <- object@startingDose
# Main simulation loop.
while (!should_stop) {
# Calculate toxicity probability at current dose.
prob <- truth_with_args(dose)
# Determine cohort size.
cohort_size <- size(object@cohort_size, dose = dose, data = data)
if (data@placebo) {
placebo_size <- size(
object@pl_cohort_size,
dose = dose,
data = data
)
}
total_size <- if (data@placebo) {
cohort_size + placebo_size
} else {
cohort_size
}
safety_window <- windowLength(object@safetyWindow, total_size)
# Simulate DLTs for cohort.
# If any patient has DLT before first patient finishes staggered window,
# further enrollment will be stopped.
h_generate_dlt_and_surv <- function(n, prob, start = NULL) {
dlts <- rbinom(
n = n,
size = 1L,
prob = prob
)
surv_times <- ceiling(generate_surv_times(
dlts,
trueTmax,
inverse_surv = inverse_truth_surv
))
if (!is.null(start)) {
dlts <- c(start$dlts, dlts)
surv_times <- c(start$surv, surv_times)
}
if (t_max < trueTmax) {
dlts[dlts == 1 & surv_times > t_max] <- 0
surv_times <- apply(
rbind(surv_times, rep(t_max, length(surv_times))),
2,
min
)
}
list(dlts = dlts, surv = surv_times)
}
# Update data with active and placebo cohorts.
h_update_data_da <- function(active, placebo, time) {
result <- update(
object = data,
y = c(observed_dlts, active$dlts),
u = c(observed_surv, active$surv),
t0 = c(observed_t0, cohort_t0),
x = dose,
trialtime = time
)
if (data@placebo) {
result <- update(
object = result,
y = c(observed_dlts, active$dlts, placebo$dlts),
u = c(observed_surv, active$surv, placebo$surv),
t0 = c(
observed_t0,
cohort_t0,
rep(cohort_t0[1], length(placebo$dlts))
),
x = object@data@doseGrid[1],
trialtime = time
)
}
result
}
if (firstSeparate && (cohort_size > 1L)) {
# Dose the first patient.
active_dlt_surv <- h_generate_dlt_and_surv(1L, prob)
placebo_dlt_surv <- if (data@placebo && (placebo_size > 0L)) {
# If placebo, also dose one placebo patient.
h_generate_dlt_and_surv(1L, prob_pl)
} else {
list()
}
cohort_t0 <- trial_time
# Check if there are DLTs during safety window.
temp_data <- h_update_data_da(
active_dlt_surv,
placebo_dlt_surv,
trial_time + safety_window$patientGap[2]
)
temp_time <- (temp_data@u + temp_data@t0)[
temp_data@y == 1 & temp_data@x <= dose
]
# If no DLTs occur during safety window, enroll remaining patients.
if (sum(temp_time > trial_time) == 0) {
# Enroll the remaining patients.
active_dlt_surv <- h_generate_dlt_and_surv(
cohort_size - 1L,
prob,
start = active_dlt_surv
)
placebo_dlt_surv <- if (data@placebo && (placebo_size > 1L)) {
h_generate_dlt_and_surv(
placebo_size - 1L,
prob_pl,
start = placebo_dlt_surv
)
} else {
list()
}
# Adjust for DLTs happening before end of safety window.
real_window <- apply(
rbind(
c(active_dlt_surv$surv, placebo_dlt_surv$surv)[-cohort_size],
safety_window$patientGap[-1]
),
2,
min
)
cohort_t0 <- trial_time + c(0, cumsum(real_window))
}
rm(temp_data)
rm(temp_time)
} else {
# Directly dose all patients.
active_dlt_surv <- h_generate_dlt_and_surv(
cohort_size,
prob
)
placebo_dlt_surv <- if (data@placebo) {
h_generate_dlt_and_surv(
placebo_size,
prob_pl
)
} else {
list()
}
# Adjust for DLTs happening before end of safety window.
real_window <- apply(
rbind(
c(active_dlt_surv$surv, placebo_dlt_surv$surv)[-cohort_size],
safety_window$patientGap[-1]
),
2,
min
)
cohort_t0 <- trial_time + c(0, cumsum(real_window))
}
# Update observed data with new cohort.
old_dlts <- observed_dlts
observed_dlts <- c(
observed_dlts,
placebo_dlt_surv$dlts,
active_dlt_surv$dlts
)
observed_surv <- c(
observed_surv,
placebo_dlt_surv$surv,
active_dlt_surv$surv
)
observed_t0 <- c(
observed_t0,
rep(cohort_t0[1], length(placebo_dlt_surv$dlts)),
rep(cohort_t0, length.out = length(active_dlt_surv$dlts))
)
time_to_next <- next_open(
window = safety_window,
surv_times = c(placebo_dlt_surv$surv, active_dlt_surv$surv)
)
# Handle deescalation if DLTs occur in previous cohorts.
if (deescalate == TRUE) {
are_dlts_after_trial_start <- (observed_surv + observed_t0) >
trial_time
are_dlts_before_open_next_cohort <- (observed_surv +
observed_t0 -
trial_time) <=
time_to_next
are_dlts_happening <- observed_dlts == 1
is_new_dlt <- (are_dlts_after_trial_start &
are_dlts_before_open_next_cohort &
are_dlts_happening)
new_dlt_ids <- seq_along(observed_dlts)[is_new_dlt]
last_id_previous_cohort <- length(old_dlts)
is_new_dlt_in_previous_cohort <- new_dlt_ids <=
last_id_previous_cohort
new_dlt_ids <- new_dlt_ids[is_new_dlt_in_previous_cohort]
if (length(new_dlt_ids) > 0) {
for (this_new_dlt_id in new_dlt_ids) {
this_new_dlt_time <- (observed_surv + observed_t0)[
this_new_dlt_id
]
# Identify patients at higher doses who are impacted.
later_ids <- c(this_new_dlt_id:length(observed_dlts))
all_doses <- c(data@x, rep(dose, length(active_dlt_surv$dlts)))
this_new_dlt_dose <- all_doses[this_new_dlt_id]
is_dose_higher_than_this_new_dlt_dose <- all_doses[later_ids] >
this_new_dlt_dose
ids_to_deescalate <- later_ids[
is_dose_higher_than_this_new_dlt_dose
]
if (length(ids_to_deescalate) > 0) {
# DLT will be observed once follow-up time >= time to DLT.
this_new_dlt_time_after_followup <- this_new_dlt_time >=
(observed_t0[ids_to_deescalate] +
observed_surv[ids_to_deescalate])
observed_dlts[ids_to_deescalate] <- as.integer(
observed_dlts[ids_to_deescalate] *
this_new_dlt_time_after_followup
)
# Some patients in later cohorts may not be enrolled yet when new DLT occurs.
# Remove those patients from the cohort.
ids_not_enrolled <- ids_to_deescalate[
(observed_t0[ids_to_deescalate] >= this_new_dlt_time)
]
ids_enrolled <- setdiff(
ids_to_deescalate,
ids_not_enrolled
)
# Update DLT-free survival time for already enrolled patients.
if (length(ids_enrolled) > 0) {
surv_time <- pmin(
observed_surv[ids_enrolled],
this_new_dlt_time - observed_t0[ids_enrolled]
)
assert_true(all(surv_time >= 0))
observed_surv[ids_enrolled] <- surv_time
}
# Remove patients not yet enrolled.
if (length(ids_not_enrolled) > 0) {
observed_surv <- observed_surv[-ids_not_enrolled]
observed_t0 <- observed_t0[-ids_not_enrolled]
observed_dlts <- observed_dlts[-ids_not_enrolled]
}
}
}
time_to_next <- min(
time_to_next,
max((observed_surv + observed_t0)[
(length(old_dlts) + 1):length(observed_dlts)
]) -
trial_time
)
}
}
# Update trial time.
trial_time <- trial_time + time_to_next
# Update data object with observations available when next cohort opens.
if (data@placebo) {
# First patients are from placebo.
data <- update(
object = data,
y = head(observed_dlts, -length(active_dlt_surv$dlts)),
u = head(observed_surv, -length(active_dlt_surv$surv)),
t0 = head(observed_t0, -length(active_dlt_surv$surv)),
x = object@data@doseGrid[1],
trialtime = trial_time
)
}
data <- update(
object = data,
y = observed_dlts,
u = observed_surv,
t0 = observed_t0,
x = dose,
trialtime = trial_time
)
try(
if (
length(data@x) != length(data@u) ||
length(data@u) != length(data@y)
) {
stop("x,y,u dimension error")
}
)
# Calculate dose limit.
dose_limit <- maxDose(object@increments, data = data)
# Generate MCMC samples from model.
if (DA == TRUE) {
samples <- mcmc(
data = data,
model = object@model,
options = mcmcOptions
)
} else if (DA == FALSE) {
temp_model <- LogisticLogNormal(
mean = object@model@params@mean,
cov = object@model@params@cov,
ref_dose = object@model@refDose
)
truncated_data <- Data(
x = data@x,
y = data@y,
doseGrid = data@doseGrid,
cohort = data@cohort,
ID = data@ID
)
samples <- mcmc(
data = truncated_data,
model = temp_model,
options = mcmcOptions
)
}
# Calculate next best dose.
dose <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = samples,
model = object@model,
data = data
)$value
# Evaluate stopping rules.
should_stop <- stopTrial(
object@stopping,
dose = dose,
samples = samples,
model = object@model,
data = data
)
stop_results <- h_unpack_stopit(should_stop)
}
# Calculate final model fit.
fit_result <- fit(
object = samples,
model = object@model,
data = data
)
# Get MTD estimate from samples.
target_dose_samples <- dose(
mean(object@nextBest@target),
model = object@model,
samples = samples
)
# Calculate additional statistics.
additional_stats <- lapply(derive, function(f) f(target_dose_samples))
# Return simulation results.
list(
data = data,
dose = dose,
duration = trial_time,
fit = subset(fit_result, select = c(middle, lower, upper)),
stop = attr(
should_stop,
"message"
),
report_results = stop_results,
additional_stats = additional_stats
)
}
result_list <- get_result_list(
fun = run_sim,
nsim = nsim,
vars = c(
"sim_seeds",
"args",
"n_args",
"firstSeparate",
"truthTox",
"truthSurv",
"object",
"mcmcOptions",
"next_open",
"ready_to_open"
),
parallel = parallel,
n_cores = nCores
)
# Process simulation results.
data_list <- lapply(result_list, "[[", "data")
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose"))
trial_duration <- as.numeric(sapply(result_list, "[[", "duration"))
fit_list <- lapply(result_list, "[[", "fit")
stop_reasons <- lapply(result_list, "[[", "stop")
stop_results <- lapply(result_list, "[[", "report_results")
stop_report <- as.matrix(do.call(rbind, stop_results))
additional_stats <- lapply(result_list, "[[", "additional_stats")
# Return simulation results.
DASimulations(
data = data_list,
doses = recommended_doses,
fit = fit_list,
trial_duration = trial_duration,
stop_report = stop_report,
stop_reasons = stop_reasons,
additional_stats = additional_stats,
seed = rng_state
)
}
)
## DesignGrouped ----
#' Simulate Method for the [`DesignGrouped`] Class
#'
#' @description `r lifecycle::badge("experimental")`
#'
#' A simulate method for [`DesignGrouped`] designs.
#'
#' @param object (`DesignGrouped`)\cr the design we want to simulate trials from.
#' @param nsim (`number`)\cr how many trials should be simulated.
#' @param seed (`RNGstate`)\cr generated with [set_seed()].
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and
#' returns the true probability (vector) for toxicity for the mono arm.
#' Additional arguments can be supplied in `args`.
#' @param combo_truth (`function`)\cr same as `truth` but for the combo arm.
#' @param args (`data.frame`)\cr optional `data.frame` with arguments that work
#' for both the `truth` and `combo_truth` functions. The column names correspond to
#' the argument names, the rows to the values of the arguments. The rows are
#' appropriately recycled in the `nsim` simulations.
#' @param firstSeparate (`flag`)\cr whether to enroll the first patient separately
#' from the rest of the cohort and close the cohort in case a DLT occurs in this
#' first patient.
#' @param mcmcOptions (`McmcOptions`)\cr MCMC options for each evaluation in the trial.
#' @param parallel (`flag`)\cr whether the simulation runs are parallelized across the
#' cores of the computer.
#' @param nCores (`number`)\cr how many cores should be used for parallel computing.
#' @param ... not used.
#'
#' @return A list of `mono` and `combo` simulation results as [`Simulations`] objects.
#'
#' @aliases simulate-DesignGrouped
#' @export
#' @example examples/Design-method-simulate-DesignGrouped.R
#'
setMethod(
"simulate",
signature = signature(
object = "DesignGrouped",
nsim = "ANY",
seed = "ANY"
),
def = function(
object,
nsim = 1L,
seed = NULL,
truth,
combo_truth,
args = data.frame(),
firstSeparate = FALSE,
mcmcOptions = McmcOptions(),
parallel = FALSE,
nCores = min(parallelly::availableCores(), 5),
...
) {
nsim <- as.integer(nsim)
assert_function(truth)
assert_function(combo_truth)
assert_data_frame(args)
assert_count(nsim, positive = TRUE)
assert_flag(firstSeparate)
assert_flag(parallel)
assert_count(nCores, positive = TRUE)
n_args <- max(nrow(args), 1L)
rng_state <- set_seed(seed)
sim_seeds <- sample.int(n = 2147483647, size = nsim)
run_sim <- function(iter_sim) {
set.seed(sim_seeds[iter_sim])
current <- list(mono = list(), combo = list())
# Define true toxicity functions.
current$args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE]
current$mono$truth <- function(dose) do.call(truth, c(dose, current$args))
current$combo$truth <- function(dose) {
do.call(combo_truth, c(dose, current$args))
}
# Start the simulated data with the provided one.
current$mono$data <- object@mono@data
current$combo$data <- object@combo@data
# We are in the first cohort and continue for mono and combo.
current$first <- TRUE
current$mono$stop <- current$combo$stop <- FALSE
# What are the next doses to be used? Initialize with starting doses.
if (
object@same_dose_for_all ||
(!object@first_cohort_mono_only && object@same_dose_for_start)
) {
current$mono$dose <- current$combo$dose <- min(
object@mono@startingDose,
object@combo@startingDose
)
} else {
current$mono$dose <- object@mono@startingDose
current$combo$dose <- object@combo@startingDose
}
# Inside this loop we simulate the whole trial, until stopping.
while (!(current$mono$stop && current$combo$stop)) {
if (!current$mono$stop) {
cohort_size_mono <- size(
object@mono@cohort_size,
dose = current$mono$dose,
data = current$mono$data
)
this_prob_mono <- current$mono$truth(current$mono$dose)
current$mono$data <- current$mono$data %>%
h_determine_dlts(
dose = current$mono$dose,
prob = this_prob_mono,
cohort_size = cohort_size_mono,
first_separate = firstSeparate
)
}
if (
!current$combo$stop &&
(!current$first || !object@first_cohort_mono_only)
) {
cohort_size_combo <- size(
object@combo@cohort_size,
dose = current$combo$dose,
data = current$combo$data
)
this_prob_combo <- current$combo$truth(current$combo$dose)
current$combo$data <- current$combo$data %>%
h_determine_dlts(
dose = current$combo$dose,
prob = this_prob_combo,
cohort_size = cohort_size_combo,
first_separate = firstSeparate
)
}
current$grouped <- h_group_data(current$mono$data, current$combo$data)
current$samples <- mcmc(current$grouped, object@model, mcmcOptions)
if (!current$mono$stop) {
current$mono$limit <- maxDose(
object@mono@increments,
data = current$mono$data
)
current$mono$dose <- object@mono@nextBest %>%
nextBest(
current$mono$limit,
current$samples,
object@model,
current$grouped,
group = "mono"
)
current$mono$dose <- current$mono$dose$value
}
if (
!current$combo$stop &&
(!current$first || !object@first_cohort_mono_only)
) {
current$combo$limit <- if (is.na(current$mono$dose)) {
0
} else {
maxDose(object@combo@increments, current$combo$data) %>%
min(current$mono$dose, na.rm = TRUE)
}
current$combo$dose <- object@combo@nextBest %>%
nextBest(
current$combo$limit,
current$samples,
object@model,
current$grouped,
group = "combo"
)
current$combo$dose <- current$combo$dose$value
current$combo$stop <- object@combo@stopping %>%
stopTrial(
current$combo$dose,
current$samples,
object@model,
current$combo$data,
group = "combo"
)
current$combo$results <- h_unpack_stopit(current$combo$stop)
}
if (!current$mono$stop) {
current$mono$stop <- object@mono@stopping %>%
stopTrial(
current$mono$dose,
current$samples,
object@model,
current$mono$data,
group = "mono",
external = current$combo$stop
)
current$mono$results <- h_unpack_stopit(current$mono$stop)
}
if (
object@same_dose_for_all && !current$mono$stop && !current$combo$stop
) {
current$mono$dose <- current$combo$dose <- min(
current$mono$dose,
current$combo$dose
)
}
if (current$first) {
current$first <- FALSE
if (object@first_cohort_mono_only && object@same_dose_for_start) {
current$mono$dose <- current$combo$dose <- min(
current$mono$dose,
current$combo$dose
)
}
}
}
current$mono$fit <- fit(
current$samples,
object@model,
current$grouped,
group = "mono"
)
current$combo$fit <- fit(
current$samples,
object@model,
current$grouped,
group = "combo"
)
lapply(
X = current[c("mono", "combo")],
FUN = with,
list(
data = data,
dose = dose,
fit = subset(fit, select = -dose),
stop = attr(stop, "message"),
results = results
)
)
}
vars_needed <- c(
"simSeeds",
"args",
"nArgs",
"truth",
"combo_truth",
"firstSeparate",
"object",
"mcmcOptions"
)
result_list <- get_result_list(run_sim, nsim, vars_needed, parallel, nCores)
# Now we have a list with each element containing mono and combo. Reorder this a bit:
result_list <- list(
mono = lapply(result_list, "[[", "mono"),
combo = lapply(result_list, "[[", "combo")
)
# Put everything in a list with both mono and combo Simulations:
lapply(result_list, function(this_list) {
data_list <- lapply(this_list, "[[", "data")
recommended_doses <- as.numeric(sapply(this_list, "[[", "dose"))
fit_list <- lapply(this_list, "[[", "fit")
stop_reasons <- lapply(this_list, "[[", "stop")
report_results <- lapply(this_list, "[[", "results")
stop_report <- as.matrix(do.call(rbind, report_results))
additional_stats <- lapply(this_list, "[[", "additional_stats")
Simulations(
data = data_list,
doses = recommended_doses,
fit = fit_list,
stop_reasons = stop_reasons,
stop_report = stop_report,
additional_stats = additional_stats,
seed = rng_state
)
})
}
)
# examine ----
#' Obtain Hypothetical Trial Course Table for a Design
#'
#' This generic function takes a design and generates a `data.frame`
#' showing the beginning of several hypothetical trial courses under
#' the design. This means, from the generated `data.frame` one can read off:
#'
#' - how many cohorts are required in the optimal case (no DLTs observed) in
#' order to reach the highest dose of the specified dose grid (or until
#' the stopping rule is fulfilled)
#' - assuming no DLTs are observed until a certain dose level, what the next
#' recommended dose is for all possible number of DLTs observed
#' - the actual relative increments that will be used in these cases
#' - whether the trial would stop at a certain cohort
#'
#' Examining the "single trial" behavior of a dose escalation design is
#' the first important step in evaluating a design, and cannot be replaced by
#' studying solely the operating characteristics in "many trials". The cohort
#' sizes are also taken from the design, assuming no DLTs occur until the dose
#' listed.
#'
#' @param object ([`Design`] or [`RuleDesign`])\cr the design we want to examine
#' @param ... additional arguments (see methods)
#' @param maxNoIncrement maximum number of contiguous next doses at 0
#' DLTs that are the same as before, i.e. no increment (default to 100)
#'
#' @return The data frame
#'
#' @export
#' @keywords methods regression
setGeneric(
"examine",
def = function(object, ..., maxNoIncrement = 100L) {
assert_count(maxNoIncrement, positive = TRUE)
standardGeneric("examine")
},
valueClass = "data.frame"
)
## Design ----
#' @describeIn examine Examine a model-based CRM.
#'
#' @param mcmcOptions ([`McmcOptions`])\cr giving the MCMC options
#' for each evaluation in the trial. By default, the standard options are used.
#'
#' @example examples/design-method-examine-Design.R
setMethod(
"examine",
signature = signature(object = "Design"),
def = function(object, mcmcOptions = McmcOptions(), ..., maxNoIncrement) {
ret <- data.frame(
dose = numeric(),
DLTs = integer(),
nextDose = numeric(),
stop = logical(),
increment = integer()
)
base_data <- object@data
should_stop <- FALSE
# Counter how many contiguous doses at 0 DLTs with no increment.
no_increment_counter <- 0L
# Initialize with starting dose.
dose <- object@startingDose
while (!should_stop) {
# What is the cohort size at this dose?
cohort_size <- size(object@cohort_size, dose = dose, data = base_data)
if (base_data@placebo) {
cohort_size_pl <- size(
object@pl_cohort_size,
dose = dose,
data = base_data
)
}
# For all possible number of DLTs:
for (num_dlts in 0:cohort_size) {
# Update data with corresponding DLT vector.
if (base_data@placebo && (cohort_size_pl > 0L)) {
data_updated <- update(
object = base_data,
x = base_data@doseGrid[1],
y = rep(0, cohort_size_pl),
check = FALSE
)
data_updated <- update(
object = data_updated,
x = dose,
y = rep(
x = c(0, 1),
times = c(
cohort_size - num_dlts,
num_dlts
)
),
new_cohort = FALSE
)
} else {
data_updated <- update(
object = base_data,
x = dose,
y = rep(
x = c(0, 1),
times = c(
cohort_size - num_dlts,
num_dlts
)
)
)
}
# Calculate dose limit.
dose_limit <- maxDose(object@increments, data = data_updated)
# Generate samples from the model.
samples <- mcmc(
data = data_updated,
model = object@model,
options = mcmcOptions
)
# Calculate next best dose.
next_dose <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = samples,
model = object@model,
data = data_updated
)$value
# Compute relative increment in percent.
increment <- round((next_dose - dose) / dose * 100)
# Evaluate stopping rules.
stop_this_trial <- stopTrial(
object@stopping,
dose = next_dose,
samples = samples,
model = object@model,
data = data_updated
)
# Append information to the data frame.
ret <- rbind(
ret,
list(
dose = dose,
DLTs = num_dlts,
nextDose = next_dose,
stop = stop_this_trial,
increment = as.integer(increment)
)
)
}
# Update base data.
if (base_data@placebo && (cohort_size_pl > 0L)) {
base_data <- update(
object = base_data,
x = base_data@doseGrid[1],
y = rep(0, cohort_size_pl),
check = FALSE
)
base_data <- update(
object = base_data,
x = dose,
y = rep(0, cohort_size),
new_cohort = FALSE
)
} else {
base_data <- update(
object = base_data,
x = dose,
y = rep(0, cohort_size)
)
}
# Extract results if 0 DLTs.
results_no_dlts <- subset(
tail(ret, cohort_size + 1),
dose == dose & DLTs == 0
)
# Determine new dose.
new_dose <- as.numeric(results_no_dlts$nextDose)
# Calculate difference to previous dose.
dose_diff <- new_dose - dose
# Update the counter for no increments of the dose.
if (dose_diff == 0) {
no_increment_counter <- no_increment_counter + 1L
} else {
no_increment_counter <- 0L
}
# Check if stopping rule would be fulfilled.
stop_already <- results_no_dlts$stop
# Update dose.
dose <- new_dose
# Check if too many times no increment.
stop_no_increment <- (no_increment_counter >= maxNoIncrement)
if (stop_no_increment) {
warning(paste(
"Stopping because",
no_increment_counter,
"times no increment vs. previous dose"
))
}
# Check if we can stop:
# Either when we have reached the highest dose in the next cohort,
# or when the stopping rule is already fulfilled,
# or when too many times no increment.
should_stop <- (dose >= max(object@data@doseGrid)) ||
stop_already ||
stop_no_increment
}
ret
}
)
## RuleDesign ----
#' @describeIn examine Examine a rule-based design.
#' @example examples/design-method-examine-RuleDesign.R
setMethod(
"examine",
signature = signature(object = "RuleDesign"),
def = function(object, ..., maxNoIncrement) {
# Start with the empty table.
ret <- data.frame(
dose = numeric(),
DLTs = integer(),
nextDose = numeric(),
stop = logical(),
increment = integer()
)
# Start the base data with the provided one.
base_data <- object@data
# Are we finished and can stop?
should_stop <- FALSE
# Counter: contiguous doses at 0 DLTs with no increment.
no_increment_counter <- 0L
# Initialize with starting dose.
dose <- object@startingDose
# Continue filling up the table until stopping.
while (!should_stop) {
# Cohort size at this dose.
cohort_size <- size(object@cohort_size, dose = dose, data = base_data)
# For all possible number of DLTs.
for (num_dlts in 0:cohort_size) {
# Update data with corresponding DLT vector.
data_updated <- update(
object = base_data,
x = dose,
y = rep(
x = c(0, 1),
times = c(
cohort_size - num_dlts,
num_dlts
)
)
)
# Evaluate the rule.
outcome <- nextBest(object@nextBest, data = data_updated)
# Next dose and whether to stop here.
next_dose <- outcome$value
stop_this_trial <- outcome$stopHere
# Compute relative increment in percent.
increment <- round((next_dose - dose) / dose * 100)
# Append information to the data frame.
ret <- rbind(
ret,
list(
dose = dose,
DLTs = num_dlts,
nextDose = next_dose,
stop = stop_this_trial,
increment = as.integer(increment)
)
)
}
# Change base data.
base_data <- update(
object = base_data,
x = dose,
y = rep(0, cohort_size)
)
# Results if 0 DLTs.
results_no_dlts <- subset(
tail(ret, cohort_size + 1),
dose == dose & DLTs == 0
)
# New dose and difference to previous dose.
new_dose <- as.numeric(results_no_dlts$nextDose)
dose_diff <- new_dose - dose
# Update the counter for no increments of the dose.
if (dose_diff == 0) {
no_increment_counter <- no_increment_counter + 1L
} else {
no_increment_counter <- 0L
}
# Would stopping rule be fulfilled already?
stop_already <- results_no_dlts$stop
# Update dose.
dose <- new_dose
# Too many times no increment?
stop_no_increment <- (no_increment_counter >= maxNoIncrement)
if (stop_no_increment) {
warning(paste(
"Stopping because",
no_increment_counter,
"times no increment vs. previous dose"
))
}
# Check if we can stop:
# highest dose reached next cohort, stopping rule fulfilled, or too many no-increment.
should_stop <- (dose >= max(object@data@doseGrid)) ||
stop_already ||
stop_no_increment
}
ret
}
)
## DADesign ----
#' @describeIn examine Examine a model-based CRM.
#'
#' @param mcmcOptions ([`McmcOptions`])\cr
#' giving the MCMC options for each evaluation in the trial. By default,
#' the standard options are used
#'
#' @example examples/design-method-examine-DADesign.R
setMethod(
"examine",
signature = signature(object = "DADesign"),
def = function(object, mcmcOptions = McmcOptions(), ..., maxNoIncrement) {
# Check follow-up sufficiency (TRUE/FALSE);
ready_to_open <- function(day, window, this_surv) {
size <- length(this_surv)
start_time <- apply(
rbind(this_surv[-size], window$patientGap[-1]),
2,
min
)
individual_check <- day - cumsum(c(0, start_time))
individual_check[individual_check < 0] <- 0
follow_up <- apply(rbind(this_surv, individual_check), 2, min)
all(
(follow_up - apply(rbind(window$patientFollow, this_surv), 2, min)) >= 0
) &&
(max(follow_up) >= min(window$patientFollowMin, max(this_surv)))
}
# Determine when to open the next cohort; applies to all trials.
next_open <- function(window, this_surv) {
size <- length(this_surv)
window$patientGap <- window$patientGap[1:size]
start_time <- apply(
rbind(this_surv[-size], window$patientGap[-1]),
2,
min
)
max_t <- max(this_surv + cumsum(c(0, start_time)))
met <- sapply(1:max_t, function(i) ready_to_open(i, window, this_surv))
if (sum(met) > 0) min(c(1:max_t)[met]) else max_t
}
# Initialize result table.
ret <- data.frame(
DLTsearly_1 = integer(),
dose = numeric(),
DLTs = integer(),
nextDose = numeric(),
stop = logical(),
increment = integer()
)
# Base data and trial state.
base_data <- object@data
should_stop <- FALSE
dose <- object@startingDose
# Observed facts trackers (cumulative across cohorts).
observed_dlts <- base_data@y
observed_surv <- base_data@u
observed_t0 <- base_data@t0
# Global trial clock and previous cohort timing.
trial_time <- 0
prev_time <- 0
# DLT window length.
t_max <- base_data@Tmax
# Number of patients with unfinished DLT window (initially none).
prev_size <- 0
# Iterate cohorts until stopping.
while (!should_stop) {
cohort_size <- size(object@cohort_size, dose = dose, data = base_data)
safety_window <- windowLength(object@safetyWindow, cohort_size)
# When cohort patients start relative to trial clock.
cohort_t0 <- trial_time + cumsum(safety_window$patientGap)
# Append placeholders for the incoming cohort (no DLTs yet, censored at t_max).
observed_dlts <- c(observed_dlts, rep(0, cohort_size))
observed_surv <- c(observed_surv, rep(t_max, cohort_size))
observed_t0 <- c(observed_t0, cohort_t0)
# Advance time until next cohort may open (all follow-up constraints satisfied).
trial_time <- trial_time +
next_open(window = safety_window, this_surv = rep(t_max, cohort_size))
# Count patients still within DLT window (for nFollow loop).
n_follow <- cohort_size + prev_size
# Identify censored patients indices.
npt <- length(base_data@x)
censored_indices <- c(
which((trial_time - base_data@t0) < base_data@Tmax & base_data@y == 0),
(npt + 1):(npt + cohort_size)
)
# For all possible number of DLTs (0..n_follow):
for (num_dlts in 0:n_follow) {
if (num_dlts == 0) {
# Update base_data for zero DLTs scenario.
base_data <- update(
object = base_data,
y = observed_dlts,
u = observed_surv,
t0 = observed_t0,
x = dose,
trialtime = trial_time
)
dose_limit <- maxDose(object@increments, data = base_data)
samples <- mcmc(
data = base_data,
model = object@model,
options = mcmcOptions
)
next_dose <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = samples,
model = object@model,
data = base_data
)$value
increment <- round((next_dose - dose) / dose * 100)
stop_this_trial <- stopTrial(
object@stopping,
dose = next_dose,
samples = samples,
model = object@model,
data = base_data
)
ret <- rbind(
ret,
list(
DLTsearly_1 = 0,
dose = dose,
DLTs = num_dlts,
nextDose = next_dose,
stop = stop_this_trial,
increment = as.integer(increment)
)
)
} else {
# Consider two extremes: DLTs at longest vs shortest follow-ups.
for (dlt_early in 1:num_dlts) {
curr_dlts <- observed_dlts
curr_surv <- observed_surv
if (dlt_early == 1) {
# Longest follow-up patients have DLTs.
curr_dlts[censored_indices][1:num_dlts] <- 1
curr_surv[censored_indices][1:num_dlts] <- apply(
rbind(
rep(t_max, num_dlts),
trial_time - observed_t0[censored_indices][1:num_dlts]
),
2,
min
)
data_current <- update(
object = base_data,
y = curr_dlts,
u = curr_surv,
t0 = observed_t0,
x = dose,
trialtime = trial_time
)
} else {
# Shortest follow-up patients have DLTs.
curr_dlts[rev(censored_indices)][1:num_dlts] <- 1
curr_surv[rev(censored_indices)][1:num_dlts] <- apply(
rbind(
rep(1, num_dlts),
prev_time + 1 - observed_t0[rev(censored_indices)][1:num_dlts]
),
2,
max
)
temp_time <- if (num_dlts >= cohort_size) {
1 + max(cohort_t0)
} else {
trial_time
}
data_current <- update(
object = base_data,
y = curr_dlts,
u = curr_surv,
t0 = observed_t0,
x = dose,
trialtime = temp_time
)
}
dose_limit <- maxDose(object@increments, data = data_current)
samples <- mcmc(
data = data_current,
model = object@model,
options = mcmcOptions
)
next_dose <- nextBest(
object@nextBest,
doselimit = dose_limit,
samples = samples,
model = object@model,
data = data_current
)$value
increment <- round((next_dose - dose) / dose * 100)
stop_this_trial <- stopTrial(
object@stopping,
dose = next_dose,
samples = samples,
model = object@model,
data = data_current
)
ret <- rbind(
ret,
list(
DLTsearly_1 = dlt_early,
dose = dose,
DLTs = num_dlts,
nextDose = next_dose,
stop = stop_this_trial,
increment = as.integer(increment)
)
)
}
}
}
# Update previous time and compute next state.
prev_time <- trial_time
# Filter results at this dose with 0 DLTs and derive new dose.
results_no_dlts <- subset(ret, dose == dose & DLTs == 0)
new_dose <- as.numeric(results_no_dlts$nextDose)
dose_diff <- new_dose - dose
stop_already <- any(results_no_dlts$stop)
# Update dose to the maximum recommended among ties.
dose <- max(new_dose)
# Patients still within DLT window.
prev_size <- sum(base_data@u[base_data@y == 0] < base_data@Tmax)
# No-increment counter and stopping due to no increment.
no_increment_counter <- if (all(dose_diff == 0)) {
no_increment_counter + 1L
} else {
0L
}
stop_no_increment <- (no_increment_counter >= maxNoIncrement)
if (stop_no_increment) {
warning(paste(
"Stopping because",
no_increment_counter,
"times no increment vs. previous dose"
))
}
# Overall stop condition.
should_stop <- (dose >= max(object@data@doseGrid)) ||
stop_already ||
stop_no_increment
}
ret
}
)
# tidy ----
## tidy-DualDesign ----
#' @rdname tidy
#' @aliases tidy-DualDesign
#' @example examples/Design-method-tidyDualDesign.R
#'
#' @export
setMethod(
f = "tidy",
signature = signature(x = "DualDesign"),
definition = function(x, ...) {
# Some Design objects have complex attributes whose structure is not supported.
rv <- h_tidy_all_slots(x, attributes = FALSE) %>% h_tidy_class(x)
if (length(rv) == 1) {
rv[[names(rv)[1]]] %>% h_tidy_class(x)
} else {
rv
}
}
)
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.