| 1 |
#' Convert Object's Attributes to a Tibble |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' A helper function that interrogates an object to see if it has attributes |
|
| 6 |
#' and, if so, converts them to a (list of) tibbles. Columns are named after |
|
| 7 |
#' attributes. If attributes have different lengths but recycling is possible, |
|
| 8 |
#' a single `tibble` is returned. Otherwise, a `list` of tibbles is returned. |
|
| 9 |
#' |
|
| 10 |
#' @param x (`CrmPackObject`)\cr object whose attributes will be interrogated. |
|
| 11 |
#' @param .ignore (`character`)\cr names of attrributes to be ignored. |
|
| 12 |
#' |
|
| 13 |
#' @return A [`tibble`] or `list` of [`tibble`]s containg the values of the |
|
| 14 |
#' object's attributes. |
|
| 15 |
#' |
|
| 16 |
#' @keywords internal |
|
| 17 |
#' @noRd |
|
| 18 |
h_handle_attributes <- function( |
|
| 19 |
x, |
|
| 20 |
.ignore = c("names", "class", "description", "row.names")
|
|
| 21 |
) {
|
|
| 22 | 3x |
a <- attributes(x) |
| 23 | 3x |
valid_names <- setdiff(names(a), .ignore) |
| 24 | 3x |
lapply( |
| 25 | 3x |
valid_names, |
| 26 | 3x |
function(n) {
|
| 27 | 18x |
z <- attr(x, n) |
| 28 | 18x |
rv <- NULL |
| 29 |
# Some Design classes have attributes that are functions or CrmPackClass objects |
|
| 30 | 18x |
if (!is.function(z)) {
|
| 31 | 18x |
if (length(z) == 1) {
|
| 32 | 18x |
if (is(z, "CrmPackClass")) {
|
| 33 | ! |
z <- z %>% tidy() |
| 34 |
} |
|
| 35 | 18x |
rv <- tibble::tibble(X = z) |
| 36 |
} else {
|
|
| 37 | ! |
if (length(z) == 0) {
|
| 38 | ! |
rv <- tibble::tibble(X = NA) |
| 39 |
} else {
|
|
| 40 | ! |
if (is(z, "CrmPackClass")) {
|
| 41 | ! |
rv <- z %>% tidy() |
| 42 |
} else {
|
|
| 43 | ! |
rv <- tibble::tibble(X = list(z)) |
| 44 |
} |
|
| 45 |
} |
|
| 46 |
} |
|
| 47 | 18x |
names(rv) <- n |
| 48 |
} |
|
| 49 | 18x |
rv |
| 50 |
} |
|
| 51 |
) %>% |
|
| 52 | 3x |
dplyr::bind_cols() |
| 53 |
} |
|
| 54 | ||
| 55 |
#' Tidy a Single Slot of a CrmPackObject |
|
| 56 |
#' |
|
| 57 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 58 |
#' |
|
| 59 |
#' A helper function that converts a single slot of a `CrmPackObject` to a tibble. |
|
| 60 |
#' If the slots value is a `list`, each element of the list is tidied individually. |
|
| 61 |
#' |
|
| 62 |
#' @param obj (`CrmPackObject`)\cr object to be converted. |
|
| 63 |
#' @param slot_name (`character`)\cr name of the slot to be tidied. |
|
| 64 |
#' @param col (`character`)\cr The name of the corresponding column in the tidied |
|
| 65 |
#' tibble. Defaults to `slot_name`. |
|
| 66 |
#' @param attributes (`flag`)\cr shoud the object's attributes, if any, be added |
|
| 67 |
#' to the output tibble |
|
| 68 |
#' |
|
| 69 |
#' @return A [`tibble`] |
|
| 70 |
#' |
|
| 71 |
#' @keywords internal |
|
| 72 |
#' @importFrom rlang := |
|
| 73 |
#' @noRd |
|
| 74 |
h_tidy_slot <- function(obj, slot_name, col = NULL, attributes = FALSE) {
|
|
| 75 | 2599x |
if (is.list(slot(obj, slot_name))) {
|
| 76 | 67x |
return( |
| 77 | 67x |
lapply( |
| 78 | 67x |
slot(obj, slot_name), |
| 79 | 67x |
function(x) {
|
| 80 | 128x |
if (is.data.frame(x)) {
|
| 81 | 11x |
x |
| 82 | 67x |
} else if ( |
| 83 | 117x |
is.list(x) && |
| 84 | 117x |
stringr::str_detect(class(x)[1], stringr::fixed("tbl_"))
|
| 85 |
) {
|
|
| 86 |
# Already tidied to a list. |
|
| 87 | ! |
x |
| 88 | 117x |
} else if (is.numeric(x) | is.character(x)) {
|
| 89 |
# tidy.numeric & tidy.character are deprecated |
|
| 90 | 6x |
tibble::tibble(!!{{ slot_name }} := x)
|
| 91 |
} else {
|
|
| 92 | 111x |
x %>% tidy() |
| 93 |
} |
|
| 94 |
} |
|
| 95 |
) |
|
| 96 |
) |
|
| 97 |
} |
|
| 98 | 2532x |
if (is(slot(obj, slot_name), "CrmPackClass")) {
|
| 99 | 356x |
rv <- slot(obj, slot_name) %>% |
| 100 | 356x |
tidy() |
| 101 |
} else {
|
|
| 102 | 2176x |
if (is.null(col)) {
|
| 103 | 2176x |
col <- slot_name |
| 104 |
} |
|
| 105 | 2176x |
rv <- tibble::tibble({{ col }} := slot(obj, slot_name))
|
| 106 |
} |
|
| 107 | 2532x |
if (attributes) {
|
| 108 | ! |
a <- h_handle_attributes(slot(obj, slot_name)) |
| 109 | ! |
if (nrow(a) > 0) {
|
| 110 | ! |
rv <- rv %>% dplyr::bind_cols(a) |
| 111 |
} |
|
| 112 |
} |
|
| 113 | 2532x |
rv |
| 114 |
} |
|
| 115 | ||
| 116 |
#' Tidy All Slots of a CrmPackObject |
|
| 117 |
#' |
|
| 118 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 119 |
#' |
|
| 120 |
#' A helper function that converts all the slots of a `CrmPackObject` to a |
|
| 121 |
#' (list of) tibble(s). |
|
| 122 |
#' |
|
| 123 |
#' @param obj (`CrmPackObject`)\cr object to be tidied. |
|
| 124 |
#' @param ... passed to h_tidy_slot |
|
| 125 |
#' |
|
| 126 |
#' @return A (list of) [`tibble`](s) |
|
| 127 |
#' |
|
| 128 |
#' @keywords internal |
|
| 129 |
#' @noRd |
|
| 130 |
h_tidy_all_slots <- function(obj, ...) {
|
|
| 131 | 793x |
slot_names <- slotNames(obj) |
| 132 | 793x |
rv <- list() |
| 133 | 793x |
for (nm in slot_names) {
|
| 134 | 2799x |
if (!is.function(slot(obj, nm))) {
|
| 135 | 2539x |
rv[[nm]] <- h_tidy_slot(obj, nm, ...) |
| 136 |
} |
|
| 137 |
} |
|
| 138 |
# Column bind of all list elements have the same number of rows |
|
| 139 | 793x |
if (length(rv) > 1 && length(unique(sapply(rv, nrow))) == 1) {
|
| 140 | 401x |
rv <- rv %>% dplyr::bind_cols() # nolint |
| 141 |
} |
|
| 142 | 793x |
rv |
| 143 |
} |
|
| 144 | ||
| 145 |
#' Amend the Class of a Tibble to Indicate that it Contains a Tidied `CrmPackObject` |
|
| 146 |
#' |
|
| 147 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 148 |
#' |
|
| 149 |
#' A helper function that prepends `tbl_<cls>`, where `<cls>` is the first |
|
| 150 |
#' element of the class attribute of the original `CrmPackObject` to the class |
|
| 151 |
#' attribute of a tibble |
|
| 152 |
#' |
|
| 153 |
#' @param d (`tibble`)\cr the tibble containing the tidied version of `obj`. |
|
| 154 |
#' @param obj (`CrmPackObject`)\cr object to be converted. |
|
| 155 |
#' |
|
| 156 |
#' @return `d`, with an amended class attribute |
|
| 157 |
#' |
|
| 158 |
#' @keywords internal |
|
| 159 |
#' @noRd |
|
| 160 |
h_tidy_class <- function(d, obj) {
|
|
| 161 | 1682x |
cls <- class(obj) |
| 162 | 1682x |
class(d) <- c(paste0("tbl_", cls[1]), class(d))
|
| 163 | 1682x |
d |
| 164 |
} |
|
| 165 | ||
| 166 |
#' Convert a `CrmPackObject`'s "Interval list" to a Min-Max |
|
| 167 |
#' |
|
| 168 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 169 |
#' |
|
| 170 |
#' `CrmPackClass` objects that define a set of intervals (such as `CohortSizeRange`) |
|
| 171 |
#' typically contain a left-open vector that dfines the intervals. For example, |
|
| 172 |
#' `my_size <- CohortSizeRange(intervals = c(0, 20), cohort_size = c(1, 3))` defines |
|
| 173 |
#' two dose ranges: [0, 20) and [20, Inf). This is convenient for coding, but |
|
| 174 |
#' awkward for reporting. This helper function converts this single-column |
|
| 175 |
#' representation to a two-column representation that explicitly defines the |
|
| 176 |
#' lower and upper ends of each interval. Using the example above, the converted |
|
| 177 |
#' tibble would look like this: |
|
| 178 |
#' |
|
| 179 |
#' | cohort_size | min | max | |
|
| 180 |
#' | ----------: | ---: | ---: | |
|
| 181 |
#' | 1 | -Inf | 20 | |
|
| 182 |
#' | 3 | 20 | Inf | |
|
| 183 |
#' |
|
| 184 |
#' @param x (`tibble`)\cr the tibble to be converted. |
|
| 185 |
#' @param col (`tidy-eval`)\cr column containing the intervals. |
|
| 186 |
#' @param min_col (`character`)\cr name of the column containing the lower end |
|
| 187 |
#' of the interval in the returned value. |
|
| 188 |
#' @param max_col (`character`)\cr name of the column containing the upper end |
|
| 189 |
#' of the interval in the returned value. |
|
| 190 |
#' @param range_min (`numeric`)\cr value of the lower end of the first interval. |
|
| 191 |
#' @param range_max (`numeric`)\cr value of the upper end of the last interval. |
|
| 192 |
#' |
|
| 193 |
#' @return A `tibble` in min-max format, with one row more than the input tibble. |
|
| 194 |
#' |
|
| 195 |
#' @importFrom rlang := |
|
| 196 |
#' @keywords internal |
|
| 197 |
#' @noRd |
|
| 198 |
h_range_to_minmax <- function( |
|
| 199 |
x, |
|
| 200 |
col, |
|
| 201 |
min_col = "min", |
|
| 202 |
max_col = "max", |
|
| 203 |
range_min = -Inf, |
|
| 204 |
range_max = Inf |
|
| 205 |
) {
|
|
| 206 | 276x |
vals <- x %>% dplyr::pull({{ col }})
|
| 207 | 276x |
tibble( |
| 208 | 276x |
{{ min_col }} := c(range_min, vals),
|
| 209 | 276x |
{{ max_col }} := c(vals, range_max)
|
| 210 |
) |
|
| 211 |
} |
| 1 |
#' @include helpers.R |
|
| 2 |
#' @include Data-class.R |
|
| 3 |
#' @include Simulations-validity.R |
|
| 4 |
#' @include CrmPackClass-class.R |
|
| 5 |
NULL |
|
| 6 | ||
| 7 |
# GeneralSimulations ---- |
|
| 8 | ||
| 9 |
## class ---- |
|
| 10 | ||
| 11 |
#' `GeneralSimulations` |
|
| 12 |
#' |
|
| 13 |
#' @description `r lifecycle::badge("stable")`
|
|
| 14 |
#' |
|
| 15 |
#' This class captures trial simulations. |
|
| 16 |
#' Here also the random generator state before starting the simulation is |
|
| 17 |
#' saved, in order to be able to reproduce the outcome. For this just use |
|
| 18 |
#' [`set.seed`] with the `seed` as argument before running |
|
| 19 |
#' [`simulate,Design-method`]. |
|
| 20 |
#' |
|
| 21 |
#' @slot data (`list`)\cr produced [`Data`] objects. |
|
| 22 |
#' @slot doses (`numeric`)\cr final dose recommendations. |
|
| 23 |
#' @slot seed (`integer`)\cr random generator state before starting the simulation. |
|
| 24 |
#' |
|
| 25 |
#' @aliases GeneralSimulations |
|
| 26 |
#' @export |
|
| 27 |
.GeneralSimulations <- |
|
| 28 |
setClass( |
|
| 29 |
Class = "GeneralSimulations", |
|
| 30 |
slots = c( |
|
| 31 |
data = "list", |
|
| 32 |
doses = "numeric", |
|
| 33 |
seed = "integer" |
|
| 34 |
), |
|
| 35 |
prototype = prototype( |
|
| 36 |
data = list( |
|
| 37 |
Data( |
|
| 38 |
x = 1:2, |
|
| 39 |
y = 0:1, |
|
| 40 |
doseGrid = 1:2, |
|
| 41 |
ID = 1L:2L, |
|
| 42 |
cohort = 1L:2L |
|
| 43 |
), |
|
| 44 |
Data( |
|
| 45 |
x = 3:4, |
|
| 46 |
y = 0:1, |
|
| 47 |
doseGrid = 3:4, |
|
| 48 |
ID = 1L:2L, |
|
| 49 |
cohort = 1L:2L |
|
| 50 |
) |
|
| 51 |
), |
|
| 52 |
doses = c(1, 2), |
|
| 53 |
seed = 1L |
|
| 54 |
), |
|
| 55 |
contains = "CrmPackClass", |
|
| 56 |
validity = v_general_simulations |
|
| 57 |
) |
|
| 58 | ||
| 59 |
## constructor ---- |
|
| 60 | ||
| 61 |
#' @rdname GeneralSimulations-class |
|
| 62 |
#' |
|
| 63 |
#' @param data (`list`)\cr see slot definition. |
|
| 64 |
#' @param doses (`numeric`)\cr see slot definition. |
|
| 65 |
#' @param seed (`integer`)\cr see slot definition. |
|
| 66 |
#' |
|
| 67 |
#' @example examples/Simulations-class-GeneralSimulations.R |
|
| 68 |
#' @export |
|
| 69 |
GeneralSimulations <- function(data, doses, seed) {
|
|
| 70 | 64x |
assert_integerish(seed) |
| 71 | 64x |
.GeneralSimulations( |
| 72 | 64x |
data = data, |
| 73 | 64x |
doses = doses, |
| 74 | 64x |
seed = as.integer(seed) |
| 75 |
) |
|
| 76 |
} |
|
| 77 | ||
| 78 | ||
| 79 |
## default constructor |
|
| 80 | ||
| 81 |
#' @rdname GeneralSimulations-class |
|
| 82 |
#' @note Typically, end users will not use the `.DefaultGeneralSimulations()` function. |
|
| 83 |
#' @export |
|
| 84 |
.DefaultGeneralSimulations <- function() {
|
|
| 85 | 6x |
GeneralSimulations( |
| 86 | 6x |
data = list( |
| 87 | 6x |
Data(x = 1:3, y = c(0, 1, 0), doseGrid = 1:3, ID = 1L:3L, cohort = 1L:3L), |
| 88 | 6x |
Data(x = 4:6, y = c(0, 1, 0), doseGrid = 4:6, ID = 1L:3L, cohort = 1L:3L) |
| 89 |
), |
|
| 90 | 6x |
doses = c(1, 2), |
| 91 | 6x |
seed = 123 |
| 92 |
) |
|
| 93 |
} |
|
| 94 | ||
| 95 | ||
| 96 |
# Simulations ---- |
|
| 97 | ||
| 98 |
## class ---- |
|
| 99 | ||
| 100 |
#' `Simulations` |
|
| 101 |
#' |
|
| 102 |
#' @description `r lifecycle::badge("stable")`
|
|
| 103 |
#' |
|
| 104 |
#' This class captures the trial simulations from model based designs. |
|
| 105 |
#' Additional slots `fit`, `stop_reasons`, `stop_report`,`additional_stats` compared to |
|
| 106 |
#' the general class [`GeneralSimulations`]. |
|
| 107 |
#' |
|
| 108 |
#' @slot fit (`list`)\cr final fits |
|
| 109 |
#' @slot stop_reasons (`list`)\cr stopping reasons for each simulation run |
|
| 110 |
#' @slot stop_report matrix of stopping rule outcomes |
|
| 111 |
#' @slot additional_stats list of additional statistical summary |
|
| 112 |
#' @aliases Simulations |
|
| 113 |
#' @export |
|
| 114 |
.Simulations <- |
|
| 115 |
setClass( |
|
| 116 |
Class = "Simulations", |
|
| 117 |
slots = c( |
|
| 118 |
fit = "list", |
|
| 119 |
stop_report = "matrix", |
|
| 120 |
stop_reasons = "list", |
|
| 121 |
additional_stats = "list" |
|
| 122 |
), |
|
| 123 |
prototype = prototype( |
|
| 124 |
fit = list( |
|
| 125 |
c(0.1, 0.2), |
|
| 126 |
c(0.1, 0.2) |
|
| 127 |
), |
|
| 128 |
stop_report = matrix(TRUE, nrow = 2), |
|
| 129 |
stop_reasons = list("A", "A"),
|
|
| 130 |
additional_stats = list(a = 1, b = 1) |
|
| 131 |
), |
|
| 132 |
contains = "GeneralSimulations", |
|
| 133 |
validity = v_simulations |
|
| 134 |
) |
|
| 135 | ||
| 136 |
## constructor ---- |
|
| 137 | ||
| 138 |
#' @rdname Simulations-class |
|
| 139 |
#' |
|
| 140 |
#' @param fit (`list`)\cr see slot definition. |
|
| 141 |
#' @param stop_reasons (`list`)\cr see slot definition. |
|
| 142 |
#' @param stop_report see [`Simulations`] |
|
| 143 |
#' @param additional_stats (`list`)\cr see slot definition. |
|
| 144 |
#' @param \dots additional parameters from [`GeneralSimulations`] |
|
| 145 |
#' |
|
| 146 |
#' @example examples/Simulations-class-Simulations.R |
|
| 147 |
#' @export |
|
| 148 |
Simulations <- function(fit, stop_reasons, stop_report, additional_stats, ...) {
|
|
| 149 | 43x |
start <- GeneralSimulations(...) |
| 150 | 43x |
.Simulations( |
| 151 | 43x |
start, |
| 152 | 43x |
fit = fit, |
| 153 | 43x |
stop_report = stop_report, |
| 154 | 43x |
stop_reasons = stop_reasons, |
| 155 | 43x |
additional_stats = additional_stats |
| 156 |
) |
|
| 157 |
} |
|
| 158 | ||
| 159 |
## default constructor ---- |
|
| 160 | ||
| 161 |
#' @rdname Simulations-class |
|
| 162 |
#' @note Typically, end users will not use the `.DefaultSimulations()` function. |
|
| 163 |
#' @export |
|
| 164 |
.DefaultSimulations <- function() {
|
|
| 165 | ! |
design <- .DefaultDesign() |
| 166 | ! |
myTruth <- probFunction(design@model, alpha0 = 7, alpha1 = 8) |
| 167 | ||
| 168 | ! |
simulate( |
| 169 | ! |
design, |
| 170 | ! |
args = NULL, |
| 171 | ! |
truth = myTruth, |
| 172 | ! |
nsim = 1, |
| 173 | ! |
seed = 819, |
| 174 | ! |
mcmcOptions = .DefaultMcmcOptions(), |
| 175 | ! |
parallel = FALSE |
| 176 |
) |
|
| 177 |
} |
|
| 178 | ||
| 179 |
# DualSimulations ---- |
|
| 180 | ||
| 181 |
## class ---- |
|
| 182 | ||
| 183 |
#' `DualSimulations` |
|
| 184 |
#' |
|
| 185 |
#' @description `r lifecycle::badge("stable")`
|
|
| 186 |
#' |
|
| 187 |
#' This class captures the trial simulations from dual-endpoint model based |
|
| 188 |
#' designs. In comparison to the parent class [`Simulations`], |
|
| 189 |
#' it contains additional slots to capture the dose-biomarker `fits`, and the |
|
| 190 |
#' `sigma2W` and `rho` estimates. |
|
| 191 |
#' |
|
| 192 |
#' @slot rho_est (`numeric`)\cr vector of final posterior median rho estimates |
|
| 193 |
#' @slot sigma2w_est (`numeric`)\cr vector of final posterior median sigma2W estimates |
|
| 194 |
#' @slot fit_biomarker (`list`)\cr with the final dose-biomarker curve fits |
|
| 195 |
#' @aliases DualSimulations |
|
| 196 |
#' @export |
|
| 197 |
.DualSimulations <- |
|
| 198 |
setClass( |
|
| 199 |
Class = "DualSimulations", |
|
| 200 |
slots = c( |
|
| 201 |
rho_est = "numeric", |
|
| 202 |
sigma2w_est = "numeric", |
|
| 203 |
fit_biomarker = "list" |
|
| 204 |
), |
|
| 205 |
prototype = prototype( |
|
| 206 |
rho_est = c(0.2, 0.3), |
|
| 207 |
sigma2w_est = c(0.2, 0.3), |
|
| 208 |
fit_biomarker = list( |
|
| 209 |
c(0.1, 0.2), |
|
| 210 |
c(0.1, 0.2) |
|
| 211 |
) |
|
| 212 |
), |
|
| 213 |
contains = "Simulations", |
|
| 214 |
validity = v_dual_simulations |
|
| 215 |
) |
|
| 216 | ||
| 217 | ||
| 218 |
## constructor ---- |
|
| 219 | ||
| 220 |
#' @rdname DualSimulations-class |
|
| 221 |
#' |
|
| 222 |
#' @param rho_est (`numeric`)\cr see [`DualSimulations`] |
|
| 223 |
#' @param sigma2w_est (`numeric`)\cr [`DualSimulations`] |
|
| 224 |
#' @param fit_biomarker (`list`)\cr see [`DualSimulations`] |
|
| 225 |
#' @param \dots additional parameters from [`Simulations`] |
|
| 226 |
#' |
|
| 227 |
#' @example examples/Simulations-class-DualSimulations.R |
|
| 228 |
#' @export |
|
| 229 |
DualSimulations <- function(rho_est, sigma2w_est, fit_biomarker, ...) {
|
|
| 230 | 7x |
start <- Simulations(...) |
| 231 | 7x |
.DualSimulations( |
| 232 | 7x |
start, |
| 233 | 7x |
rho_est = rho_est, |
| 234 | 7x |
sigma2w_est = sigma2w_est, |
| 235 | 7x |
fit_biomarker = fit_biomarker |
| 236 |
) |
|
| 237 |
} |
|
| 238 | ||
| 239 |
## default constructor ---- |
|
| 240 | ||
| 241 |
#' @rdname DualSimulations-class |
|
| 242 |
#' @note Typically, end users will not use the `.DefaultDualSimulations()` function. |
|
| 243 |
#' @export |
|
| 244 |
.DefaultDualSimulations <- function() {
|
|
| 245 | ! |
DualSimulations( |
| 246 | ! |
rho_est = c(0.25, 0.35), |
| 247 | ! |
sigma2w_est = c(0.15, 0.25), |
| 248 | ! |
fit_biomarker = list(c(0.3, 0.4), c(0.4, 0.5)), |
| 249 | ! |
fit = list( |
| 250 | ! |
c(0.1, 0.2), |
| 251 | ! |
c(0.3, 0.4) |
| 252 |
), |
|
| 253 | ! |
stop_report = matrix(c(TRUE, FALSE), nrow = 2), |
| 254 | ! |
stop_reasons = list("A", "B"),
|
| 255 | ! |
additional_stats = list(a = 1, b = 1), |
| 256 | ! |
data = list( |
| 257 | ! |
Data( |
| 258 | ! |
x = 1:2, |
| 259 | ! |
y = 0:1, |
| 260 | ! |
doseGrid = 1:2, |
| 261 | ! |
ID = 1L:2L, |
| 262 | ! |
cohort = 1L:2L |
| 263 |
), |
|
| 264 | ! |
Data( |
| 265 | ! |
x = 3:4, |
| 266 | ! |
y = 0:1, |
| 267 | ! |
doseGrid = 3:4, |
| 268 | ! |
ID = 1L:2L, |
| 269 | ! |
cohort = 1L:2L |
| 270 |
) |
|
| 271 |
), |
|
| 272 | ! |
doses = c(1, 2), |
| 273 | ! |
seed = 123L |
| 274 |
) |
|
| 275 |
} |
|
| 276 | ||
| 277 |
#' `GeneralSimulationsSummary` |
|
| 278 |
#' |
|
| 279 |
#' @description `r lifecycle::badge("stable")`
|
|
| 280 |
#' |
|
| 281 |
#' This class captures the summary of general simulations output. Note that objects |
|
| 282 |
#' should not be created by users, therefore no initialization |
|
| 283 |
#' function is provided for this class. |
|
| 284 |
#' |
|
| 285 |
#' @slot target (`numeric`)\cr target toxicity interval |
|
| 286 |
#' @slot target_dose_interval (`numeric`)\cr corresponding target dose interval |
|
| 287 |
#' @slot nsim (`integer`)\cr number of simulations |
|
| 288 |
#' @slot prop_dlts (`ANY`)\cr A numeric array (multi-dimensional) or list representing proportions of DLTs in the trials |
|
| 289 |
#' @slot mean_tox_risk (`numeric`)\cr mean toxicity risks for the patients |
|
| 290 |
#' @slot dose_selected (`numeric`)\cr doses selected as MTD |
|
| 291 |
#' @slot tox_at_doses_selected (`numeric`)\cr true toxicity at doses selected |
|
| 292 |
#' @slot prop_at_target (`numeric`)\cr Proportion of trials selecting target MTD |
|
| 293 |
#' @slot dose_most_selected (`numeric`)\cr dose most often selected as MTD |
|
| 294 |
#' @slot obs_tox_rate_at_dose_most_selected (`numeric`)\cr observed toxicity rate at dose most often selected |
|
| 295 |
#' @slot n_obs (`ANY`)\cr A numeric array (multi-dimensional) or list representing number of patients overall. |
|
| 296 |
#' @slot n_above_target (`integer`)\cr number of patients treated above target tox interval |
|
| 297 |
#' @slot dose_grid (`numeric`)\cr the dose grid that has been used |
|
| 298 |
#' @slot placebo (`logical`)\cr set to TRUE (default is FALSE) for a design with placebo |
|
| 299 |
#' @slot any_backfilled (`flag`)\cr indicates if any backfill cohorts were used |
|
| 300 |
#' @slot n_backfill (`ANY`)\cr number of patients in backfill cohorts (only if `any_backfilled=TRUE`) |
|
| 301 |
#' @slot backfill_doses (`ANY`)\cr list with doses used in backfill cohorts (only if `any_backfilled=TRUE`) |
|
| 302 |
#' @aliases GeneralSimulationsSummary |
|
| 303 |
#' @export |
|
| 304 |
.GeneralSimulationsSummary <- |
|
| 305 |
setClass( |
|
| 306 |
Class = "GeneralSimulationsSummary", |
|
| 307 |
slots = c( |
|
| 308 |
target = "numeric", |
|
| 309 |
target_dose_interval = "numeric", |
|
| 310 |
nsim = "integer", |
|
| 311 |
prop_dlts = "ANY", |
|
| 312 |
mean_tox_risk = "numeric", |
|
| 313 |
dose_selected = "numeric", |
|
| 314 |
tox_at_doses_selected = "numeric", |
|
| 315 |
prop_at_target = "numeric", |
|
| 316 |
dose_most_selected = "numeric", |
|
| 317 |
obs_tox_rate_at_dose_most_selected = "numeric", |
|
| 318 |
n_obs = "ANY", |
|
| 319 |
n_above_target = "integer", |
|
| 320 |
dose_grid = "numeric", |
|
| 321 |
placebo = "logical", |
|
| 322 |
any_backfilled = "logical", |
|
| 323 |
n_backfill = "ANY", |
|
| 324 |
backfill_doses = "ANY" |
|
| 325 |
) |
|
| 326 |
) |
|
| 327 | ||
| 328 |
## default constructor ---- |
|
| 329 | ||
| 330 |
#' @rdname GeneralSimulationsSummary-class |
|
| 331 |
#' @note Typically, end users will not use the `.DefaultGeneralSimulationsSummary()` function. |
|
| 332 |
#' @export |
|
| 333 |
.DefaultGeneralSimulationsSummary <- function() {
|
|
| 334 | 2x |
stop( |
| 335 | 2x |
paste( |
| 336 | 2x |
"Class GeneralSimulationsSummary cannot be instantiated directly.", |
| 337 | 2x |
"Please use one of its subclasses instead." |
| 338 |
) |
|
| 339 |
) |
|
| 340 |
} |
|
| 341 | ||
| 342 |
## SimulationsSummary ---- |
|
| 343 | ||
| 344 |
## class ---- |
|
| 345 | ||
| 346 |
#' `SimulationsSummary` |
|
| 347 |
#' |
|
| 348 |
#' @description `r lifecycle::badge("stable")`
|
|
| 349 |
#' |
|
| 350 |
#' In addition to the slots in the parent class [`GeneralSimulationsSummary`], |
|
| 351 |
#' it contains two slots with model fit information. |
|
| 352 |
#' |
|
| 353 |
#' @slot stop_report (`matrix`)\cr matrix of stopping rule outcomes |
|
| 354 |
#' @slot fit_at_dose_most_selected (`numeric`)\cr fitted toxicity rate at dose most often selected |
|
| 355 |
#' @slot additional_stats (`list`)\cr list of additional statistical summary |
|
| 356 |
#' @slot mean_fit (`list`)\cr list with the average, lower (2.5%) and upper (97.5%) |
|
| 357 |
#' quantiles of the mean fitted toxicity at each dose level |
|
| 358 |
#' |
|
| 359 |
#' @aliases SimulationsSummary |
|
| 360 |
#' @export |
|
| 361 |
.SimulationsSummary <- |
|
| 362 |
setClass( |
|
| 363 |
Class = "SimulationsSummary", |
|
| 364 |
slots = c( |
|
| 365 |
stop_report = "matrix", |
|
| 366 |
fit_at_dose_most_selected = "numeric", |
|
| 367 |
additional_stats = "list", |
|
| 368 |
mean_fit = "list" |
|
| 369 |
), |
|
| 370 |
contains = "GeneralSimulationsSummary" |
|
| 371 |
) |
|
| 372 | ||
| 373 |
## default constructor ---- |
|
| 374 | ||
| 375 |
#' @rdname SimulationsSummary-class |
|
| 376 |
#' @note Typically, end users will not use the `.DefaultSimulationsSummary()` function. |
|
| 377 |
#' @export |
|
| 378 |
.DefaultSimulationsSummary <- function() {
|
|
| 379 | 1x |
stop(paste( |
| 380 | 1x |
"Class SimulationsSummary cannot be instantiated directly.", |
| 381 | 1x |
"Please use one of its subclasses instead." |
| 382 |
)) |
|
| 383 |
} |
|
| 384 | ||
| 385 |
# DualSimulationsSummary ---- |
|
| 386 | ||
| 387 |
# class ---- |
|
| 388 | ||
| 389 |
#' `DualSimulationsSummary` |
|
| 390 |
#' |
|
| 391 |
#' @description `r lifecycle::badge("stable")`
|
|
| 392 |
#' This class captures the summary of dual-endpoint simulations output. |
|
| 393 |
#' In comparison to its parent class [`SimulationsSummary`], it has additional slots. |
|
| 394 |
#' |
|
| 395 |
#' @slot biomarker_fit_at_dose_most_selected (`numeric`)\cr fitted biomarker level at most often selected dose. |
|
| 396 |
#' @slot mean_biomarker_fit (`list`)\cr list with average, lower (2.5%) and upper (97.5%) quantiles of |
|
| 397 |
#' mean fitted biomarker level at each dose |
|
| 398 |
#' @aliases DualSimulationsSummary |
|
| 399 |
#' @export |
|
| 400 |
.DualSimulationsSummary <- |
|
| 401 |
setClass( |
|
| 402 |
Class = "DualSimulationsSummary", |
|
| 403 |
slots = c( |
|
| 404 |
biomarker_fit_at_dose_most_selected = "numeric", |
|
| 405 |
mean_biomarker_fit = "list" |
|
| 406 |
), |
|
| 407 |
contains = "SimulationsSummary" |
|
| 408 |
) |
|
| 409 | ||
| 410 |
# default constructor |
|
| 411 | ||
| 412 |
#' @rdname DualSimulationsSummary-class |
|
| 413 |
#' @note Typically, end users will not use the `.DefaultDualSimulationsSummary()` function. |
|
| 414 |
#' @export |
|
| 415 |
.DefaultDualSimulationsSummary <- function() {
|
|
| 416 | 1x |
empty_data <- DataDual(doseGrid = c(1, 3, 5, 10, 15, 20, 25, 30)) |
| 417 | ||
| 418 | 1x |
my_model <- DualEndpointRW( |
| 419 | 1x |
mean = c(0, 1), |
| 420 | 1x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 421 | 1x |
sigma2betaW = 0.01, |
| 422 | 1x |
sigma2W = c(a = 0.1, b = 0.1), |
| 423 | 1x |
rho = c(a = 1, b = 1), |
| 424 | 1x |
rw1 = TRUE |
| 425 |
) |
|
| 426 | ||
| 427 | 1x |
my_next_best <- NextBestDualEndpoint( |
| 428 | 1x |
target = c(0.9, 1), |
| 429 | 1x |
overdose = c(0.35, 1), |
| 430 | 1x |
max_overdose_prob = 0.25 |
| 431 |
) |
|
| 432 | ||
| 433 | 1x |
my_size1 <- CohortSizeRange( |
| 434 | 1x |
intervals = c(0, 30), |
| 435 | 1x |
cohort_size = c(1, 3) |
| 436 |
) |
|
| 437 | 1x |
my_size2 <- CohortSizeDLT( |
| 438 | 1x |
intervals = c(0, 1), |
| 439 | 1x |
cohort_size = c(1, 3) |
| 440 |
) |
|
| 441 | 1x |
my_size <- maxSize(my_size1, my_size2) |
| 442 | ||
| 443 | 1x |
my_stopping1 <- StoppingTargetBiomarker( |
| 444 | 1x |
target = c(0.9, 1), |
| 445 | 1x |
prob = 0.5 |
| 446 |
) |
|
| 447 | ||
| 448 | 1x |
my_stopping <- my_stopping1 | StoppingMinPatients(10) | StoppingMissingDose() |
| 449 | ||
| 450 | 1x |
my_increments <- IncrementsRelative( |
| 451 | 1x |
intervals = c(0, 20), |
| 452 | 1x |
increments = c(1, 0.33) |
| 453 |
) |
|
| 454 | ||
| 455 | 1x |
my_design <- DualDesign( |
| 456 | 1x |
model = my_model, |
| 457 | 1x |
data = empty_data, |
| 458 | 1x |
nextBest = my_next_best, |
| 459 | 1x |
stopping = my_stopping, |
| 460 | 1x |
increments = my_increments, |
| 461 | 1x |
cohort_size = CohortSizeConst(3), |
| 462 | 1x |
startingDose = 3 |
| 463 |
) |
|
| 464 | ||
| 465 | 1x |
beta_mod <- function(dose, e0, eMax, delta1, delta2, scal) {
|
| 466 | 4x |
maxDens <- (delta1^delta1) * |
| 467 | 4x |
(delta2^delta2) / |
| 468 | 4x |
((delta1 + delta2)^(delta1 + delta2)) |
| 469 | 4x |
dose <- dose / scal |
| 470 | 4x |
e0 + eMax / maxDens * (dose^delta1) * (1 - dose)^delta2 |
| 471 |
} |
|
| 472 | ||
| 473 | 1x |
true_biomarker <- function(dose) {
|
| 474 | 4x |
beta_mod( |
| 475 | 4x |
dose, |
| 476 | 4x |
e0 = 0.2, |
| 477 | 4x |
eMax = 0.6, |
| 478 | 4x |
delta1 = 5, |
| 479 | 4x |
delta2 = 5 * 0.5 / 0.5, |
| 480 | 4x |
scal = 100 |
| 481 |
) |
|
| 482 |
} |
|
| 483 | ||
| 484 | 1x |
true_tox <- function(dose) {
|
| 485 | 4x |
pnorm((dose - 60) / 10) |
| 486 |
} |
|
| 487 | ||
| 488 | 1x |
x <- simulate( |
| 489 | 1x |
object = my_design, |
| 490 | 1x |
trueTox = true_tox, |
| 491 | 1x |
trueBiomarker = true_biomarker, |
| 492 | 1x |
sigma2W = 0.01, |
| 493 | 1x |
rho = 0, |
| 494 | 1x |
nsim = 1, |
| 495 | 1x |
parallel = FALSE, |
| 496 | 1x |
seed = 3, |
| 497 | 1x |
startingDose = 6, |
| 498 | 1x |
mcmcOptions = .DefaultMcmcOptions() |
| 499 |
) |
|
| 500 |
} |
|
| 501 | ||
| 502 |
# PseudoSimulations ---- |
|
| 503 | ||
| 504 |
## class ---- |
|
| 505 | ||
| 506 |
#' `PseudoSimulations` |
|
| 507 |
#' |
|
| 508 |
#' @description `r lifecycle::badge("stable")`
|
|
| 509 |
#' This class captures trial simulations from designs using pseudo model. |
|
| 510 |
#' It has additional slots `fit` and `stop_reasons` compared to the |
|
| 511 |
#' general class [`GeneralSimulations`]. |
|
| 512 |
#' |
|
| 513 |
#' @slot fit (`list`)\cr final fit values. |
|
| 514 |
#' @slot final_td_target_during_trial_estimates (`numeric`)\cr final estimates of the `td_target_during_trial`. |
|
| 515 |
#' @slot final_td_target_end_of_trial_estimates (`numeric`)\cr final estimates of the `td_target_end_of_trial`. |
|
| 516 |
#' @slot final_td_target_during_trial_at_dose_grid (`numeric`) |
|
| 517 |
#' \cr dose levels at dose grid closest below the final `td_target_during_trial` estimates. |
|
| 518 |
#' @slot final_td_target_end_of_trial_at_dose_grid (`numeric`) |
|
| 519 |
#' \cr dose levels at dose grid closest below the final `td_target_end_of_trial` estimates. |
|
| 520 |
#' @slot final_tdeot_cis (`list`)\cr 95% credibility intervals of the final estimates for `td_target_end_of_trial`. |
|
| 521 |
#' @slot final_tdeot_ratios (`numeric`)\cr ratio of the upper to the lower 95% |
|
| 522 |
#' credibility intervals for `td_target_end_of_trial`. |
|
| 523 |
#' @slot final_cis (`list`)\cr final 95% credibility intervals for `td_target_end_of_trial` estimates. |
|
| 524 |
#' @slot final_ratios (`numeric`)\cr final ratios of the upper to the lower 95% |
|
| 525 |
#' credibility interval for `td_target_end_of_trial`. |
|
| 526 |
#' @slot stop_report (`matrix`)\cr outcomes of stopping rules. |
|
| 527 |
#' @slot stop_reasons (`list`)\cr reasons for stopping each simulation run. |
|
| 528 |
#' |
|
| 529 |
#' @aliases PseudoSimulations |
|
| 530 |
#' @export |
|
| 531 |
.PseudoSimulations <- |
|
| 532 |
setClass( |
|
| 533 |
Class = "PseudoSimulations", |
|
| 534 |
slots = c( |
|
| 535 |
fit = "list", |
|
| 536 |
final_td_target_during_trial_estimates = "numeric", |
|
| 537 |
final_td_target_end_of_trial_estimates = "numeric", |
|
| 538 |
final_td_target_during_trial_at_dose_grid = "numeric", |
|
| 539 |
final_td_target_end_of_trial_at_dose_grid = "numeric", |
|
| 540 |
final_tdeot_cis = "list", |
|
| 541 |
final_tdeot_ratios = "numeric", |
|
| 542 |
final_cis = "list", |
|
| 543 |
final_ratios = "numeric", |
|
| 544 |
stop_report = "matrix", |
|
| 545 |
stop_reasons = "list" |
|
| 546 |
), |
|
| 547 |
prototype = prototype( |
|
| 548 |
final_td_target_during_trial_estimates = c(0.1, 0.1), |
|
| 549 |
final_td_target_end_of_trial_estimates = c(0.1, 0.1), |
|
| 550 |
final_td_target_during_trial_at_dose_grid = c(0.1, 0.1), |
|
| 551 |
final_td_target_end_of_trial_at_dose_grid = c(0.1, 0.1), |
|
| 552 |
final_tdeot_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
| 553 |
final_tdeot_ratios = c(0.1, 0.1), |
|
| 554 |
final_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
| 555 |
final_ratios = c(0.1, 0.1), |
|
| 556 |
stop_report = matrix(TRUE, nrow = 2), |
|
| 557 |
stop_reasons = list("A", "A")
|
|
| 558 |
), |
|
| 559 |
contains = "GeneralSimulations", |
|
| 560 |
validity = v_pseudo_simulations |
|
| 561 |
) |
|
| 562 | ||
| 563 |
## constructor ---- |
|
| 564 | ||
| 565 |
#' @rdname PseudoSimulations-class |
|
| 566 |
#' |
|
| 567 |
#' @param fit (`list`)\cr see slot definition. |
|
| 568 |
#' @param final_td_target_during_trial_estimates (`numeric`)\cr see slot definition. |
|
| 569 |
#' @param final_td_target_end_of_trial_estimates (`numeric`)\cr see slot definition. |
|
| 570 |
#' @param final_td_target_during_trial_at_dose_grid (`numeric`)\cr see slot definition. |
|
| 571 |
#' @param final_td_target_end_of_trial_at_dose_grid (`numeric`)\cr see slot definition. |
|
| 572 |
#' @param final_tdeot_cis (`list`)\cr see slot definition. |
|
| 573 |
#' @param final_tdeot_ratios (`numeric`)\cr see slot definition. |
|
| 574 |
#' @param final_cis (`list`)\cr see slot definition. |
|
| 575 |
#' @param final_ratios (`numeric`)\cr see slot definition. |
|
| 576 |
#' @param stop_report see [`PseudoSimulations`] |
|
| 577 |
#' @param stop_reasons (`list`)\cr see slot definition. |
|
| 578 |
#' @param \dots additional parameters from [`GeneralSimulations`] |
|
| 579 |
#' |
|
| 580 |
#' @export |
|
| 581 |
PseudoSimulations <- function( |
|
| 582 |
fit, |
|
| 583 |
final_td_target_during_trial_estimates, |
|
| 584 |
final_td_target_end_of_trial_estimates, |
|
| 585 |
final_td_target_during_trial_at_dose_grid, |
|
| 586 |
final_td_target_end_of_trial_at_dose_grid, |
|
| 587 |
final_tdeot_cis, |
|
| 588 |
final_tdeot_ratios, |
|
| 589 |
final_cis, |
|
| 590 |
final_ratios, |
|
| 591 |
stop_report, |
|
| 592 |
stop_reasons, |
|
| 593 |
... |
|
| 594 |
) {
|
|
| 595 | 13x |
start <- GeneralSimulations(...) |
| 596 | 13x |
.PseudoSimulations( |
| 597 | 13x |
start, |
| 598 | 13x |
fit = fit, |
| 599 | 13x |
final_td_target_during_trial_estimates = final_td_target_during_trial_estimates, |
| 600 | 13x |
final_td_target_end_of_trial_estimates = final_td_target_end_of_trial_estimates, |
| 601 | 13x |
final_td_target_during_trial_at_dose_grid = final_td_target_during_trial_at_dose_grid, |
| 602 | 13x |
final_td_target_end_of_trial_at_dose_grid = final_td_target_end_of_trial_at_dose_grid, |
| 603 | 13x |
final_tdeot_cis = final_tdeot_cis, |
| 604 | 13x |
final_tdeot_ratios = final_tdeot_ratios, |
| 605 | 13x |
final_cis = final_cis, |
| 606 | 13x |
final_ratios = final_ratios, |
| 607 | 13x |
stop_report = stop_report, |
| 608 | 13x |
stop_reasons = stop_reasons |
| 609 |
) |
|
| 610 |
} |
|
| 611 | ||
| 612 |
## default constructor ---- |
|
| 613 | ||
| 614 |
#' @rdname PseudoSimulations-class |
|
| 615 |
#' @note Typically, end users will not use the `.DefaultPseudoSimulations()` function. |
|
| 616 |
#' @export |
|
| 617 |
.DefaultPseudoSimulations <- function() {
|
|
| 618 | 2x |
stop( |
| 619 | 2x |
"Class PseudoSimulations cannot be instantiated directly. Please use one of its subclasses instead." |
| 620 |
) |
|
| 621 |
} |
|
| 622 | ||
| 623 |
# PseudoDualSimulations ---- |
|
| 624 | ||
| 625 |
## class ---- |
|
| 626 | ||
| 627 |
#' `PseudoDualSimulations` |
|
| 628 |
#' |
|
| 629 |
#' @description `r lifecycle::badge("stable")`
|
|
| 630 |
#' This class conducts trial simulations for designs using both the |
|
| 631 |
#' DLE and efficacy responses. It defines final values for |
|
| 632 |
#' efficacy fit and DLE, estimates of Gstar, optimal dose and sigma2. |
|
| 633 |
#' |
|
| 634 |
#' @slot fit_eff (`list`)\cr final values of efficacy fit. |
|
| 635 |
#' @slot final_gstar_estimates (`numeric`)\cr final Gstar estimates. |
|
| 636 |
#' @slot final_gstar_at_dose_grid (`numeric`)\cr final Gstar estimates at dose grid. |
|
| 637 |
#' @slot final_gstar_cis (`list`)\cr list of 95% confidence interval for Gstar estimates. |
|
| 638 |
#' @slot final_gstar_ratios (`numeric`)\cr ratios of confidence intervals for Gstar estimates. |
|
| 639 |
#' @slot final_optimal_dose (`numeric`)\cr final optimal dose. |
|
| 640 |
#' @slot final_optimal_dose_at_dose_grid (`numeric`)\cr final optimal dose at dose grid. |
|
| 641 |
#' @slot sigma2_est (`numeric`)\cr final sigma2 estimates. |
|
| 642 |
#' |
|
| 643 |
#' @aliases PseudoDualSimulations |
|
| 644 |
#' @export |
|
| 645 |
.PseudoDualSimulations <- |
|
| 646 |
setClass( |
|
| 647 |
Class = "PseudoDualSimulations", |
|
| 648 |
slots = c( |
|
| 649 |
fit_eff = "list", |
|
| 650 |
final_gstar_estimates = "numeric", |
|
| 651 |
final_gstar_at_dose_grid = "numeric", |
|
| 652 |
final_gstar_cis = "list", |
|
| 653 |
final_gstar_ratios = "numeric", |
|
| 654 |
final_optimal_dose = "numeric", |
|
| 655 |
final_optimal_dose_at_dose_grid = "numeric", |
|
| 656 |
sigma2_est = "numeric" |
|
| 657 |
), |
|
| 658 |
prototype = prototype( |
|
| 659 |
final_gstar_estimates = c(0.1, 0.1), |
|
| 660 |
final_gstar_at_dose_grid = c(0.1, 0.1), |
|
| 661 |
final_gstar_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
| 662 |
final_gstar_ratios = c(0.01, 0.01), |
|
| 663 |
final_optimal_dose = c(0.01, 0.01), |
|
| 664 |
final_optimal_dose_at_dose_grid = c(0.01, 0.01), |
|
| 665 |
sigma2_est = c(0.001, 0.002) |
|
| 666 |
), |
|
| 667 |
contains = "PseudoSimulations", |
|
| 668 |
validity = v_pseudo_dual_simulations |
|
| 669 |
) |
|
| 670 | ||
| 671 |
## constructor ---- |
|
| 672 | ||
| 673 |
#' @rdname PseudoDualSimulations-class |
|
| 674 |
#' |
|
| 675 |
#' @param fit_eff (`list`)\cr see slot definition. |
|
| 676 |
#' @param final_gstar_estimates (`numeric`)\cr see slot definition. |
|
| 677 |
#' @param final_gstar_at_dose_grid (`numeric`)\cr see slot definition. |
|
| 678 |
#' @param final_gstar_cis (`list`)\cr see slot definition. |
|
| 679 |
#' @param final_gstar_ratios (`numeric`)\cr see slot definition. |
|
| 680 |
#' @param final_optimal_dose (`numeric`)\cr see slot definition. |
|
| 681 |
#' @param final_optimal_dose_at_dose_grid (`numeric`)\cr see slot definition. |
|
| 682 |
#' @param sigma2_est (`numeric`)\cr see slot definition. |
|
| 683 |
#' @param \dots additional parameters from [`PseudoSimulations`] |
|
| 684 |
#' @export |
|
| 685 |
PseudoDualSimulations <- function( |
|
| 686 |
fit_eff, |
|
| 687 |
final_gstar_estimates, |
|
| 688 |
final_gstar_at_dose_grid, |
|
| 689 |
final_gstar_cis, |
|
| 690 |
final_gstar_ratios, |
|
| 691 |
final_optimal_dose, |
|
| 692 |
final_optimal_dose_at_dose_grid, |
|
| 693 |
sigma2_est, |
|
| 694 |
... |
|
| 695 |
) {
|
|
| 696 | 7x |
start <- PseudoSimulations(...) |
| 697 | 7x |
.PseudoDualSimulations( |
| 698 | 7x |
start, |
| 699 | 7x |
fit_eff = fit_eff, |
| 700 | 7x |
final_gstar_estimates = final_gstar_estimates, |
| 701 | 7x |
final_gstar_at_dose_grid = final_gstar_at_dose_grid, |
| 702 | 7x |
final_gstar_cis = final_gstar_cis, |
| 703 | 7x |
final_gstar_ratios = final_gstar_ratios, |
| 704 | 7x |
final_optimal_dose = final_optimal_dose, |
| 705 | 7x |
final_optimal_dose_at_dose_grid = final_optimal_dose_at_dose_grid, |
| 706 | 7x |
sigma2_est = sigma2_est |
| 707 |
) |
|
| 708 |
} |
|
| 709 | ||
| 710 |
## default constructor ---- |
|
| 711 | ||
| 712 |
#' @rdname PseudoDualSimulations-class |
|
| 713 |
#' @note Do not use the `.DefaultPseudoDualSimulations()` function. |
|
| 714 |
#' @export |
|
| 715 |
.DefaultPseudoDualSimulations <- function() {
|
|
| 716 | 1x |
stop( |
| 717 | 1x |
"Class PseudoDualSimulations cannot be instantiated directly. Please use a subclass." |
| 718 |
) |
|
| 719 |
} |
|
| 720 | ||
| 721 |
# PseudoDualFlexiSimulations ---- |
|
| 722 | ||
| 723 |
## class ---- |
|
| 724 | ||
| 725 |
#' `PseudoDualFlexiSimulations` |
|
| 726 |
#' |
|
| 727 |
#' @description `r lifecycle::badge("stable")`
|
|
| 728 |
#' This class captures the trial simulations design using both the DLE and |
|
| 729 |
#' efficacy responses using [`EffFlexi`] efficacy model. |
|
| 730 |
#' It extends [`PseudoDualSimulations`] by adding the capability to capture the sigma2betaW estimates. |
|
| 731 |
#' |
|
| 732 |
#' @slot sigma2_beta_w_est (`numeric`)\cr the vector of the final posterior mean sigma2betaW estimates |
|
| 733 |
#' @aliases PseudoDualFlexiSimulations |
|
| 734 |
#' @export |
|
| 735 |
.PseudoDualFlexiSimulations <- |
|
| 736 |
setClass( |
|
| 737 |
Class = "PseudoDualFlexiSimulations", |
|
| 738 |
slots = c(sigma2_beta_w_est = "numeric"), |
|
| 739 |
prototype = prototype(sigma2_beta_w_est = c(0.001, 0.002)), |
|
| 740 |
contains = "PseudoDualSimulations" |
|
| 741 |
) |
|
| 742 | ||
| 743 |
## constructor ---- |
|
| 744 | ||
| 745 |
#' @rdname PseudoDualFlexiSimulations-class |
|
| 746 |
#' |
|
| 747 |
#' @param sigma2_beta_w_est (`numeric`)\cr the vector of the final posterior mean sigma2betaW estimates |
|
| 748 |
#' @param \dots additional parameters from [`PseudoDualSimulations`] |
|
| 749 |
#' |
|
| 750 |
#' @export |
|
| 751 |
PseudoDualFlexiSimulations <- function(sigma2_beta_w_est, ...) {
|
|
| 752 | 2x |
start <- PseudoDualSimulations(...) |
| 753 | 2x |
.PseudoDualFlexiSimulations(start, sigma2_beta_w_est = sigma2_beta_w_est) |
| 754 |
} |
|
| 755 | ||
| 756 |
## default constructor ---- |
|
| 757 | ||
| 758 |
#' @rdname PseudoDualFlexiSimulations-class |
|
| 759 |
#' @note Typically, end users will not use the `.DefaultPseudoFlexiSimulations()` function. |
|
| 760 |
#' @export |
|
| 761 |
.DefaultPseudoDualFlexiSimulations <- function() {
|
|
| 762 | 1x |
stop( |
| 763 | 1x |
"Class PseudoFlexiSimulations cannot be instantiated directly. Please use one of its subclasses instead." |
| 764 |
) |
|
| 765 |
} |
|
| 766 | ||
| 767 |
# PseudoSimulationsSummary ---- |
|
| 768 | ||
| 769 |
## class ---- |
|
| 770 | ||
| 771 |
#' `PseudoSimulationsSummary` |
|
| 772 |
#' |
|
| 773 |
#' @description `r lifecycle::badge("stable")`
|
|
| 774 |
#' |
|
| 775 |
#' This class captures the summary of pseudo-models simulations output. |
|
| 776 |
#' Note that objects should not be created by users, therefore no |
|
| 777 |
#' initialization function is provided for this class. |
|
| 778 |
#' |
|
| 779 |
#' @slot target_end_of_trial (`numeric`)\cr the target probability of DLE wanted at the end of a trial |
|
| 780 |
#' @slot target_dose_end_of_trial (`numeric`)\cr the dose level corresponds to the target probability |
|
| 781 |
#' of DLE wanted at the end of a trial, TDEOT |
|
| 782 |
#' @slot target_dose_end_of_trial_at_dose_grid (`numeric`)\cr the dose level at dose grid corresponds to the |
|
| 783 |
#' target probability of DLE wanted at the end of a trial |
|
| 784 |
#' @slot target_during_trial (`numeric`)\cr the target probability of DLE wanted during a trial |
|
| 785 |
#' @slot target_dose_during_trial (`numeric`)\cr the dose level corresponds to the target probability of DLE |
|
| 786 |
#' wanted during the trial. TDDT |
|
| 787 |
#' @slot target_dose_during_trial_at_dose_grid (`numeric`)\cr the dose level at dose grid corresponds to the |
|
| 788 |
#' target probability of DLE wanted during a trial |
|
| 789 |
#' @slot tdeot_summary (`table`)\cr the six-number table summary, include the lowest, the 25th percentile |
|
| 790 |
#' (lower quartile), the 50th percentile (median), the mean, the 75th percentile and the highest values of the |
|
| 791 |
#' final dose levels obtained corresponds to the target probability of DLE |
|
| 792 |
#' want at the end of a trial across all simulations |
|
| 793 |
#' @slot tddt_summary (`table`)\cr the six-number table summary, include the lowest, the 25th percentile |
|
| 794 |
#' (lower quartile), the 50th percentile (median), the mean, the 75th percentile and the highest values of the |
|
| 795 |
#' final dose levels obtained corresponds to the target probability of DLE |
|
| 796 |
#' want during a trial across all simulations |
|
| 797 |
#' @slot final_dose_rec_summary (`table`)\cr the six-number table summary, include the lowest, the 25th percentile |
|
| 798 |
#' (lower quartile), the 50th percentile (median), the mean, the 75th percentile and the highest values of the |
|
| 799 |
#' final optimal doses, which is either the TDEOT when only DLE response are incorporated into |
|
| 800 |
#' the escalation procedure or the minimum of the TDEOT and Gstar when DLE and efficacy responses are |
|
| 801 |
#' incorporated, across all simulations |
|
| 802 |
#' @slot ratio_tdeot_summary (`table`)\cr the six-number summary table of the final ratios of the upper to the |
|
| 803 |
#' lower 95% credibility intervals of the final TDEOTs across all simulations |
|
| 804 |
#' @slot final_ratio_summary (`table`)\cr the six-number summary table of the final ratios of the upper to the |
|
| 805 |
#' lower 95% credibility intervals of the final optimal doses across all simulations |
|
| 806 |
#' @slot nsim (`integer`)\cr number of simulations |
|
| 807 |
#' @slot prop_dle (`numeric`)\cr proportions of DLE in the trials |
|
| 808 |
#' @slot mean_tox_risk (`numeric`)\cr mean toxicity risks for the patients |
|
| 809 |
#' @slot dose_selected (`numeric`)\cr doses selected as MTD (target_dose_end_of_trial) |
|
| 810 |
#' @slot tox_at_doses_selected (`numeric`)\cr true toxicity at doses selected |
|
| 811 |
#' @slot prop_at_target_end_of_trial (`numeric`)\cr Proportion of trials selecting at the dose_grid closest below the |
|
| 812 |
#' MTD, the target_dose_end_of_trial |
|
| 813 |
#' @slot prop_at_target_during_trial (`numeric`)\cr Proportion of trials selecting at the dose_grid closest below |
|
| 814 |
#' the target_dose_during_trial |
|
| 815 |
#' @slot dose_most_selected (`numeric`)\cr dose most often selected as MTD |
|
| 816 |
#' @slot obs_tox_rate_at_dose_most_selected (`numeric`)\cr observed toxicity rate at dose most often |
|
| 817 |
#' selected |
|
| 818 |
#' @slot n_obs (`integer`)\cr number of patients overall |
|
| 819 |
#' @slot n_above_target_end_of_trial (`integer`)\cr number of patients treated above target_dose_end_of_trial |
|
| 820 |
#' @slot n_above_target_during_trial (`integer`)\cr number of patients treated above target_dose_during_trial |
|
| 821 |
#' @slot dose_grid (`numeric`)\cr the dose grid that has been used |
|
| 822 |
#' @slot fit_at_dose_most_selected (`numeric`)\cr fitted toxicity rate at dose most often selected |
|
| 823 |
#' @slot mean_fit (`list`)\cr list with the average, lower (2.5%) and upper (97.5%) |
|
| 824 |
#' quantiles of the mean fitted toxicity at each dose level |
|
| 825 |
#' @slot stop_report (`matrix`)\cr matrix of stopping rule outcomes |
|
| 826 |
#' |
|
| 827 |
#' @aliases PseudoSimulationsSummary |
|
| 828 |
#' @export |
|
| 829 |
.PseudoSimulationsSummary <- |
|
| 830 |
setClass( |
|
| 831 |
Class = "PseudoSimulationsSummary", |
|
| 832 |
slots = c( |
|
| 833 |
target_end_of_trial = "numeric", |
|
| 834 |
target_dose_end_of_trial = "numeric", |
|
| 835 |
target_dose_end_of_trial_at_dose_grid = "numeric", |
|
| 836 |
target_during_trial = "numeric", |
|
| 837 |
target_dose_during_trial = "numeric", |
|
| 838 |
target_dose_during_trial_at_dose_grid = "numeric", |
|
| 839 |
tdeot_summary = "table", |
|
| 840 |
tddt_summary = "table", |
|
| 841 |
final_dose_rec_summary = "table", |
|
| 842 |
ratio_tdeot_summary = "table", |
|
| 843 |
final_ratio_summary = "table", |
|
| 844 |
nsim = "integer", |
|
| 845 |
prop_dle = "numeric", |
|
| 846 |
mean_tox_risk = "numeric", |
|
| 847 |
dose_selected = "numeric", |
|
| 848 |
tox_at_doses_selected = "numeric", |
|
| 849 |
prop_at_target_end_of_trial = "numeric", |
|
| 850 |
prop_at_target_during_trial = "numeric", |
|
| 851 |
dose_most_selected = "numeric", |
|
| 852 |
obs_tox_rate_at_dose_most_selected = "numeric", |
|
| 853 |
n_obs = "integer", |
|
| 854 |
n_above_target_end_of_trial = "integer", |
|
| 855 |
n_above_target_during_trial = "integer", |
|
| 856 |
dose_grid = "numeric", |
|
| 857 |
fit_at_dose_most_selected = "numeric", |
|
| 858 |
mean_fit = "list", |
|
| 859 |
stop_report = "matrix" |
|
| 860 |
) |
|
| 861 |
) |
|
| 862 | ||
| 863 |
## default constructor ---- |
|
| 864 | ||
| 865 |
#' @rdname PseudoSimulationsSummary-class |
|
| 866 |
#' @note Typically, end users will not use the `.DefaultPseudoSimulationsSummary()` function. |
|
| 867 |
#' @export |
|
| 868 |
.DefaultPseudoSimulationsSummary <- function() {
|
|
| 869 | 2x |
stop( |
| 870 | 2x |
"Class PseudoSimulationsSummary cannot be instantiated directly. Please use one of its subclasses instead." |
| 871 |
) |
|
| 872 |
} |
|
| 873 | ||
| 874 |
# PseudoDualSimulationsSummary ---- |
|
| 875 | ||
| 876 |
## class ---- |
|
| 877 | ||
| 878 |
#' `PseudoDualSimulationsSummary` |
|
| 879 |
#' |
|
| 880 |
#' @description `r lifecycle::badge("stable")`
|
|
| 881 |
#' |
|
| 882 |
#' This class captures the summary of the dual responses simulations using pseudo models. |
|
| 883 |
#' It contains all slots from [`PseudoSimulationsSummary`] object. In addition to |
|
| 884 |
#' the slots in the parent class [`PseudoSimulationsSummary`], it contains additional |
|
| 885 |
#' slots for the efficacy model fit information. |
|
| 886 |
#' |
|
| 887 |
#' Note that objects should not be created by users, therefore no initialization function |
|
| 888 |
#' is provided for this class. |
|
| 889 |
#' |
|
| 890 |
#' @slot target_gstar (`numeric`)\cr the target dose level such that its gain value is at maximum |
|
| 891 |
#' @slot target_gstar_at_dose_grid (`numeric`)\cr the dose level at dose Grid closest and below Gstar |
|
| 892 |
#' @slot gstar_summary (`table`)\cr the six-number table summary (lowest, 25th, 50th (median), 75th percentile, mean |
|
| 893 |
#' and highest value) of the final Gstar values obtained across all simulations |
|
| 894 |
#' @slot ratio_gstar_summary (`table`)\cr the six-number summary table of the ratios of the upper to the lower 95% |
|
| 895 |
#' credibility intervals of the final Gstar across all simulations |
|
| 896 |
#' @slot eff_fit_at_dose_most_selected (`numeric`)\cr fitted expected mean efficacy value at dose most often |
|
| 897 |
#' selected |
|
| 898 |
#' @slot mean_eff_fit (`list`)\cr list with mean, lower (2.5%) and upper (97.5%) quantiles of the fitted expected |
|
| 899 |
#' efficacy value at each dose level. |
|
| 900 |
#' |
|
| 901 |
#' @aliases PseudoDualSimulationsSummary |
|
| 902 |
#' @export |
|
| 903 |
.PseudoDualSimulationsSummary <- |
|
| 904 |
setClass( |
|
| 905 |
Class = "PseudoDualSimulationsSummary", |
|
| 906 |
contains = "PseudoSimulationsSummary", |
|
| 907 |
slots = c( |
|
| 908 |
target_gstar = "numeric", |
|
| 909 |
target_gstar_at_dose_grid = "numeric", |
|
| 910 |
gstar_summary = "table", |
|
| 911 |
ratio_gstar_summary = "table", |
|
| 912 |
eff_fit_at_dose_most_selected = "numeric", |
|
| 913 |
mean_eff_fit = "list" |
|
| 914 |
) |
|
| 915 |
) |
|
| 916 | ||
| 917 |
## default constructor ---- |
|
| 918 | ||
| 919 |
#' @rdname PseudoDualSimulationsSummary-class |
|
| 920 |
#' @note Typically, end users will not use the `.DefaultPseudoDualSimulationsSummary()` function. |
|
| 921 |
#' @export |
|
| 922 |
.DefaultPseudoDualSimulationsSummary <- function() {
|
|
| 923 | 2x |
stop( |
| 924 | 2x |
"Class PseudoDualSimulationsSummary cannot be instantiated directly. Please use one of its subclasses instead." |
| 925 |
) |
|
| 926 |
} |
|
| 927 | ||
| 928 |
# DASimulations ---- |
|
| 929 | ||
| 930 |
## class ---- |
|
| 931 | ||
| 932 |
#' `DASimulations` |
|
| 933 |
#' |
|
| 934 |
#' @description `r lifecycle::badge("stable")`
|
|
| 935 |
#' |
|
| 936 |
#' This class captures the trial simulations from DA based designs. |
|
| 937 |
#' In comparison to the parent class [`Simulations`], |
|
| 938 |
#' it contains additional slots to capture the time to DLT fits, additional |
|
| 939 |
#' parameters and the trial duration. |
|
| 940 |
#' |
|
| 941 |
#' @slot trial_duration (`numeric`)\cr the vector of trial duration values for all simulations. |
|
| 942 |
#' |
|
| 943 |
#' @aliases DASimulations |
|
| 944 |
#' @export |
|
| 945 |
.DASimulations <- |
|
| 946 |
setClass( |
|
| 947 |
Class = "DASimulations", |
|
| 948 |
slots = c(trial_duration = "numeric"), |
|
| 949 |
prototype = prototype(trial_duration = rep(0, 2)), |
|
| 950 |
contains = "Simulations", |
|
| 951 |
validity = v_da_simulations |
|
| 952 |
) |
|
| 953 | ||
| 954 |
## constructor ---- |
|
| 955 | ||
| 956 |
#' @rdname DASimulations-class |
|
| 957 |
#' |
|
| 958 |
#' @param trial_duration (`numeric`)\cr see [`DASimulations`] |
|
| 959 |
#' @param \dots additional parameters from [`Simulations`] |
|
| 960 |
#' |
|
| 961 |
#' @export |
|
| 962 |
DASimulations <- function(trial_duration, ...) {
|
|
| 963 | 3x |
start <- Simulations(...) |
| 964 | 3x |
.DASimulations(start, trial_duration = trial_duration) |
| 965 |
} |
|
| 966 | ||
| 967 | ||
| 968 |
## default constructor ---- |
|
| 969 | ||
| 970 |
#' @rdname DASimulations-class |
|
| 971 |
#' @note Typically, end users will not use the `.DefaultDASimulations()` function. |
|
| 972 |
#' @export |
|
| 973 |
.DefaultDASimulations <- function() {
|
|
| 974 | ! |
design <- .DefaultDADesign() |
| 975 | ! |
myTruth <- probFunction(design@model, alpha0 = 2, alpha1 = 3) |
| 976 | ! |
exp_cond_cdf <- function(x, onset = 15) {
|
| 977 | ! |
a <- stats::pexp(28, 1 / onset, lower.tail = FALSE) |
| 978 | ! |
1 - (stats::pexp(x, 1 / onset, lower.tail = FALSE) - a) / (1 - a) |
| 979 |
} |
|
| 980 | ||
| 981 | ! |
simulate( |
| 982 | ! |
design, |
| 983 | ! |
args = NULL, |
| 984 | ! |
truthTox = myTruth, |
| 985 | ! |
truthSurv = exp_cond_cdf, |
| 986 | ! |
trueTmax = 80, |
| 987 | ! |
nsim = 2, |
| 988 | ! |
seed = 819, |
| 989 | ! |
mcmcOptions = .DefaultMcmcOptions(), |
| 990 | ! |
firstSeparate = TRUE, |
| 991 | ! |
deescalate = FALSE, |
| 992 | ! |
parallel = FALSE |
| 993 |
) |
|
| 994 |
} |
|
| 995 | ||
| 996 |
# tidy |
|
| 997 | ||
| 998 |
## tidy-Simulations ---- |
|
| 999 | ||
| 1000 |
#' @rdname tidy |
|
| 1001 |
#' @aliases tidy-Simulations |
|
| 1002 |
#' @example examples/Simulations-method-tidy.R |
|
| 1003 |
#' @export |
|
| 1004 |
setMethod( |
|
| 1005 |
f = "tidy", |
|
| 1006 |
signature = signature(x = "Simulations"), |
|
| 1007 |
definition = function(x, ...) {
|
|
| 1008 | 8x |
slot_names <- slotNames(x) |
| 1009 | 8x |
rv <- list() |
| 1010 | 8x |
for (nm in slot_names) {
|
| 1011 | 64x |
if (!is.function(slot(x, nm))) {
|
| 1012 | 64x |
if (nm %in% c("stop_reasons", "additional_stats")) {} else {
|
| 1013 | 48x |
rv[[nm]] <- h_tidy_slot(x, nm) |
| 1014 |
} |
|
| 1015 |
} |
|
| 1016 |
} |
|
| 1017 |
# Column bind of all list elements have the same number of rows |
|
| 1018 | 8x |
if (length(rv) > 1 & length(unique(sapply(rv, nrow))) == 1) {
|
| 1019 | ! |
rv <- rv %>% dplyr::bind_cols() |
| 1020 |
} |
|
| 1021 | 8x |
rv %>% h_tidy_class(x) |
| 1022 |
} |
|
| 1023 |
) |
| 1 |
#' @include Data-methods.R |
|
| 2 |
#' @include Design-class.R |
|
| 3 |
#' @include McmcOptions-class.R |
|
| 4 |
#' @include Rules-methods.R |
|
| 5 |
#' @include Backfill-methods.R |
|
| 6 |
#' @include Simulations-class.R |
|
| 7 |
#' @include helpers.R |
|
| 8 |
#' @include mcmc.R |
|
| 9 |
NULL |
|
| 10 | ||
| 11 |
# simulate ---- |
|
| 12 | ||
| 13 |
## Design ---- |
|
| 14 | ||
| 15 |
#' Simulate outcomes from a CRM design |
|
| 16 |
#' |
|
| 17 |
#' @description `r lifecycle::badge("stable")`
|
|
| 18 |
#' |
|
| 19 |
#' @param object the [`Design`] object we want to simulate data from |
|
| 20 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 21 |
#' @param seed see [set_seed()] |
|
| 22 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 23 |
#' true probability (vector) for toxicity. Additional arguments can be supplied |
|
| 24 |
#' in `args`. |
|
| 25 |
#' @param truthResponse (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 26 |
#' probability (vector) for a positive efficacy response. |
|
| 27 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The |
|
| 28 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 29 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 30 |
#' simulations. In order to produce outcomes from the posterior predictive |
|
| 31 |
#' distribution, e.g, pass an `object` that contains the data observed so |
|
| 32 |
#' far, `truth` contains the `prob` function from the model in |
|
| 33 |
#' `object`, and `args` contains posterior samples from the model. |
|
| 34 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 35 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 36 |
#' in this patient. |
|
| 37 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 38 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 39 |
#' the standard options are used |
|
| 40 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 41 |
#' clusters of the computer? (not default) |
|
| 42 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 43 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 44 |
#' @param ... not used |
|
| 45 |
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the |
|
| 46 |
#' vector of posterior MTD samples. Each list element must therefore accept |
|
| 47 |
#' one and only one argument, which is a numeric vector, and return a number. |
|
| 48 |
#' |
|
| 49 |
#' @return an object of class [`Simulations`] |
|
| 50 |
#' |
|
| 51 |
#' @example examples/design-method-simulate-Design.R |
|
| 52 |
#' @export |
|
| 53 |
#' @importFrom parallel detectCores |
|
| 54 |
setMethod( |
|
| 55 |
f = "simulate", |
|
| 56 |
signature = signature( |
|
| 57 |
object = "Design", |
|
| 58 |
nsim = "ANY", |
|
| 59 |
seed = "ANY" |
|
| 60 |
), |
|
| 61 |
definition = function( |
|
| 62 |
object, |
|
| 63 |
nsim = 1L, |
|
| 64 |
seed = NULL, |
|
| 65 |
truth, |
|
| 66 |
truthResponse = plogis, |
|
| 67 |
args = NULL, |
|
| 68 |
firstSeparate = FALSE, |
|
| 69 |
mcmcOptions = McmcOptions(), |
|
| 70 |
parallel = FALSE, |
|
| 71 |
nCores = min(parallel::detectCores(), 5), |
|
| 72 |
derive = list(), |
|
| 73 |
... |
|
| 74 |
) {
|
|
| 75 | 14x |
nsim <- as.integer(nsim) |
| 76 | 14x |
assert_function(truth) |
| 77 | 14x |
assert_function(truthResponse) |
| 78 | 14x |
assert_flag(firstSeparate) |
| 79 | 14x |
assert_count(nsim, positive = TRUE) |
| 80 | 14x |
assert_flag(parallel) |
| 81 | 14x |
assert_count(nCores, positive = TRUE) |
| 82 | ||
| 83 |
# Does this design use backfill cohorts? If no we can skip the corresponding |
|
| 84 |
# computations later. |
|
| 85 | 14x |
uses_backfill <- !is(object@backfill@opening, "OpeningNone") |
| 86 | ||
| 87 | 14x |
args <- as.data.frame(args) |
| 88 | 14x |
n_args <- max(nrow(args), 1L) |
| 89 | 14x |
rng_state <- set_seed(seed) |
| 90 | 14x |
sim_seeds <- sample.int(n = 2147483647, size = as.integer(nsim)) |
| 91 | ||
| 92 | 14x |
run_sim <- function(iter_sim) {
|
| 93 | 41x |
set.seed(sim_seeds[iter_sim]) |
| 94 | ||
| 95 | 41x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 96 | 41x |
data <- object@data |
| 97 | 41x |
prob_placebo <- NULL |
| 98 | 41x |
cohort_size_placebo <- NULL |
| 99 | 41x |
prob_response_placebo <- NULL |
| 100 | ||
| 101 | 41x |
if (data@placebo) {
|
| 102 | 3x |
placebo_dose <- object@data@doseGrid[1] |
| 103 | 3x |
prob_placebo <- h_this_truth(placebo_dose, current_args, truth) |
| 104 | 3x |
prob_response_placebo <- truthResponse(placebo_dose) |
| 105 |
} |
|
| 106 | ||
| 107 | 41x |
should_stop <- FALSE |
| 108 | 41x |
dose <- object@startingDose |
| 109 | 41x |
backfill_cohorts <- list() # Queue of backfill cohorts. |
| 110 | 41x |
backfill_patients <- 0L # Total number of backfill patients enrolled. |
| 111 | ||
| 112 | 41x |
while (!should_stop) {
|
| 113 | 177x |
prob <- h_this_truth(dose, current_args, truth) |
| 114 | 177x |
prob_response <- truthResponse(dose) |
| 115 | ||
| 116 | 177x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 117 | ||
| 118 | 177x |
if (data@placebo) {
|
| 119 | 15x |
cohort_size_placebo <- size( |
| 120 | 15x |
object@pl_cohort_size, |
| 121 | 15x |
dose = dose, |
| 122 | 15x |
data = data |
| 123 |
) |
|
| 124 |
} else {
|
|
| 125 | 162x |
cohort_size_placebo <- NULL |
| 126 |
} |
|
| 127 | ||
| 128 | 177x |
data <- h_determine_dlts( |
| 129 | 177x |
data = data, |
| 130 | 177x |
dose = dose, |
| 131 | 177x |
prob = prob, |
| 132 | 177x |
prob_placebo = prob_placebo, |
| 133 | 177x |
prob_response = prob_response, |
| 134 | 177x |
prob_response_placebo = prob_response_placebo, |
| 135 | 177x |
cohort_size = cohort_size, |
| 136 | 177x |
cohort_size_placebo = cohort_size_placebo, |
| 137 | 177x |
dose_grid = data@doseGrid, |
| 138 | 177x |
first_separate = firstSeparate |
| 139 |
) |
|
| 140 | ||
| 141 |
# Backfill logic. |
|
| 142 | 177x |
if (uses_backfill) {
|
| 143 | 66x |
backfill_cohorts <- h_update_backfill_queue( |
| 144 | 66x |
backfill_cohorts = backfill_cohorts, |
| 145 | 66x |
data = data, |
| 146 | 66x |
dose = dose, |
| 147 | 66x |
backfill = object@backfill |
| 148 |
) |
|
| 149 | ||
| 150 | 66x |
enrollment_result <- h_enroll_backfill_patients( |
| 151 | 66x |
backfill_cohorts = backfill_cohorts, |
| 152 | 66x |
data = data, |
| 153 | 66x |
backfill = object@backfill, |
| 154 | 66x |
cohort_size = cohort_size, |
| 155 | 66x |
backfill_patients = backfill_patients, |
| 156 | 66x |
current_args = current_args, |
| 157 | 66x |
truth = truth, |
| 158 | 66x |
truthResponse = truthResponse |
| 159 |
) |
|
| 160 | ||
| 161 | 66x |
data <- enrollment_result$data |
| 162 | 66x |
backfill_cohorts <- enrollment_result$backfill_cohorts |
| 163 | 66x |
backfill_patients <- enrollment_result$backfill_patients |
| 164 |
} |
|
| 165 | ||
| 166 | 177x |
dose_limit <- maxDose(object@increments, data = data) |
| 167 | 177x |
samples <- mcmc( |
| 168 | 177x |
data = data, |
| 169 | 177x |
model = object@model, |
| 170 | 177x |
options = mcmcOptions |
| 171 |
) |
|
| 172 | ||
| 173 | 177x |
dose <- nextBest( |
| 174 | 177x |
object@nextBest, |
| 175 | 177x |
doselimit = dose_limit, |
| 176 | 177x |
samples = samples, |
| 177 | 177x |
model = object@model, |
| 178 | 177x |
data = data |
| 179 | 177x |
)$value |
| 180 | ||
| 181 | 177x |
should_stop <- stopTrial( |
| 182 | 177x |
object@stopping, |
| 183 | 177x |
dose = dose, |
| 184 | 177x |
samples = samples, |
| 185 | 177x |
model = object@model, |
| 186 | 177x |
data = data |
| 187 |
) |
|
| 188 | 177x |
stopit_results <- h_unpack_stopit(should_stop) |
| 189 |
} |
|
| 190 | ||
| 191 | 41x |
fit_model <- fit(object = samples, model = object@model, data = data) |
| 192 | 41x |
target_dose_samples <- dose( |
| 193 | 41x |
mean(object@nextBest@target), |
| 194 | 41x |
model = object@model, |
| 195 | 41x |
samples = samples |
| 196 |
) |
|
| 197 | 41x |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
| 198 | ||
| 199 | 41x |
list( |
| 200 | 41x |
data = data, |
| 201 | 41x |
dose = dose, |
| 202 | 41x |
fit = subset(fit_model, select = c(middle, lower, upper)), |
| 203 | 41x |
stop = attr(should_stop, "message"), |
| 204 | 41x |
report_results = stopit_results, |
| 205 | 41x |
additional_stats = additional_stats |
| 206 |
) |
|
| 207 |
} |
|
| 208 | ||
| 209 | 14x |
result_list <- get_result_list( |
| 210 | 14x |
fun = run_sim, |
| 211 | 14x |
nsim = nsim, |
| 212 | 14x |
vars = c( |
| 213 | 14x |
"sim_seeds", |
| 214 | 14x |
"args", |
| 215 | 14x |
"n_args", |
| 216 | 14x |
"firstSeparate", |
| 217 | 14x |
"truth", |
| 218 | 14x |
"truthResponse", |
| 219 | 14x |
"object", |
| 220 | 14x |
"mcmcOptions" |
| 221 |
), |
|
| 222 | 14x |
parallel = parallel, |
| 223 | 14x |
n_cores = nCores |
| 224 |
) |
|
| 225 | ||
| 226 | 14x |
simulations_output <- h_simulations_output_format(result_list) |
| 227 | ||
| 228 | 14x |
Simulations( |
| 229 | 14x |
data = simulations_output$dataList, |
| 230 | 14x |
doses = simulations_output$recommendedDoses, |
| 231 | 14x |
fit = simulations_output$fitList, |
| 232 | 14x |
stop_report = simulations_output$stop_matrix, |
| 233 | 14x |
stop_reasons = simulations_output$stopReasons, |
| 234 | 14x |
additional_stats = simulations_output$additional_stats, |
| 235 | 14x |
seed = rng_state |
| 236 |
) |
|
| 237 |
} |
|
| 238 |
) |
|
| 239 | ||
| 240 |
## RuleDesign ---- |
|
| 241 | ||
| 242 |
#' Simulate outcomes from a rule-based design |
|
| 243 |
#' |
|
| 244 |
#' @description `r lifecycle::badge("stable")`
|
|
| 245 |
#' |
|
| 246 |
#' @param object the [`RuleDesign`] object we want to simulate data from |
|
| 247 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 248 |
#' @param seed see [set_seed()] |
|
| 249 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 250 |
#' true probability (vector) for toxicity. Additional arguments can be supplied |
|
| 251 |
#' in `args`. |
|
| 252 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The |
|
| 253 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 254 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 255 |
#' simulations. |
|
| 256 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 257 |
#' clusters of the computer? (not default) |
|
| 258 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 259 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 260 |
#' @param ... not used |
|
| 261 |
#' |
|
| 262 |
#' @return an object of class [`GeneralSimulations`] |
|
| 263 |
#' |
|
| 264 |
#' @example examples/design-method-simulate-RuleDesign.R |
|
| 265 |
#' @export |
|
| 266 |
setMethod( |
|
| 267 |
f = "simulate", |
|
| 268 |
signature = signature( |
|
| 269 |
object = "RuleDesign", |
|
| 270 |
nsim = "ANY", |
|
| 271 |
seed = "ANY" |
|
| 272 |
), |
|
| 273 |
definition = function( |
|
| 274 |
object, |
|
| 275 |
nsim = 1L, |
|
| 276 |
seed = NULL, |
|
| 277 |
truth, |
|
| 278 |
args = NULL, |
|
| 279 |
parallel = FALSE, |
|
| 280 |
nCores = min(parallel::detectCores(), 5L), |
|
| 281 |
... |
|
| 282 |
) {
|
|
| 283 | 1x |
nsim <- as.integer(nsim) |
| 284 | 1x |
assert_function(truth) |
| 285 | 1x |
assert_count(nsim, positive = TRUE) |
| 286 | 1x |
assert_flag(parallel) |
| 287 | 1x |
assert_count(nCores, positive = TRUE) |
| 288 | 1x |
assert_class(object, "RuleDesign") |
| 289 | ||
| 290 | 1x |
args <- as.data.frame(args) |
| 291 | 1x |
n_args <- max(nrow(args), 1L) |
| 292 | 1x |
rng_state <- set_seed(seed) |
| 293 | 1x |
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
| 294 | ||
| 295 | 1x |
run_sim <- function(iter_sim) {
|
| 296 | 1x |
set.seed(sim_seeds[iter_sim]) |
| 297 | ||
| 298 | 1x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 299 | ||
| 300 | 1x |
truth_with_args <- function(dose) {
|
| 301 | 12x |
do.call(truth, c(dose, current_args)) |
| 302 |
} |
|
| 303 | ||
| 304 | 1x |
data <- object@data |
| 305 | 1x |
should_stop <- FALSE |
| 306 | 1x |
dose <- object@startingDose |
| 307 | ||
| 308 | 1x |
while (!should_stop) {
|
| 309 | 12x |
prob <- truth_with_args(dose) |
| 310 | 12x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 311 | ||
| 312 | 12x |
dlts <- rbinom(n = cohort_size, size = 1L, prob = prob) |
| 313 | 12x |
data <- update(object = data, x = dose, y = dlts) |
| 314 | ||
| 315 | 12x |
outcome <- nextBest(object@nextBest, data = data) |
| 316 | 12x |
dose <- outcome$value |
| 317 | 12x |
should_stop <- outcome$stopHere |
| 318 |
} |
|
| 319 | ||
| 320 | 1x |
list(data = data, dose = dose) |
| 321 |
} |
|
| 322 | ||
| 323 | 1x |
result_list <- get_result_list( |
| 324 | 1x |
fun = run_sim, |
| 325 | 1x |
nsim = nsim, |
| 326 | 1x |
vars = c( |
| 327 | 1x |
"sim_seeds", |
| 328 | 1x |
"args", |
| 329 | 1x |
"n_args", |
| 330 | 1x |
"truth", |
| 331 | 1x |
"object" |
| 332 |
), |
|
| 333 | 1x |
parallel = parallel, |
| 334 | 1x |
n_cores = nCores |
| 335 |
) |
|
| 336 | ||
| 337 | 1x |
data_list <- lapply(result_list, "[[", "data") |
| 338 | 1x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose")) |
| 339 | ||
| 340 | 1x |
GeneralSimulations( |
| 341 | 1x |
data = data_list, |
| 342 | 1x |
doses = recommended_doses, |
| 343 | 1x |
seed = rng_state |
| 344 |
) |
|
| 345 |
} |
|
| 346 |
) |
|
| 347 | ||
| 348 |
## DualDesign ---- |
|
| 349 | ||
| 350 |
#' Simulate outcomes from a dual-endpoint design |
|
| 351 |
#' |
|
| 352 |
#' @description `r lifecycle::badge("stable")`
|
|
| 353 |
#' |
|
| 354 |
#' @param object the [`DualDesign`] object we want to simulate data from |
|
| 355 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 356 |
#' @param seed see [set_seed()] |
|
| 357 |
#' @param trueTox (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 358 |
#' true probability (vector) for toxicity. Additional arguments can be supplied |
|
| 359 |
#' in `args`. |
|
| 360 |
#' @param trueBiomarker (`function`)\cr a function which takes as input a dose (vector) and |
|
| 361 |
#' returns the true biomarker level (vector). Additional arguments can be |
|
| 362 |
#' supplied in `args`. |
|
| 363 |
#' @param args (`data.frame`)\cr data frame with arguments for the `trueTox` and |
|
| 364 |
#' `trueBiomarker` function. The column names correspond to the argument |
|
| 365 |
#' names, the rows to the values of the arguments. The rows are appropriately |
|
| 366 |
#' recycled in the `nsim` simulations. |
|
| 367 |
#' @param sigma2W (`number`)\cr variance for the biomarker measurements |
|
| 368 |
#' @param rho (`number`)\cr correlation between toxicity and biomarker measurements (default: 0) |
|
| 369 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 370 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 371 |
#' in this patient. |
|
| 372 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 373 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 374 |
#' the standard options are used |
|
| 375 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 376 |
#' clusters of the computer? (not default) |
|
| 377 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 378 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 379 |
#' @param ... not used |
|
| 380 |
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the |
|
| 381 |
#' vector of posterior MTD samples. Each list element must therefore accept |
|
| 382 |
#' one and only one argument, which is a numeric vector, and return a number. |
|
| 383 |
#' |
|
| 384 |
#' @return an object of class [`DualSimulations`] |
|
| 385 |
#' |
|
| 386 |
#' @note Backfill cohorts are not yet implemented and therefore will lead to an error if used |
|
| 387 |
#' in the `DualDesign` object. |
|
| 388 |
#' |
|
| 389 |
#' @example examples/design-method-simulate-DualDesign.R |
|
| 390 |
#' @importFrom mvtnorm rmvnorm |
|
| 391 |
#' @export |
|
| 392 |
setMethod( |
|
| 393 |
f = "simulate", |
|
| 394 |
signature = signature(object = "DualDesign"), |
|
| 395 |
definition = function( |
|
| 396 |
object, |
|
| 397 |
nsim = 1L, |
|
| 398 |
seed = NULL, |
|
| 399 |
trueTox, |
|
| 400 |
trueBiomarker, |
|
| 401 |
args = NULL, |
|
| 402 |
sigma2W, |
|
| 403 |
rho = 0, |
|
| 404 |
firstSeparate = FALSE, |
|
| 405 |
mcmcOptions = McmcOptions(), |
|
| 406 |
parallel = FALSE, |
|
| 407 |
nCores = min(parallel::detectCores(), 5), |
|
| 408 |
derive = list(), |
|
| 409 |
... |
|
| 410 |
) {
|
|
| 411 | 6x |
nsim <- as.integer(nsim) |
| 412 | 6x |
assert_function(trueTox) |
| 413 | 6x |
assert_function(trueBiomarker) |
| 414 | 6x |
assert_number(sigma2W, lower = 0) |
| 415 | 6x |
assert_number(rho, lower = -1, upper = 1) |
| 416 | 6x |
assert_flag(firstSeparate) |
| 417 | 6x |
assert_count(nsim, positive = TRUE) |
| 418 | 6x |
assert_flag(parallel) |
| 419 | 6x |
assert_count(nCores, positive = TRUE) |
| 420 | 6x |
assert_class(object, "DualDesign") |
| 421 | 6x |
assert_list(derive) |
| 422 | 6x |
assert_class(object@backfill@opening, "OpeningNone") |
| 423 | ||
| 424 | 6x |
args <- as.data.frame(args) |
| 425 | 6x |
n_args <- max(nrow(args), 1L) |
| 426 | ||
| 427 | 6x |
tox_arg_names <- names(formals(trueTox))[-1] |
| 428 | 6x |
biomarker_arg_names <- names(formals(trueBiomarker))[-1] |
| 429 | ||
| 430 | 6x |
covariance_matrix <- matrix( |
| 431 | 6x |
c( |
| 432 | 6x |
sigma2W, |
| 433 | 6x |
sqrt(sigma2W) * rho, |
| 434 | 6x |
sqrt(sigma2W) * rho, |
| 435 | 6x |
1 |
| 436 |
), |
|
| 437 | 6x |
nrow = 2, |
| 438 | 6x |
byrow = TRUE |
| 439 |
) |
|
| 440 | ||
| 441 | 6x |
rng_state <- set_seed(seed) |
| 442 | 6x |
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
| 443 | ||
| 444 | 6x |
run_sim <- function(iter_sim) {
|
| 445 | 6x |
set.seed(sim_seeds[iter_sim]) |
| 446 | ||
| 447 | 6x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 448 | ||
| 449 | 6x |
tox_with_args <- function(dose) {
|
| 450 | 20x |
do.call( |
| 451 | 20x |
trueTox, |
| 452 | 20x |
c(dose, as.list(current_args)[tox_arg_names]) |
| 453 |
) |
|
| 454 |
} |
|
| 455 | ||
| 456 | 6x |
biomarker_with_args <- function(dose) {
|
| 457 | 20x |
do.call( |
| 458 | 20x |
trueBiomarker, |
| 459 | 20x |
c(dose, as.list(current_args)[biomarker_arg_names]) |
| 460 |
) |
|
| 461 |
} |
|
| 462 | ||
| 463 | 6x |
data <- object@data |
| 464 | 6x |
should_stop <- FALSE |
| 465 | 6x |
dose <- object@startingDose |
| 466 | ||
| 467 | 6x |
prob_placebo <- NULL |
| 468 | 6x |
mean_z_placebo <- NULL |
| 469 | 6x |
mean_biomarker_placebo <- NULL |
| 470 | ||
| 471 | 6x |
if (data@placebo) {
|
| 472 | 2x |
prob_placebo <- tox_with_args(object@data@doseGrid[1]) |
| 473 | 2x |
mean_z_placebo <- qlogis(prob_placebo) |
| 474 | 2x |
mean_biomarker_placebo <- biomarker_with_args(object@data@doseGrid[1]) |
| 475 |
} |
|
| 476 | ||
| 477 | 6x |
while (!should_stop) {
|
| 478 | 18x |
prob <- tox_with_args(dose) |
| 479 | 18x |
mean_z <- qlogis(prob) |
| 480 | 18x |
mean_biomarker <- biomarker_with_args(dose) |
| 481 | 18x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 482 | ||
| 483 | 18x |
cohort_size_placebo <- NULL |
| 484 | 18x |
if (data@placebo) {
|
| 485 | 2x |
cohort_size_placebo <- size( |
| 486 | 2x |
object@pl_cohort_size, |
| 487 | 2x |
dose = dose, |
| 488 | 2x |
data = data |
| 489 |
) |
|
| 490 |
} |
|
| 491 | ||
| 492 | 18x |
response_data <- if (firstSeparate && (cohort_size > 1L)) {
|
| 493 | 8x |
first_patient <- mvtnorm::rmvnorm( |
| 494 | 8x |
n = 1, |
| 495 | 8x |
mean = c(mean_biomarker, mean_z), |
| 496 | 8x |
sigma = covariance_matrix |
| 497 |
) |
|
| 498 | ||
| 499 | 8x |
first_patient_placebo <- NULL |
| 500 | 8x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 501 | ! |
first_patient_placebo <- mvtnorm::rmvnorm( |
| 502 | ! |
n = 1, |
| 503 | ! |
mean = c(mean_biomarker_placebo, mean_z_placebo), |
| 504 | ! |
sigma = covariance_matrix |
| 505 |
) |
|
| 506 |
} |
|
| 507 | ||
| 508 | 8x |
if (first_patient[, 2] < 0) {
|
| 509 | 8x |
remaining_patients <- mvtnorm::rmvnorm( |
| 510 | 8x |
n = cohort_size - 1, |
| 511 | 8x |
mean = c(mean_biomarker, mean_z), |
| 512 | 8x |
sigma = covariance_matrix |
| 513 |
) |
|
| 514 | 8x |
first_patient <- rbind(first_patient, remaining_patients) |
| 515 | ||
| 516 | 8x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 517 | ! |
remaining_patients_placebo <- mvtnorm::rmvnorm( |
| 518 | ! |
n = cohort_size_placebo, |
| 519 | ! |
mean = c(mean_biomarker_placebo, mean_z_placebo), |
| 520 | ! |
sigma = covariance_matrix |
| 521 |
) |
|
| 522 | ! |
first_patient_placebo <- rbind( |
| 523 | ! |
first_patient_placebo, |
| 524 | ! |
remaining_patients_placebo |
| 525 |
) |
|
| 526 |
} |
|
| 527 |
} |
|
| 528 | ||
| 529 | 8x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 530 | ! |
list(active = first_patient, placebo = first_patient_placebo) |
| 531 |
} else {
|
|
| 532 | 8x |
list(active = first_patient) |
| 533 |
} |
|
| 534 |
} else {
|
|
| 535 | 10x |
active_responses <- mvtnorm::rmvnorm( |
| 536 | 10x |
n = cohort_size, |
| 537 | 10x |
mean = c(mean_biomarker, mean_z), |
| 538 | 10x |
sigma = covariance_matrix |
| 539 |
) |
|
| 540 | ||
| 541 | 10x |
placebo_responses <- NULL |
| 542 | 10x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 543 | 2x |
placebo_responses <- mvtnorm::rmvnorm( |
| 544 | 2x |
n = cohort_size_placebo, |
| 545 | 2x |
mean = c(mean_biomarker_placebo, mean_z_placebo), |
| 546 | 2x |
sigma = covariance_matrix |
| 547 |
) |
|
| 548 |
} |
|
| 549 | ||
| 550 | 10x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 551 | 2x |
list(active = active_responses, placebo = placebo_responses) |
| 552 |
} else {
|
|
| 553 | 8x |
list(active = active_responses) |
| 554 |
} |
|
| 555 |
} |
|
| 556 | ||
| 557 | 18x |
biomarkers <- response_data$active[, 1] |
| 558 | 18x |
dlts <- as.integer(response_data$active[, 2] > 0) |
| 559 | ||
| 560 | 18x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 561 | 2x |
biomarkers_placebo <- response_data$placebo[, 1] |
| 562 | 2x |
dlts_placebo <- as.integer(response_data$placebo[, 2] > 0) |
| 563 | ||
| 564 | 2x |
data <- update( |
| 565 | 2x |
object = data, |
| 566 | 2x |
x = object@data@doseGrid[1], |
| 567 | 2x |
y = dlts_placebo, |
| 568 | 2x |
w = biomarkers_placebo, |
| 569 | 2x |
check = FALSE |
| 570 |
) |
|
| 571 | ||
| 572 | 2x |
data <- update( |
| 573 | 2x |
object = data, |
| 574 | 2x |
x = dose, |
| 575 | 2x |
y = dlts, |
| 576 | 2x |
w = biomarkers, |
| 577 | 2x |
new_cohort = FALSE |
| 578 |
) |
|
| 579 |
} else {
|
|
| 580 | 16x |
data <- update( |
| 581 | 16x |
object = data, |
| 582 | 16x |
x = dose, |
| 583 | 16x |
y = dlts, |
| 584 | 16x |
w = biomarkers |
| 585 |
) |
|
| 586 |
} |
|
| 587 | ||
| 588 | 18x |
dose_limit <- maxDose(object@increments, data = data) |
| 589 | 18x |
samples <- mcmc( |
| 590 | 18x |
data = data, |
| 591 | 18x |
model = object@model, |
| 592 | 18x |
options = mcmcOptions |
| 593 |
) |
|
| 594 | ||
| 595 | 18x |
dose <- nextBest( |
| 596 | 18x |
object@nextBest, |
| 597 | 18x |
doselimit = dose_limit, |
| 598 | 18x |
samples = samples, |
| 599 | 18x |
model = object@model, |
| 600 | 18x |
data = data |
| 601 | 18x |
)$value |
| 602 | ||
| 603 | 18x |
should_stop <- stopTrial( |
| 604 | 18x |
object@stopping, |
| 605 | 18x |
dose = dose, |
| 606 | 18x |
samples = samples, |
| 607 | 18x |
model = object@model, |
| 608 | 18x |
data = data |
| 609 |
) |
|
| 610 | 18x |
stopit_results <- h_unpack_stopit(should_stop) |
| 611 |
} |
|
| 612 | ||
| 613 | 6x |
fit_model <- fit(object = samples, model = object@model, data = data) |
| 614 | ||
| 615 | 6x |
target_dose_samples <- dose( |
| 616 | 6x |
mean(object@nextBest@target), |
| 617 | 6x |
model = object@model, |
| 618 | 6x |
samples = samples |
| 619 |
) |
|
| 620 | ||
| 621 | 6x |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
| 622 | ||
| 623 | 6x |
list( |
| 624 | 6x |
data = data, |
| 625 | 6x |
dose = dose, |
| 626 | 6x |
fitTox = subset(fit_model, select = c(middle, lower, upper)), |
| 627 | 6x |
fit_biomarker = subset( |
| 628 | 6x |
fit_model, |
| 629 | 6x |
select = c(middleBiomarker, lowerBiomarker, upperBiomarker) |
| 630 |
), |
|
| 631 | 6x |
rho_est = median(samples@data$rho), |
| 632 | 6x |
sigma2w_est = median(1 / samples@data$precW), |
| 633 | 6x |
stop = attr(should_stop, "message"), |
| 634 | 6x |
additional_stats = additional_stats, |
| 635 | 6x |
report_results = stopit_results |
| 636 |
) |
|
| 637 |
} |
|
| 638 | ||
| 639 | 6x |
result_list <- get_result_list( |
| 640 | 6x |
fun = run_sim, |
| 641 | 6x |
nsim = nsim, |
| 642 | 6x |
vars = c( |
| 643 | 6x |
"sim_seeds", |
| 644 | 6x |
"args", |
| 645 | 6x |
"n_args", |
| 646 | 6x |
"firstSeparate", |
| 647 | 6x |
"trueTox", |
| 648 | 6x |
"trueBiomarker", |
| 649 | 6x |
"covariance_matrix", |
| 650 | 6x |
"object", |
| 651 | 6x |
"mcmcOptions" |
| 652 |
), |
|
| 653 | 6x |
parallel = parallel, |
| 654 | 6x |
n_cores = nCores |
| 655 |
) |
|
| 656 | ||
| 657 | 6x |
data_list <- lapply(result_list, "[[", "data") |
| 658 | 6x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose")) |
| 659 | 6x |
rho_estimates <- as.numeric(sapply(result_list, "[[", "rho_est")) |
| 660 | 6x |
sigma2w_estimates <- as.numeric(sapply(result_list, "[[", "sigma2w_est")) |
| 661 | 6x |
fit_tox_list <- lapply(result_list, "[[", "fitTox") |
| 662 | 6x |
fit_biomarker_list <- lapply(result_list, "[[", "fit_biomarker") |
| 663 | 6x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 664 | 6x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 665 | 6x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 666 | 6x |
additional_stats <- lapply(result_list, "[[", "additional_stats") |
| 667 | ||
| 668 | 6x |
DualSimulations( |
| 669 | 6x |
data = data_list, |
| 670 | 6x |
doses = recommended_doses, |
| 671 | 6x |
rho_est = rho_estimates, |
| 672 | 6x |
sigma2w_est = sigma2w_estimates, |
| 673 | 6x |
fit = fit_tox_list, |
| 674 | 6x |
fit_biomarker = fit_biomarker_list, |
| 675 | 6x |
stop_report = stop_report, |
| 676 | 6x |
stop_reasons = stop_reasons, |
| 677 | 6x |
additional_stats = additional_stats, |
| 678 | 6x |
seed = rng_state |
| 679 |
) |
|
| 680 |
} |
|
| 681 |
) |
|
| 682 | ||
| 683 |
## TDsamplesDesign ---- |
|
| 684 | ||
| 685 |
#' Simulate dose escalation procedure using DLE responses only with DLE samples |
|
| 686 |
#' |
|
| 687 |
#' @description `r lifecycle::badge("stable")`
|
|
| 688 |
#' |
|
| 689 |
#' This is a method to simulate dose escalation procedure only using the DLE responses. |
|
| 690 |
#' This is a method based on the [`TDsamplesDesign`] where model used are of |
|
| 691 |
#' [`ModelTox`] class object DLE samples are also used. |
|
| 692 |
#' |
|
| 693 |
#' @param object the [`TDsamplesDesign`] object we want to simulate the data from |
|
| 694 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 695 |
#' @param seed see [set_seed()] |
|
| 696 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the true probability |
|
| 697 |
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`. |
|
| 698 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The |
|
| 699 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 700 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 701 |
#' simulations. In order to produce outcomes from the posterior predictive |
|
| 702 |
#' distribution, e.g, pass an `object` that contains the data observed so |
|
| 703 |
#' far, `truth` contains the `prob` function from the model in |
|
| 704 |
#' `object`, and `args` contains posterior samples from the model. |
|
| 705 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 706 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 707 |
#' in this patient. |
|
| 708 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 709 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 710 |
#' the standard options are used |
|
| 711 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 712 |
#' clusters of the computer? (not default) |
|
| 713 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 714 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 715 |
#' @param ... not used |
|
| 716 |
#' |
|
| 717 |
#' @return an object of class [`PseudoSimulations`] |
|
| 718 |
#' |
|
| 719 |
#' @example examples/design-method-simulateTDsamplesDesign.R |
|
| 720 |
#' @export |
|
| 721 |
setMethod( |
|
| 722 |
f = "simulate", |
|
| 723 |
signature = signature( |
|
| 724 |
object = "TDsamplesDesign", |
|
| 725 |
nsim = "ANY", |
|
| 726 |
seed = "ANY" |
|
| 727 |
), |
|
| 728 |
definition = function( |
|
| 729 |
object, |
|
| 730 |
nsim = 1L, |
|
| 731 |
seed = NULL, |
|
| 732 |
truth, |
|
| 733 |
args = NULL, |
|
| 734 |
firstSeparate = FALSE, |
|
| 735 |
mcmcOptions = McmcOptions(), |
|
| 736 |
parallel = FALSE, |
|
| 737 |
nCores = min(parallel::detectCores(), 5L), |
|
| 738 |
... |
|
| 739 |
) {
|
|
| 740 | 2x |
nsim <- as.integer(nsim) |
| 741 | 2x |
assert_function(truth) |
| 742 | 2x |
assert_flag(firstSeparate) |
| 743 | 2x |
assert_count(nsim, positive = TRUE) |
| 744 | 2x |
assert_flag(parallel) |
| 745 | 2x |
assert_count(nCores, positive = TRUE) |
| 746 | 2x |
assert_class(object, "TDsamplesDesign") |
| 747 | ||
| 748 | 2x |
args <- as.data.frame(args) |
| 749 | 2x |
n_args <- max(nrow(args), 1L) |
| 750 | 2x |
rng_state <- set_seed(seed) |
| 751 | ||
| 752 |
# Keep original seed generation for snapshot test compatibility |
|
| 753 | 2x |
sim_seeds <- sample(x = seq_len(1e5), size = nsim) |
| 754 | ||
| 755 | 2x |
run_sim <- function(iter_sim) {
|
| 756 | 2x |
set.seed(sim_seeds[iter_sim]) |
| 757 | ||
| 758 | 2x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 759 | ||
| 760 | 2x |
truth_with_args <- function(dose) {
|
| 761 | 22x |
do.call(truth, c(dose, current_args)) |
| 762 |
} |
|
| 763 | ||
| 764 | 2x |
data <- object@data |
| 765 | 2x |
prob_placebo <- NULL |
| 766 | ||
| 767 | 2x |
if (data@placebo) {
|
| 768 | 1x |
prob_placebo <- truth_with_args(object@data@doseGrid[1]) |
| 769 |
} |
|
| 770 | ||
| 771 | 2x |
should_stop <- FALSE |
| 772 | 2x |
dose <- object@startingDose |
| 773 | ||
| 774 | 2x |
while (!should_stop) {
|
| 775 | 21x |
prob <- truth_with_args(dose) |
| 776 | 21x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 777 | ||
| 778 | 21x |
cohort_size_placebo <- NULL |
| 779 | 21x |
if (data@placebo) {
|
| 780 | 9x |
cohort_size_placebo <- size( |
| 781 | 9x |
object@pl_cohort_size, |
| 782 | 9x |
dose = dose, |
| 783 | 9x |
data = data |
| 784 |
) |
|
| 785 |
} |
|
| 786 | ||
| 787 | 21x |
dlts <- if (firstSeparate && (cohort_size > 1L)) {
|
| 788 | ! |
first_dlt <- rbinom(n = 1L, size = 1L, prob = prob) |
| 789 | ||
| 790 | ! |
dlts_placebo_first <- NULL |
| 791 | ! |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 792 | ! |
dlts_placebo_first <- rbinom(n = 1L, size = 1L, prob = prob_placebo) |
| 793 |
} |
|
| 794 | ||
| 795 | ! |
if (first_dlt == 0) {
|
| 796 | ! |
remaining_dlts <- rbinom( |
| 797 | ! |
n = cohort_size - 1L, |
| 798 | ! |
size = 1L, |
| 799 | ! |
prob = prob |
| 800 |
) |
|
| 801 | ! |
c(first_dlt, remaining_dlts) |
| 802 |
} else {
|
|
| 803 | ! |
first_dlt |
| 804 |
} |
|
| 805 |
} else {
|
|
| 806 | 21x |
rbinom(n = cohort_size, size = 1L, prob = prob) |
| 807 |
} |
|
| 808 | ||
| 809 | 21x |
dlts_placebo <- NULL |
| 810 | 21x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 811 | 9x |
if (firstSeparate && (cohort_size > 1L) && length(dlts) == 1) {
|
| 812 | ! |
dlts_placebo <- dlts_placebo_first |
| 813 |
} else {
|
|
| 814 | 9x |
dlts_placebo <- if (firstSeparate && (cohort_size > 1L)) {
|
| 815 | ! |
c( |
| 816 | ! |
dlts_placebo_first, |
| 817 | ! |
rbinom( |
| 818 | ! |
n = cohort_size_placebo, |
| 819 | ! |
size = 1L, |
| 820 | ! |
prob = prob_placebo |
| 821 |
) |
|
| 822 |
) |
|
| 823 |
} else {
|
|
| 824 | 9x |
rbinom(n = cohort_size_placebo, size = 1L, prob = prob_placebo) |
| 825 |
} |
|
| 826 |
} |
|
| 827 |
} |
|
| 828 | ||
| 829 | 21x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 830 | 9x |
data <- update(object = data, x = dose, y = dlts) |
| 831 | 9x |
data <- update( |
| 832 | 9x |
object = data, |
| 833 | 9x |
x = object@data@doseGrid[1], |
| 834 | 9x |
y = dlts_placebo, |
| 835 | 9x |
new_cohort = FALSE |
| 836 |
) |
|
| 837 |
} else {
|
|
| 838 | 12x |
data <- update(object = data, x = dose, y = dlts) |
| 839 |
} |
|
| 840 | ||
| 841 | 21x |
model <- update(object@model, data = data) |
| 842 | 21x |
dose_limit <- maxDose(object@increments, data = data) |
| 843 | 21x |
samples <- mcmc(data = data, model = model, options = mcmcOptions) |
| 844 | ||
| 845 | 21x |
next_best_result <- nextBest( |
| 846 | 21x |
object@nextBest, |
| 847 | 21x |
doselimit = dose_limit, |
| 848 | 21x |
samples = samples, |
| 849 | 21x |
model = model, |
| 850 | 21x |
data = data, |
| 851 | 21x |
in_sim = TRUE |
| 852 |
) |
|
| 853 | ||
| 854 | 21x |
dose <- next_best_result$next_dose_drt |
| 855 | 21x |
td_target_during_trial <- next_best_result$dose_target_drt |
| 856 | 21x |
td_target_end_of_trial <- next_best_result$dose_target_eot |
| 857 | 21x |
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot |
| 858 | 21x |
ci_tdeot <- list( |
| 859 | 21x |
lower = next_best_result$ci_dose_target_eot[1], |
| 860 | 21x |
upper = next_best_result$ci_dose_target_eot[2] |
| 861 |
) |
|
| 862 | 21x |
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot |
| 863 | ||
| 864 | 21x |
should_stop <- stopTrial( |
| 865 | 21x |
object@stopping, |
| 866 | 21x |
dose = dose, |
| 867 | 21x |
samples = samples, |
| 868 | 21x |
model = model, |
| 869 | 21x |
data = data |
| 870 |
) |
|
| 871 | 21x |
stopit_results <- h_unpack_stopit(should_stop) |
| 872 |
} |
|
| 873 | ||
| 874 | 2x |
fit_model <- fit(object = samples, model = model, data = data) |
| 875 | ||
| 876 | 2x |
list( |
| 877 | 2x |
data = data, |
| 878 | 2x |
dose = dose, |
| 879 | 2x |
TDtargetDuringTrial = td_target_during_trial, |
| 880 | 2x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 881 | 2x |
TDtargetEndOfTrialatdoseGrid = td_target_end_of_trial_at_dose_grid, |
| 882 | 2x |
TDtargetDuringTrialatdoseGrid = dose, |
| 883 | 2x |
CITDEOT = ci_tdeot, |
| 884 | 2x |
ratioTDEOT = ratio_tdeot, |
| 885 | 2x |
fit = subset(fit_model, select = c(middle, lower, upper)), |
| 886 | 2x |
stop = attr(should_stop, "message"), |
| 887 | 2x |
report_results = stopit_results |
| 888 |
) |
|
| 889 |
} |
|
| 890 | ||
| 891 | 2x |
result_list <- get_result_list( |
| 892 | 2x |
fun = run_sim, |
| 893 | 2x |
nsim = nsim, |
| 894 | 2x |
vars = c( |
| 895 | 2x |
"sim_seeds", |
| 896 | 2x |
"args", |
| 897 | 2x |
"n_args", |
| 898 | 2x |
"firstSeparate", |
| 899 | 2x |
"truth", |
| 900 | 2x |
"object", |
| 901 | 2x |
"mcmcOptions" |
| 902 |
), |
|
| 903 | 2x |
parallel = parallel, |
| 904 | 2x |
n_cores = nCores |
| 905 |
) |
|
| 906 | ||
| 907 | 2x |
data_list <- lapply(result_list, "[[", "data") |
| 908 | ||
| 909 | 2x |
td_target_during_trial_list <- as.numeric(sapply( |
| 910 | 2x |
result_list, |
| 911 |
"[[", |
|
| 912 | 2x |
"TDtargetDuringTrial" |
| 913 |
)) |
|
| 914 | ||
| 915 | 2x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 916 | 2x |
result_list, |
| 917 |
"[[", |
|
| 918 | 2x |
"TDtargetEndOfTrial" |
| 919 |
)) |
|
| 920 | ||
| 921 | 2x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 922 | 2x |
result_list, |
| 923 |
"[[", |
|
| 924 | 2x |
"TDtargetDuringTrialatdoseGrid" |
| 925 |
)) |
|
| 926 | ||
| 927 | 2x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 928 | 2x |
result_list, |
| 929 |
"[[", |
|
| 930 | 2x |
"TDtargetEndOfTrialatdoseGrid" |
| 931 |
)) |
|
| 932 | ||
| 933 | 2x |
recommended_doses <- as.numeric(sapply( |
| 934 | 2x |
result_list, |
| 935 |
"[[", |
|
| 936 | 2x |
"TDtargetEndOfTrialatdoseGrid" |
| 937 |
)) |
|
| 938 | ||
| 939 | 2x |
ci_list <- lapply(result_list, "[[", "CITDEOT") |
| 940 | 2x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 941 | 2x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 942 | 2x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 943 | 2x |
fit_list <- lapply(result_list, "[[", "fit") |
| 944 | 2x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 945 | ||
| 946 | 2x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 947 | 2x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 948 | ||
| 949 | 2x |
PseudoSimulations( |
| 950 | 2x |
data = data_list, |
| 951 | 2x |
doses = recommended_doses, |
| 952 | 2x |
fit = fit_list, |
| 953 | 2x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 954 | 2x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 955 | 2x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 956 | 2x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 957 | 2x |
final_cis = ci_list, |
| 958 | 2x |
final_ratios = ratio_list, |
| 959 | 2x |
final_tdeot_cis = ci_tdeot_list, |
| 960 | 2x |
final_tdeot_ratios = ratio_tdeot_list, |
| 961 | 2x |
stop_reasons = stop_reasons, |
| 962 | 2x |
stop_report = stop_report, |
| 963 | 2x |
seed = rng_state |
| 964 |
) |
|
| 965 |
} |
|
| 966 |
) |
|
| 967 | ||
| 968 |
## TDDesign ---- |
|
| 969 | ||
| 970 |
#' Simulate dose escalation procedure using DLE responses only without samples |
|
| 971 |
#' |
|
| 972 |
#' @description `r lifecycle::badge("stable")`
|
|
| 973 |
#' |
|
| 974 |
#' This is a method to simulate dose escalation procedure only using the DLE responses. |
|
| 975 |
#' This is a method based on the [`TDDesign`] where model used are of |
|
| 976 |
#' [`ModelTox`] class object and no samples are involved. |
|
| 977 |
#' |
|
| 978 |
#' @param object the [`TDDesign`] object we want to simulate the data from |
|
| 979 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 980 |
#' @param seed see [set_seed()] |
|
| 981 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the true probability |
|
| 982 |
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`. |
|
| 983 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The |
|
| 984 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 985 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 986 |
#' simulations. In order to produce outcomes from the posterior predictive |
|
| 987 |
#' distribution, e.g, pass an `object` that contains the data observed so |
|
| 988 |
#' far, `truth` contains the `prob` function from the model in |
|
| 989 |
#' `object`, and `args` contains posterior samples from the model. |
|
| 990 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 991 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 992 |
#' in this patient. |
|
| 993 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 994 |
#' clusters of the computer? (not default) |
|
| 995 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 996 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 997 |
#' @param ... not used |
|
| 998 |
#' |
|
| 999 |
#' @return an object of class [`PseudoSimulations`] |
|
| 1000 |
#' |
|
| 1001 |
#' @example examples/design-method-simulateTDDesign.R |
|
| 1002 |
#' @export |
|
| 1003 |
setMethod( |
|
| 1004 |
f = "simulate", |
|
| 1005 |
signature = signature( |
|
| 1006 |
object = "TDDesign", |
|
| 1007 |
nsim = "ANY", |
|
| 1008 |
seed = "ANY" |
|
| 1009 |
), |
|
| 1010 |
definition = function( |
|
| 1011 |
object, |
|
| 1012 |
nsim = 1L, |
|
| 1013 |
seed = NULL, |
|
| 1014 |
truth, |
|
| 1015 |
args = NULL, |
|
| 1016 |
firstSeparate = FALSE, |
|
| 1017 |
parallel = FALSE, |
|
| 1018 |
nCores = min(parallel::detectCores(), 5L), |
|
| 1019 |
... |
|
| 1020 |
) {
|
|
| 1021 | 3x |
nsim <- as.integer(nsim) |
| 1022 | 3x |
assert_function(truth) |
| 1023 | 3x |
assert_flag(firstSeparate) |
| 1024 | 3x |
assert_count(nsim, positive = TRUE) |
| 1025 | 3x |
assert_flag(parallel) |
| 1026 | 3x |
assert_count(nCores, positive = TRUE) |
| 1027 | 3x |
assert_class(object, "TDDesign") |
| 1028 | ||
| 1029 | 3x |
args <- as.data.frame(args) |
| 1030 | 3x |
n_args <- max(nrow(args), 1L) |
| 1031 | 3x |
rng_state <- set_seed(seed) |
| 1032 | ||
| 1033 |
# Keep original seed generation for snapshot test compatibility |
|
| 1034 | 3x |
sim_seeds <- sample(x = seq_len(1e5), size = nsim) |
| 1035 | ||
| 1036 | 3x |
run_sim <- function(iter_sim) {
|
| 1037 | 4x |
set.seed(sim_seeds[iter_sim]) |
| 1038 | ||
| 1039 | 4x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 1040 | ||
| 1041 | 4x |
truth_with_args <- function(dose) {
|
| 1042 | 28x |
do.call(truth, c(dose, current_args)) |
| 1043 |
} |
|
| 1044 | ||
| 1045 | 4x |
data <- object@data |
| 1046 | 4x |
prob_placebo <- NULL |
| 1047 | ||
| 1048 | 4x |
if (data@placebo) {
|
| 1049 | 1x |
prob_placebo <- truth_with_args(object@data@doseGrid[1]) |
| 1050 |
} |
|
| 1051 | ||
| 1052 | 4x |
should_stop <- FALSE |
| 1053 | 4x |
dose <- object@startingDose |
| 1054 | ||
| 1055 | 4x |
while (!should_stop) {
|
| 1056 | 27x |
prob <- truth_with_args(dose) |
| 1057 | 27x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 1058 | ||
| 1059 | 27x |
cohort_size_placebo <- NULL |
| 1060 | 27x |
if (data@placebo) {
|
| 1061 | 11x |
cohort_size_placebo <- size( |
| 1062 | 11x |
object@pl_cohort_size, |
| 1063 | 11x |
dose = dose, |
| 1064 | 11x |
data = data |
| 1065 |
) |
|
| 1066 |
} |
|
| 1067 | ||
| 1068 | 27x |
dlts <- if (firstSeparate && (cohort_size > 1L)) {
|
| 1069 | 11x |
first_dlt <- rbinom(n = 1L, size = 1L, prob = prob) |
| 1070 | ||
| 1071 | 11x |
dlts_placebo_first <- NULL |
| 1072 | 11x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1073 | 11x |
dlts_placebo_first <- rbinom(n = 1L, size = 1L, prob = prob_placebo) |
| 1074 |
} |
|
| 1075 | ||
| 1076 | 11x |
if (first_dlt == 0) {
|
| 1077 | 7x |
remaining_dlts <- rbinom( |
| 1078 | 7x |
n = cohort_size - 1L, |
| 1079 | 7x |
size = 1L, |
| 1080 | 7x |
prob = prob |
| 1081 |
) |
|
| 1082 | 7x |
c(first_dlt, remaining_dlts) |
| 1083 |
} else {
|
|
| 1084 | 4x |
first_dlt |
| 1085 |
} |
|
| 1086 |
} else {
|
|
| 1087 | 16x |
rbinom(n = cohort_size, size = 1L, prob = prob) |
| 1088 |
} |
|
| 1089 | ||
| 1090 | 27x |
dlts_placebo <- NULL |
| 1091 | 27x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1092 | 11x |
if (firstSeparate && (cohort_size > 1L) && length(dlts) == 1) {
|
| 1093 | 4x |
dlts_placebo <- dlts_placebo_first |
| 1094 |
} else {
|
|
| 1095 | 7x |
dlts_placebo <- rbinom( |
| 1096 | 7x |
n = cohort_size_placebo, |
| 1097 | 7x |
size = 1L, |
| 1098 | 7x |
prob = prob_placebo |
| 1099 |
) |
|
| 1100 |
} |
|
| 1101 |
} |
|
| 1102 | ||
| 1103 | 27x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1104 | 11x |
data <- update( |
| 1105 | 11x |
object = data, |
| 1106 | 11x |
x = object@data@doseGrid[1], |
| 1107 | 11x |
y = dlts_placebo |
| 1108 |
) |
|
| 1109 | 11x |
data <- update( |
| 1110 | 11x |
object = data, |
| 1111 | 11x |
x = dose, |
| 1112 | 11x |
y = dlts, |
| 1113 | 11x |
new_cohort = FALSE |
| 1114 |
) |
|
| 1115 |
} else {
|
|
| 1116 | 16x |
data <- update(object = data, x = dose, y = dlts) |
| 1117 |
} |
|
| 1118 | ||
| 1119 | 27x |
model <- update(object@model, data = data) |
| 1120 | 27x |
dose_limit <- maxDose(object@increments, data = data) |
| 1121 | ||
| 1122 | 27x |
next_best_result <- nextBest( |
| 1123 | 27x |
object@nextBest, |
| 1124 | 27x |
doselimit = dose_limit, |
| 1125 | 27x |
model = model, |
| 1126 | 27x |
data = data, |
| 1127 | 27x |
in_sim = TRUE |
| 1128 |
) |
|
| 1129 | ||
| 1130 | 27x |
dose <- next_best_result$next_dose_drt |
| 1131 | 27x |
td_target_during_trial <- next_best_result$dose_target_drt |
| 1132 | 27x |
td_target_end_of_trial <- next_best_result$dose_target_eot |
| 1133 | 27x |
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot |
| 1134 | 27x |
ci_tdeot <- list( |
| 1135 | 27x |
lower = next_best_result$ci_dose_target_eot[1], |
| 1136 | 27x |
upper = next_best_result$ci_dose_target_eot[2] |
| 1137 |
) |
|
| 1138 | 27x |
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot |
| 1139 | ||
| 1140 | 27x |
should_stop <- stopTrial( |
| 1141 | 27x |
object@stopping, |
| 1142 | 27x |
dose = dose, |
| 1143 | 27x |
model = model, |
| 1144 | 27x |
data = data |
| 1145 |
) |
|
| 1146 | 27x |
stopit_results <- h_unpack_stopit(should_stop) |
| 1147 |
} |
|
| 1148 | ||
| 1149 | 4x |
prob_fun <- probFunction( |
| 1150 | 4x |
model, |
| 1151 | 4x |
phi1 = model@phi1, |
| 1152 | 4x |
phi2 = model@phi2 |
| 1153 |
) |
|
| 1154 | 4x |
fit_model <- list( |
| 1155 | 4x |
phi1 = model@phi1, |
| 1156 | 4x |
phi2 = model@phi2, |
| 1157 | 4x |
probDLE = prob_fun(object@data@doseGrid) |
| 1158 |
) |
|
| 1159 | ||
| 1160 | 4x |
list( |
| 1161 | 4x |
data = data, |
| 1162 | 4x |
dose = dose, |
| 1163 | 4x |
TDtargetDuringTrial = td_target_during_trial, |
| 1164 | 4x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 1165 | 4x |
TDtargetEndOfTrialatdoseGrid = td_target_end_of_trial_at_dose_grid, |
| 1166 | 4x |
TDtargetDuringTrialatdoseGrid = dose, |
| 1167 | 4x |
CITDEOT = ci_tdeot, |
| 1168 | 4x |
ratioTDEOT = ratio_tdeot, |
| 1169 | 4x |
fit = fit_model, |
| 1170 | 4x |
stop = attr(should_stop, "message"), |
| 1171 | 4x |
report_results = stopit_results |
| 1172 |
) |
|
| 1173 |
} |
|
| 1174 | ||
| 1175 | 3x |
result_list <- get_result_list( |
| 1176 | 3x |
fun = run_sim, |
| 1177 | 3x |
nsim = nsim, |
| 1178 | 3x |
vars = c( |
| 1179 | 3x |
"sim_seeds", |
| 1180 | 3x |
"args", |
| 1181 | 3x |
"n_args", |
| 1182 | 3x |
"firstSeparate", |
| 1183 | 3x |
"truth", |
| 1184 | 3x |
"object" |
| 1185 |
), |
|
| 1186 | 3x |
parallel = parallel, |
| 1187 | 3x |
n_cores = nCores |
| 1188 |
) |
|
| 1189 | ||
| 1190 | 3x |
data_list <- lapply(result_list, "[[", "data") |
| 1191 | ||
| 1192 | 3x |
td_target_during_trial_list <- as.numeric(sapply( |
| 1193 | 3x |
result_list, |
| 1194 |
"[[", |
|
| 1195 | 3x |
"TDtargetDuringTrial" |
| 1196 |
)) |
|
| 1197 | ||
| 1198 | 3x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 1199 | 3x |
result_list, |
| 1200 |
"[[", |
|
| 1201 | 3x |
"TDtargetEndOfTrial" |
| 1202 |
)) |
|
| 1203 | ||
| 1204 | 3x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 1205 | 3x |
result_list, |
| 1206 |
"[[", |
|
| 1207 | 3x |
"TDtargetDuringTrialatdoseGrid" |
| 1208 |
)) |
|
| 1209 | ||
| 1210 | 3x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 1211 | 3x |
result_list, |
| 1212 |
"[[", |
|
| 1213 | 3x |
"TDtargetEndOfTrialatdoseGrid" |
| 1214 |
)) |
|
| 1215 | ||
| 1216 | 3x |
recommended_doses <- as.numeric(sapply( |
| 1217 | 3x |
result_list, |
| 1218 |
"[[", |
|
| 1219 | 3x |
"TDtargetEndOfTrialatdoseGrid" |
| 1220 |
)) |
|
| 1221 | ||
| 1222 | 3x |
ci_list <- lapply(result_list, "[[", "CITDEOT") |
| 1223 | 3x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 1224 | 3x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 1225 | 3x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 1226 | 3x |
fit_list <- lapply(result_list, "[[", "fit") |
| 1227 | 3x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 1228 | ||
| 1229 | 3x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 1230 | 3x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 1231 | ||
| 1232 | 3x |
PseudoSimulations( |
| 1233 | 3x |
data = data_list, |
| 1234 | 3x |
doses = recommended_doses, |
| 1235 | 3x |
fit = fit_list, |
| 1236 | 3x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 1237 | 3x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 1238 | 3x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 1239 | 3x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 1240 | 3x |
final_cis = ci_list, |
| 1241 | 3x |
final_ratios = ratio_list, |
| 1242 | 3x |
final_tdeot_cis = ci_tdeot_list, |
| 1243 | 3x |
final_tdeot_ratios = ratio_tdeot_list, |
| 1244 | 3x |
stop_reasons = stop_reasons, |
| 1245 | 3x |
stop_report = stop_report, |
| 1246 | 3x |
seed = rng_state |
| 1247 |
) |
|
| 1248 |
} |
|
| 1249 |
) |
|
| 1250 | ||
| 1251 |
## DualResponsesDesign ---- |
|
| 1252 | ||
| 1253 |
#' Simulate dose escalation procedure using both DLE and efficacy responses without samples |
|
| 1254 |
#' |
|
| 1255 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1256 |
#' |
|
| 1257 |
#' This is a method to simulate dose escalation procedure using both DLE and efficacy responses. |
|
| 1258 |
#' This is a method based on the [`DualResponsesDesign`] where DLE model used are of |
|
| 1259 |
#' [`ModelTox`] class object and efficacy model used are of [`ModelEff`] |
|
| 1260 |
#' class object. In addition, no DLE and efficacy samples are involved or generated in the simulation |
|
| 1261 |
#' process. |
|
| 1262 |
#' |
|
| 1263 |
#' @param object the [`DualResponsesDesign`] object we want to simulate the data from |
|
| 1264 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 1265 |
#' @param seed see [set_seed()] |
|
| 1266 |
#' @param trueDLE (`function`)\cr a function which takes as input a dose (vector) and returns the true probability |
|
| 1267 |
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`. |
|
| 1268 |
#' @param trueEff (`function`)\cr a function which takes as input a dose (vector) and returns the expected efficacy |
|
| 1269 |
#' responses (vector). Additional arguments can be supplied in `args`. |
|
| 1270 |
#' @param trueNu (`number`)\cr the precision, the inverse of the variance of the efficacy responses |
|
| 1271 |
#' @param args (`data.frame`)\cr data frame with arguments for the `trueDLE` and |
|
| 1272 |
#' `trueEff` function. The column names correspond to the argument |
|
| 1273 |
#' names, the rows to the values of the arguments. The rows are appropriately |
|
| 1274 |
#' recycled in the `nsim` simulations. |
|
| 1275 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 1276 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 1277 |
#' in this patient. |
|
| 1278 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 1279 |
#' clusters of the computer? (not default) |
|
| 1280 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 1281 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 1282 |
#' @param ... not used |
|
| 1283 |
#' |
|
| 1284 |
#' @return an object of class [`PseudoDualSimulations`] |
|
| 1285 |
#' |
|
| 1286 |
#' @example examples/design-method-simulateDualResponsesDesign.R |
|
| 1287 |
#' @export |
|
| 1288 |
setMethod( |
|
| 1289 |
f = "simulate", |
|
| 1290 |
signature = signature( |
|
| 1291 |
object = "DualResponsesDesign", |
|
| 1292 |
nsim = "ANY", |
|
| 1293 |
seed = "ANY" |
|
| 1294 |
), |
|
| 1295 |
definition = function( |
|
| 1296 |
object, |
|
| 1297 |
nsim = 1L, |
|
| 1298 |
seed = NULL, |
|
| 1299 |
trueDLE, |
|
| 1300 |
trueEff, |
|
| 1301 |
trueNu, |
|
| 1302 |
args = NULL, |
|
| 1303 |
firstSeparate = FALSE, |
|
| 1304 |
parallel = FALSE, |
|
| 1305 |
nCores = min(parallel::detectCores(), 5L), |
|
| 1306 |
... |
|
| 1307 |
) {
|
|
| 1308 | 2x |
nsim <- as.integer(nsim) |
| 1309 | 2x |
assert_function(trueDLE) |
| 1310 | 2x |
assert_function(trueEff) |
| 1311 | 2x |
assert_true(trueNu > 0) |
| 1312 | 2x |
assert_flag(firstSeparate) |
| 1313 | 2x |
assert_count(nsim, positive = TRUE) |
| 1314 | 2x |
assert_flag(parallel) |
| 1315 | 2x |
assert_count(nCores, positive = TRUE) |
| 1316 | 2x |
assert_class(object, "DualResponsesDesign") |
| 1317 | ||
| 1318 | 2x |
args <- as.data.frame(args) |
| 1319 | 2x |
n_args <- max(nrow(args), 1L) |
| 1320 | ||
| 1321 | 2x |
dle_arg_names <- names(formals(trueDLE))[-1] |
| 1322 | 2x |
eff_arg_names <- names(formals(trueEff))[-1] |
| 1323 | ||
| 1324 | 2x |
rng_state <- set_seed(seed) |
| 1325 | ||
| 1326 |
# Keep original seed generation for snapshot test compatibility |
|
| 1327 | 2x |
sim_seeds <- sample(x = seq_len(1e5), size = nsim) |
| 1328 | ||
| 1329 | 2x |
run_sim <- function(iter_sim) {
|
| 1330 | 2x |
set.seed(sim_seeds[iter_sim]) |
| 1331 | ||
| 1332 | 2x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 1333 | ||
| 1334 | 2x |
dle_with_args <- function(dose) {
|
| 1335 | 22x |
do.call( |
| 1336 | 22x |
trueDLE, |
| 1337 | 22x |
c(dose, as.list(current_args)[dle_arg_names]) |
| 1338 |
) |
|
| 1339 |
} |
|
| 1340 | ||
| 1341 | 2x |
eff_with_args <- function(dose) {
|
| 1342 | 22x |
do.call( |
| 1343 | 22x |
trueEff, |
| 1344 | 22x |
c(dose, as.list(current_args)[eff_arg_names]) |
| 1345 |
) |
|
| 1346 |
} |
|
| 1347 | ||
| 1348 | 2x |
data <- object@data |
| 1349 | 2x |
sigma2 <- 1 / trueNu |
| 1350 | 2x |
prob_placebo <- NULL |
| 1351 | 2x |
mean_eff_placebo <- NULL |
| 1352 | ||
| 1353 | 2x |
if (data@placebo) {
|
| 1354 | 1x |
prob_placebo <- dle_with_args(object@data@doseGrid[1]) |
| 1355 | 1x |
mean_eff_placebo <- eff_with_args(object@data@doseGrid[1]) |
| 1356 |
} |
|
| 1357 | ||
| 1358 | 2x |
should_stop <- FALSE |
| 1359 | 2x |
dose <- object@startingDose |
| 1360 | ||
| 1361 | 2x |
while (!should_stop) {
|
| 1362 | 21x |
prob <- dle_with_args(dose) |
| 1363 | 21x |
mean_eff <- eff_with_args(dose) |
| 1364 | 21x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 1365 | ||
| 1366 | 21x |
cohort_size_placebo <- NULL |
| 1367 | 21x |
if (data@placebo) {
|
| 1368 | 9x |
cohort_size_placebo <- size( |
| 1369 | 9x |
object@pl_cohort_size, |
| 1370 | 9x |
dose = dose, |
| 1371 | 9x |
data = data |
| 1372 |
) |
|
| 1373 |
} |
|
| 1374 | ||
| 1375 | 21x |
if (firstSeparate && (cohort_size > 1L)) {
|
| 1376 | 9x |
dlts <- rbinom(n = 1L, size = 1L, prob = prob) |
| 1377 | 9x |
eff_responses <- rnorm(n = 1L, mean = mean_eff, sd = sqrt(sigma2)) |
| 1378 | ||
| 1379 | 9x |
dlts_placebo <- NULL |
| 1380 | 9x |
eff_responses_placebo <- NULL |
| 1381 | 9x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1382 | 9x |
dlts_placebo <- rbinom(n = 1L, size = 1L, prob = prob_placebo) |
| 1383 | 9x |
eff_responses_placebo <- rnorm( |
| 1384 | 9x |
n = 1L, |
| 1385 | 9x |
mean = mean_eff_placebo, |
| 1386 | 9x |
sd = sqrt(sigma2) |
| 1387 |
) |
|
| 1388 |
} |
|
| 1389 | ||
| 1390 | 9x |
if (dlts == 0) {
|
| 1391 | 7x |
remaining_dlts <- rbinom( |
| 1392 | 7x |
n = cohort_size - 1L, |
| 1393 | 7x |
size = 1L, |
| 1394 | 7x |
prob = prob |
| 1395 |
) |
|
| 1396 | 7x |
remaining_eff <- rnorm( |
| 1397 | 7x |
n = cohort_size - 1L, |
| 1398 | 7x |
mean = mean_eff, |
| 1399 | 7x |
sd = sqrt(sigma2) |
| 1400 |
) |
|
| 1401 | 7x |
dlts <- c(dlts, remaining_dlts) |
| 1402 | 7x |
eff_responses <- c(eff_responses, remaining_eff) |
| 1403 | ||
| 1404 | 7x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1405 | 7x |
remaining_dlts_placebo <- rbinom( |
| 1406 | 7x |
n = cohort_size_placebo, |
| 1407 | 7x |
size = 1L, |
| 1408 | 7x |
prob = prob_placebo |
| 1409 |
) |
|
| 1410 | 7x |
remaining_eff_placebo <- rnorm( |
| 1411 | 7x |
n = cohort_size_placebo, |
| 1412 | 7x |
mean = mean_eff_placebo, |
| 1413 | 7x |
sd = sqrt(sigma2) |
| 1414 |
) |
|
| 1415 | 7x |
dlts_placebo <- c(dlts_placebo, remaining_dlts_placebo) |
| 1416 | 7x |
eff_responses_placebo <- c( |
| 1417 | 7x |
eff_responses_placebo, |
| 1418 | 7x |
remaining_eff_placebo |
| 1419 |
) |
|
| 1420 |
} |
|
| 1421 |
} |
|
| 1422 |
} else {
|
|
| 1423 | 12x |
dlts <- rbinom(n = cohort_size, size = 1L, prob = prob) |
| 1424 | 12x |
eff_responses <- rnorm( |
| 1425 | 12x |
n = cohort_size, |
| 1426 | 12x |
mean = mean_eff, |
| 1427 | 12x |
sd = sqrt(sigma2) |
| 1428 |
) |
|
| 1429 | ||
| 1430 | 12x |
dlts_placebo <- NULL |
| 1431 | 12x |
eff_responses_placebo <- NULL |
| 1432 | 12x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1433 | ! |
dlts_placebo <- rbinom( |
| 1434 | ! |
n = cohort_size_placebo, |
| 1435 | ! |
size = 1L, |
| 1436 | ! |
prob = prob_placebo |
| 1437 |
) |
|
| 1438 | ! |
eff_responses_placebo <- rnorm( |
| 1439 | ! |
n = cohort_size_placebo, |
| 1440 | ! |
mean = mean_eff_placebo, |
| 1441 | ! |
sd = sqrt(sigma2) |
| 1442 |
) |
|
| 1443 |
} |
|
| 1444 |
} |
|
| 1445 | ||
| 1446 | 21x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1447 | 9x |
data <- update( |
| 1448 | 9x |
object = data, |
| 1449 | 9x |
x = object@data@doseGrid[1], |
| 1450 | 9x |
y = dlts_placebo, |
| 1451 | 9x |
w = eff_responses_placebo, |
| 1452 | 9x |
check = FALSE |
| 1453 |
) |
|
| 1454 | 9x |
data <- update( |
| 1455 | 9x |
object = data, |
| 1456 | 9x |
x = dose, |
| 1457 | 9x |
y = dlts, |
| 1458 | 9x |
w = eff_responses, |
| 1459 | 9x |
new_cohort = FALSE |
| 1460 |
) |
|
| 1461 |
} else {
|
|
| 1462 | 12x |
data <- update(object = data, x = dose, y = dlts, w = eff_responses) |
| 1463 |
} |
|
| 1464 | ||
| 1465 | 21x |
dle_model <- update(object = object@model, data = data) |
| 1466 | 21x |
eff_model <- update(object = object@eff_model, data = data) |
| 1467 | ||
| 1468 | 21x |
eff_nu <- eff_model@nu |
| 1469 | 21x |
eff_sigma2 <- if (eff_model@use_fixed) {
|
| 1470 | ! |
1 / eff_nu |
| 1471 |
} else {
|
|
| 1472 | 21x |
1 / (as.numeric(eff_nu["a"] / eff_nu["b"])) |
| 1473 |
} |
|
| 1474 | ||
| 1475 | 21x |
dose_limit <- maxDose(object@increments, data = data) |
| 1476 | ||
| 1477 | 21x |
next_best_result <- nextBest( |
| 1478 | 21x |
object@nextBest, |
| 1479 | 21x |
doselimit = dose_limit, |
| 1480 | 21x |
model = dle_model, |
| 1481 | 21x |
data = data, |
| 1482 | 21x |
model_eff = eff_model, |
| 1483 | 21x |
in_sim = TRUE |
| 1484 |
) |
|
| 1485 | ||
| 1486 | 21x |
dose <- next_best_result$next_dose |
| 1487 | 21x |
td_target_during_trial <- next_best_result$dose_target_drt |
| 1488 | 21x |
td_target_during_trial_at_dose_grid <- next_best_result$next_dose_drt |
| 1489 | 21x |
td_target_end_of_trial <- next_best_result$dose_target_eot |
| 1490 | 21x |
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot |
| 1491 | 21x |
gstar <- next_best_result$dose_max_gain |
| 1492 | 21x |
gstar_at_dose_grid <- next_best_result$next_dose_max_gain |
| 1493 | ||
| 1494 | 21x |
recommend <- min( |
| 1495 | 21x |
td_target_end_of_trial_at_dose_grid, |
| 1496 | 21x |
gstar_at_dose_grid |
| 1497 |
) |
|
| 1498 | ||
| 1499 | 21x |
ci_tdeot <- list( |
| 1500 | 21x |
lower = next_best_result$ci_dose_target_eot[1], |
| 1501 | 21x |
upper = next_best_result$ci_dose_target_eot[2] |
| 1502 |
) |
|
| 1503 | 21x |
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot |
| 1504 | ||
| 1505 | 21x |
ci_gstar <- list( |
| 1506 | 21x |
lower = next_best_result$ci_dose_max_gain[1], |
| 1507 | 21x |
upper = next_best_result$ci_dose_max_gain[2] |
| 1508 |
) |
|
| 1509 | 21x |
ratio_gstar <- next_best_result$ci_ratio_dose_max_gain |
| 1510 | ||
| 1511 | 21x |
optimal_dose <- min(gstar, td_target_end_of_trial) |
| 1512 | ||
| 1513 | 21x |
if (optimal_dose == gstar) {
|
| 1514 | ! |
ratio <- ratio_gstar |
| 1515 | ! |
ci <- ci_gstar |
| 1516 |
} else {
|
|
| 1517 | 21x |
ratio <- ratio_tdeot |
| 1518 | 21x |
ci <- ci_tdeot |
| 1519 |
} |
|
| 1520 | ||
| 1521 | 21x |
should_stop <- stopTrial( |
| 1522 | 21x |
object@stopping, |
| 1523 | 21x |
dose = dose, |
| 1524 | 21x |
model = dle_model, |
| 1525 | 21x |
data = data, |
| 1526 | 21x |
Effmodel = eff_model |
| 1527 |
) |
|
| 1528 | 21x |
stopit_results <- h_unpack_stopit(should_stop) |
| 1529 |
} |
|
| 1530 | ||
| 1531 | 2x |
prob_fun <- probFunction( |
| 1532 | 2x |
dle_model, |
| 1533 | 2x |
phi1 = dle_model@phi1, |
| 1534 | 2x |
phi2 = dle_model@phi2 |
| 1535 |
) |
|
| 1536 | 2x |
dle_fit <- list( |
| 1537 | 2x |
phi1 = dle_model@phi1, |
| 1538 | 2x |
phi2 = dle_model@phi2, |
| 1539 | 2x |
probDLE = prob_fun(object@data@doseGrid) |
| 1540 |
) |
|
| 1541 | ||
| 1542 | 2x |
eff_fun <- efficacyFunction( |
| 1543 | 2x |
eff_model, |
| 1544 | 2x |
theta1 = eff_model@theta1, |
| 1545 | 2x |
theta2 = eff_model@theta2 |
| 1546 |
) |
|
| 1547 | 2x |
eff_fit <- list( |
| 1548 | 2x |
theta1 = eff_model@theta1, |
| 1549 | 2x |
theta2 = eff_model@theta2, |
| 1550 | 2x |
ExpEff = eff_fun(object@data@doseGrid) |
| 1551 |
) |
|
| 1552 | ||
| 1553 | 2x |
list( |
| 1554 | 2x |
data = data, |
| 1555 | 2x |
dose = dose, |
| 1556 | 2x |
TDtargetDuringTrial = td_target_during_trial, |
| 1557 | 2x |
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid, |
| 1558 | 2x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 1559 | 2x |
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid, |
| 1560 | 2x |
Gstar = gstar, |
| 1561 | 2x |
GstarAtDoseGrid = gstar_at_dose_grid, |
| 1562 | 2x |
Recommend = recommend, |
| 1563 | 2x |
OptimalDose = optimal_dose, |
| 1564 | 2x |
OptimalDoseAtDoseGrid = recommend, |
| 1565 | 2x |
ratio = ratio, |
| 1566 | 2x |
CI = ci, |
| 1567 | 2x |
ratioGstar = ratio_gstar, |
| 1568 | 2x |
CIGstar = ci_gstar, |
| 1569 | 2x |
ratioTDEOT = ratio_tdeot, |
| 1570 | 2x |
CITDEOT = ci_tdeot, |
| 1571 | 2x |
fitDLE = dle_fit, |
| 1572 | 2x |
fitEff = eff_fit, |
| 1573 | 2x |
sigma2est = eff_sigma2, |
| 1574 | 2x |
stop = attr(should_stop, "message"), |
| 1575 | 2x |
report_results = stopit_results |
| 1576 |
) |
|
| 1577 |
} |
|
| 1578 | ||
| 1579 | 2x |
result_list <- get_result_list( |
| 1580 | 2x |
fun = run_sim, |
| 1581 | 2x |
nsim = nsim, |
| 1582 | 2x |
vars = c( |
| 1583 | 2x |
"sim_seeds", |
| 1584 | 2x |
"args", |
| 1585 | 2x |
"n_args", |
| 1586 | 2x |
"firstSeparate", |
| 1587 | 2x |
"trueDLE", |
| 1588 | 2x |
"trueEff", |
| 1589 | 2x |
"trueNu", |
| 1590 | 2x |
"object" |
| 1591 |
), |
|
| 1592 | 2x |
parallel = parallel, |
| 1593 | 2x |
n_cores = nCores |
| 1594 |
) |
|
| 1595 | ||
| 1596 | 2x |
data_list <- lapply(result_list, "[[", "data") |
| 1597 | 2x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend")) |
| 1598 | ||
| 1599 | 2x |
td_target_during_trial_list <- as.numeric(sapply( |
| 1600 | 2x |
result_list, |
| 1601 |
"[[", |
|
| 1602 | 2x |
"TDtargetDuringTrial" |
| 1603 |
)) |
|
| 1604 | ||
| 1605 | 2x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 1606 | 2x |
result_list, |
| 1607 |
"[[", |
|
| 1608 | 2x |
"TDtargetEndOfTrial" |
| 1609 |
)) |
|
| 1610 | ||
| 1611 | 2x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 1612 | 2x |
result_list, |
| 1613 |
"[[", |
|
| 1614 | 2x |
"TDtargetDuringTrialAtDoseGrid" |
| 1615 |
)) |
|
| 1616 | ||
| 1617 | 2x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 1618 | 2x |
result_list, |
| 1619 |
"[[", |
|
| 1620 | 2x |
"TDtargetEndOfTrialAtDoseGrid" |
| 1621 |
)) |
|
| 1622 | ||
| 1623 | 2x |
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar")) |
| 1624 | 2x |
gstar_at_dose_grid_list <- as.numeric(sapply( |
| 1625 | 2x |
result_list, |
| 1626 |
"[[", |
|
| 1627 | 2x |
"GstarAtDoseGrid" |
| 1628 |
)) |
|
| 1629 | ||
| 1630 | 2x |
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose")) |
| 1631 | 2x |
optimal_dose_at_dose_grid_list <- as.numeric(sapply( |
| 1632 | 2x |
result_list, |
| 1633 |
"[[", |
|
| 1634 | 2x |
"Recommend" |
| 1635 |
)) |
|
| 1636 | ||
| 1637 | 2x |
ci_list <- lapply(result_list, "[[", "CI") |
| 1638 | 2x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio")) |
| 1639 | 2x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 1640 | 2x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 1641 | 2x |
ci_gstar_list <- lapply(result_list, "[[", "CIGstar") |
| 1642 | 2x |
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar")) |
| 1643 | ||
| 1644 | 2x |
fit_dle_list <- lapply(result_list, "[[", "fitDLE") |
| 1645 | 2x |
fit_eff_list <- lapply(result_list, "[[", "fitEff") |
| 1646 | 2x |
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est")) |
| 1647 | 2x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 1648 | ||
| 1649 | 2x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 1650 | 2x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 1651 | ||
| 1652 | 2x |
PseudoDualSimulations( |
| 1653 | 2x |
data = data_list, |
| 1654 | 2x |
doses = recommended_doses, |
| 1655 | 2x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 1656 | 2x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 1657 | 2x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 1658 | 2x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 1659 | 2x |
final_cis = ci_list, |
| 1660 | 2x |
final_ratios = ratio_list, |
| 1661 | 2x |
final_gstar_estimates = gstar_list, |
| 1662 | 2x |
final_gstar_at_dose_grid = gstar_at_dose_grid_list, |
| 1663 | 2x |
final_gstar_cis = ci_gstar_list, |
| 1664 | 2x |
final_gstar_ratios = ratio_gstar_list, |
| 1665 | 2x |
final_tdeot_cis = ci_tdeot_list, |
| 1666 | 2x |
final_tdeot_ratios = ratio_tdeot_list, |
| 1667 | 2x |
final_optimal_dose = optimal_dose_list, |
| 1668 | 2x |
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list, |
| 1669 | 2x |
fit = fit_dle_list, |
| 1670 | 2x |
fit_eff = fit_eff_list, |
| 1671 | 2x |
sigma2_est = sigma2_estimates, |
| 1672 | 2x |
stop_reasons = stop_reasons, |
| 1673 | 2x |
stop_report = stop_report, |
| 1674 | 2x |
seed = rng_state |
| 1675 |
) |
|
| 1676 |
} |
|
| 1677 |
) |
|
| 1678 | ||
| 1679 |
## DualResponsesSamplesDesign ---- |
|
| 1680 | ||
| 1681 |
### h_simulate_flexi ---- |
|
| 1682 | ||
| 1683 |
h_simulate_flexi <- function( |
|
| 1684 |
object, |
|
| 1685 |
nsim = 1L, |
|
| 1686 |
seed = NULL, |
|
| 1687 |
trueDLE, |
|
| 1688 |
trueEff, |
|
| 1689 |
trueNu = NULL, |
|
| 1690 |
trueSigma2 = NULL, |
|
| 1691 |
trueSigma2betaW = NULL, |
|
| 1692 |
args = NULL, |
|
| 1693 |
firstSeparate = FALSE, |
|
| 1694 |
mcmcOptions = McmcOptions(), |
|
| 1695 |
parallel = FALSE, |
|
| 1696 |
nCores = min(parallel::detectCores(), 5L), |
|
| 1697 |
... |
|
| 1698 |
) {
|
|
| 1699 | 1x |
stopifnot( |
| 1700 | 1x |
trueSigma2 > 0, |
| 1701 | 1x |
trueSigma2betaW > 0, |
| 1702 | 1x |
is.numeric(trueEff), |
| 1703 | 1x |
length(trueEff) == length(object@data@doseGrid) |
| 1704 |
) |
|
| 1705 | ||
| 1706 | 1x |
args <- as.data.frame(args) |
| 1707 | 1x |
n_args <- max(nrow(args), 1L) |
| 1708 | ||
| 1709 |
# Get argument names (excluding the first one which is the dose) |
|
| 1710 | 1x |
dle_arg_names <- names(formals(trueDLE))[-1] |
| 1711 | ||
| 1712 |
# Seed handling |
|
| 1713 | 1x |
rng_state <- set_seed(seed) |
| 1714 | ||
| 1715 |
# Generate individual seeds for simulation runs |
|
| 1716 | 1x |
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
| 1717 | ||
| 1718 |
# Function to run a single simulation with index "iter_sim" |
|
| 1719 | 1x |
run_sim <- function(iter_sim) {
|
| 1720 |
# Set the seed for this run |
|
| 1721 | 1x |
set.seed(sim_seeds[iter_sim]) |
| 1722 | ||
| 1723 |
# Get current arguments (appropriately recycled) |
|
| 1724 | 1x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 1725 | ||
| 1726 |
# DLE truth function with current arguments |
|
| 1727 | 1x |
dle_with_args <- function(dose) {
|
| 1728 | 6x |
do.call( |
| 1729 | 6x |
trueDLE, |
| 1730 |
# First argument: the dose |
|
| 1731 | 6x |
c( |
| 1732 | 6x |
dose, |
| 1733 |
# Following arguments |
|
| 1734 | 6x |
current_args |
| 1735 |
) |
|
| 1736 |
) |
|
| 1737 |
} |
|
| 1738 | ||
| 1739 |
# Efficacy truth function (fixed for EffFlexi) |
|
| 1740 | 1x |
eff_truth <- trueEff |
| 1741 | ||
| 1742 |
# Start with the provided data |
|
| 1743 | 1x |
data <- object@data |
| 1744 | ||
| 1745 |
# Trial control variables |
|
| 1746 | 1x |
should_stop <- FALSE |
| 1747 | 1x |
dose <- object@startingDose |
| 1748 | 1x |
dose_pl <- object@data@doseGrid[1] |
| 1749 | ||
| 1750 |
# Start with specified variance parameters |
|
| 1751 | 1x |
sigma2 <- trueSigma2 |
| 1752 | 1x |
sigma2_beta_w <- trueSigma2betaW |
| 1753 | ||
| 1754 |
# Main simulation loop |
|
| 1755 | 1x |
while (!should_stop) {
|
| 1756 |
# Calculate probabilities and outcomes at current dose |
|
| 1757 | 3x |
dle_prob <- dle_with_args(dose) |
| 1758 | 3x |
dle_prob_pl <- dle_with_args(dose_pl) |
| 1759 | ||
| 1760 | 3x |
dose_index <- which(dose == data@doseGrid) |
| 1761 | 3x |
mean_eff <- eff_truth[dose_index] |
| 1762 | 3x |
mean_eff_pl <- eff_truth[1] |
| 1763 | ||
| 1764 |
# Determine cohort size |
|
| 1765 | 3x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 1766 | ||
| 1767 | 3x |
if (data@placebo) {
|
| 1768 | ! |
placebo_size <- size( |
| 1769 | ! |
object@pl_cohort_size, |
| 1770 | ! |
dose = dose, |
| 1771 | ! |
data = data |
| 1772 |
) |
|
| 1773 |
} |
|
| 1774 | ||
| 1775 |
## simulate DLTs: depends on whether we |
|
| 1776 |
## separate the first patient or not. |
|
| 1777 | 3x |
if (firstSeparate && (cohort_size > 1L)) {
|
| 1778 |
## dose the first patient |
|
| 1779 | ! |
dlts <- rbinom( |
| 1780 | ! |
n = 1L, |
| 1781 | ! |
size = 1L, |
| 1782 | ! |
prob = dle_prob |
| 1783 |
) |
|
| 1784 | ||
| 1785 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 1786 | ! |
dlts_pl <- rbinom( |
| 1787 | ! |
n = 1L, |
| 1788 | ! |
size = 1L, |
| 1789 | ! |
prob = dle_prob_pl |
| 1790 |
) |
|
| 1791 |
} |
|
| 1792 | ||
| 1793 | ! |
eff_responses <- rnorm( |
| 1794 | ! |
n = 1L, |
| 1795 | ! |
mean = mean_eff, |
| 1796 | ! |
sd = sqrt(trueSigma2) |
| 1797 |
) |
|
| 1798 | ||
| 1799 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 1800 | ! |
eff_responses_pl <- rnorm( |
| 1801 | ! |
n = 1L, |
| 1802 | ! |
mean = mean_eff_pl, |
| 1803 | ! |
sd = sqrt(trueSigma2) |
| 1804 |
) |
|
| 1805 |
} |
|
| 1806 | ||
| 1807 |
# If no DLT in first patient, enroll remaining patients |
|
| 1808 | ! |
if (dlts == 0) {
|
| 1809 | ! |
dlts <- c( |
| 1810 | ! |
dlts, |
| 1811 | ! |
rbinom( |
| 1812 | ! |
n = cohort_size - 1L, |
| 1813 | ! |
size = 1L, |
| 1814 | ! |
prob = dle_prob |
| 1815 |
) |
|
| 1816 |
) |
|
| 1817 | ! |
eff_responses <- c( |
| 1818 | ! |
eff_responses, |
| 1819 | ! |
rnorm( |
| 1820 | ! |
n = cohort_size - 1L, |
| 1821 | ! |
mean = mean_eff, |
| 1822 | ! |
sd = sqrt(trueSigma2) |
| 1823 |
) |
|
| 1824 |
) |
|
| 1825 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 1826 | ! |
dlts_pl <- c( |
| 1827 | ! |
dlts_pl, |
| 1828 | ! |
rbinom( |
| 1829 | ! |
n = placebo_size, |
| 1830 | ! |
size = 1L, |
| 1831 | ! |
prob = dle_prob_pl |
| 1832 |
) |
|
| 1833 |
) |
|
| 1834 | ! |
eff_responses_pl <- c( |
| 1835 | ! |
eff_responses_pl, |
| 1836 | ! |
rnorm( |
| 1837 | ! |
n = placebo_size, |
| 1838 | ! |
mean = mean_eff_pl, |
| 1839 | ! |
sd = sqrt(trueSigma2) |
| 1840 |
) |
|
| 1841 |
) |
|
| 1842 |
} |
|
| 1843 |
} |
|
| 1844 |
} else {
|
|
| 1845 |
# Dose all patients directly |
|
| 1846 | 3x |
dlts <- rbinom( |
| 1847 | 3x |
n = cohort_size, |
| 1848 | 3x |
size = 1L, |
| 1849 | 3x |
prob = dle_prob |
| 1850 |
) |
|
| 1851 | ||
| 1852 | 3x |
eff_responses <- rnorm( |
| 1853 | 3x |
n = cohort_size, |
| 1854 | 3x |
mean = mean_eff, |
| 1855 | 3x |
sd = sqrt(trueSigma2) |
| 1856 |
) |
|
| 1857 | 3x |
if (data@placebo && (placebo_size > 0L)) {
|
| 1858 | ! |
dlts_pl <- rbinom( |
| 1859 | ! |
n = placebo_size, |
| 1860 | ! |
size = 1L, |
| 1861 | ! |
prob = dle_prob_pl |
| 1862 |
) |
|
| 1863 | ! |
eff_responses_pl <- rnorm( |
| 1864 | ! |
n = placebo_size, |
| 1865 | ! |
mean = mean_eff_pl, |
| 1866 | ! |
sd = sqrt(trueSigma2) |
| 1867 |
) |
|
| 1868 |
} |
|
| 1869 |
} |
|
| 1870 | ||
| 1871 |
## update the data with this placebo (if any) cohort and then with active dose |
|
| 1872 | 3x |
if (data@placebo && (placebo_size > 0L)) {
|
| 1873 | ! |
data <- update( |
| 1874 | ! |
object = data, |
| 1875 | ! |
x = object@data@doseGrid[1], |
| 1876 | ! |
y = dlts_pl, |
| 1877 | ! |
w = eff_responses_pl, |
| 1878 | ! |
check = FALSE |
| 1879 |
) |
|
| 1880 | ||
| 1881 |
## update the data with active dose |
|
| 1882 | ! |
data <- update( |
| 1883 | ! |
object = data, |
| 1884 | ! |
x = dose, |
| 1885 | ! |
y = dlts, |
| 1886 | ! |
w = eff_responses, |
| 1887 | ! |
new_cohort = FALSE |
| 1888 |
) |
|
| 1889 |
} else {
|
|
| 1890 |
## update the data with this cohort |
|
| 1891 | 3x |
data <- update( |
| 1892 | 3x |
object = data, |
| 1893 | 3x |
x = dose, |
| 1894 | 3x |
y = dlts, |
| 1895 | 3x |
w = eff_responses |
| 1896 |
) |
|
| 1897 |
} |
|
| 1898 | ||
| 1899 |
# Update models with new data |
|
| 1900 | 3x |
dle_model <- update( |
| 1901 | 3x |
object = object@model, |
| 1902 | 3x |
data = data |
| 1903 |
) |
|
| 1904 | ||
| 1905 | 3x |
eff_model <- update( |
| 1906 | 3x |
object = object@eff_model, |
| 1907 | 3x |
data = data |
| 1908 |
) |
|
| 1909 | ||
| 1910 |
# Calculate dose limit |
|
| 1911 | 3x |
dose_limit <- maxDose(object@increments, data = data) |
| 1912 | ||
| 1913 |
# Generate MCMC samples from both models |
|
| 1914 | 3x |
dle_samples <- mcmc( |
| 1915 | 3x |
data = data, |
| 1916 | 3x |
model = dle_model, |
| 1917 | 3x |
options = mcmcOptions |
| 1918 |
) |
|
| 1919 | ||
| 1920 | 3x |
eff_samples <- mcmc( |
| 1921 | 3x |
data = data, |
| 1922 | 3x |
model = eff_model, |
| 1923 | 3x |
options = mcmcOptions |
| 1924 |
) |
|
| 1925 | ||
| 1926 |
# Update variance estimates from MCMC samples |
|
| 1927 | 3x |
sigma2 <- mean(eff_samples@data$sigma2W) |
| 1928 | 3x |
sigma2_beta_w <- mean(eff_samples@data$sigma2betaW) |
| 1929 | ||
| 1930 |
# Calculate next best dose |
|
| 1931 | 3x |
next_bd <- nextBest( |
| 1932 | 3x |
object@nextBest, |
| 1933 | 3x |
doselimit = dose_limit, |
| 1934 | 3x |
samples = dle_samples, |
| 1935 | 3x |
model = dle_model, |
| 1936 | 3x |
model_eff = eff_model, |
| 1937 | 3x |
samples_eff = eff_samples, |
| 1938 | 3x |
data = data, |
| 1939 | 3x |
in_sim = TRUE |
| 1940 |
) |
|
| 1941 | ||
| 1942 |
# Extract dose recommendations |
|
| 1943 | 3x |
dose <- next_bd$next_dose |
| 1944 | 3x |
td_target_during_trial <- next_bd$dose_target_drt |
| 1945 | 3x |
td_target_during_trial_at_dose_grid <- next_bd$next_dose_drt |
| 1946 | 3x |
td_target_end_of_trial <- next_bd$dose_target_eot |
| 1947 | 3x |
td_target_end_of_trial_at_dose_grid <- next_bd$next_dose_eot |
| 1948 | 3x |
gstar <- next_bd$dose_max_gain |
| 1949 | 3x |
gstar_at_dose_grid <- next_bd$next_dose_max_gain |
| 1950 | ||
| 1951 | 3x |
recommend <- min( |
| 1952 | 3x |
td_target_end_of_trial_at_dose_grid, |
| 1953 | 3x |
gstar_at_dose_grid |
| 1954 |
) |
|
| 1955 | ||
| 1956 |
# Calculate 95% confidence intervals and ratios |
|
| 1957 | 3x |
ci_tdeot <- list( |
| 1958 | 3x |
lower = next_bd$ci_dose_target_eot[1], |
| 1959 | 3x |
upper = next_bd$ci_dose_target_eot[2] |
| 1960 |
) |
|
| 1961 | 3x |
ratio_tdeot <- next_bd$ci_ratio_dose_target_eot |
| 1962 | ||
| 1963 | 3x |
ci_gstar <- list( |
| 1964 | 3x |
lower = next_bd$ci_dose_max_gain[1], |
| 1965 | 3x |
upper = next_bd$ci_dose_max_gain[2] |
| 1966 |
) |
|
| 1967 | 3x |
ratio_gstar <- next_bd$ci_ratio_dose_max_gain |
| 1968 | ||
| 1969 |
# Find the optimal dose |
|
| 1970 | 3x |
optimal_dose <- min(gstar, td_target_end_of_trial) |
| 1971 | ||
| 1972 | 3x |
if (optimal_dose == gstar) {
|
| 1973 | 2x |
ratio <- ratio_gstar |
| 1974 | 2x |
ci <- ci_gstar |
| 1975 |
} else {
|
|
| 1976 | 1x |
ratio <- ratio_tdeot |
| 1977 | 1x |
ci <- ci_tdeot |
| 1978 |
} |
|
| 1979 | ||
| 1980 |
# Evaluate stopping rules |
|
| 1981 | 3x |
should_stop <- stopTrial( |
| 1982 | 3x |
object@stopping, |
| 1983 | 3x |
dose = dose, |
| 1984 | 3x |
samples = dle_samples, |
| 1985 | 3x |
model = dle_model, |
| 1986 | 3x |
data = data, |
| 1987 | 3x |
TDderive = object@nextBest@derive, |
| 1988 | 3x |
Effmodel = eff_model, |
| 1989 | 3x |
Effsamples = eff_samples, |
| 1990 | 3x |
Gstarderive = object@nextBest@mg_derive |
| 1991 |
) |
|
| 1992 | 3x |
stop_results <- h_unpack_stopit(should_stop) |
| 1993 |
} |
|
| 1994 | ||
| 1995 |
# Calculate final model fits |
|
| 1996 | 1x |
dle_fit <- fit( |
| 1997 | 1x |
object = dle_samples, |
| 1998 | 1x |
model = dle_model, |
| 1999 | 1x |
data = data |
| 2000 |
) |
|
| 2001 | ||
| 2002 | 1x |
eff_fit <- fit( |
| 2003 | 1x |
object = eff_samples, |
| 2004 | 1x |
model = eff_model, |
| 2005 | 1x |
data = data |
| 2006 |
) |
|
| 2007 | ||
| 2008 |
# Return simulation results |
|
| 2009 | 1x |
list( |
| 2010 | 1x |
data = data, |
| 2011 | 1x |
dose = dose, |
| 2012 | 1x |
TDtargetDuringTrial = td_target_during_trial, |
| 2013 | 1x |
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid, |
| 2014 | 1x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 2015 | 1x |
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid, |
| 2016 | 1x |
Gstar = gstar, |
| 2017 | 1x |
GstarAtDoseGrid = gstar_at_dose_grid, |
| 2018 | 1x |
Recommend = recommend, |
| 2019 | 1x |
OptimalDose = optimal_dose, |
| 2020 | 1x |
OptimalDoseAtDoseGrid = recommend, |
| 2021 | 1x |
ratio = ratio, |
| 2022 | 1x |
CI = ci, |
| 2023 | 1x |
ratioGstar = ratio_gstar, |
| 2024 | 1x |
CIGstar = ci_gstar, |
| 2025 | 1x |
ratioTDEOT = ratio_tdeot, |
| 2026 | 1x |
CITDEOT = ci_tdeot, |
| 2027 | 1x |
fitDLE = subset(dle_fit, select = c(middle, lower, upper)), |
| 2028 | 1x |
fitEff = subset(eff_fit, select = c(middle, lower, upper)), |
| 2029 | 1x |
sigma2est = sigma2, |
| 2030 | 1x |
sigma2betaWest = sigma2_beta_w, |
| 2031 | 1x |
stop = attr(should_stop, "message"), |
| 2032 | 1x |
report_results = stop_results |
| 2033 |
) |
|
| 2034 |
} |
|
| 2035 | ||
| 2036 | 1x |
result_list <- get_result_list( |
| 2037 | 1x |
fun = run_sim, |
| 2038 | 1x |
nsim = nsim, |
| 2039 | 1x |
vars = c( |
| 2040 | 1x |
"sim_seeds", |
| 2041 | 1x |
"args", |
| 2042 | 1x |
"n_args", |
| 2043 | 1x |
"firstSeparate", |
| 2044 | 1x |
"trueDLE", |
| 2045 | 1x |
"trueEff", |
| 2046 | 1x |
"trueSigma2", |
| 2047 | 1x |
"trueSigma2betaW", |
| 2048 | 1x |
"object", |
| 2049 | 1x |
"mcmcOptions" |
| 2050 |
), |
|
| 2051 | 1x |
parallel = parallel, |
| 2052 | 1x |
n_cores = nCores |
| 2053 |
) |
|
| 2054 | ||
| 2055 |
# Process simulation results |
|
| 2056 | 1x |
data_list <- lapply(result_list, "[[", "data") |
| 2057 | 1x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend")) |
| 2058 | ||
| 2059 |
# Extract model fits and variance estimates |
|
| 2060 | 1x |
fit_dle_list <- lapply(result_list, "[[", "fitDLE") |
| 2061 | 1x |
fit_eff_list <- lapply(result_list, "[[", "fitEff") |
| 2062 | 1x |
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est")) |
| 2063 | 1x |
sigma2_beta_w_estimates <- as.numeric(sapply( |
| 2064 | 1x |
result_list, |
| 2065 |
"[[", |
|
| 2066 | 1x |
"sigma2betaWest" |
| 2067 |
)) |
|
| 2068 | ||
| 2069 |
# Extract TD target estimates |
|
| 2070 | 1x |
td_target_during_trial_list <- as.numeric(sapply( |
| 2071 | 1x |
result_list, |
| 2072 |
"[[", |
|
| 2073 | 1x |
"TDtargetDuringTrial" |
| 2074 |
)) |
|
| 2075 | ||
| 2076 | 1x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 2077 | 1x |
result_list, |
| 2078 |
"[[", |
|
| 2079 | 1x |
"TDtargetEndOfTrial" |
| 2080 |
)) |
|
| 2081 | ||
| 2082 | 1x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 2083 | 1x |
result_list, |
| 2084 |
"[[", |
|
| 2085 | 1x |
"TDtargetDuringTrialAtDoseGrid" |
| 2086 |
)) |
|
| 2087 | ||
| 2088 | 1x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 2089 | 1x |
result_list, |
| 2090 |
"[[", |
|
| 2091 | 1x |
"TDtargetEndOfTrialAtDoseGrid" |
| 2092 |
)) |
|
| 2093 | ||
| 2094 |
# Extract Gstar and optimal dose estimates |
|
| 2095 | 1x |
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar")) |
| 2096 | ||
| 2097 | 1x |
gstar_at_dose_grid_list <- as.numeric(sapply( |
| 2098 | 1x |
result_list, |
| 2099 |
"[[", |
|
| 2100 | 1x |
"GstarAtDoseGrid" |
| 2101 |
)) |
|
| 2102 | ||
| 2103 | 1x |
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose")) |
| 2104 | ||
| 2105 | 1x |
optimal_dose_at_dose_grid_list <- as.numeric(sapply( |
| 2106 | 1x |
result_list, |
| 2107 |
"[[", |
|
| 2108 | 1x |
"Recommend" |
| 2109 |
)) |
|
| 2110 | ||
| 2111 |
# Extract confidence intervals and ratios |
|
| 2112 | 1x |
ci_list <- lapply(result_list, "[[", "CI") |
| 2113 | ||
| 2114 | 1x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio")) |
| 2115 | ||
| 2116 | 1x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 2117 | ||
| 2118 | 1x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 2119 | ||
| 2120 | 1x |
ci_gstar_list <- lapply(result_list, "[[", "CIGstar") |
| 2121 | ||
| 2122 | 1x |
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar")) |
| 2123 | ||
| 2124 |
# Extract stopping information |
|
| 2125 | 1x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 2126 | ||
| 2127 | 1x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 2128 | 1x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 2129 | ||
| 2130 |
# Return simulation results |
|
| 2131 | 1x |
PseudoDualFlexiSimulations( |
| 2132 | 1x |
data = data_list, |
| 2133 | 1x |
doses = recommended_doses, |
| 2134 | 1x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 2135 | 1x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 2136 | 1x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 2137 | 1x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 2138 | 1x |
final_cis = ci_list, |
| 2139 | 1x |
final_ratios = ratio_list, |
| 2140 | 1x |
final_gstar_estimates = gstar_list, |
| 2141 | 1x |
final_gstar_at_dose_grid = gstar_at_dose_grid_list, |
| 2142 | 1x |
final_gstar_cis = ci_gstar_list, |
| 2143 | 1x |
final_gstar_ratios = ratio_gstar_list, |
| 2144 | 1x |
final_tdeot_cis = ci_tdeot_list, |
| 2145 | 1x |
final_tdeot_ratios = ratio_tdeot_list, |
| 2146 | 1x |
final_optimal_dose = optimal_dose_list, |
| 2147 | 1x |
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list, |
| 2148 | 1x |
fit = fit_dle_list, |
| 2149 | 1x |
fit_eff = fit_eff_list, |
| 2150 | 1x |
sigma2_est = sigma2_estimates, |
| 2151 | 1x |
sigma2_beta_w_est = sigma2_beta_w_estimates, |
| 2152 | 1x |
stop_reasons = stop_reasons, |
| 2153 | 1x |
stop_report = stop_report, |
| 2154 | 1x |
seed = rng_state |
| 2155 |
) |
|
| 2156 |
} |
|
| 2157 | ||
| 2158 |
### h_simulate_nonflexi ---- |
|
| 2159 | ||
| 2160 |
h_simulate_nonflexi <- function( |
|
| 2161 |
object, |
|
| 2162 |
nsim = 1L, |
|
| 2163 |
seed = NULL, |
|
| 2164 |
trueDLE, |
|
| 2165 |
trueEff, |
|
| 2166 |
trueNu = NULL, |
|
| 2167 |
trueSigma2 = NULL, |
|
| 2168 |
trueSigma2betaW = NULL, |
|
| 2169 |
args = NULL, |
|
| 2170 |
firstSeparate = FALSE, |
|
| 2171 |
mcmcOptions = McmcOptions(), |
|
| 2172 |
parallel = FALSE, |
|
| 2173 |
nCores = min(parallel::detectCores(), 5L), |
|
| 2174 |
... |
|
| 2175 |
) {
|
|
| 2176 | 2x |
stopifnot( |
| 2177 | 2x |
trueNu > 0, |
| 2178 | 2x |
is.function(trueEff) |
| 2179 |
) |
|
| 2180 | ||
| 2181 | 2x |
args <- as.data.frame(args) |
| 2182 | 2x |
n_args <- max(nrow(args), 1L) |
| 2183 | ||
| 2184 |
# Get argument names (excluding the first one which is the dose) |
|
| 2185 | 2x |
dle_arg_names <- names(formals(trueDLE))[-1] |
| 2186 | 2x |
eff_arg_names <- names(formals(trueEff))[-1] |
| 2187 | ||
| 2188 |
# Seed handling |
|
| 2189 | 2x |
rng_state <- set_seed(seed) |
| 2190 | ||
| 2191 |
# Generate individual seeds for simulation runs |
|
| 2192 | 2x |
sim_seeds <- sample(x = seq_len(1e5), size = nsim) |
| 2193 | ||
| 2194 |
# Function to run a single simulation with index "iter_sim" |
|
| 2195 | 2x |
run_sim <- function(iter_sim) {
|
| 2196 |
# Set the seed for this run |
|
| 2197 | 2x |
set.seed(sim_seeds[iter_sim]) |
| 2198 | ||
| 2199 |
# Get current arguments (appropriately recycled) |
|
| 2200 | 2x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 2201 | ||
| 2202 |
# DLE truth function with current arguments |
|
| 2203 | 2x |
dle_with_args <- function(dose) {
|
| 2204 | 10x |
do.call( |
| 2205 | 10x |
trueDLE, |
| 2206 |
# First argument: the dose |
|
| 2207 | 10x |
c( |
| 2208 | 10x |
dose, |
| 2209 |
# Following arguments: take only those that |
|
| 2210 |
# are required by the DLE function |
|
| 2211 | 10x |
as.list(current_args)[dle_arg_names] |
| 2212 |
) |
|
| 2213 |
) |
|
| 2214 |
} |
|
| 2215 | ||
| 2216 |
# Efficacy truth function with current arguments |
|
| 2217 | 2x |
eff_with_args <- function(dose) {
|
| 2218 | 10x |
do.call( |
| 2219 | 10x |
trueEff, |
| 2220 |
# First argument: the dose |
|
| 2221 | 10x |
c( |
| 2222 | 10x |
dose, |
| 2223 |
# Following arguments: take only those that |
|
| 2224 |
# are required by the Eff function |
|
| 2225 | 10x |
as.list(current_args)[eff_arg_names] |
| 2226 |
) |
|
| 2227 |
) |
|
| 2228 |
} |
|
| 2229 | ||
| 2230 |
# Find true sigma2 to generate responses |
|
| 2231 | 2x |
true_sigma2 <- 1 / trueNu |
| 2232 | ||
| 2233 |
# Start with the provided data |
|
| 2234 | 2x |
data <- object@data |
| 2235 | ||
| 2236 |
# Trial control variables |
|
| 2237 | 2x |
should_stop <- FALSE |
| 2238 | 2x |
dose <- object@startingDose |
| 2239 | ||
| 2240 |
# Main simulation loop |
|
| 2241 | 2x |
while (!should_stop) {
|
| 2242 |
# Calculate probabilities and outcomes at current dose |
|
| 2243 | 7x |
dle_prob <- dle_with_args(dose) |
| 2244 | 7x |
mean_eff <- eff_with_args(dose) |
| 2245 | ||
| 2246 |
# Determine cohort size |
|
| 2247 | 7x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 2248 | ||
| 2249 | 7x |
if (data@placebo) {
|
| 2250 | 3x |
placebo_size <- size( |
| 2251 | 3x |
object@pl_cohort_size, |
| 2252 | 3x |
dose = dose, |
| 2253 | 3x |
data = data |
| 2254 |
) |
|
| 2255 | 3x |
dose_pl <- data@doseGrid[1] |
| 2256 | 3x |
dle_prob_pl <- dle_with_args(dose_pl) |
| 2257 | 3x |
mean_eff_pl <- eff_with_args(dose_pl) |
| 2258 |
} |
|
| 2259 | ||
| 2260 |
## simulate DLTs: depends on whether we |
|
| 2261 |
## separate the first patient or not. |
|
| 2262 | 7x |
if (firstSeparate && (cohort_size > 1L)) {
|
| 2263 |
# Dose the first patient |
|
| 2264 | ! |
dlts <- rbinom( |
| 2265 | ! |
n = 1L, |
| 2266 | ! |
size = 1L, |
| 2267 | ! |
prob = dle_prob |
| 2268 |
) |
|
| 2269 | ||
| 2270 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 2271 | ! |
dlts_pl <- rbinom( |
| 2272 | ! |
n = 1L, |
| 2273 | ! |
size = 1L, |
| 2274 | ! |
prob = dle_prob_pl |
| 2275 |
) |
|
| 2276 |
} |
|
| 2277 | ||
| 2278 | ! |
eff_responses <- rnorm( |
| 2279 | ! |
n = 1L, |
| 2280 | ! |
mean = mean_eff, |
| 2281 | ! |
sd = sqrt(true_sigma2) |
| 2282 |
) |
|
| 2283 | ||
| 2284 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 2285 | ! |
eff_responses_pl <- rnorm( |
| 2286 | ! |
n = 1L, |
| 2287 | ! |
mean = mean_eff_pl, |
| 2288 | ! |
sd = sqrt(true_sigma2) |
| 2289 |
) |
|
| 2290 |
} |
|
| 2291 | ||
| 2292 |
# If there is no DLT, enroll the remaining patients |
|
| 2293 | ! |
if (dlts == 0) {
|
| 2294 | ! |
dlts <- c( |
| 2295 | ! |
dlts, |
| 2296 | ! |
rbinom( |
| 2297 | ! |
n = cohort_size - 1L, |
| 2298 | ! |
size = 1L, |
| 2299 | ! |
prob = dle_prob |
| 2300 |
) |
|
| 2301 |
) |
|
| 2302 | ! |
eff_responses <- c( |
| 2303 | ! |
eff_responses, |
| 2304 | ! |
rnorm( |
| 2305 | ! |
n = cohort_size - 1L, |
| 2306 | ! |
mean = mean_eff, |
| 2307 | ! |
sd = sqrt(true_sigma2) |
| 2308 |
) |
|
| 2309 |
) |
|
| 2310 | ||
| 2311 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 2312 | ! |
dlts_pl <- c( |
| 2313 | ! |
dlts_pl, |
| 2314 | ! |
rbinom( |
| 2315 | ! |
n = placebo_size, |
| 2316 | ! |
size = 1L, |
| 2317 | ! |
prob = dle_prob_pl |
| 2318 |
) |
|
| 2319 |
) |
|
| 2320 | ! |
eff_responses_pl <- c( |
| 2321 | ! |
mean_eff_pl, |
| 2322 | ! |
rnorm( |
| 2323 | ! |
n = placebo_size, |
| 2324 | ! |
mean = mean_eff_pl, |
| 2325 | ! |
sd = sqrt(true_sigma2) |
| 2326 |
) |
|
| 2327 |
) |
|
| 2328 |
} |
|
| 2329 |
} |
|
| 2330 |
} else {
|
|
| 2331 |
# Directly dose all patients |
|
| 2332 | 7x |
dlts <- rbinom( |
| 2333 | 7x |
n = cohort_size, |
| 2334 | 7x |
size = 1L, |
| 2335 | 7x |
prob = dle_prob |
| 2336 |
) |
|
| 2337 | 7x |
eff_responses <- rnorm( |
| 2338 | 7x |
n = cohort_size, |
| 2339 | 7x |
mean = mean_eff, |
| 2340 | 7x |
sd = sqrt(true_sigma2) |
| 2341 |
) |
|
| 2342 | ||
| 2343 | 7x |
if (data@placebo && (placebo_size > 0L)) {
|
| 2344 | 3x |
dlts_pl <- rbinom( |
| 2345 | 3x |
n = placebo_size, |
| 2346 | 3x |
size = 1L, |
| 2347 | 3x |
prob = dle_prob_pl |
| 2348 |
) |
|
| 2349 | 3x |
eff_responses_pl <- rnorm( |
| 2350 | 3x |
n = placebo_size, |
| 2351 | 3x |
mean = mean_eff_pl, |
| 2352 | 3x |
sd = sqrt(true_sigma2) |
| 2353 |
) |
|
| 2354 |
} |
|
| 2355 |
} |
|
| 2356 | ||
| 2357 |
## update the data with this placebo (if any) cohort and then with active dose |
|
| 2358 | 7x |
if (data@placebo && (placebo_size > 0L)) {
|
| 2359 | 3x |
data <- update( |
| 2360 | 3x |
object = data, |
| 2361 | 3x |
x = object@data@doseGrid[1], |
| 2362 | 3x |
y = dlts_pl, |
| 2363 | 3x |
w = eff_responses_pl, |
| 2364 | 3x |
check = FALSE |
| 2365 |
) |
|
| 2366 | ||
| 2367 |
# Update the data with active dose |
|
| 2368 | 3x |
data <- update( |
| 2369 | 3x |
object = data, |
| 2370 | 3x |
x = dose, |
| 2371 | 3x |
y = dlts, |
| 2372 | 3x |
w = eff_responses, |
| 2373 | 3x |
new_cohort = FALSE |
| 2374 |
) |
|
| 2375 |
} else {
|
|
| 2376 |
# Update the data with this cohort |
|
| 2377 | 4x |
data <- update( |
| 2378 | 4x |
object = data, |
| 2379 | 4x |
x = dose, |
| 2380 | 4x |
y = dlts, |
| 2381 | 4x |
w = eff_responses |
| 2382 |
) |
|
| 2383 |
} |
|
| 2384 | ||
| 2385 |
# Update models with new data |
|
| 2386 | 7x |
dle_model <- update( |
| 2387 | 7x |
object = object@model, |
| 2388 | 7x |
data = data |
| 2389 |
) |
|
| 2390 | ||
| 2391 | 7x |
eff_model <- update( |
| 2392 | 7x |
object = object@eff_model, |
| 2393 | 7x |
data = data |
| 2394 |
) |
|
| 2395 | ||
| 2396 | 7x |
nu <- eff_model@nu |
| 2397 | ||
| 2398 | 7x |
dle_samples <- mcmc( |
| 2399 | 7x |
data = data, |
| 2400 | 7x |
model = dle_model, |
| 2401 | 7x |
options = mcmcOptions |
| 2402 |
) |
|
| 2403 | ||
| 2404 | 7x |
eff_samples <- mcmc( |
| 2405 | 7x |
data = data, |
| 2406 | 7x |
model = eff_model, |
| 2407 | 7x |
options = mcmcOptions |
| 2408 |
) |
|
| 2409 | ||
| 2410 | 7x |
sigma2 <- if (eff_model@use_fixed) {
|
| 2411 | ! |
1 / nu |
| 2412 |
} else {
|
|
| 2413 | 7x |
1 / (as.numeric(nu["a"] / nu["b"])) |
| 2414 |
} |
|
| 2415 | ||
| 2416 |
# Calculate dose limit |
|
| 2417 | 7x |
dose_limit <- maxDose(object@increments, data = data) |
| 2418 | ||
| 2419 |
# Calculate next best dose |
|
| 2420 | 7x |
next_bd <- nextBest( |
| 2421 | 7x |
object@nextBest, |
| 2422 | 7x |
doselimit = dose_limit, |
| 2423 | 7x |
samples = dle_samples, |
| 2424 | 7x |
model = dle_model, |
| 2425 | 7x |
data = data, |
| 2426 | 7x |
model_eff = eff_model, |
| 2427 | 7x |
samples_eff = eff_samples, |
| 2428 | 7x |
in_sim = TRUE |
| 2429 |
) |
|
| 2430 | ||
| 2431 |
# Extract dose recommendations |
|
| 2432 | 7x |
dose <- next_bd$next_dose |
| 2433 | 7x |
td_target_during_trial <- next_bd$dose_target_drt |
| 2434 | 7x |
td_target_during_trial_at_dose_grid <- next_bd$next_dose_drt |
| 2435 | 7x |
td_target_end_of_trial <- next_bd$dose_target_eot |
| 2436 | 7x |
td_target_end_of_trial_at_dose_grid <- next_bd$next_dose_eot |
| 2437 | 7x |
gstar <- next_bd$dose_max_gain |
| 2438 | 7x |
gstar_at_dose_grid <- next_bd$next_dose_max_gain |
| 2439 | ||
| 2440 | 7x |
recommend <- min( |
| 2441 | 7x |
td_target_end_of_trial_at_dose_grid, |
| 2442 | 7x |
gstar_at_dose_grid |
| 2443 |
) |
|
| 2444 | ||
| 2445 |
# Calculate 95% confidence intervals and ratios |
|
| 2446 | 7x |
ci_tdeot <- list( |
| 2447 | 7x |
lower = next_bd$ci_dose_target_eot[1], |
| 2448 | 7x |
upper = next_bd$ci_dose_target_eot[2] |
| 2449 |
) |
|
| 2450 | 7x |
ratio_tdeot <- next_bd$ci_ratio_dose_target_eot |
| 2451 | ||
| 2452 | 7x |
ci_gstar <- list( |
| 2453 | 7x |
lower = next_bd$ci_dose_max_gain[1], |
| 2454 | 7x |
upper = next_bd$ci_dose_max_gain[2] |
| 2455 |
) |
|
| 2456 | 7x |
ratio_gstar <- next_bd$ci_ratio_dose_max_gain |
| 2457 | ||
| 2458 |
# Find the optimal dose |
|
| 2459 | 7x |
optimal_dose <- min(gstar, td_target_end_of_trial) |
| 2460 | ||
| 2461 | 7x |
if (optimal_dose == gstar) {
|
| 2462 | ! |
ratio <- ratio_gstar |
| 2463 | ! |
ci <- ci_gstar |
| 2464 |
} else {
|
|
| 2465 | 7x |
ratio <- ratio_tdeot |
| 2466 | 7x |
ci <- ci_tdeot |
| 2467 |
} |
|
| 2468 | ||
| 2469 |
# Evaluate stopping rules |
|
| 2470 | 7x |
should_stop <- stopTrial( |
| 2471 | 7x |
object@stopping, |
| 2472 | 7x |
dose = dose, |
| 2473 | 7x |
samples = dle_samples, |
| 2474 | 7x |
model = dle_model, |
| 2475 | 7x |
data = data, |
| 2476 | 7x |
TDderive = object@nextBest@derive, |
| 2477 | 7x |
Effmodel = eff_model, |
| 2478 | 7x |
Effsamples = eff_samples, |
| 2479 | 7x |
Gstarderive = object@nextBest@mg_derive |
| 2480 |
) |
|
| 2481 | 7x |
stop_results <- h_unpack_stopit(should_stop) |
| 2482 |
} |
|
| 2483 |
# Calculate final model fits |
|
| 2484 | 2x |
dle_fit <- fit( |
| 2485 | 2x |
object = dle_samples, |
| 2486 | 2x |
model = dle_model, |
| 2487 | 2x |
data = data |
| 2488 |
) |
|
| 2489 | ||
| 2490 | 2x |
eff_fit <- fit( |
| 2491 | 2x |
object = eff_samples, |
| 2492 | 2x |
model = eff_model, |
| 2493 | 2x |
data = data |
| 2494 |
) |
|
| 2495 | ||
| 2496 |
# Return simulation results |
|
| 2497 | 2x |
list( |
| 2498 | 2x |
data = data, |
| 2499 | 2x |
dose = dose, |
| 2500 | 2x |
TDtargetDuringTrial = td_target_during_trial, |
| 2501 | 2x |
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid, |
| 2502 | 2x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 2503 | 2x |
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid, |
| 2504 | 2x |
Gstar = gstar, |
| 2505 | 2x |
GstarAtDoseGrid = gstar_at_dose_grid, |
| 2506 | 2x |
Recommend = recommend, |
| 2507 | 2x |
OptimalDose = optimal_dose, |
| 2508 | 2x |
OptimalDoseAtDoseGrid = recommend, |
| 2509 | 2x |
ratio = ratio, |
| 2510 | 2x |
CI = ci, |
| 2511 | 2x |
ratioGstar = ratio_gstar, |
| 2512 | 2x |
CIGstar = ci_gstar, |
| 2513 | 2x |
ratioTDEOT = ratio_tdeot, |
| 2514 | 2x |
CITDEOT = ci_tdeot, |
| 2515 | 2x |
fitDLE = subset(dle_fit, select = c(middle, lower, upper)), |
| 2516 | 2x |
fitEff = subset(eff_fit, select = c(middle, lower, upper)), |
| 2517 | 2x |
sigma2est = sigma2, |
| 2518 | 2x |
stop = attr( |
| 2519 | 2x |
should_stop, |
| 2520 | 2x |
"message" |
| 2521 |
), |
|
| 2522 | 2x |
report_results = stop_results |
| 2523 |
) |
|
| 2524 |
} |
|
| 2525 | ||
| 2526 | 2x |
result_list <- get_result_list( |
| 2527 | 2x |
fun = run_sim, |
| 2528 | 2x |
nsim = nsim, |
| 2529 | 2x |
vars = c( |
| 2530 | 2x |
"sim_seeds", |
| 2531 | 2x |
"args", |
| 2532 | 2x |
"n_args", |
| 2533 | 2x |
"firstSeparate", |
| 2534 | 2x |
"trueDLE", |
| 2535 | 2x |
"trueEff", |
| 2536 | 2x |
"trueNu", |
| 2537 | 2x |
"object" |
| 2538 |
), |
|
| 2539 | 2x |
parallel = parallel, |
| 2540 | 2x |
n_cores = nCores |
| 2541 |
) |
|
| 2542 | ||
| 2543 |
# Process simulation results |
|
| 2544 | 2x |
data_list <- lapply(result_list, "[[", "data") |
| 2545 | 2x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend")) |
| 2546 | ||
| 2547 |
# Extract TD target estimates |
|
| 2548 | 2x |
td_target_during_trial_list <- as.numeric(sapply( |
| 2549 | 2x |
result_list, |
| 2550 |
"[[", |
|
| 2551 | 2x |
"TDtargetDuringTrial" |
| 2552 |
)) |
|
| 2553 | ||
| 2554 | 2x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 2555 | 2x |
result_list, |
| 2556 |
"[[", |
|
| 2557 | 2x |
"TDtargetEndOfTrial" |
| 2558 |
)) |
|
| 2559 | ||
| 2560 | 2x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 2561 | 2x |
result_list, |
| 2562 |
"[[", |
|
| 2563 | 2x |
"TDtargetDuringTrialAtDoseGrid" |
| 2564 |
)) |
|
| 2565 | ||
| 2566 | 2x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 2567 | 2x |
result_list, |
| 2568 |
"[[", |
|
| 2569 | 2x |
"TDtargetEndOfTrialAtDoseGrid" |
| 2570 |
)) |
|
| 2571 | ||
| 2572 |
# Extract Gstar and optimal dose estimates |
|
| 2573 | 2x |
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar")) |
| 2574 | ||
| 2575 | 2x |
gstar_at_dose_grid_list <- as.numeric(sapply( |
| 2576 | 2x |
result_list, |
| 2577 |
"[[", |
|
| 2578 | 2x |
"GstarAtDoseGrid" |
| 2579 |
)) |
|
| 2580 | ||
| 2581 | 2x |
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose")) |
| 2582 | ||
| 2583 | 2x |
optimal_dose_at_dose_grid_list <- as.numeric(sapply( |
| 2584 | 2x |
result_list, |
| 2585 |
"[[", |
|
| 2586 | 2x |
"Recommend" |
| 2587 |
)) |
|
| 2588 | ||
| 2589 |
# Extract confidence intervals and ratios |
|
| 2590 | 2x |
ci_list <- lapply(result_list, "[[", "CI") |
| 2591 | 2x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio")) |
| 2592 | ||
| 2593 | 2x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 2594 | 2x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 2595 | ||
| 2596 | 2x |
ci_gstar_list <- lapply(result_list, "[[", "CIGstar") |
| 2597 | 2x |
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar")) |
| 2598 | ||
| 2599 |
# Extract model fits and variance estimates |
|
| 2600 | 2x |
fit_dle_list <- lapply(result_list, "[[", "fitDLE") |
| 2601 | 2x |
fit_eff_list <- lapply(result_list, "[[", "fitEff") |
| 2602 | 2x |
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est")) |
| 2603 | ||
| 2604 |
# Extract stopping information |
|
| 2605 | 2x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 2606 | ||
| 2607 | 2x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 2608 | 2x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 2609 | ||
| 2610 |
# Return simulation results |
|
| 2611 | 2x |
PseudoDualSimulations( |
| 2612 | 2x |
data = data_list, |
| 2613 | 2x |
doses = recommended_doses, |
| 2614 | 2x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 2615 | 2x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 2616 | 2x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 2617 | 2x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 2618 | 2x |
final_cis = ci_list, |
| 2619 | 2x |
final_ratios = ratio_list, |
| 2620 | 2x |
final_gstar_estimates = gstar_list, |
| 2621 | 2x |
final_gstar_at_dose_grid = gstar_at_dose_grid_list, |
| 2622 | 2x |
final_gstar_cis = ci_gstar_list, |
| 2623 | 2x |
final_gstar_ratios = ratio_gstar_list, |
| 2624 | 2x |
final_tdeot_cis = ci_tdeot_list, |
| 2625 | 2x |
final_tdeot_ratios = ratio_tdeot_list, |
| 2626 | 2x |
final_optimal_dose = optimal_dose_list, |
| 2627 | 2x |
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list, |
| 2628 | 2x |
fit = fit_dle_list, |
| 2629 | 2x |
fit_eff = fit_eff_list, |
| 2630 | 2x |
sigma2_est = sigma2_estimates, |
| 2631 | 2x |
stop_reasons = stop_reasons, |
| 2632 | 2x |
stop_report = stop_report, |
| 2633 | 2x |
seed = rng_state |
| 2634 |
) |
|
| 2635 |
} |
|
| 2636 | ||
| 2637 |
### method definition ---- |
|
| 2638 | ||
| 2639 |
#' Simulate dose escalation procedure using DLE and efficacy responses with samples |
|
| 2640 |
#' |
|
| 2641 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2642 |
#' |
|
| 2643 |
#' This is a method to simulate dose escalation procedure using both DLE and efficacy responses. |
|
| 2644 |
#' This is a method based on the [`DualResponsesSamplesDesign`] where DLE model used are of |
|
| 2645 |
#' [`ModelTox`] class object and efficacy model used are of [`ModelEff`] |
|
| 2646 |
#' class object (special case is [`EffFlexi`] class model object). |
|
| 2647 |
#' In addition, DLE and efficacy samples are involved or generated in the simulation |
|
| 2648 |
#' process. |
|
| 2649 |
#' |
|
| 2650 |
#' @param object the [`DualResponsesSamplesDesign`] object we want to |
|
| 2651 |
#' simulate the data from |
|
| 2652 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 2653 |
#' @param seed see [set_seed()] |
|
| 2654 |
#' @param trueDLE (`function`)\cr a function which takes as input a dose (vector) and returns the true probability |
|
| 2655 |
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`. |
|
| 2656 |
#' @param trueEff (`function`)\cr a function which takes as input a dose (vector) and returns the expected |
|
| 2657 |
#' efficacy responses (vector). Additional arguments can be supplied in `args`. |
|
| 2658 |
#' @param trueNu (`number`)\cr (not with [`EffFlexi`]) the precision, the inverse of the |
|
| 2659 |
#' variance of the efficacy responses |
|
| 2660 |
#' @param trueSigma2 (`number`)\cr (only with [`EffFlexi`]) the true variance of the efficacy |
|
| 2661 |
#' responses which must be a single positive scalar. |
|
| 2662 |
#' @param trueSigma2betaW (`number`)\cr (only with [`EffFlexi`]) the true variance for the |
|
| 2663 |
#' random walk model used for smoothing. This must be a single positive scalar. |
|
| 2664 |
#' @param args (`data.frame`)\cr data frame with arguments for the `trueDLE` and |
|
| 2665 |
#' `trueEff` function. The column names correspond to the argument |
|
| 2666 |
#' names, the rows to the values of the arguments. The rows are appropriately |
|
| 2667 |
#' recycled in the `nsim` simulations. |
|
| 2668 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 2669 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 2670 |
#' in this patient. |
|
| 2671 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 2672 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 2673 |
#' the standard options are used |
|
| 2674 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 2675 |
#' clusters of the computer? (not default) |
|
| 2676 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 2677 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 2678 |
#' @param ... not used |
|
| 2679 |
#' |
|
| 2680 |
#' @return an object of class [`PseudoDualSimulations`] or |
|
| 2681 |
#' [`PseudoDualFlexiSimulations`] |
|
| 2682 |
#' |
|
| 2683 |
#' @example examples/design-method-simulateDualResponsesSamplesDesign.R |
|
| 2684 |
#' @export |
|
| 2685 |
setMethod( |
|
| 2686 |
f = "simulate", |
|
| 2687 |
signature = signature( |
|
| 2688 |
object = "DualResponsesSamplesDesign", |
|
| 2689 |
nsim = "ANY", |
|
| 2690 |
seed = "ANY" |
|
| 2691 |
), |
|
| 2692 |
definition = function( |
|
| 2693 |
object, |
|
| 2694 |
nsim = 1L, |
|
| 2695 |
seed = NULL, |
|
| 2696 |
trueDLE, |
|
| 2697 |
trueEff, |
|
| 2698 |
trueNu = NULL, |
|
| 2699 |
trueSigma2 = NULL, |
|
| 2700 |
trueSigma2betaW = NULL, |
|
| 2701 |
args = NULL, |
|
| 2702 |
firstSeparate = FALSE, |
|
| 2703 |
mcmcOptions = McmcOptions(), |
|
| 2704 |
parallel = FALSE, |
|
| 2705 |
nCores = min(parallel::detectCores(), 5L), |
|
| 2706 |
... |
|
| 2707 |
) {
|
|
| 2708 |
# Common checks and validations |
|
| 2709 | 3x |
assert_function(trueDLE) |
| 2710 | 3x |
assert_flag(firstSeparate) |
| 2711 | 3x |
assert_count(nsim, positive = TRUE) |
| 2712 | 3x |
assert_flag(parallel) |
| 2713 | 3x |
assert_count(nCores, positive = TRUE) |
| 2714 | ||
| 2715 |
# Check if special case applies |
|
| 2716 | 3x |
is_flexi <- is(object@eff_model, "EffFlexi") |
| 2717 | ||
| 2718 |
# Dispatch to appropriate helper based on model type |
|
| 2719 | 3x |
if (is_flexi) {
|
| 2720 | 1x |
h_simulate_flexi( |
| 2721 | 1x |
object = object, |
| 2722 | 1x |
nsim = nsim, |
| 2723 | 1x |
seed = seed, |
| 2724 | 1x |
trueDLE = trueDLE, |
| 2725 | 1x |
trueEff = trueEff, |
| 2726 | 1x |
trueSigma2 = trueSigma2, |
| 2727 | 1x |
trueSigma2betaW = trueSigma2betaW, |
| 2728 | 1x |
args = args, |
| 2729 | 1x |
firstSeparate = firstSeparate, |
| 2730 | 1x |
mcmcOptions = mcmcOptions, |
| 2731 | 1x |
parallel = parallel, |
| 2732 | 1x |
nCores = nCores |
| 2733 |
) |
|
| 2734 |
} else {
|
|
| 2735 | 2x |
h_simulate_nonflexi( |
| 2736 | 2x |
object = object, |
| 2737 | 2x |
nsim = nsim, |
| 2738 | 2x |
seed = seed, |
| 2739 | 2x |
trueDLE = trueDLE, |
| 2740 | 2x |
trueEff = trueEff, |
| 2741 | 2x |
trueNu = trueNu, |
| 2742 | 2x |
args = args, |
| 2743 | 2x |
firstSeparate = firstSeparate, |
| 2744 | 2x |
mcmcOptions = mcmcOptions, |
| 2745 | 2x |
parallel = parallel, |
| 2746 | 2x |
nCores = nCores |
| 2747 |
) |
|
| 2748 |
} |
|
| 2749 |
} |
|
| 2750 |
) |
|
| 2751 | ||
| 2752 |
## DADesign ---- |
|
| 2753 | ||
| 2754 |
#' Simulate outcomes from a time-to-DLT augmented CRM design |
|
| 2755 |
#' |
|
| 2756 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2757 |
#' |
|
| 2758 |
#' This method simulates dose escalation trials using time-to-DLT data, |
|
| 2759 |
#' where the timing of dose-limiting toxicities is explicitly modeled. |
|
| 2760 |
#' |
|
| 2761 |
#' @param object the [`DADesign`] object we want to simulate data from |
|
| 2762 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 2763 |
#' @param seed see [set_seed()] |
|
| 2764 |
#' @param truthTox (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 2765 |
#' true probability (vector) for toxicity and the time DLT occurs. Additional |
|
| 2766 |
#' arguments can be supplied in `args`. |
|
| 2767 |
#' @param truthSurv (`function`)\cr a CDF which takes as input a time (vector) and returns |
|
| 2768 |
#' the true cumulative probability (vector) that the DLT would occur conditioning on the patient |
|
| 2769 |
#' has DLTs. |
|
| 2770 |
#' @param trueTmax (`number` or `NULL`)\cr the true maximum time at which DLTs can occur. |
|
| 2771 |
#' Note that this must be larger than `Tmax` from the `object`'s base data, which is |
|
| 2772 |
#' the length of the DLT window, i.e. until which time DLTs are officially declared |
|
| 2773 |
#' as such and used in the trial. |
|
| 2774 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truthTox` function. The |
|
| 2775 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 2776 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 2777 |
#' simulations. In order to produce outcomes from the posterior predictive |
|
| 2778 |
#' distribution, e.g, pass an `object` that contains the data observed so |
|
| 2779 |
#' far, `truthTox` contains the `prob` function from the model in |
|
| 2780 |
#' `object`, and `args` contains posterior samples from the model. |
|
| 2781 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 2782 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 2783 |
#' in this patient. |
|
| 2784 |
#' @param deescalate (`flag`)\cr allow deescalation when a DLT occurs in cohorts with lower dose |
|
| 2785 |
#' level? (default: TRUE) |
|
| 2786 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 2787 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 2788 |
#' the standard options are used. |
|
| 2789 |
#' @param DA (`flag`)\cr use dose-adaptation rules? (default: TRUE) |
|
| 2790 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 2791 |
#' clusters of the computer? (not default) |
|
| 2792 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 2793 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 2794 |
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the |
|
| 2795 |
#' vector of posterior MTD samples. Each list element must therefore accept |
|
| 2796 |
#' one and only one argument, which is a numeric vector, and return a number. |
|
| 2797 |
#' @param ... not used |
|
| 2798 |
#' |
|
| 2799 |
#' @return an object of class [`Simulations`] |
|
| 2800 |
#' |
|
| 2801 |
#' @note Backfill cohorts are not yet implemented and therefore will lead to an error if used |
|
| 2802 |
#' in the `DADesign` object. |
|
| 2803 |
#' |
|
| 2804 |
#' @example examples/design-method-simulate-DADesign.R |
|
| 2805 |
#' @export |
|
| 2806 |
setMethod( |
|
| 2807 |
f = "simulate", |
|
| 2808 |
signature = signature( |
|
| 2809 |
object = "DADesign", |
|
| 2810 |
nsim = "ANY", |
|
| 2811 |
seed = "ANY" |
|
| 2812 |
), |
|
| 2813 |
definition = function( |
|
| 2814 |
object, |
|
| 2815 |
nsim = 1L, |
|
| 2816 |
seed = NULL, |
|
| 2817 |
truthTox, |
|
| 2818 |
truthSurv, |
|
| 2819 |
trueTmax = NULL, |
|
| 2820 |
args = NULL, |
|
| 2821 |
firstSeparate = FALSE, |
|
| 2822 |
deescalate = TRUE, |
|
| 2823 |
mcmcOptions = McmcOptions(), |
|
| 2824 |
DA = TRUE, |
|
| 2825 |
parallel = FALSE, |
|
| 2826 |
nCores = min(parallel::detectCores(), 5), |
|
| 2827 |
derive = list(), |
|
| 2828 |
... |
|
| 2829 |
) {
|
|
| 2830 |
# Validate inputs |
|
| 2831 | 2x |
assert_function(truthTox) |
| 2832 | 2x |
assert_function(truthSurv) |
| 2833 | 2x |
assert_flag(firstSeparate) |
| 2834 | 2x |
assert_count(nsim, positive = TRUE) |
| 2835 | 2x |
assert_flag(parallel) |
| 2836 | 2x |
assert_count(nCores, positive = TRUE) |
| 2837 | 2x |
assert_class(object@backfill@opening, "OpeningNone") |
| 2838 | ||
| 2839 | 2x |
args <- as.data.frame(args) |
| 2840 | 2x |
n_args <- max(nrow(args), 1L) |
| 2841 | ||
| 2842 |
# Seed handling |
|
| 2843 | 2x |
rng_state <- set_seed(seed) |
| 2844 | ||
| 2845 |
# Generate individual seeds for simulation runs |
|
| 2846 | 2x |
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
| 2847 | ||
| 2848 |
# Define inverse function for DLT survival generation. |
|
| 2849 | 2x |
inverse <- function(f, lower = -100, upper = 100) {
|
| 2850 | 2x |
function(y) {
|
| 2851 | 5x |
uniroot((function(x) f(x) - y), lower = lower, upper = upper)[1]$root |
| 2852 |
} |
|
| 2853 |
} |
|
| 2854 | ||
| 2855 |
# Get DLT window length. |
|
| 2856 | 2x |
data <- object@data |
| 2857 | 2x |
t_max <- data@Tmax |
| 2858 | ||
| 2859 | 2x |
if (is.null(trueTmax)) {
|
| 2860 | ! |
trueTmax <- t_max |
| 2861 | 2x |
} else if (trueTmax < t_max) {
|
| 2862 | ! |
warning("trueTmax < Tmax! trueTmax is set to Tmax")
|
| 2863 | ! |
trueTmax <- t_max |
| 2864 |
} |
|
| 2865 | ||
| 2866 |
# Calculate the inverse function of survival to DLT CDF. |
|
| 2867 | 2x |
inverse_truth_surv <- inverse(truthSurv, 0, trueTmax) |
| 2868 | ||
| 2869 |
# Generate random survival times for DLT data. |
|
| 2870 |
# Returns t_max when no DLT occurs. |
|
| 2871 | 2x |
generate_surv_times <- function( |
| 2872 | 2x |
dlt, |
| 2873 | 2x |
t_max, |
| 2874 | 2x |
inverse_surv = inverse_truth_surv |
| 2875 |
) {
|
|
| 2876 | 33x |
surv_times <- rep(-100, length(dlt)) |
| 2877 | ||
| 2878 | 33x |
if (sum(dlt == 0) > 0) {
|
| 2879 | 29x |
surv_times[dlt == 0] <- t_max |
| 2880 |
} |
|
| 2881 | ||
| 2882 | 33x |
if (sum(dlt == 1) > 0) {
|
| 2883 | 5x |
surv_times[dlt == 1] <- unlist(lapply( |
| 2884 | 5x |
runif(sum(dlt == 1), 0, 1), |
| 2885 | 5x |
inverse_surv |
| 2886 |
)) |
|
| 2887 |
} |
|
| 2888 | ||
| 2889 |
# Ensure results are always positive. |
|
| 2890 | 33x |
surv_times[surv_times <= 0] <- 0.05 |
| 2891 | 33x |
surv_times |
| 2892 |
} |
|
| 2893 | ||
| 2894 |
# Check if follow-up requirements are fulfilled for opening next cohort. |
|
| 2895 | 2x |
ready_to_open <- function(day, window, surv_times) {
|
| 2896 | 1221x |
cohort_size <- length(surv_times) |
| 2897 |
# Calculate when patients start. |
|
| 2898 | 1221x |
start_time <- apply( |
| 2899 | 1221x |
rbind(surv_times[-cohort_size], window$patientGap[-1]), |
| 2900 | 1221x |
2, |
| 2901 | 1221x |
min |
| 2902 |
) |
|
| 2903 |
# Calculate relative time for each patient on the specified day. |
|
| 2904 | 1221x |
individual_check <- day - cumsum(c(0, start_time)) |
| 2905 |
# Ensure minimum is 0. |
|
| 2906 | 1221x |
individual_check[individual_check < 0] <- 0 |
| 2907 | 1221x |
follow_up <- apply(rbind(surv_times, individual_check), 2, min) |
| 2908 | ||
| 2909 | 1221x |
all( |
| 2910 | 1221x |
(follow_up - |
| 2911 | 1221x |
apply(rbind(window$patientFollow, surv_times), 2, min)) >= |
| 2912 | 1221x |
0 |
| 2913 |
) & |
|
| 2914 | 1221x |
(max(follow_up) >= min(window$patientFollowMin, max(surv_times))) |
| 2915 |
} |
|
| 2916 | ||
| 2917 |
# Determine when to open the next cohort. |
|
| 2918 |
# Assumes sufficient patients are available for immediate enrollment. |
|
| 2919 | 2x |
next_open <- function(window, surv_times) {
|
| 2920 | 22x |
cohort_size <- length(surv_times) |
| 2921 | ||
| 2922 | 22x |
window$patientGap <- window$patientGap[1:cohort_size] |
| 2923 |
# If DLT happens before end of DLT window, next cohort opens earlier. |
|
| 2924 | 22x |
start_time <- apply( |
| 2925 | 22x |
rbind(surv_times[-cohort_size], window$patientGap[-1]), |
| 2926 | 22x |
2, |
| 2927 | 22x |
min |
| 2928 |
) |
|
| 2929 |
# Duration until all DLT windows finished. |
|
| 2930 | 22x |
max_time <- max(surv_times + cumsum(c(0, start_time))) |
| 2931 | ||
| 2932 | 22x |
requirements_met <- sapply(1:max_time, function(i) {
|
| 2933 | 1221x |
ready_to_open(i, window, surv_times) |
| 2934 |
}) |
|
| 2935 | 22x |
if (sum(requirements_met) > 0) {
|
| 2936 |
# Earliest time that requirements are met. |
|
| 2937 | 22x |
time <- min(c(1:max_time)[requirements_met]) |
| 2938 |
} else {
|
|
| 2939 | ! |
time <- max_time |
| 2940 |
} |
|
| 2941 | 22x |
time |
| 2942 |
} |
|
| 2943 | ||
| 2944 |
# Function to run a single simulation. |
|
| 2945 | 2x |
run_sim <- function(iter_sim) {
|
| 2946 |
# Set the seed for this run. |
|
| 2947 | 4x |
set.seed(sim_seeds[iter_sim]) |
| 2948 | ||
| 2949 |
# Get current arguments (appropriately recycled). |
|
| 2950 | 4x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 2951 | ||
| 2952 |
# Truth function with current arguments. |
|
| 2953 | 4x |
truth_with_args <- function(dose) {
|
| 2954 | 24x |
do.call( |
| 2955 | 24x |
truthTox, |
| 2956 | 24x |
c( |
| 2957 | 24x |
dose, |
| 2958 | 24x |
current_args |
| 2959 |
) |
|
| 2960 |
) |
|
| 2961 |
} |
|
| 2962 | ||
| 2963 |
# Start with the provided data. |
|
| 2964 | 4x |
data <- object@data |
| 2965 | ||
| 2966 |
# Handle placebo if present. |
|
| 2967 | 4x |
if (data@placebo) {
|
| 2968 | 2x |
prob_pl <- truth_with_args(object@data@doseGrid[1]) |
| 2969 |
} |
|
| 2970 | ||
| 2971 |
# Trial control variables. |
|
| 2972 | 4x |
should_stop <- FALSE |
| 2973 | 4x |
trial_time <- 0 |
| 2974 | ||
| 2975 |
# Initialize observed DLT data. |
|
| 2976 | 4x |
observed_dlts <- data@y |
| 2977 | 4x |
observed_surv <- data@u |
| 2978 | 4x |
observed_t0 <- data@t0 |
| 2979 | ||
| 2980 |
# Initialize with starting dose. |
|
| 2981 | 4x |
dose <- object@startingDose |
| 2982 | ||
| 2983 |
# Main simulation loop. |
|
| 2984 | 4x |
while (!should_stop) {
|
| 2985 |
# Calculate toxicity probability at current dose. |
|
| 2986 | 22x |
prob <- truth_with_args(dose) |
| 2987 | ||
| 2988 |
# Determine cohort size. |
|
| 2989 | 22x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 2990 | ||
| 2991 | 22x |
if (data@placebo) {
|
| 2992 | 9x |
placebo_size <- size( |
| 2993 | 9x |
object@pl_cohort_size, |
| 2994 | 9x |
dose = dose, |
| 2995 | 9x |
data = data |
| 2996 |
) |
|
| 2997 |
} |
|
| 2998 | ||
| 2999 | 22x |
max_size <- if (data@placebo) {
|
| 3000 | 9x |
cohort_size + placebo_size |
| 3001 |
} else {
|
|
| 3002 | 13x |
cohort_size |
| 3003 |
} |
|
| 3004 | ||
| 3005 | 22x |
safety_window <- windowLength(object@safetyWindow, max_size) |
| 3006 | ||
| 3007 |
# Simulate DLTs for cohort. |
|
| 3008 |
# If any patient has DLT before first patient finishes staggered window, |
|
| 3009 |
# further enrollment will be stopped. |
|
| 3010 | 22x |
h_generate_dlt_and_surv <- function(n, prob, start = NULL) {
|
| 3011 | 33x |
dlts <- rbinom( |
| 3012 | 33x |
n = n, |
| 3013 | 33x |
size = 1L, |
| 3014 | 33x |
prob = prob |
| 3015 |
) |
|
| 3016 | 33x |
surv_times <- ceiling(generate_surv_times( |
| 3017 | 33x |
dlts, |
| 3018 | 33x |
trueTmax, |
| 3019 | 33x |
inverse_surv = inverse_truth_surv |
| 3020 |
)) |
|
| 3021 | ||
| 3022 | 33x |
if (!is.null(start)) {
|
| 3023 | 2x |
dlts <- c(start$dlts, dlts) |
| 3024 | 2x |
surv_times <- c(start$surv, surv_times) |
| 3025 |
} |
|
| 3026 | ||
| 3027 | 33x |
if (t_max < trueTmax) {
|
| 3028 | 33x |
dlts[dlts == 1 & surv_times > t_max] <- 0 |
| 3029 | ||
| 3030 | 33x |
surv_times <- apply( |
| 3031 | 33x |
rbind(surv_times, rep(t_max, length(surv_times))), |
| 3032 | 33x |
2, |
| 3033 | 33x |
min |
| 3034 |
) |
|
| 3035 |
} |
|
| 3036 | ||
| 3037 | 33x |
list(dlts = dlts, surv = surv_times) |
| 3038 |
} |
|
| 3039 | ||
| 3040 |
# Update data with active and placebo cohorts. |
|
| 3041 | 22x |
h_update_data_da <- function(active, placebo, time) {
|
| 3042 | 5x |
result <- update( |
| 3043 | 5x |
object = data, |
| 3044 | 5x |
y = c(observed_dlts, active$dlts), |
| 3045 | 5x |
u = c(observed_surv, active$surv), |
| 3046 | 5x |
t0 = c(observed_t0, cohort_t0), |
| 3047 | 5x |
x = dose, |
| 3048 | 5x |
trialtime = time |
| 3049 |
) |
|
| 3050 | ||
| 3051 | 5x |
if (data@placebo) {
|
| 3052 | ! |
result <- update( |
| 3053 | ! |
object = result, |
| 3054 | ! |
y = c(observed_dlts, active$dlts, placebo$dlts), |
| 3055 | ! |
u = c(observed_surv, active$surv, placebo$surv), |
| 3056 | ! |
t0 = c( |
| 3057 | ! |
observed_t0, |
| 3058 | ! |
cohort_t0, |
| 3059 | ! |
rep(cohort_t0[1], length(placebo$dlts)) |
| 3060 |
), |
|
| 3061 | ! |
x = object@data@doseGrid[1], |
| 3062 | ! |
trialtime = time |
| 3063 |
) |
|
| 3064 |
} |
|
| 3065 | ||
| 3066 | 5x |
result |
| 3067 |
} |
|
| 3068 | ||
| 3069 | 22x |
if (firstSeparate && (cohort_size > 1L)) {
|
| 3070 |
# Dose the first patient. |
|
| 3071 | 5x |
active_dlt_surv <- h_generate_dlt_and_surv(1L, prob) |
| 3072 | 5x |
placebo_dlt_surv <- if (data@placebo && (placebo_size > 0L)) {
|
| 3073 |
# If placebo, also dose one placebo patient. |
|
| 3074 | ! |
h_generate_dlt_and_surv(1L, prob_pl) |
| 3075 |
} else {
|
|
| 3076 | 5x |
list() |
| 3077 |
} |
|
| 3078 | ||
| 3079 | 5x |
cohort_t0 <- trial_time |
| 3080 | ||
| 3081 |
# Check if there are DLTs during safety window. |
|
| 3082 | 5x |
temp_data <- h_update_data_da( |
| 3083 | 5x |
active_dlt_surv, |
| 3084 | 5x |
placebo_dlt_surv, |
| 3085 | 5x |
trial_time + safety_window$patientGap[2] |
| 3086 |
) |
|
| 3087 | 5x |
temp_time <- (temp_data@u + temp_data@t0)[ |
| 3088 | 5x |
temp_data@y == 1 & temp_data@x <= dose |
| 3089 |
] |
|
| 3090 | ||
| 3091 |
# If no DLTs occur during safety window, enroll remaining patients. |
|
| 3092 | 5x |
if (sum(temp_time > trial_time) == 0) {
|
| 3093 |
# Enroll the remaining patients. |
|
| 3094 | 2x |
active_dlt_surv <- h_generate_dlt_and_surv( |
| 3095 | 2x |
cohort_size - 1L, |
| 3096 | 2x |
prob, |
| 3097 | 2x |
start = active_dlt_surv |
| 3098 |
) |
|
| 3099 | 2x |
placebo_dlt_surv <- if (data@placebo && (placebo_size > 1L)) {
|
| 3100 | ! |
h_generate_dlt_and_surv( |
| 3101 | ! |
placebo_size - 1L, |
| 3102 | ! |
prob_pl, |
| 3103 | ! |
start = placebo_dlt_surv |
| 3104 |
) |
|
| 3105 |
} else {
|
|
| 3106 | 2x |
list() |
| 3107 |
} |
|
| 3108 | ||
| 3109 |
# Adjust for DLTs happening before end of safety window. |
|
| 3110 | 2x |
real_window <- apply( |
| 3111 | 2x |
rbind( |
| 3112 | 2x |
c(active_dlt_surv$surv, placebo_dlt_surv$surv)[-cohort_size], |
| 3113 | 2x |
safety_window$patientGap[-1] |
| 3114 |
), |
|
| 3115 | 2x |
2, |
| 3116 | 2x |
min |
| 3117 |
) |
|
| 3118 | ||
| 3119 | 2x |
cohort_t0 <- trial_time + c(0, cumsum(real_window)) |
| 3120 |
} |
|
| 3121 | ||
| 3122 | 5x |
rm(temp_data) |
| 3123 | 5x |
rm(temp_time) |
| 3124 |
} else {
|
|
| 3125 |
# Directly dose all patients. |
|
| 3126 | 17x |
active_dlt_surv <- h_generate_dlt_and_surv( |
| 3127 | 17x |
cohort_size, |
| 3128 | 17x |
prob |
| 3129 |
) |
|
| 3130 | 17x |
placebo_dlt_surv <- if (data@placebo) {
|
| 3131 | 9x |
h_generate_dlt_and_surv( |
| 3132 | 9x |
placebo_size, |
| 3133 | 9x |
prob_pl |
| 3134 |
) |
|
| 3135 |
} else {
|
|
| 3136 | 8x |
list() |
| 3137 |
} |
|
| 3138 | ||
| 3139 |
# Adjust for DLTs happening before end of safety window. |
|
| 3140 | 17x |
real_window <- apply( |
| 3141 | 17x |
rbind( |
| 3142 | 17x |
c(active_dlt_surv$surv, placebo_dlt_surv$surv)[-cohort_size], |
| 3143 | 17x |
safety_window$patientGap[-1] |
| 3144 |
), |
|
| 3145 | 17x |
2, |
| 3146 | 17x |
min |
| 3147 |
) |
|
| 3148 | ||
| 3149 | 17x |
cohort_t0 <- trial_time + c(0, cumsum(real_window)) |
| 3150 |
} |
|
| 3151 | ||
| 3152 |
# Update observed data with new cohort. |
|
| 3153 | 22x |
old_dlts <- observed_dlts |
| 3154 | ||
| 3155 | 22x |
observed_dlts <- c( |
| 3156 | 22x |
observed_dlts, |
| 3157 | 22x |
placebo_dlt_surv$dlts, |
| 3158 | 22x |
active_dlt_surv$dlts |
| 3159 |
) |
|
| 3160 | ||
| 3161 | 22x |
observed_surv <- c( |
| 3162 | 22x |
observed_surv, |
| 3163 | 22x |
placebo_dlt_surv$surv, |
| 3164 | 22x |
active_dlt_surv$surv |
| 3165 |
) |
|
| 3166 | ||
| 3167 | 22x |
observed_t0 <- c( |
| 3168 | 22x |
observed_t0, |
| 3169 | 22x |
rep(cohort_t0[1], length(placebo_dlt_surv$dlts)), |
| 3170 | 22x |
rep(cohort_t0, length.out = length(active_dlt_surv$dlts)) |
| 3171 |
) |
|
| 3172 | ||
| 3173 | 22x |
time_to_next <- next_open( |
| 3174 | 22x |
window = safety_window, |
| 3175 | 22x |
surv_times = c(placebo_dlt_surv$surv, active_dlt_surv$surv) |
| 3176 |
) |
|
| 3177 | ||
| 3178 |
# Handle deescalation if DLTs occur in previous cohorts. |
|
| 3179 | 22x |
if (deescalate == TRUE) {
|
| 3180 | 9x |
are_dlts_after_trial_start <- (observed_surv + observed_t0) > |
| 3181 | 9x |
trial_time |
| 3182 | 9x |
are_dlts_before_open_next_cohort <- (observed_surv + |
| 3183 | 9x |
observed_t0 - |
| 3184 | 9x |
trial_time) <= |
| 3185 | 9x |
time_to_next |
| 3186 | 9x |
are_dlts_happening <- observed_dlts == 1 |
| 3187 | 9x |
is_new_dlt <- (are_dlts_after_trial_start & |
| 3188 | 9x |
are_dlts_before_open_next_cohort & |
| 3189 | 9x |
are_dlts_happening) |
| 3190 | ||
| 3191 | 9x |
new_dlt_ids <- seq_along(observed_dlts)[is_new_dlt] |
| 3192 | 9x |
last_id_previous_cohort <- length(old_dlts) |
| 3193 | 9x |
is_new_dlt_in_previous_cohort <- new_dlt_ids <= |
| 3194 | 9x |
last_id_previous_cohort |
| 3195 | ||
| 3196 | 9x |
new_dlt_ids <- new_dlt_ids[is_new_dlt_in_previous_cohort] |
| 3197 | ||
| 3198 | 9x |
if (length(new_dlt_ids) > 0) {
|
| 3199 | ! |
for (this_new_dlt_id in new_dlt_ids) {
|
| 3200 | ! |
this_new_dlt_time <- (observed_surv + observed_t0)[ |
| 3201 | ! |
this_new_dlt_id |
| 3202 |
] |
|
| 3203 | ||
| 3204 |
# Identify patients at higher doses who are impacted. |
|
| 3205 | ! |
later_ids <- c(this_new_dlt_id:length(observed_dlts)) |
| 3206 | ! |
all_doses <- c(data@x, rep(dose, length(active_dlt_surv$dlts))) |
| 3207 | ! |
this_new_dlt_dose <- all_doses[this_new_dlt_id] |
| 3208 | ! |
is_dose_higher_than_this_new_dlt_dose <- all_doses[later_ids] > |
| 3209 | ! |
this_new_dlt_dose |
| 3210 | ! |
ids_to_deescalate <- later_ids[ |
| 3211 | ! |
is_dose_higher_than_this_new_dlt_dose |
| 3212 |
] |
|
| 3213 | ||
| 3214 | ! |
if (length(ids_to_deescalate) > 0) {
|
| 3215 |
# DLT will be observed once follow-up time >= time to DLT. |
|
| 3216 | ! |
this_new_dlt_time_after_followup <- this_new_dlt_time >= |
| 3217 | ! |
(observed_t0[ids_to_deescalate] + |
| 3218 | ! |
observed_surv[ids_to_deescalate]) |
| 3219 | ! |
observed_dlts[ids_to_deescalate] <- as.integer( |
| 3220 | ! |
observed_dlts[ids_to_deescalate] * |
| 3221 | ! |
this_new_dlt_time_after_followup |
| 3222 |
) |
|
| 3223 | ||
| 3224 |
# Some patients in later cohorts may not be enrolled yet when new DLT occurs. |
|
| 3225 |
# Remove those patients from the cohort. |
|
| 3226 | ! |
ids_not_enrolled <- ids_to_deescalate[ |
| 3227 | ! |
(observed_t0[ids_to_deescalate] >= this_new_dlt_time) |
| 3228 |
] |
|
| 3229 | ||
| 3230 | ! |
ids_enrolled <- setdiff( |
| 3231 | ! |
ids_to_deescalate, |
| 3232 | ! |
ids_not_enrolled |
| 3233 |
) |
|
| 3234 | ||
| 3235 |
# Update DLT-free survival time for already enrolled patients. |
|
| 3236 | ! |
if (length(ids_enrolled) > 0) {
|
| 3237 | ! |
surv_time <- pmin( |
| 3238 | ! |
observed_surv[ids_enrolled], |
| 3239 | ! |
this_new_dlt_time - observed_t0[ids_enrolled] |
| 3240 |
) |
|
| 3241 | ! |
assert_true(all(surv_time >= 0)) |
| 3242 | ||
| 3243 | ! |
observed_surv[ids_enrolled] <- surv_time |
| 3244 |
} |
|
| 3245 | ||
| 3246 |
# Remove patients not yet enrolled. |
|
| 3247 | ! |
if (length(ids_not_enrolled) > 0) {
|
| 3248 | ! |
observed_surv <- observed_surv[-ids_not_enrolled] |
| 3249 | ! |
observed_t0 <- observed_t0[-ids_not_enrolled] |
| 3250 | ! |
observed_dlts <- observed_dlts[-ids_not_enrolled] |
| 3251 |
} |
|
| 3252 |
} |
|
| 3253 |
} |
|
| 3254 | ||
| 3255 | ! |
time_to_next <- min( |
| 3256 | ! |
time_to_next, |
| 3257 | ! |
max((observed_surv + observed_t0)[ |
| 3258 | ! |
(length(old_dlts) + 1):length(observed_dlts) |
| 3259 |
]) - |
|
| 3260 | ! |
trial_time |
| 3261 |
) |
|
| 3262 |
} |
|
| 3263 |
} |
|
| 3264 | ||
| 3265 |
# Update trial time. |
|
| 3266 | 22x |
trial_time <- trial_time + time_to_next |
| 3267 | ||
| 3268 |
# Update data object with observations available when next cohort opens. |
|
| 3269 | 22x |
if (data@placebo) {
|
| 3270 |
# First patients are from placebo. |
|
| 3271 | 9x |
data <- update( |
| 3272 | 9x |
object = data, |
| 3273 | 9x |
y = head(observed_dlts, -length(active_dlt_surv$dlts)), |
| 3274 | 9x |
u = head(observed_surv, -length(active_dlt_surv$surv)), |
| 3275 | 9x |
t0 = head(observed_t0, -length(active_dlt_surv$surv)), |
| 3276 | 9x |
x = object@data@doseGrid[1], |
| 3277 | 9x |
trialtime = trial_time |
| 3278 |
) |
|
| 3279 |
} |
|
| 3280 | 22x |
data <- update( |
| 3281 | 22x |
object = data, |
| 3282 | 22x |
y = observed_dlts, |
| 3283 | 22x |
u = observed_surv, |
| 3284 | 22x |
t0 = observed_t0, |
| 3285 | 22x |
x = dose, |
| 3286 | 22x |
trialtime = trial_time |
| 3287 |
) |
|
| 3288 | ||
| 3289 | 22x |
try( |
| 3290 | 22x |
if ( |
| 3291 | 22x |
length(data@x) != length(data@u) || |
| 3292 | 22x |
length(data@u) != length(data@y) |
| 3293 |
) {
|
|
| 3294 | ! |
stop("x,y,u dimension error")
|
| 3295 |
} |
|
| 3296 |
) |
|
| 3297 | ||
| 3298 |
# Calculate dose limit. |
|
| 3299 | 22x |
dose_limit <- maxDose(object@increments, data = data) |
| 3300 | ||
| 3301 |
# Generate MCMC samples from model. |
|
| 3302 | 22x |
if (DA == TRUE) {
|
| 3303 | 22x |
samples <- mcmc( |
| 3304 | 22x |
data = data, |
| 3305 | 22x |
model = object@model, |
| 3306 | 22x |
options = mcmcOptions |
| 3307 |
) |
|
| 3308 | ! |
} else if (DA == FALSE) {
|
| 3309 | ! |
temp_model <- LogisticLogNormal( |
| 3310 | ! |
mean = object@model@params@mean, |
| 3311 | ! |
cov = object@model@params@cov, |
| 3312 | ! |
ref_dose = object@model@refDose |
| 3313 |
) |
|
| 3314 | ||
| 3315 | ! |
truncated_data <- Data( |
| 3316 | ! |
x = data@x, |
| 3317 | ! |
y = data@y, |
| 3318 | ! |
doseGrid = data@doseGrid, |
| 3319 | ! |
cohort = data@cohort, |
| 3320 | ! |
ID = data@ID |
| 3321 |
) |
|
| 3322 | ||
| 3323 | ! |
samples <- mcmc( |
| 3324 | ! |
data = truncated_data, |
| 3325 | ! |
model = temp_model, |
| 3326 | ! |
options = mcmcOptions |
| 3327 |
) |
|
| 3328 |
} |
|
| 3329 | ||
| 3330 |
# Calculate next best dose. |
|
| 3331 | 22x |
dose <- nextBest( |
| 3332 | 22x |
object@nextBest, |
| 3333 | 22x |
doselimit = dose_limit, |
| 3334 | 22x |
samples = samples, |
| 3335 | 22x |
model = object@model, |
| 3336 | 22x |
data = data |
| 3337 | 22x |
)$value |
| 3338 | ||
| 3339 |
# Evaluate stopping rules. |
|
| 3340 | 22x |
should_stop <- stopTrial( |
| 3341 | 22x |
object@stopping, |
| 3342 | 22x |
dose = dose, |
| 3343 | 22x |
samples = samples, |
| 3344 | 22x |
model = object@model, |
| 3345 | 22x |
data = data |
| 3346 |
) |
|
| 3347 | 22x |
stop_results <- h_unpack_stopit(should_stop) |
| 3348 |
} |
|
| 3349 | ||
| 3350 |
# Calculate final model fit. |
|
| 3351 | 4x |
fit_result <- fit( |
| 3352 | 4x |
object = samples, |
| 3353 | 4x |
model = object@model, |
| 3354 | 4x |
data = data |
| 3355 |
) |
|
| 3356 | ||
| 3357 |
# Get MTD estimate from samples. |
|
| 3358 | 4x |
target_dose_samples <- dose( |
| 3359 | 4x |
mean(object@nextBest@target), |
| 3360 | 4x |
model = object@model, |
| 3361 | 4x |
samples = samples |
| 3362 |
) |
|
| 3363 | ||
| 3364 |
# Calculate additional statistics. |
|
| 3365 | 4x |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
| 3366 | ||
| 3367 |
# Return simulation results. |
|
| 3368 | 4x |
list( |
| 3369 | 4x |
data = data, |
| 3370 | 4x |
dose = dose, |
| 3371 | 4x |
duration = trial_time, |
| 3372 | 4x |
fit = subset(fit_result, select = c(middle, lower, upper)), |
| 3373 | 4x |
stop = attr( |
| 3374 | 4x |
should_stop, |
| 3375 | 4x |
"message" |
| 3376 |
), |
|
| 3377 | 4x |
report_results = stop_results, |
| 3378 | 4x |
additional_stats = additional_stats |
| 3379 |
) |
|
| 3380 |
} |
|
| 3381 | ||
| 3382 | 2x |
result_list <- get_result_list( |
| 3383 | 2x |
fun = run_sim, |
| 3384 | 2x |
nsim = nsim, |
| 3385 | 2x |
vars = c( |
| 3386 | 2x |
"sim_seeds", |
| 3387 | 2x |
"args", |
| 3388 | 2x |
"n_args", |
| 3389 | 2x |
"firstSeparate", |
| 3390 | 2x |
"truthTox", |
| 3391 | 2x |
"truthSurv", |
| 3392 | 2x |
"object", |
| 3393 | 2x |
"mcmcOptions", |
| 3394 | 2x |
"next_open", |
| 3395 | 2x |
"ready_to_open" |
| 3396 |
), |
|
| 3397 | 2x |
parallel = parallel, |
| 3398 | 2x |
n_cores = nCores |
| 3399 |
) |
|
| 3400 | ||
| 3401 |
# Process simulation results. |
|
| 3402 | 2x |
data_list <- lapply(result_list, "[[", "data") |
| 3403 | 2x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose")) |
| 3404 | 2x |
trial_duration <- as.numeric(sapply(result_list, "[[", "duration")) |
| 3405 | 2x |
fit_list <- lapply(result_list, "[[", "fit") |
| 3406 | ||
| 3407 | 2x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 3408 | ||
| 3409 | 2x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 3410 | 2x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 3411 | ||
| 3412 | 2x |
additional_stats <- lapply(result_list, "[[", "additional_stats") |
| 3413 | ||
| 3414 |
# Return simulation results. |
|
| 3415 | 2x |
DASimulations( |
| 3416 | 2x |
data = data_list, |
| 3417 | 2x |
doses = recommended_doses, |
| 3418 | 2x |
fit = fit_list, |
| 3419 | 2x |
trial_duration = trial_duration, |
| 3420 | 2x |
stop_report = stop_report, |
| 3421 | 2x |
stop_reasons = stop_reasons, |
| 3422 | 2x |
additional_stats = additional_stats, |
| 3423 | 2x |
seed = rng_state |
| 3424 |
) |
|
| 3425 |
} |
|
| 3426 |
) |
|
| 3427 | ||
| 3428 |
## DesignGrouped ---- |
|
| 3429 | ||
| 3430 |
#' Simulate Method for the [`DesignGrouped`] Class |
|
| 3431 |
#' |
|
| 3432 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3433 |
#' |
|
| 3434 |
#' A simulate method for [`DesignGrouped`] designs. |
|
| 3435 |
#' |
|
| 3436 |
#' @param object (`DesignGrouped`)\cr the design we want to simulate trials from. |
|
| 3437 |
#' @param nsim (`number`)\cr how many trials should be simulated. |
|
| 3438 |
#' @param seed (`RNGstate`)\cr generated with [set_seed()]. |
|
| 3439 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and |
|
| 3440 |
#' returns the true probability (vector) for toxicity for the mono arm. |
|
| 3441 |
#' Additional arguments can be supplied in `args`. |
|
| 3442 |
#' @param combo_truth (`function`)\cr same as `truth` but for the combo arm. |
|
| 3443 |
#' @param args (`data.frame`)\cr optional `data.frame` with arguments that work |
|
| 3444 |
#' for both the `truth` and `combo_truth` functions. The column names correspond to |
|
| 3445 |
#' the argument names, the rows to the values of the arguments. The rows are |
|
| 3446 |
#' appropriately recycled in the `nsim` simulations. |
|
| 3447 |
#' @param firstSeparate (`flag`)\cr whether to enroll the first patient separately |
|
| 3448 |
#' from the rest of the cohort and close the cohort in case a DLT occurs in this |
|
| 3449 |
#' first patient. |
|
| 3450 |
#' @param mcmcOptions (`McmcOptions`)\cr MCMC options for each evaluation in the trial. |
|
| 3451 |
#' @param parallel (`flag`)\cr whether the simulation runs are parallelized across the |
|
| 3452 |
#' cores of the computer. |
|
| 3453 |
#' @param nCores (`number`)\cr how many cores should be used for parallel computing. |
|
| 3454 |
#' @param ... not used. |
|
| 3455 |
#' |
|
| 3456 |
#' @return A list of `mono` and `combo` simulation results as [`Simulations`] objects. |
|
| 3457 |
#' |
|
| 3458 |
#' @aliases simulate-DesignGrouped |
|
| 3459 |
#' @export |
|
| 3460 |
#' @example examples/Design-method-simulate-DesignGrouped.R |
|
| 3461 |
#' |
|
| 3462 |
setMethod( |
|
| 3463 |
"simulate", |
|
| 3464 |
signature = signature( |
|
| 3465 |
object = "DesignGrouped", |
|
| 3466 |
nsim = "ANY", |
|
| 3467 |
seed = "ANY" |
|
| 3468 |
), |
|
| 3469 |
def = function( |
|
| 3470 |
object, |
|
| 3471 |
nsim = 1L, |
|
| 3472 |
seed = NULL, |
|
| 3473 |
truth, |
|
| 3474 |
combo_truth, |
|
| 3475 |
args = data.frame(), |
|
| 3476 |
firstSeparate = FALSE, |
|
| 3477 |
mcmcOptions = McmcOptions(), |
|
| 3478 |
parallel = FALSE, |
|
| 3479 |
nCores = min(parallelly::availableCores(), 5), |
|
| 3480 |
... |
|
| 3481 |
) {
|
|
| 3482 | 9x |
nsim <- as.integer(nsim) |
| 3483 | 9x |
assert_function(truth) |
| 3484 | 9x |
assert_function(combo_truth) |
| 3485 | 9x |
assert_data_frame(args) |
| 3486 | 9x |
assert_count(nsim, positive = TRUE) |
| 3487 | 9x |
assert_flag(firstSeparate) |
| 3488 | 9x |
assert_flag(parallel) |
| 3489 | 9x |
assert_count(nCores, positive = TRUE) |
| 3490 | ||
| 3491 | 9x |
n_args <- max(nrow(args), 1L) |
| 3492 | 9x |
rng_state <- set_seed(seed) |
| 3493 | 9x |
sim_seeds <- sample.int(n = 2147483647, size = nsim) |
| 3494 | ||
| 3495 | 9x |
run_sim <- function(iter_sim) {
|
| 3496 | 16x |
set.seed(sim_seeds[iter_sim]) |
| 3497 | 16x |
current <- list(mono = list(), combo = list()) |
| 3498 |
# Define true toxicity functions. |
|
| 3499 | 16x |
current$args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 3500 | 16x |
current$mono$truth <- function(dose) do.call(truth, c(dose, current$args)) |
| 3501 | 16x |
current$combo$truth <- function(dose) {
|
| 3502 | 63x |
do.call(combo_truth, c(dose, current$args)) |
| 3503 |
} |
|
| 3504 |
# Start the simulated data with the provided one. |
|
| 3505 | 16x |
current$mono$data <- object@mono@data |
| 3506 | 16x |
current$combo$data <- object@combo@data |
| 3507 |
# We are in the first cohort and continue for mono and combo. |
|
| 3508 | 16x |
current$first <- TRUE |
| 3509 | 16x |
current$mono$stop <- current$combo$stop <- FALSE |
| 3510 | ||
| 3511 |
# What are the next doses to be used? Initialize with starting doses. |
|
| 3512 | 9x |
if ( |
| 3513 | 16x |
object@same_dose_for_all || |
| 3514 | 16x |
(!object@first_cohort_mono_only && object@same_dose_for_start) |
| 3515 |
) {
|
|
| 3516 | 6x |
current$mono$dose <- current$combo$dose <- min( |
| 3517 | 6x |
object@mono@startingDose, |
| 3518 | 6x |
object@combo@startingDose |
| 3519 |
) |
|
| 3520 |
} else {
|
|
| 3521 | 10x |
current$mono$dose <- object@mono@startingDose |
| 3522 | 10x |
current$combo$dose <- object@combo@startingDose |
| 3523 |
} |
|
| 3524 | ||
| 3525 |
# Inside this loop we simulate the whole trial, until stopping. |
|
| 3526 | 16x |
while (!(current$mono$stop && current$combo$stop)) {
|
| 3527 | 71x |
if (!current$mono$stop) {
|
| 3528 | 55x |
cohort_size_mono <- size( |
| 3529 | 55x |
object@mono@cohort_size, |
| 3530 | 55x |
dose = current$mono$dose, |
| 3531 | 55x |
data = current$mono$data |
| 3532 |
) |
|
| 3533 | 55x |
this_prob_mono <- current$mono$truth(current$mono$dose) |
| 3534 | 55x |
current$mono$data <- current$mono$data %>% |
| 3535 | 55x |
h_determine_dlts( |
| 3536 | 55x |
dose = current$mono$dose, |
| 3537 | 55x |
prob = this_prob_mono, |
| 3538 | 55x |
cohort_size = cohort_size_mono, |
| 3539 | 55x |
first_separate = firstSeparate |
| 3540 |
) |
|
| 3541 |
} |
|
| 3542 | 16x |
if ( |
| 3543 | 71x |
!current$combo$stop && |
| 3544 | 71x |
(!current$first || !object@first_cohort_mono_only) |
| 3545 |
) {
|
|
| 3546 | 63x |
cohort_size_combo <- size( |
| 3547 | 63x |
object@combo@cohort_size, |
| 3548 | 63x |
dose = current$combo$dose, |
| 3549 | 63x |
data = current$combo$data |
| 3550 |
) |
|
| 3551 | 63x |
this_prob_combo <- current$combo$truth(current$combo$dose) |
| 3552 | 63x |
current$combo$data <- current$combo$data %>% |
| 3553 | 63x |
h_determine_dlts( |
| 3554 | 63x |
dose = current$combo$dose, |
| 3555 | 63x |
prob = this_prob_combo, |
| 3556 | 63x |
cohort_size = cohort_size_combo, |
| 3557 | 63x |
first_separate = firstSeparate |
| 3558 |
) |
|
| 3559 |
} |
|
| 3560 | ||
| 3561 | 71x |
current$grouped <- h_group_data(current$mono$data, current$combo$data) |
| 3562 | 71x |
current$samples <- mcmc(current$grouped, object@model, mcmcOptions) |
| 3563 | 71x |
if (!current$mono$stop) {
|
| 3564 | 55x |
current$mono$limit <- maxDose( |
| 3565 | 55x |
object@mono@increments, |
| 3566 | 55x |
data = current$mono$data |
| 3567 |
) |
|
| 3568 | 55x |
current$mono$dose <- object@mono@nextBest %>% |
| 3569 | 55x |
nextBest( |
| 3570 | 55x |
current$mono$limit, |
| 3571 | 55x |
current$samples, |
| 3572 | 55x |
object@model, |
| 3573 | 55x |
current$grouped, |
| 3574 | 55x |
group = "mono" |
| 3575 |
) |
|
| 3576 | 55x |
current$mono$dose <- current$mono$dose$value |
| 3577 |
} |
|
| 3578 | 16x |
if ( |
| 3579 | 71x |
!current$combo$stop && |
| 3580 | 71x |
(!current$first || !object@first_cohort_mono_only) |
| 3581 |
) {
|
|
| 3582 | 63x |
current$combo$limit <- if (is.na(current$mono$dose)) {
|
| 3583 | ! |
0 |
| 3584 |
} else {
|
|
| 3585 | 63x |
maxDose(object@combo@increments, current$combo$data) %>% |
| 3586 | 63x |
min(current$mono$dose, na.rm = TRUE) |
| 3587 |
} |
|
| 3588 | 63x |
current$combo$dose <- object@combo@nextBest %>% |
| 3589 | 63x |
nextBest( |
| 3590 | 63x |
current$combo$limit, |
| 3591 | 63x |
current$samples, |
| 3592 | 63x |
object@model, |
| 3593 | 63x |
current$grouped, |
| 3594 | 63x |
group = "combo" |
| 3595 |
) |
|
| 3596 | 63x |
current$combo$dose <- current$combo$dose$value |
| 3597 | 63x |
current$combo$stop <- object@combo@stopping %>% |
| 3598 | 63x |
stopTrial( |
| 3599 | 63x |
current$combo$dose, |
| 3600 | 63x |
current$samples, |
| 3601 | 63x |
object@model, |
| 3602 | 63x |
current$combo$data, |
| 3603 | 63x |
group = "combo" |
| 3604 |
) |
|
| 3605 | 63x |
current$combo$results <- h_unpack_stopit(current$combo$stop) |
| 3606 |
} |
|
| 3607 | 71x |
if (!current$mono$stop) {
|
| 3608 | 55x |
current$mono$stop <- object@mono@stopping %>% |
| 3609 | 55x |
stopTrial( |
| 3610 | 55x |
current$mono$dose, |
| 3611 | 55x |
current$samples, |
| 3612 | 55x |
object@model, |
| 3613 | 55x |
current$mono$data, |
| 3614 | 55x |
group = "mono", |
| 3615 | 55x |
external = current$combo$stop |
| 3616 |
) |
|
| 3617 | 55x |
current$mono$results <- h_unpack_stopit(current$mono$stop) |
| 3618 |
} |
|
| 3619 | 16x |
if ( |
| 3620 | 71x |
object@same_dose_for_all && !current$mono$stop && !current$combo$stop |
| 3621 |
) {
|
|
| 3622 | 16x |
current$mono$dose <- current$combo$dose <- min( |
| 3623 | 16x |
current$mono$dose, |
| 3624 | 16x |
current$combo$dose |
| 3625 |
) |
|
| 3626 |
} |
|
| 3627 | 71x |
if (current$first) {
|
| 3628 | 16x |
current$first <- FALSE |
| 3629 | 16x |
if (object@first_cohort_mono_only && object@same_dose_for_start) {
|
| 3630 | 2x |
current$mono$dose <- current$combo$dose <- min( |
| 3631 | 2x |
current$mono$dose, |
| 3632 | 2x |
current$combo$dose |
| 3633 |
) |
|
| 3634 |
} |
|
| 3635 |
} |
|
| 3636 |
} |
|
| 3637 | 16x |
current$mono$fit <- fit( |
| 3638 | 16x |
current$samples, |
| 3639 | 16x |
object@model, |
| 3640 | 16x |
current$grouped, |
| 3641 | 16x |
group = "mono" |
| 3642 |
) |
|
| 3643 | 16x |
current$combo$fit <- fit( |
| 3644 | 16x |
current$samples, |
| 3645 | 16x |
object@model, |
| 3646 | 16x |
current$grouped, |
| 3647 | 16x |
group = "combo" |
| 3648 |
) |
|
| 3649 | 16x |
lapply( |
| 3650 | 16x |
X = current[c("mono", "combo")],
|
| 3651 | 16x |
FUN = with, |
| 3652 | 16x |
list( |
| 3653 | 16x |
data = data, |
| 3654 | 16x |
dose = dose, |
| 3655 | 16x |
fit = subset(fit, select = -dose), |
| 3656 | 16x |
stop = attr(stop, "message"), |
| 3657 | 16x |
results = results |
| 3658 |
) |
|
| 3659 |
) |
|
| 3660 |
} |
|
| 3661 | 9x |
vars_needed <- c( |
| 3662 | 9x |
"simSeeds", |
| 3663 | 9x |
"args", |
| 3664 | 9x |
"nArgs", |
| 3665 | 9x |
"truth", |
| 3666 | 9x |
"combo_truth", |
| 3667 | 9x |
"firstSeparate", |
| 3668 | 9x |
"object", |
| 3669 | 9x |
"mcmcOptions" |
| 3670 |
) |
|
| 3671 | ||
| 3672 | 9x |
result_list <- get_result_list(run_sim, nsim, vars_needed, parallel, nCores) |
| 3673 |
# Now we have a list with each element containing mono and combo. Reorder this a bit: |
|
| 3674 | 9x |
result_list <- list( |
| 3675 | 9x |
mono = lapply(result_list, "[[", "mono"), |
| 3676 | 9x |
combo = lapply(result_list, "[[", "combo") |
| 3677 |
) |
|
| 3678 |
# Put everything in a list with both mono and combo Simulations: |
|
| 3679 | 9x |
lapply(result_list, function(this_list) {
|
| 3680 | 18x |
data_list <- lapply(this_list, "[[", "data") |
| 3681 | 18x |
recommended_doses <- as.numeric(sapply(this_list, "[[", "dose")) |
| 3682 | 18x |
fit_list <- lapply(this_list, "[[", "fit") |
| 3683 | 18x |
stop_reasons <- lapply(this_list, "[[", "stop") |
| 3684 | 18x |
report_results <- lapply(this_list, "[[", "results") |
| 3685 | 18x |
stop_report <- as.matrix(do.call(rbind, report_results)) |
| 3686 | 18x |
additional_stats <- lapply(this_list, "[[", "additional_stats") |
| 3687 | ||
| 3688 | 18x |
Simulations( |
| 3689 | 18x |
data = data_list, |
| 3690 | 18x |
doses = recommended_doses, |
| 3691 | 18x |
fit = fit_list, |
| 3692 | 18x |
stop_reasons = stop_reasons, |
| 3693 | 18x |
stop_report = stop_report, |
| 3694 | 18x |
additional_stats = additional_stats, |
| 3695 | 18x |
seed = rng_state |
| 3696 |
) |
|
| 3697 |
}) |
|
| 3698 |
} |
|
| 3699 |
) |
|
| 3700 | ||
| 3701 |
# examine ---- |
|
| 3702 | ||
| 3703 |
#' Obtain Hypothetical Trial Course Table for a Design |
|
| 3704 |
#' |
|
| 3705 |
#' This generic function takes a design and generates a `data.frame` |
|
| 3706 |
#' showing the beginning of several hypothetical trial courses under |
|
| 3707 |
#' the design. This means, from the generated `data.frame` one can read off: |
|
| 3708 |
#' |
|
| 3709 |
#' - how many cohorts are required in the optimal case (no DLTs observed) in |
|
| 3710 |
#' order to reach the highest dose of the specified dose grid (or until |
|
| 3711 |
#' the stopping rule is fulfilled) |
|
| 3712 |
#' - assuming no DLTs are observed until a certain dose level, what the next |
|
| 3713 |
#' recommended dose is for all possible number of DLTs observed |
|
| 3714 |
#' - the actual relative increments that will be used in these cases |
|
| 3715 |
#' - whether the trial would stop at a certain cohort |
|
| 3716 |
#' |
|
| 3717 |
#' Examining the "single trial" behavior of a dose escalation design is |
|
| 3718 |
#' the first important step in evaluating a design, and cannot be replaced by |
|
| 3719 |
#' studying solely the operating characteristics in "many trials". The cohort |
|
| 3720 |
#' sizes are also taken from the design, assuming no DLTs occur until the dose |
|
| 3721 |
#' listed. |
|
| 3722 |
#' |
|
| 3723 |
#' @param object ([`Design`] or [`RuleDesign`])\cr the design we want to examine |
|
| 3724 |
#' @param ... additional arguments (see methods) |
|
| 3725 |
#' @param maxNoIncrement maximum number of contiguous next doses at 0 |
|
| 3726 |
#' DLTs that are the same as before, i.e. no increment (default to 100) |
|
| 3727 |
#' |
|
| 3728 |
#' @return The data frame |
|
| 3729 |
#' |
|
| 3730 |
#' @export |
|
| 3731 |
#' @keywords methods regression |
|
| 3732 |
setGeneric( |
|
| 3733 |
"examine", |
|
| 3734 |
def = function(object, ..., maxNoIncrement = 100L) {
|
|
| 3735 | 4x |
assert_count(maxNoIncrement, positive = TRUE) |
| 3736 | 4x |
standardGeneric("examine")
|
| 3737 |
}, |
|
| 3738 |
valueClass = "data.frame" |
|
| 3739 |
) |
|
| 3740 | ||
| 3741 |
## Design ---- |
|
| 3742 | ||
| 3743 |
#' @describeIn examine Examine a model-based CRM. |
|
| 3744 |
#' |
|
| 3745 |
#' @param mcmcOptions ([`McmcOptions`])\cr giving the MCMC options |
|
| 3746 |
#' for each evaluation in the trial. By default, the standard options are used. |
|
| 3747 |
#' |
|
| 3748 |
#' @example examples/design-method-examine-Design.R |
|
| 3749 |
setMethod( |
|
| 3750 |
"examine", |
|
| 3751 |
signature = signature(object = "Design"), |
|
| 3752 |
def = function(object, mcmcOptions = McmcOptions(), ..., maxNoIncrement) {
|
|
| 3753 | 2x |
ret <- data.frame( |
| 3754 | 2x |
dose = numeric(), |
| 3755 | 2x |
DLTs = integer(), |
| 3756 | 2x |
nextDose = numeric(), |
| 3757 | 2x |
stop = logical(), |
| 3758 | 2x |
increment = integer() |
| 3759 |
) |
|
| 3760 | 2x |
base_data <- object@data |
| 3761 | ||
| 3762 | 2x |
should_stop <- FALSE |
| 3763 | ||
| 3764 |
# Counter how many contiguous doses at 0 DLTs with no increment. |
|
| 3765 | 2x |
no_increment_counter <- 0L |
| 3766 | ||
| 3767 |
# Initialize with starting dose. |
|
| 3768 | 2x |
dose <- object@startingDose |
| 3769 | ||
| 3770 | 2x |
while (!should_stop) {
|
| 3771 |
# What is the cohort size at this dose? |
|
| 3772 | 12x |
cohort_size <- size(object@cohort_size, dose = dose, data = base_data) |
| 3773 | ||
| 3774 | 12x |
if (base_data@placebo) {
|
| 3775 | 5x |
cohort_size_pl <- size( |
| 3776 | 5x |
object@pl_cohort_size, |
| 3777 | 5x |
dose = dose, |
| 3778 | 5x |
data = base_data |
| 3779 |
) |
|
| 3780 |
} |
|
| 3781 | ||
| 3782 |
# For all possible number of DLTs: |
|
| 3783 | 12x |
for (num_dlts in 0:cohort_size) {
|
| 3784 |
# Update data with corresponding DLT vector. |
|
| 3785 | 48x |
if (base_data@placebo && (cohort_size_pl > 0L)) {
|
| 3786 | 20x |
data_updated <- update( |
| 3787 | 20x |
object = base_data, |
| 3788 | 20x |
x = base_data@doseGrid[1], |
| 3789 | 20x |
y = rep(0, cohort_size_pl), |
| 3790 | 20x |
check = FALSE |
| 3791 |
) |
|
| 3792 | ||
| 3793 | 20x |
data_updated <- update( |
| 3794 | 20x |
object = data_updated, |
| 3795 | 20x |
x = dose, |
| 3796 | 20x |
y = rep( |
| 3797 | 20x |
x = c(0, 1), |
| 3798 | 20x |
times = c( |
| 3799 | 20x |
cohort_size - num_dlts, |
| 3800 | 20x |
num_dlts |
| 3801 |
) |
|
| 3802 |
), |
|
| 3803 | 20x |
new_cohort = FALSE |
| 3804 |
) |
|
| 3805 |
} else {
|
|
| 3806 | 28x |
data_updated <- update( |
| 3807 | 28x |
object = base_data, |
| 3808 | 28x |
x = dose, |
| 3809 | 28x |
y = rep( |
| 3810 | 28x |
x = c(0, 1), |
| 3811 | 28x |
times = c( |
| 3812 | 28x |
cohort_size - num_dlts, |
| 3813 | 28x |
num_dlts |
| 3814 |
) |
|
| 3815 |
) |
|
| 3816 |
) |
|
| 3817 |
} |
|
| 3818 | ||
| 3819 |
# Calculate dose limit. |
|
| 3820 | 48x |
dose_limit <- maxDose(object@increments, data = data_updated) |
| 3821 | ||
| 3822 |
# Generate samples from the model. |
|
| 3823 | 48x |
samples <- mcmc( |
| 3824 | 48x |
data = data_updated, |
| 3825 | 48x |
model = object@model, |
| 3826 | 48x |
options = mcmcOptions |
| 3827 |
) |
|
| 3828 | ||
| 3829 |
# Calculate next best dose. |
|
| 3830 | 48x |
next_dose <- nextBest( |
| 3831 | 48x |
object@nextBest, |
| 3832 | 48x |
doselimit = dose_limit, |
| 3833 | 48x |
samples = samples, |
| 3834 | 48x |
model = object@model, |
| 3835 | 48x |
data = data_updated |
| 3836 | 48x |
)$value |
| 3837 | ||
| 3838 |
# Compute relative increment in percent. |
|
| 3839 | 48x |
increment <- round((next_dose - dose) / dose * 100) |
| 3840 | ||
| 3841 |
# Evaluate stopping rules. |
|
| 3842 | 48x |
stop_this_trial <- stopTrial( |
| 3843 | 48x |
object@stopping, |
| 3844 | 48x |
dose = next_dose, |
| 3845 | 48x |
samples = samples, |
| 3846 | 48x |
model = object@model, |
| 3847 | 48x |
data = data_updated |
| 3848 |
) |
|
| 3849 | ||
| 3850 |
# Append information to the data frame. |
|
| 3851 | 48x |
ret <- rbind( |
| 3852 | 48x |
ret, |
| 3853 | 48x |
list( |
| 3854 | 48x |
dose = dose, |
| 3855 | 48x |
DLTs = num_dlts, |
| 3856 | 48x |
nextDose = next_dose, |
| 3857 | 48x |
stop = stop_this_trial, |
| 3858 | 48x |
increment = as.integer(increment) |
| 3859 |
) |
|
| 3860 |
) |
|
| 3861 |
} |
|
| 3862 | ||
| 3863 |
# Update base data. |
|
| 3864 | 12x |
if (base_data@placebo && (cohort_size_pl > 0L)) {
|
| 3865 | 5x |
base_data <- update( |
| 3866 | 5x |
object = base_data, |
| 3867 | 5x |
x = base_data@doseGrid[1], |
| 3868 | 5x |
y = rep(0, cohort_size_pl), |
| 3869 | 5x |
check = FALSE |
| 3870 |
) |
|
| 3871 | ||
| 3872 | 5x |
base_data <- update( |
| 3873 | 5x |
object = base_data, |
| 3874 | 5x |
x = dose, |
| 3875 | 5x |
y = rep(0, cohort_size), |
| 3876 | 5x |
new_cohort = FALSE |
| 3877 |
) |
|
| 3878 |
} else {
|
|
| 3879 | 7x |
base_data <- update( |
| 3880 | 7x |
object = base_data, |
| 3881 | 7x |
x = dose, |
| 3882 | 7x |
y = rep(0, cohort_size) |
| 3883 |
) |
|
| 3884 |
} |
|
| 3885 | ||
| 3886 |
# Extract results if 0 DLTs. |
|
| 3887 | 12x |
results_no_dlts <- subset( |
| 3888 | 12x |
tail(ret, cohort_size + 1), |
| 3889 | 12x |
dose == dose & DLTs == 0 |
| 3890 |
) |
|
| 3891 | ||
| 3892 |
# Determine new dose. |
|
| 3893 | 12x |
new_dose <- as.numeric(results_no_dlts$nextDose) |
| 3894 | ||
| 3895 |
# Calculate difference to previous dose. |
|
| 3896 | 12x |
dose_diff <- new_dose - dose |
| 3897 | ||
| 3898 |
# Update the counter for no increments of the dose. |
|
| 3899 | 12x |
if (dose_diff == 0) {
|
| 3900 | 10x |
no_increment_counter <- no_increment_counter + 1L |
| 3901 |
} else {
|
|
| 3902 | 2x |
no_increment_counter <- 0L |
| 3903 |
} |
|
| 3904 | ||
| 3905 |
# Check if stopping rule would be fulfilled. |
|
| 3906 | 12x |
stop_already <- results_no_dlts$stop |
| 3907 | ||
| 3908 |
# Update dose. |
|
| 3909 | 12x |
dose <- new_dose |
| 3910 | ||
| 3911 |
# Check if too many times no increment. |
|
| 3912 | 12x |
stop_no_increment <- (no_increment_counter >= maxNoIncrement) |
| 3913 | 12x |
if (stop_no_increment) {
|
| 3914 | ! |
warning(paste( |
| 3915 | ! |
"Stopping because", |
| 3916 | ! |
no_increment_counter, |
| 3917 | ! |
"times no increment vs. previous dose" |
| 3918 |
)) |
|
| 3919 |
} |
|
| 3920 | ||
| 3921 |
# Check if we can stop: |
|
| 3922 |
# Either when we have reached the highest dose in the next cohort, |
|
| 3923 |
# or when the stopping rule is already fulfilled, |
|
| 3924 |
# or when too many times no increment. |
|
| 3925 | 12x |
should_stop <- (dose >= max(object@data@doseGrid)) || |
| 3926 | 12x |
stop_already || |
| 3927 | 12x |
stop_no_increment |
| 3928 |
} |
|
| 3929 | 2x |
ret |
| 3930 |
} |
|
| 3931 |
) |
|
| 3932 | ||
| 3933 |
## RuleDesign ---- |
|
| 3934 | ||
| 3935 |
#' @describeIn examine Examine a rule-based design. |
|
| 3936 |
#' @example examples/design-method-examine-RuleDesign.R |
|
| 3937 |
setMethod( |
|
| 3938 |
"examine", |
|
| 3939 |
signature = signature(object = "RuleDesign"), |
|
| 3940 |
def = function(object, ..., maxNoIncrement) {
|
|
| 3941 |
# Start with the empty table. |
|
| 3942 | 1x |
ret <- data.frame( |
| 3943 | 1x |
dose = numeric(), |
| 3944 | 1x |
DLTs = integer(), |
| 3945 | 1x |
nextDose = numeric(), |
| 3946 | 1x |
stop = logical(), |
| 3947 | 1x |
increment = integer() |
| 3948 |
) |
|
| 3949 | ||
| 3950 |
# Start the base data with the provided one. |
|
| 3951 | 1x |
base_data <- object@data |
| 3952 | ||
| 3953 |
# Are we finished and can stop? |
|
| 3954 | 1x |
should_stop <- FALSE |
| 3955 | ||
| 3956 |
# Counter: contiguous doses at 0 DLTs with no increment. |
|
| 3957 | 1x |
no_increment_counter <- 0L |
| 3958 | ||
| 3959 |
# Initialize with starting dose. |
|
| 3960 | 1x |
dose <- object@startingDose |
| 3961 | ||
| 3962 |
# Continue filling up the table until stopping. |
|
| 3963 | 1x |
while (!should_stop) {
|
| 3964 |
# Cohort size at this dose. |
|
| 3965 | 10x |
cohort_size <- size(object@cohort_size, dose = dose, data = base_data) |
| 3966 | ||
| 3967 |
# For all possible number of DLTs. |
|
| 3968 | 10x |
for (num_dlts in 0:cohort_size) {
|
| 3969 |
# Update data with corresponding DLT vector. |
|
| 3970 | 40x |
data_updated <- update( |
| 3971 | 40x |
object = base_data, |
| 3972 | 40x |
x = dose, |
| 3973 | 40x |
y = rep( |
| 3974 | 40x |
x = c(0, 1), |
| 3975 | 40x |
times = c( |
| 3976 | 40x |
cohort_size - num_dlts, |
| 3977 | 40x |
num_dlts |
| 3978 |
) |
|
| 3979 |
) |
|
| 3980 |
) |
|
| 3981 | ||
| 3982 |
# Evaluate the rule. |
|
| 3983 | 40x |
outcome <- nextBest(object@nextBest, data = data_updated) |
| 3984 | ||
| 3985 |
# Next dose and whether to stop here. |
|
| 3986 | 40x |
next_dose <- outcome$value |
| 3987 | 40x |
stop_this_trial <- outcome$stopHere |
| 3988 | ||
| 3989 |
# Compute relative increment in percent. |
|
| 3990 | 40x |
increment <- round((next_dose - dose) / dose * 100) |
| 3991 | ||
| 3992 |
# Append information to the data frame. |
|
| 3993 | 40x |
ret <- rbind( |
| 3994 | 40x |
ret, |
| 3995 | 40x |
list( |
| 3996 | 40x |
dose = dose, |
| 3997 | 40x |
DLTs = num_dlts, |
| 3998 | 40x |
nextDose = next_dose, |
| 3999 | 40x |
stop = stop_this_trial, |
| 4000 | 40x |
increment = as.integer(increment) |
| 4001 |
) |
|
| 4002 |
) |
|
| 4003 |
} |
|
| 4004 | ||
| 4005 |
# Change base data. |
|
| 4006 | 10x |
base_data <- update( |
| 4007 | 10x |
object = base_data, |
| 4008 | 10x |
x = dose, |
| 4009 | 10x |
y = rep(0, cohort_size) |
| 4010 |
) |
|
| 4011 | ||
| 4012 |
# Results if 0 DLTs. |
|
| 4013 | 10x |
results_no_dlts <- subset( |
| 4014 | 10x |
tail(ret, cohort_size + 1), |
| 4015 | 10x |
dose == dose & DLTs == 0 |
| 4016 |
) |
|
| 4017 | ||
| 4018 |
# New dose and difference to previous dose. |
|
| 4019 | 10x |
new_dose <- as.numeric(results_no_dlts$nextDose) |
| 4020 | 10x |
dose_diff <- new_dose - dose |
| 4021 | ||
| 4022 |
# Update the counter for no increments of the dose. |
|
| 4023 | 10x |
if (dose_diff == 0) {
|
| 4024 | ! |
no_increment_counter <- no_increment_counter + 1L |
| 4025 |
} else {
|
|
| 4026 | 10x |
no_increment_counter <- 0L |
| 4027 |
} |
|
| 4028 | ||
| 4029 |
# Would stopping rule be fulfilled already? |
|
| 4030 | 10x |
stop_already <- results_no_dlts$stop |
| 4031 | ||
| 4032 |
# Update dose. |
|
| 4033 | 10x |
dose <- new_dose |
| 4034 | ||
| 4035 |
# Too many times no increment? |
|
| 4036 | 10x |
stop_no_increment <- (no_increment_counter >= maxNoIncrement) |
| 4037 | 10x |
if (stop_no_increment) {
|
| 4038 | ! |
warning(paste( |
| 4039 | ! |
"Stopping because", |
| 4040 | ! |
no_increment_counter, |
| 4041 | ! |
"times no increment vs. previous dose" |
| 4042 |
)) |
|
| 4043 |
} |
|
| 4044 | ||
| 4045 |
# Check if we can stop: |
|
| 4046 |
# highest dose reached next cohort, stopping rule fulfilled, or too many no-increment. |
|
| 4047 | 10x |
should_stop <- (dose >= max(object@data@doseGrid)) || |
| 4048 | 10x |
stop_already || |
| 4049 | 10x |
stop_no_increment |
| 4050 |
} |
|
| 4051 | ||
| 4052 | 1x |
ret |
| 4053 |
} |
|
| 4054 |
) |
|
| 4055 | ||
| 4056 |
## DADesign ---- |
|
| 4057 | ||
| 4058 |
#' @describeIn examine Examine a model-based CRM. |
|
| 4059 |
#' |
|
| 4060 |
#' @param mcmcOptions ([`McmcOptions`])\cr |
|
| 4061 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 4062 |
#' the standard options are used |
|
| 4063 |
#' |
|
| 4064 |
#' @example examples/design-method-examine-DADesign.R |
|
| 4065 |
setMethod( |
|
| 4066 |
"examine", |
|
| 4067 |
signature = signature(object = "DADesign"), |
|
| 4068 |
def = function(object, mcmcOptions = McmcOptions(), ..., maxNoIncrement) {
|
|
| 4069 |
# Check follow-up sufficiency (TRUE/FALSE); |
|
| 4070 | 1x |
ready_to_open <- function(day, window, this_surv) {
|
| 4071 | 180x |
size <- length(this_surv) |
| 4072 | 180x |
start_time <- apply( |
| 4073 | 180x |
rbind(this_surv[-size], window$patientGap[-1]), |
| 4074 | 180x |
2, |
| 4075 | 180x |
min |
| 4076 |
) |
|
| 4077 | 180x |
individual_check <- day - cumsum(c(0, start_time)) |
| 4078 | 180x |
individual_check[individual_check < 0] <- 0 |
| 4079 | 180x |
follow_up <- apply(rbind(this_surv, individual_check), 2, min) |
| 4080 | 180x |
all( |
| 4081 | 180x |
(follow_up - apply(rbind(window$patientFollow, this_surv), 2, min)) >= 0 |
| 4082 |
) && |
|
| 4083 | 180x |
(max(follow_up) >= min(window$patientFollowMin, max(this_surv))) |
| 4084 |
} |
|
| 4085 | ||
| 4086 |
# Determine when to open the next cohort; applies to all trials. |
|
| 4087 | 1x |
next_open <- function(window, this_surv) {
|
| 4088 | 3x |
size <- length(this_surv) |
| 4089 | 3x |
window$patientGap <- window$patientGap[1:size] |
| 4090 | 3x |
start_time <- apply( |
| 4091 | 3x |
rbind(this_surv[-size], window$patientGap[-1]), |
| 4092 | 3x |
2, |
| 4093 | 3x |
min |
| 4094 |
) |
|
| 4095 | 3x |
max_t <- max(this_surv + cumsum(c(0, start_time))) |
| 4096 | ||
| 4097 | 3x |
met <- sapply(1:max_t, function(i) ready_to_open(i, window, this_surv)) |
| 4098 | ! |
if (sum(met) > 0) min(c(1:max_t)[met]) else max_t |
| 4099 |
} |
|
| 4100 | ||
| 4101 |
# Initialize result table. |
|
| 4102 | 1x |
ret <- data.frame( |
| 4103 | 1x |
DLTsearly_1 = integer(), |
| 4104 | 1x |
dose = numeric(), |
| 4105 | 1x |
DLTs = integer(), |
| 4106 | 1x |
nextDose = numeric(), |
| 4107 | 1x |
stop = logical(), |
| 4108 | 1x |
increment = integer() |
| 4109 |
) |
|
| 4110 | ||
| 4111 |
# Base data and trial state. |
|
| 4112 | 1x |
base_data <- object@data |
| 4113 | 1x |
should_stop <- FALSE |
| 4114 | 1x |
dose <- object@startingDose |
| 4115 | ||
| 4116 |
# Observed facts trackers (cumulative across cohorts). |
|
| 4117 | 1x |
observed_dlts <- base_data@y |
| 4118 | 1x |
observed_surv <- base_data@u |
| 4119 | 1x |
observed_t0 <- base_data@t0 |
| 4120 | ||
| 4121 |
# Global trial clock and previous cohort timing. |
|
| 4122 | 1x |
trial_time <- 0 |
| 4123 | 1x |
prev_time <- 0 |
| 4124 | ||
| 4125 |
# DLT window length. |
|
| 4126 | 1x |
t_max <- base_data@Tmax |
| 4127 | ||
| 4128 |
# Number of patients with unfinished DLT window (initially none). |
|
| 4129 | 1x |
prev_size <- 0 |
| 4130 | ||
| 4131 |
# Iterate cohorts until stopping. |
|
| 4132 | 1x |
while (!should_stop) {
|
| 4133 | 3x |
cohort_size <- size(object@cohort_size, dose = dose, data = base_data) |
| 4134 | 3x |
safety_window <- windowLength(object@safetyWindow, cohort_size) |
| 4135 | ||
| 4136 |
# When cohort patients start relative to trial clock. |
|
| 4137 | ||
| 4138 | 3x |
cohort_t0 <- trial_time + cumsum(safety_window$patientGap) |
| 4139 | ||
| 4140 |
# Append placeholders for the incoming cohort (no DLTs yet, censored at t_max). |
|
| 4141 | 3x |
observed_dlts <- c(observed_dlts, rep(0, cohort_size)) |
| 4142 | 3x |
observed_surv <- c(observed_surv, rep(t_max, cohort_size)) |
| 4143 | 3x |
observed_t0 <- c(observed_t0, cohort_t0) |
| 4144 | ||
| 4145 |
# Advance time until next cohort may open (all follow-up constraints satisfied). |
|
| 4146 | 3x |
trial_time <- trial_time + |
| 4147 | 3x |
next_open(window = safety_window, this_surv = rep(t_max, cohort_size)) |
| 4148 | ||
| 4149 |
# Count patients still within DLT window (for nFollow loop). |
|
| 4150 | 3x |
n_follow <- cohort_size + prev_size |
| 4151 | ||
| 4152 |
# Identify censored patients indices. |
|
| 4153 | 3x |
npt <- length(base_data@x) |
| 4154 | 3x |
censored_indices <- c( |
| 4155 | 3x |
which((trial_time - base_data@t0) < base_data@Tmax & base_data@y == 0), |
| 4156 | 3x |
(npt + 1):(npt + cohort_size) |
| 4157 |
) |
|
| 4158 | ||
| 4159 |
# For all possible number of DLTs (0..n_follow): |
|
| 4160 | 3x |
for (num_dlts in 0:n_follow) {
|
| 4161 | 9x |
if (num_dlts == 0) {
|
| 4162 |
# Update base_data for zero DLTs scenario. |
|
| 4163 | 3x |
base_data <- update( |
| 4164 | 3x |
object = base_data, |
| 4165 | 3x |
y = observed_dlts, |
| 4166 | 3x |
u = observed_surv, |
| 4167 | 3x |
t0 = observed_t0, |
| 4168 | 3x |
x = dose, |
| 4169 | 3x |
trialtime = trial_time |
| 4170 |
) |
|
| 4171 | ||
| 4172 | 3x |
dose_limit <- maxDose(object@increments, data = base_data) |
| 4173 | 3x |
samples <- mcmc( |
| 4174 | 3x |
data = base_data, |
| 4175 | 3x |
model = object@model, |
| 4176 | 3x |
options = mcmcOptions |
| 4177 |
) |
|
| 4178 | 3x |
next_dose <- nextBest( |
| 4179 | 3x |
object@nextBest, |
| 4180 | 3x |
doselimit = dose_limit, |
| 4181 | 3x |
samples = samples, |
| 4182 | 3x |
model = object@model, |
| 4183 | 3x |
data = base_data |
| 4184 | 3x |
)$value |
| 4185 | ||
| 4186 | 3x |
increment <- round((next_dose - dose) / dose * 100) |
| 4187 | 3x |
stop_this_trial <- stopTrial( |
| 4188 | 3x |
object@stopping, |
| 4189 | 3x |
dose = next_dose, |
| 4190 | 3x |
samples = samples, |
| 4191 | 3x |
model = object@model, |
| 4192 | 3x |
data = base_data |
| 4193 |
) |
|
| 4194 | ||
| 4195 | 3x |
ret <- rbind( |
| 4196 | 3x |
ret, |
| 4197 | 3x |
list( |
| 4198 | 3x |
DLTsearly_1 = 0, |
| 4199 | 3x |
dose = dose, |
| 4200 | 3x |
DLTs = num_dlts, |
| 4201 | 3x |
nextDose = next_dose, |
| 4202 | 3x |
stop = stop_this_trial, |
| 4203 | 3x |
increment = as.integer(increment) |
| 4204 |
) |
|
| 4205 |
) |
|
| 4206 |
} else {
|
|
| 4207 |
# Consider two extremes: DLTs at longest vs shortest follow-ups. |
|
| 4208 | 6x |
for (dlt_early in 1:num_dlts) {
|
| 4209 | 10x |
curr_dlts <- observed_dlts |
| 4210 | 10x |
curr_surv <- observed_surv |
| 4211 | ||
| 4212 | 10x |
if (dlt_early == 1) {
|
| 4213 |
# Longest follow-up patients have DLTs. |
|
| 4214 | 6x |
curr_dlts[censored_indices][1:num_dlts] <- 1 |
| 4215 | 6x |
curr_surv[censored_indices][1:num_dlts] <- apply( |
| 4216 | 6x |
rbind( |
| 4217 | 6x |
rep(t_max, num_dlts), |
| 4218 | 6x |
trial_time - observed_t0[censored_indices][1:num_dlts] |
| 4219 |
), |
|
| 4220 | 6x |
2, |
| 4221 | 6x |
min |
| 4222 |
) |
|
| 4223 | ||
| 4224 | 6x |
data_current <- update( |
| 4225 | 6x |
object = base_data, |
| 4226 | 6x |
y = curr_dlts, |
| 4227 | 6x |
u = curr_surv, |
| 4228 | 6x |
t0 = observed_t0, |
| 4229 | 6x |
x = dose, |
| 4230 | 6x |
trialtime = trial_time |
| 4231 |
) |
|
| 4232 |
} else {
|
|
| 4233 |
# Shortest follow-up patients have DLTs. |
|
| 4234 | 4x |
curr_dlts[rev(censored_indices)][1:num_dlts] <- 1 |
| 4235 | 4x |
curr_surv[rev(censored_indices)][1:num_dlts] <- apply( |
| 4236 | 4x |
rbind( |
| 4237 | 4x |
rep(1, num_dlts), |
| 4238 | 4x |
prev_time + 1 - observed_t0[rev(censored_indices)][1:num_dlts] |
| 4239 |
), |
|
| 4240 | 4x |
2, |
| 4241 | 4x |
max |
| 4242 |
) |
|
| 4243 | ||
| 4244 | 4x |
temp_time <- if (num_dlts >= cohort_size) {
|
| 4245 | 4x |
1 + max(cohort_t0) |
| 4246 |
} else {
|
|
| 4247 | ! |
trial_time |
| 4248 |
} |
|
| 4249 | ||
| 4250 | 4x |
data_current <- update( |
| 4251 | 4x |
object = base_data, |
| 4252 | 4x |
y = curr_dlts, |
| 4253 | 4x |
u = curr_surv, |
| 4254 | 4x |
t0 = observed_t0, |
| 4255 | 4x |
x = dose, |
| 4256 | 4x |
trialtime = temp_time |
| 4257 |
) |
|
| 4258 |
} |
|
| 4259 | ||
| 4260 | 10x |
dose_limit <- maxDose(object@increments, data = data_current) |
| 4261 | 10x |
samples <- mcmc( |
| 4262 | 10x |
data = data_current, |
| 4263 | 10x |
model = object@model, |
| 4264 | 10x |
options = mcmcOptions |
| 4265 |
) |
|
| 4266 | 10x |
next_dose <- nextBest( |
| 4267 | 10x |
object@nextBest, |
| 4268 | 10x |
doselimit = dose_limit, |
| 4269 | 10x |
samples = samples, |
| 4270 | 10x |
model = object@model, |
| 4271 | 10x |
data = data_current |
| 4272 | 10x |
)$value |
| 4273 | ||
| 4274 | 10x |
increment <- round((next_dose - dose) / dose * 100) |
| 4275 | 10x |
stop_this_trial <- stopTrial( |
| 4276 | 10x |
object@stopping, |
| 4277 | 10x |
dose = next_dose, |
| 4278 | 10x |
samples = samples, |
| 4279 | 10x |
model = object@model, |
| 4280 | 10x |
data = data_current |
| 4281 |
) |
|
| 4282 | ||
| 4283 | 10x |
ret <- rbind( |
| 4284 | 10x |
ret, |
| 4285 | 10x |
list( |
| 4286 | 10x |
DLTsearly_1 = dlt_early, |
| 4287 | 10x |
dose = dose, |
| 4288 | 10x |
DLTs = num_dlts, |
| 4289 | 10x |
nextDose = next_dose, |
| 4290 | 10x |
stop = stop_this_trial, |
| 4291 | 10x |
increment = as.integer(increment) |
| 4292 |
) |
|
| 4293 |
) |
|
| 4294 |
} |
|
| 4295 |
} |
|
| 4296 |
} |
|
| 4297 | ||
| 4298 |
# Update previous time and compute next state. |
|
| 4299 | 3x |
prev_time <- trial_time |
| 4300 | ||
| 4301 |
# Filter results at this dose with 0 DLTs and derive new dose. |
|
| 4302 | 3x |
results_no_dlts <- subset(ret, dose == dose & DLTs == 0) |
| 4303 | 3x |
new_dose <- as.numeric(results_no_dlts$nextDose) |
| 4304 | 3x |
dose_diff <- new_dose - dose |
| 4305 | 3x |
stop_already <- any(results_no_dlts$stop) |
| 4306 | ||
| 4307 |
# Update dose to the maximum recommended among ties. |
|
| 4308 | 3x |
dose <- max(new_dose) |
| 4309 | ||
| 4310 |
# Patients still within DLT window. |
|
| 4311 | 3x |
prev_size <- sum(base_data@u[base_data@y == 0] < base_data@Tmax) |
| 4312 | ||
| 4313 |
# No-increment counter and stopping due to no increment. |
|
| 4314 | 3x |
no_increment_counter <- if (all(dose_diff == 0)) {
|
| 4315 | 2x |
no_increment_counter + 1L |
| 4316 |
} else {
|
|
| 4317 | 1x |
0L |
| 4318 |
} |
|
| 4319 | 3x |
stop_no_increment <- (no_increment_counter >= maxNoIncrement) |
| 4320 | 3x |
if (stop_no_increment) {
|
| 4321 | 1x |
warning(paste( |
| 4322 | 1x |
"Stopping because", |
| 4323 | 1x |
no_increment_counter, |
| 4324 | 1x |
"times no increment vs. previous dose" |
| 4325 |
)) |
|
| 4326 |
} |
|
| 4327 | ||
| 4328 |
# Overall stop condition. |
|
| 4329 | 3x |
should_stop <- (dose >= max(object@data@doseGrid)) || |
| 4330 | 3x |
stop_already || |
| 4331 | 3x |
stop_no_increment |
| 4332 |
} |
|
| 4333 | ||
| 4334 | 1x |
ret |
| 4335 |
} |
|
| 4336 |
) |
|
| 4337 | ||
| 4338 |
# tidy ---- |
|
| 4339 | ||
| 4340 |
## tidy-DualDesign ---- |
|
| 4341 | ||
| 4342 |
#' @rdname tidy |
|
| 4343 |
#' @aliases tidy-DualDesign |
|
| 4344 |
#' @example examples/Design-method-tidyDualDesign.R |
|
| 4345 |
#' |
|
| 4346 |
#' @export |
|
| 4347 |
setMethod( |
|
| 4348 |
f = "tidy", |
|
| 4349 |
signature = signature(x = "DualDesign"), |
|
| 4350 |
definition = function(x, ...) {
|
|
| 4351 |
# Some Design objects have complex attributes whose structure is not supported. |
|
| 4352 | 3x |
rv <- h_tidy_all_slots(x, attributes = FALSE) %>% h_tidy_class(x) |
| 4353 | 3x |
if (length(rv) == 1) {
|
| 4354 | ! |
rv[[names(rv)[1]]] %>% h_tidy_class(x) |
| 4355 |
} else {
|
|
| 4356 | 3x |
rv |
| 4357 |
} |
|
| 4358 |
} |
|
| 4359 |
) |
| 1 |
# for nextBest methods ---- |
|
| 2 | ||
| 3 |
## some specific helpers ---- |
|
| 4 | ||
| 5 |
#' Calculating the Information Theoretic Distance |
|
| 6 |
#' |
|
| 7 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 8 |
#' |
|
| 9 |
#' Helper function which provides the value of the |
|
| 10 |
#' divergence as given by equation in (7) in the reference at |
|
| 11 |
#' https://doi.org/10.1002/sim.8450. |
|
| 12 |
#' |
|
| 13 |
#' @param prob (`numeric`)\cr vector or matrix with probabilities of a DLT occurring. |
|
| 14 |
#' @param target (`number `)\cr single target probability of a DLT. |
|
| 15 |
#' @param asymmetry (`number`)\cr describes the rate of penalization |
|
| 16 |
#' for overly toxic does, range 0 to 2. |
|
| 17 |
#' |
|
| 18 |
#' @export |
|
| 19 |
#' @examples |
|
| 20 |
#' h_info_theory_dist(c(0.5, 0.2), 0.4, 1.2) |
|
| 21 |
h_info_theory_dist <- function(prob, target, asymmetry) {
|
|
| 22 | 63x |
assert_probabilities(prob) |
| 23 | 63x |
assert_true(test_vector(prob) || test_matrix(prob)) |
| 24 | 63x |
assert_number(target, finite = TRUE) |
| 25 | 62x |
assert_number(asymmetry, lower = 0, upper = 2) |
| 26 | ||
| 27 | 60x |
((prob - target)^2) / (((prob^asymmetry) * (1 - prob)^(2 - asymmetry))) |
| 28 |
} |
|
| 29 | ||
| 30 |
#' Credibility Intervals for Max Gain and Target Doses at `nextBest-NextBestMaxGain` Method. |
|
| 31 |
#' |
|
| 32 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 33 |
#' |
|
| 34 |
#' Helper function for [`nextBest-NextBestMaxGain()`] method. It computes a |
|
| 35 |
#' 95% credibility intervals for given target dose and max gain dose. |
|
| 36 |
#' It also returns a ratio of upper and lower bounds of the interval. |
|
| 37 |
#' |
|
| 38 |
#' @param dose_target (`number`)\cr target dose estimate. |
|
| 39 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
| 40 |
#' @param prob_target (`proportion`)\cr target DLT probability. |
|
| 41 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level in the dose grid used |
|
| 42 |
#' is considered as placebo. This is needed to adjust the max gain dose using |
|
| 43 |
#' efficacy constant value. If the `placebo` was used, then the `model_eff@const` |
|
| 44 |
#' is added to `dose_mg`. |
|
| 45 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 46 |
#' @param model_eff (`Effloglog`)\cr the efficacy model. |
|
| 47 |
#' |
|
| 48 |
#' @references |
|
| 49 |
#' \insertRef{YeungWhiteheadReignerBeyerDiackJaki2015}{crmPack}
|
|
| 50 |
#' |
|
| 51 |
#' @export |
|
| 52 |
#' |
|
| 53 |
h_next_best_mg_ci <- function( |
|
| 54 |
dose_target, |
|
| 55 |
dose_mg, |
|
| 56 |
prob_target, |
|
| 57 |
placebo, |
|
| 58 |
model, |
|
| 59 |
model_eff |
|
| 60 |
) {
|
|
| 61 | 26x |
assert_number(dose_target, na.ok = TRUE) |
| 62 | 26x |
assert_number(dose_mg, na.ok = TRUE) |
| 63 | 26x |
assert_probability(prob_target) |
| 64 | 26x |
assert_flag(placebo) |
| 65 | 26x |
assert_class(model, "ModelTox") |
| 66 | 26x |
assert_class(model_eff, "Effloglog") |
| 67 | ||
| 68 |
# Find the variance of the log of target dose. |
|
| 69 | 26x |
mat <- matrix( |
| 70 | 26x |
c( |
| 71 | 26x |
-1 / (model@phi2), |
| 72 | 26x |
-(log(prob_target / (1 - prob_target)) - model@phi1) / (model@phi2)^2 |
| 73 |
), |
|
| 74 | 26x |
nrow = 1 |
| 75 |
) |
|
| 76 | 26x |
var_dose_target <- as.vector(mat %*% model@Pcov %*% t(mat)) |
| 77 | ||
| 78 |
# 95% credibility interval for target dose. |
|
| 79 | 26x |
ci_dose_target <- exp( |
| 80 | 26x |
log(dose_target) + c(-1, 1) * 1.96 * sqrt(var_dose_target) |
| 81 |
) |
|
| 82 | 26x |
cir_dose_target <- ci_dose_target[2] / ci_dose_target[1] |
| 83 | ||
| 84 |
# Find the variance of the log of dose_mg. |
|
| 85 |
# First, find the covariance matrix of all the parameters, phi1, phi2, theta1 and theta2 |
|
| 86 |
# given that phi1 and phi2 are independent of theta1 and theta2. |
|
| 87 | 26x |
log_dose_mg <- log(dose_mg + ifelse(placebo, model_eff@const, 0)) |
| 88 | ||
| 89 |
# Find a delta_g matrix for a variance according to Yeung et. al (2015). |
|
| 90 | 26x |
mean_eff_mg <- model_eff@theta1 + model_eff@theta2 * log(log_dose_mg) |
| 91 | 26x |
denom <- model@phi2 * mean_eff_mg * (1 + model@phi2 * log_dose_mg) |
| 92 | 26x |
dgphi1 <- -(mean_eff_mg * log_dose_mg * model@phi2 - model_eff@theta2) / denom |
| 93 | 26x |
dgphi2 <- -(log_dose_mg * |
| 94 | 26x |
(mean_eff_mg * (1 + log_dose_mg * model@phi2) - model_eff@theta2)) / |
| 95 | 26x |
denom |
| 96 | 26x |
dgtheta1 <- -(log_dose_mg * model@phi2) / denom |
| 97 | 26x |
dgtheta2_num <- -(exp(model@phi1 + model@phi2 * log_dose_mg) * |
| 98 | 26x |
(model@phi2 * log_dose_mg * log(log_dose_mg) - 1) - |
| 99 | 26x |
1) |
| 100 | 26x |
dgtheta2 <- dgtheta2_num / denom |
| 101 | 26x |
delta_g <- matrix(c(dgphi1, dgphi2, dgtheta1, dgtheta2), 4, 1) |
| 102 | ||
| 103 | 26x |
zero_matrix <- matrix(0, 2, 2) |
| 104 | 26x |
cov_beta <- cbind( |
| 105 | 26x |
rbind(model@Pcov, zero_matrix), |
| 106 | 26x |
rbind(zero_matrix, model_eff@Pcov) |
| 107 |
) |
|
| 108 | 26x |
var_log_dose_mg <- as.vector(t(delta_g) %*% cov_beta %*% delta_g) |
| 109 | ||
| 110 |
# 95% credibility interval for max gain dose. |
|
| 111 | 26x |
ci_mg <- exp(log_dose_mg + c(-1, 1) * 1.96 * sqrt(var_log_dose_mg)) |
| 112 | 26x |
ci_ratio_mg <- ci_mg[2] / ci_mg[1] |
| 113 | ||
| 114 | 26x |
list( |
| 115 | 26x |
ci_dose_target = ci_dose_target, |
| 116 | 26x |
ci_ratio_dose_target = cir_dose_target, |
| 117 | 26x |
ci_dose_mg = ci_mg, |
| 118 | 26x |
ci_ratio_dose_mg = ci_ratio_mg |
| 119 |
) |
|
| 120 |
} |
|
| 121 | ||
| 122 |
## next best at grid ---- |
|
| 123 | ||
| 124 |
#' Get Closest Grid Doses for a Given Target Doses for `nextBest-NextBestMaxGain` Method. |
|
| 125 |
#' |
|
| 126 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 127 |
#' |
|
| 128 |
#' Helper function that for a given target doses finds the dose in grid that is |
|
| 129 |
#' closest and below the target. There are four different targets in the context |
|
| 130 |
#' of [`nextBest-NextBestMaxGain()`] method: \eqn{min(`dose_mg`, `dose_target_drt`)},
|
|
| 131 |
#' `dose_mg`, `dose_target_drt` or `dose_target_eot`. |
|
| 132 |
#' |
|
| 133 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
| 134 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
| 135 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
| 136 |
#' @param dose_grid (`numeric`)\cr all possible doses. |
|
| 137 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 138 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level in the `dose_grid` |
|
| 139 |
#' is considered as placebo. |
|
| 140 |
#' |
|
| 141 |
#' @export |
|
| 142 |
#' |
|
| 143 |
h_next_best_mg_doses_at_grid <- function( |
|
| 144 |
dose_target_drt, |
|
| 145 |
dose_target_eot, |
|
| 146 |
dose_mg, |
|
| 147 |
dose_grid, |
|
| 148 |
doselimit, |
|
| 149 |
placebo |
|
| 150 |
) {
|
|
| 151 | 41x |
assert_number(dose_target_drt, na.ok = TRUE) |
| 152 | 41x |
assert_number(dose_target_eot, na.ok = TRUE) |
| 153 | 41x |
assert_number(dose_mg, na.ok = TRUE) |
| 154 | ||
| 155 | 41x |
doses_eligible <- h_next_best_eligible_doses(dose_grid, doselimit, placebo) |
| 156 | ||
| 157 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
| 158 | 41x |
next_dose_lev <- h_find_interval( |
| 159 | 41x |
min(dose_mg, dose_target_drt), |
| 160 | 41x |
doses_eligible |
| 161 |
) |
|
| 162 | 41x |
next_dose <- doses_eligible[next_dose_lev] |
| 163 | ||
| 164 | 41x |
next_dose_mg_lev <- h_find_interval(dose_mg, doses_eligible) |
| 165 | 41x |
next_dose_mg <- doses_eligible[next_dose_mg_lev] |
| 166 | ||
| 167 | 41x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
| 168 | 41x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
| 169 | ||
| 170 | 41x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
| 171 | 41x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
| 172 | ||
| 173 | 41x |
next_dose_list <- list( |
| 174 | 41x |
next_dose = next_dose, |
| 175 | 41x |
next_dose_drt = next_dose_drt, |
| 176 | 41x |
next_dose_eot = next_dose_eot, |
| 177 | 41x |
next_dose_mg = next_dose_mg |
| 178 |
) |
|
| 179 |
} |
|
| 180 | ||
| 181 |
## eligible doses ---- |
|
| 182 | ||
| 183 |
#' Get Eligible Doses from the Dose Grid. |
|
| 184 |
#' |
|
| 185 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 186 |
#' |
|
| 187 |
#' Helper function that gets the eligible doses from the dose grid. |
|
| 188 |
#' The eligible doses are the doses which do not exceed a given |
|
| 189 |
#' `doselimit`. For placebo design, if safety allows (i.e. if there is at least |
|
| 190 |
#' one non-placebo dose which does not exceed the dose limit), the placebo dose |
|
| 191 |
#' is then excluded from the eligible doses. |
|
| 192 |
#' |
|
| 193 |
#' @param dose_grid (`numeric`)\cr all possible doses. |
|
| 194 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 195 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level in the `dose_grid` |
|
| 196 |
#' is considered as placebo. |
|
| 197 |
#' @param levels (`flag`)\cr if `TRUE` the levels of eligible doses are returned, |
|
| 198 |
#' otherwise, the doses (default). |
|
| 199 |
#' |
|
| 200 |
#' @return A numeric vector with eligible doses or eligible dose levels if `levels` |
|
| 201 |
#' flag is `TRUE`. |
|
| 202 |
#' |
|
| 203 |
#' @export |
|
| 204 |
#' @examples |
|
| 205 |
#' dose_grid <- c(0.001, seq(25, 200, 25)) |
|
| 206 |
#' h_next_best_eligible_doses(dose_grid, 79, TRUE) |
|
| 207 |
#' h_next_best_eligible_doses(dose_grid, 24, TRUE) |
|
| 208 |
h_next_best_eligible_doses <- function( |
|
| 209 |
dose_grid, |
|
| 210 |
doselimit, |
|
| 211 |
placebo, |
|
| 212 |
levels = FALSE |
|
| 213 |
) {
|
|
| 214 | 562x |
assert_numeric( |
| 215 | 562x |
dose_grid, |
| 216 | 562x |
finite = TRUE, |
| 217 | 562x |
any.missing = FALSE, |
| 218 | 562x |
min.len = 1L, |
| 219 | 562x |
sorted = TRUE |
| 220 |
) |
|
| 221 | 560x |
assert_number(doselimit) |
| 222 | 560x |
assert_flag(placebo) |
| 223 | 560x |
assert_flag(levels) |
| 224 | ||
| 225 | 560x |
is_dose_eligible <- dose_grid <= doselimit |
| 226 | 560x |
if (placebo && sum(is_dose_eligible) > 1L) {
|
| 227 | 85x |
is_dose_eligible[1] <- FALSE |
| 228 |
} |
|
| 229 | ||
| 230 | 560x |
if (levels) {
|
| 231 | 439x |
is_dose_eligible |
| 232 |
} else {
|
|
| 233 | 121x |
dose_grid[is_dose_eligible] |
| 234 |
} |
|
| 235 |
} |
|
| 236 | ||
| 237 |
## plot ---- |
|
| 238 | ||
| 239 |
#' Building the Plot for `nextBest-NextBestNCRMLoss` Method. |
|
| 240 |
#' |
|
| 241 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 242 |
#' |
|
| 243 |
#' Helper function which creates the plot for [`nextBest-NextBestNCRMLoss()`] |
|
| 244 |
#' method. |
|
| 245 |
#' |
|
| 246 |
#' @param prob_mat (`numeric`)\cr matrix with probabilities of a grid doses |
|
| 247 |
#' to be in a given interval. If `is_unacceptable_specified` is `TRUE`, there |
|
| 248 |
#' must be 4 intervals (columns) in `prob_mat`: `underdosing`, `target`, |
|
| 249 |
#' `excessive`, `unacceptable`. Otherwise, there must be 3 intervals (columns): |
|
| 250 |
#' `underdosing`, `target`, `overdose`. Number of rows must be equal to number |
|
| 251 |
#' of doses in a grid. |
|
| 252 |
#' @param posterior_loss (`numeric`)\cr posterior losses. |
|
| 253 |
#' @param max_overdose_prob (`number`)\cr maximum overdose posterior |
|
| 254 |
#' probability that is allowed. |
|
| 255 |
#' @param dose_grid (`numeric`)\cr dose grid. |
|
| 256 |
#' @param max_eligible_dose_level (`number`)\cr maximum eligible dose level in |
|
| 257 |
#' the `dose_grid`. |
|
| 258 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 259 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 260 |
#' @param is_unacceptable_specified (`flag`)\cr is unacceptable interval specified? |
|
| 261 |
#' |
|
| 262 |
#' @export |
|
| 263 |
h_next_best_ncrm_loss_plot <- function( |
|
| 264 |
prob_mat, |
|
| 265 |
posterior_loss, |
|
| 266 |
max_overdose_prob, |
|
| 267 |
dose_grid, |
|
| 268 |
max_eligible_dose_level, |
|
| 269 |
doselimit, |
|
| 270 |
next_dose, |
|
| 271 |
is_unacceptable_specified |
|
| 272 |
) {
|
|
| 273 | 16x |
assert_numeric(dose_grid, finite = TRUE, any.missing = FALSE, sorted = TRUE) |
| 274 | 16x |
n_grid <- length(dose_grid) |
| 275 | 16x |
assert_flag(is_unacceptable_specified) |
| 276 | 16x |
assert_probabilities(prob_mat) |
| 277 | 16x |
assert_matrix( |
| 278 | 16x |
prob_mat, |
| 279 | 16x |
min.cols = 3, |
| 280 | 16x |
max.cols = 4, |
| 281 | 16x |
nrows = n_grid, |
| 282 | 16x |
col.names = "named" |
| 283 |
) |
|
| 284 | 16x |
if (!is_unacceptable_specified) {
|
| 285 | 4x |
assert_names( |
| 286 | 4x |
colnames(prob_mat), |
| 287 | 4x |
permutation.of = c("underdosing", "target", "overdose")
|
| 288 |
) |
|
| 289 |
} else {
|
|
| 290 | 12x |
assert_names( |
| 291 | 12x |
colnames(prob_mat), |
| 292 | 12x |
permutation.of = c("underdosing", "target", "excessive", "unacceptable")
|
| 293 |
) |
|
| 294 |
} |
|
| 295 | 16x |
assert_numeric( |
| 296 | 16x |
posterior_loss, |
| 297 | 16x |
finite = TRUE, |
| 298 | 16x |
any.missing = FALSE, |
| 299 | 16x |
len = n_grid |
| 300 |
) |
|
| 301 | 16x |
assert_probability(max_overdose_prob) |
| 302 | 16x |
assert_number(max_eligible_dose_level, lower = 0, upper = n_grid) |
| 303 | 16x |
assert_number(doselimit) |
| 304 | 16x |
assert_number(next_dose, na.ok = TRUE) |
| 305 | ||
| 306 |
# Build plots, first for the target probability. |
|
| 307 | 16x |
p1 <- ggplot() + |
| 308 | 16x |
geom_bar( |
| 309 | 16x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "target"] * 100), |
| 310 | 16x |
aes(x = .data$Dose, y = .data$y), |
| 311 | 16x |
stat = "identity", |
| 312 | 16x |
position = "identity", |
| 313 | 16x |
width = min(diff(dose_grid)) / 2, |
| 314 | 16x |
colour = "darkgreen", |
| 315 | 16x |
fill = "darkgreen" |
| 316 |
) + |
|
| 317 | 16x |
ylim(c(0, 100)) + |
| 318 | 16x |
ylab(paste("Target probability [%]"))
|
| 319 | ||
| 320 | 16x |
if (is.finite(doselimit)) {
|
| 321 | 9x |
p1 <- p1 + |
| 322 | 9x |
geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
| 323 |
} |
|
| 324 | ||
| 325 | 16x |
if (max_eligible_dose_level > 0) {
|
| 326 | 16x |
p1 <- p1 + |
| 327 | 16x |
geom_vline( |
| 328 | 16x |
xintercept = dose_grid[max_eligible_dose_level], |
| 329 | 16x |
lwd = 1.1, |
| 330 | 16x |
lty = 2, |
| 331 | 16x |
colour = "red" |
| 332 |
) |
|
| 333 |
} |
|
| 334 | ||
| 335 | 16x |
p_loss <- ggplot() + |
| 336 |
# For the loss function. |
|
| 337 | 16x |
geom_bar( |
| 338 | 16x |
data = data.frame(Dose = dose_grid, y = posterior_loss), |
| 339 | 16x |
aes(x = .data$Dose, y = .data$y), |
| 340 | 16x |
stat = "identity", |
| 341 | 16x |
position = "identity", |
| 342 | 16x |
width = min(diff(dose_grid)) / 2, |
| 343 | 16x |
colour = "darkgreen", |
| 344 | 16x |
fill = "darkgreen" |
| 345 |
) + |
|
| 346 | 16x |
geom_point( |
| 347 | 16x |
aes(x = next_dose, y = max(posterior_loss) + 0.2), |
| 348 | 16x |
size = 3, |
| 349 | 16x |
pch = 25, |
| 350 | 16x |
col = "red", |
| 351 | 16x |
bg = "red" |
| 352 |
) + |
|
| 353 | 16x |
ylab(paste("Loss function"))
|
| 354 | ||
| 355 | 16x |
if (!is_unacceptable_specified) {
|
| 356 |
# Second, for the overdosing probability. |
|
| 357 | 4x |
p2 <- ggplot() + |
| 358 | 4x |
geom_bar( |
| 359 | 4x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "overdose"] * 100), |
| 360 | 4x |
aes(x = .data$Dose, y = .data$y), |
| 361 | 4x |
stat = "identity", |
| 362 | 4x |
position = "identity", |
| 363 | 4x |
width = min(diff(dose_grid)) / 2, |
| 364 | 4x |
colour = "red", |
| 365 | 4x |
fill = "red" |
| 366 |
) + |
|
| 367 | 4x |
geom_hline( |
| 368 | 4x |
yintercept = max_overdose_prob * 100, |
| 369 | 4x |
lwd = 1.1, |
| 370 | 4x |
lty = 2, |
| 371 | 4x |
colour = "black" |
| 372 |
) + |
|
| 373 | 4x |
ylim(c(0, 100)) + |
| 374 | 4x |
ylab("Overdose probability [%]")
|
| 375 | ||
| 376 |
# Combine it all together. |
|
| 377 | 4x |
plots_single <- list(plot1 = p1, plot2 = p2, plot_loss = p_loss) |
| 378 | 4x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, p_loss, nrow = 3) |
| 379 |
} else {
|
|
| 380 |
# Plot in case of 4 toxicity intervals. Second, for the overdosing probability. |
|
| 381 | 12x |
p2 <- ggplot() + |
| 382 | 12x |
geom_bar( |
| 383 | 12x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "excessive"] * 100), |
| 384 | 12x |
aes(x = .data$Dose, y = .data$y), |
| 385 | 12x |
stat = "identity", |
| 386 | 12x |
position = "identity", |
| 387 | 12x |
width = min(diff(dose_grid)) / 2, |
| 388 | 12x |
colour = "red", |
| 389 | 12x |
fill = "red" |
| 390 |
) + |
|
| 391 | 12x |
ylim(c(0, 100)) + |
| 392 | 12x |
ylab("Excessive probability [%]")
|
| 393 | ||
| 394 | 12x |
p3 <- ggplot() + |
| 395 | 12x |
geom_bar( |
| 396 | 12x |
data = data.frame( |
| 397 | 12x |
Dose = dose_grid, |
| 398 | 12x |
y = prob_mat[, "unacceptable"] * 100 |
| 399 |
), |
|
| 400 | 12x |
aes(x = .data$Dose, y = .data$y), |
| 401 | 12x |
stat = "identity", |
| 402 | 12x |
position = "identity", |
| 403 | 12x |
width = min(diff(dose_grid)) / 2, |
| 404 | 12x |
colour = "red", |
| 405 | 12x |
fill = "red" |
| 406 |
) + |
|
| 407 | 12x |
ylim(c(0, 100)) + |
| 408 | 12x |
ylab("Unacceptable probability [%]")
|
| 409 | ||
| 410 |
# Combine it all together. |
|
| 411 | 12x |
plots_single <- list(plot1 = p1, plot2 = p2, plot3 = p3, plot_loss = p_loss) |
| 412 | 12x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, p3, p_loss, nrow = 4) |
| 413 |
} |
|
| 414 | ||
| 415 | 16x |
list(plots_single = plots_single, plot_joint = plot_joint) |
| 416 |
} |
|
| 417 | ||
| 418 |
#' Building the Plot for `nextBest-NextBestTDsamples` Method. |
|
| 419 |
#' |
|
| 420 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 421 |
#' |
|
| 422 |
#' Helper function which creates the plot for [`nextBest-NextBestTDsamples()`] |
|
| 423 |
#' method. |
|
| 424 |
#' |
|
| 425 |
#' @param dose_target_drt_samples (`numeric`)\cr vector of in-trial samples. |
|
| 426 |
#' @param dose_target_eot_samples (`numeric`)\cr vector of end-of-trial samples. |
|
| 427 |
#' @param dose_target_drt (`number`)\cr target in-trial estimate. |
|
| 428 |
#' @param dose_target_eot (`number`)\cr target end-of-trial estimate. |
|
| 429 |
#' @param dose_grid_range (`numeric`)\cr range of dose grid. |
|
| 430 |
#' @param nextBest (`NextBestTDsamples`)\cr the rule for the next best dose. |
|
| 431 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 432 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 433 |
#' |
|
| 434 |
#' @export |
|
| 435 |
#' |
|
| 436 |
h_next_best_tdsamples_plot <- function( |
|
| 437 |
dose_target_drt_samples, |
|
| 438 |
dose_target_eot_samples, |
|
| 439 |
dose_target_drt, |
|
| 440 |
dose_target_eot, |
|
| 441 |
dose_grid_range, |
|
| 442 |
nextBest, |
|
| 443 |
doselimit, |
|
| 444 |
next_dose |
|
| 445 |
) {
|
|
| 446 | 26x |
assert_numeric(dose_target_drt_samples, any.missing = FALSE) |
| 447 | 26x |
assert_numeric(dose_target_eot_samples, any.missing = FALSE) |
| 448 | 26x |
assert_number(dose_target_drt) |
| 449 | 26x |
assert_number(dose_target_eot) |
| 450 | 26x |
assert_range(dose_grid_range, finite = TRUE, unique = FALSE) |
| 451 | 26x |
assert_class(nextBest, "NextBestTDsamples") |
| 452 | 26x |
assert_number(doselimit) |
| 453 | 26x |
assert_number(next_dose, na.ok = TRUE) |
| 454 | ||
| 455 | 26x |
lbl1 <- paste("TD", nextBest@prob_target_drt * 100, "Estimate")
|
| 456 | 26x |
lbl2 <- paste("TD", nextBest@prob_target_eot * 100, "Estimate")
|
| 457 | 26x |
labels <- data.frame( |
| 458 | 26x |
Type = c("during", "end", "Max", "Next"),
|
| 459 | 26x |
Alpha = c(0.25, 0.25, 1, 1), |
| 460 | 26x |
x = c( |
| 461 | 26x |
dose_target_drt, |
| 462 | 26x |
dose_target_eot, |
| 463 | 26x |
min(doselimit, dose_grid_range[2]), |
| 464 | 26x |
next_dose |
| 465 |
) |
|
| 466 |
) |
|
| 467 | 26x |
p <- ggplot( |
| 468 | 26x |
data = rbind( |
| 469 | 26x |
data.frame(period = "during", TD = dose_target_drt_samples), |
| 470 | 26x |
data.frame(period = "end", TD = dose_target_eot_samples) |
| 471 |
), |
|
| 472 | 26x |
aes(x = .data$TD, colour = .data$period), |
| 473 |
) + |
|
| 474 | 26x |
geom_density( |
| 475 | 26x |
aes(fill = .data$period, colour = .data$period), |
| 476 | 26x |
alpha = 0.25, |
| 477 | 26x |
bounds = dose_grid_range, |
| 478 | 26x |
show.legend = FALSE |
| 479 |
) + |
|
| 480 | 26x |
geom_vline(data = labels, aes(xintercept = x, colour = Type)) + |
| 481 | 26x |
ylab("Posterior density") +
|
| 482 | 26x |
scale_colour_manual( |
| 483 | 26x |
name = NULL, |
| 484 | 26x |
values = c( |
| 485 | 26x |
"during" = "orange", |
| 486 | 26x |
"end" = "violet", |
| 487 | 26x |
"Max" = "red", |
| 488 | 26x |
"Next" = "blue" |
| 489 |
), |
|
| 490 | 26x |
labels = c("during" = lbl1, "end" = lbl2, "Max" = "Max", "Next" = "Next")
|
| 491 |
) + |
|
| 492 | 26x |
scale_fill_manual( |
| 493 | 26x |
values = c("during" = "orange", "end" = "violet")
|
| 494 |
) |
|
| 495 |
} |
|
| 496 | ||
| 497 |
#' Building the Plot for `nextBest-NextBestTD` Method. |
|
| 498 |
#' |
|
| 499 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 500 |
#' |
|
| 501 |
#' Helper function which creates the plot for [`nextBest-NextBestTD()`] method. |
|
| 502 |
#' |
|
| 503 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
| 504 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
| 505 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
| 506 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
| 507 |
#' @param data (`Data`)\cr the data object from which the dose grid will be fetched. |
|
| 508 |
#' @param prob_dlt (`numeric`)\cr DLT probabilities for doses in grid. |
|
| 509 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 510 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 511 |
#' |
|
| 512 |
#' @export |
|
| 513 |
#' |
|
| 514 |
h_next_best_td_plot <- function( |
|
| 515 |
prob_target_drt, |
|
| 516 |
dose_target_drt, |
|
| 517 |
prob_target_eot, |
|
| 518 |
dose_target_eot, |
|
| 519 |
data, |
|
| 520 |
prob_dlt, |
|
| 521 |
doselimit, |
|
| 522 |
next_dose |
|
| 523 |
) {
|
|
| 524 | 34x |
assert_probability(prob_target_drt) |
| 525 | 34x |
assert_number(dose_target_drt) |
| 526 | 34x |
assert_probability(prob_target_eot) |
| 527 | 34x |
assert_number(dose_target_eot) |
| 528 | 34x |
assert_class(data, "Data") |
| 529 | 34x |
assert_probabilities(prob_dlt, len = data@nGrid) |
| 530 | 34x |
assert_number(doselimit) |
| 531 | 34x |
assert_number(next_dose, na.ok = TRUE) |
| 532 | ||
| 533 | 34x |
dosegrid_range <- dose_grid_range(data) |
| 534 | ||
| 535 | 34x |
p <- ggplot( |
| 536 | 34x |
data = data.frame(x = data@doseGrid, y = prob_dlt), |
| 537 | 34x |
aes(x = .data$x, y = .data$y) |
| 538 |
) + |
|
| 539 | 34x |
geom_line(colour = "red", linewidth = 1.5) + |
| 540 | 34x |
coord_cartesian(xlim = c(0, dosegrid_range[2])) + |
| 541 | 34x |
ylim(c(0, 1)) + |
| 542 | 34x |
xlab("Dose Levels") +
|
| 543 | 34x |
ylab("Probability of DLT")
|
| 544 |
if ( |
|
| 545 | 34x |
h_in_range(dose_target_drt, range = dosegrid_range, bounds_closed = TRUE) |
| 546 |
) {
|
|
| 547 | 32x |
p <- p + |
| 548 | 32x |
geom_point( |
| 549 | 32x |
data = data.frame(x = dose_target_drt, y = prob_target_drt), |
| 550 | 32x |
aes(x = .data$x, y = .data$y), |
| 551 | 32x |
colour = "orange", |
| 552 | 32x |
shape = 15, |
| 553 | 32x |
size = 8 |
| 554 |
) + |
|
| 555 | 32x |
annotate( |
| 556 | 32x |
geom = "text", |
| 557 | 32x |
label = paste("TD", prob_target_drt * 100, "Estimate"),
|
| 558 | 32x |
x = dose_target_drt + 1, |
| 559 | 32x |
y = prob_target_drt - 0.2, |
| 560 | 32x |
size = 5, |
| 561 | 32x |
colour = "orange" |
| 562 |
) |
|
| 563 |
} |
|
| 564 | ||
| 565 |
if ( |
|
| 566 | 34x |
h_in_range(dose_target_eot, range = dosegrid_range, bounds_closed = TRUE) |
| 567 |
) {
|
|
| 568 | 32x |
p <- p + |
| 569 | 32x |
geom_point( |
| 570 | 32x |
data = data.frame(x = dose_target_eot, y = prob_target_eot), |
| 571 | 32x |
aes(x = .data$x, y = .data$y), |
| 572 | 32x |
colour = "violet", |
| 573 | 32x |
shape = 16, |
| 574 | 32x |
size = 8 |
| 575 |
) + |
|
| 576 | 32x |
annotate( |
| 577 | 32x |
geom = "text", |
| 578 | 32x |
label = paste("TD", prob_target_eot * 100, "Estimate"),
|
| 579 | 32x |
x = dose_target_eot + 1, |
| 580 | 32x |
y = prob_target_eot - 0.1, |
| 581 | 32x |
size = 5, |
| 582 | 32x |
colour = "violet" |
| 583 |
) |
|
| 584 |
} |
|
| 585 | ||
| 586 | 34x |
maxdoselimit <- min(doselimit, dosegrid_range[2]) |
| 587 | ||
| 588 | 34x |
p + |
| 589 | 34x |
geom_vline(xintercept = maxdoselimit, colour = "brown", lwd = 1.1) + |
| 590 | 34x |
geom_text( |
| 591 | 34x |
data = data.frame(x = maxdoselimit, y = 0), |
| 592 | 34x |
aes(x = .data$x, y = .data$y, label = "Max", hjust = +1, vjust = -30), |
| 593 | 34x |
angle = 90, |
| 594 | 34x |
vjust = 1.5, |
| 595 | 34x |
hjust = 0.5, |
| 596 | 34x |
colour = "brown", |
| 597 |
) + |
|
| 598 | 34x |
geom_vline(xintercept = next_dose, colour = "purple", lwd = 1.1) + |
| 599 | 34x |
geom_text( |
| 600 | 34x |
data = data.frame(x = next_dose, y = 0), |
| 601 | 34x |
aes(x = .data$x, y = .data$y, label = "Next", hjust = 0, vjust = -30), |
| 602 | 34x |
angle = 90, |
| 603 | 34x |
vjust = -0.5, |
| 604 | 34x |
hjust = 0.5, |
| 605 | 34x |
colour = "purple" |
| 606 |
) |
|
| 607 |
} |
|
| 608 | ||
| 609 |
#' Building the Plot for `nextBest-NextBestMaxGain` Method. |
|
| 610 |
#' |
|
| 611 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 612 |
#' |
|
| 613 |
#' Helper function which creates the plot for [`nextBest-NextBestMaxGain()`] method. |
|
| 614 |
#' |
|
| 615 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
| 616 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
| 617 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
| 618 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
| 619 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
| 620 |
#' @param max_gain (`number`)\cr the maximum gain estimate. |
|
| 621 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 622 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 623 |
#' @param data (`DataDual`)\cr the data object from which the dose grid will be fetched. |
|
| 624 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 625 |
#' @param model_eff (`Effloglog`)\cr the efficacy model. |
|
| 626 |
#' |
|
| 627 |
#' @export |
|
| 628 |
#' |
|
| 629 |
h_next_best_mg_plot <- function( |
|
| 630 |
prob_target_drt, |
|
| 631 |
dose_target_drt, |
|
| 632 |
prob_target_eot, |
|
| 633 |
dose_target_eot, |
|
| 634 |
dose_mg, |
|
| 635 |
max_gain, |
|
| 636 |
next_dose, |
|
| 637 |
doselimit, |
|
| 638 |
data, |
|
| 639 |
model, |
|
| 640 |
model_eff |
|
| 641 |
) {
|
|
| 642 | 26x |
assert_probability(prob_target_drt) |
| 643 | 26x |
assert_number(dose_target_drt) |
| 644 | 26x |
assert_probability(prob_target_eot) |
| 645 | 26x |
assert_number(dose_target_eot) |
| 646 | 26x |
assert_number(dose_mg, na.ok = TRUE) |
| 647 | 26x |
assert_number(max_gain, na.ok = TRUE) |
| 648 | 26x |
assert_number(next_dose, na.ok = TRUE) |
| 649 | 26x |
assert_number(doselimit) |
| 650 | 26x |
assert_class(data, "Data") |
| 651 | 26x |
assert_class(model, "ModelTox") |
| 652 | 26x |
assert_class(model_eff, "Effloglog") |
| 653 | ||
| 654 | 26x |
dosegrid_range <- dose_grid_range(data) |
| 655 | ||
| 656 | 26x |
data_plot <- data.frame( |
| 657 | 26x |
dose = rep(data@doseGrid, 3), |
| 658 | 26x |
y = c( |
| 659 | 26x |
prob(dose = data@doseGrid, model = model), |
| 660 | 26x |
efficacy(dose = data@doseGrid, model = model_eff), |
| 661 | 26x |
gain(dose = data@doseGrid, model_dle = model, model_eff = model_eff) |
| 662 |
), |
|
| 663 | 26x |
group = c( |
| 664 | 26x |
rep("p(DLE)", data@nGrid),
|
| 665 | 26x |
rep("Expected Efficacy", data@nGrid),
|
| 666 | 26x |
rep("Gain", data@nGrid)
|
| 667 |
) |
|
| 668 |
) |
|
| 669 | ||
| 670 | 26x |
p <- ggplot(data = data_plot, aes(x = .data$dose, y = .data$y)) + |
| 671 | 26x |
geom_line(aes(group = group, color = group), linewidth = 1.5) + |
| 672 | 26x |
ggplot2::scale_colour_manual( |
| 673 | 26x |
name = "curves", |
| 674 | 26x |
values = c("blue", "green3", "red")
|
| 675 |
) + |
|
| 676 | 26x |
coord_cartesian(xlim = c(0, dosegrid_range[2])) + |
| 677 | 26x |
ylim(range(data_plot$y)) + |
| 678 | 26x |
xlab("Dose Level") +
|
| 679 | 26x |
ylab("Values")
|
| 680 | ||
| 681 |
if ( |
|
| 682 | 26x |
h_in_range(dose_target_eot, range = dosegrid_range, bounds_closed = FALSE) |
| 683 |
) {
|
|
| 684 | 26x |
lab <- paste("TD", prob_target_eot * 100, "Estimate")
|
| 685 | 26x |
p <- p + |
| 686 | 26x |
geom_point( |
| 687 | 26x |
data = data.frame(x = dose_target_eot, y = prob_target_eot), |
| 688 | 26x |
aes(x = .data$x, y = .data$y), |
| 689 | 26x |
colour = "violet", |
| 690 | 26x |
shape = 16, |
| 691 | 26x |
size = 8 |
| 692 |
) + |
|
| 693 | 26x |
annotate( |
| 694 | 26x |
geom = "text", |
| 695 | 26x |
label = lab, |
| 696 | 26x |
x = dose_target_eot - 1, |
| 697 | 26x |
y = 0.2, |
| 698 | 26x |
size = 5, |
| 699 | 26x |
colour = "violet" |
| 700 |
) |
|
| 701 |
} |
|
| 702 | ||
| 703 | 26x |
if (h_in_range(dose_mg, range = dosegrid_range, bounds_closed = FALSE)) {
|
| 704 | 25x |
p <- p + |
| 705 | 25x |
geom_point( |
| 706 | 25x |
data = data.frame(x = dose_mg, y = max_gain), |
| 707 | 25x |
aes(x = .data$x, y = .data$y), |
| 708 | 25x |
colour = "green3", |
| 709 | 25x |
shape = 17, |
| 710 | 25x |
size = 8 |
| 711 |
) + |
|
| 712 | 25x |
annotate( |
| 713 | 25x |
"text", |
| 714 | 25x |
label = "Max Gain Estimate", |
| 715 | 25x |
x = dose_mg, |
| 716 | 25x |
y = max_gain - 0.1, |
| 717 | 25x |
size = 5, |
| 718 | 25x |
colour = "green3" |
| 719 |
) |
|
| 720 |
} |
|
| 721 | ||
| 722 |
if ( |
|
| 723 | 26x |
h_in_range(dose_target_drt, range = dosegrid_range, bounds_closed = FALSE) |
| 724 |
) {
|
|
| 725 | 26x |
lab <- paste("TD", prob_target_drt * 100, "Estimate")
|
| 726 | 26x |
p <- p + |
| 727 | 26x |
geom_point( |
| 728 | 26x |
data = data.frame(x = dose_target_drt, y = prob_target_drt), |
| 729 | 26x |
aes(x = .data$x, y = .data$y), |
| 730 | 26x |
colour = "orange", |
| 731 | 26x |
shape = 15, |
| 732 | 26x |
size = 8 |
| 733 |
) + |
|
| 734 | 26x |
annotate( |
| 735 | 26x |
geom = "text", |
| 736 | 26x |
label = lab, |
| 737 | 26x |
x = dose_target_drt + 25, |
| 738 | 26x |
y = prob_target_drt + 0.01, |
| 739 | 26x |
size = 5, |
| 740 | 26x |
colour = "orange" |
| 741 |
) |
|
| 742 |
} |
|
| 743 | ||
| 744 | 26x |
maxdoselimit <- min(doselimit, dosegrid_range[2]) |
| 745 | ||
| 746 | 26x |
p + |
| 747 | 26x |
geom_vline(xintercept = maxdoselimit, colour = "brown", lwd = 1.1) + |
| 748 | 26x |
annotate( |
| 749 | 26x |
geom = "text", |
| 750 | 26x |
label = "Max", |
| 751 | 26x |
x = maxdoselimit - 2, |
| 752 | 26x |
y = max(data_plot$y), |
| 753 | 26x |
size = 5, |
| 754 | 26x |
angle = 90, |
| 755 | 26x |
vjust = -0.5, |
| 756 | 26x |
hjust = 0.5, |
| 757 | 26x |
colour = "brown" |
| 758 |
) + |
|
| 759 | 26x |
geom_vline(xintercept = next_dose, colour = "purple", lwd = 1.1) + |
| 760 | 26x |
annotate( |
| 761 | 26x |
geom = "text", |
| 762 | 26x |
label = "Next", |
| 763 | 26x |
x = next_dose + 1, |
| 764 | 26x |
y = max(data_plot$y) - 0.05, |
| 765 | 26x |
size = 5, |
| 766 | 26x |
angle = 90, |
| 767 | 26x |
vjust = 1.5, |
| 768 | 26x |
hjust = 0.5, |
| 769 | 26x |
color = "purple" |
| 770 |
) |
|
| 771 |
} |
|
| 772 | ||
| 773 |
#' Building the Plot for `nextBest-NextBestMaxGainSamples` Method. |
|
| 774 |
#' |
|
| 775 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 776 |
#' |
|
| 777 |
#' Helper function which creates the plot for [`nextBest-NextBestMaxGainSamples()`] method. |
|
| 778 |
#' |
|
| 779 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
| 780 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
| 781 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
| 782 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
| 783 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
| 784 |
#' @param dose_mg_samples (`numeric`)\cr for every sample, the dose (from the dose grid) |
|
| 785 |
#' that gives the maximum gain value. |
|
| 786 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 787 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 788 |
#' @param dose_grid_range (`numeric`)\cr dose grid range. |
|
| 789 |
#' |
|
| 790 |
#' @export |
|
| 791 |
#' |
|
| 792 |
h_next_best_mgsamples_plot <- function( |
|
| 793 |
prob_target_drt, |
|
| 794 |
dose_target_drt, |
|
| 795 |
prob_target_eot, |
|
| 796 |
dose_target_eot, |
|
| 797 |
dose_mg, |
|
| 798 |
dose_mg_samples, |
|
| 799 |
next_dose, |
|
| 800 |
doselimit, |
|
| 801 |
dose_grid_range |
|
| 802 |
) {
|
|
| 803 | 15x |
assert_range(dose_grid_range, finite = TRUE, unique = FALSE) |
| 804 | 15x |
assert_probability(prob_target_drt) |
| 805 | 15x |
assert_number(dose_target_drt) |
| 806 | 15x |
assert_probability(prob_target_eot) |
| 807 | 15x |
assert_number(dose_target_eot) |
| 808 | 15x |
assert_number(dose_mg, na.ok = TRUE) |
| 809 | 15x |
assert_numeric( |
| 810 | 15x |
dose_mg_samples, |
| 811 | 15x |
lower = dose_grid_range[1], |
| 812 | 15x |
upper = dose_grid_range[2], |
| 813 | 15x |
finite = TRUE, |
| 814 | 15x |
any.missing = FALSE |
| 815 |
) |
|
| 816 | 15x |
assert_number(next_dose, na.ok = TRUE) |
| 817 | 15x |
assert_number(doselimit) |
| 818 | ||
| 819 | 15x |
p <- ggplot() + |
| 820 | 15x |
geom_histogram( |
| 821 | 15x |
data = data.frame(Gstar = dose_mg_samples), |
| 822 | 15x |
aes(x = .data$Gstar), |
| 823 | 15x |
fill = "darkgreen", |
| 824 | 15x |
colour = "green3", |
| 825 | 15x |
binwidth = 25 |
| 826 |
) + |
|
| 827 | 15x |
coord_cartesian(xlim = c(0, dose_grid_range[2])) + |
| 828 | 15x |
ylab("Posterior density")
|
| 829 | ||
| 830 |
if ( |
|
| 831 | 15x |
h_in_range(dose_target_drt, range = dose_grid_range, bounds_closed = FALSE) |
| 832 |
) {
|
|
| 833 | 13x |
lab <- paste("TD", prob_target_drt * 100, "Estimate")
|
| 834 | 13x |
p <- p + |
| 835 | 13x |
geom_vline(xintercept = dose_target_drt, colour = "orange", lwd = 1.1) + |
| 836 | 13x |
annotate( |
| 837 | 13x |
geom = "text", |
| 838 | 13x |
label = lab, |
| 839 | 13x |
x = dose_target_drt, |
| 840 | 13x |
y = 0, |
| 841 | 13x |
hjust = -0.1, |
| 842 | 13x |
vjust = -20, |
| 843 | 13x |
size = 5, |
| 844 | 13x |
colour = "orange" |
| 845 |
) |
|
| 846 |
} |
|
| 847 | ||
| 848 |
if ( |
|
| 849 | 15x |
h_in_range(dose_target_eot, range = dose_grid_range, bounds_closed = FALSE) |
| 850 |
) {
|
|
| 851 | 13x |
lab <- paste("TD", prob_target_eot * 100, "Estimate")
|
| 852 | 13x |
p <- p + |
| 853 | 13x |
geom_vline(xintercept = dose_target_eot, colour = "violet", lwd = 1.1) + |
| 854 | 13x |
annotate( |
| 855 | 13x |
geom = "text", |
| 856 | 13x |
label = lab, |
| 857 | 13x |
x = dose_target_eot, |
| 858 | 13x |
y = 0, |
| 859 | 13x |
hjust = -0.1, |
| 860 | 13x |
vjust = -25, |
| 861 | 13x |
size = 5, |
| 862 | 13x |
colour = "violet" |
| 863 |
) |
|
| 864 |
} |
|
| 865 | ||
| 866 | 15x |
if (h_in_range(dose_mg, range = dose_grid_range, bounds_closed = FALSE)) {
|
| 867 | 8x |
lab <- "Gstar Estimate" |
| 868 | 8x |
p <- p + |
| 869 | 8x |
geom_vline(xintercept = dose_mg, colour = "green", lwd = 1.1) + |
| 870 | 8x |
annotate( |
| 871 | 8x |
geom = "text", |
| 872 | 8x |
label = lab, |
| 873 | 8x |
x = dose_mg, |
| 874 | 8x |
y = 0, |
| 875 | 8x |
hjust = -0.1, |
| 876 | 8x |
vjust = -25, |
| 877 | 8x |
size = 5, |
| 878 | 8x |
colour = "green" |
| 879 |
) |
|
| 880 |
} |
|
| 881 | ||
| 882 | 15x |
maxdoselimit <- min(doselimit, dose_grid_range[2]) |
| 883 | ||
| 884 | 15x |
p + |
| 885 | 15x |
geom_vline(xintercept = maxdoselimit, colour = "red", lwd = 1.1) + |
| 886 | 15x |
annotate( |
| 887 | 15x |
geom = "text", |
| 888 | 15x |
label = "Max", |
| 889 | 15x |
x = maxdoselimit, |
| 890 | 15x |
y = 0, |
| 891 | 15x |
hjust = +1, |
| 892 | 15x |
vjust = -35, |
| 893 | 15x |
colour = "red" |
| 894 |
) + |
|
| 895 | 15x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
| 896 | 15x |
annotate( |
| 897 | 15x |
geom = "text", |
| 898 | 15x |
label = "Next", |
| 899 | 15x |
x = next_dose, |
| 900 | 15x |
y = 0, |
| 901 | 15x |
hjust = 0.1, |
| 902 | 15x |
vjust = -30, |
| 903 | 15x |
colour = "blue" |
| 904 |
) |
|
| 905 |
} |
| 1 |
#' @include helpers.R |
|
| 2 |
#' @include helpers_jags.R |
|
| 3 |
#' @include Model-validity.R |
|
| 4 |
#' @include ModelParams-class.R |
|
| 5 |
#' @include CrmPackClass-class.R |
|
| 6 |
NULL |
|
| 7 | ||
| 8 |
# GeneralModel-class ---- |
|
| 9 | ||
| 10 |
#' `GeneralModel` |
|
| 11 |
#' |
|
| 12 |
#' @description `r lifecycle::badge("stable")`
|
|
| 13 |
#' |
|
| 14 |
#' [`GeneralModel`] is a general model class, from which all other specific |
|
| 15 |
#' model-like classes inherit. |
|
| 16 |
#' |
|
| 17 |
#' @note The `datamodel` must obey the convention that the data input is |
|
| 18 |
#' called exactly in the same way as in the corresponding data class. |
|
| 19 |
#' All prior distributions for parameters should be contained in the |
|
| 20 |
#' model function `priormodel`. The background is that this can |
|
| 21 |
#' be used to simulate from the prior distribution, before obtaining any data. |
|
| 22 |
#' |
|
| 23 |
#' @slot datamodel (`function`)\cr a function representing the `JAGS` data model |
|
| 24 |
#' specification. |
|
| 25 |
#' @slot priormodel (`function`)\cr a function representing the `JAGS` prior |
|
| 26 |
#' specification. |
|
| 27 |
#' @slot modelspecs (`function`)\cr a function computing the list of the data |
|
| 28 |
#' model and prior model specifications that are required to be specified |
|
| 29 |
#' completely (e.g. prior parameters, reference dose, etc.), based on the data |
|
| 30 |
#' slots that are required as arguments of this function. |
|
| 31 |
#' Apart of data arguments, this function can be specified with one additional |
|
| 32 |
#' (optional) argument `from_prior` of type `logical` and length one. This |
|
| 33 |
#' `from_prior` flag can be used to differentiate the output of the `modelspecs`, |
|
| 34 |
#' as its value is taken directly from the `from_prior` argument of the `mcmc` |
|
| 35 |
#' method that invokes `modelspecs` function. That is, when `from_prior` is |
|
| 36 |
#' `TRUE`, then only `priormodel` JAGS model is used (`datamodel` is not used) |
|
| 37 |
#' by the `mcmc`, and hence `modelspecs` function should return all the parameters |
|
| 38 |
#' that are required by the `priormodel` only. If the value of `from_prior` is |
|
| 39 |
#' `FALSE`, then both JAGS models `datamodel` and `priormodel` are used in the |
|
| 40 |
#' MCMC sampler, and hence `modelspecs` function should return all the parameters |
|
| 41 |
#' required by both `datamodel` and `priormodel`. |
|
| 42 |
#' @slot init (`function`)\cr a function computing the list of starting values |
|
| 43 |
#' for parameters required to be initialized in the MCMC sampler, based on the |
|
| 44 |
#' data slots that are required as arguments of this function. |
|
| 45 |
#' @slot datanames (`character`)\cr the names of all data slots that are used |
|
| 46 |
#' by `datamodel` JAGS function. No other names should be specified here. |
|
| 47 |
#' @slot datanames_prior (`character`)\cr the names of all data slots that are |
|
| 48 |
#' used by `priormodel` JAGS function. No other names should be specified here. |
|
| 49 |
#' @slot sample (`character`)\cr names of all parameters from which you would |
|
| 50 |
#' like to save the MCMC samples. |
|
| 51 |
#' |
|
| 52 |
#' @seealso [`ModelPseudo`]. |
|
| 53 |
#' |
|
| 54 |
#' @aliases GeneralModel |
|
| 55 |
#' @export |
|
| 56 |
#' |
|
| 57 |
.GeneralModel <- setClass( |
|
| 58 |
Class = "GeneralModel", |
|
| 59 |
slots = c( |
|
| 60 |
datamodel = "function", |
|
| 61 |
priormodel = "function", |
|
| 62 |
modelspecs = "function", |
|
| 63 |
init = "function", |
|
| 64 |
datanames = "character", |
|
| 65 |
datanames_prior = "character", |
|
| 66 |
sample = "character" |
|
| 67 |
), |
|
| 68 |
prototype = prototype( |
|
| 69 |
datamodel = I, |
|
| 70 |
priormodel = I, |
|
| 71 |
init = function() {
|
|
| 72 |
list() |
|
| 73 |
} |
|
| 74 |
), |
|
| 75 |
contains = "CrmPackClass", |
|
| 76 |
validity = v_general_model |
|
| 77 |
) |
|
| 78 | ||
| 79 |
## default constructor ---- |
|
| 80 | ||
| 81 |
#' @rdname GeneralModel-class |
|
| 82 |
#' @note Typically, end users will not use the `.DefaultGeneralModel()` function. |
|
| 83 |
#' @export |
|
| 84 |
.DefaultGeneralModel <- function() {
|
|
| 85 | 1x |
stop(paste0( |
| 86 | 1x |
"Class GeneralModel should not be instantiated directly. Please use one of its subclasses instead." |
| 87 |
)) |
|
| 88 |
} |
|
| 89 | ||
| 90 | ||
| 91 |
# ModelLogNormal ---- |
|
| 92 | ||
| 93 |
## class ---- |
|
| 94 | ||
| 95 |
#' `ModelLogNormal` |
|
| 96 |
#' |
|
| 97 |
#' @description `r lifecycle::badge("stable")`
|
|
| 98 |
#' |
|
| 99 |
#' [`ModelLogNormal`] is the class for a model with a reference dose and bivariate |
|
| 100 |
#' normal prior on the model parameters `alpha0` and natural logarithm of `alpha1`, |
|
| 101 |
#' i.e.: \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov),}. Transformations other
|
|
| 102 |
#' than `log`, e.g. identity, can be specified too in `priormodel` slot. |
|
| 103 |
#' The parameter `alpha1` has a log-normal distribution by default to ensure |
|
| 104 |
#' positivity of `alpha1` which further guarantees `exp(alpha1) > 1`. |
|
| 105 |
#' The slots of this class contain the mean vector, the covariance and |
|
| 106 |
#' precision matrices of the bivariate normal distribution, as well as the |
|
| 107 |
#' reference dose. Note that the precision matrix is an inverse of the |
|
| 108 |
#' covariance matrix in the `JAGS`. |
|
| 109 |
#' All ("normal") model specific classes inherit from this class.
|
|
| 110 |
#' |
|
| 111 |
#' @slot params (`ModelParamsNormal`)\cr bivariate normal prior parameters. |
|
| 112 |
#' @slot ref_dose (`positive_number`)\cr the reference dose. |
|
| 113 |
#' |
|
| 114 |
#' @seealso [`ModelParamsNormal`], [`LogisticNormal`], [`LogisticLogNormal`], |
|
| 115 |
#' [`LogisticLogNormalSub`], [`ProbitLogNormal`], [`ProbitLogNormalRel`]. |
|
| 116 |
#' |
|
| 117 |
#' @aliases ModelLogNormal |
|
| 118 |
#' @export |
|
| 119 |
#' |
|
| 120 |
.ModelLogNormal <- setClass( |
|
| 121 |
Class = "ModelLogNormal", |
|
| 122 |
contains = "GeneralModel", |
|
| 123 |
slots = c( |
|
| 124 |
params = "ModelParamsNormal", |
|
| 125 |
ref_dose = "positive_number" |
|
| 126 |
) |
|
| 127 |
) |
|
| 128 | ||
| 129 |
## constructor ---- |
|
| 130 | ||
| 131 |
#' @rdname ModelLogNormal-class |
|
| 132 |
#' |
|
| 133 |
#' @param mean (`numeric`)\cr the prior mean vector. |
|
| 134 |
#' @param cov (`matrix`)\cr the prior covariance matrix. The precision matrix |
|
| 135 |
#' `prec` is internally calculated as an inverse of `cov`. |
|
| 136 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*} (strictly positive
|
|
| 137 |
#' number). |
|
| 138 |
#' |
|
| 139 |
#' @export |
|
| 140 |
#' |
|
| 141 |
ModelLogNormal <- function(mean, cov, ref_dose = 1) {
|
|
| 142 | 352x |
params <- ModelParamsNormal(mean, cov) |
| 143 | 352x |
.ModelLogNormal( |
| 144 | 352x |
params = params, |
| 145 | 352x |
ref_dose = positive_number(ref_dose), |
| 146 | 352x |
priormodel = function() {
|
| 147 | ! |
theta ~ dmnorm(mean, prec) |
| 148 | ! |
alpha0 <- theta[1] |
| 149 | ! |
alpha1 <- exp(theta[2]) |
| 150 |
}, |
|
| 151 | 352x |
modelspecs = function(from_prior) {
|
| 152 | 271x |
ms <- list(mean = params@mean, prec = params@prec) |
| 153 | 271x |
if (!from_prior) {
|
| 154 | 260x |
ms$ref_dose <- ref_dose |
| 155 |
} |
|
| 156 | 271x |
ms |
| 157 |
}, |
|
| 158 | 352x |
init = function() {
|
| 159 | 310x |
list(theta = c(0, 1)) |
| 160 |
}, |
|
| 161 | 352x |
datanames = c("nObs", "y", "x"),
|
| 162 | 352x |
sample = c("alpha0", "alpha1")
|
| 163 |
) |
|
| 164 |
} |
|
| 165 | ||
| 166 |
## default constructor ---- |
|
| 167 | ||
| 168 |
#' @rdname ModelLogNormal-class |
|
| 169 |
#' @note Typically, end users will not use the `.DefaultModelLogNormal()` function. |
|
| 170 |
#' @export |
|
| 171 |
.DefaultModelLogNormal <- function() {
|
|
| 172 | 8x |
ModelLogNormal( |
| 173 | 8x |
mean = c(-0.85, 1), |
| 174 | 8x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
| 175 |
) |
|
| 176 |
} |
|
| 177 | ||
| 178 |
# LogisticNormal ---- |
|
| 179 | ||
| 180 |
## class ---- |
|
| 181 | ||
| 182 |
#' `LogisticNormal` |
|
| 183 |
#' |
|
| 184 |
#' @description `r lifecycle::badge("stable")`
|
|
| 185 |
#' |
|
| 186 |
#' [`LogisticNormal`] is the class for the usual logistic regression model with |
|
| 187 |
#' a bivariate normal prior on the intercept and slope. |
|
| 188 |
#' |
|
| 189 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided by
|
|
| 190 |
#' the reference dose \eqn{x*}, i.e.:
|
|
| 191 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),}
|
|
| 192 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 193 |
#' The prior \deqn{(alpha0, alpha1) ~ Normal(mean, cov).}
|
|
| 194 |
#' |
|
| 195 |
#' @seealso [`ModelLogNormal`], [`LogisticLogNormal`], [`LogisticLogNormalSub`], |
|
| 196 |
#' [`ProbitLogNormal`], [`ProbitLogNormalRel`], [`LogisticNormalMixture`]. |
|
| 197 |
#' |
|
| 198 |
#' @aliases LogisticNormal |
|
| 199 |
#' @export |
|
| 200 |
#' |
|
| 201 |
.LogisticNormal <- setClass( |
|
| 202 |
Class = "LogisticNormal", |
|
| 203 |
contains = "ModelLogNormal" |
|
| 204 |
) |
|
| 205 | ||
| 206 |
## constructor ---- |
|
| 207 | ||
| 208 |
#' @rdname LogisticNormal-class |
|
| 209 |
#' |
|
| 210 |
#' @inheritParams ModelLogNormal |
|
| 211 |
#' |
|
| 212 |
#' @export |
|
| 213 |
#' @example examples/Model-class-LogisticNormal.R |
|
| 214 |
#' |
|
| 215 |
LogisticNormal <- function(mean, cov, ref_dose = 1) {
|
|
| 216 | 31x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
| 217 | ||
| 218 | 31x |
.LogisticNormal( |
| 219 | 31x |
model_ln, |
| 220 | 31x |
datamodel = function() {
|
| 221 | ! |
for (i in 1:nObs) {
|
| 222 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 223 | ! |
y[i] ~ dbern(p[i]) |
| 224 |
} |
|
| 225 |
}, |
|
| 226 | 31x |
priormodel = function() {
|
| 227 | ! |
theta ~ dmnorm(mean, prec) |
| 228 | ! |
alpha0 <- theta[1] |
| 229 | ! |
alpha1 <- theta[2] |
| 230 |
} |
|
| 231 |
) |
|
| 232 |
} |
|
| 233 | ||
| 234 |
## default constructor ---- |
|
| 235 | ||
| 236 |
#' @rdname LogisticNormal-class |
|
| 237 |
#' @note Typically, end users will not use the `.DefaultLogisticNormal()` function. |
|
| 238 |
#' @export |
|
| 239 |
.DefaultLogisticNormal <- function() {
|
|
| 240 | 9x |
LogisticNormal( |
| 241 | 9x |
mean = c(-0.85, 1), |
| 242 | 9x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
| 243 |
) |
|
| 244 |
} |
|
| 245 | ||
| 246 | ||
| 247 |
# LogisticLogNormal ---- |
|
| 248 | ||
| 249 |
## class ---- |
|
| 250 | ||
| 251 |
#' `LogisticLogNormal` |
|
| 252 |
#' |
|
| 253 |
#' @description `r lifecycle::badge("stable")`
|
|
| 254 |
#' |
|
| 255 |
#' [`LogisticLogNormal`] is the class for the usual logistic regression model |
|
| 256 |
#' with a bivariate normal prior on the intercept and log slope. |
|
| 257 |
#' |
|
| 258 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided by
|
|
| 259 |
#' the reference dose \eqn{x*}, i.e.:
|
|
| 260 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),}
|
|
| 261 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 262 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).}
|
|
| 263 |
#' |
|
| 264 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormalSub`], |
|
| 265 |
#' [`ProbitLogNormal`], [`ProbitLogNormalRel`], [`LogisticLogNormalMixture`], |
|
| 266 |
#' [`DALogisticLogNormal`]. |
|
| 267 |
#' |
|
| 268 |
#' @aliases LogisticLogNormal |
|
| 269 |
#' @export |
|
| 270 |
#' |
|
| 271 |
.LogisticLogNormal <- setClass( |
|
| 272 |
Class = "LogisticLogNormal", |
|
| 273 |
contains = "ModelLogNormal" |
|
| 274 |
) |
|
| 275 | ||
| 276 |
## constructor ---- |
|
| 277 | ||
| 278 |
#' @rdname LogisticLogNormal-class |
|
| 279 |
#' |
|
| 280 |
#' @inheritParams ModelLogNormal |
|
| 281 |
#' |
|
| 282 |
#' @export |
|
| 283 |
#' @example examples/Model-class-LogisticLogNormal.R |
|
| 284 |
#' |
|
| 285 |
LogisticLogNormal <- function(mean, cov, ref_dose = 1) {
|
|
| 286 | 247x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
| 287 | ||
| 288 | 247x |
.LogisticLogNormal( |
| 289 | 247x |
model_ln, |
| 290 | 247x |
datamodel = function() {
|
| 291 | ! |
for (i in 1:nObs) {
|
| 292 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 293 | ! |
y[i] ~ dbern(p[i]) |
| 294 |
} |
|
| 295 |
} |
|
| 296 |
) |
|
| 297 |
} |
|
| 298 | ||
| 299 |
## default constructor ---- |
|
| 300 | ||
| 301 |
#' @rdname LogisticLogNormal-class |
|
| 302 |
#' @note Typically, end users will not use the `.DefaultLogisticLogNormal()` function. |
|
| 303 |
#' @export |
|
| 304 |
.DefaultLogisticLogNormal <- function() {
|
|
| 305 | 17x |
LogisticLogNormal( |
| 306 | 17x |
mean = c(-0.85, 1), |
| 307 | 17x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 308 | 17x |
ref_dose = 50 |
| 309 |
) |
|
| 310 |
} |
|
| 311 | ||
| 312 |
# LogisticLogNormalSub ---- |
|
| 313 | ||
| 314 |
## class ---- |
|
| 315 | ||
| 316 |
#' `LogisticLogNormalSub` |
|
| 317 |
#' |
|
| 318 |
#' @description `r lifecycle::badge("stable")`
|
|
| 319 |
#' |
|
| 320 |
#' [`LogisticLogNormalSub`] is the class for a standard logistic model with |
|
| 321 |
#' bivariate (log) normal prior with subtractive dose standardization. |
|
| 322 |
#' |
|
| 323 |
#' @details The covariate is the dose \eqn{x} minus the reference dose \eqn{x*},
|
|
| 324 |
#' i.e.: |
|
| 325 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * (x - x*),}
|
|
| 326 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 327 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).}
|
|
| 328 |
#' |
|
| 329 |
#' @slot params (`ModelParamsNormal`)\cr bivariate normal prior parameters. |
|
| 330 |
#' @slot ref_dose (`number`)\cr the reference dose \eqn{x*}.
|
|
| 331 |
#' |
|
| 332 |
#' @seealso [`LogisticNormal`], [`LogisticLogNormal`], [`ProbitLogNormal`], |
|
| 333 |
#' [`ProbitLogNormalRel`]. |
|
| 334 |
#' |
|
| 335 |
#' @aliases LogisticLogNormalSub |
|
| 336 |
#' @export |
|
| 337 |
#' |
|
| 338 |
.LogisticLogNormalSub <- setClass( |
|
| 339 |
Class = "LogisticLogNormalSub", |
|
| 340 |
slots = c( |
|
| 341 |
params = "ModelParamsNormal", |
|
| 342 |
ref_dose = "numeric" |
|
| 343 |
), |
|
| 344 |
contains = "GeneralModel" |
|
| 345 |
) |
|
| 346 | ||
| 347 |
## constructor ---- |
|
| 348 | ||
| 349 |
#' @rdname LogisticLogNormalSub-class |
|
| 350 |
#' |
|
| 351 |
#' @param mean (`numeric`)\cr the prior mean vector. |
|
| 352 |
#' @param cov (`matrix`)\cr the prior covariance matrix. The precision matrix |
|
| 353 |
#' `prec` is internally calculated as an inverse of `cov`. |
|
| 354 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*}.
|
|
| 355 |
#' |
|
| 356 |
#' @export |
|
| 357 |
#' @example examples/Model-class-LogisticLogNormalSub.R |
|
| 358 |
#' |
|
| 359 |
LogisticLogNormalSub <- function(mean, cov, ref_dose = 0) {
|
|
| 360 | 21x |
params <- ModelParamsNormal(mean, cov) |
| 361 | 21x |
.LogisticLogNormalSub( |
| 362 | 21x |
params = params, |
| 363 | 21x |
ref_dose = ref_dose, |
| 364 | 21x |
datamodel = function() {
|
| 365 | ! |
for (i in 1:nObs) {
|
| 366 | ! |
logit(p[i]) <- alpha0 + alpha1 * (x[i] - ref_dose) |
| 367 | ! |
y[i] ~ dbern(p[i]) |
| 368 |
} |
|
| 369 |
}, |
|
| 370 | 21x |
priormodel = function() {
|
| 371 | ! |
theta ~ dmnorm(mean, prec) |
| 372 | ! |
alpha0 <- theta[1] |
| 373 | ! |
alpha1 <- exp(theta[2]) |
| 374 |
}, |
|
| 375 | 21x |
modelspecs = function(from_prior) {
|
| 376 | 2x |
ms <- list(mean = params@mean, prec = params@prec) |
| 377 | 2x |
if (!from_prior) {
|
| 378 | 1x |
ms$ref_dose <- ref_dose |
| 379 |
} |
|
| 380 | 2x |
ms |
| 381 |
}, |
|
| 382 | 21x |
init = function() {
|
| 383 | 2x |
list(theta = c(0, -20)) |
| 384 |
}, |
|
| 385 | 21x |
datanames = c("nObs", "y", "x"),
|
| 386 | 21x |
sample = c("alpha0", "alpha1")
|
| 387 |
) |
|
| 388 |
} |
|
| 389 | ||
| 390 | ||
| 391 |
## default constructor ---- |
|
| 392 | ||
| 393 |
#' @rdname LogisticLogNormalSub-class |
|
| 394 |
#' @note Typically, end-users will not use the `.DefaultLogisticLogNormalSub()` function. |
|
| 395 |
#' @export |
|
| 396 |
.DefaultLogisticLogNormalSub <- function() {
|
|
| 397 | 9x |
LogisticLogNormalSub( |
| 398 | 9x |
mean = c(-0.85, 1), |
| 399 | 9x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 400 | 9x |
ref_dose = 50 |
| 401 |
) |
|
| 402 |
} |
|
| 403 | ||
| 404 |
# ProbitLogNormal ---- |
|
| 405 | ||
| 406 |
## class ---- |
|
| 407 | ||
| 408 |
#' `ProbitLogNormal` |
|
| 409 |
#' |
|
| 410 |
#' @description `r lifecycle::badge("stable")`
|
|
| 411 |
#' |
|
| 412 |
#' [`ProbitLogNormal`] is the class for probit regression model with a |
|
| 413 |
#' bivariate normal prior on the intercept and log slope. |
|
| 414 |
#' |
|
| 415 |
#' @details The covariate is the natural logarithm of dose \eqn{x} divided by a
|
|
| 416 |
#' reference dose \eqn{x*}, i.e.:
|
|
| 417 |
#' \deqn{probit[p(x)] = alpha0 + alpha1 * log(x/x*),}
|
|
| 418 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 419 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).}
|
|
| 420 |
#' |
|
| 421 |
#' @note The model used in the [`DualEndpoint`] classes is an extension of this model: |
|
| 422 |
#' `DualEndpoint` supports both `ProbitNormal` (which is not implemented yet) and |
|
| 423 |
#' `ProbitLogNormal` models through its `use_log_dose` slot. |
|
| 424 |
#' `ProbitLogNormal` has no such flag, so always uses `log(x/x*)`as a covariate in |
|
| 425 |
#' its model. Therefore this class can be used to check the prior assumptions on the |
|
| 426 |
#' dose-toxicity model, even when sampling from the prior distribution of the dual |
|
| 427 |
#' endpoint model is not possible, when `use_log_dose = TRUE` is used. |
|
| 428 |
#' |
|
| 429 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`], |
|
| 430 |
#' [`LogisticLogNormalSub`], [`ProbitLogNormalRel`]. |
|
| 431 |
#' |
|
| 432 |
#' @aliases ProbitLogNormalLogDose |
|
| 433 |
#' @export |
|
| 434 |
#' |
|
| 435 |
.ProbitLogNormal <- setClass( |
|
| 436 |
Class = "ProbitLogNormal", |
|
| 437 |
contains = "ModelLogNormal" |
|
| 438 |
) |
|
| 439 | ||
| 440 |
## constructor ---- |
|
| 441 | ||
| 442 |
#' @rdname ProbitLogNormal-class |
|
| 443 |
#' |
|
| 444 |
#' @inheritParams ModelLogNormal |
|
| 445 |
#' |
|
| 446 |
#' @export |
|
| 447 |
#' @example examples/Model-class-ProbitLogNormal.R |
|
| 448 |
#' |
|
| 449 |
ProbitLogNormal <- function(mean, cov, ref_dose = 1) {
|
|
| 450 | 43x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
| 451 | ||
| 452 | 43x |
.ProbitLogNormal( |
| 453 | 43x |
model_ln, |
| 454 | 43x |
datamodel = function() {
|
| 455 | ! |
for (i in 1:nObs) {
|
| 456 | ! |
probit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 457 | ! |
y[i] ~ dbern(p[i]) |
| 458 |
} |
|
| 459 |
} |
|
| 460 |
) |
|
| 461 |
} |
|
| 462 | ||
| 463 |
## default constructor ---- |
|
| 464 | ||
| 465 |
#' @rdname ProbitLogNormal-class |
|
| 466 |
#' @note Typically, end users will not use the `.DefaultProbitLogNormal()` function. |
|
| 467 |
#' @export |
|
| 468 |
.DefaultProbitLogNormal <- function() {
|
|
| 469 | 9x |
ProbitLogNormal( |
| 470 | 9x |
mean = c(-0.85, 1), |
| 471 | 9x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 472 | 9x |
ref_dose = 7.2 |
| 473 |
) |
|
| 474 |
} |
|
| 475 | ||
| 476 |
# ProbitLogNormalRel ---- |
|
| 477 | ||
| 478 |
## class ---- |
|
| 479 | ||
| 480 |
#' `ProbitLogNormalRel` |
|
| 481 |
#' |
|
| 482 |
#' @description `r lifecycle::badge("stable")`
|
|
| 483 |
#' |
|
| 484 |
#' [`ProbitLogNormalRel`] is the class for probit regression model with a bivariate |
|
| 485 |
#' normal prior on the intercept and log slope. |
|
| 486 |
#' |
|
| 487 |
#' @details The covariate is the dose \eqn{x} divided by a reference dose \eqn{x*},
|
|
| 488 |
#' i.e.: |
|
| 489 |
#' \deqn{probit[p(x)] = alpha0 + alpha1 * x/x*,}
|
|
| 490 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 491 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).}
|
|
| 492 |
#' |
|
| 493 |
#' @note This model is also used in the [`DualEndpoint`] classes, so this class |
|
| 494 |
#' can be used to check the prior assumptions on the dose-toxicity model, even |
|
| 495 |
#' when sampling from the prior distribution of the dual endpoint model is not |
|
| 496 |
#' possible. |
|
| 497 |
#' |
|
| 498 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`], |
|
| 499 |
#' [`LogisticLogNormalSub`], [`ProbitLogNormal`]. |
|
| 500 |
#' |
|
| 501 |
#' @aliases ProbitLogNormalRel |
|
| 502 |
#' @export |
|
| 503 |
#' |
|
| 504 |
.ProbitLogNormalRel <- setClass( |
|
| 505 |
Class = "ProbitLogNormalRel", |
|
| 506 |
contains = "ModelLogNormal" |
|
| 507 |
) |
|
| 508 | ||
| 509 |
## constructor ---- |
|
| 510 | ||
| 511 |
#' @rdname ProbitLogNormalRel-class |
|
| 512 |
#' |
|
| 513 |
#' @inheritParams ModelLogNormal |
|
| 514 |
#' |
|
| 515 |
#' @export |
|
| 516 |
#' @example examples/Model-class-ProbitLogNormalRel.R |
|
| 517 |
#' |
|
| 518 |
ProbitLogNormalRel <- function(mean, cov, ref_dose = 1) {
|
|
| 519 | 21x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
| 520 | ||
| 521 | 21x |
.ProbitLogNormalRel( |
| 522 | 21x |
model_ln, |
| 523 | 21x |
datamodel = function() {
|
| 524 | ! |
for (i in 1:nObs) {
|
| 525 | ! |
probit(p[i]) <- alpha0 + alpha1 * (x[i] / ref_dose) |
| 526 | ! |
y[i] ~ dbern(p[i]) |
| 527 |
} |
|
| 528 |
} |
|
| 529 |
) |
|
| 530 |
} |
|
| 531 | ||
| 532 |
## default constructor ---- |
|
| 533 | ||
| 534 |
#' @rdname ProbitLogNormalRel-class |
|
| 535 |
#' @note Typically, end users will not use the `.DefaultProbitLogNormalRel()` function. |
|
| 536 |
#' @export |
|
| 537 |
.DefaultProbitLogNormalRel <- function() {
|
|
| 538 | 9x |
ProbitLogNormalRel( |
| 539 | 9x |
mean = c(-0.85, 1), |
| 540 | 9x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
| 541 |
) |
|
| 542 |
} |
|
| 543 | ||
| 544 |
# LogisticLogNormalGrouped ---- |
|
| 545 | ||
| 546 |
## class ---- |
|
| 547 | ||
| 548 |
#' `LogisticLogNormalGrouped` |
|
| 549 |
#' |
|
| 550 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 551 |
#' |
|
| 552 |
#' [`LogisticLogNormalGrouped`] is the class for a logistic regression model |
|
| 553 |
#' for both the mono and the combo arms of the simultaneous dose escalation |
|
| 554 |
#' design. |
|
| 555 |
#' |
|
| 556 |
#' @details The continuous covariate is the natural logarithm of the dose \eqn{x} divided by
|
|
| 557 |
#' the reference dose \eqn{x*} as in [`LogisticLogNormal`]. In addition,
|
|
| 558 |
#' \eqn{I_c} is a binary indicator covariate which is 1 for the combo arm and 0 for the mono arm.
|
|
| 559 |
#' The model is then defined as: |
|
| 560 |
#' \deqn{logit[p(x)] = (alpha0 + I_c * delta0) + (alpha1 + I_c * delta1) * log(x / x*),}
|
|
| 561 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x},
|
|
| 562 |
#' and `delta0` and `delta1` are the differences in the combo arm compared to the mono intercept |
|
| 563 |
#' and slope parameters `alpha0` and `alpha1`. |
|
| 564 |
#' The prior is defined as \deqn{(alpha0, log(delta0), log(alpha1), log(delta1)) ~ Normal(mean, cov).}
|
|
| 565 |
#' |
|
| 566 |
#' @seealso [`ModelLogNormal`], [`LogisticLogNormal`]. |
|
| 567 |
#' |
|
| 568 |
#' @aliases LogisticLogNormalGrouped |
|
| 569 |
#' @export |
|
| 570 |
#' |
|
| 571 |
.LogisticLogNormalGrouped <- setClass( |
|
| 572 |
Class = "LogisticLogNormalGrouped", |
|
| 573 |
contains = "ModelLogNormal" |
|
| 574 |
) |
|
| 575 | ||
| 576 |
## constructor ---- |
|
| 577 | ||
| 578 |
#' @rdname LogisticLogNormalGrouped-class |
|
| 579 |
#' |
|
| 580 |
#' @inheritParams ModelLogNormal |
|
| 581 |
#' |
|
| 582 |
#' @export |
|
| 583 |
#' @example examples/Model-class-LogisticLogNormalGrouped.R |
|
| 584 |
#' |
|
| 585 |
LogisticLogNormalGrouped <- function(mean, cov, ref_dose = 1) {
|
|
| 586 | 34x |
params <- ModelParamsNormal(mean, cov) |
| 587 | 34x |
.LogisticLogNormalGrouped( |
| 588 | 34x |
params = params, |
| 589 | 34x |
ref_dose = positive_number(ref_dose), |
| 590 | 34x |
priormodel = function() {
|
| 591 | ! |
theta ~ dmnorm(mean, prec) |
| 592 | ! |
alpha0 <- theta[1] |
| 593 | ! |
delta0 <- exp(theta[2]) |
| 594 | ! |
alpha1 <- exp(theta[3]) |
| 595 | ! |
delta1 <- exp(theta[4]) |
| 596 |
}, |
|
| 597 | 34x |
datamodel = function() {
|
| 598 | ! |
for (i in 1:nObs) {
|
| 599 | ! |
logit(p[i]) <- (alpha0 + is_combo[i] * delta0) + |
| 600 | ! |
(alpha1 + is_combo[i] * delta1) * log(x[i] / ref_dose) |
| 601 | ! |
y[i] ~ dbern(p[i]) |
| 602 |
} |
|
| 603 |
}, |
|
| 604 | 34x |
modelspecs = function(group, from_prior) {
|
| 605 | 75x |
ms <- list( |
| 606 | 75x |
mean = params@mean, |
| 607 | 75x |
prec = params@prec |
| 608 |
) |
|
| 609 | 75x |
if (!from_prior) {
|
| 610 | 74x |
ms$ref_dose <- ref_dose |
| 611 | 74x |
ms$is_combo <- as.integer(group == "combo") |
| 612 |
} |
|
| 613 | 75x |
ms |
| 614 |
}, |
|
| 615 | 34x |
init = function() {
|
| 616 | 75x |
list(theta = c(0, 1, 1, 1)) |
| 617 |
}, |
|
| 618 | 34x |
datanames = c("nObs", "y", "x"),
|
| 619 | 34x |
sample = c("alpha0", "delta0", "alpha1", "delta1")
|
| 620 |
) |
|
| 621 |
} |
|
| 622 | ||
| 623 |
## default constructor ---- |
|
| 624 | ||
| 625 |
#' @rdname LogisticLogNormalGrouped-class |
|
| 626 |
#' @note Typically, end users will not use the `.DefaultLogisticLogNormalGrouped()` function. |
|
| 627 |
#' @export |
|
| 628 |
.DefaultLogisticLogNormalGrouped <- function() {
|
|
| 629 | 24x |
LogisticLogNormalGrouped( |
| 630 | 24x |
mean = rep(0, 4), |
| 631 | 24x |
cov = diag(rep(1, 4)), |
| 632 |
) |
|
| 633 |
} |
|
| 634 | ||
| 635 |
# LogisticKadane ---- |
|
| 636 | ||
| 637 |
## class ---- |
|
| 638 | ||
| 639 |
#' `LogisticKadane` |
|
| 640 |
#' |
|
| 641 |
#' @description `r lifecycle::badge("stable")`
|
|
| 642 |
#' |
|
| 643 |
#' [`LogisticKadane`] is the class for the logistic model in the parametrization |
|
| 644 |
#' of \insertCite{KadaneDickeyWinklerSmithPeters1980;textual}{crmPack}.
|
|
| 645 |
#' |
|
| 646 |
#' @details Let `rho0 = p(xmin)` be the probability of a DLT at the minimum dose |
|
| 647 |
#' `xmin`, and let `gamma` be the dose with target toxicity probability `theta`, |
|
| 648 |
#' i.e. \eqn{p(gamma) = theta}. Then it can easily be shown that the logistic
|
|
| 649 |
#' regression model has intercept |
|
| 650 |
#' \deqn{[gamma * logit(rho0) - xmin * logit(theta)] / [gamma - xmin]}
|
|
| 651 |
#' and slope |
|
| 652 |
#' \deqn{[logit(theta) - logit(rho0)] / [gamma - xmin].}
|
|
| 653 |
#' |
|
| 654 |
#' The priors are \deqn{gamma ~ Unif(xmin, xmax).} and
|
|
| 655 |
#' \deqn{rho0 ~ Unif(0, theta).}
|
|
| 656 |
#' |
|
| 657 |
#' @note The slots of this class, required for creating the model, are the target |
|
| 658 |
#' toxicity, as well as the minimum and maximum of the dose range. Note that |
|
| 659 |
#' these can be different from the minimum and maximum of the dose grid in the |
|
| 660 |
#' data later on. |
|
| 661 |
#' |
|
| 662 |
#' @slot theta (`proportion`)\cr the target toxicity probability. |
|
| 663 |
#' @slot xmin (`number`)\cr the minimum of the dose range. |
|
| 664 |
#' @slot xmax (`number`)\cr the maximum of the dose range. |
|
| 665 |
#' |
|
| 666 |
#' @seealso [`ModelLogNormal`] |
|
| 667 |
#' |
|
| 668 |
#' @aliases LogisticKadane |
|
| 669 |
#' @export |
|
| 670 |
#' @references |
|
| 671 |
#' \insertAllCited{}
|
|
| 672 |
#' |
|
| 673 |
.LogisticKadane <- setClass( |
|
| 674 |
Class = "LogisticKadane", |
|
| 675 |
contains = "GeneralModel", |
|
| 676 |
slots = c( |
|
| 677 |
theta = "numeric", |
|
| 678 |
xmin = "numeric", |
|
| 679 |
xmax = "numeric" |
|
| 680 |
), |
|
| 681 |
prototype = prototype( |
|
| 682 |
theta = 0.3, |
|
| 683 |
xmin = 0.1, |
|
| 684 |
xmax = 1 |
|
| 685 |
), |
|
| 686 |
validity = v_model_logistic_kadane |
|
| 687 |
) |
|
| 688 | ||
| 689 |
## constructor ---- |
|
| 690 | ||
| 691 |
#' @rdname LogisticKadane-class |
|
| 692 |
#' |
|
| 693 |
#' @param theta (`proportion`)\cr the target toxicity probability. |
|
| 694 |
#' @param xmin (`number`)\cr the minimum of the dose range. |
|
| 695 |
#' @param xmax (`number`)\cr the maximum of the dose range. |
|
| 696 |
#' |
|
| 697 |
#' @export |
|
| 698 |
#' @example examples/Model-class-LogisticKadane.R |
|
| 699 |
#' |
|
| 700 |
LogisticKadane <- function(theta, xmin, xmax) {
|
|
| 701 | 77x |
.LogisticKadane( |
| 702 | 77x |
theta = theta, |
| 703 | 77x |
xmin = xmin, |
| 704 | 77x |
xmax = xmax, |
| 705 | 77x |
datamodel = function() {
|
| 706 | ! |
for (i in 1:nObs) {
|
| 707 | ! |
logit(p[i]) <- (1 / (gamma - xmin)) * |
| 708 | ! |
(gamma * |
| 709 | ! |
logit(rho0) - |
| 710 | ! |
xmin * logit(theta) + |
| 711 | ! |
x[i] * (logit(theta) - logit(rho0))) |
| 712 | ! |
y[i] ~ dbern(p[i]) |
| 713 |
} |
|
| 714 |
}, |
|
| 715 | 77x |
priormodel = function() {
|
| 716 | ! |
rho0 ~ dunif(0, theta) |
| 717 | ! |
gamma ~ dunif(xmin, xmax) |
| 718 |
}, |
|
| 719 | 77x |
modelspecs = function() {
|
| 720 | 28x |
list(theta = theta, xmin = xmin, xmax = xmax) |
| 721 |
}, |
|
| 722 | 77x |
init = function() {
|
| 723 | 30x |
list(rho0 = theta / 10, gamma = (xmax - xmin) / 2) |
| 724 |
}, |
|
| 725 | 77x |
datanames = c("nObs", "y", "x"),
|
| 726 | 77x |
sample = c("rho0", "gamma")
|
| 727 |
) |
|
| 728 |
} |
|
| 729 | ||
| 730 |
## default constructor ---- |
|
| 731 | ||
| 732 |
#' @rdname LogisticKadane-class |
|
| 733 |
#' @note Typically, end-users will not use the `.DefaultLogisticKadane()` function. |
|
| 734 |
#' @export |
|
| 735 |
.DefaultLogisticKadane <- function() {
|
|
| 736 | 9x |
LogisticKadane(theta = 0.33, xmin = 1, xmax = 200) |
| 737 |
} |
|
| 738 | ||
| 739 | ||
| 740 |
# LogisticKadaneBetaGamma ---- |
|
| 741 | ||
| 742 |
## class ---- |
|
| 743 | ||
| 744 |
#' `LogisticKadaneBetaGamma` |
|
| 745 |
#' |
|
| 746 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 747 |
#' |
|
| 748 |
#' [`LogisticKadaneBetaGamma`] is the class for the logistic model in the parametrization |
|
| 749 |
#' of \insertCite{KadaneDickeyWinklerSmithPeters1980;textual}{crmPack},
|
|
| 750 |
#' using a beta and a gamma distribution as the model priors. |
|
| 751 |
#' |
|
| 752 |
#' @details Let `rho0 = p(xmin)` be the probability of a DLT at the minimum dose |
|
| 753 |
#' `xmin`, and let `gamma` be the dose with target toxicity probability `theta`, |
|
| 754 |
#' i.e. \eqn{p(gamma) = theta}. Then it can easily be shown that the logistic
|
|
| 755 |
#' regression model has intercept |
|
| 756 |
#' \deqn{[gamma * logit(rho0) - xmin * logit(theta)] / [gamma - xmin]}
|
|
| 757 |
#' and slope |
|
| 758 |
#' \deqn{[logit(theta) - logit(rho0)] / [gamma - xmin].}
|
|
| 759 |
#' |
|
| 760 |
#' The prior for `gamma`, is \deqn{gamma ~ Gamma(shape, rate).}.
|
|
| 761 |
#' The prior for `rho0 = p(xmin)`, is \deqn{rho0 ~ Beta(alpha, beta).}
|
|
| 762 |
#' |
|
| 763 |
#' @note The slots of this class, required for creating the model, are the same |
|
| 764 |
#' as in the `LogisticKadane` class. In addition, the shape parameters of the |
|
| 765 |
#' Beta prior distribution of `rho0` and the shape and rate parameters of the |
|
| 766 |
#' Gamma prior distribution of `gamma`, are required for creating the prior model. |
|
| 767 |
#' |
|
| 768 |
#' @slot theta (`proportion`)\cr the target toxicity probability. |
|
| 769 |
#' @slot xmin (`number`)\cr the minimum of the dose range. |
|
| 770 |
#' @slot xmax (`number`)\cr the maximum of the dose range. |
|
| 771 |
#' @slot alpha (`number`)\cr the first shape parameter of the Beta prior distribution |
|
| 772 |
#' of `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
| 773 |
#' @slot beta (`number`)\cr the second shape parameter of the Beta prior distribution |
|
| 774 |
#' of `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
| 775 |
#' @slot shape (`number`)\cr the shape parameter of the Gamma prior distribution |
|
| 776 |
#' of `gamma` the dose with target toxicity probability `theta`. |
|
| 777 |
#' @slot rate (`number`)\cr the rate parameter of the Gamma prior distribution |
|
| 778 |
#' of `gamma` the dose with target toxicity probability `theta`. |
|
| 779 |
#' |
|
| 780 |
#' @seealso [`ModelLogNormal`], [`LogisticKadane`]. |
|
| 781 |
#' |
|
| 782 |
#' @aliases LogisticKadaneBetaGamma |
|
| 783 |
#' @export |
|
| 784 |
#' @references |
|
| 785 |
#' \insertAllCited{}
|
|
| 786 |
#' |
|
| 787 |
.LogisticKadaneBetaGamma <- setClass( |
|
| 788 |
Class = "LogisticKadaneBetaGamma", |
|
| 789 |
contains = "LogisticKadane", |
|
| 790 |
slots = c( |
|
| 791 |
alpha = "numeric", |
|
| 792 |
beta = "numeric", |
|
| 793 |
shape = "numeric", |
|
| 794 |
rate = "numeric" |
|
| 795 |
), |
|
| 796 |
prototype = prototype( |
|
| 797 |
theta = 0.3, |
|
| 798 |
xmin = 0.1, |
|
| 799 |
xmax = 1, |
|
| 800 |
alpha = 1, |
|
| 801 |
beta = 0.5, |
|
| 802 |
shape = 1.2, |
|
| 803 |
rate = 2.5 |
|
| 804 |
), |
|
| 805 |
validity = v_model_logistic_kadane_beta_gamma |
|
| 806 |
) |
|
| 807 | ||
| 808 |
## constructor ---- |
|
| 809 | ||
| 810 |
#' @rdname LogisticKadaneBetaGamma-class |
|
| 811 |
#' |
|
| 812 |
#' @inheritParams LogisticKadane |
|
| 813 |
#' |
|
| 814 |
#' @param alpha (`number`)\cr the first shape parameter of the Beta prior distribution |
|
| 815 |
#' `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
| 816 |
#' @param beta (`number`)\cr the second shape parameter of the Beta prior distribution |
|
| 817 |
#' `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
| 818 |
#' @param shape (`number`)\cr the shape parameter of the Gamma prior distribution |
|
| 819 |
#' `gamma` the dose with target toxicity probability `theta`. |
|
| 820 |
#' @param rate (`number`)\cr the rate parameter of the Gamma prior distribution |
|
| 821 |
#' `gamma` the dose with target toxicity probability `theta`. |
|
| 822 |
#' |
|
| 823 |
#' @export |
|
| 824 |
#' @example examples/Model-class-LogisticKadaneBetaGamma.R |
|
| 825 |
#' |
|
| 826 |
LogisticKadaneBetaGamma <- function( |
|
| 827 |
theta, |
|
| 828 |
xmin, |
|
| 829 |
xmax, |
|
| 830 |
alpha, |
|
| 831 |
beta, |
|
| 832 |
shape, |
|
| 833 |
rate |
|
| 834 |
) {
|
|
| 835 | 26x |
model_lk <- LogisticKadane(theta = theta, xmin = xmin, xmax = xmax) |
| 836 | 26x |
.LogisticKadaneBetaGamma( |
| 837 | 26x |
model_lk, |
| 838 | 26x |
alpha = alpha, |
| 839 | 26x |
beta = beta, |
| 840 | 26x |
shape = shape, |
| 841 | 26x |
rate = rate, |
| 842 | 26x |
priormodel = function() {
|
| 843 | ! |
rho0 ~ dbeta(alpha, beta) |
| 844 | ! |
gamma ~ dgamma(shape, rate) |
| 845 | ! |
lowestdose <- xmin |
| 846 | ! |
highestdose <- xmax |
| 847 | ! |
DLTtarget <- theta |
| 848 |
}, |
|
| 849 | 26x |
modelspecs = function() {
|
| 850 | 2x |
list( |
| 851 | 2x |
theta = theta, |
| 852 | 2x |
xmin = xmin, |
| 853 | 2x |
xmax = xmax, |
| 854 | 2x |
alpha = alpha, |
| 855 | 2x |
beta = beta, |
| 856 | 2x |
shape = shape, |
| 857 | 2x |
rate = rate |
| 858 |
) |
|
| 859 |
} |
|
| 860 |
) |
|
| 861 |
} |
|
| 862 | ||
| 863 |
## default constructor ---- |
|
| 864 | ||
| 865 |
#' @rdname LogisticKadaneBetaGamma-class |
|
| 866 |
#' @note Typically, end users will not use the `.Default()` function. |
|
| 867 |
#' @export |
|
| 868 |
.DefaultLogisticKadaneBetaGamma <- function() {
|
|
| 869 | 9x |
LogisticKadaneBetaGamma( |
| 870 | 9x |
theta = 0.3, |
| 871 | 9x |
xmin = 0, |
| 872 | 9x |
xmax = 7, |
| 873 | 9x |
alpha = 1, |
| 874 | 9x |
beta = 19, |
| 875 | 9x |
shape = 0.5625, |
| 876 | 9x |
rate = 0.125 |
| 877 |
) |
|
| 878 |
} |
|
| 879 | ||
| 880 |
# LogisticNormalMixture ---- |
|
| 881 | ||
| 882 |
## class ---- |
|
| 883 | ||
| 884 |
#' `LogisticNormalMixture` |
|
| 885 |
#' |
|
| 886 |
#' @description `r lifecycle::badge("stable")`
|
|
| 887 |
#' |
|
| 888 |
#' [`LogisticNormalMixture`] is the class for standard logistic regression model |
|
| 889 |
#' with a mixture of two bivariate normal priors on the intercept and slope parameters. |
|
| 890 |
#' |
|
| 891 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided by
|
|
| 892 |
#' the reference dose \eqn{x*}, i.e.:
|
|
| 893 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),}
|
|
| 894 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 895 |
#' The prior |
|
| 896 |
#' \deqn{(alpha0, alpha1) ~ w * Normal(mean1, cov1) + (1 - w) * Normal(mean2, cov2).}
|
|
| 897 |
#' The weight w for the first component is assigned a beta prior `B(a, b)`. |
|
| 898 |
#' |
|
| 899 |
#' @note The weight of the two normal priors is a model parameter, hence it is a |
|
| 900 |
#' flexible mixture. This type of prior is often used with a mixture of a minimal |
|
| 901 |
#' informative and an informative component, in order to make the CRM more robust |
|
| 902 |
#' to data deviations from the informative component. |
|
| 903 |
#' |
|
| 904 |
#' @slot comp1 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
| 905 |
#' the first component. |
|
| 906 |
#' @slot comp2 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
| 907 |
#' the second component. |
|
| 908 |
#' @slot weightpar (`numeric`)\cr the beta parameters for the weight of the |
|
| 909 |
#' first component. It must a be a named vector of length 2 with names `a` and |
|
| 910 |
#' `b` and with strictly positive values. |
|
| 911 |
#' @slot ref_dose (`positive_number`)\cr the reference dose. |
|
| 912 |
#' |
|
| 913 |
#' @seealso [`ModelParamsNormal`], [`ModelLogNormal`], |
|
| 914 |
#' [`LogisticNormalFixedMixture`], [`LogisticLogNormalMixture`]. |
|
| 915 |
#' |
|
| 916 |
#' @aliases LogisticNormalMixture |
|
| 917 |
#' @export |
|
| 918 |
#' |
|
| 919 |
.LogisticNormalMixture <- setClass( |
|
| 920 |
Class = "LogisticNormalMixture", |
|
| 921 |
contains = "GeneralModel", |
|
| 922 |
slots = c( |
|
| 923 |
comp1 = "ModelParamsNormal", |
|
| 924 |
comp2 = "ModelParamsNormal", |
|
| 925 |
weightpar = "numeric", |
|
| 926 |
ref_dose = "numeric" |
|
| 927 |
), |
|
| 928 |
prototype = prototype( |
|
| 929 |
comp1 = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
| 930 |
comp2 = ModelParamsNormal(mean = c(-1, 1), cov = diag(2)), |
|
| 931 |
weightpar = c(a = 1, b = 1), |
|
| 932 |
ref_dose = 1 |
|
| 933 |
), |
|
| 934 |
validity = v_model_logistic_normal_mix |
|
| 935 |
) |
|
| 936 | ||
| 937 |
## constructor ---- |
|
| 938 | ||
| 939 |
#' @rdname LogisticNormalMixture-class |
|
| 940 |
#' |
|
| 941 |
#' @param comp1 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
| 942 |
#' the first component. See [`ModelParamsNormal`] for more details. |
|
| 943 |
#' @param comp2 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
| 944 |
#' the second component. See [`ModelParamsNormal`] for more details. |
|
| 945 |
#' @param weightpar (`numeric`)\cr the beta parameters for the weight of the |
|
| 946 |
#' first component. It must a be a named vector of length 2 with names `a` and |
|
| 947 |
#' `b` and with strictly positive values. |
|
| 948 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*}
|
|
| 949 |
#' (strictly positive number). |
|
| 950 |
#' |
|
| 951 |
#' @export |
|
| 952 |
#' @example examples/Model-class-LogisticNormalMixture.R |
|
| 953 |
#' |
|
| 954 |
LogisticNormalMixture <- function(comp1, comp2, weightpar, ref_dose) {
|
|
| 955 | 23x |
assert_number(ref_dose) |
| 956 | ||
| 957 | 23x |
.LogisticNormalMixture( |
| 958 | 23x |
comp1 = comp1, |
| 959 | 23x |
comp2 = comp2, |
| 960 | 23x |
weightpar = weightpar, |
| 961 | 23x |
ref_dose = ref_dose, |
| 962 | 23x |
datamodel = function() {
|
| 963 |
# The logistic likelihood - the same as for non-mixture case. |
|
| 964 | ! |
for (i in 1:nObs) {
|
| 965 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 966 | ! |
y[i] ~ dbern(p[i]) |
| 967 |
} |
|
| 968 |
}, |
|
| 969 | 23x |
priormodel = function() {
|
| 970 | ! |
w ~ dbeta(weightpar[1], weightpar[2]) |
| 971 | ! |
wc <- 1 - w |
| 972 | ! |
comp0 ~ dbern(wc) |
| 973 | ! |
comp <- comp0 + 1 |
| 974 |
# Conditional on the component index "comp", which is 1 or 2. |
|
| 975 |
# comp = 1 with probability "w" and comp = 2 with probability "1 - w". |
|
| 976 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
| 977 | ! |
alpha0 <- theta[1] |
| 978 | ! |
alpha1 <- theta[2] |
| 979 |
}, |
|
| 980 | 23x |
modelspecs = function(from_prior) {
|
| 981 | 2x |
ms <- list( |
| 982 | 2x |
mean = cbind(comp1@mean, comp2@mean), |
| 983 | 2x |
prec = array(data = c(comp1@prec, comp2@prec), dim = c(2, 2, 2)), |
| 984 | 2x |
weightpar = weightpar |
| 985 |
) |
|
| 986 | 2x |
if (!from_prior) {
|
| 987 | 1x |
ms$ref_dose <- ref_dose |
| 988 |
} |
|
| 989 | 2x |
ms |
| 990 |
}, |
|
| 991 | 23x |
init = function() {
|
| 992 | 2x |
list(theta = c(0, 1)) |
| 993 |
}, |
|
| 994 | 23x |
datanames = c("nObs", "y", "x"),
|
| 995 | 23x |
sample = c("alpha0", "alpha1", "w")
|
| 996 |
) |
|
| 997 |
} |
|
| 998 | ||
| 999 |
## default constructor ---- |
|
| 1000 | ||
| 1001 |
#' @rdname LogisticNormalMixture-class |
|
| 1002 |
#' @note Typically, end-users will not use the `.DefaultLogisticNormalMixture()` function. |
|
| 1003 |
#' @export |
|
| 1004 |
.DefaultLogisticNormalMixture <- function() {
|
|
| 1005 |
# nolint |
|
| 1006 | 9x |
LogisticNormalMixture( |
| 1007 | 9x |
comp1 = ModelParamsNormal( |
| 1008 | 9x |
mean = c(-0.85, 1), |
| 1009 | 9x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
| 1010 |
), |
|
| 1011 | 9x |
comp2 = ModelParamsNormal( |
| 1012 | 9x |
mean = c(1, 1.5), |
| 1013 | 9x |
cov = matrix(c(1.2, -0.45, -0.45, 0.6), nrow = 2) |
| 1014 |
), |
|
| 1015 | 9x |
weightpar = c(a = 1, b = 1), |
| 1016 | 9x |
ref_dose = 50 |
| 1017 |
) |
|
| 1018 |
} |
|
| 1019 | ||
| 1020 |
# LogisticNormalFixedMixture ---- |
|
| 1021 | ||
| 1022 |
## class ---- |
|
| 1023 | ||
| 1024 |
#' `LogisticNormalFixedMixture` |
|
| 1025 |
#' |
|
| 1026 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1027 |
#' |
|
| 1028 |
#' [`LogisticNormalFixedMixture`] is the class for standard logistic regression |
|
| 1029 |
#' model with fixed mixture of multiple bivariate (log) normal priors on the |
|
| 1030 |
#' intercept and slope parameters. The weights of the normal priors are fixed, |
|
| 1031 |
#' hence no additional model parameters are introduced. This type of prior is |
|
| 1032 |
#' often used to better approximate a given posterior distribution, or when the |
|
| 1033 |
#' information is given in terms of a mixture. |
|
| 1034 |
#' |
|
| 1035 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided
|
|
| 1036 |
#' by the reference dose \eqn{x*}, i.e.:
|
|
| 1037 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),}
|
|
| 1038 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 1039 |
#' The prior |
|
| 1040 |
#' \deqn{(alpha0, alpha1) ~ w1 * Normal(mean1, cov1) + ... + wK * Normal(meanK, covK),}
|
|
| 1041 |
#' if a normal prior is used and |
|
| 1042 |
#' \deqn{(alpha0, log(alpha1)) ~ w1 * Normal(mean1, cov1) + ... + wK * Normal(meanK, covK),}
|
|
| 1043 |
#' if a log normal prior is used. |
|
| 1044 |
#' The weights \eqn{w1, ..., wK} of the components are fixed and sum to 1.
|
|
| 1045 |
#' |
|
| 1046 |
#' The slots of this class comprise a list with components parameters. Every |
|
| 1047 |
#' single component contains the mean vector and the covariance matrix of |
|
| 1048 |
#' bivariate normal distributions. Remaining slots are the weights of the |
|
| 1049 |
#' components as well as the reference dose. Moreover, a special indicator |
|
| 1050 |
#' slot specifies whether a log normal prior is used. |
|
| 1051 |
#' |
|
| 1052 |
#' @slot components (`list`)\cr the specifications of the mixture components, |
|
| 1053 |
#' a list with [`ModelParamsNormal`] objects for each bivariate (log) normal |
|
| 1054 |
#' prior. |
|
| 1055 |
#' @slot weights (`numeric`)\cr the weights of the components; these must be |
|
| 1056 |
#' positive and must sum to 1. |
|
| 1057 |
#' @slot ref_dose (`positive_number`)\cr the reference dose. |
|
| 1058 |
#' @slot log_normal (`flag`)\cr should a log normal prior be used, such |
|
| 1059 |
#' that the mean vectors and covariance matrices are valid for the intercept |
|
| 1060 |
#' and log slope? |
|
| 1061 |
#' |
|
| 1062 |
#' @seealso [`ModelParamsNormal`], [`ModelLogNormal`], |
|
| 1063 |
#' [`LogisticNormalMixture`], [`LogisticLogNormalMixture`]. |
|
| 1064 |
#' |
|
| 1065 |
#' @aliases LogisticNormalFixedMixture |
|
| 1066 |
#' @export |
|
| 1067 |
#' |
|
| 1068 |
.LogisticNormalFixedMixture <- setClass( |
|
| 1069 |
Class = "LogisticNormalFixedMixture", |
|
| 1070 |
contains = "GeneralModel", |
|
| 1071 |
slots = c( |
|
| 1072 |
components = "list", |
|
| 1073 |
weights = "numeric", |
|
| 1074 |
ref_dose = "numeric", |
|
| 1075 |
log_normal = "logical" |
|
| 1076 |
), |
|
| 1077 |
prototype = prototype( |
|
| 1078 |
components = list( |
|
| 1079 |
comp1 = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
| 1080 |
comp2 = ModelParamsNormal(mean = c(-1, 1), cov = diag(2)) |
|
| 1081 |
), |
|
| 1082 |
weights = c(0.5, 0.5), |
|
| 1083 |
ref_dose = 1, |
|
| 1084 |
log_normal = FALSE |
|
| 1085 |
), |
|
| 1086 |
validity = v_model_logistic_normal_fixed_mix |
|
| 1087 |
) |
|
| 1088 | ||
| 1089 |
## constructor ---- |
|
| 1090 | ||
| 1091 |
#' @rdname LogisticNormalFixedMixture-class |
|
| 1092 |
#' |
|
| 1093 |
#' @param components (`list`)\cr the specifications of the mixture components, |
|
| 1094 |
#' a list with [`ModelParamsNormal`] objects for each bivariate (log) normal |
|
| 1095 |
#' prior. |
|
| 1096 |
#' @param weights (`numeric`)\cr the weights of the components; these must be |
|
| 1097 |
#' positive and will be normalized to sum to 1. |
|
| 1098 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*}
|
|
| 1099 |
#' (strictly positive number). |
|
| 1100 |
#' @param log_normal (`flag`)\cr should a log normal prior be specified, such |
|
| 1101 |
#' that the mean vectors and covariance matrices are valid for the intercept |
|
| 1102 |
#' and log slope? |
|
| 1103 |
#' |
|
| 1104 |
#' @export |
|
| 1105 |
#' @example examples/Model-class-LogisticNormalFixedMixture.R |
|
| 1106 |
#' |
|
| 1107 |
LogisticNormalFixedMixture <- function( |
|
| 1108 |
components, |
|
| 1109 |
weights, |
|
| 1110 |
ref_dose, |
|
| 1111 |
log_normal = FALSE |
|
| 1112 |
) {
|
|
| 1113 | 30x |
assert_numeric(weights) |
| 1114 | 30x |
assert_number(ref_dose) |
| 1115 | 30x |
assert_flag(log_normal) |
| 1116 | ||
| 1117 |
# Normalize weights to sum to 1. |
|
| 1118 | 30x |
weights <- weights / sum(weights) |
| 1119 | ||
| 1120 | 30x |
.LogisticNormalFixedMixture( |
| 1121 | 30x |
components = components, |
| 1122 | 30x |
weights = weights, |
| 1123 | 30x |
ref_dose = positive_number(ref_dose), |
| 1124 | 30x |
log_normal = log_normal, |
| 1125 | 30x |
datamodel = function() {
|
| 1126 | ! |
for (i in 1:nObs) {
|
| 1127 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 1128 | ! |
y[i] ~ dbern(p[i]) |
| 1129 |
} |
|
| 1130 |
}, |
|
| 1131 | 30x |
priormodel = if (log_normal) {
|
| 1132 | 2x |
function() {
|
| 1133 | ! |
comp ~ dcat(weights) |
| 1134 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
| 1135 | ! |
alpha0 <- theta[1] |
| 1136 | ! |
alpha1 <- exp(theta[2]) |
| 1137 |
} |
|
| 1138 |
} else {
|
|
| 1139 | 28x |
function() {
|
| 1140 | ! |
comp ~ dcat(weights) |
| 1141 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
| 1142 | ! |
alpha0 <- theta[1] |
| 1143 | ! |
alpha1 <- theta[2] |
| 1144 |
} |
|
| 1145 |
}, |
|
| 1146 | 30x |
modelspecs = function(from_prior) {
|
| 1147 | 4x |
ms <- list( |
| 1148 | 4x |
weights = weights, |
| 1149 | 4x |
mean = do.call( |
| 1150 | 4x |
cbind, |
| 1151 | 4x |
lapply(components, h_slots, "mean", simplify = TRUE) |
| 1152 |
), |
|
| 1153 | 4x |
prec = array( |
| 1154 | 4x |
do.call(c, lapply(components, h_slots, "prec", simplify = TRUE)), |
| 1155 | 4x |
dim = c(2, 2, length(components)) |
| 1156 |
) |
|
| 1157 |
) |
|
| 1158 | 4x |
if (!from_prior) {
|
| 1159 | 2x |
ms$ref_dose <- ref_dose |
| 1160 |
} |
|
| 1161 | 4x |
ms |
| 1162 |
}, |
|
| 1163 | 30x |
init = function() {
|
| 1164 | 4x |
list(theta = c(0, 1)) |
| 1165 |
}, |
|
| 1166 | 30x |
datanames = c("nObs", "y", "x"),
|
| 1167 | 30x |
sample = c("alpha0", "alpha1")
|
| 1168 |
) |
|
| 1169 |
} |
|
| 1170 | ||
| 1171 |
## default constructor ---- |
|
| 1172 | ||
| 1173 |
#' @rdname LogisticNormalFixedMixture-class |
|
| 1174 |
#' @note Typically, end-users will not use the `.DefaultLogisticNormalFixedMixture()` |
|
| 1175 |
#' function. |
|
| 1176 |
#' @export |
|
| 1177 |
.DefaultLogisticNormalFixedMixture <- function() {
|
|
| 1178 |
# nolint |
|
| 1179 | 9x |
LogisticNormalFixedMixture( |
| 1180 | 9x |
components = list( |
| 1181 | 9x |
comp1 = ModelParamsNormal( |
| 1182 | 9x |
mean = c(-0.85, 1), |
| 1183 | 9x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
| 1184 |
), |
|
| 1185 | 9x |
comp2 = ModelParamsNormal( |
| 1186 | 9x |
mean = c(1, 1.5), |
| 1187 | 9x |
cov = matrix(c(1.2, -0.45, -0.45, 0.6), nrow = 2) |
| 1188 |
) |
|
| 1189 |
), |
|
| 1190 | 9x |
weights = c(0.3, 0.7), |
| 1191 | 9x |
ref_dose = 50 |
| 1192 |
) |
|
| 1193 |
} |
|
| 1194 | ||
| 1195 |
# LogisticLogNormalMixture ---- |
|
| 1196 | ||
| 1197 |
## class ---- |
|
| 1198 | ||
| 1199 |
#' `LogisticLogNormalMixture` |
|
| 1200 |
#' |
|
| 1201 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1202 |
#' |
|
| 1203 |
#' [`LogisticLogNormalMixture`] is the class for standard logistic model with |
|
| 1204 |
#' online mixture of two bivariate log normal priors. |
|
| 1205 |
#' |
|
| 1206 |
#' @details This model can be used when data is arising online from the informative |
|
| 1207 |
#' component of the prior, at the same time with the data of the trial of |
|
| 1208 |
#' main interest. Formally, this is achieved by assuming that the probability |
|
| 1209 |
#' of a DLT at dose \eqn{x} is given by
|
|
| 1210 |
#' \deqn{p(x) = \pi * p1(x) + (1 - \pi) * p2(x)}
|
|
| 1211 |
#' where \eqn{\pi} is the probability for the model \eqn{p(x)} being the same
|
|
| 1212 |
#' as the model \eqn{p1(x)}, which is the informative component of the prior.
|
|
| 1213 |
#' From this model data arises in parallel: at doses `xshare`, DLT information |
|
| 1214 |
#' `yshare` is observed, in total `nObsshare` data points (see [`DataMixture`]). |
|
| 1215 |
#' On the other hand, \eqn{1 - \pi}, is the probability of a separate model
|
|
| 1216 |
#' \eqn{p2(x)}. Both components have the same log normal prior distribution,
|
|
| 1217 |
#' which can be specified by the user, and which is inherited from the |
|
| 1218 |
#' [`LogisticLogNormal`] class. |
|
| 1219 |
#' |
|
| 1220 |
#' @slot share_weight (`proportion`)\cr the prior weight for the share component |
|
| 1221 |
#' \eqn{p_{1}(x)}.
|
|
| 1222 |
#' |
|
| 1223 |
#' @seealso [`ModelLogNormal`], [`LogisticNormalMixture`], |
|
| 1224 |
#' [`LogisticNormalFixedMixture`]. |
|
| 1225 |
#' |
|
| 1226 |
#' @aliases LogisticLogNormalMixture |
|
| 1227 |
#' @export |
|
| 1228 |
#' |
|
| 1229 |
.LogisticLogNormalMixture <- setClass( |
|
| 1230 |
Class = "LogisticLogNormalMixture", |
|
| 1231 |
contains = "LogisticLogNormal", |
|
| 1232 |
slots = c( |
|
| 1233 |
share_weight = "numeric" |
|
| 1234 |
), |
|
| 1235 |
prototype = prototype( |
|
| 1236 |
share_weight = 0.1 |
|
| 1237 |
), |
|
| 1238 |
validity = v_model_logistic_log_normal_mix |
|
| 1239 |
) |
|
| 1240 | ||
| 1241 |
## constructor ---- |
|
| 1242 | ||
| 1243 |
#' @rdname LogisticLogNormalMixture-class |
|
| 1244 |
#' |
|
| 1245 |
#' @inheritParams ModelLogNormal |
|
| 1246 |
#' @param share_weight (`proportion`)\cr the prior weight for the share component. |
|
| 1247 |
#' |
|
| 1248 |
#' @export |
|
| 1249 |
#' @example examples/Model-class-LogisticLogNormalMixture.R |
|
| 1250 |
#' |
|
| 1251 |
LogisticLogNormalMixture <- function(mean, cov, ref_dose, share_weight) {
|
|
| 1252 | 22x |
assert_number(ref_dose) |
| 1253 | ||
| 1254 | 22x |
params <- ModelParamsNormal(mean, cov) |
| 1255 | 22x |
.LogisticLogNormalMixture( |
| 1256 | 22x |
params = params, |
| 1257 | 22x |
ref_dose = positive_number(ref_dose), |
| 1258 | 22x |
share_weight = share_weight, |
| 1259 | 22x |
datamodel = function() {
|
| 1260 | ! |
for (i in 1:nObs) {
|
| 1261 |
# comp gives the component: non-informative (1) or share (2) the two components. |
|
| 1262 | ! |
stand_log_dose[i] <- log(x[i] / ref_dose) |
| 1263 | ! |
logit(p[i]) <- alpha0[comp] + alpha1[comp] * stand_log_dose[i] |
| 1264 | ! |
y[i] ~ dbern(p[i]) |
| 1265 |
} |
|
| 1266 | ! |
for (j in 1:nObsshare) {
|
| 1267 | ! |
stand_log_dose_share[j] <- log(xshare[j] / ref_dose) |
| 1268 | ! |
logit(pshare[j]) <- alpha0[2] + alpha1[2] * stand_log_dose_share[j] |
| 1269 | ! |
yshare[j] ~ dbern(pshare[j]) |
| 1270 |
} |
|
| 1271 |
}, |
|
| 1272 | 22x |
priormodel = function() {
|
| 1273 | ! |
for (k in 1:2) {
|
| 1274 | ! |
theta[k, 1:2] ~ dmnorm(mean, prec) |
| 1275 | ! |
alpha0[k] <- theta[k, 1] |
| 1276 | ! |
alpha1[k] <- exp(theta[k, 2]) |
| 1277 |
} |
|
| 1278 |
# The component indicator. |
|
| 1279 | ! |
comp ~ dcat(cat_probs) |
| 1280 |
}, |
|
| 1281 | 22x |
modelspecs = function(from_prior) {
|
| 1282 | 2x |
ms <- list( |
| 1283 | 2x |
cat_probs = c(1 - share_weight, share_weight), |
| 1284 | 2x |
mean = params@mean, |
| 1285 | 2x |
prec = params@prec |
| 1286 |
) |
|
| 1287 | 2x |
if (!from_prior) {
|
| 1288 | 1x |
ms$ref_dose <- ref_dose |
| 1289 |
} |
|
| 1290 | 2x |
ms |
| 1291 |
}, |
|
| 1292 | 22x |
init = function() {
|
| 1293 | 2x |
list(theta = matrix(c(0, 0, 1, 1), nrow = 2)) |
| 1294 |
}, |
|
| 1295 | 22x |
datanames = c("nObs", "y", "x", "nObsshare", "yshare", "xshare"),
|
| 1296 | 22x |
sample = c("alpha0", "alpha1", "comp")
|
| 1297 |
) |
|
| 1298 |
} |
|
| 1299 | ||
| 1300 |
## default constructor ---- |
|
| 1301 | ||
| 1302 |
#' @rdname LogisticLogNormalMixture-class |
|
| 1303 |
#' @note Typically, end users will not use the `.DefaultLogNormalMixture()` function. |
|
| 1304 |
#' @export |
|
| 1305 |
.DefaultLogisticLogNormalMixture <- function() {
|
|
| 1306 |
# nolint |
|
| 1307 | 9x |
LogisticLogNormalMixture( |
| 1308 | 9x |
share_weight = 0.1, |
| 1309 | 9x |
mean = c(-0.85, 1), |
| 1310 | 9x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 1311 | 9x |
ref_dose = 50 |
| 1312 |
) |
|
| 1313 |
} |
|
| 1314 | ||
| 1315 |
# DualEndpoint ---- |
|
| 1316 | ||
| 1317 |
## class ---- |
|
| 1318 | ||
| 1319 |
#' `DualEndpoint` |
|
| 1320 |
#' |
|
| 1321 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1322 |
#' |
|
| 1323 |
#' [`DualEndpoint`] is the general class for the dual endpoint model. |
|
| 1324 |
#' |
|
| 1325 |
#' @details The idea of the dual-endpoint models is to model not only the |
|
| 1326 |
#' dose-toxicity relationship, but also to model, at the same time, the |
|
| 1327 |
#' relationship of a PD biomarker with the dose. The sub-classes of this class |
|
| 1328 |
#' define how the dose-biomarker relationship is parametrized. This class here |
|
| 1329 |
#' shall contain all the common features to reduce duplicate code. |
|
| 1330 |
#' (This class however, must not be virtual as we need to create objects |
|
| 1331 |
#' of it during the construction of subclass objects.) |
|
| 1332 |
#' |
|
| 1333 |
#' The dose-toxicity relationship is modeled with probit regression model |
|
| 1334 |
#' \deqn{probit[p(x)] = betaZ1 + betaZ2 * x/x*,}
|
|
| 1335 |
#' or |
|
| 1336 |
#' \deqn{probit[p(x)] = betaZ1 + betaZ2 * log(x/x*),}
|
|
| 1337 |
#' in case when the option `use_log_dose` is `TRUE`. |
|
| 1338 |
#' Here, \eqn{p(x)} is the probability of observing a DLT for a given
|
|
| 1339 |
#' dose \eqn{x} and \eqn{x*} is the reference dose.
|
|
| 1340 |
#' The prior \deqn{(betaZ1, log(betaZ2)) ~ Normal(mean, cov).}
|
|
| 1341 |
#' |
|
| 1342 |
#' For the biomarker response \eqn{w} at a dose \eqn{x}, we assume
|
|
| 1343 |
#' \deqn{w(x) ~ Normal(f(x), sigma2W),}
|
|
| 1344 |
#' where \eqn{f(x)} is a function of the dose \eqn{x}, which is further
|
|
| 1345 |
#' specified in sub-classes. The biomarker variance \eqn{sigma2W} can be fixed
|
|
| 1346 |
#' or assigned an Inverse-Gamma prior distribution; see the details below under |
|
| 1347 |
#' slot `sigma2W`. |
|
| 1348 |
#' |
|
| 1349 |
#' Finally, the two endpoints \eqn{y} (the binary DLT variable) and \eqn{w}
|
|
| 1350 |
#' (the biomarker) can be correlated, by assuming a correlation of level |
|
| 1351 |
#' \eqn{rho} between the underlying continuous latent toxicity variable \eqn{z}
|
|
| 1352 |
#' and the biomarker \eqn{w}. Again, this correlation can be fixed or assigned
|
|
| 1353 |
#' a prior distribution from the scaled Beta family; see the details below |
|
| 1354 |
#' under slot `rho`. |
|
| 1355 |
#' |
|
| 1356 |
#' Please see the example vignette by typing `crmPackExample()` for a full example. |
|
| 1357 |
#' |
|
| 1358 |
#' @slot betaZ_params (`ModelParamsNormal`)\cr for the probit toxicity model, it |
|
| 1359 |
#' contains the prior mean, covariance matrix and precision matrix which is |
|
| 1360 |
#' internally calculated as an inverse of the covariance matrix. |
|
| 1361 |
#' @slot ref_dose (`positive_number`)\cr for the probit toxicity model, the |
|
| 1362 |
#' reference dose. |
|
| 1363 |
#' @slot use_log_dose (`flag`)\cr for the probit toxicity model, whether a log |
|
| 1364 |
#' transformation of the (standardized) dose should be used? |
|
| 1365 |
#' @slot sigma2W (`numeric`)\cr the biomarker variance. Either a fixed value or |
|
| 1366 |
#' Inverse-Gamma distribution parameters, i.e. vector with two elements named |
|
| 1367 |
#' `a` and `b`. |
|
| 1368 |
#' @slot rho (`numeric`)\cr either a fixed value for the correlation |
|
| 1369 |
#' (between `-1` and `1`), or a named vector with two elements named `a` and `b` |
|
| 1370 |
#' for the Beta prior on the transformation `kappa = (rho + 1) / 2`, which is |
|
| 1371 |
#' in `(0, 1)`. For example, `a = 1, b = 1` leads to a uniform prior on `rho`. |
|
| 1372 |
#' @slot use_fixed (`logical`)\cr indicates whether a fixed value for `sigma2W` |
|
| 1373 |
#' or `rho` (for each parameter separately) is used or not. This slot is |
|
| 1374 |
#' needed for internal purposes and must not be touched by the user. |
|
| 1375 |
#' |
|
| 1376 |
#' @seealso [`DualEndpointRW`], [`DualEndpointBeta`], [`DualEndpointEmax`]. |
|
| 1377 |
#' |
|
| 1378 |
#' @aliases DualEndpoint |
|
| 1379 |
#' @export |
|
| 1380 |
#' |
|
| 1381 |
.DualEndpoint <- setClass( |
|
| 1382 |
Class = "DualEndpoint", |
|
| 1383 |
slots = c( |
|
| 1384 |
betaZ_params = "ModelParamsNormal", |
|
| 1385 |
ref_dose = "positive_number", |
|
| 1386 |
use_log_dose = "logical", |
|
| 1387 |
sigma2W = "numeric", |
|
| 1388 |
rho = "numeric", |
|
| 1389 |
use_fixed = "logical" |
|
| 1390 |
), |
|
| 1391 |
prototype = prototype( |
|
| 1392 |
betaZ_params = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
| 1393 |
ref_dose = positive_number(1), |
|
| 1394 |
use_log_dose = FALSE, |
|
| 1395 |
sigma2W = 1, |
|
| 1396 |
rho = 0, |
|
| 1397 |
use_fixed = c(sigma2W = TRUE, rho = TRUE) |
|
| 1398 |
), |
|
| 1399 |
contains = "GeneralModel", |
|
| 1400 |
validity = v_model_dual_endpoint |
|
| 1401 |
) |
|
| 1402 | ||
| 1403 |
## constructor ---- |
|
| 1404 | ||
| 1405 |
#' @rdname DualEndpoint-class |
|
| 1406 |
#' |
|
| 1407 |
#' @param mean (`numeric`)\cr for the probit toxicity model, the prior mean vector. |
|
| 1408 |
#' @param cov (`matrix`)\cr for the probit toxicity model, the prior covariance |
|
| 1409 |
#' matrix. The precision matrix is internally calculated as an inverse of `cov`. |
|
| 1410 |
#' @param ref_dose (`number`)\cr for the probit toxicity model, the reference |
|
| 1411 |
#' dose \eqn{x*} (strictly positive number).
|
|
| 1412 |
#' @param use_log_dose (`flag`)\cr for the probit toxicity model, whether a log |
|
| 1413 |
#' transformation of the (standardized) dose should be used? |
|
| 1414 |
#' @param sigma2W (`numeric`)\cr the biomarker variance. Either a fixed value or |
|
| 1415 |
#' Inverse-Gamma distribution parameters, i.e. vector with two elements named |
|
| 1416 |
#' `a` and `b`. |
|
| 1417 |
#' @param rho (`numeric`)\cr either a fixed value for the correlation |
|
| 1418 |
#' (between `-1` and `1`), or a named vector with two elements named `a` and `b` |
|
| 1419 |
#' for the Beta prior on the transformation `kappa = (rho + 1) / 2`, which is |
|
| 1420 |
#' in `(0, 1)`. For example, `a = 1, b = 1` leads to a uniform prior on `rho`. |
|
| 1421 |
#' |
|
| 1422 |
#' @export |
|
| 1423 |
#' |
|
| 1424 |
DualEndpoint <- function( |
|
| 1425 |
mean, |
|
| 1426 |
cov, |
|
| 1427 |
ref_dose = 1, |
|
| 1428 |
use_log_dose = FALSE, |
|
| 1429 |
sigma2W, |
|
| 1430 |
rho |
|
| 1431 |
) {
|
|
| 1432 | 214x |
assert_number(ref_dose) |
| 1433 | 214x |
assert_numeric(sigma2W, min.len = 1, max.len = 2) |
| 1434 | 214x |
assert_numeric(rho, min.len = 1, max.len = 2) |
| 1435 | ||
| 1436 | 214x |
use_fixed <- c( |
| 1437 | 214x |
sigma2W = test_number(sigma2W), |
| 1438 | 214x |
rho = test_number(rho) |
| 1439 |
) |
|
| 1440 | 214x |
beta_z_params <- ModelParamsNormal(mean, cov) |
| 1441 | ||
| 1442 | 214x |
datamodel <- function() {
|
| 1443 | ! |
for (i in 1:nObs) {
|
| 1444 |
# The toxicity model. |
|
| 1445 | ! |
stand_dose_temp[i] <- x[i] / ref_dose |
| 1446 | ! |
stand_dose[i] <- ifelse( |
| 1447 | ! |
use_log_dose, |
| 1448 | ! |
log(stand_dose_temp[i]), |
| 1449 | ! |
stand_dose_temp[i] |
| 1450 |
) |
|
| 1451 | ! |
meanZ[i] <- betaZ[1] + betaZ[2] * stand_dose[i] |
| 1452 | ! |
z[i] ~ dnorm(meanZ[i], 1) |
| 1453 | ! |
y[i] ~ dinterval(z[i], 0) |
| 1454 | ||
| 1455 |
# The conditional biomarker model; betaW defined in subclasses! |
|
| 1456 | ! |
condMeanW[i] <- betaW[xLevel[i]] + rho / sqrt(precW) * (z[i] - meanZ[i]) |
| 1457 | ! |
w[i] ~ dnorm(condMeanW[i], condPrecW) |
| 1458 |
} |
|
| 1459 |
} |
|
| 1460 | 214x |
priormodel <- function() {
|
| 1461 |
# Priors for betaW defined in subclasses! |
|
| 1462 | ! |
theta ~ dmnorm(betaZ_mean, betaZ_prec) |
| 1463 | ! |
betaZ[1] <- theta[1] |
| 1464 | ! |
betaZ[2] <- exp(theta[2]) |
| 1465 |
# Conditional precision for biomarker. |
|
| 1466 |
# Code for `precW` and `rho` will be added by |
|
| 1467 |
# `h_model_dual_endpoint_sigma2w()`, `h_model_dual_endpoint_rho()` helpers, below. |
|
| 1468 | ! |
condPrecW <- precW / (1 - pow(rho, 2)) |
| 1469 |
} |
|
| 1470 | 214x |
modelspecs_prior <- list( |
| 1471 | 214x |
betaZ_mean = beta_z_params@mean, |
| 1472 | 214x |
betaZ_prec = beta_z_params@prec |
| 1473 |
) |
|
| 1474 | ||
| 1475 | 214x |
comp <- list( |
| 1476 | 214x |
priormodel = priormodel, |
| 1477 | 214x |
modelspecs = modelspecs_prior, |
| 1478 | 214x |
init = NULL, |
| 1479 | 214x |
sample = "betaZ" |
| 1480 |
) |
|
| 1481 | ||
| 1482 |
# Update model components with regard to biomarker regression variance. |
|
| 1483 | 214x |
comp <- h_model_dual_endpoint_sigma2w( |
| 1484 | 214x |
use_fixed["sigma2W"], |
| 1485 | 214x |
sigma2W = sigma2W, |
| 1486 | 214x |
comp = comp |
| 1487 |
) |
|
| 1488 | ||
| 1489 |
# Update model components with regard to DLT and biomarker correlation. |
|
| 1490 | 214x |
comp <- h_model_dual_endpoint_rho( |
| 1491 | 214x |
use_fixed["rho"], |
| 1492 | 214x |
rho = rho, |
| 1493 | 214x |
comp = comp |
| 1494 |
) |
|
| 1495 | ||
| 1496 | 214x |
.DualEndpoint( |
| 1497 | 214x |
betaZ_params = beta_z_params, |
| 1498 | 214x |
ref_dose = positive_number(ref_dose), |
| 1499 | 214x |
use_log_dose = use_log_dose, |
| 1500 | 214x |
sigma2W = sigma2W, |
| 1501 | 214x |
rho = rho, |
| 1502 | 214x |
use_fixed = use_fixed, |
| 1503 | 214x |
datamodel = datamodel, |
| 1504 | 214x |
priormodel = comp$priormodel, |
| 1505 | 214x |
modelspecs = function(from_prior) {
|
| 1506 | 73x |
if (!from_prior) {
|
| 1507 | 48x |
comp$modelspecs$ref_dose <- ref_dose |
| 1508 | 48x |
comp$modelspecs$use_log_dose <- use_log_dose |
| 1509 |
} |
|
| 1510 | 73x |
comp$modelspecs |
| 1511 |
}, |
|
| 1512 | 214x |
init = function(y) {
|
| 1513 | 63x |
c(comp$init, list(z = ifelse(y == 0, -1, 1), theta = c(0, 1))) |
| 1514 |
}, |
|
| 1515 | 214x |
datanames = c("nObs", "w", "x", "xLevel", "y"),
|
| 1516 | 214x |
sample = comp$sample |
| 1517 |
) |
|
| 1518 |
} |
|
| 1519 | ||
| 1520 |
## default constructor ---- |
|
| 1521 | ||
| 1522 |
#' @rdname DualEndpoint-class |
|
| 1523 |
#' @note Typically, end users will not use the `.DefaultDualEndpoint()` function. |
|
| 1524 |
#' @export |
|
| 1525 |
.DefaultDualEndpoint <- function() {
|
|
| 1526 | 4x |
stop(paste0( |
| 1527 | 4x |
"Class DualEndpoint cannot be instantiated directly. Please use one of its subclasses instead." |
| 1528 |
)) |
|
| 1529 |
} |
|
| 1530 | ||
| 1531 |
# DualEndpointRW ---- |
|
| 1532 | ||
| 1533 |
## class ---- |
|
| 1534 | ||
| 1535 |
#' `DualEndpointRW` |
|
| 1536 |
#' |
|
| 1537 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1538 |
#' |
|
| 1539 |
#' [`DualEndpointRW`] is the class for the dual endpoint model with random walk |
|
| 1540 |
#' prior for biomarker. |
|
| 1541 |
#' |
|
| 1542 |
#' |
|
| 1543 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
| 1544 |
#' relationship \eqn{f(x)} is modelled by a non-parametric random walk of first
|
|
| 1545 |
#' or second order. That means, for the first order random walk we assume |
|
| 1546 |
#' \deqn{betaW_i - betaW_i-1 ~ Normal(0, (x_i - x_i-1) * sigma2betaW),}
|
|
| 1547 |
#' where \eqn{betaW_i = f(x_i)} is the biomarker mean at the \eqn{i}-th dose
|
|
| 1548 |
#' gridpoint \eqn{x_i}.
|
|
| 1549 |
#' For the second order random walk, the second-order differences instead of |
|
| 1550 |
#' the first-order differences of the biomarker means follow the normal distribution |
|
| 1551 |
#' with \eqn{0} mean and \eqn{2 * (x_i - x_i-2) * sigma2betaW} variance.
|
|
| 1552 |
#' |
|
| 1553 |
#' The variance parameter \eqn{sigma2betaW} is important because it steers the
|
|
| 1554 |
#' smoothness of the function \eqn{f(x)}, i.e.: if it is large, then \eqn{f(x)}
|
|
| 1555 |
#' will be very wiggly; if it is small, then \eqn{f(x)} will be smooth.
|
|
| 1556 |
#' This parameter can either be a fixed value or assigned an inverse gamma prior |
|
| 1557 |
#' distribution. |
|
| 1558 |
#' |
|
| 1559 |
#' @note Non-equidistant dose grids can be used now, because the difference |
|
| 1560 |
#' \eqn{x_i - x_i-1} is included in the modelling assumption above.
|
|
| 1561 |
#' Please note that due to impropriety of the random walk prior distributions, |
|
| 1562 |
#' it is not possible to produce MCMC samples with empty data objects (i.e., |
|
| 1563 |
#' sample from the prior). This is not a bug, but a theoretical feature of this |
|
| 1564 |
#' model. |
|
| 1565 |
#' |
|
| 1566 |
#' @slot sigma2betaW (`numeric`)\cr the prior variance factor of the random walk |
|
| 1567 |
#' prior for the biomarker model. Either a fixed value or Inverse-Gamma distribution |
|
| 1568 |
#' parameters, i.e. vector with two elements named `a` and `b`. |
|
| 1569 |
#' @slot rw1 (`flag`)\cr for specifying the random walk prior on the biomarker |
|
| 1570 |
#' level. When `TRUE`, random walk of first order is used. Otherwise, the |
|
| 1571 |
#' random walk of second order is used. |
|
| 1572 |
#' |
|
| 1573 |
#' @seealso [`DualEndpoint`], [`DualEndpointBeta`], [`DualEndpointEmax`]. |
|
| 1574 |
#' |
|
| 1575 |
#' @aliases DualEndpointRW |
|
| 1576 |
#' @export |
|
| 1577 |
#' |
|
| 1578 |
.DualEndpointRW <- setClass( |
|
| 1579 |
Class = "DualEndpointRW", |
|
| 1580 |
slots = c( |
|
| 1581 |
sigma2betaW = "numeric", |
|
| 1582 |
rw1 = "logical" |
|
| 1583 |
), |
|
| 1584 |
prototype = prototype( |
|
| 1585 |
sigma2betaW = 1, |
|
| 1586 |
rw1 = TRUE, |
|
| 1587 |
use_fixed = c( |
|
| 1588 |
sigma2W = TRUE, |
|
| 1589 |
rho = TRUE, |
|
| 1590 |
sigma2betaW = TRUE |
|
| 1591 |
) |
|
| 1592 |
), |
|
| 1593 |
contains = "DualEndpoint", |
|
| 1594 |
validity = v_model_dual_endpoint_rw |
|
| 1595 |
) |
|
| 1596 | ||
| 1597 |
## constructor ---- |
|
| 1598 | ||
| 1599 |
#' @rdname DualEndpointRW-class |
|
| 1600 |
#' |
|
| 1601 |
#' @param sigma2betaW (`numeric`)\cr the prior variance factor of the random walk |
|
| 1602 |
#' prior for the biomarker model. Either a fixed value or Inverse-Gamma distribution |
|
| 1603 |
#' parameters, i.e. vector with two elements named `a` and `b`. |
|
| 1604 |
#' @param rw1 (`flag`)\cr for specifying the random walk prior on the biomarker |
|
| 1605 |
#' level. When `TRUE`, random walk of first order is used. Otherwise, the |
|
| 1606 |
#' random walk of second order is used. |
|
| 1607 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
| 1608 |
#' |
|
| 1609 |
#' @export |
|
| 1610 |
#' @example examples/Model-class-DualEndpointRW.R |
|
| 1611 |
#' |
|
| 1612 |
DualEndpointRW <- function(sigma2betaW, rw1 = TRUE, ...) {
|
|
| 1613 | 61x |
assert_numeric(sigma2betaW, min.len = 1, max.len = 2) |
| 1614 | 61x |
assert_flag(rw1) |
| 1615 | ||
| 1616 | 61x |
start <- DualEndpoint(...) |
| 1617 | 61x |
start@use_fixed["sigma2betaW"] <- length(sigma2betaW) == 1L |
| 1618 | ||
| 1619 | 61x |
priormodel <- if (rw1) {
|
| 1620 | 52x |
function() {
|
| 1621 |
# The 1st order differences. |
|
| 1622 |
# Essentially dflat(), which is not available in JAGS. |
|
| 1623 | ! |
betaW[1] ~ dnorm(0, 0.000001) |
| 1624 | ! |
for (i in 2:nGrid) {
|
| 1625 | ! |
delta[i - 1] ~ dnorm(0, precBetaW / (doseGrid[i] - doseGrid[i - 1])) |
| 1626 | ! |
betaW[i] <- betaW[i - 1] + delta[i - 1] |
| 1627 |
} |
|
| 1628 |
} |
|
| 1629 |
} else {
|
|
| 1630 | 9x |
function() {
|
| 1631 |
# The 2nd order differences. |
|
| 1632 | ! |
delta[1] ~ dnorm(0, 0.000001) |
| 1633 | ! |
betaW[1] ~ dnorm(0, 0.000001) |
| 1634 | ! |
betaW[2] <- betaW[1] + delta[1] |
| 1635 | ! |
for (i in 3:nGrid) {
|
| 1636 |
# delta2: differences of the differences of betaW follow normal dist. |
|
| 1637 | ! |
delta2[i - 2] ~ |
| 1638 | ! |
dnorm(0, 2 * precBetaW / (doseGrid[i] - doseGrid[i - 2])) |
| 1639 | ! |
delta[i - 1] <- delta[i - 2] + delta2[i - 2] |
| 1640 | ! |
betaW[i] <- betaW[i - 1] + delta[i - 1] |
| 1641 |
} |
|
| 1642 |
} |
|
| 1643 |
} |
|
| 1644 | 61x |
start@priormodel <- h_jags_join_models(start@priormodel, priormodel) |
| 1645 | 61x |
start@datanames_prior <- c("nGrid", "doseGrid")
|
| 1646 | 61x |
start@sample <- c(start@sample, "betaW", "delta") |
| 1647 | ||
| 1648 |
# Update model components with regard to biomarker regression variance. |
|
| 1649 | 61x |
start <- h_model_dual_endpoint_sigma2betaw( |
| 1650 | 61x |
start@use_fixed["sigma2betaW"], |
| 1651 | 61x |
sigma2betaW = sigma2betaW, |
| 1652 | 61x |
de = start |
| 1653 |
) |
|
| 1654 | ||
| 1655 | 61x |
.DualEndpointRW( |
| 1656 | 61x |
start, |
| 1657 | 61x |
sigma2betaW = sigma2betaW, |
| 1658 | 61x |
rw1 = rw1 |
| 1659 |
) |
|
| 1660 |
} |
|
| 1661 | ||
| 1662 |
## default constructor ---- |
|
| 1663 | ||
| 1664 |
#' @rdname DualEndpointRW-class |
|
| 1665 |
#' @note Typically, end users will not use the `.DefaultDualEndpointRW()` function. |
|
| 1666 |
#' @export |
|
| 1667 |
.DefaultDualEndpointRW <- function() {
|
|
| 1668 | 9x |
DualEndpointRW( |
| 1669 | 9x |
mean = c(0, 1), |
| 1670 | 9x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 1671 | 9x |
sigma2W = c(a = 0.1, b = 0.1), |
| 1672 | 9x |
rho = c(a = 1, b = 1), |
| 1673 | 9x |
sigma2betaW = 0.01, |
| 1674 | 9x |
rw1 = TRUE |
| 1675 |
) |
|
| 1676 |
} |
|
| 1677 | ||
| 1678 |
# DualEndpointBeta ---- |
|
| 1679 | ||
| 1680 |
## class ---- |
|
| 1681 | ||
| 1682 |
#' `DualEndpointBeta` |
|
| 1683 |
#' |
|
| 1684 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1685 |
#' |
|
| 1686 |
#' [`DualEndpointBeta`] is the class for the dual endpoint model with beta |
|
| 1687 |
#' function for dose-biomarker relationship. |
|
| 1688 |
#' |
|
| 1689 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
| 1690 |
#' relationship \eqn{f(x)} is modelled by a parametric, rescaled beta density
|
|
| 1691 |
#' function: |
|
| 1692 |
#' \deqn{f(x) = E0 + (Emax - E0) * Beta(delta1, delta2) * (x/x*)^{delta1} * (1 - x/x*)^{delta2},}
|
|
| 1693 |
#' where \eqn{x*} is the maximum dose (end of the dose range to be considered),
|
|
| 1694 |
#' \eqn{delta1} and \eqn{delta2} are the two beta function parameters, and
|
|
| 1695 |
#' \eqn{E0}, \eqn{Emax} are the minimum and maximum levels, respectively.
|
|
| 1696 |
#' For ease of interpretation, we use the parametrization based on \eqn{delta1}
|
|
| 1697 |
#' and the mode, where |
|
| 1698 |
#' \deqn{mode = delta1 / (delta1 + delta2),}
|
|
| 1699 |
#' so that multiplying this by \eqn{x*} gives the mode on the dose grid.
|
|
| 1700 |
#' |
|
| 1701 |
#' All parameters can currently be assigned uniform distributions or be fixed |
|
| 1702 |
#' in advance. Note that \code{E0} and \code{Emax} can have negative values or
|
|
| 1703 |
#' uniform distributions reaching into negative range, while \code{delta1} and
|
|
| 1704 |
#' \code{mode} must be positive or have uniform distributions in the positive
|
|
| 1705 |
#' range. |
|
| 1706 |
#' |
|
| 1707 |
#' @slot E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1708 |
#' parameters. |
|
| 1709 |
#' @slot Emax (`numeric`)\cr either a fixed number or the two uniform |
|
| 1710 |
#' distribution parameters. |
|
| 1711 |
#' @slot delta1 (`numeric`)\cr either a fixed positive number or the two |
|
| 1712 |
#' parameters of the uniform distribution, that can take only positive values. |
|
| 1713 |
#' @slot mode (`numeric`)\cr either a fixed positive number or the two |
|
| 1714 |
#' parameters of the uniform distribution, that can take only positive values. |
|
| 1715 |
#' @slot ref_dose_beta (`positive_number`)\cr the reference dose \eqn{x*} (note
|
|
| 1716 |
#' that this is different from the `ref_dose` in the inherited [`DualEndpoint`] |
|
| 1717 |
#' model). |
|
| 1718 |
#' |
|
| 1719 |
#' @seealso [`DualEndpoint`], [`DualEndpointRW`], [`DualEndpointEmax`]. |
|
| 1720 |
#' |
|
| 1721 |
#' @aliases DualEndpointBeta |
|
| 1722 |
#' @export |
|
| 1723 |
#' |
|
| 1724 |
.DualEndpointBeta <- setClass( |
|
| 1725 |
Class = "DualEndpointBeta", |
|
| 1726 |
slots = c( |
|
| 1727 |
E0 = "numeric", |
|
| 1728 |
Emax = "numeric", |
|
| 1729 |
delta1 = "numeric", |
|
| 1730 |
mode = "numeric", |
|
| 1731 |
ref_dose_beta = "positive_number" |
|
| 1732 |
), |
|
| 1733 |
prototype = prototype( |
|
| 1734 |
E0 = c(0, 100), |
|
| 1735 |
Emax = c(0, 500), |
|
| 1736 |
delta1 = c(0, 5), |
|
| 1737 |
mode = c(1, 15), |
|
| 1738 |
ref_dose_beta = positive_number(1), |
|
| 1739 |
use_fixed = c( |
|
| 1740 |
sigma2W = TRUE, |
|
| 1741 |
rho = TRUE, |
|
| 1742 |
E0 = FALSE, |
|
| 1743 |
Emax = FALSE, |
|
| 1744 |
delta1 = FALSE, |
|
| 1745 |
mode = FALSE |
|
| 1746 |
) |
|
| 1747 |
), |
|
| 1748 |
contains = "DualEndpoint", |
|
| 1749 |
validity = v_model_dual_endpoint_beta |
|
| 1750 |
) |
|
| 1751 | ||
| 1752 |
## constructor ---- |
|
| 1753 | ||
| 1754 |
#' @rdname DualEndpointBeta-class |
|
| 1755 |
#' |
|
| 1756 |
#' @param E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1757 |
#' parameters. |
|
| 1758 |
#' @param Emax (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1759 |
#' parameters. |
|
| 1760 |
#' @param delta1 (`numeric`)\cr either a fixed positive number or the two parameters |
|
| 1761 |
#' of the uniform distribution, that can take only positive values. |
|
| 1762 |
#' @param mode (`numeric`)\cr either a fixed positive number or the two parameters |
|
| 1763 |
#' of the uniform distribution, that can take only positive values. |
|
| 1764 |
#' @param ref_dose_beta (`number`)\cr the reference dose \eqn{x*} (strictly
|
|
| 1765 |
#' positive number). Note that this is different from the `ref_dose` in the |
|
| 1766 |
#' inherited [`DualEndpoint`] model). |
|
| 1767 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
| 1768 |
#' |
|
| 1769 |
#' @export |
|
| 1770 |
#' @example examples/Model-class-DualEndpointBeta.R |
|
| 1771 |
#' |
|
| 1772 |
DualEndpointBeta <- function(E0, Emax, delta1, mode, ref_dose_beta = 1, ...) {
|
|
| 1773 | 28x |
assert_numeric(E0, min.len = 1, max.len = 2) |
| 1774 | 28x |
assert_numeric(Emax, min.len = 1, max.len = 2) |
| 1775 | 28x |
assert_numeric(delta1, min.len = 1, max.len = 2) |
| 1776 | 28x |
assert_numeric(mode, min.len = 1, max.len = 2) |
| 1777 | 28x |
assert_number(ref_dose_beta) |
| 1778 | ||
| 1779 | 28x |
start <- DualEndpoint(...) |
| 1780 | ||
| 1781 | 28x |
ms <- start@modelspecs |
| 1782 | 28x |
start@modelspecs <- function(from_prior) {
|
| 1783 | 8x |
c(list(ref_dose_beta = ref_dose_beta), ms(from_prior)) |
| 1784 |
} |
|
| 1785 | 28x |
start@datanames_prior <- c("nGrid", "doseGrid")
|
| 1786 | 28x |
start@sample <- c(start@sample, "betaW") |
| 1787 | ||
| 1788 | 28x |
start <- h_model_dual_endpoint_beta( |
| 1789 | 28x |
param = E0, |
| 1790 | 28x |
param_name = "E0", |
| 1791 | 28x |
priormodel = function() {
|
| 1792 | ! |
E0 ~ dunif(E0_low, E0_high) |
| 1793 |
}, |
|
| 1794 | 28x |
de = start |
| 1795 |
) |
|
| 1796 | ||
| 1797 | 28x |
start <- h_model_dual_endpoint_beta( |
| 1798 | 28x |
param = Emax, |
| 1799 | 28x |
param_name = "Emax", |
| 1800 | 28x |
priormodel = function() {
|
| 1801 | ! |
Emax ~ dunif(Emax_low, Emax_high) |
| 1802 |
}, |
|
| 1803 | 28x |
de = start |
| 1804 |
) |
|
| 1805 | ||
| 1806 | 28x |
start <- h_model_dual_endpoint_beta( |
| 1807 | 28x |
param = delta1, |
| 1808 | 28x |
param_name = "delta1", |
| 1809 | 28x |
priormodel = function() {
|
| 1810 | ! |
delta1 ~ dunif(delta1_low, delta1_high) |
| 1811 |
}, |
|
| 1812 | 28x |
de = start |
| 1813 |
) |
|
| 1814 | ||
| 1815 | 28x |
start <- h_model_dual_endpoint_beta( |
| 1816 | 28x |
param = mode, |
| 1817 | 28x |
param_name = "mode", |
| 1818 | 28x |
priormodel = function() {
|
| 1819 | ! |
mode ~ dunif(mode_low, mode_high) |
| 1820 |
}, |
|
| 1821 | 28x |
de = start |
| 1822 |
) |
|
| 1823 | ||
| 1824 | 28x |
start@priormodel <- h_jags_join_models( |
| 1825 | 28x |
start@priormodel, |
| 1826 | 28x |
function() {
|
| 1827 |
# delta2 <- delta1 * (1 - (mode/ref_dose_beta)) / (mode/ref_dose_beta) # nolint |
|
| 1828 | ! |
delta2 <- delta1 * (ref_dose_beta / mode - 1) |
| 1829 |
# betafun <- (delta1 + delta2)^(delta1 + delta2) * delta1^(- delta1) * delta2^(- delta2) # nolint |
|
| 1830 | ! |
betafun <- (1 + delta2 / delta1)^delta1 * (delta1 / delta2 + 1)^delta2 |
| 1831 | ! |
for (i in 1:nGrid) {
|
| 1832 | ! |
stand_dose_beta[i] <- doseGrid[i] / ref_dose_beta |
| 1833 | ! |
betaW[i] <- E0 + |
| 1834 | ! |
(Emax - E0) * |
| 1835 | ! |
betafun * |
| 1836 | ! |
stand_dose_beta[i]^delta1 * |
| 1837 | ! |
(1 - stand_dose_beta[i])^delta2 |
| 1838 |
} |
|
| 1839 |
} |
|
| 1840 |
) |
|
| 1841 | ||
| 1842 | 28x |
.DualEndpointBeta( |
| 1843 | 28x |
start, |
| 1844 | 28x |
E0 = E0, |
| 1845 | 28x |
Emax = Emax, |
| 1846 | 28x |
delta1 = delta1, |
| 1847 | 28x |
mode = mode, |
| 1848 | 28x |
ref_dose_beta = positive_number(ref_dose_beta) |
| 1849 |
) |
|
| 1850 |
} |
|
| 1851 | ||
| 1852 |
## default constructor ---- |
|
| 1853 | ||
| 1854 |
#' @rdname DualEndpointBeta-class |
|
| 1855 |
#' @note Typically, end users will not use the `.DefaultDualEndpointBeta()` function. |
|
| 1856 |
#' @export |
|
| 1857 |
.DefaultDualEndpointBeta <- function() {
|
|
| 1858 | 9x |
DualEndpointBeta( |
| 1859 | 9x |
mean = c(0, 1), |
| 1860 | 9x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 1861 | 9x |
ref_dose = 10, |
| 1862 | 9x |
use_log_dose = TRUE, |
| 1863 | 9x |
sigma2W = c(a = 0.1, b = 0.1), |
| 1864 | 9x |
rho = c(a = 1, b = 1), |
| 1865 | 9x |
E0 = c(0, 100), |
| 1866 | 9x |
Emax = c(0, 500), |
| 1867 | 9x |
delta1 = c(0, 5), |
| 1868 | 9x |
mode = c(1, 15), |
| 1869 | 9x |
ref_dose_beta = 1000 |
| 1870 |
) |
|
| 1871 |
} |
|
| 1872 | ||
| 1873 |
# DualEndpointEmax ---- |
|
| 1874 | ||
| 1875 |
## class ---- |
|
| 1876 | ||
| 1877 |
#' `DualEndpointEmax` |
|
| 1878 |
#' |
|
| 1879 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1880 |
#' |
|
| 1881 |
#' [`DualEndpointEmax`] is the class for the dual endpoint model with `Emax` |
|
| 1882 |
#' function for dose-biomarker relationship. |
|
| 1883 |
#' |
|
| 1884 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
| 1885 |
#' relationship \eqn{f(x)} is modelled by a parametric `Emax` function:
|
|
| 1886 |
#' \deqn{f(x) = E0 + [(Emax - E0) * (x/x*)]/[ED50 + (x/x*)],}
|
|
| 1887 |
#' where \eqn{x*} is a reference dose, \eqn{E0} and \eqn{Emax} are the minimum
|
|
| 1888 |
#' and maximum levels for the biomarker, and \eqn{ED50} is the dose achieving
|
|
| 1889 |
#' half of the maximum effect \eqn{0.5 * Emax}.
|
|
| 1890 |
#' All parameters can currently be assigned uniform distributions or be fixed. |
|
| 1891 |
#' |
|
| 1892 |
#' @slot E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1893 |
#' parameters. |
|
| 1894 |
#' @slot Emax (`numeric`)\cr either a fixed number or the two uniform |
|
| 1895 |
#' distribution parameters. |
|
| 1896 |
#' @slot ED50 (`numeric`)\cr either a fixed number or the two uniform |
|
| 1897 |
#' distribution parameters. |
|
| 1898 |
#' @slot ref_dose_emax (`positive_number`)\cr the reference dose \eqn{x*} (note
|
|
| 1899 |
#' that this is different from the `ref_dose` in the inherited [`DualEndpoint`] |
|
| 1900 |
#' model). |
|
| 1901 |
#' |
|
| 1902 |
#' @seealso [`DualEndpoint`], [`DualEndpointRW`], [`DualEndpointBeta`]. |
|
| 1903 |
#' |
|
| 1904 |
#' @aliases DualEndpointEmax |
|
| 1905 |
#' @export |
|
| 1906 |
#' |
|
| 1907 |
.DualEndpointEmax <- setClass( |
|
| 1908 |
Class = "DualEndpointEmax", |
|
| 1909 |
slots = c( |
|
| 1910 |
E0 = "numeric", |
|
| 1911 |
Emax = "numeric", |
|
| 1912 |
ED50 = "numeric", |
|
| 1913 |
ref_dose_emax = "numeric" |
|
| 1914 |
), |
|
| 1915 |
prototype = prototype( |
|
| 1916 |
E0 = c(0, 100), |
|
| 1917 |
Emax = c(0, 500), |
|
| 1918 |
ED50 = c(0, 500), |
|
| 1919 |
ref_dose_emax = positive_number(1), |
|
| 1920 |
use_fixed = c( |
|
| 1921 |
sigma2W = TRUE, |
|
| 1922 |
rho = TRUE, |
|
| 1923 |
E0 = FALSE, |
|
| 1924 |
Emax = FALSE, |
|
| 1925 |
ED50 = FALSE |
|
| 1926 |
) |
|
| 1927 |
), |
|
| 1928 |
contains = "DualEndpoint", |
|
| 1929 |
validity = v_model_dual_endpoint_emax |
|
| 1930 |
) |
|
| 1931 | ||
| 1932 |
## constructor ---- |
|
| 1933 | ||
| 1934 |
#' @rdname DualEndpointEmax-class |
|
| 1935 |
#' |
|
| 1936 |
#' @param E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1937 |
#' parameters. |
|
| 1938 |
#' @param Emax (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1939 |
#' parameters. |
|
| 1940 |
#' @param ED50 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1941 |
#' parameters. |
|
| 1942 |
#' @param ref_dose_emax (`number`)\cr the reference dose \eqn{x*} (strictly
|
|
| 1943 |
#' positive number). Note that this is different from the `ref_dose` in the |
|
| 1944 |
#' inherited [`DualEndpoint`] model). |
|
| 1945 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
| 1946 |
#' |
|
| 1947 |
#' @export |
|
| 1948 |
#' @example examples/Model-class-DualEndpointEmax.R |
|
| 1949 |
#' |
|
| 1950 |
DualEndpointEmax <- function(E0, Emax, ED50, ref_dose_emax = 1, ...) {
|
|
| 1951 | 25x |
assert_numeric(E0, min.len = 1, max.len = 2) |
| 1952 | 25x |
assert_numeric(Emax, min.len = 1, max.len = 2) |
| 1953 | 25x |
assert_numeric(ED50, min.len = 1, max.len = 2) |
| 1954 | 25x |
assert_number(ref_dose_emax) |
| 1955 | ||
| 1956 | 25x |
start <- DualEndpoint(...) |
| 1957 | ||
| 1958 | 25x |
start@sample <- c(start@sample, "betaW") |
| 1959 | 25x |
start@datanames_prior <- c("nGrid", "doseGrid")
|
| 1960 | 25x |
ms <- start@modelspecs |
| 1961 | 25x |
start@modelspecs <- function(from_prior) {
|
| 1962 | 8x |
c(list(ref_dose_emax = ref_dose_emax), ms(from_prior)) |
| 1963 |
} |
|
| 1964 | ||
| 1965 | 25x |
start <- h_model_dual_endpoint_beta( |
| 1966 | 25x |
param = E0, |
| 1967 | 25x |
param_name = "E0", |
| 1968 | 25x |
priormodel = function() {
|
| 1969 | ! |
E0 ~ dunif(E0_low, E0_high) |
| 1970 |
}, |
|
| 1971 | 25x |
de = start |
| 1972 |
) |
|
| 1973 | ||
| 1974 | 25x |
start <- h_model_dual_endpoint_beta( |
| 1975 | 25x |
param = Emax, |
| 1976 | 25x |
param_name = "Emax", |
| 1977 | 25x |
priormodel = function() {
|
| 1978 | ! |
Emax ~ dunif(Emax_low, Emax_high) |
| 1979 |
}, |
|
| 1980 | 25x |
de = start |
| 1981 |
) |
|
| 1982 | ||
| 1983 | 25x |
start <- h_model_dual_endpoint_beta( |
| 1984 | 25x |
param = ED50, |
| 1985 | 25x |
param_name = "ED50", |
| 1986 | 25x |
priormodel = function() {
|
| 1987 | ! |
ED50 ~ dunif(ED50_low, ED50_high) |
| 1988 |
}, |
|
| 1989 | 25x |
de = start |
| 1990 |
) |
|
| 1991 | ||
| 1992 | 25x |
start@priormodel <- h_jags_join_models( |
| 1993 | 25x |
start@priormodel, |
| 1994 | 25x |
function() {
|
| 1995 | ! |
for (i in 1:nGrid) {
|
| 1996 | ! |
stand_dose_emax[i] <- doseGrid[i] / ref_dose_emax |
| 1997 | ! |
betaW[i] <- E0 + |
| 1998 | ! |
(Emax - E0) * stand_dose_emax[i] / (ED50 + stand_dose_emax[i]) |
| 1999 |
} |
|
| 2000 |
} |
|
| 2001 |
) |
|
| 2002 | ||
| 2003 | 25x |
.DualEndpointEmax( |
| 2004 | 25x |
start, |
| 2005 | 25x |
E0 = E0, |
| 2006 | 25x |
Emax = Emax, |
| 2007 | 25x |
ED50 = ED50, |
| 2008 | 25x |
ref_dose_emax = positive_number(ref_dose_emax) |
| 2009 |
) |
|
| 2010 |
} |
|
| 2011 | ||
| 2012 |
## default constructor ---- |
|
| 2013 | ||
| 2014 |
#' @rdname DualEndpointEmax-class |
|
| 2015 |
#' @note Typically, end users will not use the `.DefaultDualEndpointEmax()` function. |
|
| 2016 |
#' @export |
|
| 2017 |
.DefaultDualEndpointEmax <- function() {
|
|
| 2018 | 9x |
DualEndpointEmax( |
| 2019 | 9x |
mean = c(0, 1), |
| 2020 | 9x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 2021 | 9x |
sigma2W = c(a = 0.1, b = 0.1), |
| 2022 | 9x |
rho = c(a = 1, b = 1), |
| 2023 | 9x |
E0 = c(0, 100), |
| 2024 | 9x |
Emax = c(0, 500), |
| 2025 | 9x |
ED50 = c(10, 200), |
| 2026 | 9x |
ref_dose_emax = 1000 |
| 2027 |
) |
|
| 2028 |
} |
|
| 2029 | ||
| 2030 |
# ModelPseudo ---- |
|
| 2031 | ||
| 2032 |
## class ---- |
|
| 2033 | ||
| 2034 |
#' `ModelPseudo` |
|
| 2035 |
#' |
|
| 2036 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2037 |
#' |
|
| 2038 |
#' [`ModelPseudo`] is the parent class for models that express their prior in |
|
| 2039 |
#' the form of pseudo data (as if there is some data before the trial starts). |
|
| 2040 |
#' |
|
| 2041 |
#' @seealso [`GeneralModel`]. |
|
| 2042 |
#' |
|
| 2043 |
#' @aliases ModelPseudo |
|
| 2044 |
#' @export |
|
| 2045 |
#' |
|
| 2046 |
.ModelPseudo <- setClass( |
|
| 2047 |
Class = "ModelPseudo", |
|
| 2048 |
contains = "CrmPackClass" |
|
| 2049 |
) |
|
| 2050 | ||
| 2051 |
## default constructor ---- |
|
| 2052 | ||
| 2053 |
#' @rdname ModelPseudo-class |
|
| 2054 |
#' @note Typically, end users will not use the `.DefaultModelPseudo()` function. |
|
| 2055 |
#' @export |
|
| 2056 |
.DefaultModelPseudo <- function() {
|
|
| 2057 | 1x |
stop(paste0( |
| 2058 | 1x |
"Class ModelPseudo should not be instantiated directly. Please use one of its subclasses instead." |
| 2059 |
)) |
|
| 2060 |
} |
|
| 2061 | ||
| 2062 |
# ModelTox ---- |
|
| 2063 | ||
| 2064 |
## class ---- |
|
| 2065 | ||
| 2066 |
#' `ModelTox` |
|
| 2067 |
#' |
|
| 2068 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2069 |
#' |
|
| 2070 |
#' [`ModelTox`] is the parent class for DLE (dose-limiting events) models using |
|
| 2071 |
#' pseudo data prior. It is dedicated for DLE models or toxicity models that |
|
| 2072 |
#' have their prior specified in the form of pseudo data (as if there is some |
|
| 2073 |
#' data before the trial starts). |
|
| 2074 |
#' |
|
| 2075 |
#' The `data` must obey the convention of the [`Data`] class. This refers to any |
|
| 2076 |
#' observed DLE responses (`y` in [`Data`]), the dose levels (`x` in [`Data`]) |
|
| 2077 |
#' at which these responses are observed, all dose levels considered in the |
|
| 2078 |
#' study (`doseGrid` in [`Data`]), and finally other specifications in [`Data`] |
|
| 2079 |
#' class that can be used to generate prior or posterior modal estimates or |
|
| 2080 |
#' samples estimates for model parameter(s). |
|
| 2081 |
#' If no responses are observed, at least `doseGrid` has to be specified |
|
| 2082 |
#' in `data` for which prior modal estimates or samples can be obtained for |
|
| 2083 |
#' model parameters based on the specified pseudo data. |
|
| 2084 |
#' |
|
| 2085 |
#' @slot data (`Data`)\cr observed data that is used to obtain model parameters |
|
| 2086 |
#' estimates or samples (see details above). |
|
| 2087 |
#' |
|
| 2088 |
#' @seealso [`ModelEff`]. |
|
| 2089 |
#' |
|
| 2090 |
#' @aliases ModelTox |
|
| 2091 |
#' @export |
|
| 2092 |
#' |
|
| 2093 |
.ModelTox <- setClass( |
|
| 2094 |
Class = "ModelTox", |
|
| 2095 |
slots = c( |
|
| 2096 |
data = "Data" |
|
| 2097 |
), |
|
| 2098 |
contains = "ModelPseudo" |
|
| 2099 |
) |
|
| 2100 | ||
| 2101 |
## default constructor ---- |
|
| 2102 | ||
| 2103 |
#' @rdname ModelTox-class |
|
| 2104 |
#' @note Typically, end users will not use the `.DefaultModelTox()` function. |
|
| 2105 |
#' @export |
|
| 2106 |
.DefaultModelTox <- function() {
|
|
| 2107 | 1x |
stop(paste0( |
| 2108 | 1x |
"Class ModelTox should not be instantiated directly. Please use one of its subclasses instead." |
| 2109 |
)) |
|
| 2110 |
} |
|
| 2111 | ||
| 2112 |
# ModelEff ---- |
|
| 2113 | ||
| 2114 |
## class ---- |
|
| 2115 | ||
| 2116 |
#' `ModelEff` |
|
| 2117 |
#' |
|
| 2118 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2119 |
#' |
|
| 2120 |
#' [`ModelEff`] is the parent class for efficacy models using pseudo data prior. |
|
| 2121 |
#' It is dedicated all efficacy models that have their prior specified in the |
|
| 2122 |
#' form of pseudo data (as if there is some data before the trial starts). |
|
| 2123 |
#' |
|
| 2124 |
#' The `data` must obey the convention of the [`DataDual`] class. This refers to |
|
| 2125 |
#' any observed efficacy/biomarker responses (`w` in [`DataDual`]), the dose |
|
| 2126 |
#' levels at which these responses are observed (`x` in [`DataDual`]), all dose |
|
| 2127 |
#' levels considered in the study (`doseGrid` in [`DataDual`]), and finally |
|
| 2128 |
#' other specifications in [`DataDual`] class that can be used to generate prior |
|
| 2129 |
#' or posterior modal estimates or samples estimates for model parameter(s). |
|
| 2130 |
#' If no responses are observed, at least `doseGrid` has to be specified |
|
| 2131 |
#' in `data` for which prior modal estimates or samples can be obtained for |
|
| 2132 |
#' model parameters based on the specified pseudo data. |
|
| 2133 |
#' |
|
| 2134 |
#' @slot data (`DataDual`)\cr observed data that is used to obtain model |
|
| 2135 |
#' parameters estimates or samples (see details above). |
|
| 2136 |
#' |
|
| 2137 |
#' @seealso [`ModelTox`]. |
|
| 2138 |
#' |
|
| 2139 |
#' @aliases ModelEff |
|
| 2140 |
#' @export |
|
| 2141 |
#' |
|
| 2142 |
.ModelEff <- setClass( |
|
| 2143 |
Class = "ModelEff", |
|
| 2144 |
slots = c( |
|
| 2145 |
data = "DataDual" |
|
| 2146 |
), |
|
| 2147 |
contains = "ModelPseudo" |
|
| 2148 |
) |
|
| 2149 | ||
| 2150 |
## default constructor ---- |
|
| 2151 | ||
| 2152 |
#' @rdname ModelEff-class |
|
| 2153 |
#' @note Typically, end users will not use the `.DefaultModelEff()` function. |
|
| 2154 |
#' @export |
|
| 2155 |
.DefaultModelEff <- function() {
|
|
| 2156 | 1x |
stop(paste0( |
| 2157 | 1x |
"Class ModelEff should not be instantiated directly. Please use one of its subclasses instead." |
| 2158 |
)) |
|
| 2159 |
} |
|
| 2160 | ||
| 2161 |
# LogisticIndepBeta ---- |
|
| 2162 | ||
| 2163 |
## class ---- |
|
| 2164 | ||
| 2165 |
#' `LogisticIndepBeta` |
|
| 2166 |
#' |
|
| 2167 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2168 |
#' |
|
| 2169 |
#' [`LogisticIndepBeta`] is the class for the two-parameters logistic regression |
|
| 2170 |
#' dose-limiting events (DLE) model with prior expressed in form of pseudo data. |
|
| 2171 |
#' This model describes the relationship between the binary DLE responses |
|
| 2172 |
#' and the dose levels. More specifically, it represents the relationship of the |
|
| 2173 |
#' probabilities of the occurrence of a DLE for corresponding dose levels in log |
|
| 2174 |
#' scale. This model is specified as |
|
| 2175 |
#' \deqn{p(x) = exp(phi1 + phi2 * log(x)) / (1 + exp(phi1 + phi2 * log(x)))}
|
|
| 2176 |
#' where \eqn{p(x)} is the probability of the occurrence of a DLE at dose \eqn{x}.
|
|
| 2177 |
#' The two parameters of this model are the intercept \eqn{phi1} and the slope
|
|
| 2178 |
#' \eqn{phi2}. The `LogisticIndepBeta` inherits all slots from [`ModelTox`] class.
|
|
| 2179 |
#' |
|
| 2180 |
#' In the context of pseudo data, the following three arguments are used, |
|
| 2181 |
#' `binDLE`, `DLEdose` and `DLEweights`. The `DLEdose` represents fixed dose |
|
| 2182 |
#' levels at which the pseudo DLE responses `binDLE` are observed. `DLEweights` |
|
| 2183 |
#' represents total number of subjects treated per each dose level in `DLEdose`. |
|
| 2184 |
#' The `binDLE` represents the number of subjects observed with DLE per each |
|
| 2185 |
#' dose level in `DLEdose`. Hence, all these three vectors must be of the same |
|
| 2186 |
#' length and the order of the elements in any of the vectors `binDLE`, |
|
| 2187 |
#' `DLEdose` and `DLEweights` must be kept, so that an element of a given vector |
|
| 2188 |
#' corresponds to the elements of the remaining two vectors (see the example for |
|
| 2189 |
#' more insight). |
|
| 2190 |
#' Finally, since at least two DLE pseudo responses are needed to |
|
| 2191 |
#' obtain prior modal estimates (same as the maximum likelihood estimates) for |
|
| 2192 |
#' the model parameters, the `binDLE`, `DLEdose` and `DLEweights` must all be |
|
| 2193 |
#' vectors of at least length 2. |
|
| 2194 |
#' |
|
| 2195 |
#' @details The pseudo data can be interpreted as if we obtain some observations |
|
| 2196 |
#' before the trial starts. It can be used to express our prior, i.e. the |
|
| 2197 |
#' initial beliefs for the model parameters. The pseudo data is expressed in |
|
| 2198 |
#' the following way. First, fix at least two dose levels, then ask for experts' |
|
| 2199 |
#' opinion on how many subjects are to be treated at each of these dose levels |
|
| 2200 |
#' and on the number of subjects observed with a DLE. At each dose level, the |
|
| 2201 |
#' number of subjects observed with a DLE, divided by the total number of |
|
| 2202 |
#' subjects treated, is the probability of the occurrence of a DLE at that |
|
| 2203 |
#' particular dose level. The probabilities of the occurrence of a DLE based |
|
| 2204 |
#' on this pseudo data are independent and they follow Beta distributions. |
|
| 2205 |
#' Therefore, the joint prior probability density function of all these |
|
| 2206 |
#' probabilities can be obtained. Hence, by a change of variable, the joint |
|
| 2207 |
#' prior probability density function of the two parameters in this model can |
|
| 2208 |
#' also be obtained. In addition, a conjugate joint prior density function of |
|
| 2209 |
#' the two parameters in the model is used. For details about the form of all |
|
| 2210 |
#' these joint prior and posterior probability density functions, please refer |
|
| 2211 |
#' to \insertCite{WhiteheadWilliamson1998;textual}{crmPack}.
|
|
| 2212 |
#' |
|
| 2213 |
#' @slot binDLE (`numeric`)\cr a vector of total numbers of DLE responses. |
|
| 2214 |
#' It must be at least of length 2 and the order of its elements must |
|
| 2215 |
#' correspond to values specified in `DLEdose` and `DLEweights`. |
|
| 2216 |
#' @slot DLEdose (`numeric`)\cr a vector of the dose levels corresponding to |
|
| 2217 |
#' It must be at least of length 2 and the order of its elements must |
|
| 2218 |
#' correspond to values specified in `binDLE` and `DLEweights`. |
|
| 2219 |
#' @slot DLEweights (`integer`)\cr total number of subjects treated at each of |
|
| 2220 |
#' the pseudo dose level `DLEdose`. |
|
| 2221 |
#' It must be at least of length 2 and the order of its elements must |
|
| 2222 |
#' correspond to values specified in `binDLE` and `DLEdose`. |
|
| 2223 |
#' @slot phi1 (`number`)\cr the intercept of the model. This slot is used in |
|
| 2224 |
#' output to display the resulting prior or posterior modal estimate of the |
|
| 2225 |
#' intercept obtained based on the pseudo data and (if any) observed data/responses. |
|
| 2226 |
#' @slot phi2 (`number`)\cr the slope of the model. This slot is used in output |
|
| 2227 |
#' to display the resulting prior or posterior modal estimate of the slope |
|
| 2228 |
#' obtained based on the pseudo data and (if any) the observed data/responses. |
|
| 2229 |
#' @slot Pcov (`matrix`)\cr refers to the 2x2 covariance matrix of the intercept |
|
| 2230 |
#' (\eqn{phi1}) and the slope parameters (\eqn{phi2}) of the model.
|
|
| 2231 |
#' This is used in output to display the resulting prior and posterior |
|
| 2232 |
#' covariance matrix of \eqn{phi1} and \eqn{phi2} obtained, based on the
|
|
| 2233 |
#' pseudo data and (if any) the observed data and responses. This slot is |
|
| 2234 |
#' needed for internal purposes. |
|
| 2235 |
#' |
|
| 2236 |
#' @aliases LogisticIndepBeta |
|
| 2237 |
#' @export |
|
| 2238 |
#' @references |
|
| 2239 |
#' \insertAllCited{}
|
|
| 2240 |
#' |
|
| 2241 |
.LogisticIndepBeta <- setClass( |
|
| 2242 |
Class = "LogisticIndepBeta", |
|
| 2243 |
slots = c( |
|
| 2244 |
binDLE = "numeric", |
|
| 2245 |
DLEdose = "numeric", |
|
| 2246 |
DLEweights = "integer", |
|
| 2247 |
phi1 = "numeric", |
|
| 2248 |
phi2 = "numeric", |
|
| 2249 |
Pcov = "matrix" |
|
| 2250 |
), |
|
| 2251 |
prototype = prototype( |
|
| 2252 |
binDLE = c(0, 0), |
|
| 2253 |
DLEdose = c(1, 1), |
|
| 2254 |
DLEweights = c(1L, 1L) |
|
| 2255 |
), |
|
| 2256 |
contains = "ModelTox", |
|
| 2257 |
validity = v_model_logistic_indep_beta |
|
| 2258 |
) |
|
| 2259 | ||
| 2260 |
## constructor ---- |
|
| 2261 | ||
| 2262 |
#' @rdname LogisticIndepBeta-class |
|
| 2263 |
#' |
|
| 2264 |
#' @param binDLE (`numeric`)\cr the number of subjects observed with a DLE, the |
|
| 2265 |
#' pseudo DLE responses, depending on dose levels `DLEdose`. |
|
| 2266 |
#' Elements of `binDLE` must correspond to the elements of `DLEdose` and |
|
| 2267 |
#' `DLEweights`. |
|
| 2268 |
#' @param DLEdose (`numeric`)\cr dose levels for the pseudo DLE responses. |
|
| 2269 |
#' Elements of `DLEdose` must correspond to the elements of `binDLE` and |
|
| 2270 |
#' `DLEweights`. |
|
| 2271 |
#' @param DLEweights (`numeric`)\cr the total number of subjects treated at each |
|
| 2272 |
#' of the dose levels `DLEdose`, pseudo weights. |
|
| 2273 |
#' Elements of `DLEweights` must correspond to the elements of `binDLE` and |
|
| 2274 |
#' `DLEdose`. |
|
| 2275 |
#' @param data (`Data`)\cr the input data to update estimates of the model |
|
| 2276 |
#' parameters. |
|
| 2277 |
#' |
|
| 2278 |
#' @export |
|
| 2279 |
#' @example examples/Model-class-LogisticIndepBeta.R |
|
| 2280 |
#' |
|
| 2281 |
LogisticIndepBeta <- function(binDLE, DLEdose, DLEweights, data) {
|
|
| 2282 | 272x |
assert_numeric(binDLE) |
| 2283 | 272x |
assert_numeric(DLEdose) |
| 2284 | 272x |
assert_integerish(DLEweights, lower = 0, any.missing = FALSE) |
| 2285 | 272x |
assert_class(data, "Data") |
| 2286 | ||
| 2287 |
# Combine pseudo and observed data. It can also happen that data@nObs == 0. |
|
| 2288 | 272x |
y <- c(binDLE, data@y) |
| 2289 | 272x |
x <- c(DLEdose, data@x) |
| 2290 | 272x |
w <- c(DLEweights, rep(1, data@nObs)) |
| 2291 | ||
| 2292 | 272x |
fit_dle <- suppressWarnings( |
| 2293 | 272x |
glm(y / w ~ log(x), family = binomial(link = "logit"), weights = w) |
| 2294 |
) |
|
| 2295 | 272x |
phi1 <- coef(fit_dle)[["(Intercept)"]] |
| 2296 | 272x |
phi2 <- coef(fit_dle)[["log(x)"]] |
| 2297 | 272x |
Pcov <- vcov(fit_dle) |
| 2298 | ||
| 2299 | 272x |
.LogisticIndepBeta( |
| 2300 | 272x |
binDLE = binDLE, |
| 2301 | 272x |
DLEdose = DLEdose, |
| 2302 | 272x |
DLEweights = as.integer(DLEweights), |
| 2303 | 272x |
phi1 = phi1, |
| 2304 | 272x |
phi2 = phi2, |
| 2305 | 272x |
Pcov = Pcov, |
| 2306 | 272x |
data = data |
| 2307 |
) |
|
| 2308 |
} |
|
| 2309 | ||
| 2310 |
## default constructor ---- |
|
| 2311 | ||
| 2312 |
#' @rdname LogisticIndepBeta-class |
|
| 2313 |
#' @note Typically, end users will not use the `.DefaultLogisticIndepBeta()` function. |
|
| 2314 |
#' @export |
|
| 2315 |
.DefaultLogisticIndepBeta <- function() {
|
|
| 2316 | 7x |
my_model <- LogisticIndepBeta( |
| 2317 | 7x |
binDLE = c(1.05, 1.8), |
| 2318 | 7x |
DLEweights = c(3L, 3L), |
| 2319 | 7x |
DLEdose = c(25, 300), |
| 2320 | 7x |
data = Data(doseGrid = seq(25, 300, 25)) |
| 2321 |
) |
|
| 2322 |
} |
|
| 2323 | ||
| 2324 | ||
| 2325 |
# Effloglog ---- |
|
| 2326 | ||
| 2327 |
## class ---- |
|
| 2328 | ||
| 2329 |
#' `Effloglog` |
|
| 2330 |
#' |
|
| 2331 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2332 |
#' |
|
| 2333 |
#' [`Effloglog`] is the class for the linear log-log efficacy model using pseudo |
|
| 2334 |
#' data prior. It describes the relationship between continuous efficacy |
|
| 2335 |
#' responses and corresponding dose levels in log-log scale. This efficacy |
|
| 2336 |
#' log-log model is given as |
|
| 2337 |
#' \deqn{y_i = theta1 + theta2 * log(log(x_i)) + epsilon_i,}
|
|
| 2338 |
#' where \eqn{y_i} is the efficacy response for subject \eqn{i}, \eqn{x_i} is
|
|
| 2339 |
#' the dose level treated for subject \eqn{i} and \eqn{epsilon_i} is the random
|
|
| 2340 |
#' error term of efficacy model at subject \eqn{i}. The error term
|
|
| 2341 |
#' \eqn{epsilon_i} is a random variable that follows normal distribution with
|
|
| 2342 |
#' mean \eqn{0} and variance \eqn{nu^{-1}}, which is assumed to be the
|
|
| 2343 |
#' same for all subjects. |
|
| 2344 |
#' There are three parameters in this model, the intercept \eqn{theta1}, the
|
|
| 2345 |
#' slope \eqn{theta2} and the precision \eqn{nu} of the efficacy responses, also
|
|
| 2346 |
#' known as the inverse of the variance of the pseudo efficacy responses. It can |
|
| 2347 |
#' be a fixed constant or having a gamma distribution. Therefore, a single scalar |
|
| 2348 |
#' value or a vector with two positive numbers values must be specified for `nu` |
|
| 2349 |
#' slot. If there are some observed efficacy responses available, in the output, |
|
| 2350 |
#' `nu` will display the updated value of the precision or the updated values |
|
| 2351 |
#' for the parameters of the gamma distribution. |
|
| 2352 |
#' The `Effloglog` inherits all slots from [`ModelEff`] class. |
|
| 2353 |
#' |
|
| 2354 |
#' @details The prior of this model is specified in form of pseudo data. First, |
|
| 2355 |
#' at least two dose levels are fixed. Then, using e.g. experts' opinion, the |
|
| 2356 |
#' efficacy values that correspond to these dose levels can be obtained, |
|
| 2357 |
#' The `eff` and `eff_dose` arguments represent the prior in form of the pseudo |
|
| 2358 |
#' data. The `eff` represents the pseudo efficacy values. The `eff_dose` |
|
| 2359 |
#' represents the dose levels at which these pseudo efficacy values are |
|
| 2360 |
#' observed. Hence, the positions of the elements specified in `eff` and |
|
| 2361 |
#' `eff_dose` must correspond to each other between these vectors. |
|
| 2362 |
#' Since at least 2 pseudo efficacy values are needed to obtain modal |
|
| 2363 |
#' estimates of the intercept and slope parameters, both `eff` and `eff_dose` |
|
| 2364 |
#' must be vectors of length at least 2. |
|
| 2365 |
#' |
|
| 2366 |
#' The joint prior distribution of the intercept \eqn{theta1} and the slope
|
|
| 2367 |
#' \eqn{theta2} of this model follows bivariate normal distribution with mean
|
|
| 2368 |
#' \eqn{mu} and covariance matrix \eqn{(nu * Q)^{-1}}.
|
|
| 2369 |
#' The mean \eqn{mu} is a \eqn{2 x 1} column vector that contains the prior
|
|
| 2370 |
#' modal estimates of the intercept and the slope. |
|
| 2371 |
#' Scalar \eqn{nu} is the precision of the pseudo efficacy responses and
|
|
| 2372 |
#' \eqn{Q} is the prior or posterior (given that observed, no DLT data is
|
|
| 2373 |
#' available) precision matrix. |
|
| 2374 |
#' It is specified as \eqn{Q = X0^T * X0 + X^T * X}, where \eqn{X0} is a
|
|
| 2375 |
#' design matrix that is based on pseudo dose levels only, and \eqn{X} is a
|
|
| 2376 |
#' design matrix that is based on dose levels corresponding to the no DLT |
|
| 2377 |
#' efficacy responses observed only (if any). |
|
| 2378 |
#' Hence, the \eqn{X0} (or \eqn{X}) will be of size \eqn{r x 2}, if
|
|
| 2379 |
#' there are \eqn{r >= 2} pseudo efficacy responses specified (or
|
|
| 2380 |
#' if there are \eqn{r} no DLT efficacy responses observed in the `data`).
|
|
| 2381 |
#' |
|
| 2382 |
#' @slot eff (`numeric`)\cr the pseudo efficacy responses. Each element here |
|
| 2383 |
#' must represent responses treated based on one subject. |
|
| 2384 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
| 2385 |
#' correspond to values specified in `eff_dose`. |
|
| 2386 |
#' @slot eff_dose (`numeric`)\cr the pseudo efficacy dose levels at which the |
|
| 2387 |
#' pseudo efficacy responses are observed. |
|
| 2388 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
| 2389 |
#' correspond to values specified in `eff`. |
|
| 2390 |
#' @slot nu (`numeric`)\cr parameter of the prior precision of pseudo efficacy |
|
| 2391 |
#' responses. This is either a fixed value or a named vector with two positive |
|
| 2392 |
#' numbers, the shape (`a`), and the rate (`b`) parameters for the gamma |
|
| 2393 |
#' distribution. |
|
| 2394 |
#' @slot use_fixed (`flag`)\cr indicates whether `nu` specified is a fixed value |
|
| 2395 |
#' or a vector with two parameters for gamma distribution. This slot is for |
|
| 2396 |
#' internal purposes only and must not be used by the user. |
|
| 2397 |
#' @slot theta1 (`number`)\cr the intercept in this efficacy log-log model. This |
|
| 2398 |
#' slot is used in output to display the resulting prior or posterior modal |
|
| 2399 |
#' estimates obtained based on the pseudo and observed (if any) data. |
|
| 2400 |
#' @slot theta2 (`number`)\cr the slope in this efficacy log-log model. This |
|
| 2401 |
#' slot is used in output to display the resulting prior or posterior modal |
|
| 2402 |
#' estimates obtained based on the pseudo and observed (if any) data. |
|
| 2403 |
#' @slot Pcov (`matrix`)\cr refers to the \eqn{2 x 2} covariance matrix of the
|
|
| 2404 |
#' estimators of the intercept \eqn{theta1} and the slope \eqn{theta2}
|
|
| 2405 |
#' parameters in this model. |
|
| 2406 |
#' This is used in output to display the resulting prior and posterior |
|
| 2407 |
#' covariance matrix of \eqn{theta1} and \eqn{theta2} obtained, based on the
|
|
| 2408 |
#' pseudo and observed (if any) data. This slot is needed for internal purposes. |
|
| 2409 |
#' @slot X (`matrix`)\cr is the design matrix that is based on either the pseudo |
|
| 2410 |
#' dose levels or observed dose levels (without DLT). This is used |
|
| 2411 |
#' in the output to display the design matrix for the pseudo or the observed |
|
| 2412 |
#' efficacy responses. |
|
| 2413 |
#' @slot Y (`numeric`)\cr is a vector that either contains the pseudo efficacy |
|
| 2414 |
#' responses or observed efficacy responses (without DLT). |
|
| 2415 |
#' @slot mu (`numeric`)\cr a vector of the prior or the posterior modal estimates |
|
| 2416 |
#' of the intercept (\eqn{theta1}) and the slope (\eqn{theta2}).
|
|
| 2417 |
#' This slot is used in output to display as the mean of the prior or posterior |
|
| 2418 |
#' bivariate normal distribution for \eqn{theta1} and \eqn{theta2}.
|
|
| 2419 |
#' @slot Q (`matrix`)\cr is the prior or posterior (given that observed, no DLT |
|
| 2420 |
#' data is available) precision matrix. It is specified as |
|
| 2421 |
#' \eqn{Q = X0^T * X0 + X^T * X}, where \eqn{X0} is a design matrix that is
|
|
| 2422 |
#' based on pseudo dose levels only, and \eqn{X} is a design matrix that is
|
|
| 2423 |
#' based on dose levels corresponding to the observed, no DLT efficacy values |
|
| 2424 |
#' only (if any). |
|
| 2425 |
#' @slot const (`number`)\cr a non-negative number (default to 0), leading to the |
|
| 2426 |
#' model form described above. In general, the model has the form |
|
| 2427 |
#' \eqn{y_i = theta1 + theta2 * log(log(x_i + const)) + epsilon_i}, such that
|
|
| 2428 |
#' dose levels greater than \eqn{1 - const} can be considered as described in
|
|
| 2429 |
#' \insertCite{YeungWhiteheadReignerBeyerDiackJaki2015;textual}{crmPack}.
|
|
| 2430 |
#' |
|
| 2431 |
#' @aliases Effloglog |
|
| 2432 |
#' @export |
|
| 2433 |
#' @references |
|
| 2434 |
#' \insertAllCited{}
|
|
| 2435 |
#' |
|
| 2436 |
.Effloglog <- setClass( |
|
| 2437 |
Class = "Effloglog", |
|
| 2438 |
slots = c( |
|
| 2439 |
eff = "numeric", |
|
| 2440 |
eff_dose = "numeric", |
|
| 2441 |
nu = "numeric", |
|
| 2442 |
use_fixed = "logical", |
|
| 2443 |
theta1 = "numeric", |
|
| 2444 |
theta2 = "numeric", |
|
| 2445 |
Pcov = "matrix", |
|
| 2446 |
X = "matrix", |
|
| 2447 |
Y = "numeric", |
|
| 2448 |
mu = "numeric", |
|
| 2449 |
Q = "matrix", |
|
| 2450 |
const = "numeric" |
|
| 2451 |
), |
|
| 2452 |
prototype = prototype( |
|
| 2453 |
eff = c(0, 0), |
|
| 2454 |
eff_dose = c(1, 1), |
|
| 2455 |
nu = 1 / 0.025, |
|
| 2456 |
use_fixed = TRUE, |
|
| 2457 |
const = 0 |
|
| 2458 |
), |
|
| 2459 |
contains = "ModelEff", |
|
| 2460 |
validity = v_model_eff_log_log |
|
| 2461 |
) |
|
| 2462 | ||
| 2463 |
## constructor ---- |
|
| 2464 | ||
| 2465 |
#' @rdname Effloglog-class |
|
| 2466 |
#' |
|
| 2467 |
#' @param eff (`numeric`)\cr the pseudo efficacy responses. |
|
| 2468 |
#' Elements of `eff` must correspond to the elements of `eff_dose`. |
|
| 2469 |
#' @param eff_dose (`numeric`)\cr dose levels that correspond to pseudo efficacy |
|
| 2470 |
#' responses in `eff`. |
|
| 2471 |
#' @param nu (`numeric`)\cr the precision (inverse of the variance) of the |
|
| 2472 |
#' efficacy responses. This is either a fixed value or a named vector with two |
|
| 2473 |
#' positive numbers, the shape (`a`), and the rate (`b`) parameters for the |
|
| 2474 |
#' gamma distribution. |
|
| 2475 |
#' @param data (`DataDual`)\cr observed data to update estimates of the model |
|
| 2476 |
#' parameters. |
|
| 2477 |
#' @param const (`number`)\cr the constant value added to the dose level when |
|
| 2478 |
#' the dose level value is less than or equal to 1 and a special form of the |
|
| 2479 |
#' linear log-log has to be applied |
|
| 2480 |
#' \insertCite{YeungWhiteheadReignerBeyerDiackJaki2015}{crmPack}.
|
|
| 2481 |
#' |
|
| 2482 |
#' @export |
|
| 2483 |
#' @example examples/Model-class-Effloglog.R |
|
| 2484 |
#' |
|
| 2485 |
Effloglog <- function(eff, eff_dose, nu, data, const = 0) {
|
|
| 2486 | 166x |
assert_numeric(eff) |
| 2487 | 166x |
assert_numeric(eff_dose, len = length(eff)) |
| 2488 | 166x |
assert_numeric(nu, min.len = 1, max.len = 2) |
| 2489 | 166x |
assert_class(data, "Data") |
| 2490 | 166x |
assert_number(const, finite = TRUE) |
| 2491 | ||
| 2492 | 166x |
use_fixed <- length(nu) == 1L |
| 2493 | ||
| 2494 | 166x |
eff_dose <- eff_dose + const |
| 2495 |
# Get observed efficacy data without DLT (if any). |
|
| 2496 | 166x |
eff_obsrv_w_x <- getEff(data, no_dlt = TRUE) |
| 2497 | 166x |
eff_obsrv <- eff_obsrv_w_x$w_no_dlt |
| 2498 | 166x |
eff_obsrv_dose <- eff_obsrv_w_x$x_no_dlt + const |
| 2499 | ||
| 2500 |
# Fit pseudo and observed (if any) efficacy. |
|
| 2501 | 166x |
w <- c(eff, eff_obsrv) |
| 2502 | 166x |
x <- c(eff_dose, eff_obsrv_dose) |
| 2503 | 166x |
fit_eff <- suppressWarnings(lm(w ~ log(log(x)))) |
| 2504 | 166x |
X <- model.matrix(fit_eff) |
| 2505 | 166x |
Y <- w |
| 2506 | 166x |
mu <- coef(fit_eff) # This is [theta1, theta2]^T est. |
| 2507 | 166x |
Q <- crossprod(X) |
| 2508 | 166x |
Pcov <- vcov(fit_eff) |
| 2509 | ||
| 2510 | 166x |
nobs_no_dlt <- length(eff_obsrv) |
| 2511 | 166x |
if (nobs_no_dlt > 0L) {
|
| 2512 |
# Observed data available. |
|
| 2513 |
# Set X, Y to observed data only. |
|
| 2514 | 116x |
X <- model.matrix(fit_eff)[-seq_along(eff), ] |
| 2515 | 116x |
Y <- eff_obsrv |
| 2516 | ||
| 2517 | 116x |
fit_eff0 <- lm(eff ~ log(log(eff_dose))) # Pseudo only. |
| 2518 | 116x |
X0 <- model.matrix(fit_eff0) |
| 2519 | 116x |
mu0 <- coef(fit_eff0) |
| 2520 | 116x |
Q0 <- crossprod(X0) |
| 2521 |
# Note that mu = (Q0 + X^T * X)^{-1} * (Q0 * mu0 + X^T * X * (X^T * X)^{-1} X^T * Y),
|
|
| 2522 |
# given that (X^T * X) is invertible and X, Y, mu0, Q0, are specified in this else block. |
|
| 2523 | 116x |
if (!use_fixed) {
|
| 2524 | 116x |
nu["a"] <- nu["a"] + (nobs_no_dlt) / 2 |
| 2525 | 116x |
nu["b"] <- nu["b"] + |
| 2526 | 116x |
(crossprod(Y) + t(mu0) %*% Q0 %*% mu0 - t(mu) %*% Q %*% mu) / 2 |
| 2527 |
} |
|
| 2528 |
} |
|
| 2529 | ||
| 2530 | 166x |
.Effloglog( |
| 2531 | 166x |
eff = eff, |
| 2532 | 166x |
eff_dose = eff_dose, |
| 2533 | 166x |
nu = nu, |
| 2534 | 166x |
use_fixed = use_fixed, |
| 2535 | 166x |
theta1 = mu[["(Intercept)"]], |
| 2536 | 166x |
theta2 = mu[["log(log(x))"]], |
| 2537 | 166x |
Pcov = Pcov, |
| 2538 | 166x |
X = X, |
| 2539 | 166x |
Y = Y, |
| 2540 | 166x |
mu = as.vector(mu), |
| 2541 | 166x |
Q = Q, |
| 2542 | 166x |
const = const, |
| 2543 | 166x |
data = data |
| 2544 |
) |
|
| 2545 |
} |
|
| 2546 | ||
| 2547 |
## default constructor ---- |
|
| 2548 | ||
| 2549 |
#' @rdname Effloglog-class |
|
| 2550 |
#' @note Typically, end users will not use the `.DefaultEffloglog()` function. |
|
| 2551 |
#' @export |
|
| 2552 |
.DefaultEffloglog <- function() {
|
|
| 2553 | 7x |
emptydata <- DataDual(doseGrid = seq(25, 300, 25), placebo = FALSE) |
| 2554 | ||
| 2555 | 7x |
my_data <- DataDual( |
| 2556 | 7x |
x = c(25, 50, 50, 75, 100, 100, 225, 300), |
| 2557 | 7x |
y = c(0, 0, 0, 0, 1, 1, 1, 1), |
| 2558 | 7x |
w = c(0.31, 0.42, 0.59, 0.45, 0.6, 0.7, 0.6, 0.52), |
| 2559 | 7x |
doseGrid = emptydata@doseGrid, |
| 2560 | 7x |
ID = 1L:8L, |
| 2561 | 7x |
cohort = as.integer(c(1, 2, 2, 3, 4, 4, 5, 6)) |
| 2562 |
) |
|
| 2563 | ||
| 2564 | 7x |
Effloglog( |
| 2565 | 7x |
eff = c(1.223, 2.513), |
| 2566 | 7x |
eff_dose = c(25, 300), |
| 2567 | 7x |
nu = c(a = 1, b = 0.025), |
| 2568 | 7x |
data = my_data |
| 2569 |
) |
|
| 2570 |
} |
|
| 2571 | ||
| 2572 |
# EffFlexi ---- |
|
| 2573 | ||
| 2574 |
## class ---- |
|
| 2575 | ||
| 2576 |
#' `EffFlexi` |
|
| 2577 |
#' |
|
| 2578 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2579 |
#' |
|
| 2580 |
#' [`EffFlexi`] is the class for the efficacy model in flexible form of prior |
|
| 2581 |
#' expressed in form of pseudo data. In this class, a flexible form is used to |
|
| 2582 |
#' describe the relationship between the efficacy responses and the dose levels |
|
| 2583 |
#' and it is specified as |
|
| 2584 |
#' \deqn{(W | betaW, sigma2W) ~ Normal(X * betaW, sigma2W * I),}
|
|
| 2585 |
#' where \eqn{W} is a vector of the efficacy responses, \eqn{betaW} is a column
|
|
| 2586 |
#' vector of the mean efficacy responses for all dose levels, and \eqn{X} is
|
|
| 2587 |
#' the design matrix with entries \eqn{I_i,j} that are equal to 1 if subject
|
|
| 2588 |
#' \eqn{i} is allocated to dose \eqn{j}, and \eqn{0} otherwise. The \eqn{sigma2W}
|
|
| 2589 |
#' is the variance of the efficacy responses which can be either a fixed number |
|
| 2590 |
#' or a number from an inverse gamma distribution. |
|
| 2591 |
#' This flexible form aims to capture different shapes of the dose-efficacy |
|
| 2592 |
#' curve. In addition, the first (RW1) or second order (RW2) random walk model |
|
| 2593 |
#' can be used for smoothing data. That is the random walk model is used to model |
|
| 2594 |
#' the first or the second order differences of the mean efficacy responses to |
|
| 2595 |
#' its neighboring dose levels of their mean efficacy responses. |
|
| 2596 |
#' |
|
| 2597 |
#' The RW1 model is given as |
|
| 2598 |
#' \deqn{betaW_j - betaW_j-1) ~ Normal(0, sigma2betaW),}
|
|
| 2599 |
#' and for RW2 as |
|
| 2600 |
#' \deqn{betaW_j-2 - 2 * betaW_j-1 + beta_j ~ Normal(0, sigma2betaW),}
|
|
| 2601 |
#' where \eqn{betaW_j} is the vector of mean efficacy responses at dose j, and
|
|
| 2602 |
#' the \eqn{sigma2betaW} is the prior variance which can be either a fixed
|
|
| 2603 |
#' number or a number from an inverse gamma distribution. |
|
| 2604 |
#' |
|
| 2605 |
#' The `eff` and `eff_dose` are the pseudo efficacy responses and dose levels at |
|
| 2606 |
#' which these pseudo efficacy responses are observed. Both, `eff` and `eff_dose` |
|
| 2607 |
#' must be vectors of length at least 2. The positions of the elements specified |
|
| 2608 |
#' in `eff` and `eff_dose` must correspond to each other between these vectors. |
|
| 2609 |
#' |
|
| 2610 |
#' @details This model will output the updated value or the updated values of the |
|
| 2611 |
#' parameters of the inverse gamma distributions for \eqn{sigma2W} and
|
|
| 2612 |
#' \eqn{sigma2betaW}. The `EffFlexi` inherits all slots from [`ModelEff`] class.
|
|
| 2613 |
#' |
|
| 2614 |
#' @slot eff (`numeric`)\cr the pseudo efficacy responses. Each element here |
|
| 2615 |
#' must represent responses treated based on one subject. |
|
| 2616 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
| 2617 |
#' correspond to values specified in `eff_dose`. |
|
| 2618 |
#' @slot eff_dose (`numeric`)\cr the pseudo efficacy dose levels at which the |
|
| 2619 |
#' pseudo efficacy responses are observed. |
|
| 2620 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
| 2621 |
#' correspond to values specified in `eff`. |
|
| 2622 |
#' @slot sigma2W (`numeric`)\cr the prior variance of the flexible efficacy form. |
|
| 2623 |
#' This is either a fixed value or a named vector with two positive numbers, |
|
| 2624 |
#' the shape (`a`), and the rate (`b`) parameters for the gamma distribution. |
|
| 2625 |
#' @slot sigma2betaW (`numeric`)\cr the prior variance of the random walk model |
|
| 2626 |
#' for the mean efficacy responses. This is either a fixed value or a named |
|
| 2627 |
#' vector with two positive numbers, the shape (`a`), and the rate (`b`) |
|
| 2628 |
#' parameters for the gamma distribution. |
|
| 2629 |
#' @slot use_fixed (`logical`)\cr indicates whether a fixed value for |
|
| 2630 |
#' `sigma2W` and `sigma2betaW` (for each parameter separately) is used or not. |
|
| 2631 |
#' This slot is needed for internal purposes and must not be touched by the user. |
|
| 2632 |
#' @slot rw1 (`flag`)\cr used for smoothing data for this efficacy model. If it |
|
| 2633 |
#' is `TRUE`, the first-order random walk model is used for the mean efficacy |
|
| 2634 |
#' responses. Otherwise, the random walk of second order is used. |
|
| 2635 |
#' @slot X (`matrix`)\cr the design matrix for the efficacy responses. It is |
|
| 2636 |
#' based on both the pseudo and the observed efficacy responses. |
|
| 2637 |
#' @slot RW (`matrix`)\cr the difference matrix for the random walk model. This |
|
| 2638 |
#' slot is needed for internal purposes and must not be used by the user. |
|
| 2639 |
#' @slot RW_rank (`integer`)\cr is the rank of the difference matrix. This |
|
| 2640 |
#' slot is needed for internal purposes and must not be used by the user. |
|
| 2641 |
#' |
|
| 2642 |
#' @aliases EffFlexi |
|
| 2643 |
#' @export |
|
| 2644 |
#' |
|
| 2645 |
.EffFlexi <- setClass( |
|
| 2646 |
Class = "EffFlexi", |
|
| 2647 |
slots = c( |
|
| 2648 |
eff = "numeric", |
|
| 2649 |
eff_dose = "numeric", |
|
| 2650 |
sigma2W = "numeric", |
|
| 2651 |
sigma2betaW = "numeric", |
|
| 2652 |
use_fixed = "logical", |
|
| 2653 |
rw1 = "logical", |
|
| 2654 |
X = "matrix", |
|
| 2655 |
RW = "matrix", |
|
| 2656 |
RW_rank = "integer" |
|
| 2657 |
), |
|
| 2658 |
prototype = prototype( |
|
| 2659 |
eff = c(0, 0), |
|
| 2660 |
eff_dose = c(1, 1), |
|
| 2661 |
sigma2W = 0.025, |
|
| 2662 |
sigma2betaW = 1, |
|
| 2663 |
rw1 = TRUE, |
|
| 2664 |
use_fixed = c(sigma2W = TRUE, sigma2betaW = TRUE) |
|
| 2665 |
), |
|
| 2666 |
contains = "ModelEff", |
|
| 2667 |
validity = v_model_eff_flexi |
|
| 2668 |
) |
|
| 2669 | ||
| 2670 |
## constructor ---- |
|
| 2671 | ||
| 2672 |
#' @rdname EffFlexi-class |
|
| 2673 |
#' |
|
| 2674 |
#' @param eff (`numeric`)\cr the pseudo efficacy responses. |
|
| 2675 |
#' Elements of `eff` must correspond to the elements of `eff_dose`. |
|
| 2676 |
#' @param eff_dose (`numeric`)\cr dose levels that correspond to pseudo efficacy |
|
| 2677 |
#' responses in `eff`. |
|
| 2678 |
#' @param sigma2W (`numeric`)\cr the prior variance of the efficacy responses. |
|
| 2679 |
#' This is either a fixed value or a named vector with two positive numbers, |
|
| 2680 |
#' the shape (`a`), and the rate (`b`) parameters for the inverse gamma |
|
| 2681 |
#' distribution. |
|
| 2682 |
#' @param sigma2betaW (`numeric`)\cr the prior variance of the random walk model |
|
| 2683 |
#' used for smoothing. This is either a fixed value or a named vector with two |
|
| 2684 |
#' positive numbers, the shape (`a`), and the rate (`b`) parameters for the |
|
| 2685 |
#' inverse gamma distribution. |
|
| 2686 |
#' @param rw1 (`flag`)\cr used for smoothing data for this efficacy model. If it |
|
| 2687 |
#' is `TRUE`, the first-order random walk model is used for the mean efficacy |
|
| 2688 |
#' responses. Otherwise, the random walk of second order is used. |
|
| 2689 |
#' @param data (`DataDual`)\cr observed data to update estimates of the model |
|
| 2690 |
#' parameters. |
|
| 2691 |
#' |
|
| 2692 |
#' @export |
|
| 2693 |
#' @example examples/Model-class-EffFlexi.R |
|
| 2694 |
#' |
|
| 2695 |
EffFlexi <- function(eff, eff_dose, sigma2W, sigma2betaW, rw1 = TRUE, data) {
|
|
| 2696 | 67x |
assert_numeric(eff) |
| 2697 | 67x |
assert_numeric(eff_dose) |
| 2698 | 67x |
assert_numeric(sigma2W, min.len = 1, max.len = 2) |
| 2699 | 67x |
assert_numeric(sigma2betaW, min.len = 1, max.len = 2) |
| 2700 | 67x |
assert_flag(rw1) |
| 2701 | 67x |
assert_class(data, "DataDual") |
| 2702 | ||
| 2703 | 67x |
use_fixed <- c( |
| 2704 | 67x |
sigma2W = test_number(sigma2W), |
| 2705 | 67x |
sigma2betaW = test_number(sigma2betaW) |
| 2706 |
) |
|
| 2707 | ||
| 2708 | 67x |
x <- c(eff_dose, getEff(data, no_dlt = TRUE)$x_no_dlt) |
| 2709 | 67x |
x_level <- match_within_tolerance(x, data@doseGrid) |
| 2710 | 67x |
X <- model.matrix(~ -1L + factor(x_level, levels = seq_len(data@nGrid))) |
| 2711 | 67x |
X <- matrix(as.integer(X), ncol = ncol(X)) # To remove some obsolete attributes. |
| 2712 | ||
| 2713 |
# Set up the random walk penalty matrix and its rank. |
|
| 2714 |
# D1: difference matrix of order 1. |
|
| 2715 | 67x |
D1 <- cbind(0, diag(data@nGrid - 1)) - cbind(diag(data@nGrid - 1), 0) |
| 2716 | 67x |
if (rw1) {
|
| 2717 |
# the rank-deficient prior precision for the RW1 prior. |
|
| 2718 | 44x |
RW <- crossprod(D1) |
| 2719 | 44x |
RW_rank <- data@nGrid - 1L # rank = dimension - 1. # nolintr |
| 2720 |
} else {
|
|
| 2721 |
# Second-order difference. |
|
| 2722 | 23x |
D2 <- D1[-1, -1] %*% D1 |
| 2723 | 23x |
RW <- crossprod(D2) |
| 2724 | 23x |
RW_rank <- data@nGrid - 2L # nolintr |
| 2725 |
} |
|
| 2726 | ||
| 2727 | 67x |
.EffFlexi( |
| 2728 | 67x |
eff = eff, |
| 2729 | 67x |
eff_dose = eff_dose, |
| 2730 | 67x |
sigma2W = sigma2W, |
| 2731 | 67x |
sigma2betaW = sigma2betaW, |
| 2732 | 67x |
use_fixed = use_fixed, |
| 2733 | 67x |
rw1 = rw1, |
| 2734 | 67x |
X = X, |
| 2735 | 67x |
RW = RW, |
| 2736 | 67x |
RW_rank = RW_rank, |
| 2737 | 67x |
data = data |
| 2738 |
) |
|
| 2739 |
} |
|
| 2740 | ||
| 2741 |
## default constructor ---- |
|
| 2742 | ||
| 2743 |
#' @rdname EffFlexi-class |
|
| 2744 |
#' @note Typically, end users will not use the `.DefaultEffFlexi()` function. |
|
| 2745 |
#' @export |
|
| 2746 |
.DefaultEffFlexi <- function() {
|
|
| 2747 | 6x |
empty_data <- DataDual(doseGrid = seq(25, 300, 25)) |
| 2748 | 6x |
EffFlexi( |
| 2749 | 6x |
eff = c(1.223, 2.513), |
| 2750 | 6x |
eff_dose = c(25, 300), |
| 2751 | 6x |
sigma2W = c(a = 0.1, b = 0.1), |
| 2752 | 6x |
sigma2betaW = c(a = 20, b = 50), |
| 2753 | 6x |
rw1 = FALSE, |
| 2754 | 6x |
data = empty_data |
| 2755 |
) |
|
| 2756 | ||
| 2757 | 6x |
data <- DataDual( |
| 2758 | 6x |
x = c(25, 50, 50, 75, 100, 100, 225, 300), |
| 2759 | 6x |
y = c(0, 0, 0, 0, 1, 1, 1, 1), |
| 2760 | 6x |
w = c(0.31, 0.42, 0.59, 0.45, 0.6, 0.7, 0.6, 0.52), |
| 2761 | 6x |
doseGrid = empty_data@doseGrid, |
| 2762 | 6x |
ID = 1L:8L, |
| 2763 | 6x |
cohort = as.integer(c(1, 2, 2, 3, 4, 4, 5, 6)) |
| 2764 |
) |
|
| 2765 | ||
| 2766 | 6x |
EffFlexi( |
| 2767 | 6x |
eff = c(1.223, 2.513), |
| 2768 | 6x |
eff_dose = c(25, 300), |
| 2769 | 6x |
sigma2W = c(a = 0.1, b = 0.1), |
| 2770 | 6x |
sigma2betaW = c(a = 20, b = 50), |
| 2771 | 6x |
rw1 = FALSE, |
| 2772 | 6x |
data = data |
| 2773 |
) |
|
| 2774 |
} |
|
| 2775 | ||
| 2776 |
# DALogisticLogNormal ---- |
|
| 2777 | ||
| 2778 |
## class ---- |
|
| 2779 | ||
| 2780 |
#' `DALogisticLogNormal` |
|
| 2781 |
#' |
|
| 2782 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2783 |
#' |
|
| 2784 |
#' [`DALogisticLogNormal`] is the class for the logistic model with bivariate |
|
| 2785 |
#' (log) normal prior and data augmentation. This class inherits from the |
|
| 2786 |
#' [`LogisticLogNormal`] class. |
|
| 2787 |
#' |
|
| 2788 |
#' @note We still need to include here formula for the lambda prior. |
|
| 2789 |
#' |
|
| 2790 |
#' @slot npiece (`number`)\cr the number of pieces in the `PEM`. |
|
| 2791 |
#' @slot l (`numeric`)\cr a vector used in the lambda prior. |
|
| 2792 |
#' @slot c_par (`numeric`)\cr a parameter used in the lambda prior; according to |
|
| 2793 |
#' Liu's paper, `c_par = 2` is recommended. |
|
| 2794 |
#' @slot cond_pem (`flag`)\cr is a conditional piecewise-exponential model used? |
|
| 2795 |
#' (default). Otherwise an unconditional model is used. |
|
| 2796 |
#' |
|
| 2797 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`]. |
|
| 2798 |
#' |
|
| 2799 |
#' @aliases DALogisticLogNormal |
|
| 2800 |
#' @export |
|
| 2801 |
#' |
|
| 2802 |
.DALogisticLogNormal <- setClass( |
|
| 2803 |
Class = "DALogisticLogNormal", |
|
| 2804 |
slots = c( |
|
| 2805 |
npiece = "integer", |
|
| 2806 |
l = "numeric", |
|
| 2807 |
c_par = "numeric", |
|
| 2808 |
cond_pem = "logical" |
|
| 2809 |
), |
|
| 2810 |
prototype = prototype( |
|
| 2811 |
npiece = 3L, |
|
| 2812 |
l = 0.5, |
|
| 2813 |
c_par = 2, |
|
| 2814 |
cond_pem = TRUE |
|
| 2815 |
), |
|
| 2816 |
contains = "LogisticLogNormal", |
|
| 2817 |
validity = v_model_da_logistic_log_normal |
|
| 2818 |
) |
|
| 2819 | ||
| 2820 |
## constructor ---- |
|
| 2821 | ||
| 2822 |
#' @rdname DALogisticLogNormal-class |
|
| 2823 |
#' |
|
| 2824 |
#' @param npiece (`number`)\cr the number of pieces in the `PEM`. |
|
| 2825 |
#' @param l (`numeric`)\cr a vector used in the lambda prior. |
|
| 2826 |
#' @param c_par (`numeric`)\cr a parameter used in the lambda prior; according to |
|
| 2827 |
#' Liu's paper, `c_par = 2` is recommended. |
|
| 2828 |
#' @param cond_pem (`flag`)\cr is a conditional piecewise-exponential model used? |
|
| 2829 |
#' (default). Otherwise an unconditional model is used. |
|
| 2830 |
#' @inheritDotParams LogisticLogNormal |
|
| 2831 |
#' |
|
| 2832 |
#' @export |
|
| 2833 |
#' @example examples/Model-class-DALogisticLogNormal.R |
|
| 2834 |
#' |
|
| 2835 |
DALogisticLogNormal <- function( |
|
| 2836 |
npiece = 3, |
|
| 2837 |
l, |
|
| 2838 |
c_par = 2, |
|
| 2839 |
cond_pem = TRUE, |
|
| 2840 |
... |
|
| 2841 |
) {
|
|
| 2842 | 35x |
assert_flag(cond_pem) |
| 2843 | ||
| 2844 | 35x |
start <- LogisticLogNormal(...) |
| 2845 | ||
| 2846 | 35x |
datamodel <- function() {
|
| 2847 | ! |
for (i in 1:nObs) {
|
| 2848 |
# Part I: describe the logistic model of DLTs vs dose. |
|
| 2849 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 2850 | ||
| 2851 |
# Part II: describe the piecewise exponential. |
|
| 2852 |
# Notice that: |
|
| 2853 |
# when y=1 -> DLT=1 and u=<T; |
|
| 2854 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
| 2855 |
# when y=0 & T>t (u<T) -> DLT=NA/missing; |
|
| 2856 |
# when indx=0 -> censored, i.e u<T and event=0; |
|
| 2857 |
# when indx=1 -> not censored, i.e. u>=T or event=1; |
|
| 2858 | ! |
indx[i] <- 1 - step(Tmax - u[i] - eps) * (1 - y[i]) |
| 2859 | ||
| 2860 | ! |
for (j in 1:npiece) {
|
| 2861 |
# When not censored, i.e DLT!=NA & t[i]=u[i]; |
|
| 2862 |
# if t[i]<h[j], d[i,j]=0; |
|
| 2863 |
# if h[j]<t[i]=<h[j+1], d[i,j]=1 |
|
| 2864 |
# if h[j+1]<t[i], d[i,j]=0 |
|
| 2865 |
# When censored t[i]>u[i] -> d[i,j]=0 |
|
| 2866 | ! |
d[i, j] <- y[i] * step(u[i] - h[j] - eps) * step(h[j + 1] - u[i]) |
| 2867 | ||
| 2868 |
# DLT free survival(time) for patient i in interval I(j); |
|
| 2869 |
# if t[i]<h[j], s[i,j]=0; |
|
| 2870 |
# if h[j]<t[i]<=h[j+1], s[i,j]=t[i]-h[j] |
|
| 2871 |
# if h[j+1]<=t[i], s[i,j]=h[j+1]-h[j] |
|
| 2872 | ! |
s[i, j] <- min(u[i] - h[j], h[j + 1] - h[j]) * step(u[i] - h[j]) |
| 2873 | ||
| 2874 |
# piecewise exponential hazard rate lambda[j]; |
|
| 2875 | ! |
mu_u[i, j] <- lambda[j] * s[i, j] |
| 2876 | ! |
mu[i, j] <- d[i, j] * log(lambda[j]) - y[i] * mu_u[i, j] |
| 2877 |
} |
|
| 2878 | ||
| 2879 |
# The likelihood function. |
|
| 2880 |
# nolint start |
|
| 2881 | ! |
L_obs[i] <- exp(sum(mu[i, ])) * |
| 2882 | ! |
pow(p[i] / A, y[i]) * |
| 2883 | ! |
pow(1 - p[i], 1 - y[i]) # Not censored. |
| 2884 |
# nolint end |
|
| 2885 | ! |
L_cnsr[i] <- 1 - p[i] * (1 - exp(-sum(mu_u[i, ]))) / A # Censored. # nolintr |
| 2886 | ! |
L[i] <- pow(L_obs[i], indx[i]) * pow(L_cnsr[i], 1 - indx[i]) |
| 2887 | ||
| 2888 |
# Apply zero trick in JAGS. |
|
| 2889 | ! |
phi[i] <- -log(L[i]) + cadj |
| 2890 | ! |
zeros[i] ~ dpois(phi[i]) |
| 2891 |
} |
|
| 2892 |
} |
|
| 2893 | ||
| 2894 | 35x |
priormodel <- h_jags_join_models( |
| 2895 | 35x |
start@priormodel, |
| 2896 | 35x |
function() {
|
| 2897 | ! |
g_beta <- 1 / c_par |
| 2898 | ! |
for (j in 1:npiece) {
|
| 2899 | ! |
g_alpha[j] <- l[j] / c_par |
| 2900 | ! |
lambda[j] ~ dgamma(g_alpha[j], g_beta) |
| 2901 | ! |
mu_T[j] <- lambda[j] * (h[j + 1] - h[j]) # nolintr |
| 2902 |
} |
|
| 2903 |
# If cond = 1, then conditional PEM is used and A is defined as |
|
| 2904 |
# the probability to have DLT, i.e. t<T, otherwise |
|
| 2905 |
# cond = 0 and A is just 1 (so no impact in likelihood). |
|
| 2906 | ! |
A <- cond * (1 - exp(-sum(mu_T))) + (1 - cond) |
| 2907 |
} |
|
| 2908 |
) |
|
| 2909 | ||
| 2910 | 35x |
modelspecs <- function(nObs, Tmax, from_prior) {
|
| 2911 | 42x |
ms <- list( |
| 2912 | 42x |
prec = start@params@prec, |
| 2913 | 42x |
mean = start@params@mean, |
| 2914 | 42x |
npiece = npiece, |
| 2915 | 42x |
l = l, |
| 2916 | 42x |
c_par = c_par, |
| 2917 | 42x |
h = seq(from = 0L, to = Tmax, length = npiece + 1), |
| 2918 | 42x |
cond = as.integer(cond_pem) |
| 2919 |
) |
|
| 2920 | 42x |
if (!from_prior) {
|
| 2921 | 41x |
ms <- c( |
| 2922 | 41x |
list( |
| 2923 | 41x |
ref_dose = start@ref_dose, |
| 2924 | 41x |
zeros = rep(0, nObs), |
| 2925 | 41x |
eps = 1e-10, |
| 2926 | 41x |
cadj = 1e10 |
| 2927 |
), |
|
| 2928 | 41x |
ms |
| 2929 |
) |
|
| 2930 |
} |
|
| 2931 | 42x |
ms |
| 2932 |
} |
|
| 2933 | ||
| 2934 | 35x |
assert_integerish(npiece, lower = 1) |
| 2935 | ||
| 2936 | 35x |
.DALogisticLogNormal( |
| 2937 | 35x |
start, |
| 2938 | 35x |
npiece = as.integer(npiece), |
| 2939 | 35x |
l = l, |
| 2940 | 35x |
c_par = c_par, |
| 2941 | 35x |
cond_pem = cond_pem, |
| 2942 | 35x |
datamodel = datamodel, |
| 2943 | 35x |
priormodel = priormodel, |
| 2944 | 35x |
modelspecs = modelspecs, |
| 2945 | 35x |
datanames = c("nObs", "y", "x", "u", "Tmax"),
|
| 2946 | 35x |
sample = c("alpha0", "alpha1", "lambda")
|
| 2947 |
) |
|
| 2948 |
} |
|
| 2949 | ||
| 2950 |
## default constructor ---- |
|
| 2951 | ||
| 2952 |
#' @rdname DALogisticLogNormal-class |
|
| 2953 |
#' @note Typically, end users will not use the `.DefaultDALogisticLogNormal()` function. |
|
| 2954 |
#' @export |
|
| 2955 |
.DefaultDALogisticLogNormal <- function() {
|
|
| 2956 | 9x |
npiece <- 10 |
| 2957 | 9x |
Tmax <- 60 |
| 2958 | ||
| 2959 | 9x |
lambda_prior <- function(k) {
|
| 2960 | 9x |
npiece / (Tmax * (npiece - k + 0.5)) |
| 2961 |
} |
|
| 2962 | ||
| 2963 | 9x |
DALogisticLogNormal( |
| 2964 | 9x |
mean = c(-0.85, 1), |
| 2965 | 9x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 2966 | 9x |
ref_dose = 56, |
| 2967 | 9x |
npiece = npiece, |
| 2968 | 9x |
l = as.numeric(t(apply( |
| 2969 | 9x |
as.matrix(c(1:npiece), 1, npiece), |
| 2970 | 9x |
2, |
| 2971 | 9x |
lambda_prior |
| 2972 |
))), |
|
| 2973 | 9x |
c_par = 2 |
| 2974 |
) |
|
| 2975 |
} |
|
| 2976 | ||
| 2977 |
# TITELogisticLogNormal ---- |
|
| 2978 | ||
| 2979 |
## class ---- |
|
| 2980 | ||
| 2981 |
#' `TITELogisticLogNormal` |
|
| 2982 |
#' |
|
| 2983 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2984 |
#' |
|
| 2985 |
#' [`TITELogisticLogNormal`] is the class for TITE-CRM based on a logistic |
|
| 2986 |
#' regression model using a bivariate normal prior on the intercept and log |
|
| 2987 |
#' slope parameters. |
|
| 2988 |
#' |
|
| 2989 |
#' This class inherits from the [`LogisticLogNormal`]. |
|
| 2990 |
#' |
|
| 2991 |
#' @slot weight_method (`string`)\cr the weight function method: either linear |
|
| 2992 |
#' or adaptive; see \insertCite{LiuYinYuan2013;textual}{crmPack}.
|
|
| 2993 |
#' |
|
| 2994 |
#' @details Basically, the adaptive function allocates more weight to each record |
|
| 2995 |
#' than the linear function when DLTs are observed early and less weight when DLTs |
|
| 2996 |
#' are observed late. When DLT times are evenly distributed both weights are similar. |
|
| 2997 |
#' In addition, with more DLTs, the adaptive weights become more extreme |
|
| 2998 |
#' and different from the linear weights. |
|
| 2999 |
#' |
|
| 3000 |
#' @seealso [`DALogisticLogNormal`]. |
|
| 3001 |
#' |
|
| 3002 |
#' @aliases TITELogisticLogNormal |
|
| 3003 |
#' @references |
|
| 3004 |
#' \insertAllCited{}
|
|
| 3005 |
#' @export |
|
| 3006 |
#' |
|
| 3007 |
.TITELogisticLogNormal <- setClass( |
|
| 3008 |
Class = "TITELogisticLogNormal", |
|
| 3009 |
slots = c(weight_method = "character"), |
|
| 3010 |
prototype = prototype(weight_method = "linear"), |
|
| 3011 |
contains = "LogisticLogNormal", |
|
| 3012 |
validity = v_model_tite_logistic_log_normal |
|
| 3013 |
) |
|
| 3014 | ||
| 3015 |
## constructor ---- |
|
| 3016 | ||
| 3017 |
#' @rdname TITELogisticLogNormal-class |
|
| 3018 |
#' |
|
| 3019 |
#' @param weight_method (`string`)\cr see the slot description. |
|
| 3020 |
#' @inheritDotParams LogisticLogNormal |
|
| 3021 |
#' |
|
| 3022 |
#' @export |
|
| 3023 |
#' @example examples/Model-class-TITELogisticLogNormal.R |
|
| 3024 |
#' |
|
| 3025 |
TITELogisticLogNormal <- function(weight_method = "linear", ...) {
|
|
| 3026 | 20x |
assert_character( |
| 3027 | 20x |
weight_method, |
| 3028 | 20x |
min.len = 1L, |
| 3029 | 20x |
max.len = 2L, |
| 3030 | 20x |
any.missing = FALSE |
| 3031 |
) |
|
| 3032 | ||
| 3033 | 20x |
start <- LogisticLogNormal(...) |
| 3034 | ||
| 3035 | 20x |
datamodel <- function() {
|
| 3036 | ! |
for (i in 1:nObs) {
|
| 3037 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 3038 | ||
| 3039 |
# The piecewise exponential likelihood. Notice that: |
|
| 3040 |
# when y=1 -> DLT=1 and u=<T; |
|
| 3041 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
| 3042 |
# when y=0 & T>t (u<T) -> DLT=NA/missing; |
|
| 3043 |
# when indx=0 -> censored, i.e u<T and event=0; |
|
| 3044 |
# when indx=1 -> not censored, i.e. u>=T or event=1; |
|
| 3045 | ! |
L[i] <- pow(p[i], y[i]) * pow((1 - w[i] * p[i]), (1 - y[i])) |
| 3046 | ||
| 3047 |
# Apply zero trick in JAGS. |
|
| 3048 | ! |
phi[i] <- -log(L[i]) + cadj |
| 3049 | ! |
zeros[i] ~ dpois(phi[i]) |
| 3050 |
} |
|
| 3051 |
} |
|
| 3052 | ||
| 3053 | 20x |
modelspecs <- function(nObs, u, Tmax, y, from_prior) {
|
| 3054 | 6x |
ms <- list(prec = start@params@prec, mean = start@params@mean) |
| 3055 |
# Calculate weights `w` based on the input data. |
|
| 3056 | 6x |
if (!from_prior && nObs > 0L) {
|
| 3057 | 4x |
if (weight_method == "linear") {
|
| 3058 | 2x |
w <- u / Tmax |
| 3059 | 2x |
} else if (weight_method == "adaptive") {
|
| 3060 | 2x |
nDLT <- sum(y) |
| 3061 | 2x |
if (nDLT > 0) {
|
| 3062 | 2x |
u_dlt <- sort(u[y == 1]) |
| 3063 | 2x |
w <- sapply(u, function(u_i) {
|
| 3064 | 20x |
m <- sum(u_i >= u_dlt) |
| 3065 | 20x |
w_i <- if (m == 0) {
|
| 3066 | 9x |
u_i / u_dlt[1] |
| 3067 | 20x |
} else if (m < nDLT) {
|
| 3068 | 5x |
m + (u_i - u_dlt[m]) / (u_dlt[m + 1] - u_dlt[m]) |
| 3069 |
} else {
|
|
| 3070 |
# m == nDLT. nolintr |
|
| 3071 | 6x |
m + (u_i - u_dlt[m]) / (Tmax + 0.00000001 - u_dlt[m]) |
| 3072 |
} |
|
| 3073 | 20x |
w_i / (nDLT + 1) |
| 3074 |
}) |
|
| 3075 |
} else {
|
|
| 3076 | ! |
w <- u / Tmax |
| 3077 |
} |
|
| 3078 |
} |
|
| 3079 | 4x |
w[y == 1] <- 1 |
| 3080 | 4x |
w[u == Tmax] <- 1 |
| 3081 | ||
| 3082 | 4x |
ms <- c( |
| 3083 | 4x |
list( |
| 3084 | 4x |
ref_dose = start@ref_dose, |
| 3085 | 4x |
zeros = rep(0, nObs), |
| 3086 | 4x |
cadj = 1e10, |
| 3087 | 4x |
w = w |
| 3088 |
), |
|
| 3089 | 4x |
ms |
| 3090 |
) |
|
| 3091 |
} |
|
| 3092 | 6x |
ms |
| 3093 |
} |
|
| 3094 | ||
| 3095 | 20x |
.TITELogisticLogNormal( |
| 3096 | 20x |
start, |
| 3097 | 20x |
weight_method = weight_method, |
| 3098 | 20x |
datamodel = datamodel, |
| 3099 | 20x |
modelspecs = modelspecs, |
| 3100 | 20x |
datanames = c("nObs", "y", "x")
|
| 3101 |
) |
|
| 3102 |
} |
|
| 3103 | ||
| 3104 |
## default constructor ---- |
|
| 3105 | ||
| 3106 |
#' @rdname TITELogisticLogNormal-class |
|
| 3107 |
#' @note Typically, end users will not use the `.DefaultTITELogisticLogNormal()` function. |
|
| 3108 |
#' @export |
|
| 3109 |
.DefaultTITELogisticLogNormal <- function() {
|
|
| 3110 | 9x |
TITELogisticLogNormal( |
| 3111 | 9x |
mean = c(0, 1), |
| 3112 | 9x |
cov = diag(2), |
| 3113 | 9x |
ref_dose = 1, |
| 3114 | 9x |
weight_method = "linear" |
| 3115 |
) |
|
| 3116 |
} |
|
| 3117 | ||
| 3118 |
# OneParLogNormalPrior ---- |
|
| 3119 | ||
| 3120 |
## class ---- |
|
| 3121 | ||
| 3122 |
#' `OneParLogNormalPrior` |
|
| 3123 |
#' |
|
| 3124 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3125 |
#' |
|
| 3126 |
#' [`OneParLogNormalPrior`] is the class for a standard CRM with a normal prior on |
|
| 3127 |
#' the log power parameter for the skeleton prior probabilities. |
|
| 3128 |
#' |
|
| 3129 |
#' @slot skel_fun (`function`)\cr function to calculate the prior DLT probabilities. |
|
| 3130 |
#' @slot skel_fun_inv (`function`)\cr inverse function of `skel_fun`. |
|
| 3131 |
#' @slot skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
| 3132 |
#' of unique and sorted probability values between 0 and 1. |
|
| 3133 |
#' @slot sigma2 (`number`)\cr prior variance of log power parameter alpha. |
|
| 3134 |
#' |
|
| 3135 |
#' @seealso [`ModelLogNormal`]. |
|
| 3136 |
#' |
|
| 3137 |
#' @aliases OneParLogNormalPrior |
|
| 3138 |
#' @export |
|
| 3139 |
#' |
|
| 3140 |
.OneParLogNormalPrior <- setClass( |
|
| 3141 |
Class = "OneParLogNormalPrior", |
|
| 3142 |
slots = c( |
|
| 3143 |
skel_fun = "function", |
|
| 3144 |
skel_fun_inv = "function", |
|
| 3145 |
skel_probs = "numeric", |
|
| 3146 |
sigma2 = "numeric" |
|
| 3147 |
), |
|
| 3148 |
contains = "GeneralModel", |
|
| 3149 |
validity = v_model_one_par_exp_normal_prior |
|
| 3150 |
) |
|
| 3151 | ||
| 3152 |
## constructor ---- |
|
| 3153 | ||
| 3154 |
#' @rdname OneParLogNormalPrior-class |
|
| 3155 |
#' |
|
| 3156 |
#' @param skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
| 3157 |
#' of unique and sorted probability values between 0 and 1. |
|
| 3158 |
#' @param dose_grid (`numeric`)\cr dose grid. It must be must be a sorted vector |
|
| 3159 |
#' of the same length as `skel_probs`. |
|
| 3160 |
#' @param sigma2 (`number`)\cr prior variance of log power parameter alpha. |
|
| 3161 |
#' |
|
| 3162 |
#' @export |
|
| 3163 |
#' @example examples/Model-class-OneParLogNormalPrior.R |
|
| 3164 |
#' |
|
| 3165 |
OneParLogNormalPrior <- function(skel_probs, dose_grid, sigma2) {
|
|
| 3166 | 48x |
assert_probabilities(skel_probs, unique = TRUE, sorted = TRUE) # So that skel_fun_inv exists. |
| 3167 | 45x |
assert_numeric( |
| 3168 | 45x |
dose_grid, |
| 3169 | 45x |
len = length(skel_probs), |
| 3170 | 45x |
any.missing = FALSE, |
| 3171 | 45x |
unique = TRUE, |
| 3172 | 45x |
sorted = TRUE |
| 3173 |
) |
|
| 3174 | ||
| 3175 | 42x |
skel_fun <- approxfun(x = dose_grid, y = skel_probs, rule = 2) |
| 3176 | 42x |
skel_fun_inv <- approxfun(x = skel_probs, y = dose_grid, rule = 2) |
| 3177 | ||
| 3178 | 42x |
.OneParLogNormalPrior( |
| 3179 | 42x |
skel_fun = skel_fun, |
| 3180 | 42x |
skel_fun_inv = skel_fun_inv, |
| 3181 | 42x |
skel_probs = skel_probs, |
| 3182 | 42x |
sigma2 = sigma2, |
| 3183 | 42x |
datamodel = function() {
|
| 3184 | ! |
for (i in 1:nObs) {
|
| 3185 | ! |
p[i] <- skel_probs[xLevel[i]]^exp(alpha) |
| 3186 | ! |
y[i] ~ dbern(p[i]) |
| 3187 |
} |
|
| 3188 |
}, |
|
| 3189 | 42x |
priormodel = function() {
|
| 3190 | ! |
alpha ~ dnorm(0, 1 / sigma2) |
| 3191 |
}, |
|
| 3192 | 42x |
modelspecs = function(from_prior) {
|
| 3193 | 5x |
ms <- list(sigma2 = sigma2) |
| 3194 | 5x |
if (!from_prior) {
|
| 3195 | 3x |
ms$skel_probs <- skel_probs |
| 3196 |
} |
|
| 3197 | 5x |
ms |
| 3198 |
}, |
|
| 3199 | 42x |
init = function() {
|
| 3200 | 7x |
list(alpha = 1) |
| 3201 |
}, |
|
| 3202 | 42x |
datanames = c("nObs", "y", "xLevel"),
|
| 3203 | 42x |
sample = "alpha" |
| 3204 |
) |
|
| 3205 |
} |
|
| 3206 | ||
| 3207 |
## default constructor ---- |
|
| 3208 | ||
| 3209 |
#' @rdname OneParLogNormalPrior-class |
|
| 3210 |
#' @return an instance of the `OneParLogNormalPrior` class |
|
| 3211 |
#' @export |
|
| 3212 |
.DefaultOneParLogNormalPrior <- function() {
|
|
| 3213 | 8x |
OneParLogNormalPrior( |
| 3214 | 8x |
skel_probs = seq(from = 0.1, to = 0.9, length = 5), |
| 3215 | 8x |
dose_grid = 1:5, |
| 3216 | 8x |
sigma2 = 2 |
| 3217 |
) |
|
| 3218 |
} |
|
| 3219 | ||
| 3220 |
# OneParExpPrior ---- |
|
| 3221 | ||
| 3222 |
## class ---- |
|
| 3223 | ||
| 3224 |
#' `OneParExpPrior` |
|
| 3225 |
#' |
|
| 3226 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3227 |
#' |
|
| 3228 |
#' [`OneParExpPrior`] is the class for a standard CRM with an exponential prior |
|
| 3229 |
#' on the power parameter for the skeleton prior probabilities. It is an |
|
| 3230 |
#' implementation of a version of the one-parameter CRM |
|
| 3231 |
#' \insertCite{OQuigleyPepeFisher1990}{crmPack}.
|
|
| 3232 |
#' |
|
| 3233 |
#' @note Typically, end users will not use the `.DefaultOneparExpPrior()` function. |
|
| 3234 |
#' |
|
| 3235 |
#' @slot skel_fun (`function`)\cr function to calculate the prior DLT probabilities. |
|
| 3236 |
#' @slot skel_fun_inv (`function`)\cr inverse function of `skel_fun`. |
|
| 3237 |
#' @slot skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
| 3238 |
#' of unique and sorted probability values between 0 and 1. |
|
| 3239 |
#' @slot lambda (`number`)\cr rate parameter of prior exponential distribution |
|
| 3240 |
#' for theta. |
|
| 3241 |
#' |
|
| 3242 |
#' @aliases OneParExpPrior |
|
| 3243 |
#' @export |
|
| 3244 |
#' @references |
|
| 3245 |
#' \insertAllCited{}
|
|
| 3246 |
#' |
|
| 3247 |
.OneParExpPrior <- setClass( |
|
| 3248 |
Class = "OneParExpPrior", |
|
| 3249 |
slots = c( |
|
| 3250 |
skel_fun = "function", |
|
| 3251 |
skel_fun_inv = "function", |
|
| 3252 |
skel_probs = "numeric", |
|
| 3253 |
lambda = "numeric" |
|
| 3254 |
), |
|
| 3255 |
contains = "GeneralModel", |
|
| 3256 |
validity = v_model_one_par_exp_prior |
|
| 3257 |
) |
|
| 3258 | ||
| 3259 |
## constructor ---- |
|
| 3260 | ||
| 3261 |
#' @rdname OneParExpPrior-class |
|
| 3262 |
#' |
|
| 3263 |
#' @param skel_probs see slot definition. |
|
| 3264 |
#' @param dose_grid (`numeric`)\cr dose grid. It must be must be a sorted vector |
|
| 3265 |
#' of the same length as `skel_probs`. |
|
| 3266 |
#' @param lambda see slot definition. |
|
| 3267 |
#' |
|
| 3268 |
#' @export |
|
| 3269 |
#' @example examples/Model-class-OneParExpPrior.R |
|
| 3270 |
#' |
|
| 3271 |
OneParExpPrior <- function(skel_probs, dose_grid, lambda) {
|
|
| 3272 | 32x |
assert_probabilities(skel_probs, unique = TRUE, sorted = TRUE) # So that skel_fun_inv exists. |
| 3273 | 29x |
assert_numeric( |
| 3274 | 29x |
dose_grid, |
| 3275 | 29x |
len = length(skel_probs), |
| 3276 | 29x |
any.missing = FALSE, |
| 3277 | 29x |
unique = TRUE, |
| 3278 | 29x |
sorted = TRUE |
| 3279 |
) |
|
| 3280 | ||
| 3281 | 26x |
skel_fun <- approxfun(x = dose_grid, y = skel_probs, rule = 2) |
| 3282 | 26x |
skel_fun_inv <- approxfun(x = skel_probs, y = dose_grid, rule = 2) |
| 3283 | ||
| 3284 | 26x |
.OneParExpPrior( |
| 3285 | 26x |
skel_fun = skel_fun, |
| 3286 | 26x |
skel_fun_inv = skel_fun_inv, |
| 3287 | 26x |
skel_probs = skel_probs, |
| 3288 | 26x |
lambda = lambda, |
| 3289 | 26x |
datamodel = function() {
|
| 3290 | ! |
for (i in 1:nObs) {
|
| 3291 | ! |
p[i] <- skel_probs[xLevel[i]]^theta |
| 3292 | ! |
y[i] ~ dbern(p[i]) |
| 3293 |
} |
|
| 3294 |
}, |
|
| 3295 | 26x |
priormodel = function() {
|
| 3296 | ! |
theta ~ dexp(lambda) |
| 3297 |
}, |
|
| 3298 | 26x |
modelspecs = function(from_prior) {
|
| 3299 | 2x |
ms <- list(lambda = lambda) |
| 3300 | 2x |
if (!from_prior) {
|
| 3301 | 1x |
ms$skel_probs <- skel_probs |
| 3302 |
} |
|
| 3303 | 2x |
ms |
| 3304 |
}, |
|
| 3305 | 26x |
init = function() {
|
| 3306 | 2x |
list(theta = 1) |
| 3307 |
}, |
|
| 3308 | 26x |
datanames = c("nObs", "y", "xLevel"),
|
| 3309 | 26x |
sample = "theta" |
| 3310 |
) |
|
| 3311 |
} |
|
| 3312 | ||
| 3313 |
## default constructor ---- |
|
| 3314 | ||
| 3315 |
#' @rdname OneParExpPrior-class |
|
| 3316 |
#' @note Typically, end users will not use the `.DefaultOneParLogNormalPrior()` function. |
|
| 3317 |
#' @export |
|
| 3318 |
.DefaultOneParExpPrior <- function() {
|
|
| 3319 | 8x |
OneParExpPrior( |
| 3320 | 8x |
skel_probs = c(0.1, 0.3, 0.5, 0.7, 0.9), |
| 3321 | 8x |
dose_grid = 1:5, |
| 3322 | 8x |
lambda = 2 |
| 3323 |
) |
|
| 3324 |
} |
|
| 3325 | ||
| 3326 |
# FractionalCRM ---- |
|
| 3327 | ||
| 3328 |
## class ---- |
|
| 3329 | ||
| 3330 |
#' `FractionalCRM` |
|
| 3331 |
#' |
|
| 3332 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3333 |
#' |
|
| 3334 |
#' [`FractionalCRM`] is the class for a fractional CRM model based on a one |
|
| 3335 |
#' parameter CRM (with normal prior on the log-power parameter) as well as |
|
| 3336 |
#' Kaplan-Meier based estimation of the conditional probability to experience a |
|
| 3337 |
#' DLT for non-complete observations. |
|
| 3338 |
#' |
|
| 3339 |
#' This fractional CRM model follows the paper and code by \insertCite{YinZhengXu2013;textual}{crmPack}.
|
|
| 3340 |
#' |
|
| 3341 |
#' @seealso [`TITELogisticLogNormal`]. |
|
| 3342 |
#' |
|
| 3343 |
#' @aliases FractionalCRM |
|
| 3344 |
#' @export |
|
| 3345 |
#' @references |
|
| 3346 |
#' \insertAllCited{}
|
|
| 3347 |
#' |
|
| 3348 |
.FractionalCRM <- setClass( |
|
| 3349 |
Class = "FractionalCRM", |
|
| 3350 |
contains = "OneParLogNormalPrior" |
|
| 3351 |
) |
|
| 3352 | ||
| 3353 |
## constructor ---- |
|
| 3354 | ||
| 3355 |
#' @rdname FractionalCRM-class |
|
| 3356 |
#' |
|
| 3357 |
#' @inheritDotParams OneParLogNormalPrior |
|
| 3358 |
#' |
|
| 3359 |
#' @export |
|
| 3360 |
#' @example examples/Model-class-FractionalCRM.R |
|
| 3361 |
#' |
|
| 3362 |
FractionalCRM <- function(...) {
|
|
| 3363 | 14x |
start <- OneParLogNormalPrior(...) |
| 3364 | ||
| 3365 |
# This is adapted from the TITELogisticLogNormal class. |
|
| 3366 | 14x |
datamodel <- function() {
|
| 3367 | ! |
for (i in 1:nObs) {
|
| 3368 | ! |
p[i] <- skel_probs[xLevel[i]]^exp(alpha) |
| 3369 | ||
| 3370 |
# The piecewise exponential likelihood. Notice that: |
|
| 3371 |
# when y=1 -> DLT=1 and u=<T; |
|
| 3372 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
| 3373 |
# when y=0 & T>t (u<T) -> DLT=NA/missing. |
|
| 3374 |
# Therefore, `yhat` is used instead of `y` for the likelihood f. (see `modelspecs`). |
|
| 3375 | ! |
L[i] <- pow(p[i], yhat[i]) * pow((1 - p[i]), (1 - yhat[i])) |
| 3376 | ||
| 3377 |
# Apply zero trick in JAGS. |
|
| 3378 | ! |
phi[i] <- -log(L[i]) + cadj |
| 3379 | ! |
zeros[i] ~ dpois(phi[i]) |
| 3380 |
} |
|
| 3381 |
} |
|
| 3382 | ||
| 3383 | 14x |
modelspecs <- function(nObs, u, Tmax, y, from_prior) {
|
| 3384 | 2x |
ms <- list(sigma2 = start@sigma2) |
| 3385 | 2x |
if (!from_prior) {
|
| 3386 |
# Calculate fractional contribution `yhat` |
|
| 3387 |
# based on the input data using the Kaplan-Meier method. |
|
| 3388 | 1x |
yhat <- if (nObs > 0) {
|
| 3389 | 1x |
km <- survival::survfit(survival::Surv(u, y) ~ 1) |
| 3390 | 1x |
s_tau <- tail(km$surv[km$time <= Tmax], 1) # Survival probability = S(Tmax). |
| 3391 | 1x |
ifelse( |
| 3392 | 1x |
u < Tmax & y == 0L, # Within the assessment window and so far no DLT. |
| 3393 | 1x |
yes = 1 - |
| 3394 | 1x |
s_tau / sapply(u, function(u_i) tail(km$surv[km$time <= u_i], 1)), |
| 3395 | 1x |
no = y |
| 3396 |
) |
|
| 3397 |
} else {
|
|
| 3398 | ! |
1L |
| 3399 |
} |
|
| 3400 | 1x |
ms <- c( |
| 3401 | 1x |
list( |
| 3402 | 1x |
skel_probs = start@skel_probs, |
| 3403 | 1x |
zeros = rep(0, nObs), |
| 3404 | 1x |
cadj = 1e10, |
| 3405 | 1x |
yhat = yhat |
| 3406 |
), |
|
| 3407 | 1x |
ms |
| 3408 |
) |
|
| 3409 |
} |
|
| 3410 | 2x |
ms |
| 3411 |
} |
|
| 3412 | ||
| 3413 | 14x |
.FractionalCRM( |
| 3414 | 14x |
start, |
| 3415 | 14x |
datamodel = datamodel, |
| 3416 | 14x |
modelspecs = modelspecs, |
| 3417 | 14x |
datanames = c("nObs", "xLevel")
|
| 3418 |
) |
|
| 3419 |
} |
|
| 3420 | ||
| 3421 |
## default constructor ---- |
|
| 3422 | ||
| 3423 |
#' @rdname FractionalCRM-class |
|
| 3424 |
#' @note Typically, end users will not use the `.DefaultTITELogisticLogNormal()` function. |
|
| 3425 |
#' @export |
|
| 3426 |
.DefaultFractionalCRM <- function() {
|
|
| 3427 | 9x |
FractionalCRM( |
| 3428 | 9x |
skel_probs = c(0.1, 0.2, 0.3, 0.4), |
| 3429 | 9x |
dose_grid = c(10, 30, 50, 100), |
| 3430 | 9x |
sigma2 = 2 |
| 3431 |
) |
|
| 3432 |
} |
|
| 3433 | ||
| 3434 |
## class ---- |
|
| 3435 | ||
| 3436 |
#' `LogisticLogNormalOrdinal` |
|
| 3437 |
#' |
|
| 3438 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3439 |
#' |
|
| 3440 |
#' [`LogisticLogNormalOrdinal`] is the class for a logistic lognormal CRM model |
|
| 3441 |
#' using an ordinal toxicity scale. |
|
| 3442 |
#' |
|
| 3443 |
#' @aliases LogisticLogNormalOrdinal |
|
| 3444 |
#' @export |
|
| 3445 |
.LogisticLogNormalOrdinal <- setClass( |
|
| 3446 |
Class = "LogisticLogNormalOrdinal", |
|
| 3447 |
contains = "ModelLogNormal", |
|
| 3448 |
validity = v_logisticlognormalordinal |
|
| 3449 |
) |
|
| 3450 | ||
| 3451 |
## constructor ---- |
|
| 3452 | ||
| 3453 |
#' @rdname LogisticLogNormalOrdinal-class |
|
| 3454 |
#' @inheritParams ModelLogNormal |
|
| 3455 |
#' @export |
|
| 3456 |
#' @example examples/Model-class-LogisticLogNormalOrdinal.R |
|
| 3457 |
LogisticLogNormalOrdinal <- function(mean, cov, ref_dose) {
|
|
| 3458 | 37x |
params <- ModelParamsNormal(mean, cov) |
| 3459 | 37x |
.LogisticLogNormalOrdinal( |
| 3460 | 37x |
params = params, |
| 3461 | 37x |
ref_dose = positive_number(ref_dose), |
| 3462 | 37x |
priormodel = function() {
|
| 3463 | ! |
alpha[1] ~ dnorm(mean[1], prec[1, 1]) |
| 3464 | ! |
for (i in 2:(k - 1)) {
|
| 3465 | ! |
alpha[i] ~ dnorm(mean[i], prec[i, i]) %_% T(, alpha[i - 1]) |
| 3466 |
} |
|
| 3467 | ! |
gamma ~ dnorm(mean[k], prec[k, k]) |
| 3468 | ! |
beta <- exp(gamma) |
| 3469 |
}, |
|
| 3470 | 37x |
datamodel = function() {
|
| 3471 | ! |
for (i in 1:nObs) {
|
| 3472 | ! |
xhat[i] <- log(x[i] / ref_dose) |
| 3473 | ! |
for (j in 1:(k - 1)) {
|
| 3474 | ! |
z[i, j] <- alpha[j] + beta * xhat[i] |
| 3475 | ! |
p[i, j] <- exp(z[i, j]) / (1 + exp(z[i, j])) |
| 3476 | ! |
tox[i, j] ~ dbern(p[i, j]) |
| 3477 |
} |
|
| 3478 |
} |
|
| 3479 |
}, |
|
| 3480 | 37x |
modelspecs = function(y, from_prior) {
|
| 3481 | 17x |
ms <- list( |
| 3482 | 17x |
mean = params@mean, |
| 3483 | 17x |
prec = params@prec, |
| 3484 | 17x |
k = length(mean) |
| 3485 |
) |
|
| 3486 | 17x |
if (!from_prior) {
|
| 3487 | 15x |
ms$tox <- array(dim = c(length(y), length(mean) - 1)) |
| 3488 | 15x |
for (i in seq_along(y)) {
|
| 3489 | 140x |
for (j in 1:(ms$k - 1)) {
|
| 3490 | 280x |
ms$tox[i, j] <- y[i] >= j |
| 3491 |
} |
|
| 3492 |
} |
|
| 3493 | 15x |
ms$ref_dose <- ref_dose |
| 3494 |
} |
|
| 3495 | 17x |
ms |
| 3496 |
}, |
|
| 3497 | 37x |
init = function() {
|
| 3498 | 17x |
list( |
| 3499 | 17x |
alpha = sapply(1:(length(mean) - 1), function(x) -(x + 1)), |
| 3500 | 17x |
gamma = 1 |
| 3501 |
) |
|
| 3502 |
}, |
|
| 3503 | 37x |
datanames = c("nObs", "x"),
|
| 3504 |
# Need to provide JAGS column names here |
|
| 3505 | 37x |
sample = c(paste0("alpha[", 1:(length(mean) - 1), "]"), "beta")
|
| 3506 |
) |
|
| 3507 |
} |
|
| 3508 | ||
| 3509 |
## default constructor ---- |
|
| 3510 | ||
| 3511 |
#' @rdname LogisticLogNormalOrdinal-class |
|
| 3512 |
#' @note Typically, end users will not use the `.DefaultLogisticLogNormalOrdinal()` function. |
|
| 3513 |
#' @export |
|
| 3514 |
.DefaultLogisticLogNormalOrdinal <- function() {
|
|
| 3515 | 25x |
LogisticLogNormalOrdinal( |
| 3516 | 25x |
mean = c(-3, -4, 1), |
| 3517 | 25x |
cov = diag(c(3, 4, 1)), |
| 3518 | 25x |
ref_dose = 50 |
| 3519 |
) |
|
| 3520 |
} |
| 1 |
# NextBest ---- |
|
| 2 | ||
| 3 |
#' Internal Helper Functions for Validation of [`NextBest`] Objects |
|
| 4 |
#' |
|
| 5 |
#' @description `r lifecycle::badge("stable")`
|
|
| 6 |
#' |
|
| 7 |
#' These functions are only used internally to validate the format of an input |
|
| 8 |
#' [`NextBest`] or inherited classes and therefore not exported. |
|
| 9 |
#' |
|
| 10 |
#' @name v_next_best |
|
| 11 |
#' @param object (`NextBest`)\cr object to validate. |
|
| 12 |
#' @return A `character` vector with the validation failure messages, |
|
| 13 |
#' or `TRUE` in case validation passes. |
|
| 14 |
NULL |
|
| 15 | ||
| 16 |
#' @describeIn v_next_best validates that the [`NextBestMTD`] object |
|
| 17 |
#' contains valid `target` probability and `derive` function. |
|
| 18 |
v_next_best_mtd <- function(object) {
|
|
| 19 | 7x |
v <- Validate() |
| 20 | 7x |
v$check( |
| 21 | 7x |
test_probability(object@target, bounds_closed = FALSE), |
| 22 | 7x |
"target must be a probability value from (0, 1) interval" |
| 23 |
) |
|
| 24 | 7x |
v$check( |
| 25 | 7x |
test_function(object@derive, nargs = 1), |
| 26 | 7x |
"derive must have a single argument" |
| 27 |
) |
|
| 28 | 7x |
v$check( |
| 29 | 7x |
test_number(object@derive(1:5)), |
| 30 | 7x |
"derive must accept numerical vector as an argument and return a number" |
| 31 |
) |
|
| 32 | 7x |
v$result() |
| 33 |
} |
|
| 34 | ||
| 35 |
#' @describeIn v_next_best validates that the [`NextBestNCRM`] object |
|
| 36 |
#' contains valid `target` probability, `overdose` and `max_overdose_prob` |
|
| 37 |
#' probability ranges. |
|
| 38 |
v_next_best_ncrm <- function(object) {
|
|
| 39 | 14x |
v <- Validate() |
| 40 | 14x |
v$check( |
| 41 | 14x |
test_probability_range(object@target), |
| 42 | 14x |
"target has to be a probability range" |
| 43 |
) |
|
| 44 | 14x |
v$check( |
| 45 | 14x |
test_probability_range(object@overdose), |
| 46 | 14x |
"overdose has to be a probability range" |
| 47 |
) |
|
| 48 | 14x |
v$check( |
| 49 | 14x |
test_probability(object@max_overdose_prob, bounds_closed = FALSE), |
| 50 | 14x |
"max_overdose_prob must be a probability value from (0, 1) interval" |
| 51 |
) |
|
| 52 | 14x |
v$result() |
| 53 |
} |
|
| 54 | ||
| 55 |
#' @describeIn v_next_best validates that the [`NextBestNCRMLoss`] object |
|
| 56 |
#' contains valid objects. |
|
| 57 |
v_next_best_ncrm_loss <- function(object) {
|
|
| 58 | 20x |
v <- Validate() |
| 59 | 20x |
v$check( |
| 60 | 20x |
test_probability_range(object@target, bounds_closed = FALSE), |
| 61 | 20x |
"target has to be a probability range excluding 0 and 1" |
| 62 |
) |
|
| 63 | ||
| 64 | 20x |
is_overdose_ok <- test_probability_range( |
| 65 | 20x |
object@overdose, |
| 66 | 20x |
bounds_closed = TRUE |
| 67 |
) |
|
| 68 | 20x |
v$check(is_overdose_ok, "overdose has to be a probability range") |
| 69 | ||
| 70 | 20x |
is_unacceptable_ok <- test_probability_range( |
| 71 | 20x |
object@unacceptable, |
| 72 | 20x |
bounds_closed = TRUE |
| 73 |
) |
|
| 74 | 20x |
v$check(is_unacceptable_ok, "unacceptable has to be a probability range") |
| 75 | ||
| 76 | 20x |
if (is_overdose_ok && is_unacceptable_ok) {
|
| 77 | 12x |
v$check( |
| 78 | 12x |
object@overdose[2] <= object@unacceptable[1], |
| 79 | 12x |
"lower bound of unacceptable has to be >= than upper bound of overdose" |
| 80 |
) |
|
| 81 |
} |
|
| 82 | 20x |
if (is_unacceptable_ok) {
|
| 83 | 16x |
losses_len <- ifelse(all(object@unacceptable == c(1, 1)), 3L, 4L) |
| 84 | 16x |
v$check( |
| 85 | 16x |
test_numeric( |
| 86 | 16x |
object@losses, |
| 87 | 16x |
lower = 0, |
| 88 | 16x |
finite = TRUE, |
| 89 | 16x |
any.missing = FALSE, |
| 90 | 16x |
len = losses_len |
| 91 |
), |
|
| 92 | 16x |
"losses must be a vector of non-negative numbers of length 3 if unacceptable is c(1, 1), otherwise 4" |
| 93 |
) |
|
| 94 |
} |
|
| 95 | 20x |
v$result() |
| 96 |
} |
|
| 97 | ||
| 98 |
#' @describeIn v_next_best validates that the [`NextBestDualEndpoint`] object |
|
| 99 |
#' contains valid probability objects. |
|
| 100 |
v_next_best_dual_endpoint <- function(object) {
|
|
| 101 | 22x |
v <- Validate() |
| 102 | 22x |
v$check( |
| 103 | 22x |
test_flag(object@target_relative), |
| 104 | 22x |
"target_relative must be a flag" |
| 105 |
) |
|
| 106 | 22x |
if (isTRUE(object@target_relative)) {
|
| 107 | 17x |
v$check( |
| 108 | 17x |
test_probability_range(object@target), |
| 109 | 17x |
"target has to be a probability range when target_relative is TRUE" |
| 110 |
) |
|
| 111 |
} else {
|
|
| 112 | 5x |
v$check( |
| 113 | 5x |
test_range(object@target), |
| 114 | 5x |
"target must be a numeric range" |
| 115 |
) |
|
| 116 |
} |
|
| 117 | 22x |
v$check( |
| 118 | 22x |
test_probability_range(object@overdose), |
| 119 | 22x |
"overdose has to be a probability range" |
| 120 |
) |
|
| 121 | 22x |
v$check( |
| 122 | 22x |
test_probability(object@max_overdose_prob, bounds_closed = FALSE), |
| 123 | 22x |
"max_overdose_prob must be a probability value from (0, 1) interval" |
| 124 |
) |
|
| 125 | 22x |
v$check( |
| 126 | 22x |
test_probability(object@target_thresh), |
| 127 | 22x |
"target_thresh must be a probability value from [0, 1] interval" |
| 128 |
) |
|
| 129 | 22x |
v$result() |
| 130 |
} |
|
| 131 | ||
| 132 |
#' @describeIn v_next_best validates that the [`NextBestMinDist`] object |
|
| 133 |
#' contains valid `target` object. |
|
| 134 |
v_next_best_min_dist <- function(object) {
|
|
| 135 | 5x |
v <- Validate() |
| 136 | 5x |
v$check( |
| 137 | 5x |
test_probability(object@target, bounds_closed = FALSE), |
| 138 | 5x |
"target must be a probability value from (0, 1) interval" |
| 139 |
) |
|
| 140 | 5x |
v$result() |
| 141 |
} |
|
| 142 | ||
| 143 |
#' @describeIn v_next_best validates that the [`NextBestEWOC`] object |
|
| 144 |
#' contains valid `target`, `overdose` and `max_overdose_prob` parameters. |
|
| 145 |
v_next_best_ewoc <- function(object) {
|
|
| 146 | 4x |
v <- Validate() |
| 147 | 4x |
v$check( |
| 148 | 4x |
test_probability(object@target, bounds_closed = FALSE), |
| 149 | 4x |
"target must be a probability value from (0, 1) interval" |
| 150 |
) |
|
| 151 | 4x |
v$check( |
| 152 | 4x |
test_number(object@target, upper = object@overdose[1]), |
| 153 | 4x |
"target must be below the overdose interval" |
| 154 |
) |
|
| 155 | 4x |
v$check( |
| 156 | 4x |
test_probability_range(object@overdose, bounds_closed = TRUE), |
| 157 | 4x |
"overdose has to be a probability range" |
| 158 |
) |
|
| 159 | 4x |
v$check( |
| 160 | 4x |
test_probability(object@max_overdose_prob, bounds_closed = FALSE), |
| 161 | 4x |
"max_overdose_prob must be a probability value from (0, 1) interval" |
| 162 |
) |
|
| 163 | 4x |
v$result() |
| 164 |
} |
|
| 165 | ||
| 166 |
#' @describeIn v_next_best validates that the [`NextBestInfTheory`] object |
|
| 167 |
#' contains valid `target` and `asymmetry` objects. |
|
| 168 |
v_next_best_inf_theory <- function(object) {
|
|
| 169 | 9x |
v <- Validate() |
| 170 | 9x |
v$check( |
| 171 | 9x |
test_probability(object@target, bounds_closed = FALSE), |
| 172 | 9x |
"target must be a probability value from (0, 1) interval" |
| 173 |
) |
|
| 174 | 9x |
v$check( |
| 175 | 9x |
test_number(object@asymmetry, finite = TRUE) && |
| 176 | 9x |
h_in_range(object@asymmetry, c(0, 2), FALSE), |
| 177 | 9x |
"asymmetry must be a number from (0, 2) interval" |
| 178 |
) |
|
| 179 | 9x |
v$result() |
| 180 |
} |
|
| 181 | ||
| 182 |
#' @describeIn v_next_best validates that the [`NextBestTD`] object |
|
| 183 |
#' contains valid `prob_target_drt` and `prob_target_eot` probabilities. |
|
| 184 |
v_next_best_td <- function(object) {
|
|
| 185 | 9x |
v <- Validate() |
| 186 | 9x |
v$check( |
| 187 | 9x |
test_probability(object@prob_target_drt, bounds_closed = FALSE), |
| 188 | 9x |
"prob_target_drt must be a probability value from (0, 1) interval" |
| 189 |
) |
|
| 190 | 9x |
v$check( |
| 191 | 9x |
test_probability(object@prob_target_eot, bounds_closed = FALSE), |
| 192 | 9x |
"prob_target_eot must be a probability value from (0, 1) interval" |
| 193 |
) |
|
| 194 | 9x |
v$result() |
| 195 |
} |
|
| 196 | ||
| 197 |
#' @describeIn v_next_best validates that the [`NextBestTDsamples`] object |
|
| 198 |
#' contains valid `derive` function. |
|
| 199 |
v_next_best_td_samples <- function(object) {
|
|
| 200 | 3x |
v <- Validate() |
| 201 | 3x |
v$check( |
| 202 | 3x |
test_function(object@derive, nargs = 1), |
| 203 | 3x |
"derive must have a single argument" |
| 204 |
) |
|
| 205 | 3x |
v$check( |
| 206 | 3x |
test_number(object@derive(1:5)), |
| 207 | 3x |
"derive must accept numerical vector as an argument and return a number" |
| 208 |
) |
|
| 209 | 3x |
v$result() |
| 210 |
} |
|
| 211 | ||
| 212 |
#' @describeIn v_next_best validates that the [`NextBestMaxGainSamples`] object |
|
| 213 |
#' contains valid `derive` and `mg_derive` functions. |
|
| 214 |
v_next_best_max_gain_samples <- function(object) {
|
|
| 215 | 5x |
v <- Validate() |
| 216 | 5x |
v$check( |
| 217 | 5x |
test_function(object@derive, nargs = 1), |
| 218 | 5x |
"derive must have a single argument" |
| 219 |
) |
|
| 220 | 5x |
v$check( |
| 221 | 5x |
test_number(object@derive(1:5)), |
| 222 | 5x |
"derive must accept numerical vector as an argument and return a number" |
| 223 |
) |
|
| 224 | 5x |
v$check( |
| 225 | 5x |
test_function(object@mg_derive, nargs = 1), |
| 226 | 5x |
"mg_derive must have a single argument" |
| 227 |
) |
|
| 228 | 5x |
v$check( |
| 229 | 5x |
test_number(object@mg_derive(1:5)), |
| 230 | 5x |
"mg_derive must accept numerical vector as an argument and return a number" |
| 231 |
) |
|
| 232 | 5x |
v$result() |
| 233 |
} |
|
| 234 | ||
| 235 |
#' @describeIn v_next_best validates that the [`NextBestProbMTDLTE`] object |
|
| 236 |
#' contains valid `target` probability and `method` string value. |
|
| 237 |
v_next_best_prob_mtd_lte <- function(object) {
|
|
| 238 | 5x |
v <- Validate() |
| 239 | 5x |
v$check( |
| 240 | 5x |
test_probability(object@target, bounds_closed = FALSE), |
| 241 | 5x |
"target must be a probability value from (0, 1) interval" |
| 242 |
) |
|
| 243 | 5x |
v$result() |
| 244 |
} |
|
| 245 | ||
| 246 |
#' @describeIn v_next_best validates that the [`NextBestProbMTDMinDist`] object |
|
| 247 |
#' contains valid `target` probability and `method` string value. |
|
| 248 |
v_next_best_prob_mtd_min_dist <- function(object) {
|
|
| 249 | 5x |
v <- Validate() |
| 250 | 5x |
v$check( |
| 251 | 5x |
test_probability(object@target, bounds_closed = FALSE), |
| 252 | 5x |
"target must be a probability value from (0, 1) interval" |
| 253 |
) |
|
| 254 | 5x |
v$result() |
| 255 |
} |
|
| 256 | ||
| 257 |
# Increments ---- |
|
| 258 | ||
| 259 |
#' Internal Helper Functions for Validation of [`Increments`] Objects |
|
| 260 |
#' |
|
| 261 |
#' @description `r lifecycle::badge("stable")`
|
|
| 262 |
#' |
|
| 263 |
#' These functions are only used internally to validate the format of an input |
|
| 264 |
#' [`Increments`] or inherited classes and therefore not exported. |
|
| 265 |
#' |
|
| 266 |
#' @name v_increments |
|
| 267 |
#' @param object (`Increments`)\cr object to validate. |
|
| 268 |
#' @return A `character` vector with the validation failure messages, |
|
| 269 |
#' or `TRUE` in case validation passes. |
|
| 270 |
NULL |
|
| 271 | ||
| 272 |
#' @describeIn v_increments validates that the [`IncrementsRelative`] object |
|
| 273 |
#' contains valid `intervals` and `increments` parameters. |
|
| 274 |
v_increments_relative <- function(object) {
|
|
| 275 | 6x |
v <- Validate() |
| 276 | 6x |
v$check( |
| 277 | 6x |
test_numeric( |
| 278 | 6x |
object@intervals, |
| 279 | 6x |
lower = 0, |
| 280 | 6x |
finite = TRUE, |
| 281 | 6x |
any.missing = FALSE, |
| 282 | 6x |
unique = TRUE, |
| 283 | 6x |
sorted = TRUE |
| 284 |
), |
|
| 285 | 6x |
"intervals has to be a numerical vector with unique, finite, non-negative and sorted non-missing values" |
| 286 |
) |
|
| 287 | 6x |
v$check( |
| 288 | 6x |
test_numeric( |
| 289 | 6x |
object@increments, |
| 290 | 6x |
finite = TRUE, |
| 291 | 6x |
any.missing = FALSE, |
| 292 | 6x |
len = length(object@intervals) |
| 293 |
), |
|
| 294 | 6x |
"increments has to be a numerical vector of the same length as `intervals` with finite values" |
| 295 |
) |
|
| 296 | 6x |
v$result() |
| 297 |
} |
|
| 298 | ||
| 299 |
#' @describeIn v_increments validates that the [`IncrementsRelativeParts`] object |
|
| 300 |
#' contains valid `dlt_start` and `clean_start` parameters. |
|
| 301 |
v_increments_relative_parts <- function(object) {
|
|
| 302 | 6x |
v <- Validate() |
| 303 | 6x |
is_dlt_start_ok <- test_int(object@dlt_start) |
| 304 | 6x |
v$check(is_dlt_start_ok, "dlt_start must be an integer number") |
| 305 | 6x |
if (is_dlt_start_ok) {
|
| 306 | 4x |
v$check( |
| 307 | 4x |
test_int(object@clean_start, lower = object@dlt_start), |
| 308 | 4x |
"clean_start must be an integer number and it must be >= dlt_start" |
| 309 |
) |
|
| 310 |
} |
|
| 311 | 6x |
v$result() |
| 312 |
} |
|
| 313 | ||
| 314 |
#' @describeIn v_increments validates that the [`IncrementsRelativeDLT`] object |
|
| 315 |
#' contains valid `intervals` and `increments` parameters. |
|
| 316 |
v_increments_relative_dlt <- function(object) {
|
|
| 317 | 6x |
v <- Validate() |
| 318 | 6x |
v$check( |
| 319 | 6x |
test_integer( |
| 320 | 6x |
object@intervals, |
| 321 | 6x |
lower = 0, |
| 322 | 6x |
any.missing = FALSE, |
| 323 | 6x |
unique = TRUE, |
| 324 | 6x |
sorted = TRUE |
| 325 |
), |
|
| 326 | 6x |
"intervals has to be an integer vector with unique, finite, non-negative and sorted non-missing values" |
| 327 |
) |
|
| 328 | 6x |
v$check( |
| 329 | 6x |
test_numeric( |
| 330 | 6x |
object@increments, |
| 331 | 6x |
finite = TRUE, |
| 332 | 6x |
any.missing = FALSE, |
| 333 | 6x |
len = length(object@intervals) |
| 334 |
), |
|
| 335 | 6x |
"increments has to be a numerical vector of the same length as `intervals` with finite values" |
| 336 |
) |
|
| 337 | 6x |
v$result() |
| 338 |
} |
|
| 339 | ||
| 340 |
#' @describeIn v_increments validates that the [`IncrementsDoseLevels`] object |
|
| 341 |
#' contains valid `levels` and `basis_level` option. |
|
| 342 |
v_increments_dose_levels <- function(object) {
|
|
| 343 | 9x |
v <- Validate() |
| 344 | 9x |
v$check( |
| 345 | 9x |
test_int(object@levels, lower = .Machine$double.xmin), |
| 346 | 9x |
"levels must be scalar positive integer" |
| 347 |
) |
|
| 348 | 9x |
v$check( |
| 349 | 9x |
test_string(object@basis_level, pattern = "^last$|^max$"), |
| 350 | 9x |
"basis_level must be either 'last' or 'max'" |
| 351 |
) |
|
| 352 | 9x |
v$result() |
| 353 |
} |
|
| 354 | ||
| 355 |
#' @describeIn v_increments validates that the [`IncrementsHSRBeta`] |
|
| 356 |
#' object contains valid probability target, threshold and shape parameters. |
|
| 357 |
v_increments_hsr_beta <- function(object) {
|
|
| 358 | 12x |
v <- Validate() |
| 359 | 12x |
v$check( |
| 360 | 12x |
test_probability(object@target, bounds_closed = FALSE), |
| 361 | 12x |
"target must be a probability value from (0, 1) interval" |
| 362 |
) |
|
| 363 | 12x |
v$check( |
| 364 | 12x |
test_probability(object@prob, bounds_closed = FALSE), |
| 365 | 12x |
"prob must be a probability value from (0, 1) interval" |
| 366 |
) |
|
| 367 | 12x |
v$check( |
| 368 | 12x |
test_number(object@a, lower = .Machine$double.xmin, finite = TRUE), |
| 369 | 12x |
"Beta distribution shape parameter a must be a positive scalar" |
| 370 |
) |
|
| 371 | 12x |
v$check( |
| 372 | 12x |
test_number(object@b, lower = .Machine$double.xmin, finite = TRUE), |
| 373 | 12x |
"Beta distribution shape parameter b must be a positive scalar" |
| 374 |
) |
|
| 375 | 12x |
v$result() |
| 376 |
} |
|
| 377 | ||
| 378 |
#' @describeIn v_increments validates that the [`IncrementsMin`] |
|
| 379 |
#' object contains a list with `Increments` objects. |
|
| 380 |
v_increments_min <- function(object) {
|
|
| 381 | 2x |
v <- Validate() |
| 382 | 2x |
v$check( |
| 383 | 2x |
all(sapply(object@increments_list, test_class, "Increments")), |
| 384 | 2x |
"all elements in increments_list must be of Increments class" |
| 385 |
) |
|
| 386 | 2x |
v$result() |
| 387 |
} |
|
| 388 | ||
| 389 |
#' @describeIn v_increments validates the [`IncrementsMaxToxProb`] |
|
| 390 |
v_increments_maxtoxprob <- function(object) {
|
|
| 391 | ! |
v <- Validate() |
| 392 | ! |
v$check( |
| 393 | ! |
test_probabilities(object@prob), |
| 394 | ! |
"prob must be a vector of probabilities with minimum length 1 and no missing values" |
| 395 |
) |
|
| 396 | ! |
v$result() |
| 397 |
} |
|
| 398 | ||
| 399 |
# Stopping ---- |
|
| 400 | ||
| 401 |
#' Internal Helper Functions for Validation of [`Stopping`] Objects |
|
| 402 |
#' |
|
| 403 |
#' @description `r lifecycle::badge("stable")`
|
|
| 404 |
#' |
|
| 405 |
#' These functions are only used internally to validate the format of an input |
|
| 406 |
#' [`Stopping`] or inherited classes and therefore not exported. |
|
| 407 |
#' |
|
| 408 |
#' @name v_stopping |
|
| 409 |
#' @param object (`Stopping`)\cr object to validate. |
|
| 410 |
#' @return A `character` vector with the validation failure messages, |
|
| 411 |
#' or `TRUE` in case validation passes. |
|
| 412 |
NULL |
|
| 413 | ||
| 414 |
#' @describeIn v_stopping validates that the [`StoppingCohortsNearDose`] |
|
| 415 |
#' object contains valid `nCohorts` and `percentage` parameters. |
|
| 416 |
v_stopping_cohorts_near_dose <- function(object) {
|
|
| 417 | 11x |
v <- Validate() |
| 418 | 11x |
v$check( |
| 419 | 11x |
test_int(object@nCohorts, lower = .Machine$double.xmin), |
| 420 | 11x |
"nCohorts must be positive integer scalar" |
| 421 |
) |
|
| 422 | 11x |
v$check( |
| 423 | 11x |
test_probability(object@percentage / 100), |
| 424 | 11x |
"percentage must be a number between 0 and 100" |
| 425 |
) |
|
| 426 | 11x |
v$result() |
| 427 |
} |
|
| 428 | ||
| 429 |
#' @describeIn v_stopping validates that the [`StoppingPatientsNearDose`] |
|
| 430 |
#' object contains valid `nPatients` and `percentage` parameters. |
|
| 431 |
v_stopping_patients_near_dose <- function(object) {
|
|
| 432 | 12x |
v <- Validate() |
| 433 | 12x |
v$check( |
| 434 | 12x |
test_int(object@nPatients, lower = .Machine$double.xmin), |
| 435 | 12x |
"nPatients must be positive integer scalar" |
| 436 |
) |
|
| 437 | 12x |
v$check( |
| 438 | 12x |
test_probability(object@percentage / 100), |
| 439 | 12x |
"percentage must be a number between 0 and 100" |
| 440 |
) |
|
| 441 | 12x |
v$check( |
| 442 | 12x |
test_flag(object@include_backfill), |
| 443 | 12x |
"include_backfill must be a flag" |
| 444 |
) |
|
| 445 | 12x |
v$result() |
| 446 |
} |
|
| 447 | ||
| 448 |
#' @describeIn v_stopping validates that the [`StoppingMinCohorts`] |
|
| 449 |
#' object contains valid `nCohorts` parameter. |
|
| 450 |
v_stopping_min_cohorts <- function(object) {
|
|
| 451 | 4x |
v <- Validate() |
| 452 | 4x |
v$check( |
| 453 | 4x |
test_int(object@nCohorts, lower = .Machine$double.xmin), |
| 454 | 4x |
"nCohorts must be positive integer scalar" |
| 455 |
) |
|
| 456 | 4x |
v$result() |
| 457 |
} |
|
| 458 | ||
| 459 |
#' @describeIn v_stopping validates that the [`StoppingMinPatients`] |
|
| 460 |
#' object contains valid `nPatients` parameter. |
|
| 461 |
v_stopping_min_patients <- function(object) {
|
|
| 462 | 4x |
v <- Validate() |
| 463 | 4x |
v$check( |
| 464 | 4x |
test_int(object@nPatients, lower = .Machine$double.xmin), |
| 465 | 4x |
"nPatients must be positive integer scalar" |
| 466 |
) |
|
| 467 | 4x |
v$result() |
| 468 |
} |
|
| 469 | ||
| 470 |
#' @describeIn v_stopping validates that the [`StoppingTargetProb`] |
|
| 471 |
#' object contains valid `target` and `prob` parameters. |
|
| 472 |
v_stopping_target_prob <- function(object) {
|
|
| 473 | 10x |
v <- Validate() |
| 474 | 10x |
v$check( |
| 475 | 10x |
test_probability_range(object@target), |
| 476 | 10x |
"target has to be a probability range" |
| 477 |
) |
|
| 478 | 10x |
v$check( |
| 479 | 10x |
test_probability(object@prob, bounds_closed = FALSE), |
| 480 | 10x |
"prob must be a probability value from (0, 1) interval" |
| 481 |
) |
|
| 482 | 10x |
v$result() |
| 483 |
} |
|
| 484 | ||
| 485 |
#' @describeIn v_stopping validates that the [`StoppingMTDdistribution`] |
|
| 486 |
#' object contains valid `target`, `thresh` and `prob` parameters. |
|
| 487 |
v_stopping_mtd_distribution <- function(object) {
|
|
| 488 | 13x |
v <- Validate() |
| 489 | 13x |
v$check( |
| 490 | 13x |
test_probability(object@target, bounds_closed = FALSE), |
| 491 | 13x |
"target must be a probability value from (0, 1) interval" |
| 492 |
) |
|
| 493 | 13x |
v$check( |
| 494 | 13x |
test_probability(object@thresh, bounds_closed = FALSE), |
| 495 | 13x |
"thresh must be a probability value from (0, 1) interval" |
| 496 |
) |
|
| 497 | 13x |
v$check( |
| 498 | 13x |
test_probability(object@prob, bounds_closed = FALSE), |
| 499 | 13x |
"prob must be a probability value from (0, 1) interval" |
| 500 |
) |
|
| 501 | 13x |
v$result() |
| 502 |
} |
|
| 503 | ||
| 504 |
#' @describeIn v_stopping validates that the [`StoppingMTDCV`] object |
|
| 505 |
#' contains valid probability target and percentage threshold. |
|
| 506 |
v_stopping_mtd_cv <- function(object) {
|
|
| 507 | 11x |
v <- Validate() |
| 508 | 11x |
v$check( |
| 509 | 11x |
test_probability(object@target, bounds_closed = FALSE), |
| 510 | 11x |
"target must be probability value from (0, 1) interval" |
| 511 |
) |
|
| 512 | 11x |
v$check( |
| 513 | 11x |
test_probability(object@thresh_cv / 100, bounds_closed = c(FALSE, TRUE)), |
| 514 | 11x |
"thresh_cv must be percentage > 0" |
| 515 |
) |
|
| 516 | 11x |
v$result() |
| 517 |
} |
|
| 518 | ||
| 519 |
#' @describeIn v_stopping validates that the [`StoppingTargetBiomarker`] object |
|
| 520 |
#' contains valid `target`, `is_relative` and `prob`slots. |
|
| 521 |
v_stopping_target_biomarker <- function(object) {
|
|
| 522 | 16x |
v <- Validate() |
| 523 | 16x |
v$check( |
| 524 | 16x |
test_flag(object@is_relative), |
| 525 | 16x |
"is_relative must be a flag" |
| 526 |
) |
|
| 527 | 16x |
if (isTRUE(object@is_relative)) {
|
| 528 | 10x |
v$check( |
| 529 | 10x |
test_probability_range(object@target), |
| 530 | 10x |
"target has to be a probability range when is_relative flag is 'TRUE'" |
| 531 |
) |
|
| 532 |
} else {
|
|
| 533 | 6x |
v$check( |
| 534 | 6x |
test_range(object@target, finite = TRUE), |
| 535 | 6x |
"target must be a numeric range" |
| 536 |
) |
|
| 537 |
} |
|
| 538 | 16x |
v$check( |
| 539 | 16x |
test_probability(object@prob, bounds_closed = FALSE), |
| 540 | 16x |
"prob must be a probability value from (0, 1) interval" |
| 541 |
) |
|
| 542 | 16x |
v$result() |
| 543 |
} |
|
| 544 | ||
| 545 |
#' @describeIn v_stopping validates that the [`StoppingList`] object |
|
| 546 |
#' contains valid `stop_list`, `summary` slots. |
|
| 547 |
v_stopping_list <- function(object) {
|
|
| 548 | 8x |
v <- Validate() |
| 549 | 8x |
v$check( |
| 550 | 8x |
all(sapply(object@stop_list, test_class, "Stopping")), |
| 551 | 8x |
"every stop_list element must be of class 'Stopping'" |
| 552 |
) |
|
| 553 | 8x |
is_summary_ok <- test_function(object@summary, nargs = 1) |
| 554 | 8x |
v$check( |
| 555 | 8x |
is_summary_ok, |
| 556 | 8x |
"summary must be a function that accepts a single argument, without ..." |
| 557 |
) |
|
| 558 | 8x |
if (is_summary_ok) {
|
| 559 | 5x |
summary_res <- object@summary( |
| 560 | 5x |
rep(c(TRUE, FALSE), length.out = length(object@stop_list)) |
| 561 |
) |
|
| 562 | 5x |
v$check( |
| 563 | 5x |
test_flag(summary_res), |
| 564 | 5x |
"summary must accept a logical vector of the same length as 'stop_list' and return a boolean value" |
| 565 |
) |
|
| 566 |
} |
|
| 567 | 8x |
v$result() |
| 568 |
} |
|
| 569 | ||
| 570 |
#' @describeIn v_stopping validates that the [`StoppingAll`] object |
|
| 571 |
#' contains valid `stop_list` slot. |
|
| 572 |
v_stopping_all <- function(object) {
|
|
| 573 | 3x |
v <- Validate() |
| 574 | 3x |
v$check( |
| 575 | 3x |
all(sapply(object@stop_list, test_class, "Stopping")), |
| 576 | 3x |
"every stop_list element must be of class 'Stopping'" |
| 577 |
) |
|
| 578 | 3x |
v$result() |
| 579 |
} |
|
| 580 | ||
| 581 |
#' @describeIn v_stopping validates that the [`StoppingTDCIRatio`] object |
|
| 582 |
#' contains valid `target_ratio` and `prob_target` slots. |
|
| 583 |
v_stopping_tdci_ratio <- function(object) {
|
|
| 584 | 9x |
v <- Validate() |
| 585 | 9x |
v$check( |
| 586 | 9x |
test_number( |
| 587 | 9x |
object@target_ratio, |
| 588 | 9x |
lower = .Machine$double.xmin, |
| 589 | 9x |
finite = TRUE |
| 590 |
), |
|
| 591 | 9x |
"target_ratio must be a positive number" |
| 592 |
) |
|
| 593 | 9x |
v$check( |
| 594 | 9x |
test_probability(object@prob_target), |
| 595 | 9x |
"prob_target must be a probability value from [0, 1] interval" |
| 596 |
) |
|
| 597 | 9x |
v$result() |
| 598 |
} |
|
| 599 | ||
| 600 |
# CohortSize ---- |
|
| 601 | ||
| 602 |
#' Internal Helper Functions for Validation of [`CohortSize`] Objects |
|
| 603 |
#' |
|
| 604 |
#' @description `r lifecycle::badge("stable")`
|
|
| 605 |
#' |
|
| 606 |
#' These functions are only used internally to validate the format of an input |
|
| 607 |
#' [`CohortSize`] or inherited classes and therefore not exported. |
|
| 608 |
#' |
|
| 609 |
#' @name v_cohort_size |
|
| 610 |
#' @param object (`CohortSize`)\cr object to validate. |
|
| 611 |
#' @return A `character` vector with the validation failure messages, |
|
| 612 |
#' or `TRUE` in case validation passes. |
|
| 613 |
NULL |
|
| 614 | ||
| 615 |
#' @describeIn v_cohort_size validates that the [`CohortSizeRange`] object |
|
| 616 |
#' contains valid `intervals` and `cohort_size` slots. |
|
| 617 |
v_cohort_size_range <- function(object) {
|
|
| 618 | 13x |
v <- Validate() |
| 619 | 13x |
v$check( |
| 620 | 13x |
test_numeric( |
| 621 | 13x |
object@intervals, |
| 622 | 13x |
lower = 0, |
| 623 | 13x |
finite = TRUE, |
| 624 | 13x |
any.missing = FALSE, |
| 625 | 13x |
min.len = 1, |
| 626 | 13x |
unique = TRUE, |
| 627 | 13x |
sorted = TRUE |
| 628 |
), |
|
| 629 | 13x |
"intervals must be a numeric vector with non-negative, sorted (asc.) and unique values" |
| 630 |
) |
|
| 631 | 13x |
v$check( |
| 632 | 13x |
test_integer( |
| 633 | 13x |
object@cohort_size, |
| 634 | 13x |
lower = 0, |
| 635 | 13x |
any.missing = FALSE, |
| 636 | 13x |
len = length(object@intervals) |
| 637 |
), |
|
| 638 | 13x |
"cohort_size must be an integer vector of the same length as intervals, containing non-negative values only" |
| 639 |
) |
|
| 640 | 13x |
v$result() |
| 641 |
} |
|
| 642 | ||
| 643 |
#' @describeIn v_cohort_size validates that the [`CohortSizeDLT`] object |
|
| 644 |
#' contains valid `intervals` and `cohort_size` slots. |
|
| 645 |
v_cohort_size_dlt <- function(object) {
|
|
| 646 | 12x |
v <- Validate() |
| 647 | 12x |
v$check( |
| 648 | 12x |
test_integer( |
| 649 | 12x |
object@intervals, |
| 650 | 12x |
lower = 0, |
| 651 | 12x |
any.missing = FALSE, |
| 652 | 12x |
min.len = 1, |
| 653 | 12x |
unique = TRUE, |
| 654 | 12x |
sorted = TRUE |
| 655 |
), |
|
| 656 | 12x |
"intervals must be an integer vector with non-negative, sorted (asc.) and unique values" |
| 657 |
) |
|
| 658 | 12x |
v$check( |
| 659 | 12x |
test_integer( |
| 660 | 12x |
object@cohort_size, |
| 661 | 12x |
lower = 0, |
| 662 | 12x |
any.missing = FALSE, |
| 663 | 12x |
len = length(object@intervals) |
| 664 |
), |
|
| 665 | 12x |
"cohort_size must be an integer vector of the same length as intervals, containing non-negative values only" |
| 666 |
) |
|
| 667 | 12x |
v$result() |
| 668 |
} |
|
| 669 | ||
| 670 |
#' @describeIn v_cohort_size validates that the [`CohortSizeConst`] object |
|
| 671 |
#' contains valid `size` slot. |
|
| 672 |
v_cohort_size_const <- function(object) {
|
|
| 673 | 5x |
v <- Validate() |
| 674 | 5x |
v$check( |
| 675 | 5x |
test_int(object@size, lower = 0), |
| 676 | 5x |
"size needs to be a non-negative scalar" |
| 677 |
) |
|
| 678 | 5x |
v$result() |
| 679 |
} |
|
| 680 | ||
| 681 |
#' @describeIn v_cohort_size validates that the [`CohortSizeRandom`] object |
|
| 682 |
#' contains valid `min_size` and `max_size` slots. |
|
| 683 |
v_cohort_size_random <- function(object) {
|
|
| 684 | 4x |
v <- Validate() |
| 685 | 4x |
v$check( |
| 686 | 4x |
test_int(object@min_size, lower = 1), |
| 687 | 4x |
"min_size needs to be a positive integer" |
| 688 |
) |
|
| 689 | 4x |
v$check( |
| 690 | 4x |
test_int(object@max_size, lower = object@min_size + 1), |
| 691 | 4x |
"max_size needs to be an integer larger than min_size" |
| 692 |
) |
|
| 693 | 4x |
v$result() |
| 694 |
} |
|
| 695 | ||
| 696 |
#' @describeIn v_cohort_size validates that the [`CohortSizeParts`] object |
|
| 697 |
#' contains valid `sizes` slot. |
|
| 698 |
v_cohort_size_parts <- function(object) {
|
|
| 699 | 9x |
v <- Validate() |
| 700 | 9x |
v$check( |
| 701 | 9x |
test_integer( |
| 702 | 9x |
object@cohort_sizes, |
| 703 | 9x |
lower = .Machine$double.xmin, |
| 704 | 9x |
any.missing = FALSE, |
| 705 | 9x |
len = 2 |
| 706 |
), |
|
| 707 | 9x |
"cohort_sizes needs to be an integer vector of length 2 with all elements positive" |
| 708 |
) |
|
| 709 | 9x |
v$result() |
| 710 |
} |
|
| 711 | ||
| 712 |
#' @describeIn v_cohort_size validates that the [`CohortSizeMax`] object |
|
| 713 |
#' contains valid `cohort_sizes` slot. |
|
| 714 |
v_cohort_size_max <- function(object) {
|
|
| 715 | 8x |
v <- Validate() |
| 716 | 8x |
v$check( |
| 717 | 8x |
test_list( |
| 718 | 8x |
object@cohort_sizes, |
| 719 | 8x |
types = "CohortSize", |
| 720 | 8x |
any.missing = FALSE, |
| 721 | 8x |
min.len = 2, |
| 722 | 8x |
unique = TRUE |
| 723 |
), |
|
| 724 | 8x |
"cohort_sizes must be a list of CohortSize (unique) objects only and be of length >= 2" |
| 725 |
) |
|
| 726 | 8x |
v$result() |
| 727 |
} |
|
| 728 | ||
| 729 |
# SafetyWindow ---- |
|
| 730 | ||
| 731 |
#' Internal Helper Functions for Validation of [`SafetyWindow`] Objects |
|
| 732 |
#' |
|
| 733 |
#' @description `r lifecycle::badge("stable")`
|
|
| 734 |
#' |
|
| 735 |
#' These functions are only used internally to validate the format of an input |
|
| 736 |
#' [`SafetyWindow`] or inherited classes and therefore not exported. |
|
| 737 |
#' |
|
| 738 |
#' @name v_safety_window |
|
| 739 |
#' @param object (`SafetyWindow`)\cr object to validate. |
|
| 740 |
#' @return A `character` vector with the validation failure messages, |
|
| 741 |
#' or `TRUE` in case validation passes. |
|
| 742 |
NULL |
|
| 743 | ||
| 744 |
#' @describeIn v_safety_window validates that the [`SafetyWindowSize`] object |
|
| 745 |
#' contains valid slots. |
|
| 746 |
v_safety_window_size <- function(object) {
|
|
| 747 | 21x |
v <- Validate() |
| 748 | 21x |
v$check( |
| 749 | 21x |
test_list(object@gap, types = "integer", any.missing = FALSE, min.len = 1), |
| 750 | 21x |
"gap must be a list of length >= 1 with integer vectors only" |
| 751 |
) |
|
| 752 | 21x |
v$check( |
| 753 | 21x |
all(sapply( |
| 754 | 21x |
object@gap, |
| 755 | 21x |
test_integer, |
| 756 | 21x |
lower = 0, |
| 757 | 21x |
any.missing = FALSE, |
| 758 | 21x |
min.len = 1 |
| 759 |
)), |
|
| 760 | 21x |
"every element in gap list has to be an integer vector with non-negative and non-missing values" |
| 761 |
) |
|
| 762 | 21x |
pg_len <- length(object@gap) |
| 763 | 21x |
v$check( |
| 764 | 21x |
test_integer( |
| 765 | 21x |
object@size, |
| 766 | 21x |
lower = .Machine$double.xmin, |
| 767 | 21x |
any.missing = FALSE, |
| 768 | 21x |
len = pg_len, |
| 769 | 21x |
unique = TRUE, |
| 770 | 21x |
sorted = TRUE |
| 771 |
), |
|
| 772 | 21x |
"size has to be an integer vector, of the same length as gap, with positive, unique and sorted non-missing values" |
| 773 |
) |
|
| 774 | 21x |
v$check( |
| 775 | 21x |
test_int(object@follow, lower = .Machine$double.xmin), |
| 776 | 21x |
"follow has to be a positive integer number" |
| 777 |
) |
|
| 778 | 21x |
v$check( |
| 779 | 21x |
test_int(object@follow_min, lower = .Machine$double.xmin), |
| 780 | 21x |
"follow_min has to be a positive integer number" |
| 781 |
) |
|
| 782 | 21x |
v$result() |
| 783 |
} |
|
| 784 | ||
| 785 |
#' @describeIn v_safety_window validates that the [`SafetyWindowConst`] object |
|
| 786 |
#' contains valid slots. |
|
| 787 |
v_safety_window_const <- function(object) {
|
|
| 788 | 15x |
v <- Validate() |
| 789 | 15x |
v$check( |
| 790 | 15x |
test_integer(object@gap, lower = 0, any.missing = FALSE), |
| 791 | 15x |
"gap has to be an integer vector with non-negative and non-missing elements" |
| 792 |
) |
|
| 793 | 15x |
v$check( |
| 794 | 15x |
test_int(object@follow, lower = .Machine$double.xmin), |
| 795 | 15x |
"follow has to be a positive integer number" |
| 796 |
) |
|
| 797 | 15x |
v$check( |
| 798 | 15x |
test_int(object@follow_min, lower = .Machine$double.xmin), |
| 799 | 15x |
"follow_min has to be a positive integer number" |
| 800 |
) |
|
| 801 | 15x |
v$result() |
| 802 |
} |
|
| 803 | ||
| 804 |
#' @describeIn v_next_best validates that the [`NextBestOrdinal`] object |
|
| 805 |
#' contains valid `grade` and standard `NextBest` rule. |
|
| 806 |
v_next_best_ordinal <- function(object) {
|
|
| 807 | ! |
v <- Validate() |
| 808 | ! |
v$check( |
| 809 | ! |
test_integer(object@grade, lower = 1), |
| 810 | ! |
"grade must be a positive integer" |
| 811 |
) |
|
| 812 | ! |
v$check( |
| 813 | ! |
test_class(object@rule, "NextBest"), |
| 814 | ! |
"rule must be a NextBest object" |
| 815 |
) |
|
| 816 | ! |
v$result() |
| 817 |
} |
|
| 818 | ||
| 819 |
#' @describeIn v_increments validates that the [`IncrementsOrdinal`] object |
|
| 820 |
#' contains valid `grade` and standard `Increments` rule. |
|
| 821 |
v_increments_ordinal <- function(object) {
|
|
| 822 | ! |
v <- Validate() |
| 823 | ! |
v$check( |
| 824 | ! |
test_integer(object@grade, lower = 1), |
| 825 | ! |
"grade must be a positive integer" |
| 826 |
) |
|
| 827 | ! |
v$check( |
| 828 | ! |
test_class(object@rule, "Increments"), |
| 829 | ! |
"rule must be a Increments object" |
| 830 |
) |
|
| 831 | ! |
v$result() |
| 832 |
} |
|
| 833 | ||
| 834 |
#' @describeIn v_increments validates that the [`CohortSizeOrdinal`] object |
|
| 835 |
#' contains valid `grade` and standard `CohortSize` rule. |
|
| 836 |
v_cohort_size_ordinal <- function(object) {
|
|
| 837 | ! |
v <- Validate() |
| 838 | ! |
v$check( |
| 839 | ! |
test_integer(object@grade, lower = 1), |
| 840 | ! |
"grade must be a positive integer" |
| 841 |
) |
|
| 842 | ! |
v$check( |
| 843 | ! |
test_class(object@rule, "CohortSize"), |
| 844 | ! |
"rule must be a CohortSize object" |
| 845 |
) |
|
| 846 | ! |
v$result() |
| 847 |
} |
| 1 |
#' @include McmcOptions-class.R |
|
| 2 |
#' @include Model-methods.R |
|
| 3 |
#' @include fromQuantiles.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 |
# size ---- |
|
| 7 | ||
| 8 |
## Samples ---- |
|
| 9 | ||
| 10 |
#' @describeIn size get the number of MCMC samples from `Samples` object. |
|
| 11 |
#' @aliases size-Samples |
|
| 12 |
#' |
|
| 13 |
#' @export |
|
| 14 |
#' @example examples/Samples-methods-size.R |
|
| 15 |
#' |
|
| 16 |
setMethod( |
|
| 17 |
f = "size", |
|
| 18 |
signature = signature(object = "Samples"), |
|
| 19 |
definition = function(object, ...) {
|
|
| 20 | 44397x |
size(object@options) |
| 21 |
} |
|
| 22 |
) |
|
| 23 | ||
| 24 |
# names ---- |
|
| 25 | ||
| 26 |
## Samples ---- |
|
| 27 | ||
| 28 |
#' The Names of the Sampled Parameters |
|
| 29 |
#' |
|
| 30 |
#' @description `r lifecycle::badge("stable")`
|
|
| 31 |
#' |
|
| 32 |
#' A method that returns names of the parameters that are sampled. |
|
| 33 |
#' |
|
| 34 |
#' @param x (`Samples`)\cr object with samples. |
|
| 35 |
#' |
|
| 36 |
#' @aliases names-Samples |
|
| 37 |
#' @export |
|
| 38 |
#' @example examples/Samples-methods-names.R |
|
| 39 |
#' |
|
| 40 |
setMethod( |
|
| 41 |
f = "names", |
|
| 42 |
signature = signature(x = "Samples"), |
|
| 43 |
definition = function(x) {
|
|
| 44 | 29198x |
names(x@data) |
| 45 |
} |
|
| 46 |
) |
|
| 47 | ||
| 48 |
## -------------------------------------------------- |
|
| 49 |
## Extract certain parameter from "Samples" object to produce |
|
| 50 |
## plots with "ggmcmc" package |
|
| 51 |
## -------------------------------------------------- |
|
| 52 | ||
| 53 |
# The next line is required to suppress the message "Creating a generic function |
|
| 54 |
# for ‘get’ from package ‘base’ in package ‘crmPack’" on package load. |
|
| 55 |
# See https://github.com/openpharma/crmPack/issues/723 |
|
| 56 |
setGeneric("get")
|
|
| 57 | ||
| 58 |
#' Get specific parameter samples and produce a data.frame |
|
| 59 |
#' |
|
| 60 |
#' Here you have to specify with \code{pos} which
|
|
| 61 |
#' parameter you would like to extract from the \code{\linkS4class{Samples}}
|
|
| 62 |
#' object |
|
| 63 |
#' |
|
| 64 |
#' @param x the \code{\linkS4class{Samples}} object
|
|
| 65 |
#' @param pos the name of the parameter |
|
| 66 |
#' @param envir for vectorial parameters, you can give the indices of the |
|
| 67 |
#' elements you would like to extract. If \code{NULL}, the whole vector samples
|
|
| 68 |
#' will be returned |
|
| 69 |
#' @param mode not used |
|
| 70 |
#' @param inherits not used |
|
| 71 |
#' |
|
| 72 |
#' @return the data frame suitable for use with \code{\link[ggmcmc]{ggmcmc}}
|
|
| 73 |
#' |
|
| 74 |
#' @example examples/Sample-methods-get.R |
|
| 75 |
#' @export |
|
| 76 |
#' @keywords methods |
|
| 77 |
setMethod( |
|
| 78 |
"get", |
|
| 79 |
signature = signature( |
|
| 80 |
x = "Samples", |
|
| 81 |
pos = "character", |
|
| 82 |
envir = "ANY", |
|
| 83 |
mode = "ANY", |
|
| 84 |
inherits = "ANY" |
|
| 85 |
), |
|
| 86 |
def = function(x, pos, envir = NULL, mode = NULL, inherits = NULL) {
|
|
| 87 |
## check the parameter name |
|
| 88 | 17x |
assert_scalar(pos) |
| 89 | 16x |
assert_choice(pos, names(x)) |
| 90 | ||
| 91 |
## get the samples for this parameter |
|
| 92 | 15x |
d <- x@data[[pos]] |
| 93 |
## this can be either a vector or a matrix |
|
| 94 | ||
| 95 |
## how many parameters do we have? |
|
| 96 | 15x |
nPars <- NCOL(d) |
| 97 | ||
| 98 |
## what are the names of all parameter |
|
| 99 |
## elements? |
|
| 100 | 15x |
elements <- |
| 101 | 15x |
if (nPars == 1L) {
|
| 102 | 11x |
pos |
| 103 |
} else {
|
|
| 104 | 4x |
paste(pos, "[", seq_len(nPars), "]", sep = "") |
| 105 |
} |
|
| 106 | ||
| 107 |
## in case we have a vector parameter |
|
| 108 | 15x |
if (nPars > 1L) {
|
| 109 |
## what are the indices to be returned? |
|
| 110 | 4x |
indices <- |
| 111 | 4x |
if (is.null(envir)) {
|
| 112 | 1x |
seq_along(elements) |
| 113 |
} else {
|
|
| 114 | 3x |
assert_integer(envir) |
| 115 | 1x |
assert_subset(envir, seq_along(elements)) |
| 116 |
} |
|
| 117 | ||
| 118 |
## subset the data matrix and par names appropriately |
|
| 119 | 1x |
d <- d[, indices, drop = FALSE] |
| 120 | 1x |
elements <- elements[indices] |
| 121 | ||
| 122 |
## and also reduce the number of parameters |
|
| 123 | 1x |
nPars <- length(indices) |
| 124 |
} |
|
| 125 | ||
| 126 |
## now we can build |
|
| 127 | 12x |
ret <- data.frame( |
| 128 | 12x |
Iteration = seq_len(NROW(d)), |
| 129 | 12x |
Chain = 1L, |
| 130 | 12x |
Parameter = factor(rep(elements, each = NROW(d)), levels = elements), |
| 131 | 12x |
value = as.numeric(d) |
| 132 |
) |
|
| 133 | ||
| 134 |
## add the attributes |
|
| 135 | 12x |
structure( |
| 136 | 12x |
ret, |
| 137 | 12x |
nChains = 1L, |
| 138 | 12x |
nParameters = nPars, |
| 139 | 12x |
nIterations = x@options@iterations, |
| 140 | 12x |
nBurnin = x@options@burnin, |
| 141 | 12x |
nThin = x@options@step, |
| 142 | 12x |
description = elements, |
| 143 | 12x |
parallel = FALSE |
| 144 |
) |
|
| 145 |
} |
|
| 146 |
) |
|
| 147 | ||
| 148 | ||
| 149 |
## -------------------------------------------------- |
|
| 150 |
## Get fitted curves from Samples |
|
| 151 |
## -------------------------------------------------- |
|
| 152 | ||
| 153 |
#' Fit method for the Samples class |
|
| 154 |
#' |
|
| 155 |
#' Note this new generic function is necessary because the \code{\link{fitted}}
|
|
| 156 |
#' function only allows the first argument \code{object} to appear in the
|
|
| 157 |
#' signature. But we need also other arguments in the signature. |
|
| 158 |
#' |
|
| 159 |
#' @param object the \code{\linkS4class{Samples}} object
|
|
| 160 |
#' @param model the \code{\linkS4class{GeneralModel}} object
|
|
| 161 |
#' @param data the \code{\linkS4class{Data}} object
|
|
| 162 |
#' @param \dots passed down to the [prob()] method. |
|
| 163 |
#' @return the data frame with required information (see method details) |
|
| 164 |
#' |
|
| 165 |
#' @export |
|
| 166 |
#' @keywords methods |
|
| 167 |
setGeneric( |
|
| 168 |
"fit", |
|
| 169 |
def = function(object, model, data, ...) {
|
|
| 170 |
## there should be no default method, |
|
| 171 |
## therefore just forward to next method! |
|
| 172 | 157x |
standardGeneric("fit")
|
| 173 |
}, |
|
| 174 |
valueClass = "data.frame" |
|
| 175 |
) |
|
| 176 | ||
| 177 | ||
| 178 |
## -------------------------------------------------- |
|
| 179 |
## Get fitted dose-tox curve from Samples |
|
| 180 |
## -------------------------------------------------- |
|
| 181 | ||
| 182 |
#' @param points at which dose levels is the fit requested? default is the dose |
|
| 183 |
#' grid |
|
| 184 |
#' @param quantiles the quantiles to be calculated (default: 0.025 and |
|
| 185 |
#' 0.975) |
|
| 186 |
#' @param middle the function for computing the middle point. Default: |
|
| 187 |
#' \code{\link{mean}}
|
|
| 188 |
#' |
|
| 189 |
#' @describeIn fit This method returns a data frame with dose, middle, lower |
|
| 190 |
#' and upper quantiles for the dose-toxicity curve |
|
| 191 |
#' @example examples/Sample-methods-fit.R |
|
| 192 |
#' |
|
| 193 |
setMethod( |
|
| 194 |
"fit", |
|
| 195 |
signature = signature( |
|
| 196 |
object = "Samples", |
|
| 197 |
model = "GeneralModel", |
|
| 198 |
data = "Data" |
|
| 199 |
), |
|
| 200 |
def = function( |
|
| 201 |
object, |
|
| 202 |
model, |
|
| 203 |
data, |
|
| 204 |
points = data@doseGrid, |
|
| 205 |
quantiles = c(0.025, 0.975), |
|
| 206 |
middle = mean, |
|
| 207 |
... |
|
| 208 |
) {
|
|
| 209 |
## some checks |
|
| 210 | 107x |
assert_probability_range(quantiles) |
| 211 | 106x |
assert_numeric(points) |
| 212 | ||
| 213 |
## first we have to get samples from the dose-tox |
|
| 214 |
## curve at the dose grid points. |
|
| 215 | 105x |
probSamples <- matrix( |
| 216 | 105x |
nrow = size(object), |
| 217 | 105x |
ncol = length(points) |
| 218 |
) |
|
| 219 | ||
| 220 |
## evaluate the probs, for all samples. |
|
| 221 | 105x |
for (i in seq_along(points)) {
|
| 222 |
## Now we want to evaluate for the |
|
| 223 |
## following dose: |
|
| 224 | 4500x |
probSamples[, i] <- prob( |
| 225 | 4500x |
dose = points[i], |
| 226 | 4500x |
model, |
| 227 | 4500x |
object, |
| 228 |
... |
|
| 229 |
) |
|
| 230 |
} |
|
| 231 | ||
| 232 |
## extract middle curve |
|
| 233 | 105x |
middleCurve <- apply(probSamples, 2L, FUN = middle) |
| 234 | ||
| 235 |
## extract quantiles |
|
| 236 | 105x |
quantCurve <- apply(probSamples, 2L, quantile, prob = quantiles) |
| 237 | ||
| 238 |
## now create the data frame |
|
| 239 | 105x |
data.frame( |
| 240 | 105x |
dose = points, |
| 241 | 105x |
middle = middleCurve, |
| 242 | 105x |
lower = quantCurve[1, ], |
| 243 | 105x |
upper = quantCurve[2, ] |
| 244 |
) |
|
| 245 |
} |
|
| 246 |
) |
|
| 247 | ||
| 248 |
## -------------------------------------------------- |
|
| 249 |
## Get fitted dose-tox and dose-biomarker curves from Samples |
|
| 250 |
## -------------------------------------------------- |
|
| 251 | ||
| 252 |
#' @describeIn fit This method returns a data frame with dose, and middle, |
|
| 253 |
#' lower and upper quantiles, for both the dose-tox and dose-biomarker (suffix |
|
| 254 |
#' "Biomarker") curves, for all grid points (Note that currently only the grid |
|
| 255 |
#' points can be used, because the DualEndpointRW models only allow that) |
|
| 256 |
#' |
|
| 257 |
#' @example examples/Sample-methods-fit-DualEndpoint.R |
|
| 258 |
setMethod( |
|
| 259 |
"fit", |
|
| 260 |
signature = signature( |
|
| 261 |
object = "Samples", |
|
| 262 |
model = "DualEndpoint", |
|
| 263 |
data = "DataDual" |
|
| 264 |
), |
|
| 265 |
def = function( |
|
| 266 |
object, |
|
| 267 |
model, |
|
| 268 |
data, |
|
| 269 |
quantiles = c(0.025, 0.975), |
|
| 270 |
middle = mean, |
|
| 271 |
... |
|
| 272 |
) {
|
|
| 273 |
## some checks |
|
| 274 | 10x |
assert_probability_range(quantiles) |
| 275 | ||
| 276 |
## first obtain the dose-tox curve results from the parent method |
|
| 277 | 10x |
start <- callNextMethod( |
| 278 | 10x |
object = object, |
| 279 | 10x |
model = model, |
| 280 | 10x |
data = data, |
| 281 | 10x |
points = data@doseGrid, |
| 282 | 10x |
quantiles = quantiles, |
| 283 | 10x |
middle = middle, |
| 284 |
... |
|
| 285 |
) |
|
| 286 | ||
| 287 |
## now obtain the dose-biomarker results |
|
| 288 | ||
| 289 |
## get the biomarker level samples |
|
| 290 |
## at the dose grid points. |
|
| 291 | 10x |
biomLevelSamples <- biomarker( |
| 292 | 10x |
xLevel = seq_len(data@nGrid), |
| 293 | 10x |
model, |
| 294 | 10x |
samples = object |
| 295 |
) |
|
| 296 | ||
| 297 |
## extract middle curve |
|
| 298 | 10x |
middleCurve <- apply(biomLevelSamples, 2L, FUN = middle) |
| 299 | ||
| 300 |
## extract quantiles |
|
| 301 | 10x |
quantCurve <- apply(biomLevelSamples, 2L, quantile, prob = quantiles) |
| 302 | ||
| 303 |
## now create the data frame |
|
| 304 | 10x |
biomResults <- data.frame( |
| 305 | 10x |
middleBiomarker = middleCurve, |
| 306 | 10x |
lowerBiomarker = quantCurve[1, ], |
| 307 | 10x |
upperBiomarker = quantCurve[2, ] |
| 308 |
) |
|
| 309 | ||
| 310 |
## return both, pasted together |
|
| 311 | 10x |
cbind(start, biomResults) |
| 312 |
} |
|
| 313 |
) |
|
| 314 | ||
| 315 |
## -------------------------------------------------- |
|
| 316 |
## Approximate posterior with (log) normal distribution |
|
| 317 |
## -------------------------------------------------- |
|
| 318 | ||
| 319 |
#' Approximate posterior with (log) normal distribution |
|
| 320 |
#' |
|
| 321 |
#' To reproduce the resultant approximate model in the future exactly, include |
|
| 322 |
#' \code{seed = xxxx} in the call to `approximate`.
|
|
| 323 |
#' |
|
| 324 |
#' @param object the \code{\linkS4class{Samples}} object
|
|
| 325 |
#' @param model the \code{\linkS4class{GeneralModel}} object
|
|
| 326 |
#' @param data the \code{\linkS4class{Data}} object
|
|
| 327 |
#' @param \dots additional arguments (see methods) |
|
| 328 |
#' @return a `list` containing the approximation model and, if requested, a |
|
| 329 |
#' `ggplot2` object containing a graphical representation of the fitted model |
|
| 330 |
#' |
|
| 331 |
#' @export |
|
| 332 |
#' @keywords methods |
|
| 333 |
setGeneric( |
|
| 334 |
"approximate", |
|
| 335 |
def = function(object, model, data, ...) {
|
|
| 336 |
## there should be no default method, |
|
| 337 |
## therefore just forward to next method! |
|
| 338 | 10x |
standardGeneric("approximate")
|
| 339 |
}, |
|
| 340 |
valueClass = "list" |
|
| 341 |
) |
|
| 342 | ||
| 343 | ||
| 344 |
##' @param points optional parameter, which gives the dose values at which |
|
| 345 |
##' the approximation should rely on (default: 5 values equally spaced from |
|
| 346 |
##' minimum to maximum of the dose grid) |
|
| 347 |
##' @param refDose the reference dose to be used (default: median of |
|
| 348 |
##' \code{points})
|
|
| 349 |
##' @param logNormal use the log-normal prior? (not default) otherwise, the |
|
| 350 |
##' normal prior for the logistic regression coefficients is used |
|
| 351 |
##' @param verbose be verbose (progress statements)? (default) |
|
| 352 |
##' @param create_plot add a `ggplot2` object to the return value (default) |
|
| 353 |
##' |
|
| 354 |
##' @describeIn approximate Here the \dots argument can transport additional arguments for |
|
| 355 |
##' \code{\link{Quantiles2LogisticNormal}}, e.g. in order to control the
|
|
| 356 |
##' approximation quality, etc. |
|
| 357 |
##' |
|
| 358 |
##' @example examples/Sample-methods-approximate.R |
|
| 359 |
setMethod( |
|
| 360 |
"approximate", |
|
| 361 |
signature = signature(object = "Samples"), |
|
| 362 |
def = function( |
|
| 363 |
object, |
|
| 364 |
model, |
|
| 365 |
data, |
|
| 366 |
points = seq( |
|
| 367 |
from = min(data@doseGrid), |
|
| 368 |
to = max(data@doseGrid), |
|
| 369 |
length = 5L |
|
| 370 |
), |
|
| 371 |
refDose = median(points), |
|
| 372 |
logNormal = FALSE, |
|
| 373 |
verbose = TRUE, |
|
| 374 |
create_plot = TRUE, |
|
| 375 |
... |
|
| 376 |
) {
|
|
| 377 |
# Validation |
|
| 378 | 10x |
assert_logical(logNormal) |
| 379 | 9x |
assert_logical(verbose) |
| 380 | 8x |
assert_logical(create_plot) |
| 381 | 7x |
assert_numeric(points) |
| 382 | 6x |
assert_numeric(refDose) |
| 383 |
## get the required quantiles at these dose levels: |
|
| 384 | 5x |
quants <- fit( |
| 385 | 5x |
object, |
| 386 | 5x |
model, |
| 387 | 5x |
data, |
| 388 | 5x |
points = points, |
| 389 | 5x |
quantiles = c(0.025, 0.975), |
| 390 | 5x |
middle = median |
| 391 |
) |
|
| 392 | ||
| 393 |
## get better starting values if it is already a logistic normal |
|
| 394 |
## model |
|
| 395 | 5x |
if (is(model, "LogisticNormal") && (!logNormal)) {
|
| 396 | 1x |
means <- sapply( |
| 397 | 1x |
object@data, |
| 398 | 1x |
mean |
| 399 |
) |
|
| 400 | 1x |
cov <- cov(as.data.frame(object@data)) |
| 401 | ||
| 402 | 1x |
parstart <- c( |
| 403 | 1x |
means[1], |
| 404 | 1x |
means[2], |
| 405 | 1x |
sqrt(cov[1, 1]), |
| 406 | 1x |
sqrt(cov[2, 2]), |
| 407 | 1x |
cov2cor(cov)[1, 2] |
| 408 |
) |
|
| 409 | 4x |
} else if (is(model, "LogisticLogNormal") && logNormal) {
|
| 410 | 1x |
datTrafo <- with( |
| 411 | 1x |
object@data, |
| 412 | 1x |
cbind( |
| 413 | 1x |
alpha0, |
| 414 | 1x |
log(alpha1) |
| 415 |
) |
|
| 416 |
) |
|
| 417 | ||
| 418 | 1x |
means <- colMeans(datTrafo) |
| 419 | 1x |
cov <- cov(datTrafo) |
| 420 | ||
| 421 | 1x |
parstart <- c( |
| 422 | 1x |
means[1], |
| 423 | 1x |
means[2], |
| 424 | 1x |
sqrt(cov[1, 1]), |
| 425 | 1x |
sqrt(cov[2, 2]), |
| 426 | 1x |
cov2cor(cov)[1, 2] |
| 427 |
) |
|
| 428 |
} else {
|
|
| 429 | 3x |
parstart <- NULL |
| 430 |
} |
|
| 431 | ||
| 432 |
## run the approx function |
|
| 433 | 5x |
quantRes <- Quantiles2LogisticNormal( |
| 434 | 5x |
dosegrid = quants$dose, |
| 435 | 5x |
refDose = refDose, |
| 436 | 5x |
lower = quants$lower, |
| 437 | 5x |
upper = quants$upper, |
| 438 | 5x |
median = quants$middle, |
| 439 | 5x |
verbose = verbose, |
| 440 | 5x |
parstart = parstart, |
| 441 | 5x |
logNormal = logNormal, |
| 442 |
... |
|
| 443 |
) |
|
| 444 | 5x |
rv <- list() |
| 445 | 5x |
rv$model <- quantRes$model |
| 446 | 5x |
if (create_plot) {
|
| 447 | 4x |
rv$plot <- tibble::as_tibble(quantRes$required) %>% |
| 448 | 4x |
tibble::add_column(Type = "original") %>% |
| 449 | 4x |
tibble::add_column(x = points) %>% |
| 450 | 4x |
dplyr::bind_rows( |
| 451 | 4x |
tibble::as_tibble(quantRes$quantiles) %>% |
| 452 | 4x |
tibble::add_column(Type = "approximation") %>% |
| 453 | 4x |
tibble::add_column(x = points) |
| 454 |
) %>% |
|
| 455 | 4x |
tidyr::pivot_longer( |
| 456 | 4x |
c(lower, median, upper), |
| 457 | 4x |
names_to = "Line", |
| 458 | 4x |
values_to = "y" |
| 459 |
) %>% |
|
| 460 | 4x |
ggplot( |
| 461 | 4x |
aes( |
| 462 | 4x |
x = x, |
| 463 | 4x |
y = y, |
| 464 | 4x |
colour = Type, |
| 465 | 4x |
group = interaction(Type, .data$Line), |
| 466 | 4x |
linetype = (.data$Line == "median") |
| 467 |
) |
|
| 468 |
) + |
|
| 469 | 4x |
geom_line() + |
| 470 | 4x |
scale_colour_manual( |
| 471 | 4x |
name = " ", |
| 472 | 4x |
values = c("red", "blue")
|
| 473 |
) + |
|
| 474 | 4x |
scale_linetype_manual( |
| 475 | 4x |
name = " ", |
| 476 | 4x |
values = c("dotted", "solid"),
|
| 477 | 4x |
labels = c("95% CI", "Median"),
|
| 478 | 4x |
guide = guide_legend(reverse = TRUE) |
| 479 |
) + |
|
| 480 | 4x |
labs( |
| 481 | 4x |
x = "Dose", |
| 482 | 4x |
y = "p(Tox)" |
| 483 |
) + |
|
| 484 | 4x |
theme_light() |
| 485 |
} |
|
| 486 | 5x |
rv |
| 487 |
} |
|
| 488 |
) |
|
| 489 | ||
| 490 |
## -------------------------------------------------- |
|
| 491 |
## Plot dose-tox fit from a model |
|
| 492 |
## -------------------------------------------------- |
|
| 493 | ||
| 494 |
#' Plotting dose-toxicity model fits |
|
| 495 |
#' |
|
| 496 |
#' @param x the \code{\linkS4class{Samples}} object
|
|
| 497 |
#' @param y the \code{\linkS4class{GeneralModel}} object
|
|
| 498 |
#' @param data the \code{\linkS4class{Data}} object
|
|
| 499 |
#' @param xlab the x axis label |
|
| 500 |
#' @param ylab the y axis label |
|
| 501 |
#' @param showLegend should the legend be shown? (default) |
|
| 502 |
#' @param \dots not used |
|
| 503 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 504 |
#' object for the dose-toxicity model fit |
|
| 505 |
#' |
|
| 506 |
#' @example examples/Sample-methods-plot.R |
|
| 507 |
#' @export |
|
| 508 |
setMethod( |
|
| 509 |
"plot", |
|
| 510 |
signature = signature( |
|
| 511 |
x = "Samples", |
|
| 512 |
y = "GeneralModel" |
|
| 513 |
), |
|
| 514 |
def = function( |
|
| 515 |
x, |
|
| 516 |
y, |
|
| 517 |
data, |
|
| 518 |
..., |
|
| 519 |
xlab = "Dose level", |
|
| 520 |
ylab = "Probability of DLT [%]", |
|
| 521 |
showLegend = TRUE |
|
| 522 |
) {
|
|
| 523 |
## check args |
|
| 524 | 11x |
assert_logical(showLegend) |
| 525 | ||
| 526 |
## get the fit |
|
| 527 | 10x |
plotData <- fit( |
| 528 | 10x |
x, |
| 529 | 10x |
model = y, |
| 530 | 10x |
data = data, |
| 531 | 10x |
quantiles = c(0.025, 0.975), |
| 532 | 10x |
middle = mean, |
| 533 |
... |
|
| 534 |
) |
|
| 535 | ||
| 536 |
## make the plot |
|
| 537 | 10x |
gdata <- |
| 538 | 10x |
with( |
| 539 | 10x |
plotData, |
| 540 | 10x |
data.frame( |
| 541 | 10x |
x = rep(dose, 3), |
| 542 | 10x |
y = c(middle, lower, upper) * 100, |
| 543 | 10x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotData)),
|
| 544 | 10x |
Type = factor( |
| 545 | 10x |
c( |
| 546 | 10x |
rep( |
| 547 | 10x |
"Estimate", |
| 548 | 10x |
nrow(plotData) |
| 549 |
), |
|
| 550 | 10x |
rep( |
| 551 | 10x |
"95% Credible Interval", |
| 552 | 10x |
nrow(plotData) * 2 |
| 553 |
) |
|
| 554 |
), |
|
| 555 | 10x |
levels = c( |
| 556 | 10x |
"Estimate", |
| 557 | 10x |
"95% Credible Interval" |
| 558 |
) |
|
| 559 |
) |
|
| 560 |
) |
|
| 561 |
) |
|
| 562 | ||
| 563 | 10x |
ret <- gdata %>% |
| 564 | 10x |
ggplot() + |
| 565 | 10x |
geom_line( |
| 566 | 10x |
aes( |
| 567 | 10x |
x = x, |
| 568 | 10x |
y = y, |
| 569 | 10x |
group = group, |
| 570 | 10x |
linetype = Type, |
| 571 |
), |
|
| 572 | 10x |
colour = I("red"),
|
| 573 |
) + |
|
| 574 | 10x |
coord_cartesian(ylim = c(0, 100)) + |
| 575 | 10x |
labs( |
| 576 | 10x |
x = xlab, |
| 577 | 10x |
y = ylab, |
| 578 |
) |
|
| 579 | ||
| 580 | 10x |
ret + |
| 581 | 10x |
scale_linetype_manual( |
| 582 | 10x |
breaks = c( |
| 583 | 10x |
"Estimate", |
| 584 | 10x |
"95% Credible Interval" |
| 585 |
), |
|
| 586 | 10x |
values = c(1, 2), |
| 587 | 10x |
guide = ifelse(showLegend, "legend", "none") |
| 588 |
) |
|
| 589 |
} |
|
| 590 |
) |
|
| 591 | ||
| 592 | ||
| 593 |
## -------------------------------------------------- |
|
| 594 |
## Special method for dual endpoint model |
|
| 595 |
## -------------------------------------------------- |
|
| 596 | ||
| 597 |
#' Plotting dose-toxicity and dose-biomarker model fits |
|
| 598 |
#' |
|
| 599 |
#' When we have the dual endpoint model, |
|
| 600 |
#' also the dose-biomarker fit is shown in the plot |
|
| 601 |
#' |
|
| 602 |
#' @param x the \code{\linkS4class{Samples}} object
|
|
| 603 |
#' @param y the \code{\linkS4class{DualEndpoint}} object
|
|
| 604 |
#' @param data the \code{\linkS4class{DataDual}} object
|
|
| 605 |
#' @param extrapolate should the biomarker fit be extrapolated to the whole |
|
| 606 |
#' dose grid? (default) |
|
| 607 |
#' @param showLegend should the legend be shown? (not default) |
|
| 608 |
#' @param \dots additional arguments for the parent method |
|
| 609 |
#' \code{\link{plot,Samples,GeneralModel-method}}
|
|
| 610 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 611 |
#' object with the dose-toxicity and dose-biomarker model fits |
|
| 612 |
#' |
|
| 613 |
#' @example examples/Sample-methods-plot-DualEndpoint.R |
|
| 614 |
#' @export |
|
| 615 |
setMethod( |
|
| 616 |
"plot", |
|
| 617 |
signature = signature( |
|
| 618 |
x = "Samples", |
|
| 619 |
y = "DualEndpoint" |
|
| 620 |
), |
|
| 621 |
def = function(x, y, data, extrapolate = TRUE, showLegend = FALSE, ...) {
|
|
| 622 | 4x |
assert_logical(extrapolate) |
| 623 | ||
| 624 |
## call the superclass method, to get the toxicity plot |
|
| 625 | 3x |
plot1 <- callNextMethod(x, y, data, showLegend = showLegend, ...) |
| 626 | ||
| 627 |
## only look at these dose levels for the plot: |
|
| 628 | 3x |
xLevels <- |
| 629 | 3x |
if (extrapolate) {
|
| 630 | 2x |
seq_along(data@doseGrid) |
| 631 |
} else {
|
|
| 632 | 1x |
1:max(data@xLevel) |
| 633 |
} |
|
| 634 | ||
| 635 |
## get the plot data for the biomarker plot |
|
| 636 | 3x |
functionSamples <- biomarker(xLevel = xLevels, model = y, samples = x) |
| 637 | ||
| 638 |
## extract mean curve |
|
| 639 | 3x |
meanCurve <- colMeans(functionSamples) |
| 640 | ||
| 641 |
## extract quantiles |
|
| 642 | 3x |
quantiles <- c(0.025, 0.975) |
| 643 | 3x |
quantCurve <- apply(functionSamples, 2L, quantile, prob = quantiles) |
| 644 | ||
| 645 |
## now create the data frame |
|
| 646 | 3x |
plotData <- data.frame( |
| 647 | 3x |
dose = data@doseGrid[xLevels], |
| 648 | 3x |
mean = meanCurve, |
| 649 | 3x |
lower = quantCurve[1, ], |
| 650 | 3x |
upper = quantCurve[2, ] |
| 651 |
) |
|
| 652 | ||
| 653 |
## make the second plot |
|
| 654 | 3x |
gdata <- |
| 655 | 3x |
with( |
| 656 | 3x |
plotData, |
| 657 | 3x |
data.frame( |
| 658 | 3x |
x = rep(dose, 3), |
| 659 | 3x |
y = c(mean, lower, upper), |
| 660 | 3x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotData)),
|
| 661 | 3x |
Type = factor( |
| 662 | 3x |
c( |
| 663 | 3x |
rep( |
| 664 | 3x |
"Estimate", |
| 665 | 3x |
nrow(plotData) |
| 666 |
), |
|
| 667 | 3x |
rep( |
| 668 | 3x |
"95% Credible Interval", |
| 669 | 3x |
nrow(plotData) * 2 |
| 670 |
) |
|
| 671 |
), |
|
| 672 | 3x |
levels = c( |
| 673 | 3x |
"Estimate", |
| 674 | 3x |
"95% Credible Interval" |
| 675 |
) |
|
| 676 |
) |
|
| 677 |
) |
|
| 678 |
) |
|
| 679 | 3x |
plot2 <- gdata %>% |
| 680 | 3x |
ggplot() + |
| 681 | 3x |
geom_line( |
| 682 | 3x |
aes( |
| 683 | 3x |
x = x, |
| 684 | 3x |
y = y, |
| 685 | 3x |
group = group, |
| 686 | 3x |
linetype = Type |
| 687 |
), |
|
| 688 | 3x |
colour = I("blue")
|
| 689 |
) + |
|
| 690 | 3x |
labs( |
| 691 | 3x |
x = "Dose level", |
| 692 | 3x |
y = "Biomarker level" |
| 693 |
) |
|
| 694 | ||
| 695 | 3x |
plot2 <- plot2 + |
| 696 | 3x |
scale_linetype_manual( |
| 697 | 3x |
breaks = c( |
| 698 | 3x |
"Estimate", |
| 699 | 3x |
"95% Credible Interval" |
| 700 |
), |
|
| 701 | 3x |
values = c(1, 2), |
| 702 | 3x |
guide = ifelse(showLegend, "legend", "none") |
| 703 |
) |
|
| 704 | ||
| 705 |
## arrange both plots side by side |
|
| 706 | 3x |
gridExtra::arrangeGrob(plot1, plot2, ncol = 2) |
| 707 |
} |
|
| 708 |
) |
|
| 709 | ||
| 710 | ||
| 711 |
## ------------------------------------------------------------------------------------- |
|
| 712 |
## Get fitted dose-tox curve from Samples for 'LogisticIndepBeta' model class |
|
| 713 |
## ------------------------------------------------------------------------------------ |
|
| 714 |
#' @describeIn fit This method return a data frame with dose, middle lower and upper quantiles |
|
| 715 |
#' for the dose-DLE curve using DLE samples for \dQuote{LogisticIndepBeta} model class
|
|
| 716 |
#' @example examples/Samples-method-fitDLE.R |
|
| 717 |
setMethod( |
|
| 718 |
"fit", |
|
| 719 |
signature = signature( |
|
| 720 |
object = "Samples", |
|
| 721 |
model = "LogisticIndepBeta", |
|
| 722 |
data = "Data" |
|
| 723 |
), |
|
| 724 |
def = function( |
|
| 725 |
object, |
|
| 726 |
model, |
|
| 727 |
data, |
|
| 728 |
points = data@doseGrid, |
|
| 729 |
quantiles = c(0.025, 0.975), |
|
| 730 |
middle = mean, |
|
| 731 |
... |
|
| 732 |
) {
|
|
| 733 |
## some checks |
|
| 734 | 17x |
assert_probability_range(quantiles) |
| 735 | 15x |
assert_numeric(points) |
| 736 | ||
| 737 |
## first we have to get samples from the dose-tox |
|
| 738 |
## curve at the dose grid points. |
|
| 739 | 14x |
probSamples <- matrix( |
| 740 | 14x |
nrow = size(object), |
| 741 | 14x |
ncol = length(points) |
| 742 |
) |
|
| 743 | ||
| 744 |
## evaluate the probs, for all samples. |
|
| 745 | 14x |
for (i in seq_along(points)) {
|
| 746 |
## Now we want to evaluate for the |
|
| 747 |
## following dose: |
|
| 748 | 170x |
probSamples[, i] <- prob( |
| 749 | 170x |
dose = points[i], |
| 750 | 170x |
model, |
| 751 | 170x |
object |
| 752 |
) |
|
| 753 |
} |
|
| 754 | ||
| 755 |
## extract middle curve |
|
| 756 | 14x |
middleCurve <- apply(probSamples, 2L, FUN = middle) |
| 757 | ||
| 758 |
## extract quantiles |
|
| 759 | 14x |
quantCurve <- apply(probSamples, 2L, quantile, prob = quantiles) |
| 760 | ||
| 761 |
## now create the data frame |
|
| 762 | 14x |
data.frame( |
| 763 | 14x |
dose = points, |
| 764 | 14x |
middle = middleCurve, |
| 765 | 14x |
lower = quantCurve[1, ], |
| 766 | 14x |
upper = quantCurve[2, ] |
| 767 |
) |
|
| 768 |
} |
|
| 769 |
) |
|
| 770 | ||
| 771 |
## ------------------------------------------------------------------------------------- |
|
| 772 |
## Get fitted dose-efficacy curve from Samples for 'Effloglog' model class |
|
| 773 |
## ------------------------------------------------------------------------------------ |
|
| 774 | ||
| 775 |
#' @describeIn fit This method returns a data frame with dose, middle, lower, upper quantiles for |
|
| 776 |
#' the dose-efficacy curve using efficacy samples for \dQuote{Effloglog} model class
|
|
| 777 |
#' @example examples/Samples-method-fitEff.R |
|
| 778 |
setMethod( |
|
| 779 |
"fit", |
|
| 780 |
signature = signature( |
|
| 781 |
object = "Samples", |
|
| 782 |
model = "Effloglog", |
|
| 783 |
data = "DataDual" |
|
| 784 |
), |
|
| 785 |
def = function( |
|
| 786 |
object, |
|
| 787 |
model, |
|
| 788 |
data, |
|
| 789 |
points = data@doseGrid, |
|
| 790 |
quantiles = c(0.025, 0.975), |
|
| 791 |
middle = mean, |
|
| 792 |
... |
|
| 793 |
) {
|
|
| 794 |
## some checks |
|
| 795 | 12x |
assert_probability_range(quantiles) |
| 796 | 10x |
assert_numeric(points) |
| 797 | ||
| 798 |
## first we have to get samples from the dose-tox |
|
| 799 |
## curve at the dose grid points. |
|
| 800 | 9x |
ExpEffSamples <- matrix( |
| 801 | 9x |
nrow = size(object), |
| 802 | 9x |
ncol = length(points) |
| 803 |
) |
|
| 804 | ||
| 805 |
## evaluate the probs, for all samples. |
|
| 806 | 9x |
for (i in seq_along(points)) {
|
| 807 |
## Now we want to evaluate for the |
|
| 808 |
## following dose: |
|
| 809 | 109x |
ExpEffSamples[, i] <- efficacy( |
| 810 | 109x |
dose = points[i], |
| 811 | 109x |
model, |
| 812 | 109x |
object |
| 813 |
) |
|
| 814 |
} |
|
| 815 | ||
| 816 |
## extract middle curve |
|
| 817 | 9x |
middleCurve <- apply(ExpEffSamples, 2L, FUN = middle) |
| 818 | ||
| 819 |
## extract quantiles |
|
| 820 | 9x |
quantCurve <- apply(ExpEffSamples, 2L, quantile, prob = quantiles) |
| 821 | ||
| 822 |
## now create the data frame |
|
| 823 | 9x |
data.frame( |
| 824 | 9x |
dose = points, |
| 825 | 9x |
middle = middleCurve, |
| 826 | 9x |
lower = quantCurve[1, ], |
| 827 | 9x |
upper = quantCurve[2, ] |
| 828 |
) |
|
| 829 |
} |
|
| 830 |
) |
|
| 831 |
## ========================================================================================== |
|
| 832 |
## -------------------------------------------------------------------- |
|
| 833 |
## Get fitted dose-efficacy based on the Efficacy Flexible model |
|
| 834 |
## ------------------------------------------------------------- |
|
| 835 |
#' @describeIn fit This method returns a data frame with dose, middle, lower and upper |
|
| 836 |
#' quantiles for the dose-efficacy curve using efficacy samples for \dQuote{EffFlexi}
|
|
| 837 |
#' model class |
|
| 838 |
#' @example examples/Samples-method-fitEffFlexi.R |
|
| 839 |
setMethod( |
|
| 840 |
"fit", |
|
| 841 |
signature = signature( |
|
| 842 |
object = "Samples", |
|
| 843 |
model = "EffFlexi", |
|
| 844 |
data = "DataDual" |
|
| 845 |
), |
|
| 846 |
def = function( |
|
| 847 |
object, |
|
| 848 |
model, |
|
| 849 |
data, |
|
| 850 |
points = data@doseGrid, |
|
| 851 |
quantiles = c(0.025, 0.975), |
|
| 852 |
middle = mean, |
|
| 853 |
... |
|
| 854 |
) {
|
|
| 855 |
## some checks |
|
| 856 | 6x |
assert_probability_range(quantiles) |
| 857 | 4x |
assert_numeric(points) |
| 858 | ||
| 859 |
## first we have to get samples from the dose-tox |
|
| 860 |
## curve at the dose grid points. |
|
| 861 | 3x |
ExpEffSamples <- matrix( |
| 862 | 3x |
nrow = size(object), |
| 863 | 3x |
ncol = length(points) |
| 864 |
) |
|
| 865 | ||
| 866 |
## evaluate the probs, for all samples. |
|
| 867 | 3x |
for (i in seq_along(points)) {
|
| 868 |
## Now we want to evaluate for the |
|
| 869 |
## following dose: |
|
| 870 | 36x |
ExpEffSamples[, i] <- efficacy( |
| 871 | 36x |
dose = points[i], |
| 872 | 36x |
model, |
| 873 | 36x |
object |
| 874 |
) |
|
| 875 |
} |
|
| 876 | ||
| 877 |
## extract middle curve |
|
| 878 | 3x |
middleCurve <- apply(ExpEffSamples, 2L, FUN = middle) |
| 879 | ||
| 880 |
## extract quantiles |
|
| 881 | 3x |
quantCurve <- apply(ExpEffSamples, 2L, quantile, prob = quantiles) |
| 882 | ||
| 883 |
## now create the data frame |
|
| 884 | 3x |
data.frame( |
| 885 | 3x |
dose = points, |
| 886 | 3x |
middle = middleCurve, |
| 887 | 3x |
lower = quantCurve[1, ], |
| 888 | 3x |
upper = quantCurve[2, ] |
| 889 |
) |
|
| 890 |
} |
|
| 891 |
) |
|
| 892 | ||
| 893 |
#' @describeIn fit This method returns a data frame with dose, middle, lower |
|
| 894 |
#' and upper quantiles for the dose-efficacy curve using efficacy samples |
|
| 895 |
#' for the \dQuote{LogisticLogNormalOrdinal} model class
|
|
| 896 |
#' @example examples/Sample-methods-fit-LogisticLogNormalOrdinal.R |
|
| 897 |
setMethod( |
|
| 898 |
"fit", |
|
| 899 |
signature = signature( |
|
| 900 |
object = "Samples", |
|
| 901 |
model = "LogisticLogNormalOrdinal", |
|
| 902 |
data = "DataOrdinal" |
|
| 903 |
), |
|
| 904 |
def = function( |
|
| 905 |
object, |
|
| 906 |
model, |
|
| 907 |
data, |
|
| 908 |
points = data@doseGrid, |
|
| 909 |
quantiles = c(0.025, 0.975), |
|
| 910 |
middle = mean, |
|
| 911 |
... |
|
| 912 |
) {
|
|
| 913 |
# Validation |
|
| 914 | 15x |
assert_probability_range(quantiles) |
| 915 | 12x |
assert_numeric(points) |
| 916 | 11x |
assert_function(middle) |
| 917 | ||
| 918 |
# Begin |
|
| 919 |
# Get samples from the dose-tox curve at the dose grid points. |
|
| 920 | 10x |
probSamples <- matrix( |
| 921 | 10x |
nrow = size(object), |
| 922 | 10x |
ncol = length(points) |
| 923 |
) |
|
| 924 |
# Evaluate the probs, for all samples. |
|
| 925 | 10x |
for (i in seq_along(points)) {
|
| 926 |
# Now we want to evaluate for the following dose: |
|
| 927 | 68x |
probSamples[, i] <- prob( |
| 928 | 68x |
dose = points[i], |
| 929 | 68x |
model, |
| 930 | 68x |
object, |
| 931 |
... |
|
| 932 |
) |
|
| 933 |
} |
|
| 934 |
# Extract middle curve |
|
| 935 | 10x |
middleCurve <- apply(probSamples, 2L, FUN = middle) |
| 936 |
# Extract quantiles |
|
| 937 | 10x |
quantCurve <- apply(probSamples, 2L, quantile, prob = quantiles) |
| 938 | ||
| 939 |
# Create the data frame... |
|
| 940 | 10x |
data.frame( |
| 941 | 10x |
dose = points, |
| 942 | 10x |
middle = middleCurve, |
| 943 | 10x |
lower = quantCurve[1, ], |
| 944 | 10x |
upper = quantCurve[2, ] |
| 945 |
) |
|
| 946 |
} |
|
| 947 |
) |
|
| 948 |
## ============================================================== |
|
| 949 |
## ---------------------------------------------------------------- |
|
| 950 |
## Get fitted values at all dose levels from gain samples |
|
| 951 |
## ----------------------------------------------------------------- |
|
| 952 |
#' Get the fitted values for the gain values at all dose levels based on |
|
| 953 |
#' a given pseudo DLE model, DLE sample, a pseudo efficacy model, a Efficacy sample |
|
| 954 |
#' and data. This method returns a data frame with dose, middle, lower and upper quantiles |
|
| 955 |
#' of the gain value samples |
|
| 956 |
#' |
|
| 957 |
#' @param DLEmodel the DLE pseudo model of \code{\linkS4class{ModelTox}} class object
|
|
| 958 |
#' @param DLEsamples the DLE samples of \code{\linkS4class{Samples}} class object
|
|
| 959 |
#' @param Effmodel the efficacy pseudo model of \code{\linkS4class{ModelEff}} class object
|
|
| 960 |
#' @param Effsamples the efficacy samples of \code{\linkS4class{Samples}} class object
|
|
| 961 |
#' @param data the data input of \code{\linkS4class{DataDual}} class object
|
|
| 962 |
#' @param \dots additional arguments for methods |
|
| 963 |
#' |
|
| 964 |
#' @export |
|
| 965 |
#' @keywords methods |
|
| 966 |
#' @example examples/Samples-method-fitGain.R |
|
| 967 |
setGeneric( |
|
| 968 |
"fitGain", |
|
| 969 |
def = function(DLEmodel, DLEsamples, Effmodel, Effsamples, data, ...) {
|
|
| 970 |
## there should be no default method, |
|
| 971 |
## therefore just forward to next method! |
|
| 972 | 10x |
standardGeneric("fitGain")
|
| 973 |
}, |
|
| 974 |
valueClass = "data.frame" |
|
| 975 |
) |
|
| 976 | ||
| 977 |
#' @describeIn fitGain This method returns a data frame with dose, middle, lower, upper quantiles for |
|
| 978 |
#' the gain values obtained given the DLE and the efficacy samples |
|
| 979 |
#' @param points at which dose levels is the fit requested? default is the dose |
|
| 980 |
#' grid |
|
| 981 |
#' @param quantiles the quantiles to be calculated (default: 0.025 and |
|
| 982 |
#' 0.975) |
|
| 983 |
#' @param middle the function for computing the middle point. Default: |
|
| 984 |
#' \code{\link{mean}}
|
|
| 985 |
#' @example examples/Samples-method-fitGain.R |
|
| 986 |
setMethod( |
|
| 987 |
"fitGain", |
|
| 988 |
signature = signature( |
|
| 989 |
DLEmodel = "ModelTox", |
|
| 990 |
DLEsamples = "Samples", |
|
| 991 |
Effmodel = "ModelEff", |
|
| 992 |
Effsamples = "Samples", |
|
| 993 |
data = "DataDual" |
|
| 994 |
), |
|
| 995 |
def = function( |
|
| 996 |
DLEmodel, |
|
| 997 |
DLEsamples, |
|
| 998 |
Effmodel, |
|
| 999 |
Effsamples, |
|
| 1000 |
data, |
|
| 1001 |
points = data@doseGrid, |
|
| 1002 |
quantiles = c(0.025, 0.975), |
|
| 1003 |
middle = mean, |
|
| 1004 |
... |
|
| 1005 |
) {
|
|
| 1006 |
## some checks |
|
| 1007 | 10x |
assert_probability_range(quantiles) |
| 1008 | 6x |
assert_numeric(points) |
| 1009 | ||
| 1010 |
## first we have to get samples from the gain |
|
| 1011 |
## at the dose grid points. |
|
| 1012 | 4x |
GainSamples <- matrix( |
| 1013 | 4x |
nrow = size(DLEsamples), |
| 1014 | 4x |
ncol = length(points) |
| 1015 |
) |
|
| 1016 | ||
| 1017 |
## evaluate the probs, for all gain samples. |
|
| 1018 | 4x |
for (i in seq_along(points)) {
|
| 1019 |
## Now we want to evaluate for the |
|
| 1020 |
## following dose: |
|
| 1021 | 48x |
GainSamples[, i] <- gain( |
| 1022 | 48x |
dose = points[i], |
| 1023 | 48x |
DLEmodel, |
| 1024 | 48x |
DLEsamples, |
| 1025 | 48x |
Effmodel, |
| 1026 | 48x |
Effsamples |
| 1027 |
) |
|
| 1028 |
} |
|
| 1029 | ||
| 1030 |
## extract middle curve |
|
| 1031 | 4x |
middleCurve <- apply(GainSamples, 2L, FUN = middle) |
| 1032 | ||
| 1033 |
## extract quantiles |
|
| 1034 | 4x |
quantCurve <- apply(GainSamples, 2L, quantile, prob = quantiles) |
| 1035 | ||
| 1036 |
## now create the data frame |
|
| 1037 | 4x |
data.frame( |
| 1038 | 4x |
dose = points, |
| 1039 | 4x |
middle = middleCurve, |
| 1040 | 4x |
lower = quantCurve[1, ], |
| 1041 | 4x |
upper = quantCurve[2, ] |
| 1042 |
) |
|
| 1043 |
} |
|
| 1044 |
) |
|
| 1045 |
## --------------------------------------------------------------------------------- |
|
| 1046 |
## Plot the fitted dose-DLE curve with pseudo DLE model with samples |
|
| 1047 |
## ------------------------------------------------------------------------------- |
|
| 1048 |
#' Plot the fitted dose-DLE curve using a \code{\linkS4class{ModelTox}} class model with samples
|
|
| 1049 |
#' |
|
| 1050 |
#' @param x the \code{\linkS4class{Samples}} object
|
|
| 1051 |
#' @param y the \code{\linkS4class{ModelTox}} model class object
|
|
| 1052 |
#' @param data the \code{\linkS4class{Data}} object
|
|
| 1053 |
#' @param xlab the x axis label |
|
| 1054 |
#' @param ylab the y axis label |
|
| 1055 |
#' @param showLegend should the legend be shown? (default) |
|
| 1056 |
#' @param \dots not used |
|
| 1057 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 1058 |
#' object for the dose-DLE model fit |
|
| 1059 |
#' |
|
| 1060 |
#' @example examples/Samples-method-plotModelTox.R |
|
| 1061 |
#' @export |
|
| 1062 |
#' @keywords methods |
|
| 1063 |
setMethod( |
|
| 1064 |
"plot", |
|
| 1065 |
signature = signature( |
|
| 1066 |
x = "Samples", |
|
| 1067 |
y = "ModelTox" |
|
| 1068 |
), |
|
| 1069 |
def = function( |
|
| 1070 |
x, |
|
| 1071 |
y, |
|
| 1072 |
data, |
|
| 1073 |
..., |
|
| 1074 |
xlab = "Dose level", |
|
| 1075 |
ylab = "Probability of DLT [%]", |
|
| 1076 |
showLegend = TRUE |
|
| 1077 |
) {
|
|
| 1078 |
## check args |
|
| 1079 | 3x |
assert_logical(showLegend) |
| 1080 | ||
| 1081 |
## get the fit |
|
| 1082 | 2x |
plotData <- fit( |
| 1083 | 2x |
x, |
| 1084 | 2x |
model = y, |
| 1085 | 2x |
data = data, |
| 1086 | 2x |
quantiles = c(0.025, 0.975), |
| 1087 | 2x |
middle = mean |
| 1088 |
) |
|
| 1089 | ||
| 1090 |
## make the plot |
|
| 1091 | 2x |
gdata <- |
| 1092 | 2x |
with( |
| 1093 | 2x |
plotData, |
| 1094 | 2x |
data.frame( |
| 1095 | 2x |
x = rep(dose, 3), |
| 1096 | 2x |
y = c(middle, lower, upper) * 100, |
| 1097 | 2x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotData)),
|
| 1098 | 2x |
Type = factor( |
| 1099 | 2x |
c( |
| 1100 | 2x |
rep( |
| 1101 | 2x |
"Estimate", |
| 1102 | 2x |
nrow(plotData) |
| 1103 |
), |
|
| 1104 | 2x |
rep( |
| 1105 | 2x |
"95% Credible Interval", |
| 1106 | 2x |
nrow(plotData) * 2 |
| 1107 |
) |
|
| 1108 |
), |
|
| 1109 | 2x |
levels = c( |
| 1110 | 2x |
"Estimate", |
| 1111 | 2x |
"95% Credible Interval" |
| 1112 |
) |
|
| 1113 |
) |
|
| 1114 |
) |
|
| 1115 |
) |
|
| 1116 | ||
| 1117 | 2x |
ret <- gdata %>% |
| 1118 | 2x |
ggplot() + |
| 1119 | 2x |
geom_line( |
| 1120 | 2x |
aes( |
| 1121 | 2x |
x = x, |
| 1122 | 2x |
y = y, |
| 1123 | 2x |
group = group, |
| 1124 | 2x |
linetype = Type |
| 1125 |
), |
|
| 1126 | 2x |
colour = I("red"),
|
| 1127 |
) + |
|
| 1128 | 2x |
coord_cartesian(ylim = c(0, 100)) + |
| 1129 | 2x |
labs( |
| 1130 | 2x |
x = xlab, |
| 1131 | 2x |
y = ylab |
| 1132 |
) |
|
| 1133 | ||
| 1134 | 2x |
ret + |
| 1135 | 2x |
scale_linetype_manual( |
| 1136 | 2x |
breaks = c( |
| 1137 | 2x |
"Estimate", |
| 1138 | 2x |
"95% Credible Interval" |
| 1139 |
), |
|
| 1140 | 2x |
values = c(1, 2), |
| 1141 | 2x |
guide = ifelse(showLegend, "legend", "none") |
| 1142 |
) |
|
| 1143 |
} |
|
| 1144 |
) |
|
| 1145 | ||
| 1146 | ||
| 1147 |
# -------------------------------------------------------------------------------------------- |
|
| 1148 |
## Plot the fitted dose-efficacy curve using a pseudo efficacy model with samples |
|
| 1149 |
## ------------------------------------------------------------------------------------------- |
|
| 1150 |
#' Plot the fitted dose-efficacy curve using a model from \code{\linkS4class{ModelEff}} class
|
|
| 1151 |
#' with samples |
|
| 1152 |
#' |
|
| 1153 |
#' @param x the \code{\linkS4class{Samples}} object
|
|
| 1154 |
#' @param y the \code{\linkS4class{ModelEff}} model class object
|
|
| 1155 |
#' @param data the \code{\linkS4class{Data}} object
|
|
| 1156 |
#' @param xlab the x axis label |
|
| 1157 |
#' @param ylab the y axis label |
|
| 1158 |
#' @param showLegend should the legend be shown? (default) |
|
| 1159 |
#' @param \dots not used |
|
| 1160 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 1161 |
#' object for the dose-efficacy model fit |
|
| 1162 |
#' |
|
| 1163 |
#' @example examples/Samples-method-plotModelEff.R |
|
| 1164 |
#' @export |
|
| 1165 |
#' @keywords methods |
|
| 1166 |
setMethod( |
|
| 1167 |
"plot", |
|
| 1168 |
signature = signature( |
|
| 1169 |
x = "Samples", |
|
| 1170 |
y = "ModelEff" |
|
| 1171 |
), |
|
| 1172 |
def = function( |
|
| 1173 |
x, |
|
| 1174 |
y, |
|
| 1175 |
data, |
|
| 1176 |
..., |
|
| 1177 |
xlab = "Dose level", |
|
| 1178 |
ylab = "Expected Efficacy", |
|
| 1179 |
showLegend = TRUE |
|
| 1180 |
) {
|
|
| 1181 |
## check args |
|
| 1182 | 6x |
assert_logical(showLegend) |
| 1183 | ||
| 1184 |
## get the fit |
|
| 1185 | 4x |
plotData <- fit( |
| 1186 | 4x |
x, |
| 1187 | 4x |
model = y, |
| 1188 | 4x |
data = data, |
| 1189 | 4x |
quantiles = c(0.025, 0.975), |
| 1190 | 4x |
middle = mean |
| 1191 |
) |
|
| 1192 | ||
| 1193 |
## make the plot |
|
| 1194 | 4x |
gdata <- |
| 1195 | 4x |
with( |
| 1196 | 4x |
plotData, |
| 1197 | 4x |
data.frame( |
| 1198 | 4x |
x = rep(dose, 3), |
| 1199 | 4x |
y = c(middle, lower, upper), |
| 1200 | 4x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotData)),
|
| 1201 | 4x |
Type = factor( |
| 1202 | 4x |
c( |
| 1203 | 4x |
rep( |
| 1204 | 4x |
"Estimate", |
| 1205 | 4x |
nrow(plotData) |
| 1206 |
), |
|
| 1207 | 4x |
rep( |
| 1208 | 4x |
"95% Credible Interval", |
| 1209 | 4x |
nrow(plotData) * 2 |
| 1210 |
) |
|
| 1211 |
), |
|
| 1212 | 4x |
levels = c( |
| 1213 | 4x |
"Estimate", |
| 1214 | 4x |
"95% Credible Interval" |
| 1215 |
) |
|
| 1216 |
) |
|
| 1217 |
) |
|
| 1218 |
) |
|
| 1219 | ||
| 1220 | 4x |
ret <- gdata %>% |
| 1221 | 4x |
ggplot() + |
| 1222 | 4x |
geom_line( |
| 1223 | 4x |
aes( |
| 1224 | 4x |
x = x, |
| 1225 | 4x |
y = y, |
| 1226 | 4x |
group = group, |
| 1227 | 4x |
linetype = Type |
| 1228 |
), |
|
| 1229 | 4x |
colour = I("blue")
|
| 1230 |
) + |
|
| 1231 | 4x |
labs( |
| 1232 | 4x |
x = xlab, |
| 1233 | 4x |
y = ylab |
| 1234 |
) + |
|
| 1235 | 4x |
coord_cartesian(xlim = c(0, max(data@doseGrid))) |
| 1236 | ||
| 1237 | 4x |
ret + |
| 1238 | 4x |
scale_linetype_manual( |
| 1239 | 4x |
breaks = c( |
| 1240 | 4x |
"Estimate", |
| 1241 | 4x |
"95% Credible Interval" |
| 1242 |
), |
|
| 1243 | 4x |
values = c(1, 2), |
| 1244 | 4x |
guide = ifelse(showLegend, "legend", "none") |
| 1245 |
) |
|
| 1246 |
} |
|
| 1247 |
) |
|
| 1248 | ||
| 1249 |
## ---------------------------------------------------------------------------------------- |
|
| 1250 |
## Plot of fitted dose-DLE curve based on a pseudo DLE model without sample |
|
| 1251 |
## ------------------------------------------------------------------------------------- |
|
| 1252 |
#' Plot of the fitted dose-tox based with a given pseudo DLE model and data without samples |
|
| 1253 |
#' |
|
| 1254 |
#' @param x the data of \code{\linkS4class{Data}} class object
|
|
| 1255 |
#' @param y the model of the \code{\linkS4class{ModelTox}} class object
|
|
| 1256 |
#' @param xlab the x axis label |
|
| 1257 |
#' @param ylab the y axis label |
|
| 1258 |
#' @param showLegend should the legend be shown? (default) |
|
| 1259 |
#' @param \dots not used |
|
| 1260 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 1261 |
#' object for the dose-DLE model plot |
|
| 1262 |
#' |
|
| 1263 |
#' @example examples/Samples-method-plotModelToxNoSamples.R |
|
| 1264 |
#' @export |
|
| 1265 |
#' @keywords methods |
|
| 1266 |
setMethod( |
|
| 1267 |
"plot", |
|
| 1268 |
signature = signature( |
|
| 1269 |
x = "Data", |
|
| 1270 |
y = "ModelTox" |
|
| 1271 |
), |
|
| 1272 |
def = function( |
|
| 1273 |
x, |
|
| 1274 |
y, |
|
| 1275 |
xlab = "Dose level", |
|
| 1276 |
ylab = "Probability of DLE", |
|
| 1277 |
showLegend = TRUE, |
|
| 1278 |
... |
|
| 1279 |
) {
|
|
| 1280 |
## check args |
|
| 1281 | 3x |
assert_logical(showLegend) |
| 1282 | ||
| 1283 |
## Make sure the right model estimates are use with the given data |
|
| 1284 | 2x |
y <- update(object = y, data = x) |
| 1285 | ||
| 1286 |
## create data frame |
|
| 1287 | ||
| 1288 | 2x |
plotData <- data.frame( |
| 1289 | 2x |
dose = x@doseGrid, |
| 1290 | 2x |
probDLE = prob( |
| 1291 | 2x |
dose = x@doseGrid, |
| 1292 | 2x |
model = y |
| 1293 |
) |
|
| 1294 |
) |
|
| 1295 |
## Look for TD30 and TD35 |
|
| 1296 | 2x |
TD30 <- dose( |
| 1297 | 2x |
x = 0.30, |
| 1298 | 2x |
model = y |
| 1299 |
) |
|
| 1300 | 2x |
TD35 <- dose( |
| 1301 | 2x |
x = 0.35, |
| 1302 | 2x |
model = y |
| 1303 |
) |
|
| 1304 | ||
| 1305 |
## make the plot |
|
| 1306 | 2x |
gdata <- with( |
| 1307 | 2x |
plotData, |
| 1308 | 2x |
data.frame( |
| 1309 | 2x |
x = dose, |
| 1310 | 2x |
y = probDLE, |
| 1311 | 2x |
group = rep("Estimated DLE", each = nrow(plotData)),
|
| 1312 | 2x |
Type = factor( |
| 1313 | 2x |
rep("Estimated DLE", nrow(plotData)),
|
| 1314 | 2x |
levels = "Estimated DLE" |
| 1315 |
) |
|
| 1316 |
) |
|
| 1317 |
) |
|
| 1318 | ||
| 1319 | 2x |
gdata %>% |
| 1320 | 2x |
ggplot() + |
| 1321 | 2x |
geom_line( |
| 1322 | 2x |
aes( |
| 1323 | 2x |
x = x, |
| 1324 | 2x |
y = y, |
| 1325 | 2x |
group = group, |
| 1326 | 2x |
linetype = Type |
| 1327 |
), |
|
| 1328 | 2x |
colour = I("red"),
|
| 1329 | 2x |
linewidth = 1.5 |
| 1330 |
) + |
|
| 1331 | 2x |
labs( |
| 1332 | 2x |
x = xlab, |
| 1333 | 2x |
y = ylab |
| 1334 |
) + |
|
| 1335 | 2x |
coord_cartesian(ylim = c(0, 1)) + |
| 1336 | 2x |
scale_linetype_manual( |
| 1337 | 2x |
breaks = "Estimated DLE", |
| 1338 | 2x |
values = c(1, 2), |
| 1339 | 2x |
guide = ifelse(showLegend, "legend", "none") |
| 1340 |
) |
|
| 1341 |
} |
|
| 1342 |
) |
|
| 1343 | ||
| 1344 | ||
| 1345 |
## --------------------------------------------------------------------------------------------- |
|
| 1346 |
## Plot the fitted dose-efficacy curve given a pseudo efficacy model without samples |
|
| 1347 |
## ---------------------------------------------------------------------------------- |
|
| 1348 |
#' Plot of the fitted dose-efficacy based with a given pseudo efficacy model and data without samples |
|
| 1349 |
#' |
|
| 1350 |
#' @param x the data of \code{\linkS4class{DataDual}} class object
|
|
| 1351 |
#' @param y the model of the \code{\linkS4class{ModelEff}} class object
|
|
| 1352 |
#' @param xlab the x axis label |
|
| 1353 |
#' @param ylab the y axis label |
|
| 1354 |
#' @param showLegend should the legend be shown? (default) |
|
| 1355 |
#' @param \dots not used |
|
| 1356 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 1357 |
#' object for the dose-efficacy model plot |
|
| 1358 |
#' |
|
| 1359 |
#' @example examples/Samples-method-plotModelEffNoSamples.R |
|
| 1360 |
#' @export |
|
| 1361 |
#' @keywords methods |
|
| 1362 |
setMethod( |
|
| 1363 |
"plot", |
|
| 1364 |
signature = signature( |
|
| 1365 |
x = "DataDual", |
|
| 1366 |
y = "ModelEff" |
|
| 1367 |
), |
|
| 1368 |
def = function( |
|
| 1369 |
x, |
|
| 1370 |
y, |
|
| 1371 |
..., |
|
| 1372 |
xlab = "Dose level", |
|
| 1373 |
ylab = "Expected Efficacy", |
|
| 1374 |
showLegend = TRUE |
|
| 1375 |
) {
|
|
| 1376 |
## check args |
|
| 1377 | 1x |
assert_logical(showLegend) |
| 1378 | 1x |
y <- update(object = y, data = x) |
| 1379 | ||
| 1380 |
## create data frame |
|
| 1381 | ||
| 1382 | 1x |
plotEffData <- data.frame( |
| 1383 | 1x |
dose = x@doseGrid, |
| 1384 | 1x |
ExpEff = efficacy( |
| 1385 | 1x |
dose = x@doseGrid, |
| 1386 | 1x |
model = y |
| 1387 |
) |
|
| 1388 |
) |
|
| 1389 | ||
| 1390 |
## make the second plot |
|
| 1391 | 1x |
ggdata <- with( |
| 1392 | 1x |
plotEffData, |
| 1393 | 1x |
data.frame( |
| 1394 | 1x |
x = dose, |
| 1395 | 1x |
y = ExpEff, |
| 1396 | 1x |
group = rep("Estimated Expected Efficacy", each = nrow(plotEffData)),
|
| 1397 | 1x |
Type = factor( |
| 1398 | 1x |
rep("Estimated Expected Efficacy", nrow(plotEffData)),
|
| 1399 | 1x |
levels = "Estimated Expected Efficacy" |
| 1400 |
) |
|
| 1401 |
) |
|
| 1402 |
) |
|
| 1403 | ||
| 1404 |
## Get efficacy plot |
|
| 1405 | 1x |
plot2 <- ggplot(data = ggdata, aes(x = x, y = y, group = group)) + |
| 1406 | 1x |
xlab("Dose Levels") +
|
| 1407 | 1x |
ylab(paste("Estimated Expected Efficacy")) +
|
| 1408 | 1x |
xlim(c(0, max(x@doseGrid))) + |
| 1409 | 1x |
geom_line(colour = I("blue"), linewidth = 1.5)
|
| 1410 | ||
| 1411 | 1x |
plot2 + |
| 1412 | 1x |
geom_line(linewidth = 1.5, colour = "blue") |
| 1413 |
} |
|
| 1414 |
) |
|
| 1415 | ||
| 1416 |
## ---------------------------------------------------------------------------------------------------------- |
|
| 1417 |
## Plot the gain curve using a pseudo DLE and a pseudo Efficacy model with samples |
|
| 1418 |
## ---------------------------------------------------------------------------------------------------- |
|
| 1419 |
#' Plot the gain curve in addition with the dose-DLE and dose-efficacy curve using a given DLE pseudo model, |
|
| 1420 |
#' a DLE sample, a given efficacy pseudo model and an efficacy sample |
|
| 1421 |
#' |
|
| 1422 |
#' @param DLEmodel the dose-DLE model of \code{\linkS4class{ModelTox}} class object
|
|
| 1423 |
#' @param DLEsamples the DLE sample of \code{\linkS4class{Samples}} class object
|
|
| 1424 |
#' @param Effmodel the dose-efficacy model of \code{\linkS4class{ModelEff}} class object
|
|
| 1425 |
#' @param Effsamples the efficacy sample of of \code{\linkS4class{Samples}} class object
|
|
| 1426 |
#' @param data the data input of \code{\linkS4class{DataDual}} class object
|
|
| 1427 |
#' @param \dots not used |
|
| 1428 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 1429 |
#' object for the plot |
|
| 1430 |
#' |
|
| 1431 |
#' @example examples/Samples-method-plotGain.R |
|
| 1432 |
#' @export |
|
| 1433 |
#' @keywords methods |
|
| 1434 |
setGeneric( |
|
| 1435 |
"plotGain", |
|
| 1436 |
def = function(DLEmodel, DLEsamples, Effmodel, Effsamples, data, ...) {
|
|
| 1437 | 2x |
standardGeneric("plotGain")
|
| 1438 |
} |
|
| 1439 |
) |
|
| 1440 |
#' @describeIn plotGain Standard method |
|
| 1441 |
setMethod( |
|
| 1442 |
"plotGain", |
|
| 1443 |
signature = signature( |
|
| 1444 |
DLEmodel = "ModelTox", |
|
| 1445 |
DLEsamples = "Samples", |
|
| 1446 |
Effmodel = "ModelEff", |
|
| 1447 |
Effsamples = "Samples" |
|
| 1448 |
), |
|
| 1449 |
def = function(DLEmodel, DLEsamples, Effmodel, Effsamples, data, ...) {
|
|
| 1450 |
## Get fitted values for probabilities of DLE at all dose levels |
|
| 1451 | ||
| 1452 | 1x |
plotDLEData <- fit( |
| 1453 | 1x |
DLEsamples, |
| 1454 | 1x |
model = DLEmodel, |
| 1455 | 1x |
data = data, |
| 1456 | 1x |
quantiles = c(0.025, 0.975), |
| 1457 | 1x |
middle = mean |
| 1458 |
) |
|
| 1459 | ||
| 1460 |
## Get fitted values for mean efficacy values at all dose levels |
|
| 1461 | 1x |
plotEffData <- fit( |
| 1462 | 1x |
Effsamples, |
| 1463 | 1x |
model = Effmodel, |
| 1464 | 1x |
data = data, |
| 1465 | 1x |
quantiles = c(0.025, 0.975), |
| 1466 | 1x |
middle = mean |
| 1467 |
) |
|
| 1468 | ||
| 1469 |
## Get fitted values for gain values at all dose levels |
|
| 1470 | 1x |
plotGainData <- fitGain( |
| 1471 | 1x |
DLEmodel = DLEmodel, |
| 1472 | 1x |
DLEsamples = DLEsamples, |
| 1473 | 1x |
Effmodel = Effmodel, |
| 1474 | 1x |
Effsamples = Effsamples, |
| 1475 | 1x |
data = data |
| 1476 |
) |
|
| 1477 | ||
| 1478 |
## For each of the dose levels, take the mean for the probabilties of DLE, mean efiicacy values |
|
| 1479 |
## and gain values. Hence combine them into a data frame |
|
| 1480 | ||
| 1481 | 1x |
plotData <- data.frame( |
| 1482 | 1x |
dose = rep(data@doseGrid, 3), |
| 1483 | 1x |
values = c( |
| 1484 | 1x |
plotDLEData$middle, |
| 1485 | 1x |
plotEffData$middle, |
| 1486 | 1x |
plotGainData$middle |
| 1487 |
) |
|
| 1488 |
) |
|
| 1489 |
## only the line plots for the mean value of the DLE, efficacy and gain samples |
|
| 1490 |
## at all dose levels |
|
| 1491 | 1x |
gdata <- with( |
| 1492 | 1x |
plotData, |
| 1493 | 1x |
data.frame( |
| 1494 | 1x |
x = dose, |
| 1495 | 1x |
y = values, |
| 1496 | 1x |
group = c( |
| 1497 | 1x |
rep("p(DLE)", length(data@doseGrid)),
|
| 1498 | 1x |
rep("Mean Expected Efficacy", length(data@doseGrid)),
|
| 1499 | 1x |
rep("Gain", length(data@doseGrid))
|
| 1500 |
), |
|
| 1501 | 1x |
Type = factor("Estimate", levels = "Estimate")
|
| 1502 |
) |
|
| 1503 |
) |
|
| 1504 | ||
| 1505 | 1x |
ggplot(data = gdata, aes(x = x, y = y)) + |
| 1506 | 1x |
geom_line(aes(group = group, color = group), linewidth = 1.5) + |
| 1507 | 1x |
scale_colour_manual( |
| 1508 | 1x |
name = "curves", |
| 1509 | 1x |
values = c("green3", "blue", "red")
|
| 1510 |
) + |
|
| 1511 | 1x |
xlab("Dose Level") +
|
| 1512 | 1x |
xlim(c(0, max(data@doseGrid))) + |
| 1513 | 1x |
ylab(paste("Values")) +
|
| 1514 | 1x |
ylim(c(min(gdata$y), max(gdata$y))) |
| 1515 |
} |
|
| 1516 |
) |
|
| 1517 | ||
| 1518 |
## ---------------------------------------------------------------------------------------------------- |
|
| 1519 |
## Plot the gain curve using a pseudo DLE and a pseudo Efficacy model without samples |
|
| 1520 |
## ---------------------------------------------------------------------------------------------------- |
|
| 1521 |
#' Plot the gain curve in addition with the dose-DLE and dose-efficacy curve using a given DLE pseudo model, |
|
| 1522 |
#' and a given efficacy pseudo model |
|
| 1523 |
#' |
|
| 1524 |
#' @describeIn plotGain Standard method |
|
| 1525 |
#' @param size (`integer`)\cr a vector of length two defining the sizes of |
|
| 1526 |
#' the shapes used to identify the doses with, respectively, p(DLE = 0.3) and the |
|
| 1527 |
#' maximum gain |
|
| 1528 |
#' @param shape (`integer`)\cr a vector of length two defining the shapes |
|
| 1529 |
#' used to identify the doses with, respectively, p(DLE = 0.3) and the maximum gain |
|
| 1530 |
#' |
|
| 1531 |
#' @example examples/Samples-method-plotGainNoSamples.R |
|
| 1532 |
#' @export |
|
| 1533 |
#' @keywords methods |
|
| 1534 |
setMethod( |
|
| 1535 |
"plotGain", |
|
| 1536 |
signature = signature( |
|
| 1537 |
DLEmodel = "ModelTox", |
|
| 1538 |
DLEsamples = "missing", |
|
| 1539 |
Effmodel = "ModelEff", |
|
| 1540 |
Effsamples = "missing" |
|
| 1541 |
), |
|
| 1542 |
def = function( |
|
| 1543 |
DLEmodel, |
|
| 1544 |
Effmodel, |
|
| 1545 |
data, |
|
| 1546 |
size = c(8L, 8L), |
|
| 1547 |
shape = c(16L, 17L), |
|
| 1548 |
... |
|
| 1549 |
) {
|
|
| 1550 | 1x |
assert_integer(size, len = 2, any.missing = FALSE, lower = 0, upper = 20) |
| 1551 | 1x |
assert_integer( |
| 1552 | 1x |
shape, |
| 1553 | 1x |
len = 2, |
| 1554 | 1x |
any.missing = FALSE, |
| 1555 | 1x |
unique = TRUE, |
| 1556 | 1x |
lower = 0, |
| 1557 | 1x |
upper = 25 |
| 1558 |
) |
|
| 1559 |
## Make sure the model estimates are corresponds to the input data |
|
| 1560 | 1x |
DLEmodel <- update(object = DLEmodel, data = data) |
| 1561 | 1x |
Effmodel <- update(object = Effmodel, data = data) |
| 1562 | ||
| 1563 | 1x |
plotData <- data.frame( |
| 1564 | 1x |
dose = rep(data@doseGrid, 3), |
| 1565 | 1x |
values = c( |
| 1566 | 1x |
prob( |
| 1567 | 1x |
dose = data@doseGrid, |
| 1568 | 1x |
model = DLEmodel |
| 1569 |
), |
|
| 1570 | 1x |
efficacy( |
| 1571 | 1x |
dose = data@doseGrid, |
| 1572 | 1x |
model = Effmodel |
| 1573 |
), |
|
| 1574 | 1x |
gain( |
| 1575 | 1x |
dose = data@doseGrid, |
| 1576 | 1x |
model_dle = DLEmodel, |
| 1577 | 1x |
model_eff = Effmodel |
| 1578 |
) |
|
| 1579 |
) |
|
| 1580 |
) |
|
| 1581 | 1x |
gdata <- with( |
| 1582 | 1x |
plotData, |
| 1583 | 1x |
data.frame( |
| 1584 | 1x |
x = dose, |
| 1585 | 1x |
y = values, |
| 1586 | 1x |
group = c( |
| 1587 | 1x |
rep("p(DLE)", length(data@doseGrid)),
|
| 1588 | 1x |
rep("Expected Efficacy", length(data@doseGrid)),
|
| 1589 | 1x |
rep("Gain", length(data@doseGrid))
|
| 1590 |
), |
|
| 1591 | 1x |
colour = rep(c("blue", "green3", "red")),
|
| 1592 | 1x |
Type = factor("Estimate", levels = "Estimate")
|
| 1593 |
) |
|
| 1594 |
) |
|
| 1595 | ||
| 1596 |
# if changing the line type is unacceptable, consider |
|
| 1597 |
# https://stackoverflow.com/questions/25632242/filled-and-hollow-shapes-where-the-fill-color-the-line-color |
|
| 1598 | 1x |
plot1 <- ggplot(data = gdata, aes(x = x, y = y)) + |
| 1599 | 1x |
geom_line( |
| 1600 | 1x |
aes(group = group, linetype = group, colour = group), |
| 1601 | 1x |
linewidth = 1 |
| 1602 |
) + |
|
| 1603 | 1x |
scale_colour_manual( |
| 1604 | 1x |
name = "Curves", |
| 1605 | 1x |
values = c("blue", "green3", "red")
|
| 1606 |
) + |
|
| 1607 | 1x |
scale_linetype_manual( |
| 1608 | 1x |
name = "Curves", |
| 1609 | 1x |
values = c("solid", "dotted", "dashed")
|
| 1610 |
) + |
|
| 1611 | 1x |
xlab("Dose Level") +
|
| 1612 | 1x |
ylab(paste("Values"))
|
| 1613 | ||
| 1614 | 1x |
TD30 <- dose(x = 0.3, model = DLEmodel) |
| 1615 | ||
| 1616 | 1x |
Gainfun <- function(DOSE) {
|
| 1617 | 66x |
-gain(DOSE, model_dle = DLEmodel, model_eff = Effmodel) |
| 1618 |
} |
|
| 1619 | 1x |
Gstar <- (optim( |
| 1620 | 1x |
min(data@doseGrid), |
| 1621 | 1x |
Gainfun, |
| 1622 | 1x |
method = "L-BFGS-B", |
| 1623 | 1x |
lower = min(data@doseGrid), |
| 1624 | 1x |
upper = max(data@doseGrid) |
| 1625 | 1x |
)$par) |
| 1626 | 1x |
MaxGain <- -(optim( |
| 1627 | 1x |
min(data@doseGrid), |
| 1628 | 1x |
Gainfun, |
| 1629 | 1x |
method = "L-BFGS-B", |
| 1630 | 1x |
lower = min(data@doseGrid), |
| 1631 | 1x |
upper = max(data@doseGrid) |
| 1632 | 1x |
)$value) |
| 1633 | ||
| 1634 | 1x |
if ((TD30 < min(data@doseGrid)) | (TD30 > max(data@doseGrid))) {
|
| 1635 | ! |
plot1 <- plot1 |
| 1636 | ! |
message(paste("TD30", paste(TD30, " not within dose Grid")))
|
| 1637 |
} else {
|
|
| 1638 | 1x |
plot1 <- plot1 + |
| 1639 | 1x |
geom_point( |
| 1640 | 1x |
data = data.frame(x = TD30, y = 0.3), |
| 1641 | 1x |
aes(x = x, y = y), |
| 1642 | 1x |
colour = "violet", |
| 1643 | 1x |
shape = 16, |
| 1644 | 1x |
size = 8 |
| 1645 |
) + |
|
| 1646 | 1x |
annotate( |
| 1647 | 1x |
"text", |
| 1648 | 1x |
label = "p(DLE=0.3)", |
| 1649 | 1x |
x = TD30 + 1, |
| 1650 | 1x |
y = 0.2, |
| 1651 | 1x |
size = 5, |
| 1652 | 1x |
colour = "violet" |
| 1653 |
) |
|
| 1654 |
} |
|
| 1655 | ||
| 1656 |
# Add annotated point estimates to graph |
|
| 1657 | 1x |
point_data <- tibble::tibble( |
| 1658 | 1x |
Text = NA_character_, |
| 1659 | 1x |
X = NA_real_, |
| 1660 | 1x |
Y = NA_real_, |
| 1661 | 1x |
Shape = NA_real_, |
| 1662 | 1x |
Size = NA_real_, |
| 1663 | 1x |
Colour = NA_character_, |
| 1664 | 1x |
.rows = 0 |
| 1665 |
) |
|
| 1666 | ||
| 1667 | 1x |
if ((TD30 < min(data@doseGrid)) | (TD30 > max(data@doseGrid))) {
|
| 1668 | ! |
message(paste("TD30", paste(TD30, " not within dose Grid")))
|
| 1669 |
} else {
|
|
| 1670 | 1x |
point_data <- point_data %>% |
| 1671 | 1x |
tibble::add_row( |
| 1672 | 1x |
X = TD30, |
| 1673 | 1x |
Y = 0.3, |
| 1674 | 1x |
Shape = shape[1], |
| 1675 | 1x |
Size = size[1], |
| 1676 | 1x |
Colour = "violet", |
| 1677 | 1x |
Text = "p(DLE=0.3)" |
| 1678 |
) |
|
| 1679 |
} |
|
| 1680 | 1x |
if ((Gstar < min(data@doseGrid)) | (Gstar > max(data@doseGrid))) {
|
| 1681 | ! |
print(paste("Gstar=", paste(Gstar, " not within dose Grid")))
|
| 1682 |
} else {
|
|
| 1683 | 1x |
plot1 <- plot1 + |
| 1684 | 1x |
geom_point( |
| 1685 | 1x |
data = data.frame(x = Gstar, y = MaxGain), |
| 1686 | 1x |
aes(x = x, y = y), |
| 1687 | 1x |
colour = "green3", |
| 1688 | 1x |
shape = 17, |
| 1689 | 1x |
size = 8 |
| 1690 |
) + |
|
| 1691 | 1x |
annotate( |
| 1692 | 1x |
"text", |
| 1693 | 1x |
label = "Max Gain", |
| 1694 | 1x |
x = Gstar, |
| 1695 | 1x |
y = MaxGain - 0.1, |
| 1696 | 1x |
size = 5, |
| 1697 | 1x |
colour = "green3" |
| 1698 |
) |
|
| 1699 |
} |
|
| 1700 | 1x |
point_data <- point_data %>% |
| 1701 | 1x |
tibble::add_row( |
| 1702 | 1x |
X = Gstar, |
| 1703 | 1x |
Y = MaxGain, |
| 1704 | 1x |
Shape = shape[2], |
| 1705 | 1x |
Size = size[2], |
| 1706 | 1x |
Colour = "green3", |
| 1707 | 1x |
Text = "Max Gain" |
| 1708 |
) |
|
| 1709 | ||
| 1710 | 1x |
plot1 + |
| 1711 | 1x |
geom_point( |
| 1712 | 1x |
data = point_data, |
| 1713 | 1x |
inherit.aes = FALSE, |
| 1714 | 1x |
aes( |
| 1715 | 1x |
x = .data$X, |
| 1716 | 1x |
y = .data$Y, |
| 1717 | 1x |
shape = as.factor(.data$Shape), |
| 1718 | 1x |
fill = .data$Colour |
| 1719 |
), |
|
| 1720 | 1x |
colour = point_data$Colour, |
| 1721 | 1x |
size = point_data$Size, |
| 1722 |
) + |
|
| 1723 | 1x |
scale_fill_manual( |
| 1724 | 1x |
name = "Estimates", |
| 1725 | 1x |
labels = c("p(DLE = 0.3)", "Max Gain"),
|
| 1726 | 1x |
values = point_data$Colour |
| 1727 |
) + |
|
| 1728 | 1x |
scale_shape_discrete( |
| 1729 | 1x |
name = "Estimates", |
| 1730 | 1x |
labels = c("p(DLE = 0.3)", "Max Gain"),
|
| 1731 | 1x |
breaks = point_data$Shape |
| 1732 |
) + |
|
| 1733 | 1x |
guides( |
| 1734 | 1x |
shape = guide_legend(override.aes = list(color = c("violet", "green3")))
|
| 1735 |
) + |
|
| 1736 | 1x |
coord_cartesian( |
| 1737 | 1x |
xlim = c(0, max(data@doseGrid)), |
| 1738 | 1x |
ylim = c(min(gdata$y), max(gdata$y)) |
| 1739 |
) |
|
| 1740 |
} |
|
| 1741 |
) |
|
| 1742 |
## ========================================================================================== |
|
| 1743 | ||
| 1744 |
## ------------------------------------------------------------------------------- |
|
| 1745 |
## Plot of the DLE and efficacy curve sides by side with samples |
|
| 1746 |
## ----------------------------------------------------------------------------- |
|
| 1747 |
#' Plot of the DLE and efficacy curve side by side given a DLE pseudo model, |
|
| 1748 |
#' a DLE sample, an efficacy pseudo model and a given efficacy sample |
|
| 1749 |
#' |
|
| 1750 |
#' @param DLEmodel the pseudo DLE model of \code{\linkS4class{ModelTox}} class object
|
|
| 1751 |
#' @param DLEsamples the DLE samples of \code{\linkS4class{Samples}} class object
|
|
| 1752 |
#' @param Effmodel the pseudo efficacy model of \code{\linkS4class{ModelEff}} class object
|
|
| 1753 |
#' @param Effsamples the Efficacy samples of \code{\linkS4class{Samples}} class object
|
|
| 1754 |
#' @param data the data input of \code{\linkS4class{DataDual}} class object
|
|
| 1755 |
#' @param extrapolate should the biomarker fit be extrapolated to the whole |
|
| 1756 |
#' dose grid? (default) |
|
| 1757 |
#' @param showLegend should the legend be shown? (not default) |
|
| 1758 |
#' @param \dots additional arguments for the parent method |
|
| 1759 |
#' \code{\link{plot,Samples,GeneralModel-method}}
|
|
| 1760 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 1761 |
#' object with the dose-toxicity and dose-efficacy model fits |
|
| 1762 |
#' |
|
| 1763 |
#' @example examples/Samples-method-plotDualResponses.R |
|
| 1764 |
#' |
|
| 1765 |
#' @export |
|
| 1766 |
#' @keywords methods |
|
| 1767 |
setGeneric( |
|
| 1768 |
"plotDualResponses", |
|
| 1769 |
def = function(DLEmodel, DLEsamples, Effmodel, Effsamples, data, ...) {
|
|
| 1770 | 7x |
standardGeneric("plotDualResponses")
|
| 1771 |
} |
|
| 1772 |
) |
|
| 1773 | ||
| 1774 |
#' @describeIn plotDualResponses function still to be documented |
|
| 1775 |
setMethod( |
|
| 1776 |
"plotDualResponses", |
|
| 1777 |
signature = signature( |
|
| 1778 |
DLEmodel = "ModelTox", |
|
| 1779 |
DLEsamples = "Samples", |
|
| 1780 |
Effmodel = "ModelEff", |
|
| 1781 |
Effsamples = "Samples" |
|
| 1782 |
), |
|
| 1783 |
def = function( |
|
| 1784 |
DLEmodel, |
|
| 1785 |
DLEsamples, |
|
| 1786 |
Effmodel, |
|
| 1787 |
Effsamples, |
|
| 1788 |
data, |
|
| 1789 |
extrapolate = TRUE, |
|
| 1790 |
showLegend = FALSE, |
|
| 1791 |
... |
|
| 1792 |
) {
|
|
| 1793 | 6x |
assert_logical(extrapolate) |
| 1794 | 5x |
assert_logical(showLegend) |
| 1795 |
## Get Toxicity plot |
|
| 1796 |
## get the fit |
|
| 1797 | ||
| 1798 | 4x |
plotDLEData <- fit( |
| 1799 | 4x |
DLEsamples, |
| 1800 | 4x |
model = DLEmodel, |
| 1801 | 4x |
data = data, |
| 1802 | 4x |
quantiles = c(0.025, 0.975), |
| 1803 | 4x |
middle = mean |
| 1804 |
) |
|
| 1805 | ||
| 1806 |
## make the plot |
|
| 1807 | 4x |
gdata <- |
| 1808 | 4x |
with( |
| 1809 | 4x |
plotDLEData, |
| 1810 | 4x |
data.frame( |
| 1811 | 4x |
x = rep(dose, 3), |
| 1812 | 4x |
y = c(middle, lower, upper) * 100, |
| 1813 | 4x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotDLEData)),
|
| 1814 | 4x |
Type = factor( |
| 1815 | 4x |
c( |
| 1816 | 4x |
rep( |
| 1817 | 4x |
"Estimate", |
| 1818 | 4x |
nrow(plotDLEData) |
| 1819 |
), |
|
| 1820 | 4x |
rep( |
| 1821 | 4x |
"95% Credible Interval", |
| 1822 | 4x |
nrow(plotDLEData) * 2 |
| 1823 |
) |
|
| 1824 |
), |
|
| 1825 | 4x |
levels = c( |
| 1826 | 4x |
"Estimate", |
| 1827 | 4x |
"95% Credible Interval" |
| 1828 |
) |
|
| 1829 |
) |
|
| 1830 |
) |
|
| 1831 |
) |
|
| 1832 | ||
| 1833 | 4x |
ret1 <- gdata %>% |
| 1834 | 4x |
ggplot() + |
| 1835 | 4x |
geom_line( |
| 1836 | 4x |
aes( |
| 1837 | 4x |
x = x, |
| 1838 | 4x |
y = y, |
| 1839 | 4x |
group = group, |
| 1840 | 4x |
linetype = Type |
| 1841 |
), |
|
| 1842 | 4x |
colour = I("red"),
|
| 1843 |
) + |
|
| 1844 | 4x |
labs( |
| 1845 | 4x |
x = "Dose Levels", |
| 1846 | 4x |
y = "Probability of DLE [%]" |
| 1847 |
) + |
|
| 1848 | 4x |
coord_cartesian(ylim = c(0, 100)) + |
| 1849 | 4x |
scale_linetype_manual( |
| 1850 | 4x |
breaks = c( |
| 1851 | 4x |
"Estimate", |
| 1852 | 4x |
"95% Credible Interval" |
| 1853 |
), |
|
| 1854 | 4x |
values = c(1, 2), |
| 1855 | 4x |
guide = ifelse(showLegend, "legend", "none") |
| 1856 |
) |
|
| 1857 |
## only look at these dose levels for the plot: |
|
| 1858 | ||
| 1859 | 4x |
xLevels <- if (extrapolate) {
|
| 1860 | 2x |
seq_along(data@doseGrid) |
| 1861 |
} else {
|
|
| 1862 | 2x |
1:max(data@xLevel) |
| 1863 |
} |
|
| 1864 | ||
| 1865 |
## get the plot data for the efficacy |
|
| 1866 | 4x |
functionSamples <- matrix( |
| 1867 | 4x |
nrow = size(Effsamples), |
| 1868 | 4x |
ncol = length(xLevels) |
| 1869 |
) |
|
| 1870 |
## evaluate the efficacy for all samples |
|
| 1871 | 4x |
for (i in seq_along(xLevels)) {
|
| 1872 |
## Now we want to evaluate for the following dose |
|
| 1873 | 48x |
functionSamples[, i] <- efficacy( |
| 1874 | 48x |
dose = data@doseGrid[xLevels[i]], |
| 1875 | 48x |
model = Effmodel, |
| 1876 | 48x |
samples = Effsamples |
| 1877 |
) |
|
| 1878 |
} |
|
| 1879 |
## extract mean curve |
|
| 1880 | 4x |
meanCurve <- colMeans(functionSamples) |
| 1881 | ||
| 1882 |
## extract quantiles |
|
| 1883 | 4x |
quantiles <- c(0.025, 0.975) |
| 1884 | 4x |
quantCurve <- apply(functionSamples, 2L, quantile, prob = quantiles) |
| 1885 | ||
| 1886 |
## now create the data frame |
|
| 1887 | 4x |
plotEffData <- data.frame( |
| 1888 | 4x |
dose = data@doseGrid[xLevels], |
| 1889 | 4x |
mean = meanCurve, |
| 1890 | 4x |
lower = quantCurve[1, ], |
| 1891 | 4x |
upper = quantCurve[2, ] |
| 1892 |
) |
|
| 1893 |
## make the second plot |
|
| 1894 | 4x |
ggdata <- with( |
| 1895 | 4x |
plotEffData, |
| 1896 | 4x |
data.frame( |
| 1897 | 4x |
x = rep(dose, 3), |
| 1898 | 4x |
y = c(mean, lower, upper), |
| 1899 | 4x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotEffData)),
|
| 1900 | 4x |
Type = factor( |
| 1901 | 4x |
c( |
| 1902 | 4x |
rep( |
| 1903 | 4x |
"Estimate", |
| 1904 | 4x |
nrow(plotEffData) |
| 1905 |
), |
|
| 1906 | 4x |
rep( |
| 1907 | 4x |
"95% Credible Interval", |
| 1908 | 4x |
nrow(plotEffData) * 2 |
| 1909 |
) |
|
| 1910 |
), |
|
| 1911 | 4x |
levels = c( |
| 1912 | 4x |
"Estimate", |
| 1913 | 4x |
"95% Credible Interval" |
| 1914 |
) |
|
| 1915 |
) |
|
| 1916 |
) |
|
| 1917 |
) |
|
| 1918 | ||
| 1919 | 4x |
plot2 <- ggdata %>% |
| 1920 | 4x |
ggplot() + |
| 1921 | 4x |
geom_line( |
| 1922 | 4x |
aes( |
| 1923 | 4x |
x = x, |
| 1924 | 4x |
y = y, |
| 1925 | 4x |
group = group, |
| 1926 | 4x |
linetype = Type |
| 1927 |
), |
|
| 1928 | 4x |
colour = I("blue"),
|
| 1929 |
) + |
|
| 1930 | 4x |
labs( |
| 1931 | 4x |
x = "Dose level", |
| 1932 | 4x |
y = "Expected Efficacy" |
| 1933 |
) + |
|
| 1934 | 4x |
scale_linetype_manual( |
| 1935 | 4x |
breaks = c( |
| 1936 | 4x |
"Estimate", |
| 1937 | 4x |
"95% Credible Interval" |
| 1938 |
), |
|
| 1939 | 4x |
values = c(1, 2), |
| 1940 | 4x |
guide = ifelse(showLegend, "legend", "none") |
| 1941 |
) |
|
| 1942 | ||
| 1943 |
## arrange both plots side by side |
|
| 1944 | 4x |
gridExtra::arrangeGrob(ret1, plot2, ncol = 2) |
| 1945 |
} |
|
| 1946 |
) |
|
| 1947 | ||
| 1948 |
## ------------------------------------------------------------------------------ |
|
| 1949 |
## Plot of the DLE and efficacy curve sides by side without samples |
|
| 1950 |
## ----------------------------------------------------------------------------- |
|
| 1951 |
#' Plot of the dose-DLE and dose-efficacy curve side by side given a DLE pseudo model |
|
| 1952 |
#' and a given pseudo efficacy model without DLE and efficacy samples |
|
| 1953 |
#' |
|
| 1954 |
#' @describeIn plotDualResponses Plot the DLE and efficacy curve side by side given a DLE model |
|
| 1955 |
#' and an efficacy model without any samples |
|
| 1956 |
#' |
|
| 1957 |
#' @example examples/Samples-method-plotDualResponsesNoSamples.R |
|
| 1958 |
#' |
|
| 1959 |
#' @export |
|
| 1960 |
#' @keywords methods |
|
| 1961 |
setMethod( |
|
| 1962 |
"plotDualResponses", |
|
| 1963 |
signature = signature( |
|
| 1964 |
DLEmodel = "ModelTox", |
|
| 1965 |
DLEsamples = "missing", |
|
| 1966 |
Effmodel = "ModelEff", |
|
| 1967 |
Effsamples = "missing" |
|
| 1968 |
), |
|
| 1969 |
def = function(DLEmodel, Effmodel, data, ...) {
|
|
| 1970 |
## Get Toxicity plot |
|
| 1971 |
## get the fit |
|
| 1972 | ||
| 1973 |
## Make sure the model estimates are corresponds to the input data |
|
| 1974 | 1x |
DLEmodel <- update(object = DLEmodel, data = data) |
| 1975 | 1x |
Effmodel <- update(object = Effmodel, data = data) |
| 1976 | ||
| 1977 | 1x |
plotDLEData <- data.frame( |
| 1978 | 1x |
dose = data@doseGrid, |
| 1979 | 1x |
probDLE = prob( |
| 1980 | 1x |
dose = data@doseGrid, |
| 1981 | 1x |
model = DLEmodel |
| 1982 |
) |
|
| 1983 |
) |
|
| 1984 |
## make the plot |
|
| 1985 | 1x |
gdata <- with( |
| 1986 | 1x |
plotDLEData, |
| 1987 | 1x |
data.frame( |
| 1988 | 1x |
x = dose, |
| 1989 | 1x |
y = probDLE, |
| 1990 | 1x |
group = rep("Estimated DLE", each = nrow(plotDLEData)),
|
| 1991 | 1x |
Type = factor( |
| 1992 | 1x |
rep("Estimated DLE", nrow(plotDLEData)),
|
| 1993 | 1x |
levels = "Estimated DLE" |
| 1994 |
) |
|
| 1995 |
) |
|
| 1996 |
) |
|
| 1997 | ||
| 1998 | 1x |
plot1 <- ggplot(data = gdata, aes(x = x, y = y, group = group)) + |
| 1999 | 1x |
xlab("Dose Levels") +
|
| 2000 | 1x |
ylab(paste("Probability of DLE")) +
|
| 2001 | 1x |
ylim(c(0, 1)) + |
| 2002 | 1x |
xlim(c(0, max(data@doseGrid))) + |
| 2003 | 1x |
geom_line(colour = I("red"), linewidth = 1.5)
|
| 2004 | ||
| 2005 | 1x |
plot1 <- plot1 + |
| 2006 | 1x |
geom_line(linewidth = 1.5, colour = "red") |
| 2007 | ||
| 2008 |
## only look at these dose levels for the plot: |
|
| 2009 | ||
| 2010 |
## get the plot data for the efficacy |
|
| 2011 | 1x |
plotEffData <- data.frame( |
| 2012 | 1x |
dose = data@doseGrid, |
| 2013 | 1x |
ExpEff = efficacy( |
| 2014 | 1x |
dose = data@doseGrid, |
| 2015 | 1x |
model = Effmodel |
| 2016 |
) |
|
| 2017 |
) |
|
| 2018 | ||
| 2019 |
## make the second plot |
|
| 2020 | 1x |
ggdata <- with( |
| 2021 | 1x |
plotEffData, |
| 2022 | 1x |
data.frame( |
| 2023 | 1x |
x = dose, |
| 2024 | 1x |
y = ExpEff, |
| 2025 | 1x |
group = rep("Estimated Expected Efficacy", each = nrow(plotEffData)),
|
| 2026 | 1x |
Type = factor( |
| 2027 | 1x |
rep("Estimated Expected Efficacy", nrow(plotEffData)),
|
| 2028 | 1x |
levels = "Estimated Expected Efficacy" |
| 2029 |
) |
|
| 2030 |
) |
|
| 2031 |
) |
|
| 2032 | ||
| 2033 |
## Get efficacy plot |
|
| 2034 | 1x |
plot2 <- ggplot(data = ggdata, aes(x = x, y = y, group = group)) + |
| 2035 | 1x |
xlab("Dose Levels") +
|
| 2036 | 1x |
ylab(paste("Estimated Expected Efficacy")) +
|
| 2037 | 1x |
xlim(c(0, max(data@doseGrid))) + |
| 2038 | 1x |
geom_line(colour = I("blue"), linewidth = 1.5)
|
| 2039 | ||
| 2040 | 1x |
plot2 <- plot2 + |
| 2041 | 1x |
geom_line(linewidth = 1.5, colour = "blue") |
| 2042 | ||
| 2043 |
## arrange both plots side by side |
|
| 2044 | 1x |
gridExtra::arrangeGrob(plot1, plot2, ncol = 2) |
| 2045 |
} |
|
| 2046 |
) |
|
| 2047 |
## ======================================================================================================= |
|
| 2048 | ||
| 2049 |
## ---------------------------------------------------------------- |
|
| 2050 |
## Get fitted DLT free survival (piecewise exponential model) based on |
|
| 2051 |
## the DA-CRM model |
|
| 2052 |
## ----------------------------------------------------------------- |
|
| 2053 |
#' Get the fitted DLT free survival (piecewise exponential model). |
|
| 2054 |
#' This function returns a data frame with dose, middle, lower and upper |
|
| 2055 |
#' quantiles for the `PEM` curve. If hazard=TRUE, |
|
| 2056 |
#' @param object mcmc samples |
|
| 2057 |
#' @param model the mDA-CRM model |
|
| 2058 |
#' @param data the data input, a \code{\linkS4class{DataDA}} class object
|
|
| 2059 |
#' @param quantiles the quantiles to be calculated (default: 0.025 and |
|
| 2060 |
#' 0.975) |
|
| 2061 |
#' @param middle the function for computing the middle point. Default: |
|
| 2062 |
#' \code{\link{mean}}
|
|
| 2063 |
#' @param hazard should the the hazard over time be plotted based on the `PEM`? (not default) |
|
| 2064 |
#' Otherwise ... |
|
| 2065 |
#' @param \dots additional arguments for methods |
|
| 2066 |
#' |
|
| 2067 |
#' @export |
|
| 2068 |
#' @keywords methods |
|
| 2069 |
setGeneric( |
|
| 2070 |
"fitPEM", |
|
| 2071 |
def = function( |
|
| 2072 |
object, |
|
| 2073 |
model, |
|
| 2074 |
data, |
|
| 2075 |
quantiles = c(0.025, 0.975), |
|
| 2076 |
middle = mean, |
|
| 2077 |
hazard = FALSE, |
|
| 2078 |
... |
|
| 2079 |
) {
|
|
| 2080 |
## there should be no default method, |
|
| 2081 |
## therefore just forward to next method! |
|
| 2082 | 12x |
standardGeneric("fitPEM")
|
| 2083 |
}, |
|
| 2084 |
valueClass = "data.frame" |
|
| 2085 |
) |
|
| 2086 | ||
| 2087 | ||
| 2088 |
#' Likelihood of DLTs in each interval |
|
| 2089 |
#' |
|
| 2090 |
#' This is a helper function for the `fitPEM` methods below. |
|
| 2091 |
#' |
|
| 2092 |
#' @param lambda the vector of piecewise hazards |
|
| 2093 |
#' @param Tmax the end of the time interval for DLTs |
|
| 2094 |
#' @return vector with the probabilities for DLTs within the intervals. |
|
| 2095 |
#' |
|
| 2096 |
#' @keywords internal |
|
| 2097 |
DLTLikelihood <- function(lambda, Tmax) {
|
|
| 2098 | 17000x |
npiece <- length(lambda) |
| 2099 | 17000x |
h <- seq(from = 0L, to = Tmax, length = npiece + 1) |
| 2100 | ||
| 2101 |
# Length of each time interval; |
|
| 2102 | 17000x |
sT <- rep(0, npiece) |
| 2103 | ||
| 2104 | 17000x |
for (i in 1:npiece) {
|
| 2105 | 170000x |
sT[i] <- h[i + 1] - h[i] |
| 2106 |
} |
|
| 2107 | ||
| 2108 |
# calculate the exponential part of the distribution: |
|
| 2109 | 17000x |
s_ij <- function(t, j) {
|
| 2110 | 10200000x |
if (t > h[j]) {
|
| 2111 | 8500000x |
min(t - h[j], h[j + 1] - h[j]) |
| 2112 |
} else {
|
|
| 2113 | 1700000x |
0 |
| 2114 |
} |
|
| 2115 |
} |
|
| 2116 | ||
| 2117 |
# The cumulative hazard function |
|
| 2118 | 17000x |
expNmu <- function(t) {
|
| 2119 | 1020000x |
ret <- 1 |
| 2120 | 1020000x |
for (j in 1:npiece) {
|
| 2121 | 10200000x |
ret <- ret * exp(-lambda[j] * s_ij(t, j)) |
| 2122 |
} |
|
| 2123 | 1020000x |
ret |
| 2124 |
} |
|
| 2125 | ||
| 2126 |
# CDF of the piecewise exponential |
|
| 2127 | 17000x |
piece_exp_cdf <- function(x) {
|
| 2128 | 340000x |
1 - expNmu(x) |
| 2129 |
} |
|
| 2130 | ||
| 2131 | 17000x |
DLTFreeS <- function(x) {
|
| 2132 | 340000x |
(expNmu(x) - expNmu(Tmax)) / piece_exp_cdf(Tmax) |
| 2133 |
} |
|
| 2134 | ||
| 2135 | 17000x |
pDLT <- rep(0, npiece + 1) |
| 2136 | ||
| 2137 | 17000x |
for (i in 1:(npiece)) {
|
| 2138 | 170000x |
pDLT[i] <- DLTFreeS(h[i]) - DLTFreeS(h[i + 1]) |
| 2139 |
} |
|
| 2140 | ||
| 2141 | 17000x |
pDLT |
| 2142 |
} |
|
| 2143 | ||
| 2144 |
## -------------------------------------------------------------------- |
|
| 2145 |
## Get fitted DLT free survival (piecewise exponential model) based on |
|
| 2146 |
## the DA-CRM model |
|
| 2147 |
## ------------------------------------------------------------- |
|
| 2148 |
#' @describeIn fitPEM This method works for the \code{\linkS4class{DALogisticLogNormal}}
|
|
| 2149 |
#' model class. |
|
| 2150 |
#' @example examples/Samples-method-fitPEM-DALogisticLogNormal.R |
|
| 2151 |
setMethod( |
|
| 2152 |
"fitPEM", |
|
| 2153 |
signature = signature( |
|
| 2154 |
object = "Samples", |
|
| 2155 |
model = "DALogisticLogNormal", |
|
| 2156 |
data = "DataDA" |
|
| 2157 |
), |
|
| 2158 |
def = function( |
|
| 2159 |
object, |
|
| 2160 |
model, |
|
| 2161 |
data, |
|
| 2162 |
quantiles = c(0.025, 0.975), |
|
| 2163 |
middle = mean, |
|
| 2164 |
hazard = FALSE, |
|
| 2165 |
... |
|
| 2166 |
) {
|
|
| 2167 |
## some checks |
|
| 2168 | 12x |
assert_probability_range(quantiles) |
| 2169 | 9x |
assert_logical(hazard) |
| 2170 |
## Plot points |
|
| 2171 | 8x |
points <- seq(0, data@Tmax, length = model@npiece + 1) |
| 2172 |
## first we have to get samples from the PEM |
|
| 2173 |
## at intercept points and 2 middel points between |
|
| 2174 |
## intercepts. |
|
| 2175 | 8x |
PEMSamples <- matrix( |
| 2176 | 8x |
nrow = size(object), |
| 2177 | 8x |
ncol = length(points) |
| 2178 |
) |
|
| 2179 | ||
| 2180 | 8x |
i_max <- max(seq_along(points)) |
| 2181 |
## evaluate the probs, for all samples. |
|
| 2182 | ||
| 2183 |
# The PEM |
|
| 2184 | 8x |
if (hazard == FALSE) {
|
| 2185 | 5x |
PEMSamples <- t(apply(object@data$lambda, 1, function(x) {
|
| 2186 | 17000x |
DLTLikelihood(x, data@Tmax) |
| 2187 |
})) |
|
| 2188 | 3x |
} else if (hazard == TRUE) {
|
| 2189 | 3x |
for (i in seq_along(points)) {
|
| 2190 | 33x |
if (i == i_max) {
|
| 2191 | 3x |
PEMSamples[, i_max] <- object@data$lambda[, model@npiece] |
| 2192 |
} else {
|
|
| 2193 | 30x |
PEMSamples[, i] <- object@data$lambda[, i] |
| 2194 |
} |
|
| 2195 |
} |
|
| 2196 |
} |
|
| 2197 | ||
| 2198 |
## extract middle curve |
|
| 2199 | 8x |
middleCurve <- apply(PEMSamples, 2L, FUN = middle) |
| 2200 | ||
| 2201 |
## extract quantiles |
|
| 2202 | 8x |
quantCurve <- apply(PEMSamples, 2L, quantile, prob = quantiles) |
| 2203 | ||
| 2204 |
## now create the data frame |
|
| 2205 | 8x |
data.frame( |
| 2206 | 8x |
time = points, |
| 2207 | 8x |
middle = middleCurve, |
| 2208 | 8x |
lower = quantCurve[1, ], |
| 2209 | 8x |
upper = quantCurve[2, ] |
| 2210 |
) |
|
| 2211 |
} |
|
| 2212 |
) |
|
| 2213 | ||
| 2214 |
## ======================================================================================================= |
|
| 2215 | ||
| 2216 |
## -------------------------------------------------- |
|
| 2217 |
## Plot survival curve fit over time |
|
| 2218 |
## -------------------------------------------------- |
|
| 2219 | ||
| 2220 |
## todo: add example file |
|
| 2221 |
#' Plotting dose-toxicity model fits |
|
| 2222 |
#' |
|
| 2223 |
#' @param x the \code{\linkS4class{Samples}} object
|
|
| 2224 |
#' @param y the \code{\linkS4class{DALogisticLogNormal}} object
|
|
| 2225 |
#' @param data the \code{\linkS4class{DataDA}} object
|
|
| 2226 |
#' @param hazard see \code{\link{fitPEM}} for the explanation
|
|
| 2227 |
#' @param \dots not used |
|
| 2228 |
#' @param showLegend should the legend be shown? (default) |
|
| 2229 |
#' @return This returns the \code{\link[ggplot2]{ggplot}}
|
|
| 2230 |
#' object for the dose-toxicity model fit |
|
| 2231 |
#' |
|
| 2232 |
#' @export |
|
| 2233 |
setMethod( |
|
| 2234 |
"plot", |
|
| 2235 |
signature = signature( |
|
| 2236 |
x = "Samples", |
|
| 2237 |
y = "DALogisticLogNormal" |
|
| 2238 |
), |
|
| 2239 |
def = function(x, y, data, hazard = FALSE, ..., showLegend = TRUE) {
|
|
| 2240 |
## check args |
|
| 2241 | 6x |
assert_logical(showLegend) |
| 2242 | 5x |
assert_logical(hazard) |
| 2243 | ||
| 2244 |
## call the superclass method, to get the toxicity plot |
|
| 2245 | 4x |
plot1 <- callNextMethod(x, y, data, showLegend = showLegend, ...) |
| 2246 | ||
| 2247 |
## get the fit |
|
| 2248 | 4x |
fitData <- fitPEM( |
| 2249 | 4x |
x, |
| 2250 | 4x |
model = y, |
| 2251 | 4x |
data = data, |
| 2252 | 4x |
quantiles = c(0.025, 0.975), |
| 2253 | 4x |
middle = mean, |
| 2254 | 4x |
hazard = hazard |
| 2255 |
) |
|
| 2256 | ||
| 2257 |
## make the plot |
|
| 2258 | 4x |
Tpoints <- seq(0, data@Tmax, length = y@npiece + 1) |
| 2259 | 4x |
plotData <- |
| 2260 | 4x |
with( |
| 2261 | 4x |
fitData, |
| 2262 | 4x |
data.frame( |
| 2263 | 4x |
x = rep(Tpoints, 3), |
| 2264 | 4x |
y = c(middle, lower, upper) * 100, |
| 2265 | 4x |
group = rep(c("mean", "lower", "upper"), each = nrow(fitData)),
|
| 2266 | 4x |
Type = factor( |
| 2267 | 4x |
c( |
| 2268 | 4x |
rep( |
| 2269 | 4x |
"Estimate", |
| 2270 | 4x |
nrow(fitData) |
| 2271 |
), |
|
| 2272 | 4x |
rep( |
| 2273 | 4x |
"95% Credible Interval", |
| 2274 | 4x |
nrow(fitData) * 2 |
| 2275 |
) |
|
| 2276 |
), |
|
| 2277 | 4x |
levels = c( |
| 2278 | 4x |
"Estimate", |
| 2279 | 4x |
"95% Credible Interval" |
| 2280 |
) |
|
| 2281 |
) |
|
| 2282 |
) |
|
| 2283 |
) |
|
| 2284 | 4x |
plot2 <- plotData %>% |
| 2285 | 4x |
ggplot() + |
| 2286 | 4x |
geom_step( |
| 2287 | 4x |
aes( |
| 2288 | 4x |
x = x, |
| 2289 | 4x |
y = y, |
| 2290 | 4x |
group = group, |
| 2291 | 4x |
linetype = Type |
| 2292 |
), |
|
| 2293 | 4x |
colour = I("blue")
|
| 2294 |
) + |
|
| 2295 | 4x |
labs( |
| 2296 | 4x |
x = "Time", |
| 2297 | 4x |
y = if (hazard) "Hazard rate*100" else "Probability of DLT [%]" |
| 2298 |
) + |
|
| 2299 | 4x |
coord_cartesian( |
| 2300 | 4x |
ylim = if (hazard) range(plotData$y) else c(0, 100) |
| 2301 |
) |
|
| 2302 | ||
| 2303 | 4x |
gridExtra::arrangeGrob(plot1, plot2, ncol = 2) |
| 2304 |
} |
|
| 2305 |
) |
|
| 2306 | ||
| 2307 | ||
| 2308 |
## ======================================================================================================= |
|
| 2309 | ||
| 2310 |
# tidy ---- |
|
| 2311 | ||
| 2312 |
## Samples |
|
| 2313 | ||
| 2314 |
## tidy-Samples ---- |
|
| 2315 | ||
| 2316 |
#' @rdname tidy |
|
| 2317 |
#' @aliases tidy-Samples |
|
| 2318 |
#' @example examples/Samples-method-tidy.R |
|
| 2319 |
#' @export |
|
| 2320 |
setMethod( |
|
| 2321 |
f = "tidy", |
|
| 2322 |
signature = signature(x = "Samples"), |
|
| 2323 |
definition = function(x, ...) {
|
|
| 2324 | 3x |
rv <- lapply( |
| 2325 | 3x |
slotNames(x), |
| 2326 | 3x |
function(nm) {
|
| 2327 | 6x |
if (nm == "data") {
|
| 2328 | 3x |
lapply( |
| 2329 | 3x |
names(x@data), |
| 2330 | 3x |
function(nm) {
|
| 2331 | 6x |
as_tibble(get(x, nm)) |
| 2332 |
} |
|
| 2333 |
) %>% |
|
| 2334 | 3x |
dplyr::bind_rows() %>% |
| 2335 | 3x |
tidyr::pivot_wider( |
| 2336 | 3x |
names_from = Parameter, |
| 2337 | 3x |
values_from = value |
| 2338 |
) %>% |
|
| 2339 | 3x |
dplyr::bind_cols(h_handle_attributes(get(x, names(x@data)[1]))) |
| 2340 |
} else {
|
|
| 2341 | 3x |
slot(x, nm) %>% |
| 2342 | 3x |
tidy() %>% |
| 2343 | 3x |
dplyr::bind_cols() |
| 2344 |
} |
|
| 2345 |
} |
|
| 2346 |
) |
|
| 2347 | 3x |
names(rv) <- c("data", "options")
|
| 2348 | 3x |
rv <- rv %>% h_tidy_class(x) |
| 2349 | 3x |
rv |
| 2350 |
} |
|
| 2351 |
) |
| 1 |
#' @include checkmate.R |
|
| 2 |
#' @include Model-methods.R |
|
| 3 |
#' @include Samples-class.R |
|
| 4 |
#' @include Rules-class.R |
|
| 5 |
#' @include helpers.R |
|
| 6 |
#' @include helpers_rules.R |
|
| 7 |
#' @include helpers_broom.R |
|
| 8 |
NULL |
|
| 9 | ||
| 10 |
# nextBest ---- |
|
| 11 | ||
| 12 |
## generic ---- |
|
| 13 | ||
| 14 |
#' Finding the Next Best Dose |
|
| 15 |
#' |
|
| 16 |
#' @description `r lifecycle::badge("stable")`
|
|
| 17 |
#' |
|
| 18 |
#' A function that computes the recommended next best dose based on the |
|
| 19 |
#' corresponding rule `nextBest`, the posterior `samples` from the `model` and |
|
| 20 |
#' the underlying `data`. |
|
| 21 |
#' |
|
| 22 |
#' @param nextBest (`NextBest`)\cr the rule for the next best dose. |
|
| 23 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. If it is an |
|
| 24 |
#' infinity (default), then essentially no dose limit will be applied in the |
|
| 25 |
#' course of dose recommendation calculation. |
|
| 26 |
#' @param samples (`Samples`)\cr posterior samples from `model` parameters given |
|
| 27 |
#' `data`. |
|
| 28 |
#' @param model (`GeneralModel`)\cr model that was used to generate the samples. |
|
| 29 |
#' @param data (`Data`)\cr data that was used to generate the samples. |
|
| 30 |
#' @param ... additional arguments without method dispatch. |
|
| 31 |
#' |
|
| 32 |
#' @return A list with the next best dose recommendation (element named `value`) |
|
| 33 |
#' from the grid defined in `data`, and a plot depicting this recommendation |
|
| 34 |
#' (element named `plot`). In case of multiple plots also an element |
|
| 35 |
#' named `singlePlots` is included. The `singlePlots` is itself a list with |
|
| 36 |
#' single plots. An additional list with elements describing the outcome |
|
| 37 |
#' of the rule can be contained too. |
|
| 38 |
#' |
|
| 39 |
#' @export |
|
| 40 |
#' |
|
| 41 |
setGeneric( |
|
| 42 |
name = "nextBest", |
|
| 43 |
def = function(nextBest, doselimit, samples, model, data, ...) {
|
|
| 44 | 597x |
if (!missing(doselimit)) {
|
| 45 | 538x |
assert_number(doselimit, lower = 0, finite = FALSE) |
| 46 |
} |
|
| 47 | 597x |
standardGeneric("nextBest")
|
| 48 |
}, |
|
| 49 |
valueClass = "list" |
|
| 50 |
) |
|
| 51 | ||
| 52 |
## NextBestEWOC ---- |
|
| 53 | ||
| 54 |
#' @describeIn nextBest find the next best dose based on the EWOC rule. |
|
| 55 |
#' |
|
| 56 |
#' @aliases nextBest-NextBestEWOC |
|
| 57 |
#' |
|
| 58 |
#' @export |
|
| 59 |
#' @example examples/Rules-method-nextBest-NextBestEWOC.R |
|
| 60 |
#' |
|
| 61 |
setMethod( |
|
| 62 |
f = "nextBest", |
|
| 63 |
signature = signature( |
|
| 64 |
nextBest = "NextBestEWOC", |
|
| 65 |
doselimit = "numeric", |
|
| 66 |
samples = "Samples", |
|
| 67 |
model = "GeneralModel", |
|
| 68 |
data = "Data" |
|
| 69 |
), |
|
| 70 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 71 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 72 | 2x |
prob_samples <- sapply( |
| 73 | 2x |
data@doseGrid, |
| 74 | 2x |
prob, |
| 75 | 2x |
model = model, |
| 76 | 2x |
samples = samples, |
| 77 |
... |
|
| 78 |
) |
|
| 79 | ||
| 80 |
# Estimates of posterior probabilities that are based on the prob. samples |
|
| 81 |
# which are within overdose/target interval. |
|
| 82 | 2x |
prob_overdose <- colMeans(h_in_range( |
| 83 | 2x |
prob_samples, |
| 84 | 2x |
nextBest@overdose, |
| 85 | 2x |
bounds_closed = c(FALSE, TRUE) |
| 86 |
)) |
|
| 87 | ||
| 88 |
# Eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
| 89 | 2x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 90 | 2x |
data@doseGrid, |
| 91 | 2x |
doselimit, |
| 92 | 2x |
data@placebo, |
| 93 | 2x |
levels = TRUE |
| 94 |
) & |
|
| 95 | 2x |
(prob_overdose <= nextBest@max_overdose_prob) |
| 96 | ||
| 97 | 2x |
next_dose <- if (any(is_dose_eligible)) {
|
| 98 |
# Take the highest eligible dose. |
|
| 99 | 2x |
next_best_level <- sum(is_dose_eligible) |
| 100 | 2x |
data@doseGrid[is_dose_eligible][next_best_level] |
| 101 |
} else {
|
|
| 102 | 2x |
NA_real_ |
| 103 |
} |
|
| 104 | ||
| 105 |
# Build plot for the overdosing probability. |
|
| 106 | 2x |
p <- ggplot() + |
| 107 | 2x |
geom_bar( |
| 108 | 2x |
data = data.frame(Dose = data@doseGrid, y = prob_overdose * 100), |
| 109 | 2x |
aes(x = .data$Dose, y = .data$y), |
| 110 | 2x |
stat = "identity", |
| 111 | 2x |
position = "identity", |
| 112 | 2x |
width = min(diff(data@doseGrid)) / 2, |
| 113 | 2x |
colour = "red", |
| 114 | 2x |
fill = "red" |
| 115 |
) + |
|
| 116 | 2x |
geom_hline( |
| 117 | 2x |
yintercept = nextBest@max_overdose_prob * 100, |
| 118 | 2x |
lwd = 1.1, |
| 119 | 2x |
lty = 2, |
| 120 | 2x |
colour = "black" |
| 121 |
) + |
|
| 122 | 2x |
ylim(c(0, 100)) + |
| 123 | 2x |
ylab("Overdose probability [%]")
|
| 124 | ||
| 125 | 2x |
list( |
| 126 | 2x |
value = next_dose, |
| 127 | 2x |
plot = p, |
| 128 | 2x |
singlePlots = list(overdose = p), |
| 129 | 2x |
probs = cbind( |
| 130 | 2x |
dose = data@doseGrid, |
| 131 | 2x |
overdose = prob_overdose |
| 132 |
) |
|
| 133 |
) |
|
| 134 |
} |
|
| 135 |
) |
|
| 136 | ||
| 137 | ||
| 138 |
## NextBestMTD ---- |
|
| 139 | ||
| 140 |
#' @describeIn nextBest find the next best dose based on the MTD rule. |
|
| 141 |
#' |
|
| 142 |
#' @aliases nextBest-NextBestMTD |
|
| 143 |
#' |
|
| 144 |
#' @export |
|
| 145 |
#' @example examples/Rules-method-nextBest-NextBestMTD.R |
|
| 146 |
#' |
|
| 147 |
setMethod( |
|
| 148 |
f = "nextBest", |
|
| 149 |
signature = signature( |
|
| 150 |
nextBest = "NextBestMTD", |
|
| 151 |
doselimit = "numeric", |
|
| 152 |
samples = "Samples", |
|
| 153 |
model = "GeneralModel", |
|
| 154 |
data = "Data" |
|
| 155 |
), |
|
| 156 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 157 |
# Generate the MTD samples and derive the next best dose. |
|
| 158 | 4x |
dose_target_samples <- dose(x = nextBest@target, model, samples, ...) |
| 159 | 4x |
dose_target <- nextBest@derive(dose_target_samples) |
| 160 | ||
| 161 |
# Round to the next possible grid point. |
|
| 162 | 4x |
doses_eligible <- h_next_best_eligible_doses( |
| 163 | 4x |
data@doseGrid, |
| 164 | 4x |
doselimit, |
| 165 | 4x |
data@placebo |
| 166 |
) |
|
| 167 | 4x |
next_dose_level <- which.min(abs(doses_eligible - dose_target)) |
| 168 | 4x |
next_dose <- doses_eligible[next_dose_level] |
| 169 | ||
| 170 |
# Create a plot. |
|
| 171 | 4x |
p <- ggplot( |
| 172 | 4x |
data = data.frame(x = dose_target_samples), |
| 173 | 4x |
aes(.data$x) |
| 174 |
) + |
|
| 175 | 4x |
geom_density(colour = "grey50") + |
| 176 | 4x |
coord_cartesian(xlim = range(data@doseGrid)) + |
| 177 | 4x |
geom_vline(xintercept = dose_target, colour = "black", lwd = 1.1) + |
| 178 | 4x |
geom_text( |
| 179 | 4x |
data = data.frame(x = dose_target), |
| 180 | 4x |
aes(.data$x, 0), |
| 181 | 4x |
label = "Est", |
| 182 | 4x |
vjust = -0.5, |
| 183 | 4x |
hjust = 0.5, |
| 184 | 4x |
colour = "black", |
| 185 | 4x |
angle = 90 |
| 186 |
) + |
|
| 187 | 4x |
xlab("MTD") +
|
| 188 | 4x |
ylab("Posterior density")
|
| 189 | ||
| 190 | 4x |
if (is.finite(doselimit)) {
|
| 191 | 2x |
p <- p + |
| 192 | 2x |
geom_vline(xintercept = doselimit, colour = "red", lwd = 1.1) + |
| 193 | 2x |
geom_text( |
| 194 | 2x |
data = data.frame(x = doselimit), |
| 195 | 2x |
aes(.data$x, 0), |
| 196 | 2x |
label = "Max", |
| 197 | 2x |
vjust = -0.5, |
| 198 | 2x |
hjust = -0.5, |
| 199 | 2x |
colour = "red", |
| 200 | 2x |
angle = 90 |
| 201 |
) |
|
| 202 |
} |
|
| 203 | ||
| 204 | 4x |
p <- p + |
| 205 | 4x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
| 206 | 4x |
geom_text( |
| 207 | 4x |
data = data.frame(x = next_dose), |
| 208 | 4x |
aes(.data$x, 0), |
| 209 | 4x |
label = "Next", |
| 210 | 4x |
vjust = -0.5, |
| 211 | 4x |
hjust = -1.5, |
| 212 | 4x |
colour = "blue", |
| 213 | 4x |
angle = 90 |
| 214 |
) |
|
| 215 | ||
| 216 | 4x |
list(value = next_dose, plot = p) |
| 217 |
} |
|
| 218 |
) |
|
| 219 | ||
| 220 |
## NextBestNCRM ---- |
|
| 221 | ||
| 222 |
#' @describeIn nextBest find the next best dose based on the NCRM method. The |
|
| 223 |
#' additional element `probs` in the output's list contains the target and |
|
| 224 |
#' overdosing probabilities (across all doses in the dose grid) used in the |
|
| 225 |
#' derivation of the next best dose. |
|
| 226 |
#' |
|
| 227 |
#' @aliases nextBest-NextBestNCRM |
|
| 228 |
#' |
|
| 229 |
#' @export |
|
| 230 |
#' @example examples/Rules-method-nextBest-NextBestNCRM.R |
|
| 231 |
#' |
|
| 232 |
setMethod( |
|
| 233 |
f = "nextBest", |
|
| 234 |
signature = signature( |
|
| 235 |
nextBest = "NextBestNCRM", |
|
| 236 |
doselimit = "numeric", |
|
| 237 |
samples = "Samples", |
|
| 238 |
model = "GeneralModel", |
|
| 239 |
data = "Data" |
|
| 240 |
), |
|
| 241 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 242 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 243 | 334x |
prob_samples <- sapply( |
| 244 | 334x |
data@doseGrid, |
| 245 | 334x |
prob, |
| 246 | 334x |
model = model, |
| 247 | 334x |
samples = samples, |
| 248 |
... |
|
| 249 |
) |
|
| 250 | ||
| 251 |
# Estimates of posterior probabilities that are based on the prob. samples |
|
| 252 |
# which are within overdose/target interval. |
|
| 253 | 334x |
prob_overdose <- colMeans(h_in_range( |
| 254 | 334x |
prob_samples, |
| 255 | 334x |
nextBest@overdose, |
| 256 | 334x |
bounds_closed = c(FALSE, TRUE) |
| 257 |
)) |
|
| 258 | 334x |
prob_target <- colMeans(h_in_range(prob_samples, nextBest@target)) |
| 259 | ||
| 260 |
# Eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
| 261 | 334x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 262 | 334x |
data@doseGrid, |
| 263 | 334x |
doselimit, |
| 264 | 334x |
data@placebo, |
| 265 | 334x |
levels = TRUE |
| 266 |
) & |
|
| 267 | 334x |
(prob_overdose <= nextBest@max_overdose_prob) |
| 268 | ||
| 269 | 334x |
next_dose <- if (any(is_dose_eligible)) {
|
| 270 |
# If maximum target probability is higher than some numerical threshold, |
|
| 271 |
# then take that level, otherwise stick to the maximum level that is OK. |
|
| 272 |
# next_best_level is relative to eligible doses. |
|
| 273 | 319x |
next_best_level <- ifelse( |
| 274 | 319x |
test = any(prob_target[is_dose_eligible] > 0.05), |
| 275 | 319x |
yes = which.max(prob_target[is_dose_eligible]), |
| 276 | 319x |
no = sum(is_dose_eligible) |
| 277 |
) |
|
| 278 | 319x |
data@doseGrid[is_dose_eligible][next_best_level] |
| 279 |
} else {
|
|
| 280 | 334x |
NA_real_ |
| 281 |
} |
|
| 282 | ||
| 283 |
# Build plots, first for the target probability. |
|
| 284 | 334x |
p1 <- ggplot() + |
| 285 | 334x |
geom_bar( |
| 286 | 334x |
data = data.frame(Dose = data@doseGrid, y = prob_target * 100), |
| 287 | 334x |
aes(x = .data$Dose, y = .data$y), |
| 288 | 334x |
stat = "identity", |
| 289 | 334x |
position = "identity", |
| 290 | 334x |
width = min(diff(data@doseGrid)) / 2, |
| 291 | 334x |
colour = "darkgreen", |
| 292 | 334x |
fill = "darkgreen" |
| 293 |
) + |
|
| 294 | 334x |
coord_cartesian(ylim = c(0, 100)) + |
| 295 | 334x |
ylab(paste("Target probability [%]"))
|
| 296 | ||
| 297 | 334x |
if (is.finite(doselimit)) {
|
| 298 | 331x |
p1 <- p1 + |
| 299 | 331x |
geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
| 300 |
} |
|
| 301 | ||
| 302 | 334x |
if (any(is_dose_eligible)) {
|
| 303 | 319x |
p1 <- p1 + |
| 304 | 319x |
geom_vline( |
| 305 | 319x |
xintercept = data@doseGrid[sum(is_dose_eligible)], |
| 306 | 319x |
lwd = 1.1, |
| 307 | 319x |
lty = 2, |
| 308 | 319x |
colour = "red" |
| 309 |
) + |
|
| 310 | 319x |
geom_point( |
| 311 | 319x |
data = data.frame( |
| 312 | 319x |
x = next_dose, |
| 313 | 319x |
y = prob_target[is_dose_eligible][next_best_level] * 100 + 0.03 |
| 314 |
), |
|
| 315 | 319x |
aes(x = x, y = y), |
| 316 | 319x |
size = 3, |
| 317 | 319x |
pch = 25, |
| 318 | 319x |
col = "red", |
| 319 | 319x |
bg = "red" |
| 320 |
) |
|
| 321 |
} |
|
| 322 | ||
| 323 |
# Second, for the overdosing probability. |
|
| 324 | 334x |
p2 <- ggplot() + |
| 325 | 334x |
geom_bar( |
| 326 | 334x |
data = data.frame(Dose = data@doseGrid, y = prob_overdose * 100), |
| 327 | 334x |
aes(x = .data$Dose, y = .data$y), |
| 328 | 334x |
stat = "identity", |
| 329 | 334x |
position = "identity", |
| 330 | 334x |
width = min(diff(data@doseGrid)) / 2, |
| 331 | 334x |
colour = "red", |
| 332 | 334x |
fill = "red" |
| 333 |
) + |
|
| 334 | 334x |
geom_hline( |
| 335 | 334x |
yintercept = nextBest@max_overdose_prob * 100, |
| 336 | 334x |
lwd = 1.1, |
| 337 | 334x |
lty = 2, |
| 338 | 334x |
colour = "black" |
| 339 |
) + |
|
| 340 | 334x |
ylim(c(0, 100)) + |
| 341 | 334x |
ylab("Overdose probability [%]")
|
| 342 | ||
| 343 |
# Place them below each other. |
|
| 344 | 334x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, nrow = 2) |
| 345 | ||
| 346 | 334x |
list( |
| 347 | 334x |
value = next_dose, |
| 348 | 334x |
plot = plot_joint, |
| 349 | 334x |
singlePlots = list(plot1 = p1, plot2 = p2), |
| 350 | 334x |
probs = cbind( |
| 351 | 334x |
dose = data@doseGrid, |
| 352 | 334x |
target = prob_target, |
| 353 | 334x |
overdose = prob_overdose |
| 354 |
) |
|
| 355 |
) |
|
| 356 |
} |
|
| 357 |
) |
|
| 358 | ||
| 359 |
## NextBestNCRM-DataParts ---- |
|
| 360 | ||
| 361 |
#' @describeIn nextBest find the next best dose based on the NCRM method when |
|
| 362 |
#' two parts trial is used. |
|
| 363 |
#' |
|
| 364 |
#' @aliases nextBest-NextBestNCRM-DataParts |
|
| 365 |
#' |
|
| 366 |
#' @export |
|
| 367 |
#' @example examples/Rules-method-nextBest-NextBestNCRM-DataParts.R |
|
| 368 |
#' |
|
| 369 |
setMethod( |
|
| 370 |
f = "nextBest", |
|
| 371 |
signature = signature( |
|
| 372 |
nextBest = "NextBestNCRM", |
|
| 373 |
doselimit = "numeric", |
|
| 374 |
samples = "Samples", |
|
| 375 |
model = "GeneralModel", |
|
| 376 |
data = "DataParts" |
|
| 377 |
), |
|
| 378 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 379 |
# Exception when we are in part I or about to start part II! |
|
| 380 | 4x |
if (all(data@part == 1L)) {
|
| 381 |
# Propose the highest possible dose (assuming that the dose limit came |
|
| 382 |
# from reasonable increments rule, i.e. incrementsRelativeParts). |
|
| 383 | 2x |
if (is.infinite(doselimit)) {
|
| 384 | 1x |
stop("A finite doselimit needs to be specified for Part I.")
|
| 385 |
} |
|
| 386 | 1x |
list(value = doselimit, plot = NULL) |
| 387 |
} else {
|
|
| 388 |
# Otherwise we will just do the standard thing. |
|
| 389 | 2x |
callNextMethod(nextBest, doselimit, samples, model, data, ...) |
| 390 |
} |
|
| 391 |
} |
|
| 392 |
) |
|
| 393 | ||
| 394 |
## NextBestNCRMLoss ---- |
|
| 395 | ||
| 396 |
#' @describeIn nextBest find the next best dose based on the NCRM method and |
|
| 397 |
#' loss function. |
|
| 398 |
#' |
|
| 399 |
#' @aliases nextBest-NextBestNCRMLoss |
|
| 400 |
#' |
|
| 401 |
#' @export |
|
| 402 |
#' @example examples/Rules-method-nextBest-NextBestNCRMLoss.R |
|
| 403 |
#' |
|
| 404 |
setMethod( |
|
| 405 |
"nextBest", |
|
| 406 |
signature = signature( |
|
| 407 |
nextBest = "NextBestNCRMLoss", |
|
| 408 |
doselimit = "numeric", |
|
| 409 |
samples = "Samples", |
|
| 410 |
model = "GeneralModel", |
|
| 411 |
data = "Data" |
|
| 412 |
), |
|
| 413 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 414 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 415 | 13x |
prob_samples <- sapply( |
| 416 | 13x |
data@doseGrid, |
| 417 | 13x |
prob, |
| 418 | 13x |
model = model, |
| 419 | 13x |
samples = samples, |
| 420 |
... |
|
| 421 |
) |
|
| 422 |
# Compute probabilities to be in target and overdose tox interval. |
|
| 423 | 13x |
prob_underdosing <- colMeans(prob_samples < nextBest@target[1]) |
| 424 | 13x |
prob_target <- colMeans(h_in_range(prob_samples, nextBest@target)) |
| 425 | 13x |
prob_overdose <- colMeans(h_in_range( |
| 426 | 13x |
prob_samples, |
| 427 | 13x |
nextBest@overdose, |
| 428 | 13x |
bounds_closed = c(FALSE, TRUE) |
| 429 |
)) |
|
| 430 | 13x |
prob_mean <- colMeans(prob_samples) |
| 431 | 13x |
prob_sd <- apply(prob_samples, 2, stats::sd) |
| 432 | ||
| 433 | 13x |
is_unacceptable_specified <- any(nextBest@unacceptable != c(1, 1)) |
| 434 | ||
| 435 | 13x |
prob_mat <- if (!is_unacceptable_specified) {
|
| 436 | 2x |
cbind( |
| 437 | 2x |
underdosing = prob_underdosing, |
| 438 | 2x |
target = prob_target, |
| 439 | 2x |
overdose = prob_overdose |
| 440 |
) |
|
| 441 |
} else {
|
|
| 442 | 11x |
prob_unacceptable <- colMeans( |
| 443 | 11x |
h_in_range( |
| 444 | 11x |
prob_samples, |
| 445 | 11x |
nextBest@unacceptable, |
| 446 | 11x |
bounds_closed = c(FALSE, TRUE) |
| 447 |
) |
|
| 448 |
) |
|
| 449 | 11x |
prob_excessive <- prob_overdose |
| 450 | 11x |
prob_overdose <- prob_excessive + prob_unacceptable |
| 451 | 11x |
cbind( |
| 452 | 11x |
underdosing = prob_underdosing, |
| 453 | 11x |
target = prob_target, |
| 454 | 11x |
excessive = prob_excessive, |
| 455 | 11x |
unacceptable = prob_unacceptable |
| 456 |
) |
|
| 457 |
} |
|
| 458 | ||
| 459 | 13x |
posterior_loss <- as.vector(nextBest@losses %*% t(prob_mat)) |
| 460 | 13x |
names(posterior_loss) <- data@doseGrid |
| 461 | ||
| 462 | 13x |
probs <- cbind( |
| 463 | 13x |
dose = data@doseGrid, |
| 464 | 13x |
prob_mat = prob_mat, |
| 465 | 13x |
mean = prob_mean, |
| 466 | 13x |
std_dev = prob_sd, |
| 467 | 13x |
posterior_loss = posterior_loss |
| 468 |
) |
|
| 469 | ||
| 470 |
# Eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
| 471 | 13x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 472 | 13x |
data@doseGrid, |
| 473 | 13x |
doselimit, |
| 474 | 13x |
data@placebo, |
| 475 | 13x |
levels = TRUE |
| 476 |
) & |
|
| 477 | 13x |
(prob_overdose < nextBest@max_overdose_prob) |
| 478 | ||
| 479 |
# Next best dose is the dose with the minimum loss function. |
|
| 480 | 13x |
next_dose <- if (any(is_dose_eligible)) {
|
| 481 | 13x |
next_best_level <- which.min(posterior_loss[is_dose_eligible]) |
| 482 | 13x |
data@doseGrid[is_dose_eligible][next_best_level] |
| 483 |
} else {
|
|
| 484 | 13x |
NA_real_ |
| 485 |
} |
|
| 486 | ||
| 487 |
# Build plot. |
|
| 488 | 13x |
p <- h_next_best_ncrm_loss_plot( |
| 489 | 13x |
prob_mat = prob_mat, |
| 490 | 13x |
posterior_loss = posterior_loss, |
| 491 | 13x |
max_overdose_prob = nextBest@max_overdose_prob, |
| 492 | 13x |
dose_grid = data@doseGrid, |
| 493 | 13x |
max_eligible_dose_level = sum(is_dose_eligible), |
| 494 | 13x |
doselimit = doselimit, |
| 495 | 13x |
next_dose = next_dose, |
| 496 | 13x |
is_unacceptable_specified = is_unacceptable_specified |
| 497 |
) |
|
| 498 | ||
| 499 | 13x |
c(list(value = next_dose, probs = probs), p) |
| 500 |
} |
|
| 501 |
) |
|
| 502 | ||
| 503 |
## NextBestThreePlusThree ---- |
|
| 504 | ||
| 505 |
#' @describeIn nextBest find the next best dose based on the 3+3 method. |
|
| 506 |
#' |
|
| 507 |
#' @aliases nextBest-NextBestThreePlusThree |
|
| 508 |
#' |
|
| 509 |
#' @export |
|
| 510 |
#' @example examples/Rules-method-nextBest-NextBestThreePlusThree.R |
|
| 511 |
#' |
|
| 512 |
setMethod( |
|
| 513 |
f = "nextBest", |
|
| 514 |
signature = signature( |
|
| 515 |
nextBest = "NextBestThreePlusThree", |
|
| 516 |
doselimit = "missing", |
|
| 517 |
samples = "missing", |
|
| 518 |
model = "missing", |
|
| 519 |
data = "Data" |
|
| 520 |
), |
|
| 521 |
definition = function(nextBest, doselimit, samples, model, data, ...) {
|
|
| 522 |
# The last dose level tested (not necessarily the maximum one). |
|
| 523 | 59x |
last_level <- tail(data@xLevel, 1L) |
| 524 | ||
| 525 |
# Get number of patients per grid's dose and DLT rate at the last level. |
|
| 526 | 59x |
nPatients <- table(factor(data@x, levels = data@doseGrid)) |
| 527 | 59x |
n_dlts_last_level <- sum(data@y[data@xLevel == last_level]) |
| 528 | 59x |
dlt_rate_last_level <- n_dlts_last_level / nPatients[last_level] |
| 529 | ||
| 530 | 59x |
level_change <- if (dlt_rate_last_level < 1 / 3) {
|
| 531 |
# Escalate it, unless this is the highest level or the higher dose was already tried. |
|
| 532 | 23x |
ifelse( |
| 533 | 23x |
(last_level == data@nGrid) || (nPatients[last_level + 1L] > 0), |
| 534 | 23x |
0L, |
| 535 | 23x |
1L |
| 536 |
) |
|
| 537 |
} else {
|
|
| 538 |
# Rate is too high, deescalate it, unless an edge case of 1/3, where the decision |
|
| 539 |
# depends on the num. of patients: if >3, then deescalate it, otherwise stay. |
|
| 540 | 36x |
ifelse( |
| 541 | 36x |
(dlt_rate_last_level == 1 / 3) && (nPatients[last_level] <= 3L), |
| 542 | 36x |
0L, |
| 543 | 36x |
-1L |
| 544 |
) |
|
| 545 |
} |
|
| 546 | 59x |
next_dose_level <- last_level + level_change |
| 547 | ||
| 548 |
# Do we stop here? Only if we have no MTD, or the next level has been tried |
|
| 549 |
# enough (more than three patients already). |
|
| 550 | 59x |
if (next_dose_level == 0L) {
|
| 551 | 3x |
next_dose <- NA |
| 552 | 3x |
stop_here <- TRUE |
| 553 |
} else {
|
|
| 554 | 56x |
next_dose <- data@doseGrid[next_dose_level] |
| 555 | 56x |
stop_here <- nPatients[next_dose_level] > 3L |
| 556 |
} |
|
| 557 | ||
| 558 | 59x |
list(value = next_dose, stopHere = stop_here) |
| 559 |
} |
|
| 560 |
) |
|
| 561 | ||
| 562 |
## NextBestDualEndpoint ---- |
|
| 563 | ||
| 564 |
#' @describeIn nextBest find the next best dose based on the dual endpoint |
|
| 565 |
#' model. The additional list element `probs` contains the target and |
|
| 566 |
#' overdosing probabilities (across all doses in the dose grid) used in the |
|
| 567 |
#' derivation of the next best dose. |
|
| 568 |
#' |
|
| 569 |
#' @aliases nextBest-NextBestDualEndpoint |
|
| 570 |
#' |
|
| 571 |
#' @export |
|
| 572 |
#' @example examples/Rules-method-nextBest-NextBestDualEndpoint.R |
|
| 573 |
#' |
|
| 574 |
setMethod( |
|
| 575 |
f = "nextBest", |
|
| 576 |
signature = signature( |
|
| 577 |
nextBest = "NextBestDualEndpoint", |
|
| 578 |
doselimit = "numeric", |
|
| 579 |
samples = "Samples", |
|
| 580 |
model = "DualEndpoint", |
|
| 581 |
data = "Data" |
|
| 582 |
), |
|
| 583 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 584 |
# Biomarker samples at the dose grid points. |
|
| 585 | 22x |
biom_samples <- samples@data$betaW |
| 586 | ||
| 587 | 22x |
prob_target <- if (nextBest@target_relative) {
|
| 588 |
# If 'Emax' parameter available, target biomarker level will be relative to 'Emax', |
|
| 589 |
# otherwise, it will be relative to the maximum biomarker level achieved |
|
| 590 |
# in dose range for a given sample. |
|
| 591 | 20x |
if ("Emax" %in% names(samples)) {
|
| 592 | 1x |
lwr <- nextBest@target[1] * samples@data$Emax |
| 593 | 1x |
upr <- nextBest@target[2] * samples@data$Emax |
| 594 | 1x |
colMeans(apply(biom_samples, 2L, function(s) (s >= lwr) & (s <= upr))) |
| 595 |
} else {
|
|
| 596 | 19x |
target_levels <- apply(biom_samples, 1L, function(x) {
|
| 597 | 8204x |
rng <- range(x) |
| 598 | 8204x |
min(which(h_in_range( |
| 599 | 8204x |
x, |
| 600 | 8204x |
nextBest@target * diff(rng) + rng[1] + c(0, 1e-10), |
| 601 | 8204x |
bounds_closed = c(FALSE, TRUE) |
| 602 |
))) |
|
| 603 |
}) |
|
| 604 | 19x |
prob_target <- as.vector(table(factor( |
| 605 | 19x |
target_levels, |
| 606 | 19x |
levels = 1:data@nGrid |
| 607 |
))) |
|
| 608 | 19x |
prob_target / nrow(biom_samples) |
| 609 |
} |
|
| 610 |
} else {
|
|
| 611 | 2x |
colMeans(h_in_range(biom_samples, nextBest@target)) |
| 612 |
} |
|
| 613 | ||
| 614 |
# Now, compute probabilities to be in overdose tox interval, then flag |
|
| 615 |
# eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
| 616 | 22x |
prob_samples <- sapply( |
| 617 | 22x |
data@doseGrid, |
| 618 | 22x |
prob, |
| 619 | 22x |
model = model, |
| 620 | 22x |
samples = samples |
| 621 |
) |
|
| 622 | 22x |
prob_overdose <- colMeans(h_in_range( |
| 623 | 22x |
prob_samples, |
| 624 | 22x |
nextBest@overdose, |
| 625 | 22x |
bounds_closed = c(FALSE, TRUE) |
| 626 |
)) |
|
| 627 | ||
| 628 | 22x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 629 | 22x |
data@doseGrid, |
| 630 | 22x |
doselimit, |
| 631 | 22x |
data@placebo, |
| 632 | 22x |
levels = TRUE |
| 633 |
) & |
|
| 634 | 22x |
(prob_overdose < nextBest@max_overdose_prob) |
| 635 | ||
| 636 | 22x |
next_dose <- if (any(is_dose_eligible)) {
|
| 637 |
# If maximum target probability is higher the threshold, then take that |
|
| 638 |
# level, otherwise stick to the maximum level that is eligible. |
|
| 639 |
# next_dose_level is relative to eligible doses. |
|
| 640 | 20x |
next_dose_level <- ifelse( |
| 641 | 20x |
test = any(prob_target[is_dose_eligible] > nextBest@target_thresh), |
| 642 | 20x |
yes = which.max(prob_target[is_dose_eligible]), |
| 643 | 20x |
no = sum(is_dose_eligible) |
| 644 |
) |
|
| 645 | 20x |
data@doseGrid[is_dose_eligible][next_dose_level] |
| 646 |
} else {
|
|
| 647 | 22x |
NA_real_ |
| 648 |
} |
|
| 649 | ||
| 650 |
# Build plots, first for the target probability. |
|
| 651 | 22x |
p1 <- ggplot() + |
| 652 | 22x |
geom_bar( |
| 653 | 22x |
data = data.frame(Dose = data@doseGrid, y = prob_target * 100), |
| 654 | 22x |
aes(x = .data$Dose, y = .data$y), |
| 655 | 22x |
stat = "identity", |
| 656 | 22x |
position = "identity", |
| 657 | 22x |
width = min(diff(data@doseGrid)) / 2, |
| 658 | 22x |
colour = "darkgreen", |
| 659 | 22x |
fill = "darkgreen" |
| 660 |
) + |
|
| 661 | 22x |
ylim(c(0, 100)) + |
| 662 | 22x |
ylab(paste("Target probability [%]"))
|
| 663 | ||
| 664 | 22x |
if (is.finite(doselimit)) {
|
| 665 | 21x |
p1 <- p1 + |
| 666 | 21x |
geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
| 667 |
} |
|
| 668 | ||
| 669 | 22x |
if (any(is_dose_eligible)) {
|
| 670 | 20x |
p1 <- p1 + |
| 671 | 20x |
geom_vline( |
| 672 | 20x |
xintercept = data@doseGrid[sum(is_dose_eligible)], |
| 673 | 20x |
lwd = 1.1, |
| 674 | 20x |
lty = 2, |
| 675 | 20x |
colour = "red" |
| 676 |
) + |
|
| 677 | 20x |
geom_point( |
| 678 | 20x |
data = data.frame( |
| 679 | 20x |
x = next_dose, |
| 680 | 20x |
y = prob_target[is_dose_eligible][next_dose_level] * 100 + 0.03 |
| 681 |
), |
|
| 682 | 20x |
aes(x = x, y = y), |
| 683 | 20x |
size = 3, |
| 684 | 20x |
pch = 25, |
| 685 | 20x |
col = "red", |
| 686 | 20x |
bg = "red" |
| 687 |
) |
|
| 688 |
} |
|
| 689 | ||
| 690 |
# Second, for the overdosing probability. |
|
| 691 | 22x |
p2 <- ggplot() + |
| 692 | 22x |
geom_bar( |
| 693 | 22x |
data = data.frame(Dose = data@doseGrid, y = prob_overdose * 100), |
| 694 | 22x |
aes(x = .data$Dose, y = .data$y), |
| 695 | 22x |
stat = "identity", |
| 696 | 22x |
position = "identity", |
| 697 | 22x |
width = min(diff(data@doseGrid)) / 2, |
| 698 | 22x |
colour = "red", |
| 699 | 22x |
fill = "red" |
| 700 |
) + |
|
| 701 | 22x |
geom_hline( |
| 702 | 22x |
yintercept = nextBest@max_overdose_prob * 100, |
| 703 | 22x |
lwd = 1.1, |
| 704 | 22x |
lty = 2, |
| 705 | 22x |
colour = "black" |
| 706 |
) + |
|
| 707 | 22x |
ylim(c(0, 100)) + |
| 708 | 22x |
ylab("Overdose probability [%]")
|
| 709 | ||
| 710 |
# Place them below each other. |
|
| 711 | 22x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, nrow = 2) |
| 712 | ||
| 713 | 22x |
list( |
| 714 | 22x |
value = next_dose, |
| 715 | 22x |
plot = plot_joint, |
| 716 | 22x |
singlePlots = list(plot1 = p1, plot2 = p2), |
| 717 | 22x |
probs = cbind( |
| 718 | 22x |
dose = data@doseGrid, |
| 719 | 22x |
target = prob_target, |
| 720 | 22x |
overdose = prob_overdose |
| 721 |
) |
|
| 722 |
) |
|
| 723 |
} |
|
| 724 |
) |
|
| 725 | ||
| 726 |
## NextBestMinDist ---- |
|
| 727 | ||
| 728 |
#' @describeIn nextBest gives the dose which is below the dose limit and has an |
|
| 729 |
#' estimated DLT probability which is closest to the target dose. |
|
| 730 |
#' |
|
| 731 |
#' @aliases nextBest-NextBestMinDist |
|
| 732 |
#' |
|
| 733 |
#' @export |
|
| 734 |
#' |
|
| 735 |
setMethod( |
|
| 736 |
f = "nextBest", |
|
| 737 |
signature = signature( |
|
| 738 |
nextBest = "NextBestMinDist", |
|
| 739 |
doselimit = "numeric", |
|
| 740 |
samples = "Samples", |
|
| 741 |
model = "GeneralModel", |
|
| 742 |
data = "Data" |
|
| 743 |
), |
|
| 744 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 745 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 746 | 3x |
prob_samples <- sapply( |
| 747 | 3x |
data@doseGrid, |
| 748 | 3x |
prob, |
| 749 | 3x |
model = model, |
| 750 | 3x |
samples = samples, |
| 751 |
... |
|
| 752 |
) |
|
| 753 | 3x |
dlt_prob <- colMeans(prob_samples) |
| 754 | ||
| 755 |
# Determine the dose with the closest distance. |
|
| 756 | 3x |
dose_target <- data@doseGrid[which.min(abs(dlt_prob - nextBest@target))] |
| 757 | ||
| 758 |
# Determine next dose. |
|
| 759 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
| 760 | 3x |
data@doseGrid, |
| 761 | 3x |
doselimit, |
| 762 | 3x |
data@placebo |
| 763 |
) |
|
| 764 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
| 765 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
| 766 | ||
| 767 |
# Create a plot. |
|
| 768 | 3x |
p <- ggplot( |
| 769 | 3x |
data = data.frame(x = data@doseGrid, y = dlt_prob), |
| 770 | 3x |
aes(.data$x, .data$y) |
| 771 |
) + |
|
| 772 | 3x |
geom_line(colour = "grey50") + |
| 773 | 3x |
geom_point(fill = "grey50", colour = "grey50") + |
| 774 | 3x |
coord_cartesian(xlim = range(data@doseGrid)) + |
| 775 | 3x |
scale_x_continuous( |
| 776 | 3x |
labels = as.character(data@doseGrid), |
| 777 | 3x |
breaks = data@doseGrid, |
| 778 | 3x |
guide = guide_axis(check.overlap = TRUE) |
| 779 |
) + |
|
| 780 | 3x |
geom_hline(yintercept = nextBest@target, linetype = "dotted") + |
| 781 | 3x |
geom_vline(xintercept = dose_target, colour = "black", lwd = 1.1) + |
| 782 | 3x |
geom_text( |
| 783 | 3x |
data = data.frame(x = dose_target), |
| 784 | 3x |
aes(.data$x, 0), |
| 785 | 3x |
label = "Est", |
| 786 | 3x |
vjust = -0.5, |
| 787 | 3x |
hjust = 0.5, |
| 788 | 3x |
colour = "black", |
| 789 | 3x |
angle = 90 |
| 790 |
) + |
|
| 791 | 3x |
xlab("Dose") +
|
| 792 | 3x |
ylab("Posterior toxicity probability")
|
| 793 | ||
| 794 | 3x |
if (is.finite(doselimit)) {
|
| 795 | 2x |
p <- p + |
| 796 | 2x |
geom_vline(xintercept = doselimit, colour = "red", lwd = 1.1) + |
| 797 | 2x |
geom_text( |
| 798 | 2x |
data = data.frame(x = doselimit), |
| 799 | 2x |
aes(.data$x, 0), |
| 800 | 2x |
label = "Max", |
| 801 | 2x |
vjust = -0.5, |
| 802 | 2x |
hjust = -0.5, |
| 803 | 2x |
colour = "red", |
| 804 | 2x |
angle = 90 |
| 805 |
) |
|
| 806 |
} |
|
| 807 | ||
| 808 | 3x |
p <- p + |
| 809 | 3x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
| 810 | 3x |
geom_text( |
| 811 | 3x |
data = data.frame(x = next_dose), |
| 812 | 3x |
aes(.data$x, 0), |
| 813 | 3x |
label = "Next", |
| 814 | 3x |
vjust = -0.5, |
| 815 | 3x |
hjust = -1.5, |
| 816 | 3x |
colour = "blue", |
| 817 | 3x |
angle = 90 |
| 818 |
) |
|
| 819 | ||
| 820 | 3x |
list( |
| 821 | 3x |
value = next_dose, |
| 822 | 3x |
probs = cbind(dose = data@doseGrid, dlt_prob = dlt_prob), |
| 823 | 3x |
plot = p |
| 824 |
) |
|
| 825 |
} |
|
| 826 |
) |
|
| 827 | ||
| 828 |
## NextBestInfTheory ---- |
|
| 829 | ||
| 830 |
#' @describeIn nextBest gives the appropriate dose within an information |
|
| 831 |
#' theoretic framework. |
|
| 832 |
#' |
|
| 833 |
#' @aliases nextBest-NextBestInfTheory |
|
| 834 |
#' |
|
| 835 |
#' @export |
|
| 836 |
#' |
|
| 837 |
setMethod( |
|
| 838 |
f = "nextBest", |
|
| 839 |
signature = signature( |
|
| 840 |
nextBest = "NextBestInfTheory", |
|
| 841 |
doselimit = "numeric", |
|
| 842 |
samples = "Samples", |
|
| 843 |
model = "GeneralModel", |
|
| 844 |
data = "Data" |
|
| 845 |
), |
|
| 846 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 847 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 848 | 57x |
prob_samples <- sapply( |
| 849 | 57x |
data@doseGrid, |
| 850 | 57x |
prob, |
| 851 | 57x |
model = model, |
| 852 | 57x |
samples = samples, |
| 853 |
... |
|
| 854 |
) |
|
| 855 | ||
| 856 | 57x |
criterion <- colMeans(h_info_theory_dist( |
| 857 | 57x |
prob_samples, |
| 858 | 57x |
nextBest@target, |
| 859 | 57x |
nextBest@asymmetry |
| 860 |
)) |
|
| 861 | ||
| 862 | 57x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 863 | 57x |
data@doseGrid, |
| 864 | 57x |
doselimit, |
| 865 | 57x |
data@placebo, |
| 866 | 57x |
levels = TRUE |
| 867 |
) |
|
| 868 | 57x |
doses_eligible <- data@doseGrid[is_dose_eligible] |
| 869 | 57x |
next_best_level <- which.min(criterion[is_dose_eligible]) |
| 870 | 57x |
next_best <- doses_eligible[next_best_level] |
| 871 | 57x |
list(value = next_best) |
| 872 |
} |
|
| 873 |
) |
|
| 874 | ||
| 875 |
## NextBestTD ---- |
|
| 876 | ||
| 877 |
#' @describeIn nextBest find the next best dose based only on the DLT responses |
|
| 878 |
#' and for [`LogisticIndepBeta`] model class object without DLT samples. |
|
| 879 |
#' |
|
| 880 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 881 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
| 882 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
| 883 |
#' are outside of the dose grid range, the information message is printed by |
|
| 884 |
#' this method. |
|
| 885 |
#' |
|
| 886 |
#' @aliases nextBest-NextBestTD |
|
| 887 |
#' |
|
| 888 |
#' @export |
|
| 889 |
#' @example examples/Rules-method-nextBest-NextBestTD.R |
|
| 890 |
#' |
|
| 891 |
setMethod( |
|
| 892 |
f = "nextBest", |
|
| 893 |
signature = signature( |
|
| 894 |
nextBest = "NextBestTD", |
|
| 895 |
doselimit = "numeric", |
|
| 896 |
samples = "missing", |
|
| 897 |
model = "LogisticIndepBeta", |
|
| 898 |
data = "Data" |
|
| 899 |
), |
|
| 900 |
definition = function( |
|
| 901 |
nextBest, |
|
| 902 |
doselimit = Inf, |
|
| 903 |
model, |
|
| 904 |
data, |
|
| 905 |
in_sim = FALSE, |
|
| 906 |
... |
|
| 907 |
) {
|
|
| 908 | 32x |
assert_flag(in_sim) |
| 909 | ||
| 910 |
# 'drt' - during the trial, 'eot' end of trial. |
|
| 911 | 32x |
prob_target_drt <- nextBest@prob_target_drt |
| 912 | 32x |
prob_target_eot <- nextBest@prob_target_eot |
| 913 | ||
| 914 |
# Target dose estimates, i.e. the dose with probability of the occurrence of |
|
| 915 |
# a DLT that equals to the prob_target_drt or prob_target_eot. |
|
| 916 | 32x |
dose_target_drt <- dose(x = prob_target_drt, model, ...) |
| 917 | 32x |
dose_target_eot <- dose(x = prob_target_eot, model, ...) |
| 918 | ||
| 919 |
# Find the next best doses in the doseGrid. The next best dose is the dose |
|
| 920 |
# at level closest and below the target dose estimate. |
|
| 921 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
| 922 | 32x |
doses_eligible <- h_next_best_eligible_doses( |
| 923 | 32x |
data@doseGrid, |
| 924 | 32x |
doselimit, |
| 925 | 32x |
data@placebo |
| 926 |
) |
|
| 927 | ||
| 928 | 32x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
| 929 | 32x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
| 930 | ||
| 931 | 32x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
| 932 | 32x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
| 933 | ||
| 934 |
# Find the variance of the log of the dose_target_eot. |
|
| 935 | 32x |
mat <- matrix( |
| 936 | 32x |
c( |
| 937 | 32x |
-1 / (model@phi2), |
| 938 | 32x |
-(log(prob_target_eot / (1 - prob_target_eot)) - model@phi1) / |
| 939 | 32x |
(model@phi2)^2 |
| 940 |
), |
|
| 941 | 32x |
nrow = 1 |
| 942 |
) |
|
| 943 | 32x |
var_dose_target_eot <- as.vector(mat %*% model@Pcov %*% t(mat)) |
| 944 | ||
| 945 |
# 95% credibility interval. |
|
| 946 | 32x |
ci_dose_target_eot <- exp( |
| 947 | 32x |
log(dose_target_eot) + c(-1, 1) * 1.96 * sqrt(var_dose_target_eot) |
| 948 |
) |
|
| 949 | 32x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
| 950 | ||
| 951 |
# Build plot. |
|
| 952 | 32x |
p <- h_next_best_td_plot( |
| 953 | 32x |
prob_target_drt = prob_target_drt, |
| 954 | 32x |
dose_target_drt = dose_target_drt, |
| 955 | 32x |
prob_target_eot = prob_target_eot, |
| 956 | 32x |
dose_target_eot = dose_target_eot, |
| 957 | 32x |
data = data, |
| 958 | 32x |
prob_dlt = prob(dose = data@doseGrid, model = model, ...), |
| 959 | 32x |
doselimit = doselimit, |
| 960 | 32x |
next_dose = next_dose_drt |
| 961 |
) |
|
| 962 | ||
| 963 |
if ( |
|
| 964 | 32x |
!h_in_range( |
| 965 | 32x |
dose_target_drt, |
| 966 | 32x |
range = dose_grid_range(data), |
| 967 | 32x |
bounds_closed = TRUE |
| 968 |
) && |
|
| 969 | 32x |
!in_sim |
| 970 |
) {
|
|
| 971 | 2x |
warning(paste( |
| 972 | 2x |
"TD", |
| 973 | 2x |
prob_target_drt * 100, |
| 974 |
"=", |
|
| 975 | 2x |
dose_target_drt, |
| 976 | 2x |
"not within dose grid" |
| 977 |
)) |
|
| 978 |
} |
|
| 979 |
if ( |
|
| 980 | 32x |
!h_in_range( |
| 981 | 32x |
dose_target_eot, |
| 982 | 32x |
range = dose_grid_range(data), |
| 983 | 32x |
bounds_closed = TRUE |
| 984 |
) && |
|
| 985 | 32x |
!in_sim |
| 986 |
) {
|
|
| 987 | 2x |
warning(paste( |
| 988 | 2x |
"TD", |
| 989 | 2x |
prob_target_eot * 100, |
| 990 |
"=", |
|
| 991 | 2x |
dose_target_eot, |
| 992 | 2x |
"not within dose grid" |
| 993 |
)) |
|
| 994 |
} |
|
| 995 | ||
| 996 | 32x |
list( |
| 997 | 32x |
next_dose_drt = next_dose_drt, |
| 998 | 32x |
prob_target_drt = prob_target_drt, |
| 999 | 32x |
dose_target_drt = dose_target_drt, |
| 1000 | 32x |
next_dose_eot = next_dose_eot, |
| 1001 | 32x |
prob_target_eot = prob_target_eot, |
| 1002 | 32x |
dose_target_eot = dose_target_eot, |
| 1003 | 32x |
ci_dose_target_eot = ci_dose_target_eot, |
| 1004 | 32x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
| 1005 | 32x |
plot = p |
| 1006 |
) |
|
| 1007 |
} |
|
| 1008 |
) |
|
| 1009 | ||
| 1010 |
## NextBestTDsamples ---- |
|
| 1011 | ||
| 1012 |
#' @describeIn nextBest find the next best dose based only on the DLT responses |
|
| 1013 |
#' and for [`LogisticIndepBeta`] model class object involving DLT samples. |
|
| 1014 |
#' |
|
| 1015 |
#' @aliases nextBest-NextBestTDsamples |
|
| 1016 |
#' |
|
| 1017 |
#' @export |
|
| 1018 |
#' @example examples/Rules-method-nextBest-NextBestTDsamples.R |
|
| 1019 |
#' |
|
| 1020 |
setMethod( |
|
| 1021 |
f = "nextBest", |
|
| 1022 |
signature = signature( |
|
| 1023 |
nextBest = "NextBestTDsamples", |
|
| 1024 |
doselimit = "numeric", |
|
| 1025 |
samples = "Samples", |
|
| 1026 |
model = "LogisticIndepBeta", |
|
| 1027 |
data = "Data" |
|
| 1028 |
), |
|
| 1029 |
definition = function( |
|
| 1030 |
nextBest, |
|
| 1031 |
doselimit = Inf, |
|
| 1032 |
samples, |
|
| 1033 |
model, |
|
| 1034 |
data, |
|
| 1035 |
in_sim, |
|
| 1036 |
... |
|
| 1037 |
) {
|
|
| 1038 |
# Generate target dose samples, i.e. the doses with probability of the |
|
| 1039 |
# occurrence of a DLT that equals to the nextBest@prob_target_drt |
|
| 1040 |
# (or nextBest@prob_target_eot, respectively). |
|
| 1041 | 24x |
dose_target_drt_samples <- dose( |
| 1042 | 24x |
x = nextBest@prob_target_drt, |
| 1043 | 24x |
model, |
| 1044 | 24x |
samples, |
| 1045 |
... |
|
| 1046 |
) |
|
| 1047 | 24x |
dose_target_eot_samples <- dose( |
| 1048 | 24x |
x = nextBest@prob_target_eot, |
| 1049 | 24x |
model, |
| 1050 | 24x |
samples, |
| 1051 |
... |
|
| 1052 |
) |
|
| 1053 | ||
| 1054 |
# Derive the prior/posterior estimates based on two above samples. |
|
| 1055 | 24x |
dose_target_drt <- nextBest@derive(dose_target_drt_samples) |
| 1056 | 24x |
dose_target_eot <- nextBest@derive(dose_target_eot_samples) |
| 1057 | ||
| 1058 |
# Find the next doses in the doseGrid. The next dose is the dose at level |
|
| 1059 |
# closest and below the dose_target_drt (or dose_target_eot, respectively). |
|
| 1060 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
| 1061 | 24x |
doses_eligible <- h_next_best_eligible_doses( |
| 1062 | 24x |
data@doseGrid, |
| 1063 | 24x |
doselimit, |
| 1064 | 24x |
data@placebo |
| 1065 |
) |
|
| 1066 | ||
| 1067 | 24x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
| 1068 | 24x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
| 1069 | ||
| 1070 | 24x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
| 1071 | 24x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
| 1072 | ||
| 1073 |
# 95% credibility interval. |
|
| 1074 | 24x |
ci_dose_target_eot <- as.numeric(quantile( |
| 1075 | 24x |
dose_target_eot_samples, |
| 1076 | 24x |
probs = c(0.025, 0.975) |
| 1077 |
)) |
|
| 1078 | 24x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
| 1079 | ||
| 1080 |
# Build plot. |
|
| 1081 | 24x |
p <- h_next_best_tdsamples_plot( |
| 1082 | 24x |
dose_target_drt_samples = dose_target_drt_samples, |
| 1083 | 24x |
dose_target_eot_samples = dose_target_eot_samples, |
| 1084 | 24x |
dose_target_drt = dose_target_drt, |
| 1085 | 24x |
dose_target_eot = dose_target_eot, |
| 1086 | 24x |
dose_grid_range = range(data@doseGrid), |
| 1087 | 24x |
nextBest = nextBest, |
| 1088 | 24x |
doselimit = doselimit, |
| 1089 | 24x |
next_dose = next_dose_drt |
| 1090 |
) |
|
| 1091 | ||
| 1092 | 24x |
list( |
| 1093 | 24x |
next_dose_drt = next_dose_drt, |
| 1094 | 24x |
prob_target_drt = nextBest@prob_target_drt, |
| 1095 | 24x |
dose_target_drt = dose_target_drt, |
| 1096 | 24x |
next_dose_eot = next_dose_eot, |
| 1097 | 24x |
prob_target_eot = nextBest@prob_target_eot, |
| 1098 | 24x |
dose_target_eot = dose_target_eot, |
| 1099 | 24x |
ci_dose_target_eot = ci_dose_target_eot, |
| 1100 | 24x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
| 1101 | 24x |
plot = p |
| 1102 |
) |
|
| 1103 |
} |
|
| 1104 |
) |
|
| 1105 | ||
| 1106 |
## NextBestMaxGain ---- |
|
| 1107 | ||
| 1108 |
#' @describeIn nextBest find the next best dose based only on pseudo DLT model |
|
| 1109 |
#' [`ModelTox`] and [`Effloglog`] efficacy model without samples. |
|
| 1110 |
#' |
|
| 1111 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 1112 |
#' @param model_eff (`Effloglog`)\cr the efficacy model. |
|
| 1113 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
| 1114 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
| 1115 |
#' are outside of the dose grid range, the information message is printed by |
|
| 1116 |
#' this method. |
|
| 1117 |
#' |
|
| 1118 |
#' @aliases nextBest-NextBestMaxGain |
|
| 1119 |
#' |
|
| 1120 |
#' @export |
|
| 1121 |
#' @example examples/Rules-method-nextBest-NextBestMaxGain.R |
|
| 1122 |
#' |
|
| 1123 |
setMethod( |
|
| 1124 |
f = "nextBest", |
|
| 1125 |
signature = signature( |
|
| 1126 |
nextBest = "NextBestMaxGain", |
|
| 1127 |
doselimit = "numeric", |
|
| 1128 |
samples = "missing", |
|
| 1129 |
model = "ModelTox", |
|
| 1130 |
data = "DataDual" |
|
| 1131 |
), |
|
| 1132 |
definition = function( |
|
| 1133 |
nextBest, |
|
| 1134 |
doselimit = Inf, |
|
| 1135 |
model, |
|
| 1136 |
data, |
|
| 1137 |
model_eff, |
|
| 1138 |
in_sim = FALSE, |
|
| 1139 |
... |
|
| 1140 |
) {
|
|
| 1141 | 24x |
assert_class(model_eff, "Effloglog") |
| 1142 | 24x |
assert_flag(in_sim) |
| 1143 | ||
| 1144 |
# 'drt' - during the trial, 'eot' end of trial. |
|
| 1145 | 24x |
prob_target_drt <- nextBest@prob_target_drt |
| 1146 | 24x |
prob_target_eot <- nextBest@prob_target_eot |
| 1147 | ||
| 1148 |
# Target dose estimates, i.e. the dose with probability of the occurrence of |
|
| 1149 |
# a DLT that equals to the prob_target_drt or prob_target_eot. |
|
| 1150 | 24x |
dose_target_drt <- dose(x = prob_target_drt, model, ...) |
| 1151 | 24x |
dose_target_eot <- dose(x = prob_target_eot, model, ...) |
| 1152 | ||
| 1153 |
# Find the dose which gives the maximum gain. |
|
| 1154 | 24x |
dosegrid_range <- dose_grid_range(data) |
| 1155 | 24x |
opt <- optim( |
| 1156 | 24x |
par = dosegrid_range[1], |
| 1157 | 24x |
fn = function(DOSE) {
|
| 1158 | 1002x |
-gain(DOSE, model_dle = model, model_eff = model_eff, ...) |
| 1159 |
}, |
|
| 1160 | 24x |
method = "L-BFGS-B", |
| 1161 | 24x |
lower = dosegrid_range[1], |
| 1162 | 24x |
upper = dosegrid_range[2] |
| 1163 |
) |
|
| 1164 | 24x |
dose_mg <- opt$par # this is G*. # no lintr |
| 1165 | 24x |
max_gain <- -opt$value |
| 1166 | ||
| 1167 |
# Print info message if dose target is outside of the range. |
|
| 1168 |
if ( |
|
| 1169 | 24x |
!h_in_range( |
| 1170 | 24x |
dose_target_drt, |
| 1171 | 24x |
range = dose_grid_range(data), |
| 1172 | 24x |
bounds_closed = FALSE |
| 1173 |
) && |
|
| 1174 | 24x |
!in_sim |
| 1175 |
) {
|
|
| 1176 | ! |
print(paste( |
| 1177 | ! |
"Estimated TD", |
| 1178 | ! |
prob_target_drt * 100, |
| 1179 |
"=", |
|
| 1180 | ! |
dose_target_drt, |
| 1181 | ! |
"not within dose grid" |
| 1182 |
)) |
|
| 1183 |
} |
|
| 1184 |
if ( |
|
| 1185 | 24x |
!h_in_range( |
| 1186 | 24x |
dose_target_eot, |
| 1187 | 24x |
range = dose_grid_range(data), |
| 1188 | 24x |
bounds_closed = FALSE |
| 1189 |
) && |
|
| 1190 | 24x |
!in_sim |
| 1191 |
) {
|
|
| 1192 | ! |
print(paste( |
| 1193 | ! |
"Estimated TD", |
| 1194 | ! |
prob_target_eot * 100, |
| 1195 |
"=", |
|
| 1196 | ! |
dose_target_eot, |
| 1197 | ! |
"not within dose grid" |
| 1198 |
)) |
|
| 1199 |
} |
|
| 1200 |
if ( |
|
| 1201 | 24x |
!h_in_range( |
| 1202 | 24x |
dose_mg, |
| 1203 | 24x |
range = dose_grid_range(data), |
| 1204 | 24x |
bounds_closed = FALSE |
| 1205 |
) && |
|
| 1206 | 24x |
!in_sim |
| 1207 |
) {
|
|
| 1208 | ! |
print(paste("Estimated max gain dose =", dose_mg, "not within dose grid"))
|
| 1209 |
} |
|
| 1210 | ||
| 1211 |
# Get closest grid doses for a given target doses. |
|
| 1212 | 24x |
nb_doses_at_grid <- h_next_best_mg_doses_at_grid( |
| 1213 | 24x |
dose_target_drt = dose_target_drt, |
| 1214 | 24x |
dose_target_eot = dose_target_eot, |
| 1215 | 24x |
dose_mg = dose_mg, |
| 1216 | 24x |
dose_grid = data@doseGrid, |
| 1217 | 24x |
doselimit = doselimit, |
| 1218 | 24x |
placebo = data@placebo |
| 1219 |
) |
|
| 1220 | ||
| 1221 |
# 95% credibility intervals and corresponding ratios for maximum gain dose and target dose eot. |
|
| 1222 | 24x |
ci <- h_next_best_mg_ci( |
| 1223 | 24x |
dose_target = dose_target_eot, |
| 1224 | 24x |
dose_mg = dose_mg, |
| 1225 | 24x |
prob_target = prob_target_eot, |
| 1226 | 24x |
placebo = data@placebo, |
| 1227 | 24x |
model = model, |
| 1228 | 24x |
model_eff = model_eff |
| 1229 |
) |
|
| 1230 | ||
| 1231 |
# Build plot. |
|
| 1232 | 24x |
p <- h_next_best_mg_plot( |
| 1233 | 24x |
prob_target_drt = prob_target_drt, |
| 1234 | 24x |
dose_target_drt = dose_target_drt, |
| 1235 | 24x |
prob_target_eot = prob_target_eot, |
| 1236 | 24x |
dose_target_eot = dose_target_eot, |
| 1237 | 24x |
dose_mg = dose_mg, |
| 1238 | 24x |
max_gain = max_gain, |
| 1239 | 24x |
next_dose = nb_doses_at_grid$next_dose, |
| 1240 | 24x |
doselimit = doselimit, |
| 1241 | 24x |
data = data, |
| 1242 | 24x |
model = model, |
| 1243 | 24x |
model_eff = model_eff |
| 1244 |
) |
|
| 1245 | ||
| 1246 | 24x |
list( |
| 1247 | 24x |
next_dose = nb_doses_at_grid$next_dose, |
| 1248 | 24x |
prob_target_drt = prob_target_drt, |
| 1249 | 24x |
dose_target_drt = dose_target_drt, |
| 1250 | 24x |
next_dose_drt = nb_doses_at_grid$next_dose_drt, |
| 1251 | 24x |
prob_target_eot = prob_target_eot, |
| 1252 | 24x |
dose_target_eot = dose_target_eot, |
| 1253 | 24x |
next_dose_eot = nb_doses_at_grid$next_dose_eot, |
| 1254 | 24x |
dose_max_gain = dose_mg, |
| 1255 | 24x |
next_dose_max_gain = nb_doses_at_grid$next_dose_mg, |
| 1256 | 24x |
ci_dose_target_eot = ci$ci_dose_target, |
| 1257 | 24x |
ci_ratio_dose_target_eot = ci$ci_ratio_dose_target, |
| 1258 | 24x |
ci_dose_max_gain = ci$ci_dose_mg, |
| 1259 | 24x |
ci_ratio_dose_max_gain = ci$ci_ratio_dose_mg, |
| 1260 | 24x |
plot = p |
| 1261 |
) |
|
| 1262 |
} |
|
| 1263 |
) |
|
| 1264 | ||
| 1265 |
## NextBestMaxGainSamples ---- |
|
| 1266 | ||
| 1267 |
#' @describeIn nextBest find the next best dose based on DLT and efficacy |
|
| 1268 |
#' responses with DLT and efficacy samples. |
|
| 1269 |
#' |
|
| 1270 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 1271 |
#' @param model_eff (`Effloglog` or `EffFlexi`)\cr the efficacy model. |
|
| 1272 |
#' @param samples_eff (`Samples`)\cr posterior samples from `model_eff` parameters |
|
| 1273 |
#' given `data`. |
|
| 1274 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
| 1275 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
| 1276 |
#' are outside of the dose grid range, the information message is printed by |
|
| 1277 |
#' this method. |
|
| 1278 |
#' |
|
| 1279 |
#' @aliases nextBest-NextBestMaxGainSamples |
|
| 1280 |
#' |
|
| 1281 |
#' @export |
|
| 1282 |
#' @example examples/Rules-method-nextBest-NextBestMaxGainSamples.R |
|
| 1283 |
#' |
|
| 1284 |
setMethod( |
|
| 1285 |
f = "nextBest", |
|
| 1286 |
signature = signature( |
|
| 1287 |
nextBest = "NextBestMaxGainSamples", |
|
| 1288 |
doselimit = "numeric", |
|
| 1289 |
samples = "Samples", |
|
| 1290 |
model = "ModelTox", |
|
| 1291 |
data = "DataDual" |
|
| 1292 |
), |
|
| 1293 |
definition = function( |
|
| 1294 |
nextBest, |
|
| 1295 |
doselimit = Inf, |
|
| 1296 |
samples, |
|
| 1297 |
model, |
|
| 1298 |
data, |
|
| 1299 |
model_eff, |
|
| 1300 |
samples_eff, |
|
| 1301 |
in_sim = FALSE, |
|
| 1302 |
... |
|
| 1303 |
) {
|
|
| 1304 | 13x |
assert_true( |
| 1305 | 13x |
test_class(model_eff, "Effloglog") || test_class(model_eff, "EffFlexi") |
| 1306 |
) |
|
| 1307 | 13x |
assert_class(samples_eff, "Samples") |
| 1308 | 13x |
assert_flag(in_sim) |
| 1309 | ||
| 1310 |
# 'drt' - during the trial, 'eot' end of trial. |
|
| 1311 | 13x |
prob_target_drt <- nextBest@prob_target_drt |
| 1312 | 13x |
prob_target_eot <- nextBest@prob_target_eot |
| 1313 | ||
| 1314 |
# Generate target dose samples, i.e. the doses with probability of the |
|
| 1315 |
# occurrence of a DLT that equals to the prob_target_drt or prob_target_eot. |
|
| 1316 | 13x |
dose_target_drt_samples <- dose( |
| 1317 | 13x |
x = prob_target_drt, |
| 1318 | 13x |
model, |
| 1319 | 13x |
samples = samples, |
| 1320 |
... |
|
| 1321 |
) |
|
| 1322 | 13x |
dose_target_eot_samples <- dose( |
| 1323 | 13x |
x = prob_target_eot, |
| 1324 | 13x |
model, |
| 1325 | 13x |
samples = samples, |
| 1326 |
... |
|
| 1327 |
) |
|
| 1328 | ||
| 1329 |
# Derive the prior/posterior estimates based on two above samples. |
|
| 1330 | 13x |
dose_target_drt <- nextBest@derive(dose_target_drt_samples) |
| 1331 | 13x |
dose_target_eot <- nextBest@derive(dose_target_eot_samples) |
| 1332 | ||
| 1333 |
# Gain samples. |
|
| 1334 | 13x |
gain_samples <- sapply( |
| 1335 | 13x |
data@doseGrid, |
| 1336 | 13x |
gain, |
| 1337 | 13x |
model, |
| 1338 | 13x |
samples, |
| 1339 | 13x |
model_eff, |
| 1340 | 13x |
samples_eff, |
| 1341 |
... |
|
| 1342 |
) |
|
| 1343 |
# For every sample, get the dose (from the dose grid) that gives the maximum gain value. |
|
| 1344 | 13x |
dose_lev_mg_samples <- apply(gain_samples, 1, which.max) |
| 1345 | 13x |
dose_mg_samples <- data@doseGrid[dose_lev_mg_samples] |
| 1346 |
# Maximum gain dose estimate is the nth percentile of the maximum gain dose samples. |
|
| 1347 | 13x |
dose_mg <- nextBest@mg_derive(dose_mg_samples) |
| 1348 | 13x |
gain_values <- apply(gain_samples, 2, FUN = nextBest@mg_derive) |
| 1349 | ||
| 1350 |
# Print info message if dose target is outside of the range. |
|
| 1351 | 13x |
dosegrid_range <- dose_grid_range(data) |
| 1352 |
if ( |
|
| 1353 | 13x |
!h_in_range( |
| 1354 | 13x |
dose_target_drt, |
| 1355 | 13x |
range = dosegrid_range, |
| 1356 | 13x |
bounds_closed = FALSE |
| 1357 |
) && |
|
| 1358 | 13x |
!in_sim |
| 1359 |
) {
|
|
| 1360 | ! |
print(paste( |
| 1361 | ! |
"Estimated TD", |
| 1362 | ! |
prob_target_drt * 100, |
| 1363 |
"=", |
|
| 1364 | ! |
dose_target_drt, |
| 1365 | ! |
"not within dose grid" |
| 1366 |
)) |
|
| 1367 |
} |
|
| 1368 |
if ( |
|
| 1369 | 13x |
!h_in_range( |
| 1370 | 13x |
dose_target_eot, |
| 1371 | 13x |
range = dosegrid_range, |
| 1372 | 13x |
bounds_closed = FALSE |
| 1373 |
) && |
|
| 1374 | 13x |
!in_sim |
| 1375 |
) {
|
|
| 1376 | ! |
print(paste( |
| 1377 | ! |
"Estimated TD", |
| 1378 | ! |
prob_target_eot * 100, |
| 1379 |
"=", |
|
| 1380 | ! |
dose_target_eot, |
| 1381 | ! |
"not within dose grid" |
| 1382 |
)) |
|
| 1383 |
} |
|
| 1384 |
if ( |
|
| 1385 | 13x |
!h_in_range(dose_mg, range = dosegrid_range, bounds_closed = FALSE) && |
| 1386 | 13x |
!in_sim |
| 1387 |
) {
|
|
| 1388 | ! |
print(paste("Estimated max gain dose =", dose_mg, "not within dose grid"))
|
| 1389 |
} |
|
| 1390 | ||
| 1391 |
# Get closest grid doses for a given target doses. |
|
| 1392 | 13x |
nb_doses_at_grid <- h_next_best_mg_doses_at_grid( |
| 1393 | 13x |
dose_target_drt = dose_target_drt, |
| 1394 | 13x |
dose_target_eot = dose_target_eot, |
| 1395 | 13x |
dose_mg = dose_mg, |
| 1396 | 13x |
dose_grid = data@doseGrid, |
| 1397 | 13x |
doselimit = doselimit, |
| 1398 | 13x |
placebo = data@placebo |
| 1399 |
) |
|
| 1400 | ||
| 1401 |
# 95% credibility intervals and corresponding ratios for maximum gain dose and target dose eot. |
|
| 1402 | 13x |
ci_dose_mg <- as.numeric(quantile(dose_mg_samples, probs = c(0.025, 0.975))) |
| 1403 | 13x |
cir_dose_mg <- ci_dose_mg[2] / ci_dose_mg[1] |
| 1404 | ||
| 1405 | 13x |
ci_dose_target_eot <- as.numeric(quantile( |
| 1406 | 13x |
dose_target_eot, |
| 1407 | 13x |
probs = c(0.025, 0.975) |
| 1408 |
)) |
|
| 1409 | 13x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
| 1410 | ||
| 1411 |
# Build plot. |
|
| 1412 | 13x |
p <- h_next_best_mgsamples_plot( |
| 1413 | 13x |
prob_target_drt = prob_target_drt, |
| 1414 | 13x |
dose_target_drt = dose_target_drt, |
| 1415 | 13x |
prob_target_eot = prob_target_eot, |
| 1416 | 13x |
dose_target_eot = dose_target_eot, |
| 1417 | 13x |
dose_mg = dose_mg, |
| 1418 | 13x |
dose_mg_samples = dose_mg_samples, |
| 1419 | 13x |
next_dose = nb_doses_at_grid$next_dose, |
| 1420 | 13x |
doselimit = doselimit, |
| 1421 | 13x |
dose_grid_range = dosegrid_range |
| 1422 |
) |
|
| 1423 | ||
| 1424 | 13x |
list( |
| 1425 | 13x |
next_dose = nb_doses_at_grid$next_dose, |
| 1426 | 13x |
prob_target_drt = prob_target_drt, |
| 1427 | 13x |
dose_target_drt = dose_target_drt, |
| 1428 | 13x |
next_dose_drt = nb_doses_at_grid$next_dose_drt, |
| 1429 | 13x |
prob_target_eot = prob_target_eot, |
| 1430 | 13x |
dose_target_eot = dose_target_eot, |
| 1431 | 13x |
next_dose_eot = nb_doses_at_grid$next_dose_eot, |
| 1432 | 13x |
dose_max_gain = dose_mg, |
| 1433 | 13x |
next_dose_max_gain = nb_doses_at_grid$next_dose_mg, |
| 1434 | 13x |
ci_dose_target_eot = ci_dose_target_eot, |
| 1435 | 13x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
| 1436 | 13x |
ci_dose_max_gain = ci_dose_mg, |
| 1437 | 13x |
ci_ratio_dose_max_gain = cir_dose_mg, |
| 1438 | 13x |
plot = p |
| 1439 |
) |
|
| 1440 |
} |
|
| 1441 |
) |
|
| 1442 | ||
| 1443 |
## NextBestProbMTDLTE ---- |
|
| 1444 | ||
| 1445 |
#' @describeIn nextBest find the next best dose based with the highest |
|
| 1446 |
#' probability of having a toxicity rate less or equal to the target toxicity |
|
| 1447 |
#' level. |
|
| 1448 |
#' |
|
| 1449 |
#' @aliases nextBest-NextBestProbMTDLTE |
|
| 1450 |
#' |
|
| 1451 |
#' @export |
|
| 1452 |
#' @example examples/Rules-method-nextBest-NextBestProbMTDLTE.R |
|
| 1453 |
#' |
|
| 1454 |
setMethod( |
|
| 1455 |
f = "nextBest", |
|
| 1456 |
signature = signature( |
|
| 1457 |
nextBest = "NextBestProbMTDLTE", |
|
| 1458 |
doselimit = "numeric", |
|
| 1459 |
samples = "Samples", |
|
| 1460 |
model = "GeneralModel", |
|
| 1461 |
data = "Data" |
|
| 1462 |
), |
|
| 1463 |
definition = function(nextBest, doselimit, samples, model, data, ...) {
|
|
| 1464 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 1465 | 3x |
prob_samples <- sapply( |
| 1466 | 3x |
data@doseGrid, |
| 1467 | 3x |
prob, |
| 1468 | 3x |
model = model, |
| 1469 | 3x |
samples = samples, |
| 1470 |
... |
|
| 1471 |
) |
|
| 1472 | ||
| 1473 |
# Determine the maximum dose level with a toxicity probability below or |
|
| 1474 |
# equal to the target and calculate how often a dose is selected as MTD |
|
| 1475 |
# across iterations. |
|
| 1476 |
# The first element of the vector is the relative frequency that no |
|
| 1477 |
# dose in the grid is below or equal to the target, the |
|
| 1478 |
# second element that the 1st dose of the grid is the MTD, etc.. |
|
| 1479 | 3x |
prob_mtd_lte <- prop.table( |
| 1480 | 3x |
table(factor( |
| 1481 | 3x |
rowSums(prob_samples <= nextBest@target), |
| 1482 | 3x |
levels = 0:data@nGrid |
| 1483 |
)) |
|
| 1484 |
) |
|
| 1485 | ||
| 1486 | 3x |
allocation_crit <- as.vector(prob_mtd_lte) |
| 1487 | 3x |
names(allocation_crit) <- as.character(c(0, data@doseGrid)) |
| 1488 | ||
| 1489 |
# In case that placebo is used, placebo and the portion that is not assigned |
|
| 1490 |
# to any dose of the grid are merged. |
|
| 1491 | 3x |
if (data@placebo) {
|
| 1492 | 1x |
allocation_crit[1] <- sum(allocation_crit[1:2]) |
| 1493 | 1x |
allocation_crit <- allocation_crit[-2] |
| 1494 |
} |
|
| 1495 | ||
| 1496 |
# Handling of the portion that is not assigned to an active dose of |
|
| 1497 |
# the dose grid. The portion is added to the minimum active dose |
|
| 1498 |
# of the dose grid. |
|
| 1499 | 3x |
allocation_crit[2] <- sum(allocation_crit[1:2]) |
| 1500 | 3x |
allocation_crit <- allocation_crit[-1] |
| 1501 | ||
| 1502 |
# Determine the dose with the highest relative frequency. |
|
| 1503 | 3x |
allocation_crit_dose <- as.numeric(names(allocation_crit)) |
| 1504 | 3x |
dose_target <- allocation_crit_dose[which.max(allocation_crit)] |
| 1505 | ||
| 1506 |
# Determine next dose. |
|
| 1507 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
| 1508 | 3x |
data@doseGrid, |
| 1509 | 3x |
doselimit, |
| 1510 | 3x |
data@placebo |
| 1511 |
) |
|
| 1512 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
| 1513 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
| 1514 | ||
| 1515 |
# Create a plot. |
|
| 1516 | 3x |
plt_data <- if (data@placebo && (data@doseGrid[1] == next_dose)) {
|
| 1517 | ! |
data.frame( |
| 1518 | ! |
x = as.factor(data@doseGrid), |
| 1519 | ! |
y = c(0, as.numeric(allocation_crit)) * 100 |
| 1520 |
) |
|
| 1521 |
} else {
|
|
| 1522 | 3x |
data.frame( |
| 1523 | 3x |
x = as.factor(allocation_crit_dose), |
| 1524 | 3x |
y = as.numeric(allocation_crit) * 100 |
| 1525 |
) |
|
| 1526 |
} |
|
| 1527 | ||
| 1528 | 3x |
p <- ggplot( |
| 1529 | 3x |
data = plt_data |
| 1530 |
) + |
|
| 1531 | 3x |
geom_col(aes(x, y), fill = "grey75") + |
| 1532 | 3x |
scale_x_discrete(drop = FALSE, guide = guide_axis(check.overlap = TRUE)) + |
| 1533 | 3x |
geom_vline( |
| 1534 | 3x |
xintercept = as.factor(dose_target), |
| 1535 | 3x |
lwd = 1.1, |
| 1536 | 3x |
colour = "black" |
| 1537 |
) + |
|
| 1538 | 3x |
geom_text( |
| 1539 | 3x |
data = data.frame(x = as.factor(dose_target)), |
| 1540 | 3x |
aes(.data$x, 0), |
| 1541 | 3x |
label = "Est", |
| 1542 | 3x |
vjust = -0.5, |
| 1543 | 3x |
hjust = -0.5, |
| 1544 | 3x |
colour = "black", |
| 1545 | 3x |
angle = 90 |
| 1546 |
) + |
|
| 1547 | 3x |
xlab("Dose") +
|
| 1548 | 3x |
ylab(paste("Allocation criterion [%]"))
|
| 1549 | ||
| 1550 | 3x |
if (is.finite(doselimit)) {
|
| 1551 | 2x |
doselimit_level <- if (sum(allocation_crit_dose == doselimit) > 0) {
|
| 1552 | ! |
which(allocation_crit_dose == doselimit) |
| 1553 |
} else {
|
|
| 1554 | 2x |
ifelse( |
| 1555 | 2x |
test = data@placebo && (data@doseGrid[1] == next_dose), |
| 1556 | 2x |
yes = 1.5, |
| 1557 | 2x |
no = sum(allocation_crit_dose < doselimit) + 0.5 |
| 1558 |
) |
|
| 1559 |
} |
|
| 1560 | ||
| 1561 | 2x |
p <- p + |
| 1562 | 2x |
geom_vline( |
| 1563 | 2x |
xintercept = doselimit_level, |
| 1564 | 2x |
colour = "red", |
| 1565 | 2x |
lwd = 1.1 |
| 1566 |
) + |
|
| 1567 | 2x |
geom_text( |
| 1568 | 2x |
data = data.frame(x = doselimit_level), |
| 1569 | 2x |
aes(.data$x, 0), |
| 1570 | 2x |
label = "Max", |
| 1571 | 2x |
vjust = -0.5, |
| 1572 | 2x |
hjust = -1.5, |
| 1573 | 2x |
colour = "red", |
| 1574 | 2x |
angle = 90 |
| 1575 |
) |
|
| 1576 |
} |
|
| 1577 | ||
| 1578 | 3x |
p <- p + |
| 1579 | 3x |
geom_vline( |
| 1580 | 3x |
xintercept = as.factor(next_dose), |
| 1581 | 3x |
colour = "blue", |
| 1582 | 3x |
lwd = 1.1 |
| 1583 |
) + |
|
| 1584 | 3x |
geom_text( |
| 1585 | 3x |
data = data.frame(x = as.factor(next_dose)), |
| 1586 | 3x |
aes(.data$x, 0), |
| 1587 | 3x |
label = "Next", |
| 1588 | 3x |
vjust = -0.5, |
| 1589 | 3x |
hjust = -2.5, |
| 1590 | 3x |
colour = "blue", |
| 1591 | 3x |
angle = 90 |
| 1592 |
) |
|
| 1593 | ||
| 1594 | 3x |
list( |
| 1595 | 3x |
value = next_dose, |
| 1596 | 3x |
allocation = cbind( |
| 1597 | 3x |
dose = allocation_crit_dose, |
| 1598 | 3x |
allocation = allocation_crit |
| 1599 |
), |
|
| 1600 | 3x |
plot = p |
| 1601 |
) |
|
| 1602 |
} |
|
| 1603 |
) |
|
| 1604 | ||
| 1605 |
## NextBestProbMTDMinDist ---- |
|
| 1606 | ||
| 1607 |
#' @describeIn nextBest find the next best dose based with the highest |
|
| 1608 |
#' probability of having a toxicity rate with minimum distance to the |
|
| 1609 |
#' target toxicity level. |
|
| 1610 |
#' |
|
| 1611 |
#' @aliases nextBest-NextBestProbMTDMinDist |
|
| 1612 |
#' |
|
| 1613 |
#' @export |
|
| 1614 |
#' @example examples/Rules-method-nextBest-NextBestProbMtdMinDist.R |
|
| 1615 |
#' |
|
| 1616 |
setMethod( |
|
| 1617 |
f = "nextBest", |
|
| 1618 |
signature = signature( |
|
| 1619 |
nextBest = "NextBestProbMTDMinDist", |
|
| 1620 |
doselimit = "numeric", |
|
| 1621 |
samples = "Samples", |
|
| 1622 |
model = "GeneralModel", |
|
| 1623 |
data = "Data" |
|
| 1624 |
), |
|
| 1625 |
definition = function(nextBest, doselimit, samples, model, data, ...) {
|
|
| 1626 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 1627 | 3x |
prob_samples <- sapply( |
| 1628 | 3x |
data@doseGrid, |
| 1629 | 3x |
prob, |
| 1630 | 3x |
model = model, |
| 1631 | 3x |
samples = samples, |
| 1632 |
... |
|
| 1633 |
) |
|
| 1634 | ||
| 1635 |
# Determine which dose level has the minimum distance to target. |
|
| 1636 | 3x |
dose_min_mtd_dist <- apply( |
| 1637 | 3x |
prob_samples, |
| 1638 | 3x |
1, |
| 1639 | 3x |
function(x) which.min(abs(x - nextBest@target)) |
| 1640 |
) |
|
| 1641 | ||
| 1642 | 3x |
allocation_crit <- prop.table( |
| 1643 | 3x |
table(factor(dose_min_mtd_dist, levels = 1:data@nGrid)) |
| 1644 |
) |
|
| 1645 | 3x |
names(allocation_crit) <- as.character(data@doseGrid) |
| 1646 | ||
| 1647 |
# In case that placebo is used, placebo and the first non-placebo dose |
|
| 1648 |
# of the grid are merged. |
|
| 1649 | 3x |
if (data@placebo) {
|
| 1650 | 1x |
allocation_crit[2] <- sum(allocation_crit[1:2]) |
| 1651 | 1x |
allocation_crit <- allocation_crit[-1] |
| 1652 |
} |
|
| 1653 | ||
| 1654 |
# Determine the dose with the highest relative frequency. |
|
| 1655 | 3x |
allocation_crit_dose <- as.numeric(names(allocation_crit)) |
| 1656 | 3x |
dose_target <- allocation_crit_dose[which.max(allocation_crit)] |
| 1657 | ||
| 1658 |
# Determine next dose. |
|
| 1659 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
| 1660 | 3x |
data@doseGrid, |
| 1661 | 3x |
doselimit, |
| 1662 | 3x |
data@placebo |
| 1663 |
) |
|
| 1664 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
| 1665 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
| 1666 | ||
| 1667 |
# Create a plot. |
|
| 1668 | 3x |
plt_data <- if (data@placebo && data@doseGrid[1] == next_dose) {
|
| 1669 | ! |
data.frame( |
| 1670 | ! |
x = as.factor(data@doseGrid), |
| 1671 | ! |
y = c(0, as.numeric(allocation_crit)) * 100 |
| 1672 |
) |
|
| 1673 |
} else {
|
|
| 1674 | 3x |
data.frame( |
| 1675 | 3x |
x = as.factor(allocation_crit_dose), |
| 1676 | 3x |
y = as.numeric(allocation_crit) * 100 |
| 1677 |
) |
|
| 1678 |
} |
|
| 1679 | ||
| 1680 | 3x |
p <- ggplot( |
| 1681 | 3x |
data = plt_data |
| 1682 |
) + |
|
| 1683 | 3x |
geom_col(aes(x, y), fill = "grey75") + |
| 1684 | 3x |
scale_x_discrete(guide = guide_axis(check.overlap = TRUE)) + |
| 1685 | 3x |
geom_vline( |
| 1686 | 3x |
xintercept = as.factor(dose_target), |
| 1687 | 3x |
lwd = 1.1, |
| 1688 | 3x |
colour = "black" |
| 1689 |
) + |
|
| 1690 | 3x |
geom_text( |
| 1691 | 3x |
data = data.frame(x = as.factor(dose_target)), |
| 1692 | 3x |
aes(.data$x, 0), |
| 1693 | 3x |
label = "Est", |
| 1694 | 3x |
vjust = -0.5, |
| 1695 | 3x |
hjust = -0.5, |
| 1696 | 3x |
colour = "black", |
| 1697 | 3x |
angle = 90 |
| 1698 |
) + |
|
| 1699 | 3x |
xlab("Dose") +
|
| 1700 | 3x |
ylab(paste("Allocation criterion [%]"))
|
| 1701 | ||
| 1702 | 3x |
if (is.finite(doselimit)) {
|
| 1703 | 2x |
doselimit_level <- if (any(allocation_crit_dose == doselimit)) {
|
| 1704 | ! |
which(allocation_crit_dose == doselimit) |
| 1705 |
} else {
|
|
| 1706 | 2x |
ifelse( |
| 1707 | 2x |
test = data@placebo && data@doseGrid[1] == next_dose, |
| 1708 | 2x |
yes = 1.5, |
| 1709 | 2x |
no = sum(allocation_crit_dose < doselimit) + 0.5 |
| 1710 |
) |
|
| 1711 |
} |
|
| 1712 | ||
| 1713 | 2x |
p <- p + |
| 1714 | 2x |
geom_vline( |
| 1715 | 2x |
xintercept = doselimit_level, |
| 1716 | 2x |
colour = "red", |
| 1717 | 2x |
lwd = 1.1 |
| 1718 |
) + |
|
| 1719 | 2x |
geom_text( |
| 1720 | 2x |
data = data.frame(x = doselimit_level), |
| 1721 | 2x |
aes(.data$x, 0), |
| 1722 | 2x |
label = "Max", |
| 1723 | 2x |
vjust = -0.5, |
| 1724 | 2x |
hjust = -1.5, |
| 1725 | 2x |
colour = "red", |
| 1726 | 2x |
angle = 90 |
| 1727 |
) |
|
| 1728 |
} |
|
| 1729 | ||
| 1730 | 3x |
p <- p + |
| 1731 | 3x |
geom_vline( |
| 1732 | 3x |
xintercept = as.factor(next_dose), |
| 1733 | 3x |
colour = "blue", |
| 1734 | 3x |
lwd = 1.1 |
| 1735 |
) + |
|
| 1736 | 3x |
geom_text( |
| 1737 | 3x |
data = data.frame(x = as.factor(next_dose)), |
| 1738 | 3x |
aes(.data$x, 0), |
| 1739 | 3x |
label = "Next", |
| 1740 | 3x |
vjust = -0.5, |
| 1741 | 3x |
hjust = -2.5, |
| 1742 | 3x |
colour = "blue", |
| 1743 | 3x |
angle = 90 |
| 1744 |
) |
|
| 1745 | ||
| 1746 | 3x |
list( |
| 1747 | 3x |
value = next_dose, |
| 1748 | 3x |
allocation = cbind( |
| 1749 | 3x |
dose = allocation_crit_dose, |
| 1750 | 3x |
allocation = allocation_crit |
| 1751 |
), |
|
| 1752 | 3x |
plot = p |
| 1753 |
) |
|
| 1754 |
} |
|
| 1755 |
) |
|
| 1756 | ||
| 1757 |
## NextBestOrdinal ---- |
|
| 1758 | ||
| 1759 |
#' @describeIn nextBest find the next best dose for ordinal CRM models. |
|
| 1760 |
#' |
|
| 1761 |
#' @aliases nextBest-NextBestOrdinal |
|
| 1762 |
#' |
|
| 1763 |
#' @export |
|
| 1764 |
#' @example examples/Rules-method-nextBest-NextBestOrdinal.R |
|
| 1765 |
#' |
|
| 1766 |
setMethod( |
|
| 1767 |
f = "nextBest", |
|
| 1768 |
signature = signature( |
|
| 1769 |
nextBest = "NextBestOrdinal", |
|
| 1770 |
doselimit = "numeric", |
|
| 1771 |
samples = "Samples", |
|
| 1772 |
model = "GeneralModel", |
|
| 1773 |
data = "Data" |
|
| 1774 |
), |
|
| 1775 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 1776 | 1x |
stop( |
| 1777 | 1x |
paste0( |
| 1778 | 1x |
"NextBestOrdinal objects can only be used with LogisticLogNormalOrdinal ", |
| 1779 | 1x |
"models and DataOrdinal data objects. In this case, the model is a '", |
| 1780 | 1x |
class(model), |
| 1781 | 1x |
"' object and the data is in a ", |
| 1782 | 1x |
class(data), |
| 1783 | 1x |
" object." |
| 1784 |
) |
|
| 1785 |
) |
|
| 1786 |
} |
|
| 1787 |
) |
|
| 1788 | ||
| 1789 |
#' @describeIn nextBest find the next best dose for ordinal CRM models. |
|
| 1790 |
#' |
|
| 1791 |
#' @aliases nextBest-NextBestOrdinal |
|
| 1792 |
#' |
|
| 1793 |
#' @export |
|
| 1794 |
#' @example examples/Rules-method-nextBest-NextBestOrdinal.R |
|
| 1795 |
#' |
|
| 1796 |
setMethod( |
|
| 1797 |
f = "nextBest", |
|
| 1798 |
signature = signature( |
|
| 1799 |
nextBest = "NextBestOrdinal", |
|
| 1800 |
doselimit = "numeric", |
|
| 1801 |
samples = "Samples", |
|
| 1802 |
model = "LogisticLogNormalOrdinal", |
|
| 1803 |
data = "DataOrdinal" |
|
| 1804 |
), |
|
| 1805 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 1806 | 1x |
nextBest( |
| 1807 | 1x |
nextBest = nextBest@rule, |
| 1808 | 1x |
doselimit = doselimit, |
| 1809 | 1x |
samples = h_convert_ordinal_samples(samples, nextBest@grade), |
| 1810 | 1x |
model = h_convert_ordinal_model(model, nextBest@grade), |
| 1811 | 1x |
data = h_convert_ordinal_data(data, nextBest@grade), |
| 1812 |
... |
|
| 1813 |
) |
|
| 1814 |
} |
|
| 1815 |
) |
|
| 1816 | ||
| 1817 |
# maxDose ---- |
|
| 1818 | ||
| 1819 |
## generic ---- |
|
| 1820 | ||
| 1821 |
#' Determine the Maximum Possible Next Dose |
|
| 1822 |
#' |
|
| 1823 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1824 |
#' |
|
| 1825 |
#' This function determines the upper limit of the next dose based on the |
|
| 1826 |
#' `increments`and the `data`. |
|
| 1827 |
#' |
|
| 1828 |
#' @param increments (`Increments`)\cr the rule for the next best dose. |
|
| 1829 |
#' @param data (`Data`)\cr input data. |
|
| 1830 |
#' @param ... additional arguments without method dispatch. |
|
| 1831 |
#' |
|
| 1832 |
#' @return A `number`, the maximum possible next dose. |
|
| 1833 |
#' |
|
| 1834 |
#' @export |
|
| 1835 |
#' |
|
| 1836 |
setGeneric( |
|
| 1837 |
name = "maxDose", |
|
| 1838 |
def = function(increments, data, ...) {
|
|
| 1839 | 583x |
standardGeneric("maxDose")
|
| 1840 |
}, |
|
| 1841 |
valueClass = "numeric" |
|
| 1842 |
) |
|
| 1843 | ||
| 1844 |
## IncrementsRelative ---- |
|
| 1845 | ||
| 1846 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1847 |
#' relative increments. |
|
| 1848 |
#' |
|
| 1849 |
#' @aliases maxDose-IncrementsRelative |
|
| 1850 |
#' |
|
| 1851 |
#' @export |
|
| 1852 |
#' @example examples/Rules-method-maxDose-IncrementsRelative.R |
|
| 1853 |
#' |
|
| 1854 |
setMethod( |
|
| 1855 |
f = "maxDose", |
|
| 1856 |
signature = signature( |
|
| 1857 |
increments = "IncrementsRelative", |
|
| 1858 |
data = "Data" |
|
| 1859 |
), |
|
| 1860 |
definition = function(increments, data, ...) {
|
|
| 1861 | 406x |
if (data@nObs == 0L) {
|
| 1862 |
# In this case we return Inf, because there is no restriction |
|
| 1863 |
# from this stopping rule because we cannot reference any |
|
| 1864 |
# previous dose. In practice this does not matter because |
|
| 1865 |
# there is a starting dose fixed externally anyway. |
|
| 1866 | 1x |
return(Inf) |
| 1867 |
} |
|
| 1868 | 405x |
last_dose <- data@x[data@nObs] |
| 1869 |
# Determine in which interval the `last_dose` is. |
|
| 1870 | 405x |
assert_true(last_dose >= head(increments@intervals, 1)) |
| 1871 | 403x |
last_dose_interval <- findInterval( |
| 1872 | 403x |
x = last_dose, |
| 1873 | 403x |
vec = increments@intervals |
| 1874 |
) |
|
| 1875 | 403x |
(1 + increments@increments[last_dose_interval]) * last_dose |
| 1876 |
} |
|
| 1877 |
) |
|
| 1878 | ||
| 1879 |
## IncrementsRelativeDLT ---- |
|
| 1880 | ||
| 1881 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1882 |
#' relative increments determined by DLTs so far. |
|
| 1883 |
#' |
|
| 1884 |
#' @aliases maxDose-IncrementsRelativeDLT |
|
| 1885 |
#' |
|
| 1886 |
#' @export |
|
| 1887 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeDLT.R |
|
| 1888 |
#' |
|
| 1889 |
setMethod( |
|
| 1890 |
f = "maxDose", |
|
| 1891 |
signature = signature( |
|
| 1892 |
increments = "IncrementsRelativeDLT", |
|
| 1893 |
data = "Data" |
|
| 1894 |
), |
|
| 1895 |
definition = function(increments, data, ...) {
|
|
| 1896 | 19x |
dlt_count <- sum(data@y) |
| 1897 |
# Determine in which interval the `dlt_count` is. |
|
| 1898 | 19x |
assert_true(dlt_count >= increments@intervals[1]) |
| 1899 | 16x |
dlt_count_interval <- findInterval( |
| 1900 | 16x |
x = dlt_count, |
| 1901 | 16x |
vec = increments@intervals |
| 1902 |
) |
|
| 1903 | 16x |
(1 + increments@increments[dlt_count_interval]) * data@x[data@nObs] |
| 1904 |
} |
|
| 1905 |
) |
|
| 1906 | ||
| 1907 |
## IncrementsRelativeDLTCurrent ---- |
|
| 1908 | ||
| 1909 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1910 |
#' relative increments determined by DLTs in the current cohort. |
|
| 1911 |
#' |
|
| 1912 |
#' @aliases maxDose-IncrementsRelativeDLTCurrent |
|
| 1913 |
#' |
|
| 1914 |
#' @export |
|
| 1915 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeDLTCurrent.R |
|
| 1916 |
#' |
|
| 1917 |
setMethod( |
|
| 1918 |
f = "maxDose", |
|
| 1919 |
signature = signature( |
|
| 1920 |
increments = "IncrementsRelativeDLTCurrent", |
|
| 1921 |
data = "Data" |
|
| 1922 |
), |
|
| 1923 |
definition = function(increments, data, ...) {
|
|
| 1924 | 12x |
last_dose <- data@x[data@nObs] |
| 1925 | ||
| 1926 |
# Determine how many DLTs have occurred in the last cohort. |
|
| 1927 | 12x |
last_cohort <- data@cohort[data@nObs] |
| 1928 | 12x |
last_cohort_indices <- which(data@cohort == last_cohort) |
| 1929 | 12x |
dlt_count_lcohort <- sum(data@y[last_cohort_indices]) |
| 1930 | ||
| 1931 |
# Determine in which interval the `dlt_count_lcohort` is. |
|
| 1932 | 12x |
assert_true(dlt_count_lcohort >= increments@intervals[1]) |
| 1933 | 9x |
dlt_count_lcohort_int <- findInterval( |
| 1934 | 9x |
x = dlt_count_lcohort, |
| 1935 | 9x |
vec = increments@intervals |
| 1936 |
) |
|
| 1937 | 9x |
(1 + increments@increments[dlt_count_lcohort_int]) * last_dose |
| 1938 |
} |
|
| 1939 |
) |
|
| 1940 | ||
| 1941 |
## IncrementsRelativeParts ---- |
|
| 1942 | ||
| 1943 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1944 |
#' relative increments as well as part 1 and beginning of part 2. |
|
| 1945 |
#' |
|
| 1946 |
#' @aliases maxDose-IncrementsRelativeParts |
|
| 1947 |
#' |
|
| 1948 |
#' @export |
|
| 1949 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeParts.R |
|
| 1950 |
#' |
|
| 1951 |
setMethod( |
|
| 1952 |
f = "maxDose", |
|
| 1953 |
signature = signature( |
|
| 1954 |
increments = "IncrementsRelativeParts", |
|
| 1955 |
data = "DataParts" |
|
| 1956 |
), |
|
| 1957 |
definition = function(increments, data, ...) {
|
|
| 1958 | 10x |
all_in_part1 <- all(data@part == 1L) |
| 1959 | 10x |
incrmnt <- if (all_in_part1) {
|
| 1960 | 9x |
part2_started <- data@nextPart == 2L |
| 1961 | 9x |
if (part2_started) {
|
| 1962 | 7x |
any_dlt <- any(data@y == 1L) |
| 1963 | 7x |
if (any_dlt) {
|
| 1964 | 4x |
increments@dlt_start |
| 1965 | 3x |
} else if (increments@clean_start <= 0L) {
|
| 1966 | 2x |
increments@clean_start |
| 1967 |
} |
|
| 1968 |
} else {
|
|
| 1969 | 2x |
1L |
| 1970 |
} |
|
| 1971 |
} |
|
| 1972 | ||
| 1973 | 10x |
if (is.null(incrmnt)) {
|
| 1974 | 2x |
callNextMethod(increments, data, ...) |
| 1975 |
} else {
|
|
| 1976 | 8x |
max_dose_lev_part1 <- match_within_tolerance( |
| 1977 | 8x |
max(data@x), |
| 1978 | 8x |
data@part1Ladder |
| 1979 |
) |
|
| 1980 | 8x |
new_max_dose_level <- max_dose_lev_part1 + incrmnt |
| 1981 | 8x |
assert_true(new_max_dose_level >= 0L) |
| 1982 | 6x |
assert_true(new_max_dose_level <= length(data@part1Ladder)) |
| 1983 | 3x |
data@part1Ladder[new_max_dose_level] |
| 1984 |
} |
|
| 1985 |
} |
|
| 1986 |
) |
|
| 1987 | ||
| 1988 |
## IncrementsDoseLevels ---- |
|
| 1989 | ||
| 1990 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1991 |
#' the number of dose grid levels. That is, the max dose is determined as |
|
| 1992 |
#' the one which level is equal to: base dose level + level increment. |
|
| 1993 |
#' The base dose level is the level of the last dose in grid or the level |
|
| 1994 |
#' of the maximum dose applied, which is defined in `increments` object. |
|
| 1995 |
#' Find out more in [`IncrementsDoseLevels`]. |
|
| 1996 |
#' |
|
| 1997 |
#' @aliases maxDose-IncrementsDoseLevels |
|
| 1998 |
#' |
|
| 1999 |
#' @export |
|
| 2000 |
#' @example examples/Rules-method-maxDose-IncrementsDoseLevels.R |
|
| 2001 |
#' |
|
| 2002 |
setMethod( |
|
| 2003 |
f = "maxDose", |
|
| 2004 |
signature = signature( |
|
| 2005 |
increments = "IncrementsDoseLevels", |
|
| 2006 |
data = "Data" |
|
| 2007 |
), |
|
| 2008 |
definition = function(increments, data, ...) {
|
|
| 2009 |
# Determine what is the basis level for increment, |
|
| 2010 |
# i.e. the last dose or the max dose applied. |
|
| 2011 | 106x |
basis_dose_level <- ifelse( |
| 2012 | 106x |
increments@basis_level == "last", |
| 2013 | 106x |
data@xLevel[data@nObs], |
| 2014 | 106x |
max(data@xLevel) |
| 2015 |
) |
|
| 2016 | 106x |
max_dose_level <- min(basis_dose_level + increments@levels, data@nGrid) |
| 2017 | 106x |
data@doseGrid[max_dose_level] |
| 2018 |
} |
|
| 2019 |
) |
|
| 2020 | ||
| 2021 |
## IncrementsHSRBeta ---- |
|
| 2022 | ||
| 2023 |
#' @describeIn maxDose determine the maximum possible next dose for escalation. |
|
| 2024 |
#' |
|
| 2025 |
#' @aliases maxDose-IncrementsHSRBeta |
|
| 2026 |
#' |
|
| 2027 |
#' @export |
|
| 2028 |
#' @example examples/Rules-method-maxDose-IncrementsHSRBeta.R |
|
| 2029 |
#' |
|
| 2030 |
setMethod( |
|
| 2031 |
f = "maxDose", |
|
| 2032 |
signature = signature( |
|
| 2033 |
increments = "IncrementsHSRBeta", |
|
| 2034 |
data = "Data" |
|
| 2035 |
), |
|
| 2036 |
definition = function(increments, data, ...) {
|
|
| 2037 |
# Summary of observed data per dose level. |
|
| 2038 | 7x |
y <- factor(data@y, levels = c("0", "1"))
|
| 2039 | 7x |
dlt_tab <- table(y, data@x) |
| 2040 | ||
| 2041 |
# Ignore placebo if applied. |
|
| 2042 | 7x |
if (data@placebo == TRUE & min(data@x) == data@doseGrid[1]) {
|
| 2043 | 4x |
dlt_tab <- dlt_tab[, -1] |
| 2044 |
} |
|
| 2045 | ||
| 2046 |
# Extract dose names as these get lost if only one dose available. |
|
| 2047 | 7x |
non_plcb_doses <- unique(sort(as.numeric(colnames(dlt_tab)))) |
| 2048 | ||
| 2049 |
# Toxicity probability per dose level. |
|
| 2050 | 7x |
x <- dlt_tab[2, ] |
| 2051 | 7x |
n <- apply(dlt_tab, 2, sum) |
| 2052 | 7x |
tox_prob <- pbeta( |
| 2053 | 7x |
increments@target, |
| 2054 | 7x |
x + increments@a, |
| 2055 | 7x |
n - x + increments@b, |
| 2056 | 7x |
lower.tail = FALSE |
| 2057 |
) |
|
| 2058 | ||
| 2059 |
# Return the min toxic dose level or maximum dose level if no dose is toxic, |
|
| 2060 |
# while ignoring placebo. |
|
| 2061 | 7x |
dose_tox <- if (sum(tox_prob > increments@prob) > 0) {
|
| 2062 | 5x |
min(non_plcb_doses[which(tox_prob > increments@prob)]) |
| 2063 |
} else {
|
|
| 2064 |
# Add small value to max dose, so that the max dose is always smaller. |
|
| 2065 | 2x |
max(data@doseGrid) + 0.01 |
| 2066 |
} |
|
| 2067 | ||
| 2068 |
# Determine the next maximum possible dose. |
|
| 2069 |
# In case that the first active dose is above probability threshold, |
|
| 2070 |
# the first active dose is reported as maximum. I.e. in case that placebo is used, |
|
| 2071 |
# the second dose is reported. Please note that this rule should be used together |
|
| 2072 |
# with the hard safety stopping rule to avoid inconsistent results. |
|
| 2073 | 7x |
max( |
| 2074 | 7x |
data@doseGrid[data@doseGrid < dose_tox], |
| 2075 | 7x |
data@doseGrid[data@placebo + 1] |
| 2076 |
) |
|
| 2077 |
} |
|
| 2078 |
) |
|
| 2079 | ||
| 2080 |
## IncrementsMin ---- |
|
| 2081 | ||
| 2082 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 2083 |
#' multiple increment rules, taking the minimum across individual increments. |
|
| 2084 |
#' |
|
| 2085 |
#' @aliases maxDose-IncrementsMin |
|
| 2086 |
#' |
|
| 2087 |
#' @export |
|
| 2088 |
#' @example examples/Rules-method-maxDose-IncrementsMin.R |
|
| 2089 |
#' |
|
| 2090 |
setMethod( |
|
| 2091 |
f = "maxDose", |
|
| 2092 |
signature = signature( |
|
| 2093 |
increments = "IncrementsMin", |
|
| 2094 |
data = "Data" |
|
| 2095 |
), |
|
| 2096 |
definition = function(increments, data, ...) {
|
|
| 2097 | 10x |
individual_results <- sapply( |
| 2098 | 10x |
increments@increments_list, |
| 2099 | 10x |
maxDose, |
| 2100 | 10x |
data = data, |
| 2101 |
... |
|
| 2102 |
) |
|
| 2103 | 10x |
min(individual_results) |
| 2104 |
} |
|
| 2105 |
) |
|
| 2106 | ||
| 2107 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 2108 |
#' multiple increment rules, taking the minimum across individual increments. |
|
| 2109 |
#' |
|
| 2110 |
#' @aliases maxDose-IncrementsMin |
|
| 2111 |
#' |
|
| 2112 |
#' @export |
|
| 2113 |
setMethod( |
|
| 2114 |
f = "maxDose", |
|
| 2115 |
signature = signature( |
|
| 2116 |
increments = "IncrementsMin", |
|
| 2117 |
data = "DataOrdinal" |
|
| 2118 |
), |
|
| 2119 |
definition = function(increments, data, ...) {
|
|
| 2120 | 3x |
individual_results <- sapply( |
| 2121 | 3x |
increments@increments_list, |
| 2122 | 3x |
maxDose, |
| 2123 | 3x |
data = data, |
| 2124 |
... |
|
| 2125 |
) |
|
| 2126 | 3x |
min(individual_results) |
| 2127 |
} |
|
| 2128 |
) |
|
| 2129 | ||
| 2130 |
## IncrementsOrdinal ---- |
|
| 2131 | ||
| 2132 |
#' @describeIn maxDose determine the maximum possible next dose in an ordinal |
|
| 2133 |
#' CRM trial |
|
| 2134 |
#' |
|
| 2135 |
#' @aliases maxDose-IncrementsOrdinal |
|
| 2136 |
#' |
|
| 2137 |
#' @export |
|
| 2138 |
#' @example examples/Rules-method-maxDose-IncrementsOrdinal.R |
|
| 2139 |
#' |
|
| 2140 |
setMethod( |
|
| 2141 |
f = "maxDose", |
|
| 2142 |
signature = signature( |
|
| 2143 |
increments = "IncrementsOrdinal", |
|
| 2144 |
data = "DataOrdinal" |
|
| 2145 |
), |
|
| 2146 |
definition = function(increments, data, ...) {
|
|
| 2147 | 8x |
maxDose( |
| 2148 | 8x |
increments = increments@rule, |
| 2149 | 8x |
data = h_convert_ordinal_data( |
| 2150 | 8x |
data, |
| 2151 | 8x |
increments@grade, |
| 2152 |
... |
|
| 2153 |
) |
|
| 2154 |
) |
|
| 2155 |
} |
|
| 2156 |
) |
|
| 2157 | ||
| 2158 |
## IncrementsMaxToxProb ---- |
|
| 2159 | ||
| 2160 |
#' @describeIn maxDose determine the maximum possible next dose based on the |
|
| 2161 |
#' probability of toxicity |
|
| 2162 |
#' @param model (`GeneralModel`)\cr The model on which probabilities will be based |
|
| 2163 |
#' @param samples (`Samples`)\cr The MCMC samples to which `model` will be applied |
|
| 2164 |
#' |
|
| 2165 |
#' @aliases maxDose-IncrementsMaxToxProb |
|
| 2166 |
#' |
|
| 2167 |
#' @export |
|
| 2168 |
#' @example examples/Rules-method-maxDose-IncrementsMaxToxProb.R |
|
| 2169 |
#' |
|
| 2170 |
setMethod( |
|
| 2171 |
f = "maxDose", |
|
| 2172 |
signature = signature( |
|
| 2173 |
increments = "IncrementsMaxToxProb", |
|
| 2174 |
data = "DataOrdinal" |
|
| 2175 |
), |
|
| 2176 |
definition = function(increments, data, model, samples, ...) {
|
|
| 2177 | 3x |
assert_class(samples, "Samples") |
| 2178 | 3x |
assert_true(length(increments@prob) == length(data@yCategories) - 1) |
| 2179 | 3x |
nm <- utils::tail(names(data@yCategories), -1) |
| 2180 | 3x |
assert_set_equal(names(increments@prob), nm) |
| 2181 | ||
| 2182 | 3x |
probs <- dplyr::bind_rows( |
| 2183 | 3x |
lapply( |
| 2184 | 3x |
seq_along(increments@prob), |
| 2185 | 3x |
function(g) {
|
| 2186 | 6x |
fitted_probs <- fit(samples, model, data, grade = g, ...) |
| 2187 | 6x |
safe_fitted_probs <- dplyr::filter( |
| 2188 | 6x |
fitted_probs, |
| 2189 | 6x |
middle < increments@prob[nm[g]] |
| 2190 |
) |
|
| 2191 | 6x |
highest_safe_fitted_prob <- utils::tail(safe_fitted_probs, 1) |
| 2192 |
} |
|
| 2193 |
) |
|
| 2194 |
) |
|
| 2195 | 3x |
min(probs$dose) |
| 2196 |
} |
|
| 2197 |
) |
|
| 2198 |
#' @describeIn maxDose determine the maximum possible next dose based on the |
|
| 2199 |
#' probability of toxicity |
|
| 2200 |
#' @param model (`GeneralModel`)\cr The model on which probabilities will be based |
|
| 2201 |
#' @param samples (`Samples`)\cr The MCMC samples to which `model` will be applied |
|
| 2202 |
#' |
|
| 2203 |
#' @aliases maxDose-IncrementsMaxToxProb |
|
| 2204 |
#' |
|
| 2205 |
#' @export |
|
| 2206 |
#' @example examples/Rules-method-maxDose-IncrementsMaxToxProb.R |
|
| 2207 |
#' |
|
| 2208 |
setMethod( |
|
| 2209 |
f = "maxDose", |
|
| 2210 |
signature = signature( |
|
| 2211 |
increments = "IncrementsMaxToxProb", |
|
| 2212 |
data = "Data" |
|
| 2213 |
), |
|
| 2214 |
definition = function(increments, data, model, samples, ...) {
|
|
| 2215 | 1x |
assert_class(samples, "Samples") |
| 2216 | 1x |
assert_true(length(increments@prob) == 1) |
| 2217 | ||
| 2218 | 1x |
fitted_prob <- fit(samples, model, data, ...) |
| 2219 | 1x |
safe_fitted_prob <- dplyr::filter(fitted_prob, middle < increments@prob) |
| 2220 | 1x |
highest_safe_fitted_prob <- utils::tail(safe_fitted_prob, 1) |
| 2221 | 1x |
highest_safe_fitted_prob$dose |
| 2222 |
} |
|
| 2223 |
) |
|
| 2224 | ||
| 2225 |
## tidy-IncrementsMaxToxProb ---- |
|
| 2226 | ||
| 2227 |
#' @rdname tidy |
|
| 2228 |
#' @aliases tidy-IncrementsMaxToxProb |
|
| 2229 |
#' @example examples/Rules-method-tidyIncrementsMaxToxProb.R |
|
| 2230 |
#' @export |
|
| 2231 |
setMethod( |
|
| 2232 |
f = "tidy", |
|
| 2233 |
signature = signature(x = "IncrementsMaxToxProb"), |
|
| 2234 |
definition = function(x, ...) {
|
|
| 2235 | 2x |
grades <- names(x@prob) |
| 2236 | 2x |
if (is.null(grades)) {
|
| 2237 | ! |
grades <- "1" |
| 2238 |
} |
|
| 2239 | 2x |
tibble( |
| 2240 | 2x |
Grade = grades, |
| 2241 | 2x |
Prob = x@prob |
| 2242 |
) %>% |
|
| 2243 | 2x |
h_tidy_class(x) |
| 2244 |
} |
|
| 2245 |
) |
|
| 2246 | ||
| 2247 |
# and-Stopping-Stopping ---- |
|
| 2248 | ||
| 2249 |
#' Combine Two Stopping Rules with AND |
|
| 2250 |
#' |
|
| 2251 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2252 |
#' |
|
| 2253 |
#' The method combining two atomic stopping rules. |
|
| 2254 |
#' |
|
| 2255 |
#' @param e1 (`Stopping`)\cr first stopping rule object. |
|
| 2256 |
#' @param e2 (`Stopping`)\cr second stopping rule object. |
|
| 2257 |
#' |
|
| 2258 |
#' @return The [`StoppingAll`] object. |
|
| 2259 |
#' |
|
| 2260 |
#' @aliases and-Stopping-Stopping |
|
| 2261 |
#' @example examples/Rules-method-and-stopping-stopping.R |
|
| 2262 |
#' @export |
|
| 2263 |
#' |
|
| 2264 |
setMethod( |
|
| 2265 |
f = "&", |
|
| 2266 |
signature = signature( |
|
| 2267 |
e1 = "Stopping", |
|
| 2268 |
e2 = "Stopping" |
|
| 2269 |
), |
|
| 2270 |
definition = function(e1, e2) {
|
|
| 2271 | 27x |
StoppingAll(list(e1, e2)) |
| 2272 |
} |
|
| 2273 |
) |
|
| 2274 | ||
| 2275 |
# and-StoppingAll-Stopping ---- |
|
| 2276 | ||
| 2277 |
#' Combine a Stopping List and an Atomic Stopping Rule with AND |
|
| 2278 |
#' |
|
| 2279 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2280 |
#' |
|
| 2281 |
#' The method combining a stopping list and an atomic stopping rule. |
|
| 2282 |
#' |
|
| 2283 |
#' @param e1 (`StoppingAll`)\cr stopping list object. |
|
| 2284 |
#' @param e2 (`Stopping`)\cr stopping rule object. |
|
| 2285 |
#' |
|
| 2286 |
#' @return The modified [`StoppingAll`] object. |
|
| 2287 |
#' |
|
| 2288 |
#' @aliases and-StoppingAll-Stopping |
|
| 2289 |
#' @example examples/Rules-method-and-stoppingAll-stopping.R |
|
| 2290 |
#' @export |
|
| 2291 |
#' |
|
| 2292 |
setMethod( |
|
| 2293 |
f = "&", |
|
| 2294 |
signature = signature( |
|
| 2295 |
e1 = "StoppingAll", |
|
| 2296 |
e2 = "Stopping" |
|
| 2297 |
), |
|
| 2298 |
definition = function(e1, e2) {
|
|
| 2299 | 1x |
e1@stop_list <- c( |
| 2300 | 1x |
e1@stop_list, |
| 2301 | 1x |
e2 |
| 2302 |
) |
|
| 2303 | 1x |
e1 |
| 2304 |
} |
|
| 2305 |
) |
|
| 2306 | ||
| 2307 |
# and-Stopping-StoppingAll ---- |
|
| 2308 | ||
| 2309 |
#' Combine an Atomic Stopping Rule and a Stopping List with AND |
|
| 2310 |
#' |
|
| 2311 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2312 |
#' |
|
| 2313 |
#' The method combining an atomic stopping rule and a stopping list. |
|
| 2314 |
#' |
|
| 2315 |
#' @param e1 (`Stopping`)\cr stopping rule object. |
|
| 2316 |
#' @param e2 (`StoppingAll`)\cr stopping list object. |
|
| 2317 |
#' |
|
| 2318 |
#' @return The modified [`StoppingAll`] object. |
|
| 2319 |
#' |
|
| 2320 |
#' @aliases and-Stopping-StoppingAll |
|
| 2321 |
#' @example examples/Rules-method-and-stopping-stoppingAll.R |
|
| 2322 |
#' @export |
|
| 2323 |
#' |
|
| 2324 |
setMethod( |
|
| 2325 |
f = "&", |
|
| 2326 |
signature = signature( |
|
| 2327 |
e1 = "Stopping", |
|
| 2328 |
e2 = "StoppingAll" |
|
| 2329 |
), |
|
| 2330 |
definition = function(e1, e2) {
|
|
| 2331 | 1x |
e2@stop_list <- c( |
| 2332 | 1x |
e1, |
| 2333 | 1x |
e2@stop_list |
| 2334 |
) |
|
| 2335 | 1x |
e2 |
| 2336 |
} |
|
| 2337 |
) |
|
| 2338 | ||
| 2339 |
# or-Stopping-Stopping ---- |
|
| 2340 | ||
| 2341 |
#' Combine Two Stopping Rules with OR |
|
| 2342 |
#' |
|
| 2343 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2344 |
#' |
|
| 2345 |
#' The method combining two atomic stopping rules. |
|
| 2346 |
#' |
|
| 2347 |
#' @param e1 (`Stopping`)\cr first stopping rule object. |
|
| 2348 |
#' @param e2 (`Stopping`)\cr second stopping rule object. |
|
| 2349 |
#' |
|
| 2350 |
#' @return The [`StoppingAny`] object. |
|
| 2351 |
#' |
|
| 2352 |
#' @aliases |,Stopping,Stopping-method |
|
| 2353 |
#' @name or-Stopping-Stopping |
|
| 2354 |
#' @example examples/Rules-method-or-stopping-stopping.R |
|
| 2355 |
#' @export |
|
| 2356 |
#' |
|
| 2357 |
setMethod( |
|
| 2358 |
f = "|", |
|
| 2359 |
signature = signature( |
|
| 2360 |
e1 = "Stopping", |
|
| 2361 |
e2 = "Stopping" |
|
| 2362 |
), |
|
| 2363 |
definition = function(e1, e2) {
|
|
| 2364 | 60x |
StoppingAny(list(e1, e2)) |
| 2365 |
} |
|
| 2366 |
) |
|
| 2367 | ||
| 2368 |
# or-StoppingAny-Stopping ---- |
|
| 2369 | ||
| 2370 |
#' Combine a Stopping List and an Atomic Stopping Rule with OR |
|
| 2371 |
#' |
|
| 2372 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2373 |
#' |
|
| 2374 |
#' The method combining a stopping list and an atomic stopping rule. |
|
| 2375 |
#' |
|
| 2376 |
#' @param e1 (`StoppingAny`)\cr stopping list object. |
|
| 2377 |
#' @param e2 (`Stopping`)\cr stopping rule object. |
|
| 2378 |
#' |
|
| 2379 |
#' @return The modified [`StoppingAny`] object. |
|
| 2380 |
#' |
|
| 2381 |
#' @aliases |,StoppingAny,Stopping-method |
|
| 2382 |
#' @name or-StoppingAny-Stopping |
|
| 2383 |
#' @example examples/Rules-method-or-stoppingAny-stopping.R |
|
| 2384 |
#' @export |
|
| 2385 |
#' |
|
| 2386 |
setMethod( |
|
| 2387 |
f = "|", |
|
| 2388 |
signature = signature( |
|
| 2389 |
e1 = "StoppingAny", |
|
| 2390 |
e2 = "Stopping" |
|
| 2391 |
), |
|
| 2392 |
definition = function(e1, e2) {
|
|
| 2393 | 15x |
e1@stop_list <- c( |
| 2394 | 15x |
e1@stop_list, |
| 2395 | 15x |
e2 |
| 2396 |
) |
|
| 2397 | 15x |
e1 |
| 2398 |
} |
|
| 2399 |
) |
|
| 2400 | ||
| 2401 |
# or-Stopping-StoppingAny ---- |
|
| 2402 | ||
| 2403 |
#' Combine an Atomic Stopping Rule and a Stopping List with OR |
|
| 2404 |
#' |
|
| 2405 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2406 |
#' |
|
| 2407 |
#' The method combining an atomic stopping rule and a stopping list. |
|
| 2408 |
#' |
|
| 2409 |
#' @param e1 (`Stopping`)\cr stopping rule object. |
|
| 2410 |
#' @param e2 (`StoppingAny`)\cr stopping list object. |
|
| 2411 |
#' |
|
| 2412 |
#' @return The modified [`StoppingAny`] object. |
|
| 2413 |
#' |
|
| 2414 |
#' @aliases |,Stopping,StoppingAny-method |
|
| 2415 |
#' @name or-Stopping-StoppingAny |
|
| 2416 |
#' @example examples/Rules-method-or-stopping-stoppingAny.R |
|
| 2417 |
#' @export |
|
| 2418 |
#' |
|
| 2419 |
setMethod( |
|
| 2420 |
f = "|", |
|
| 2421 |
signature = signature( |
|
| 2422 |
e1 = "Stopping", |
|
| 2423 |
e2 = "StoppingAny" |
|
| 2424 |
), |
|
| 2425 |
definition = function(e1, e2) {
|
|
| 2426 | 1x |
e2@stop_list <- c( |
| 2427 | 1x |
e1, |
| 2428 | 1x |
e2@stop_list |
| 2429 |
) |
|
| 2430 | 1x |
e2 |
| 2431 |
} |
|
| 2432 |
) |
|
| 2433 | ||
| 2434 |
# Stopping ---- |
|
| 2435 | ||
| 2436 |
## stopTrial ---- |
|
| 2437 | ||
| 2438 |
#' Stop the trial? |
|
| 2439 |
#' |
|
| 2440 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2441 |
#' |
|
| 2442 |
#' This function returns whether to stop the trial. |
|
| 2443 |
#' |
|
| 2444 |
#' @param stopping (`Stopping`)\cr the rule for stopping the trial. |
|
| 2445 |
#' @param dose the recommended next best dose. |
|
| 2446 |
#' @param samples (`Samples`)\cr the mcmc samples. |
|
| 2447 |
#' @param model (`GeneralModel`)\cr the model. |
|
| 2448 |
#' @param data (`Data`)\cr input data. |
|
| 2449 |
#' @param ... additional arguments without method dispatch. |
|
| 2450 |
#' |
|
| 2451 |
#' @return logical value: `TRUE` if the trial can be stopped, `FALSE` |
|
| 2452 |
#' otherwise. It should have an attribute `message` which gives the reason |
|
| 2453 |
#' for the decision. |
|
| 2454 |
#' |
|
| 2455 |
#' @export |
|
| 2456 |
#' @example examples/Rules-method-CombiningStoppingRulesAndOr.R |
|
| 2457 |
setGeneric( |
|
| 2458 |
name = "stopTrial", |
|
| 2459 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2460 | 3242x |
standardGeneric("stopTrial")
|
| 2461 |
}, |
|
| 2462 |
valueClass = "logical" |
|
| 2463 |
) |
|
| 2464 | ||
| 2465 |
## stopTrial-StoppingMissingDose ---- |
|
| 2466 | ||
| 2467 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
| 2468 |
#' |
|
| 2469 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 2470 |
#' |
|
| 2471 |
#' @aliases stopTrial-StoppingMissingDose |
|
| 2472 |
#' @example examples/Rules-method-stopTrial-StoppingMissingDose.R |
|
| 2473 |
#' |
|
| 2474 |
setMethod( |
|
| 2475 |
f = "stopTrial", |
|
| 2476 |
signature = signature( |
|
| 2477 |
stopping = "StoppingMissingDose", |
|
| 2478 |
dose = "numeric", |
|
| 2479 |
samples = "ANY", |
|
| 2480 |
model = "ANY", |
|
| 2481 |
data = "Data" |
|
| 2482 |
), |
|
| 2483 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2484 | 169x |
do_stop <- is.na(dose) || (data@placebo && dose == min(data@doseGrid)) |
| 2485 | ||
| 2486 | 169x |
msg <- paste( |
| 2487 | 169x |
"Next dose is", |
| 2488 | 169x |
ifelse( |
| 2489 | 169x |
do_stop, |
| 2490 | 169x |
paste( |
| 2491 | 169x |
ifelse( |
| 2492 | 169x |
data@placebo && dose == min(data@doseGrid), |
| 2493 | 169x |
"placebo dose", |
| 2494 | 169x |
"NA" |
| 2495 |
), |
|
| 2496 | 169x |
", i.e., no active dose is safe enough according to the NextBest rule." |
| 2497 |
), |
|
| 2498 | 169x |
"available at the dose grid." |
| 2499 |
) |
|
| 2500 |
) |
|
| 2501 | ||
| 2502 | 169x |
structure(do_stop, message = msg, report_label = stopping@report_label) |
| 2503 |
} |
|
| 2504 |
) |
|
| 2505 | ||
| 2506 |
## stopTrial-StoppingList ---- |
|
| 2507 | ||
| 2508 |
#' @describeIn stopTrial Stop based on multiple stopping rules. |
|
| 2509 |
#' |
|
| 2510 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2511 |
#' |
|
| 2512 |
#' @aliases stopTrial-StoppingList |
|
| 2513 |
#' @example examples/Rules-method-stopTrial-StoppingList.R |
|
| 2514 |
#' @export |
|
| 2515 |
#' |
|
| 2516 |
setMethod( |
|
| 2517 |
f = "stopTrial", |
|
| 2518 |
signature = signature( |
|
| 2519 |
stopping = "StoppingList", |
|
| 2520 |
dose = "ANY", |
|
| 2521 |
samples = "ANY", |
|
| 2522 |
model = "ANY", |
|
| 2523 |
data = "ANY" |
|
| 2524 |
), |
|
| 2525 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2526 |
# Evaluate the individual stopping rules in the list. |
|
| 2527 | 14x |
individual_results <- |
| 2528 | 14x |
if (missing(samples)) {
|
| 2529 | 7x |
lapply( |
| 2530 | 7x |
stopping@stop_list, |
| 2531 | 7x |
stopTrial, |
| 2532 | 7x |
dose = dose, |
| 2533 | 7x |
model = model, |
| 2534 | 7x |
data = data, |
| 2535 |
... |
|
| 2536 |
) |
|
| 2537 |
} else {
|
|
| 2538 | 7x |
lapply( |
| 2539 | 7x |
stopping@stop_list, |
| 2540 | 7x |
stopTrial, |
| 2541 | 7x |
dose = dose, |
| 2542 | 7x |
samples = samples, |
| 2543 | 7x |
model = model, |
| 2544 | 7x |
data = data, |
| 2545 |
... |
|
| 2546 |
) |
|
| 2547 |
} |
|
| 2548 | ||
| 2549 |
# Summarize to obtain overall result. |
|
| 2550 | 14x |
overall_result <- stopping@summary(as.logical(individual_results)) |
| 2551 | ||
| 2552 |
# Retrieve individual text messages, but let them in the list structure. |
|
| 2553 | 14x |
overall_text <- lapply(individual_results, attr, "message") |
| 2554 | ||
| 2555 | 14x |
structure( |
| 2556 | 14x |
overall_result, |
| 2557 | 14x |
message = overall_text, |
| 2558 | 14x |
individual = individual_results |
| 2559 |
) |
|
| 2560 |
} |
|
| 2561 |
) |
|
| 2562 | ||
| 2563 |
## stopTrial-StoppingAll ---- |
|
| 2564 | ||
| 2565 |
#' @describeIn stopTrial Stop based on fulfillment of all multiple stopping |
|
| 2566 |
#' rules. |
|
| 2567 |
#' |
|
| 2568 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2569 |
#' |
|
| 2570 |
#' @aliases stopTrial-StoppingAll |
|
| 2571 |
#' @example examples/Rules-method-stopTrial-StoppingAll.R |
|
| 2572 |
#' @export |
|
| 2573 |
#' |
|
| 2574 |
setMethod( |
|
| 2575 |
f = "stopTrial", |
|
| 2576 |
signature = signature( |
|
| 2577 |
stopping = "StoppingAll", |
|
| 2578 |
dose = "ANY", |
|
| 2579 |
samples = "ANY", |
|
| 2580 |
model = "ANY", |
|
| 2581 |
data = "ANY" |
|
| 2582 |
), |
|
| 2583 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2584 |
# Evaluate the individual stopping rules in the list. |
|
| 2585 | 145x |
individual_results <- |
| 2586 | 145x |
if (missing(samples)) {
|
| 2587 | 6x |
lapply( |
| 2588 | 6x |
stopping@stop_list, |
| 2589 | 6x |
stopTrial, |
| 2590 | 6x |
dose = dose, |
| 2591 | 6x |
model = model, |
| 2592 | 6x |
data = data, |
| 2593 |
... |
|
| 2594 |
) |
|
| 2595 |
} else {
|
|
| 2596 | 139x |
lapply( |
| 2597 | 139x |
stopping@stop_list, |
| 2598 | 139x |
stopTrial, |
| 2599 | 139x |
dose = dose, |
| 2600 | 139x |
samples = samples, |
| 2601 | 139x |
model = model, |
| 2602 | 139x |
data = data, |
| 2603 |
... |
|
| 2604 |
) |
|
| 2605 |
} |
|
| 2606 | ||
| 2607 |
# Summarize to obtain overall result. |
|
| 2608 | 145x |
overall_result <- all(as.logical(individual_results)) |
| 2609 | ||
| 2610 |
# Retrieve individual text messages, but let them in the list structure. |
|
| 2611 | 145x |
overall_text <- lapply(individual_results, attr, "message") |
| 2612 | ||
| 2613 | 145x |
structure( |
| 2614 | 145x |
overall_result, |
| 2615 | 145x |
message = overall_text, |
| 2616 | 145x |
individual = individual_results, |
| 2617 | 145x |
report_label = stopping@report_label |
| 2618 |
) |
|
| 2619 |
} |
|
| 2620 |
) |
|
| 2621 | ||
| 2622 | ||
| 2623 |
## stopTrial-StoppingAny ---- |
|
| 2624 | ||
| 2625 |
#' @describeIn stopTrial Stop based on fulfillment of any stopping rule. |
|
| 2626 |
#' |
|
| 2627 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2628 |
#' |
|
| 2629 |
#' @aliases stopTrial-StoppingAny |
|
| 2630 |
#' @example examples/Rules-method-stopTrial-StoppingAny.R |
|
| 2631 |
#' @export |
|
| 2632 |
#' |
|
| 2633 |
setMethod( |
|
| 2634 |
f = "stopTrial", |
|
| 2635 |
signature = signature( |
|
| 2636 |
stopping = "StoppingAny", |
|
| 2637 |
dose = "ANY", |
|
| 2638 |
samples = "ANY", |
|
| 2639 |
model = "ANY", |
|
| 2640 |
data = "ANY" |
|
| 2641 |
), |
|
| 2642 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2643 |
# Evaluate the individual stopping rules in the list. |
|
| 2644 | 205x |
individual_results <- |
| 2645 | 205x |
if (missing(samples)) {
|
| 2646 | 6x |
lapply( |
| 2647 | 6x |
stopping@stop_list, |
| 2648 | 6x |
stopTrial, |
| 2649 | 6x |
dose = dose, |
| 2650 | 6x |
model = model, |
| 2651 | 6x |
data = data, |
| 2652 |
... |
|
| 2653 |
) |
|
| 2654 |
} else {
|
|
| 2655 | 199x |
lapply( |
| 2656 | 199x |
stopping@stop_list, |
| 2657 | 199x |
stopTrial, |
| 2658 | 199x |
dose = dose, |
| 2659 | 199x |
samples = samples, |
| 2660 | 199x |
model = model, |
| 2661 | 199x |
data = data, |
| 2662 |
... |
|
| 2663 |
) |
|
| 2664 |
} |
|
| 2665 | ||
| 2666 |
# Summarize to obtain overall result. |
|
| 2667 | 205x |
overall_result <- any(as.logical(individual_results)) |
| 2668 | ||
| 2669 |
# Retrieve individual text messages, but let them in the list structure. |
|
| 2670 | 205x |
overall_text <- lapply(individual_results, attr, "message") |
| 2671 | ||
| 2672 | 205x |
structure( |
| 2673 | 205x |
overall_result, |
| 2674 | 205x |
message = overall_text, |
| 2675 | 205x |
individual = individual_results, |
| 2676 | 205x |
report_label = stopping@report_label |
| 2677 |
) |
|
| 2678 |
} |
|
| 2679 |
) |
|
| 2680 | ||
| 2681 |
## stopTrial-StoppingCohortsNearDose ---- |
|
| 2682 | ||
| 2683 |
#' @describeIn stopTrial Stop based on number of cohorts near to next best |
|
| 2684 |
#' dose. |
|
| 2685 |
#' |
|
| 2686 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2687 |
#' |
|
| 2688 |
#' @aliases stopTrial-StoppingCohortsNearDose |
|
| 2689 |
#' @example examples/Rules-method-stopTrial-StoppingCohortsNearDose.R |
|
| 2690 |
#' @export |
|
| 2691 |
#' |
|
| 2692 |
setMethod( |
|
| 2693 |
f = "stopTrial", |
|
| 2694 |
signature = signature( |
|
| 2695 |
stopping = "StoppingCohortsNearDose", |
|
| 2696 |
dose = "numeric", |
|
| 2697 |
samples = "ANY", |
|
| 2698 |
model = "ANY", |
|
| 2699 |
data = "Data" |
|
| 2700 |
), |
|
| 2701 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2702 |
# Determine the range where the cohorts must lie in. |
|
| 2703 | 14x |
lower <- (100 - stopping@percentage) / 100 * dose |
| 2704 | 14x |
upper <- (100 + stopping@percentage) / 100 * dose |
| 2705 | ||
| 2706 |
# Which patients lie there? |
|
| 2707 | 14x |
index_patients <- which((data@x >= lower) & (data@x <= upper)) |
| 2708 | ||
| 2709 |
# How many cohorts? |
|
| 2710 | 14x |
n_cohorts <- length(unique(data@cohort[index_patients])) |
| 2711 | ||
| 2712 |
# So can we stop? |
|
| 2713 | 14x |
do_stop <- n_cohorts >= stopping@nCohorts |
| 2714 | ||
| 2715 |
# Generate message. |
|
| 2716 | 14x |
text <- paste( |
| 2717 | 14x |
n_cohorts, |
| 2718 | 14x |
" cohorts lie within ", |
| 2719 | 14x |
stopping@percentage, |
| 2720 | 14x |
"% of the next best dose ", |
| 2721 | 14x |
dose, |
| 2722 | 14x |
". This ", |
| 2723 | 14x |
ifelse(do_stop, "reached", "is below"), |
| 2724 | 14x |
" the required ", |
| 2725 | 14x |
stopping@nCohorts, |
| 2726 | 14x |
" cohorts", |
| 2727 | 14x |
sep = "" |
| 2728 |
) |
|
| 2729 | ||
| 2730 |
# Return both. |
|
| 2731 | 14x |
structure( |
| 2732 | 14x |
do_stop, |
| 2733 | 14x |
message = text, |
| 2734 | 14x |
report_label = stopping@report_label |
| 2735 |
) |
|
| 2736 |
} |
|
| 2737 |
) |
|
| 2738 | ||
| 2739 |
## stopTrial-StoppingPatientsNearDose ---- |
|
| 2740 | ||
| 2741 |
#' @describeIn stopTrial Stop based on number of patients near to next best |
|
| 2742 |
#' dose. |
|
| 2743 |
#' |
|
| 2744 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2745 |
#' |
|
| 2746 |
#' @aliases stopTrial-StoppingPatientsNearDose |
|
| 2747 |
#' @example examples/Rules-method-stopTrial-StoppingPatientsNearDose.R |
|
| 2748 |
#' @export |
|
| 2749 |
#' |
|
| 2750 |
setMethod( |
|
| 2751 |
f = "stopTrial", |
|
| 2752 |
signature = signature( |
|
| 2753 |
stopping = "StoppingPatientsNearDose", |
|
| 2754 |
dose = "numeric", |
|
| 2755 |
samples = "ANY", |
|
| 2756 |
model = "ANY", |
|
| 2757 |
data = "Data" |
|
| 2758 |
), |
|
| 2759 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2760 |
# Determine the range where the cohorts must lie in. |
|
| 2761 | 3x |
lower <- (100 - stopping@percentage) / 100 * dose |
| 2762 | 3x |
upper <- (100 + stopping@percentage) / 100 * dose |
| 2763 | ||
| 2764 |
# Get patients' dose levels. |
|
| 2765 | 3x |
doses <- data@x |
| 2766 | 3x |
if (!stopping@include_backfill) {
|
| 2767 | 1x |
doses <- doses[!data@backfilled] |
| 2768 |
} |
|
| 2769 | ||
| 2770 |
# How many patients lie there? |
|
| 2771 | 3x |
n_patients <- ifelse( |
| 2772 | 3x |
is.na(dose), |
| 2773 | 3x |
0, |
| 2774 | 3x |
sum((doses >= lower) & (doses <= upper)) |
| 2775 |
) |
|
| 2776 | ||
| 2777 |
# So can we stop? |
|
| 2778 | 3x |
do_stop <- n_patients >= stopping@nPatients |
| 2779 | ||
| 2780 |
# Generate message. |
|
| 2781 | 3x |
text <- paste0( |
| 2782 | 3x |
n_patients, |
| 2783 | 3x |
ifelse( |
| 2784 | 3x |
stopping@include_backfill, |
| 2785 | 3x |
" patients ", |
| 2786 | 3x |
" patients (excluding backfilled) " |
| 2787 |
), |
|
| 2788 | 3x |
"lie within ", |
| 2789 | 3x |
stopping@percentage, |
| 2790 | 3x |
"% of the next best dose ", |
| 2791 | 3x |
dose, |
| 2792 | 3x |
". This ", |
| 2793 | 3x |
ifelse(do_stop, "reached", "is below"), |
| 2794 | 3x |
" the required ", |
| 2795 | 3x |
stopping@nPatients, |
| 2796 | 3x |
" patients" |
| 2797 |
) |
|
| 2798 | ||
| 2799 |
# Return both. |
|
| 2800 | 3x |
structure( |
| 2801 | 3x |
do_stop, |
| 2802 | 3x |
message = text, |
| 2803 | 3x |
report_label = stopping@report_label |
| 2804 |
) |
|
| 2805 |
} |
|
| 2806 |
) |
|
| 2807 | ||
| 2808 |
## stopTrial-StoppingMinCohorts ---- |
|
| 2809 | ||
| 2810 |
#' @describeIn stopTrial Stop based on minimum number of cohorts. |
|
| 2811 |
#' |
|
| 2812 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2813 |
#' |
|
| 2814 |
#' @aliases stopTrial-StoppingMinCohorts |
|
| 2815 |
#' @example examples/Rules-method-stopTrial-StoppingMinCohorts.R |
|
| 2816 |
#' @export |
|
| 2817 |
#' |
|
| 2818 |
setMethod( |
|
| 2819 |
f = "stopTrial", |
|
| 2820 |
signature = signature( |
|
| 2821 |
stopping = "StoppingMinCohorts", |
|
| 2822 |
dose = "ANY", |
|
| 2823 |
samples = "ANY", |
|
| 2824 |
model = "ANY", |
|
| 2825 |
data = "Data" |
|
| 2826 |
), |
|
| 2827 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2828 |
# Determine number of cohorts. |
|
| 2829 | 172x |
n_cohorts <- length(unique(data@cohort)) |
| 2830 | ||
| 2831 |
# So can we stop? |
|
| 2832 | 172x |
do_stop <- n_cohorts >= stopping@nCohorts |
| 2833 | ||
| 2834 |
# Generate message. |
|
| 2835 | 172x |
text <- |
| 2836 | 172x |
paste( |
| 2837 | 172x |
"Number of cohorts is", |
| 2838 | 172x |
n_cohorts, |
| 2839 | 172x |
"and thus", |
| 2840 | 172x |
ifelse(do_stop, "reached", "below"), |
| 2841 | 172x |
"the prespecified minimum number", |
| 2842 | 172x |
stopping@nCohorts |
| 2843 |
) |
|
| 2844 | ||
| 2845 |
# Return both. |
|
| 2846 | 172x |
structure( |
| 2847 | 172x |
do_stop, |
| 2848 | 172x |
message = text, |
| 2849 | 172x |
report_label = stopping@report_label |
| 2850 |
) |
|
| 2851 |
} |
|
| 2852 |
) |
|
| 2853 | ||
| 2854 |
## stopTrial-StoppingMinPatients ---- |
|
| 2855 | ||
| 2856 |
#' @describeIn stopTrial Stop based on minimum number of patients. |
|
| 2857 |
#' |
|
| 2858 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2859 |
#' |
|
| 2860 |
#' @aliases stopTrial-StoppingMinPatients |
|
| 2861 |
#' @example examples/Rules-method-stopTrial-StoppingMinPatients.R |
|
| 2862 |
#' @export |
|
| 2863 |
#' |
|
| 2864 |
setMethod( |
|
| 2865 |
f = "stopTrial", |
|
| 2866 |
signature = signature( |
|
| 2867 |
stopping = "StoppingMinPatients", |
|
| 2868 |
dose = "ANY", |
|
| 2869 |
samples = "ANY", |
|
| 2870 |
model = "ANY", |
|
| 2871 |
data = "Data" |
|
| 2872 |
), |
|
| 2873 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2874 |
# So can we stop? |
|
| 2875 | 467x |
do_stop <- data@nObs >= stopping@nPatients |
| 2876 | ||
| 2877 |
# Generate message. |
|
| 2878 | 467x |
text <- |
| 2879 | 467x |
paste( |
| 2880 | 467x |
"Number of patients is", |
| 2881 | 467x |
data@nObs, |
| 2882 | 467x |
"and thus", |
| 2883 | 467x |
ifelse(do_stop, "reached", "below"), |
| 2884 | 467x |
"the prespecified minimum number", |
| 2885 | 467x |
stopping@nPatients |
| 2886 |
) |
|
| 2887 | ||
| 2888 |
# Return both. |
|
| 2889 | 467x |
structure( |
| 2890 | 467x |
do_stop, |
| 2891 | 467x |
message = text, |
| 2892 | 467x |
report_label = stopping@report_label |
| 2893 |
) |
|
| 2894 |
} |
|
| 2895 |
) |
|
| 2896 | ||
| 2897 |
## stopTrial-StoppingTargetProb ---- |
|
| 2898 | ||
| 2899 |
#' @describeIn stopTrial Stop based on probability of target tox interval |
|
| 2900 |
#' |
|
| 2901 |
#' @aliases stopTrial-StoppingTargetProb |
|
| 2902 |
#' @example examples/Rules-method-stopTrial-StoppingTargetProb.R |
|
| 2903 |
setMethod( |
|
| 2904 |
f = "stopTrial", |
|
| 2905 |
signature = signature( |
|
| 2906 |
stopping = "StoppingTargetProb", |
|
| 2907 |
dose = "numeric", |
|
| 2908 |
samples = "Samples", |
|
| 2909 |
model = "GeneralModel", |
|
| 2910 |
data = "ANY" |
|
| 2911 |
), |
|
| 2912 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2913 |
# Compute probability to be in target interval. |
|
| 2914 | 203x |
prob_target <- ifelse( |
| 2915 | 203x |
is.na(dose), |
| 2916 | 203x |
0, |
| 2917 | 203x |
mean( |
| 2918 | 203x |
prob(dose = dose, model, samples, ...) >= stopping@target[1] & |
| 2919 | 203x |
prob(dose = dose, model, samples, ...) <= stopping@target[2] |
| 2920 |
) |
|
| 2921 |
) |
|
| 2922 | ||
| 2923 | 203x |
do_stop <- prob_target >= stopping@prob |
| 2924 | ||
| 2925 | 203x |
msg <- paste( |
| 2926 | 203x |
"Probability for target toxicity is", |
| 2927 | 203x |
round(prob_target * 100), |
| 2928 | 203x |
"% for dose", |
| 2929 | 203x |
dose, |
| 2930 | 203x |
"and thus", |
| 2931 | 203x |
ifelse(do_stop, "above", "below"), |
| 2932 | 203x |
"the required", |
| 2933 | 203x |
round(stopping@prob * 100), |
| 2934 |
"%" |
|
| 2935 |
) |
|
| 2936 | ||
| 2937 | 203x |
structure( |
| 2938 | 203x |
do_stop, |
| 2939 | 203x |
message = msg, |
| 2940 | 203x |
report_label = stopping@report_label |
| 2941 |
) |
|
| 2942 |
} |
|
| 2943 |
) |
|
| 2944 | ||
| 2945 |
## stopTrial-StoppingMTDdistribution ---- |
|
| 2946 | ||
| 2947 |
#' @describeIn stopTrial Stop based on MTD distribution. |
|
| 2948 |
#' |
|
| 2949 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2950 |
#' |
|
| 2951 |
#' @aliases stopTrial-StoppingMTDdistribution |
|
| 2952 |
#' @example examples/Rules-method-stopTrial-StoppingMTDdistribution.R |
|
| 2953 |
#' @export |
|
| 2954 |
#' |
|
| 2955 |
setMethod( |
|
| 2956 |
f = "stopTrial", |
|
| 2957 |
signature = signature( |
|
| 2958 |
stopping = "StoppingMTDdistribution", |
|
| 2959 |
dose = "numeric", |
|
| 2960 |
samples = "Samples", |
|
| 2961 |
model = "GeneralModel", |
|
| 2962 |
data = "ANY" |
|
| 2963 |
), |
|
| 2964 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2965 |
# First, generate the MTD samples. |
|
| 2966 |
# Add prior data and samples to the function environment so that they can |
|
| 2967 |
# be used. |
|
| 2968 | 751x |
mtd_samples <- dose( |
| 2969 | 751x |
x = stopping@target, |
| 2970 | 751x |
model, |
| 2971 | 751x |
samples, |
| 2972 |
... |
|
| 2973 |
) |
|
| 2974 | ||
| 2975 |
# What is the absolute threshold? |
|
| 2976 | 751x |
abs_thresh <- stopping@thresh * dose |
| 2977 | ||
| 2978 |
# What is the probability to be above this dose? |
|
| 2979 | 751x |
prob <- ifelse( |
| 2980 | 751x |
is.na(abs_thresh), |
| 2981 | 751x |
0, |
| 2982 | 751x |
mean(mtd_samples > abs_thresh) |
| 2983 |
) |
|
| 2984 | ||
| 2985 |
# So can we stop? |
|
| 2986 | 751x |
do_stop <- prob >= stopping@prob |
| 2987 | ||
| 2988 |
# Generate message. |
|
| 2989 | 751x |
text <- |
| 2990 | 751x |
paste( |
| 2991 | 751x |
"Probability of MTD above", |
| 2992 | 751x |
round(stopping@thresh * 100), |
| 2993 | 751x |
"% of current dose", |
| 2994 | 751x |
dose, |
| 2995 | 751x |
"is", |
| 2996 | 751x |
round(prob * 100), |
| 2997 | 751x |
"% and thus", |
| 2998 | 751x |
ifelse(do_stop, "greater than or equal to", "strictly less than"), |
| 2999 | 751x |
"the required", |
| 3000 | 751x |
round(stopping@prob * 100), |
| 3001 |
"%" |
|
| 3002 |
) |
|
| 3003 | ||
| 3004 |
# Return both. |
|
| 3005 | 751x |
structure( |
| 3006 | 751x |
do_stop, |
| 3007 | 751x |
message = text, |
| 3008 | 751x |
report_label = stopping@report_label |
| 3009 |
) |
|
| 3010 |
} |
|
| 3011 |
) |
|
| 3012 | ||
| 3013 |
## stopTrial-StoppingMTDCV ---- |
|
| 3014 | ||
| 3015 |
#' @rdname stopTrial |
|
| 3016 |
#' |
|
| 3017 |
#' @description Stopping rule based precision of the MTD estimation. |
|
| 3018 |
#' The trial is stopped, when the MTD can be estimated with sufficient precision. |
|
| 3019 |
#' The criteria is based on the robust coefficient of variation (CV) calculated |
|
| 3020 |
#' from the posterior distribution. |
|
| 3021 |
#' The robust CV is defined `mad(MTD) / median(MTD)`, where `mad` is the median |
|
| 3022 |
#' absolute deviation. |
|
| 3023 |
#' |
|
| 3024 |
#' @aliases stopTrial-StoppingMTDCV |
|
| 3025 |
#' @example examples/Rules-method-stopTrial-StoppingMTDCV.R |
|
| 3026 |
#' @export |
|
| 3027 |
#' |
|
| 3028 |
setMethod( |
|
| 3029 |
f = "stopTrial", |
|
| 3030 |
signature = signature( |
|
| 3031 |
stopping = "StoppingMTDCV", |
|
| 3032 |
dose = "numeric", |
|
| 3033 |
samples = "Samples", |
|
| 3034 |
model = "GeneralModel", |
|
| 3035 |
data = "ANY" |
|
| 3036 |
), |
|
| 3037 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3038 | 3x |
mtd_samples <- dose( |
| 3039 | 3x |
x = stopping@target, |
| 3040 | 3x |
model, |
| 3041 | 3x |
samples, |
| 3042 |
... |
|
| 3043 |
) |
|
| 3044 |
# CV of MTD expressed as percentage, derived based on MTD posterior samples. |
|
| 3045 | 3x |
mtd_cv <- (mad(mtd_samples) / median(mtd_samples)) * 100 |
| 3046 | 3x |
do_stop <- mtd_cv <= stopping@thresh_cv |
| 3047 | ||
| 3048 | 3x |
msg <- paste( |
| 3049 | 3x |
"CV of MTD is", |
| 3050 | 3x |
round(mtd_cv), |
| 3051 | 3x |
"% and thus", |
| 3052 | 3x |
ifelse(do_stop, "below", "above"), |
| 3053 | 3x |
"the required precision threshold of", |
| 3054 | 3x |
round(stopping@thresh_cv), |
| 3055 |
"%" |
|
| 3056 |
) |
|
| 3057 | ||
| 3058 | 3x |
structure( |
| 3059 | 3x |
do_stop, |
| 3060 | 3x |
message = msg, |
| 3061 | 3x |
report_label = stopping@report_label |
| 3062 |
) |
|
| 3063 |
} |
|
| 3064 |
) |
|
| 3065 | ||
| 3066 |
## stopTrial-StoppingLowestDoseHSRBeta ---- |
|
| 3067 | ||
| 3068 |
#' @rdname stopTrial |
|
| 3069 |
#' |
|
| 3070 |
#' @description Stopping based based on the lowest non placebo dose. The trial is |
|
| 3071 |
#' stopped when the lowest non placebo dose meets the Hard |
|
| 3072 |
#' Safety Rule, i.e. it is deemed to be overly toxic. Stopping is based on the |
|
| 3073 |
#' observed data at the lowest dose level using a Bin-Beta model |
|
| 3074 |
#' based on DLT probability. |
|
| 3075 |
#' |
|
| 3076 |
#' @aliases stopTrial-StoppingLowestDoseHSRBeta |
|
| 3077 |
#' @example examples/Rules-method-stopTrial-StoppingLowestDoseHSRBeta.R |
|
| 3078 |
#' @export |
|
| 3079 |
setMethod( |
|
| 3080 |
f = "stopTrial", |
|
| 3081 |
signature = signature( |
|
| 3082 |
stopping = "StoppingLowestDoseHSRBeta", |
|
| 3083 |
dose = "numeric", |
|
| 3084 |
samples = "Samples" |
|
| 3085 |
), |
|
| 3086 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3087 |
# Actual number of patients at first active dose. |
|
| 3088 | 7x |
n <- sum(data@x == data@doseGrid[data@placebo + 1]) |
| 3089 | ||
| 3090 |
# Determine toxicity probability of the first active dose. |
|
| 3091 | 7x |
tox_prob_first_dose <- |
| 3092 | 7x |
if (n > 0) {
|
| 3093 | 5x |
x <- sum(data@y[which(data@x == data@doseGrid[data@placebo + 1])]) |
| 3094 | 5x |
pbeta( |
| 3095 | 5x |
stopping@target, |
| 3096 | 5x |
x + stopping@a, |
| 3097 | 5x |
n - x + stopping@b, |
| 3098 | 5x |
lower.tail = FALSE |
| 3099 |
) |
|
| 3100 |
} else {
|
|
| 3101 | 2x |
0 |
| 3102 |
} |
|
| 3103 | ||
| 3104 | 7x |
do_stop <- tox_prob_first_dose > stopping@prob |
| 3105 | ||
| 3106 |
# generate message |
|
| 3107 | 7x |
msg <- if (n == 0) {
|
| 3108 | 2x |
"Lowest active dose not tested, stopping rule not applied." |
| 3109 |
} else {
|
|
| 3110 | 5x |
paste( |
| 3111 | 5x |
"Probability that the lowest active dose of ", |
| 3112 | 5x |
data@doseGrid[data@placebo + 1], |
| 3113 | 5x |
" being toxic based on posterior Beta distribution using a Beta(",
|
| 3114 | 5x |
stopping@a, |
| 3115 |
",", |
|
| 3116 | 5x |
stopping@b, |
| 3117 | 5x |
") prior is ", |
| 3118 | 5x |
round(tox_prob_first_dose * 100), |
| 3119 | 5x |
"% and thus ", |
| 3120 | 5x |
ifelse(do_stop, "above", "below"), |
| 3121 | 5x |
" the required ", |
| 3122 | 5x |
round(stopping@prob * 100), |
| 3123 | 5x |
"% threshold.", |
| 3124 | 5x |
sep = "" |
| 3125 |
) |
|
| 3126 |
} |
|
| 3127 | ||
| 3128 | 7x |
structure( |
| 3129 | 7x |
do_stop, |
| 3130 | 7x |
message = msg, |
| 3131 | 7x |
report_label = stopping@report_label |
| 3132 |
) |
|
| 3133 |
} |
|
| 3134 |
) |
|
| 3135 | ||
| 3136 |
## stopTrial-StoppingTargetBiomarker ---- |
|
| 3137 | ||
| 3138 |
#' @describeIn stopTrial Stop based on probability of targeting biomarker |
|
| 3139 |
#' |
|
| 3140 |
#' @aliases stopTrial-StoppingTargetBiomarker |
|
| 3141 |
#' @example examples/Rules-method-stopTrial-StoppingTargetBiomarker.R |
|
| 3142 |
setMethod( |
|
| 3143 |
f = "stopTrial", |
|
| 3144 |
signature = signature( |
|
| 3145 |
stopping = "StoppingTargetBiomarker", |
|
| 3146 |
dose = "numeric", |
|
| 3147 |
samples = "Samples", |
|
| 3148 |
model = "DualEndpoint", |
|
| 3149 |
data = "ANY" |
|
| 3150 |
), |
|
| 3151 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3152 |
# Compute the target biomarker prob at this dose. |
|
| 3153 |
# Get the biomarker level samples at the dose grid points. |
|
| 3154 | 60x |
biom_level_samples <- biomarker( |
| 3155 | 60x |
xLevel = seq_len(data@nGrid), |
| 3156 | 60x |
model, |
| 3157 | 60x |
samples, |
| 3158 |
... |
|
| 3159 |
) |
|
| 3160 | ||
| 3161 |
# If target is relative to maximum. |
|
| 3162 | 60x |
if (stopping@is_relative) {
|
| 3163 |
# If there is an 'Emax' parameter, target biomarker level will |
|
| 3164 |
# be relative to 'Emax', otherwise will be relative to the |
|
| 3165 |
# maximum biomarker level achieved in the given dose range. |
|
| 3166 | 60x |
if ("Emax" %in% names(samples)) {
|
| 3167 |
# For each sample, look which dose is maximizing the |
|
| 3168 |
# simultaneous probability to be in the target biomarker |
|
| 3169 |
# range and below overdose toxicity. |
|
| 3170 | ! |
prob_target <- numeric(ncol(biom_level_samples)) |
| 3171 | ! |
prob_target <- sapply( |
| 3172 | ! |
seq(1, ncol(biom_level_samples)), |
| 3173 | ! |
function(x) {
|
| 3174 | ! |
sum( |
| 3175 | ! |
biom_level_samples[, x] >= |
| 3176 | ! |
stopping@target[1] * samples@data$Emax & |
| 3177 | ! |
biom_level_samples[, x] <= |
| 3178 | ! |
stopping@target[2] * samples@data$Emax |
| 3179 |
) / |
|
| 3180 | ! |
nrow(biom_level_samples) |
| 3181 |
} |
|
| 3182 |
) |
|
| 3183 |
} else {
|
|
| 3184 |
# For each sample, look which was the minimum dose giving |
|
| 3185 |
# relative target level. |
|
| 3186 | 60x |
targetIndex <- apply( |
| 3187 | 60x |
biom_level_samples, |
| 3188 | 60x |
1L, |
| 3189 | 60x |
function(x) {
|
| 3190 | 28704x |
rnx <- range(x) |
| 3191 | 28704x |
min(which( |
| 3192 | 28704x |
(x >= stopping@target[1] * diff(rnx) + rnx[1]) & |
| 3193 | 28704x |
(x <= stopping@target[2] * diff(rnx) + rnx[1] + 1e-10) |
| 3194 |
)) |
|
| 3195 |
} |
|
| 3196 |
) |
|
| 3197 | 60x |
prob_target <- numeric(ncol(biom_level_samples)) |
| 3198 | 60x |
tab <- table(targetIndex) |
| 3199 | 60x |
prob_target[as.numeric(names(tab))] <- tab |
| 3200 | 60x |
prob_target <- prob_target / nrow(biom_level_samples) |
| 3201 |
} |
|
| 3202 |
} else {
|
|
| 3203 |
# Otherwise the target is absolute. |
|
| 3204 |
# For each sample, look which dose is maximizing the |
|
| 3205 |
# simultaneous probability to be in the target biomarker |
|
| 3206 |
# range and below overdose toxicity. |
|
| 3207 | ! |
prob_target <- numeric(ncol(biom_level_samples)) |
| 3208 | ! |
prob_target <- sapply( |
| 3209 | ! |
seq(1, ncol(biom_level_samples)), |
| 3210 | ! |
function(x) {
|
| 3211 | ! |
sum( |
| 3212 | ! |
biom_level_samples[, x] >= stopping@target[1] & |
| 3213 | ! |
biom_level_samples[, x] <= stopping@target[2] |
| 3214 |
) / |
|
| 3215 | ! |
nrow(biom_level_samples) |
| 3216 |
} |
|
| 3217 |
) |
|
| 3218 |
} |
|
| 3219 | ||
| 3220 | 60x |
prob_target <- ifelse( |
| 3221 | 60x |
is.na(dose), |
| 3222 | 60x |
0, |
| 3223 | 60x |
prob_target[which(data@doseGrid == dose)] |
| 3224 |
) |
|
| 3225 | ||
| 3226 | 60x |
do_stop <- prob_target >= stopping@prob |
| 3227 | ||
| 3228 | 60x |
msg <- paste( |
| 3229 | 60x |
"Probability for target biomarker is", |
| 3230 | 60x |
round(prob_target * 100), |
| 3231 | 60x |
"% for dose", |
| 3232 | 60x |
dose, |
| 3233 | 60x |
"and thus", |
| 3234 | 60x |
ifelse(do_stop, "above", "below"), |
| 3235 | 60x |
"the required", |
| 3236 | 60x |
round(stopping@prob * 100), |
| 3237 |
"%" |
|
| 3238 |
) |
|
| 3239 | ||
| 3240 | 60x |
structure( |
| 3241 | 60x |
do_stop, |
| 3242 | 60x |
message = msg, |
| 3243 | 60x |
report_label = stopping@report_label |
| 3244 |
) |
|
| 3245 |
} |
|
| 3246 |
) |
|
| 3247 | ||
| 3248 |
## stopTrial-StoppingSpecificDose ---- |
|
| 3249 | ||
| 3250 |
#' @describeIn stopTrial if Stopping rule is met for specific dose of the planned |
|
| 3251 |
#' dose grid and not just for the default next best dose. |
|
| 3252 |
#' |
|
| 3253 |
#' @aliases stopTrial-StoppingSpecificDose |
|
| 3254 |
#' |
|
| 3255 |
#' @export |
|
| 3256 |
#' @example examples/Rules-method-stopTrial-StoppingSpecificDose.R |
|
| 3257 |
#' |
|
| 3258 |
setMethod( |
|
| 3259 |
f = "stopTrial", |
|
| 3260 |
signature = signature( |
|
| 3261 |
stopping = "StoppingSpecificDose", |
|
| 3262 |
dose = "numeric", |
|
| 3263 |
samples = "ANY", |
|
| 3264 |
model = "ANY", |
|
| 3265 |
data = "Data" |
|
| 3266 |
), |
|
| 3267 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3268 |
# Specific dose must be a part of the dose grid. |
|
| 3269 | 6x |
assert_subset(x = stopping@dose@.Data, choices = data@doseGrid) |
| 3270 | ||
| 3271 |
# Evaluate the original (wrapped) stopping rule at the specific dose. |
|
| 3272 | 6x |
result <- stopTrial( |
| 3273 | 6x |
stopping = stopping@rule, |
| 3274 | 6x |
dose = stopping@dose@.Data, |
| 3275 | 6x |
samples = samples, |
| 3276 | 6x |
model = model, |
| 3277 | 6x |
data = data, |
| 3278 |
... |
|
| 3279 |
) |
|
| 3280 |
# Correct the text message from the original stopping rule. |
|
| 3281 | 6x |
attr(result, "message") <- gsub( |
| 3282 | 6x |
pattern = "next best", |
| 3283 | 6x |
replacement = "specific", |
| 3284 | 6x |
x = attr(result, "message"), |
| 3285 | 6x |
ignore.case = TRUE |
| 3286 |
) |
|
| 3287 | ||
| 3288 | 6x |
attr(result, "report_label") <- stopping@report_label |
| 3289 | ||
| 3290 | 6x |
result |
| 3291 |
} |
|
| 3292 |
) |
|
| 3293 | ||
| 3294 |
## stopTrial-StoppingHighestDose ---- |
|
| 3295 | ||
| 3296 |
#' @describeIn stopTrial Stop when the highest dose is reached. |
|
| 3297 |
#' |
|
| 3298 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3299 |
#' |
|
| 3300 |
#' @aliases stopTrial-StoppingHighestDose |
|
| 3301 |
#' @example examples/Rules-method-stopTrial-StoppingHighestDose.R |
|
| 3302 |
#' @export |
|
| 3303 |
#' |
|
| 3304 |
setMethod( |
|
| 3305 |
f = "stopTrial", |
|
| 3306 |
signature = signature( |
|
| 3307 |
stopping = "StoppingHighestDose", |
|
| 3308 |
dose = "numeric", |
|
| 3309 |
samples = "ANY", |
|
| 3310 |
model = "ANY", |
|
| 3311 |
data = "Data" |
|
| 3312 |
), |
|
| 3313 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3314 | 34x |
is_highest_dose <- ifelse( |
| 3315 | 34x |
is.na(dose), |
| 3316 | 34x |
FALSE, |
| 3317 | 34x |
(dose == data@doseGrid[data@nGrid]) |
| 3318 |
) |
|
| 3319 | 34x |
structure( |
| 3320 | 34x |
is_highest_dose, |
| 3321 | 34x |
message = paste( |
| 3322 | 34x |
"Next best dose is", |
| 3323 | 34x |
dose, |
| 3324 | 34x |
"and thus", |
| 3325 | 34x |
ifelse(is_highest_dose, "the", "not the"), |
| 3326 | 34x |
"highest dose" |
| 3327 |
), |
|
| 3328 | 34x |
report_label = stopping@report_label |
| 3329 |
) |
|
| 3330 |
} |
|
| 3331 |
) |
|
| 3332 | ||
| 3333 |
## stopTrial-StoppingOrdinal ---- |
|
| 3334 | ||
| 3335 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
| 3336 |
#' |
|
| 3337 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3338 |
#' |
|
| 3339 |
#' @aliases stopTrial-StoppingOrdinal |
|
| 3340 |
#' @example examples/Rules-method-stopTrial-StoppingOrdinal.R |
|
| 3341 |
#' |
|
| 3342 |
setMethod( |
|
| 3343 |
f = "stopTrial", |
|
| 3344 |
signature = signature( |
|
| 3345 |
stopping = "StoppingOrdinal", |
|
| 3346 |
dose = "numeric", |
|
| 3347 |
samples = "ANY", |
|
| 3348 |
model = "LogisticLogNormalOrdinal", |
|
| 3349 |
data = "DataOrdinal" |
|
| 3350 |
), |
|
| 3351 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3352 | 22x |
stopTrial( |
| 3353 | 22x |
stopping = stopping@rule, |
| 3354 | 22x |
dose = dose, |
| 3355 | 22x |
samples = h_convert_ordinal_samples(samples, stopping@grade), |
| 3356 | 22x |
model = h_convert_ordinal_model(model, stopping@grade), |
| 3357 | 22x |
data = h_convert_ordinal_data(data, stopping@grade), |
| 3358 |
... |
|
| 3359 |
) |
|
| 3360 |
} |
|
| 3361 |
) |
|
| 3362 | ||
| 3363 |
## stopTrial-StoppingOrdinal ---- |
|
| 3364 | ||
| 3365 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
| 3366 |
#' |
|
| 3367 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3368 |
#' |
|
| 3369 |
#' @aliases stopTrial-StoppingOrdinal |
|
| 3370 |
#' @example examples/Rules-method-stopTrial-StoppingOrdinal.R |
|
| 3371 |
#' |
|
| 3372 |
setMethod( |
|
| 3373 |
f = "stopTrial", |
|
| 3374 |
signature = signature( |
|
| 3375 |
stopping = "StoppingOrdinal", |
|
| 3376 |
dose = "numeric", |
|
| 3377 |
samples = "ANY", |
|
| 3378 |
model = "ANY", |
|
| 3379 |
data = "ANY" |
|
| 3380 |
), |
|
| 3381 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3382 | ! |
stop( |
| 3383 | ! |
paste0( |
| 3384 | ! |
"StoppingOrdinal objects can only be used with LogisticLogNormalOrdinal ", |
| 3385 | ! |
"models and DataOrdinal data objects. In this case, the model is a '", |
| 3386 | ! |
class(model), |
| 3387 | ! |
"' object and the data is in a ", |
| 3388 | ! |
class(data), |
| 3389 | ! |
" object." |
| 3390 |
) |
|
| 3391 |
) |
|
| 3392 |
} |
|
| 3393 |
) |
|
| 3394 | ||
| 3395 |
## stopTrial-StoppingExternal ---- |
|
| 3396 | ||
| 3397 |
#' @describeIn stopTrial Stop based on an external flag. |
|
| 3398 |
#' |
|
| 3399 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3400 |
#' @param external (`flag`)\cr whether to stop based on the external |
|
| 3401 |
#' result or not. |
|
| 3402 |
#' |
|
| 3403 |
#' @aliases stopTrial-StoppingExternal |
|
| 3404 |
#' @example examples/Rules-method-stopTrial-StoppingExternal.R |
|
| 3405 |
#' |
|
| 3406 |
setMethod( |
|
| 3407 |
f = "stopTrial", |
|
| 3408 |
signature = signature( |
|
| 3409 |
stopping = "StoppingExternal", |
|
| 3410 |
dose = "numeric", |
|
| 3411 |
samples = "ANY", |
|
| 3412 |
model = "ANY", |
|
| 3413 |
data = "ANY" |
|
| 3414 |
), |
|
| 3415 |
definition = function(stopping, dose, samples, model, data, external, ...) {
|
|
| 3416 | 6x |
assert_flag(external) |
| 3417 | ||
| 3418 | 6x |
msg <- paste( |
| 3419 | 6x |
"Based on external result", |
| 3420 | 6x |
ifelse(external, "stop", "continue") |
| 3421 |
) |
|
| 3422 | ||
| 3423 | 6x |
structure( |
| 3424 | 6x |
external, |
| 3425 | 6x |
message = msg, |
| 3426 | 6x |
report_label = stopping@report_label |
| 3427 |
) |
|
| 3428 |
} |
|
| 3429 |
) |
|
| 3430 | ||
| 3431 | ||
| 3432 |
## stopTrial-StoppingTDCIRatio ---- |
|
| 3433 | ||
| 3434 |
#' @describeIn stopTrial Stop based on [`StoppingTDCIRatio`] class when |
|
| 3435 |
#' reaching the target ratio of the upper to the lower 95% credibility |
|
| 3436 |
#' interval of the estimate (TDtargetEndOfTrial). This is a stopping rule |
|
| 3437 |
#' which incorporates only DLE responses and DLE samples are given. |
|
| 3438 |
#' |
|
| 3439 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3440 |
#' |
|
| 3441 |
#' @aliases stopTrial-StoppingTDCIRatio |
|
| 3442 |
#' @example examples/Rules-method-stopTrialCITDsamples.R |
|
| 3443 |
#' @export |
|
| 3444 |
#' |
|
| 3445 |
setMethod( |
|
| 3446 |
f = "stopTrial", |
|
| 3447 |
signature = signature( |
|
| 3448 |
stopping = "StoppingTDCIRatio", |
|
| 3449 |
dose = "ANY", |
|
| 3450 |
samples = "Samples", |
|
| 3451 |
model = "ModelTox", |
|
| 3452 |
data = "ANY" |
|
| 3453 |
), |
|
| 3454 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3455 | 481x |
assert_probability(stopping@prob_target) |
| 3456 | ||
| 3457 | 481x |
dose_target_samples <- dose( |
| 3458 | 481x |
x = stopping@prob_target, |
| 3459 | 481x |
model = model, |
| 3460 | 481x |
samples = samples, |
| 3461 |
... |
|
| 3462 |
) |
|
| 3463 |
# 95% credibility interval. |
|
| 3464 | 481x |
dose_target_ci <- quantile(dose_target_samples, probs = c(0.025, 0.975)) |
| 3465 | 481x |
dose_target_ci_ratio <- dose_target_ci[[2]] / dose_target_ci[[1]] |
| 3466 | ||
| 3467 | 481x |
do_stop <- dose_target_ci_ratio <= stopping@target_ratio |
| 3468 | 481x |
text <- paste0( |
| 3469 | 481x |
"95% CI is (",
|
| 3470 | 481x |
paste(dose_target_ci, collapse = ", "), |
| 3471 | 481x |
"), Ratio = ", |
| 3472 | 481x |
round(dose_target_ci_ratio, 4), |
| 3473 | 481x |
" is ", |
| 3474 | 481x |
ifelse(do_stop, "less than or equal to ", "greater than "), |
| 3475 | 481x |
"target_ratio = ", |
| 3476 | 481x |
stopping@target_ratio |
| 3477 |
) |
|
| 3478 | 481x |
structure(do_stop, message = text, report_label = stopping@report_label) |
| 3479 |
} |
|
| 3480 |
) |
|
| 3481 | ||
| 3482 |
## stopTrial-StoppingTDCIRatio ---- |
|
| 3483 | ||
| 3484 |
#' @describeIn stopTrial Stop based on [`StoppingTDCIRatio`] class when |
|
| 3485 |
#' reaching the target ratio of the upper to the lower 95% credibility |
|
| 3486 |
#' interval of the estimate (TDtargetEndOfTrial). This is a stopping rule |
|
| 3487 |
#' which incorporates only DLE responses and no DLE samples are involved. |
|
| 3488 |
#' |
|
| 3489 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3490 |
#' |
|
| 3491 |
#' @aliases stopTrial-StoppingTDCIRatio |
|
| 3492 |
#' @example examples/Rules-method-stopTrialCITD.R |
|
| 3493 |
#' @export |
|
| 3494 |
#' |
|
| 3495 |
setMethod( |
|
| 3496 |
f = "stopTrial", |
|
| 3497 |
signature = signature( |
|
| 3498 |
stopping = "StoppingTDCIRatio", |
|
| 3499 |
dose = "ANY", |
|
| 3500 |
samples = "missing", |
|
| 3501 |
model = "ModelTox", |
|
| 3502 |
data = "ANY" |
|
| 3503 |
), |
|
| 3504 |
definition = function(stopping, dose, model, data, ...) {
|
|
| 3505 | 480x |
assert_probability(stopping@prob_target) |
| 3506 | ||
| 3507 | 480x |
prob_target <- stopping@prob_target |
| 3508 | 480x |
dose_target_samples <- dose(x = prob_target, model = model, ...) |
| 3509 |
# Find the variance of the log of the dose_target_samples (eta). |
|
| 3510 | 480x |
m1 <- matrix( |
| 3511 | 480x |
c( |
| 3512 | 480x |
-1 / (model@phi2), |
| 3513 | 480x |
-(log(prob_target / (1 - prob_target)) - model@phi1) / (model@phi2)^2 |
| 3514 |
), |
|
| 3515 | 480x |
1, |
| 3516 | 480x |
2 |
| 3517 |
) |
|
| 3518 | 480x |
m2 <- model@Pcov |
| 3519 | 480x |
var_eta <- as.vector(m1 %*% m2 %*% t(m1)) |
| 3520 | ||
| 3521 |
# Find the upper and lower limit of the 95% credibility interval. |
|
| 3522 | 480x |
ci <- exp(log(dose_target_samples) + c(-1, 1) * 1.96 * sqrt(var_eta)) |
| 3523 | 480x |
ratio <- ci[2] / ci[1] |
| 3524 | ||
| 3525 |
# So can we stop? |
|
| 3526 | 480x |
do_stop <- ratio <= stopping@target_ratio |
| 3527 |
# Generate message. |
|
| 3528 | 480x |
text <- paste( |
| 3529 | 480x |
"95% CI is (",
|
| 3530 | 480x |
round(ci[1], 4), |
| 3531 |
",", |
|
| 3532 | 480x |
round(ci[2], 4), |
| 3533 | 480x |
"), Ratio =", |
| 3534 | 480x |
round(ratio, 4), |
| 3535 | 480x |
"is ", |
| 3536 | 480x |
ifelse(do_stop, "is less than or equal to", "greater than"), |
| 3537 | 480x |
"target_ratio =", |
| 3538 | 480x |
stopping@target_ratio |
| 3539 |
) |
|
| 3540 |
# Return both. |
|
| 3541 | 480x |
structure( |
| 3542 | 480x |
do_stop, |
| 3543 | 480x |
message = text, |
| 3544 | 480x |
report_label = stopping@report_label |
| 3545 |
) |
|
| 3546 |
} |
|
| 3547 |
) |
|
| 3548 | ||
| 3549 |
## stopTrial-StoppingMaxGainCIRatio ---- |
|
| 3550 | ||
| 3551 |
#' @describeIn stopTrial Stop based on reaching the target ratio of the upper |
|
| 3552 |
#' to the lower 95% credibility interval of the estimate (the minimum of |
|
| 3553 |
#' Gstar and TDtargetEndOfTrial). This is a stopping rule which incorporates |
|
| 3554 |
#' DLE and efficacy responses and DLE and efficacy samples are also used. |
|
| 3555 |
#' |
|
| 3556 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3557 |
#' |
|
| 3558 |
#' @param TDderive (`function`)\cr the function which derives from the input, |
|
| 3559 |
#' a vector of the posterior samples called `TDsamples` of the dose which has |
|
| 3560 |
#' the probability of the occurrence of DLE equals to either the |
|
| 3561 |
#' targetDuringTrial or targetEndOfTrial, the final next best |
|
| 3562 |
#' TDtargetDuringTrial (the dose with probability of the occurrence of DLE |
|
| 3563 |
#' equals to the targetDuringTrial) and TDtargetEndOfTrial estimate. |
|
| 3564 |
#' @param Effmodel (`ModelEff`)\cr the efficacy model. |
|
| 3565 |
#' @param Effsamples (`Samples`)\cr the efficacy samples. |
|
| 3566 |
#' @param Gstarderive (`function`)\cr the function which derives from the input, |
|
| 3567 |
#' a vector of the posterior Gstar (the dose which gives the maximum gain |
|
| 3568 |
#' value) samples called `Gstarsamples`, the final next best Gstar estimate. |
|
| 3569 |
#' |
|
| 3570 |
#' @aliases stopTrial-StoppingMaxGainCIRatio |
|
| 3571 |
#' @example examples/Rules-method-stopTrialCIMaxGainSamples.R |
|
| 3572 |
#' @export |
|
| 3573 |
#' |
|
| 3574 |
setMethod( |
|
| 3575 |
f = "stopTrial", |
|
| 3576 |
signature = signature( |
|
| 3577 |
stopping = "StoppingMaxGainCIRatio", |
|
| 3578 |
dose = "ANY", |
|
| 3579 |
samples = "Samples", |
|
| 3580 |
model = "ModelTox", |
|
| 3581 |
data = "DataDual" |
|
| 3582 |
), |
|
| 3583 |
definition = function( |
|
| 3584 |
stopping, |
|
| 3585 |
dose, |
|
| 3586 |
samples, |
|
| 3587 |
model, |
|
| 3588 |
data, |
|
| 3589 |
TDderive, |
|
| 3590 |
Effmodel, |
|
| 3591 |
Effsamples, |
|
| 3592 |
Gstarderive, |
|
| 3593 |
... |
|
| 3594 |
) {
|
|
| 3595 | ! |
prob_target <- stopping@prob_target |
| 3596 | ||
| 3597 |
# Checks. |
|
| 3598 | ! |
assert_probability(prob_target) |
| 3599 | ! |
stopifnot(is(Effmodel, "ModelEff")) |
| 3600 | ! |
stopifnot(is(Effsamples, "Samples")) |
| 3601 | ! |
stopifnot(is.function(TDderive)) |
| 3602 | ! |
stopifnot(is.function(Gstarderive)) |
| 3603 | ||
| 3604 |
# Find the TDtarget End of Trial samples. |
|
| 3605 | ! |
td_target_end_of_trial_samples <- dose( |
| 3606 | ! |
x = prob_target, |
| 3607 | ! |
model = model, |
| 3608 | ! |
samples = samples, |
| 3609 |
... |
|
| 3610 |
) |
|
| 3611 |
# Find the TDtarget End of trial estimate. |
|
| 3612 | ! |
td_target_end_of_trial_estimate <- TDderive(td_target_end_of_trial_samples) |
| 3613 | ||
| 3614 |
# Find the gain value samples then the GstarSamples. |
|
| 3615 | ! |
points <- data@doseGrid |
| 3616 | ||
| 3617 | ! |
gain_samples <- matrix( |
| 3618 | ! |
nrow = size(samples), |
| 3619 | ! |
ncol = length(points) |
| 3620 |
) |
|
| 3621 | ||
| 3622 |
# Evaluate the probs, for all gain samples. |
|
| 3623 | ! |
for (i in seq_along(points)) {
|
| 3624 |
# Now we want to evaluate for the following dose. |
|
| 3625 | ! |
gain_samples[, i] <- gain( |
| 3626 | ! |
dose = points[i], |
| 3627 | ! |
model, |
| 3628 | ! |
samples, |
| 3629 | ! |
Effmodel, |
| 3630 | ! |
Effsamples, |
| 3631 |
... |
|
| 3632 |
) |
|
| 3633 |
} |
|
| 3634 | ||
| 3635 |
# Find the maximum gain value samples. |
|
| 3636 | ! |
max_gain_samples <- apply(gain_samples, 1, max) |
| 3637 | ||
| 3638 |
# Obtain Gstar samples, samples for the dose level which gives the maximum |
|
| 3639 |
# gain value. |
|
| 3640 | ! |
index_g <- apply(gain_samples, 1, which.max) |
| 3641 | ! |
gstar_samples <- data@doseGrid[index_g] |
| 3642 | ||
| 3643 |
# Find the Gstar estimate. |
|
| 3644 | ! |
gstar <- Gstarderive(gstar_samples) |
| 3645 |
# Find the 95% credibility interval of Gstar and its ratio of the upper to |
|
| 3646 |
# the lower limit. |
|
| 3647 | ! |
ci_gstar <- quantile(gstar_samples, probs = c(0.025, 0.975)) |
| 3648 | ! |
ratio_gstar <- as.numeric(ci_gstar[2] / ci_gstar[1]) |
| 3649 | ||
| 3650 |
# Find the 95% credibility interval of TDtargetEndOfTrial and its ratio of |
|
| 3651 |
# the upper to the lower limit. |
|
| 3652 | ! |
ci_tdeot <- quantile( |
| 3653 | ! |
td_target_end_of_trial_samples, |
| 3654 | ! |
probs = c(0.025, 0.975) |
| 3655 |
) |
|
| 3656 | ! |
ratio_tdeot <- as.numeric(ci_tdeot[2] / ci_tdeot[1]) |
| 3657 | ||
| 3658 |
# Find which is smaller (TDtargetEndOfTrialEstimate or Gstar). |
|
| 3659 | ! |
if (td_target_end_of_trial_estimate <= gstar) {
|
| 3660 |
# Find the upper and lower limit of the 95% credibility interval and its |
|
| 3661 |
# ratio of the smaller. |
|
| 3662 | ! |
ci <- ci_tdeot |
| 3663 | ! |
ratio <- ratio_tdeot |
| 3664 | ! |
choose_td <- TRUE |
| 3665 |