| 1 |
#' Format a `doseGrid` for Printing |
|
| 2 |
#' |
|
| 3 |
#' @param grid (`numeric`)\cr the dose grid |
|
| 4 |
#' @param units (`character`)\cr The units in which the values in `doseGrid` are |
|
| 5 |
#' @param fmt (`character`)\cr The format used to display values in `doseGrid`. |
|
| 6 |
#' If `NA`, grid values are not pre-formatted |
|
| 7 |
#' @param ... not used at present |
|
| 8 |
#' measured. Appended to each value in `doseGrid` when `knit_print`ed. The |
|
| 9 |
#' default, `NA`, omits the units. |
|
| 10 |
#' @return A character string containing the formatted dose grid. If the grid |
|
| 11 |
#' is `c(1, 2, 3)` and `units` is `"mg"`, the returned value is `"1 mg, 2 mg and 3 mg"`. |
|
| 12 |
#' @keywords internal |
|
| 13 |
h_get_formatted_dosegrid <- function(grid, units = NA, fmt = NA, ...) {
|
|
| 14 | 106x |
assert_numeric( |
| 15 | 106x |
grid, |
| 16 | 106x |
lower = 0, |
| 17 | 106x |
min.len = 2, |
| 18 | 106x |
unique = TRUE, |
| 19 | 106x |
finite = TRUE, |
| 20 | 106x |
sorted = TRUE, |
| 21 | 106x |
any.missing = FALSE |
| 22 |
) |
|
| 23 | 106x |
assert_character(units, len = 1) |
| 24 | ||
| 25 | 106x |
n <- length(grid) |
| 26 | 106x |
units <- h_prepare_units(units) |
| 27 | 106x |
formattedGrid <- if (is.na(fmt)) {
|
| 28 | 104x |
as.character(grid) |
| 29 |
} else {
|
|
| 30 | 2x |
sprintf(fmt, grid) |
| 31 |
} |
|
| 32 | 106x |
paste0( |
| 33 | 106x |
paste( |
| 34 | 106x |
lapply( |
| 35 | 106x |
formattedGrid[1:(n - 1)], |
| 36 | 106x |
paste0, |
| 37 | 106x |
sep = units |
| 38 |
), |
|
| 39 | 106x |
collapse = ", " |
| 40 |
), |
|
| 41 | 106x |
" and ", |
| 42 | 106x |
formattedGrid[n], |
| 43 | 106x |
paste0(units, ".\n\n") |
| 44 |
) |
|
| 45 |
} |
|
| 46 | ||
| 47 |
#' Set Column Headers in Custom `knit_print` Methods |
|
| 48 |
#' |
|
| 49 |
#' This is a helper method used `knit_print` for `crmPack` classes. |
|
| 50 |
#' |
|
| 51 |
#' @param x (`ANY`)\cr object that will be printed |
|
| 52 |
#' @param param (`list`)\cr A list of the `...` parameters passed to `knit_print` |
|
| 53 |
#' @param summarise (`flag`)\cr Is the object to be summarised (default) or listed? |
|
| 54 |
#' @return A character vector of column names. |
|
| 55 |
#' @noRd |
|
| 56 |
h_knit_print_set_headers <- function(x, param, summarise, ...) {
|
|
| 57 | 97x |
UseMethod("h_knit_print_set_headers")
|
| 58 |
} |
|
| 59 | ||
| 60 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 61 |
#' @rdname knit_print_set_headers |
|
| 62 |
#' @noRd |
|
| 63 |
h_knit_print_set_headers.GeneralData <- function(x, param, summarise, ...) {
|
|
| 64 | 42x |
if (!("col.names" %in% names(param))) {
|
| 65 | 42x |
if (summarise == "none") {
|
| 66 | 40x |
param[["col.names"]] <- c("ID", "Cohort", "Dose", "DLT?")
|
| 67 | 2x |
} else if (summarise == "dose") {
|
| 68 | 1x |
param[["col.names"]] <- c("Dose", "Evaluable", "With Toxicities")
|
| 69 |
} else {
|
|
| 70 | 1x |
param[["col.names"]] <- c("Cohort", "Evaluable", "With Toxicities")
|
| 71 |
} |
|
| 72 |
} |
|
| 73 | 42x |
param |
| 74 |
} |
|
| 75 | ||
| 76 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 77 |
#' @rdname knit_print_set_headers |
|
| 78 |
#' @noRd |
|
| 79 |
h_knit_print_set_headers.DataDA <- function(x, param, summarise, ...) {
|
|
| 80 | 10x |
if (!("col.names" %in% names(param))) {
|
| 81 | 10x |
if (summarise == "none") {
|
| 82 | 10x |
param[["col.names"]] <- c( |
| 83 | 10x |
"ID", |
| 84 | 10x |
"Cohort", |
| 85 | 10x |
"Dose", |
| 86 | 10x |
"Tox", |
| 87 | 10x |
"U", |
| 88 | 10x |
"T0", |
| 89 | 10x |
"TMax" |
| 90 |
) |
|
| 91 | ! |
} else if (summarise == "dose") {
|
| 92 | ! |
param[["col.names"]] <- c("Dose", "Evaluable", "With Toxicities")
|
| 93 |
} else {
|
|
| 94 | ! |
param[["col.names"]] <- c("Cohort", "Evaluable", "With Toxicities")
|
| 95 |
} |
|
| 96 |
} |
|
| 97 | 10x |
param |
| 98 |
} |
|
| 99 | ||
| 100 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 101 |
#' @rdname knit_print_set_headers |
|
| 102 |
#' @noRd |
|
| 103 |
h_knit_print_set_headers.DataGrouped <- function(x, param, summarise, ...) {
|
|
| 104 | 4x |
if (!("col.names" %in% names(param))) {
|
| 105 | 4x |
if (summarise == "none") {
|
| 106 | 4x |
param[["col.names"]] <- c("ID", "Cohort", "Dose", "Group", "Tox")
|
| 107 | ! |
} else if (summarise == "dose") {
|
| 108 | ! |
param[["col.names"]] <- c("Dose", "Group", "Evaluable", "With Toxicities")
|
| 109 |
} else {
|
|
| 110 | ! |
param[["col.names"]] <- c( |
| 111 | ! |
"Cohort", |
| 112 | ! |
"Group", |
| 113 | ! |
"Evaluable", |
| 114 | ! |
"With Toxicities" |
| 115 |
) |
|
| 116 |
} |
|
| 117 |
} |
|
| 118 | 4x |
param |
| 119 |
} |
|
| 120 | ||
| 121 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 122 |
#' @rdname knit_print_set_headers |
|
| 123 |
#' @noRd |
|
| 124 |
h_knit_print_set_headers.DataParts <- function(x, param, summarise, ...) {
|
|
| 125 | 4x |
if (!("col.names" %in% names(param))) {
|
| 126 | 4x |
if (summarise == "none") {
|
| 127 | 4x |
param[["col.names"]] <- c("ID", "Part", "Cohort", "Dose", "Tox")
|
| 128 | ! |
} else if (summarise == "dose") {
|
| 129 | ! |
param[["col.names"]] <- c("Dose", "Evaluable", "With Toxicities")
|
| 130 |
} else {
|
|
| 131 | ! |
param[["col.names"]] <- c("Cohort", "Evaluable", "With Toxicities")
|
| 132 |
} |
|
| 133 |
} |
|
| 134 | 4x |
param |
| 135 |
} |
|
| 136 | ||
| 137 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 138 |
#' @noRd |
|
| 139 |
h_knit_print_set_headers.DataOrdinal <- function(x, param, summarise, ...) {
|
|
| 140 | 15x |
if (!("col.names" %in% names(param))) {
|
| 141 | 15x |
if (summarise == "none") {
|
| 142 | 15x |
param[["col.names"]] <- c( |
| 143 | 15x |
"ID", |
| 144 | 15x |
"Cohort", |
| 145 | 15x |
"Dose", |
| 146 | 15x |
paste0("Cat", 0:(length(x@yCategories) - 1))
|
| 147 |
) |
|
| 148 | ! |
} else if (summarise == "dose") {
|
| 149 | ! |
param[["col.names"]] <- c("Dose", "Evaluable", names(x@yCategories))
|
| 150 |
} else {
|
|
| 151 | ! |
param[["col.names"]] <- c("Cohort", "Evaluable", names(x@yCategories))
|
| 152 |
} |
|
| 153 |
} |
|
| 154 | 15x |
param |
| 155 |
} |
|
| 156 | ||
| 157 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 158 |
#' @rdname knit_print_set_headers |
|
| 159 |
#' @noRd |
|
| 160 |
h_knit_print_set_headers.DataDual <- function(x, param, summarise, ...) {
|
|
| 161 | 22x |
if (!("col.names" %in% names(param))) {
|
| 162 | 22x |
if (summarise == "none") {
|
| 163 | 22x |
param[["col.names"]] <- c("ID", "Cohort", "Dose", "Tox", "W")
|
| 164 | ! |
} else if (summarise == "dose") {
|
| 165 | ! |
param[["col.names"]] <- c("Dose", "Evaluable", "With Toxicities")
|
| 166 |
} else {
|
|
| 167 | ! |
param[["col.names"]] <- c("Cohort", "Evaluable", "With Toxicities")
|
| 168 |
} |
|
| 169 |
} |
|
| 170 | 22x |
param |
| 171 |
} |
|
| 172 | ||
| 173 |
#' Select Columns to Print in Custom `knit_print` Methods |
|
| 174 |
#' |
|
| 175 |
#' This is a helper method used `knit_print` for `crmPack` classes. |
|
| 176 |
#' |
|
| 177 |
#' @param x (`ANY`)\cr object that will be printed |
|
| 178 |
#' @param ... Not used at present. |
|
| 179 |
#' @return A tidied version of `x`, containing only the selected columns. |
|
| 180 |
#' @noRd |
|
| 181 |
h_knit_print_select_columns <- function(x, ...) {
|
|
| 182 | 95x |
UseMethod("h_knit_print_select_columns")
|
| 183 |
} |
|
| 184 | ||
| 185 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 186 |
#' @rdname knit_print_select_columns |
|
| 187 |
#' @noRd |
|
| 188 |
h_knit_print_select_columns.GeneralData <- function(x, ...) {
|
|
| 189 | ! |
x %>% |
| 190 | ! |
tidy() %>% |
| 191 | ! |
dplyr::select("ID", "Cohort", "Dose", "Tox")
|
| 192 |
} |
|
| 193 | ||
| 194 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 195 |
#' @rdname knit_print_select_columns |
|
| 196 |
#' @noRd |
|
| 197 |
h_knit_print_select_columns.Data <- function(x, ...) {
|
|
| 198 | 40x |
x %>% |
| 199 | 40x |
tidy() %>% |
| 200 | 40x |
dplyr::select("ID", "Cohort", "Dose", "Tox")
|
| 201 |
} |
|
| 202 | ||
| 203 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 204 |
#' @rdname knit_print_select_columns |
|
| 205 |
#' @noRd |
|
| 206 |
h_knit_print_select_columns.DataParts <- function(x, ...) {
|
|
| 207 | 4x |
x %>% |
| 208 | 4x |
tidy() %>% |
| 209 | 4x |
dplyr::select("ID", "Part", "Cohort", "Dose", "Tox")
|
| 210 |
} |
|
| 211 | ||
| 212 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 213 |
#' @rdname knit_print_select_columns |
|
| 214 |
#' @noRd |
|
| 215 |
h_knit_print_select_columns.DataOrdinal <- function(x, ...) {
|
|
| 216 | 15x |
x %>% |
| 217 | 15x |
tidy() %>% |
| 218 | 15x |
dplyr::select("ID", "Cohort", "Dose", tidyselect::starts_with("Cat"))
|
| 219 |
} |
|
| 220 | ||
| 221 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 222 |
#' @rdname knit_print_select_columns |
|
| 223 |
#' @noRd |
|
| 224 |
h_knit_print_select_columns.DataDA <- function(x, param, summarise, ...) {
|
|
| 225 | 10x |
x %>% |
| 226 | 10x |
tidy() %>% |
| 227 | 10x |
dplyr::select("ID", "Cohort", "Dose", "Tox", "U", "T0", "TMax")
|
| 228 |
} |
|
| 229 | ||
| 230 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 231 |
#' @rdname knit_print_select_columns |
|
| 232 |
#' @noRd |
|
| 233 |
h_knit_print_select_columns.DataGrouped <- function(x, param, summarise, ...) {
|
|
| 234 | 4x |
x %>% |
| 235 | 4x |
tidy() %>% |
| 236 | 4x |
dplyr::select("ID", "Cohort", "Dose", "Group", "Tox")
|
| 237 |
} |
|
| 238 | ||
| 239 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 240 |
#' @rdname knit_print_select_columns |
|
| 241 |
#' @noRd |
|
| 242 |
h_knit_print_select_columns.DataDual <- function(x, param, summarise, ...) {
|
|
| 243 | 22x |
x %>% |
| 244 | 22x |
tidy() %>% |
| 245 | 22x |
dplyr::select("ID", "Cohort", "Dose", "Tox", "W")
|
| 246 |
} |
|
| 247 | ||
| 248 |
#' Summarise a `Data` Object by Dose or Cohort for Display in Custom `knit_print` Methods |
|
| 249 |
#' |
|
| 250 |
#' This is a helper method used `knit_print` for `crmPack` classes. |
|
| 251 |
#' |
|
| 252 |
#' @param x (`ANY`)\cr object that will be printed |
|
| 253 |
#' @param full_grid (`flag`)\cr Should the full grid be included or only those |
|
| 254 |
#' doses with at least one evaluable participant? |
|
| 255 |
#' @param ... Not used at present. |
|
| 256 |
#' @return A tibble containing the summarised data |
|
| 257 |
#' @noRd |
|
| 258 |
h_knit_print_summarise <- function(x, summarise, full_grid, ...) {
|
|
| 259 | 2x |
UseMethod("h_knit_print_summarise")
|
| 260 |
} |
|
| 261 | ||
| 262 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 263 |
#' @rdname knit_print_summarise |
|
| 264 |
#' @noRd |
|
| 265 |
h_knit_print_summarise.GeneralData <- function(x, summarise, full_grid, ...) {
|
|
| 266 | 2x |
xTidy <- x %>% tidy() |
| 267 | 2x |
xTidy <- xTidy %>% |
| 268 | 2x |
dplyr::group_by(.data[[stringr::str_to_title(summarise)]]) %>% |
| 269 | 2x |
dplyr::summarise( |
| 270 | 2x |
N = dplyr::n(), |
| 271 | 2x |
ToxCount = sum(Tox) |
| 272 |
) |
|
| 273 | 2x |
if (full_grid && summarise == "dose") {
|
| 274 | ! |
xTidy <- xTidy %>% |
| 275 | ! |
tidyr::complete( |
| 276 | ! |
Dose = x@doseGrid, |
| 277 | ! |
fill = list(ToxCount = 0, N = 0) |
| 278 |
) |
|
| 279 |
} |
|
| 280 | 2x |
xTidy |
| 281 |
} |
|
| 282 | ||
| 283 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 284 |
#' @rdname knit_print_summarise |
|
| 285 |
#' @noRd |
|
| 286 |
h_knit_print_summarise.DataOrdinal <- function(x, summarise, full_grid, ...) {
|
|
| 287 | ! |
xTidy <- x %>% tidy() |
| 288 | ! |
xTidy <- xTidy %>% |
| 289 | ! |
dplyr::group_by(.data$Dose) %>% |
| 290 | ! |
dplyr::summarise( |
| 291 | ! |
N = dplyr::n(), |
| 292 | ! |
dplyr::across(tidyselect::starts_with("Cat"), sum)
|
| 293 |
) |
|
| 294 | ! |
if (full_grid && summarise == "dose") {
|
| 295 | ! |
replace_list <- as.list( |
| 296 | ! |
c( |
| 297 | ! |
"N", |
| 298 | ! |
names(xTidy)[which(stringr::str_detect(names(xTidy), "Cat\\d+"))] |
| 299 |
) |
|
| 300 |
) |
|
| 301 |
# Create a list whose names are the columns in which we need to replace NAs |
|
| 302 |
# and whose values are 0 |
|
| 303 | ! |
names(replace_list) <- sapply(replace_list, \(x) x) |
| 304 | ! |
replace_list <- lapply(replace_list, \(x) 0) |
| 305 |
# Expand the tibble and do the replacement |
|
| 306 | ! |
xTidy <- tidyr::expand_grid(Dose = x@doseGrid) %>% |
| 307 | ! |
dplyr::left_join(xTidy, by = "Dose") %>% |
| 308 | ! |
tidyr::replace_na(replace_list) |
| 309 |
} |
|
| 310 | ! |
xTidy |
| 311 |
} |
|
| 312 | ||
| 313 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 314 |
#' @rdname knit_print_summarise |
|
| 315 |
#' @noRd |
|
| 316 |
h_knit_print_summarise.DataGrouped <- function(x, summarise, full_grid, ...) {
|
|
| 317 | ! |
xTidy <- x %>% tidy() |
| 318 | ! |
xTidy <- xTidy %>% |
| 319 | ! |
dplyr::group_by(.data[[stringr::str_to_title(summarise)]], .data$Group) %>% |
| 320 | ! |
dplyr::summarise( |
| 321 | ! |
N = dplyr::n(), |
| 322 | ! |
ToxCount = sum(Tox) |
| 323 |
) |
|
| 324 | ! |
if (full_grid && summarise == "dose") {
|
| 325 | ! |
xTidy <- tidyr::expand_grid( |
| 326 | ! |
Dose = x@doseGrid, |
| 327 | ! |
Group = c("mono", "combo")
|
| 328 |
) %>% |
|
| 329 | ! |
dplyr::left_join(xTidy, by = c("Dose", "Group")) %>%
|
| 330 | ! |
tidyr::replace_na(list(N = 0, ToxCount = 0)) |
| 331 |
} |
|
| 332 | ! |
xTidy |
| 333 |
} |
|
| 334 | ||
| 335 |
#' Print a `GeneralData` Object in a Markdown or Quarto Chunk |
|
| 336 |
#' |
|
| 337 |
#' @param label (`character`)\cr How to describe the participants in the trial. |
|
| 338 |
#' See Usage Notes below. |
|
| 339 |
#' @param full_grid (`flag`)\cr Should the full dose grid appear in the output table |
|
| 340 |
#' or simply those doses for whom at least one evaluable participant is available? |
|
| 341 |
#' Ignored unless `summarise == "dose"`. |
|
| 342 |
#' @param summarise (`character`)\cr How to summarise the observed data. The default, |
|
| 343 |
#' `"none"`, lists observed data at the participant level. `"dose"` presents |
|
| 344 |
#' participant counts by dose and `"cohort"` by cohort. |
|
| 345 |
#' @param summarize (`character`)\cr Synonym for `summarise` |
|
| 346 |
#' @param format_func (`function`)\cr The function used to format the participant table. |
|
| 347 |
#' The default applies no formatting. Obvious alternatives include `kableExtra::kable_styling`. |
|
| 348 |
#' @param ... passed to [knitr::kable()] |
|
| 349 |
#' @section Usage Notes: |
|
| 350 |
#' `label` describes the trial's participants. |
|
| 351 |
#' |
|
| 352 |
#' It should be a character vector of length 1 or 2. If of length 2, the first |
|
| 353 |
#' element describes a single participant and the second describes all other |
|
| 354 |
#' situations. If of length 1, the character `s` is appended to the value |
|
| 355 |
#' when the number of participants is not 1. |
|
| 356 |
#' The default values of `col.names` and `caption` vary depending on the summary |
|
| 357 |
#' requested. The default values can be overridden by passing `col.names` and |
|
| 358 |
#' `caption` in the function call. |
|
| 359 |
#' |
|
| 360 |
#' @inheritParams h_get_formatted_dosegrid |
|
| 361 |
#' @export |
|
| 362 |
#' @method knit_print GeneralData |
|
| 363 |
#' @rdname knit_print |
|
| 364 |
knit_print.GeneralData <- function( |
|
| 365 |
x, |
|
| 366 |
..., |
|
| 367 |
asis = TRUE, |
|
| 368 |
label = c("participant", "participants"),
|
|
| 369 |
full_grid = FALSE, |
|
| 370 |
summarise = c("none", "dose", "cohort"),
|
|
| 371 |
summarize = summarise, |
|
| 372 |
units = NA, |
|
| 373 |
format_func = h_knit_format_func |
|
| 374 |
) {
|
|
| 375 |
# Validate |
|
| 376 | 111x |
assert_flag(asis) |
| 377 | 97x |
assert_flag(full_grid) |
| 378 | 97x |
assert_function(format_func) |
| 379 | 97x |
summarise <- match.arg(summarise) |
| 380 | 97x |
summarize <- match.arg(summarize) |
| 381 | ||
| 382 | 97x |
if (is.na(summarise) || is.null(summarise)) {
|
| 383 | ! |
summarise <- summarize |
| 384 |
} |
|
| 385 | 97x |
assert_choice(summarise, c("none", "dose", "cohort"))
|
| 386 |
# Initialise |
|
| 387 | 97x |
label <- h_prepare_labels(label) |
| 388 | 97x |
param <- list(...) |
| 389 | ||
| 390 |
# Execute |
|
| 391 | 97x |
param <- h_knit_print_set_headers(x, param, summarise, ...) |
| 392 | 97x |
if (summarise == "none") {
|
| 393 | 95x |
if (!("caption" %in% names(param))) {
|
| 394 | 95x |
param[["caption"]] <- paste("Evaluable", label[2], "to-date")
|
| 395 |
} |
|
| 396 | 95x |
xTidy <- h_knit_print_select_columns(x) |
| 397 |
} else {
|
|
| 398 | 2x |
xTidy <- h_knit_print_summarise(x, summarise, full_grid) |
| 399 | 2x |
if (!("caption" %in% names(param))) {
|
| 400 | 2x |
param[["caption"]] <- paste0("Summarised by ", summarise)
|
| 401 |
} |
|
| 402 |
} |
|
| 403 | 97x |
param[["x"]] <- xTidy |
| 404 | 97x |
rv <- if (length(x@x) > 0) {
|
| 405 | 32x |
paste((do.call(knitr::kable, param)) %>% format_func(), collapse = "\n") |
| 406 |
} else {
|
|
| 407 | 65x |
paste("No", label[2], "are yet evaluable.\n\n")
|
| 408 |
} |
|
| 409 | 97x |
rv <- paste0( |
| 410 | 97x |
rv, |
| 411 | 97x |
paste0( |
| 412 | 97x |
"\n\nThe dose grid is ", |
| 413 | 97x |
h_get_formatted_dosegrid( |
| 414 | 97x |
grid = x@doseGrid, |
| 415 | 97x |
units = units, |
| 416 |
... |
|
| 417 |
), |
|
| 418 |
"" |
|
| 419 |
), |
|
| 420 | 97x |
"\n\n", |
| 421 | 97x |
collpase = "\n" |
| 422 |
) |
|
| 423 | 97x |
if (asis) {
|
| 424 | 18x |
rv <- knitr::asis_output(rv) |
| 425 |
} |
|
| 426 | 97x |
rv |
| 427 |
} |
|
| 428 | ||
| 429 |
#' Used to obtain expected format. |
|
| 430 |
#' @keywords internal |
|
| 431 |
h_knit_format_func <- function(x) {
|
|
| 432 | 37x |
kableExtra::kable_styling( |
| 433 | 37x |
x, |
| 434 | 37x |
bootstrap_options = c("striped", "hover", "condensed")
|
| 435 |
) |
|
| 436 |
} |
|
| 437 | ||
| 438 |
#' @export |
|
| 439 |
#' @method knit_print DataParts |
|
| 440 |
#' @rdname knit_print |
|
| 441 |
knit_print.DataParts <- function( |
|
| 442 |
x, |
|
| 443 |
..., |
|
| 444 |
asis = TRUE, |
|
| 445 |
label = c("participant", "participants"),
|
|
| 446 |
full_grid = FALSE, |
|
| 447 |
summarise = c("none", "dose", "cohort"),
|
|
| 448 |
summarize = summarise, |
|
| 449 |
units = NA, |
|
| 450 |
format_func = h_knit_format_func |
|
| 451 |
) {
|
|
| 452 | 6x |
rv <- NextMethod() |
| 453 | 4x |
rv <- paste0( |
| 454 | 4x |
rv, |
| 455 | 4x |
paste0( |
| 456 | 4x |
"\n\nThe part 1 ladder is ", |
| 457 | 4x |
h_get_formatted_dosegrid(x@part1Ladder, units) |
| 458 |
), |
|
| 459 | 4x |
paste0("\n\nThe next part is Part ", x@nextPart, ".\n\n")
|
| 460 |
) |
|
| 461 | 4x |
if (asis) {
|
| 462 | 2x |
rv <- knitr::asis_output(rv) |
| 463 |
} |
|
| 464 | 4x |
rv |
| 465 |
} |
| 1 |
#' @include helpers.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
#' Appending a Dummy Number for Selected Slots in Data |
|
| 5 |
#' |
|
| 6 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 7 |
#' |
|
| 8 |
#' A helper function that appends a dummy value to a given slots in [`GeneralData`] |
|
| 9 |
#' class object, if and only if the total number of observations (as indicated |
|
| 10 |
#' by `object@nObs`) equals to `1`. Otherwise, the `object` is not changed. |
|
| 11 |
#' |
|
| 12 |
#' @note The main motivation behind this function is related to the `JAGS`. |
|
| 13 |
#' If there is only one observation, the data is not passed correctly to |
|
| 14 |
#' `JAGS`, i.e. e.g. `x` and `y` are treated like scalars in the data file. |
|
| 15 |
#' Therefore it is necessary to add dummy values to the vectors in this case |
|
| 16 |
#' As we don't change the number of observations (`nObs`), this addition of |
|
| 17 |
#' zeros doesn't affect the results of `JAGS` computations. |
|
| 18 |
#' |
|
| 19 |
#' @param object (`GeneralData`)\cr object into which dummy values will be added. |
|
| 20 |
#' @param where (`character`)\cr names of slots in `object` to which a `dummy` |
|
| 21 |
#' number will be appended. |
|
| 22 |
#' @param dummy (`number`)\cr a dummy number that will be appended to selected |
|
| 23 |
#' slots in `object`. Default to `0`. |
|
| 24 |
#' |
|
| 25 |
#' @return A [`GeneralData`] object with slots updated with dummy number. |
|
| 26 |
#' |
|
| 27 |
#' @export |
|
| 28 |
#' @example examples/helpers-jags_add_dummy.R |
|
| 29 |
#' |
|
| 30 |
h_jags_add_dummy <- function(object, where, dummy = 0) {
|
|
| 31 | 445x |
assert_class(object, "GeneralData") |
| 32 | 445x |
assert_character(where) |
| 33 | 445x |
assert_subset(where, slotNames(object)) |
| 34 | 444x |
assert_number(dummy) |
| 35 | ||
| 36 | 444x |
if (object@nObs == 1L) {
|
| 37 | 8x |
for (i in where) {
|
| 38 |
# Add dummy value and preserve the class. |
|
| 39 | 19x |
slot(object, i) <- as(c(slot(object, i), dummy), class(slot(object, i))) |
| 40 |
} |
|
| 41 |
} |
|
| 42 | 444x |
object |
| 43 |
} |
|
| 44 | ||
| 45 |
#' Joining `JAGS` Models |
|
| 46 |
#' |
|
| 47 |
#' @description `r lifecycle::badge("stable")`
|
|
| 48 |
#' |
|
| 49 |
#' This helper function joins two JAGS models in the way that the body of the |
|
| 50 |
#' second model is appended to the body of the first model (in this order). |
|
| 51 |
#' After that, the first, body-extended model is returned. The arguments of |
|
| 52 |
#' `model1`, `model2` model functions (if any) are not combined in any way. |
|
| 53 |
#' |
|
| 54 |
#' @note `model1` and `model2` functions must have a multi-expression |
|
| 55 |
#' body, i.e. braced expression(s). Environments or any attributes of the |
|
| 56 |
#' function bodies are not preserved in any way after joining. |
|
| 57 |
#' |
|
| 58 |
#' @param model1 (`function`)\cr the first model to join. |
|
| 59 |
#' @param model2 (`function`)\cr the second model to join. |
|
| 60 |
#' |
|
| 61 |
#' @return joined models. |
|
| 62 |
#' |
|
| 63 |
#' @export |
|
| 64 |
#' |
|
| 65 |
h_jags_join_models <- function(model1, model2) {
|
|
| 66 | 834x |
assert_function(model1) |
| 67 | 834x |
assert_function(model2) |
| 68 | 834x |
assert_class(body(model1), "{")
|
| 69 | 833x |
assert_class(body(model2), "{")
|
| 70 | ||
| 71 |
# This workaround is needed to avoid bugs related to covr-injected code. |
|
| 72 | 833x |
if (h_covr_active()) {
|
| 73 | 833x |
body(model2) <- h_covr_detrace(body(model2)) |
| 74 |
} |
|
| 75 | ||
| 76 | 833x |
body2 <- as.list(body(model2)) |
| 77 | 833x |
if (length(body2) >= 2) {
|
| 78 | 832x |
body1 <- as.list(body(model1)) |
| 79 | 832x |
body(model1) <- as.call(c(body1, body2[-1])) |
| 80 |
} |
|
| 81 | 833x |
model1 |
| 82 |
} |
|
| 83 | ||
| 84 | ||
| 85 |
#' Setting Initial Values for `JAGS` Model Parameters |
|
| 86 |
#' |
|
| 87 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 88 |
#' |
|
| 89 |
#' A simple helper function that prepares an object for `inits` argument of |
|
| 90 |
#' [rjags::jags.model()], which is invoked by [mcmc()] method. The `inits` |
|
| 91 |
#' argument specifies initial values for model parameters. |
|
| 92 |
#' |
|
| 93 |
#' @param model (`GeneralModel`)\cr an input model. |
|
| 94 |
#' @param data (`GeneralData`)\cr an input data. |
|
| 95 |
#' |
|
| 96 |
#' @return A `list` of starting values for parameters required to be initialized |
|
| 97 |
#' in the MCMC `JAGS `sampler. |
|
| 98 |
#' |
|
| 99 |
#' @export |
|
| 100 |
#' @example examples/helpers-jags_get_model_inits.R |
|
| 101 |
#' |
|
| 102 |
h_jags_get_model_inits <- function(model, data) {
|
|
| 103 | 438x |
assert_class(model, "GeneralModel") |
| 104 | 438x |
assert_class(data, "GeneralData") |
| 105 | ||
| 106 | 438x |
inits <- do.call(model@init, h_slots(data, formalArgs(model@init))) |
| 107 | 438x |
assert_list(inits) |
| 108 | 437x |
inits[sapply(inits, length) > 0L] |
| 109 |
} |
|
| 110 | ||
| 111 |
#' Getting Data for `JAGS` |
|
| 112 |
#' |
|
| 113 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 114 |
#' |
|
| 115 |
#' A simple helper function that prepares an object for `data` argument of |
|
| 116 |
#' [rjags::jags.model()], which is invoked by [mcmc()] method. |
|
| 117 |
#' |
|
| 118 |
#' @param model (`GeneralModel`)\cr an input model. |
|
| 119 |
#' @param data (`GeneralData`)\cr an input data. |
|
| 120 |
#' @param from_prior (`flag`)\cr sample from the prior only? In this case |
|
| 121 |
#' data will not be appended to the output, i.e. only the variables required |
|
| 122 |
#' by the `model@priormodel` model will be returned in data. |
|
| 123 |
#' |
|
| 124 |
#' @export |
|
| 125 |
#' @example examples/helpers-jags_get_data.R |
|
| 126 |
#' |
|
| 127 |
h_jags_get_data <- function(model, data, from_prior) {
|
|
| 128 | 441x |
assert_class(model, "GeneralModel") |
| 129 | 441x |
assert_class(data, "GeneralData") |
| 130 | 441x |
assert_flag(from_prior) |
| 131 | ||
| 132 |
# 1) Extract variables from `data` as required by `modelspecs`. |
|
| 133 | 441x |
ms_args_names <- formalArgs(model@modelspecs) |
| 134 | 441x |
ms_args <- if ("from_prior" %in% ms_args_names) {
|
| 135 | 410x |
c( |
| 136 | 410x |
h_slots(data, setdiff(ms_args_names, "from_prior")), |
| 137 | 410x |
list(from_prior = from_prior) |
| 138 |
) |
|
| 139 |
} else {
|
|
| 140 | 31x |
h_slots(data, ms_args_names) |
| 141 |
} |
|
| 142 | 441x |
modelspecs <- do.call(model@modelspecs, ms_args) |
| 143 | 441x |
assert_list(modelspecs) |
| 144 | ||
| 145 |
# 2) Extract variables from `data` as required by `datanames`. |
|
| 146 | 440x |
datanames <- if (from_prior) {
|
| 147 | 42x |
model@datanames_prior |
| 148 |
} else {
|
|
| 149 | 398x |
union(model@datanames, model@datanames_prior) |
| 150 |
} |
|
| 151 | ||
| 152 |
# Add dummy to ensure that e.g. `x` and `y` in `data` won't be treated as |
|
| 153 |
# scalars by `JAGS` if `data@nObs == 1`, which leads to failures. |
|
| 154 | 440x |
add_where <- setdiff( |
| 155 | 440x |
datanames, |
| 156 | 440x |
c("nObs", "nGrid", "nObsshare", "yshare", "xshare", "Tmax")
|
| 157 |
) |
|
| 158 | 440x |
data <- h_jags_add_dummy(data, where = add_where) |
| 159 | ||
| 160 | 440x |
data_model <- h_slots(data, datanames) |
| 161 | 440x |
c(data_model, modelspecs) |
| 162 |
} |
|
| 163 | ||
| 164 |
#' Writing JAGS Model to a File |
|
| 165 |
#' |
|
| 166 |
#' @description `r lifecycle::badge("stable")`
|
|
| 167 |
#' |
|
| 168 |
#' This function converts a R function with JAGS model into a text and then |
|
| 169 |
#' writes it into a given file. During the "model into text" conversion, the |
|
| 170 |
#' format of numbers of which absolute value is less than `0.001` or greater |
|
| 171 |
#' than `10000` is changed. These numbers will be converted into scientific |
|
| 172 |
#' format with specified number of significant digits using [formatC()] |
|
| 173 |
#' function. |
|
| 174 |
#' |
|
| 175 |
#' @note JAGS syntax allows truncation specification like `dnorm(...) I(...)`, |
|
| 176 |
#' which is illegal in R. To overcome this incompatibility, use dummy operator |
|
| 177 |
#' `\%_\%` before `I(...)`, i.e. `dnorm(...) \%_\% I(...)` in the model's |
|
| 178 |
#' code. This dummy operator `\%_\%` will be removed just before saving the |
|
| 179 |
#' JAGS code into a file. |
|
| 180 |
#' Due to technical issues related to conversion of numbers to scientific |
|
| 181 |
#' format, it is required that the body of a model function does not contain |
|
| 182 |
#' `TEMP_NUM_PREF_` or `_TEMP_NUM_SUF` character constants in its body. |
|
| 183 |
#' |
|
| 184 |
#' @param model (`function`)\cr function containing the JAGS model. |
|
| 185 |
#' @param file (`string` or `NULL`)\cr the name of the file (including the |
|
| 186 |
#' optional path) where the model will be saved. If `NULL`, the file will be |
|
| 187 |
#' created in a `R_crmPack` folder placed under temporary directory indicated |
|
| 188 |
#' by [tempdir()] function. |
|
| 189 |
#' @param digits (`count`)\cr a desired number of significant digits for |
|
| 190 |
#' for numbers used in JAGS input, see [formatC()]. |
|
| 191 |
#' @return The name of the file where the model was saved. |
|
| 192 |
#' |
|
| 193 |
#' @export |
|
| 194 |
#' @example examples/helpers-jags_write_model.R |
|
| 195 |
#' |
|
| 196 |
h_jags_write_model <- function(model, file = NULL, digits = 5) {
|
|
| 197 | 482x |
assert_function(model) |
| 198 | 482x |
assert_count(digits) |
| 199 | ||
| 200 |
# This workaround is needed to avoid bugs related to covr-injected code. |
|
| 201 | 482x |
if (h_covr_active()) {
|
| 202 | 482x |
body(model) <- h_covr_detrace(body(model)) |
| 203 |
} |
|
| 204 | ||
| 205 | 482x |
if (!is.null(file)) {
|
| 206 | 1x |
assert_path_for_output(file) |
| 207 |
} else {
|
|
| 208 | 481x |
dir <- file.path(tempdir(), "R_crmPack") |
| 209 |
# Don't warn, as the temp dir often exists (which is OK). |
|
| 210 | 481x |
dir.create(dir, showWarnings = FALSE) |
| 211 | 481x |
file <- tempfile( |
| 212 | 481x |
pattern = "jags_model_fun", |
| 213 | 481x |
tmpdir = dir, |
| 214 | 481x |
fileext = ".txt" |
| 215 |
) |
|
| 216 |
} |
|
| 217 | ||
| 218 |
# Replace scientific notation. |
|
| 219 | 482x |
model_sci_replaced <- h_rapply( |
| 220 | 482x |
x = body(model), |
| 221 | 482x |
fun = h_format_number, |
| 222 | 482x |
classes = c("integer", "numeric"),
|
| 223 | 482x |
digits = digits, |
| 224 | 482x |
prefix = "TEMP_NUM_PREF_", |
| 225 | 482x |
suffix = "_TEMP_NUM_SUF" |
| 226 |
) |
|
| 227 |
# Transform `model` body into character vector. |
|
| 228 | 482x |
model_text <- deparse(model_sci_replaced, control = NULL) |
| 229 | 482x |
model_text <- gsub("\"TEMP_NUM_PREF_|_TEMP_NUM_SUF\"", "", model_text)
|
| 230 | 482x |
model_text <- gsub("%_% ", "", model_text)
|
| 231 | 482x |
model_text <- c("model", model_text)
|
| 232 | ||
| 233 | 482x |
log_trace("Writting JAGS model function into: %s", file)
|
| 234 | 482x |
writeLines(model_text, con = file) |
| 235 | 482x |
file |
| 236 |
} |
|
| 237 | ||
| 238 |
#' Extracting Samples from `JAGS` `mcarray` Object |
|
| 239 |
#' |
|
| 240 |
#' @description `r lifecycle::badge("stable")`
|
|
| 241 |
#' |
|
| 242 |
#' A simple helper function that extracts a sample from |
|
| 243 |
#' [`rjags::mcarray.object`] S3 class object. The [`rjags::mcarray.object`] |
|
| 244 |
#' object is used by the [rjags::jags.samples()] function to represent MCMC |
|
| 245 |
#' output from a `JAGS` model. |
|
| 246 |
#' |
|
| 247 |
#' @param x an [`rjags::mcarray.object`] object. |
|
| 248 |
#' |
|
| 249 |
#' @export |
|
| 250 |
#' |
|
| 251 |
h_jags_extract_samples <- function(x) {
|
|
| 252 | 1223x |
assert_class(x, "mcarray") |
| 253 | ||
| 254 | 1223x |
x <- x[,, 1L] |
| 255 |
# In case that there are multiple parameters in a node. |
|
| 256 | 1223x |
if (is.matrix(x)) {
|
| 257 | 202x |
x <- t(x) |
| 258 |
} |
|
| 259 | 1223x |
x |
| 260 |
} |
| 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 | 42386x |
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 | 27223x |
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 | 130x |
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 | 88x |
assert_probability_range(quantiles) |
| 211 | 87x |
assert_numeric(points) |
| 212 | ||
| 213 |
## first we have to get samples from the dose-tox |
|
| 214 |
## curve at the dose grid points. |
|
| 215 | 86x |
probSamples <- matrix( |
| 216 | 86x |
nrow = size(object), |
| 217 | 86x |
ncol = length(points) |
| 218 |
) |
|
| 219 | ||
| 220 |
## evaluate the probs, for all samples. |
|
| 221 | 86x |
for (i in seq_along(points)) {
|
| 222 |
## Now we want to evaluate for the |
|
| 223 |
## following dose: |
|
| 224 | 4069x |
probSamples[, i] <- prob( |
| 225 | 4069x |
dose = points[i], |
| 226 | 4069x |
model, |
| 227 | 4069x |
object, |
| 228 |
... |
|
| 229 |
) |
|
| 230 |
} |
|
| 231 | ||
| 232 |
## extract middle curve |
|
| 233 | 86x |
middleCurve <- apply(probSamples, 2L, FUN = middle) |
| 234 | ||
| 235 |
## extract quantiles |
|
| 236 | 86x |
quantCurve <- apply(probSamples, 2L, quantile, prob = quantiles) |
| 237 | ||
| 238 |
## now create the data frame |
|
| 239 | 86x |
data.frame( |
| 240 | 86x |
dose = points, |
| 241 | 86x |
middle = middleCurve, |
| 242 | 86x |
lower = quantCurve[1, ], |
| 243 | 86x |
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 | 8x |
assert_probability_range(quantiles) |
| 275 | ||
| 276 |
## first obtain the dose-tox curve results from the parent method |
|
| 277 | 8x |
start <- callNextMethod( |
| 278 | 8x |
object = object, |
| 279 | 8x |
model = model, |
| 280 | 8x |
data = data, |
| 281 | 8x |
points = data@doseGrid, |
| 282 | 8x |
quantiles = quantiles, |
| 283 | 8x |
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 | 8x |
biomLevelSamples <- biomarker( |
| 292 | 8x |
xLevel = seq_len(data@nGrid), |
| 293 | 8x |
model, |
| 294 | 8x |
samples = object |
| 295 |
) |
|
| 296 | ||
| 297 |
## extract middle curve |
|
| 298 | 8x |
middleCurve <- apply(biomLevelSamples, 2L, FUN = middle) |
| 299 | ||
| 300 |
## extract quantiles |
|
| 301 | 8x |
quantCurve <- apply(biomLevelSamples, 2L, quantile, prob = quantiles) |
| 302 | ||
| 303 |
## now create the data frame |
|
| 304 | 8x |
biomResults <- data.frame( |
| 305 | 8x |
middleBiomarker = middleCurve, |
| 306 | 8x |
lowerBiomarker = quantCurve[1, ], |
| 307 | 8x |
upperBiomarker = quantCurve[2, ] |
| 308 |
) |
|
| 309 | ||
| 310 |
## return both, pasted together |
|
| 311 | 8x |
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 | 7x |
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 | 7x |
assert_logical(logNormal) |
| 379 | 6x |
assert_logical(verbose) |
| 380 | 5x |
assert_logical(create_plot) |
| 381 | 4x |
assert_numeric(points) |
| 382 | 3x |
assert_numeric(refDose) |
| 383 |
## get the required quantiles at these dose levels: |
|
| 384 | 2x |
quants <- fit( |
| 385 | 2x |
object, |
| 386 | 2x |
model, |
| 387 | 2x |
data, |
| 388 | 2x |
points = points, |
| 389 | 2x |
quantiles = c(0.025, 0.975), |
| 390 | 2x |
middle = median |
| 391 |
) |
|
| 392 | ||
| 393 |
## get better starting values if it is already a logistic normal |
|
| 394 |
## model |
|
| 395 | 2x |
if (is(model, "LogisticNormal") && (!logNormal)) {
|
| 396 | ! |
means <- sapply( |
| 397 | ! |
object@data, |
| 398 | ! |
mean |
| 399 |
) |
|
| 400 | ! |
cov <- cov(as.data.frame(object@data)) |
| 401 | ||
| 402 | ! |
parstart <- c( |
| 403 | ! |
means[1], |
| 404 | ! |
means[2], |
| 405 | ! |
sqrt(cov[1, 1]), |
| 406 | ! |
sqrt(cov[2, 2]), |
| 407 | ! |
cov2cor(cov)[1, 2] |
| 408 |
) |
|
| 409 | 2x |
} 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 | 1x |
parstart <- NULL |
| 430 |
} |
|
| 431 | ||
| 432 |
## run the approx function |
|
| 433 | 2x |
quantRes <- Quantiles2LogisticNormal( |
| 434 | 2x |
dosegrid = quants$dose, |
| 435 | 2x |
refDose = refDose, |
| 436 | 2x |
lower = quants$lower, |
| 437 | 2x |
upper = quants$upper, |
| 438 | 2x |
median = quants$middle, |
| 439 | 2x |
verbose = verbose, |
| 440 | 2x |
parstart = parstart, |
| 441 | 2x |
logNormal = logNormal, |
| 442 |
... |
|
| 443 |
) |
|
| 444 | 2x |
rv <- list() |
| 445 | 2x |
rv$model <- quantRes$model |
| 446 | 2x |
if (create_plot) {
|
| 447 | 2x |
rv$plot <- tibble::as_tibble(quantRes$required) %>% |
| 448 | 2x |
tibble::add_column(Type = "original") %>% |
| 449 | 2x |
tibble::add_column(x = points) %>% |
| 450 | 2x |
dplyr::bind_rows( |
| 451 | 2x |
tibble::as_tibble(quantRes$quantiles) %>% |
| 452 | 2x |
tibble::add_column(Type = "approximation") %>% |
| 453 | 2x |
tibble::add_column(x = points) |
| 454 |
) %>% |
|
| 455 | 2x |
tidyr::pivot_longer( |
| 456 | 2x |
c(lower, median, upper), |
| 457 | 2x |
names_to = "Line", |
| 458 | 2x |
values_to = "y" |
| 459 |
) %>% |
|
| 460 | 2x |
ggplot( |
| 461 | 2x |
aes( |
| 462 | 2x |
x = x, |
| 463 | 2x |
y = y, |
| 464 | 2x |
colour = Type, |
| 465 | 2x |
group = interaction(Type, .data$Line), |
| 466 | 2x |
linetype = (.data$Line == "median") |
| 467 |
) |
|
| 468 |
) + |
|
| 469 | 2x |
geom_line() + |
| 470 | 2x |
scale_colour_manual( |
| 471 | 2x |
name = " ", |
| 472 | 2x |
values = c("red", "blue")
|
| 473 |
) + |
|
| 474 | 2x |
scale_linetype_manual( |
| 475 | 2x |
name = " ", |
| 476 | 2x |
values = c("dotted", "solid"),
|
| 477 | 2x |
labels = c("95% CI", "Median"),
|
| 478 | 2x |
guide = guide_legend(reverse = TRUE) |
| 479 |
) + |
|
| 480 | 2x |
labs( |
| 481 | 2x |
x = "Dose", |
| 482 | 2x |
y = "p(Tox)" |
| 483 |
) + |
|
| 484 | 2x |
theme_light() |
| 485 |
} |
|
| 486 | 2x |
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 | 5x |
assert_logical(showLegend) |
| 525 | ||
| 526 |
## get the fit |
|
| 527 | 4x |
plotData <- fit( |
| 528 | 4x |
x, |
| 529 | 4x |
model = y, |
| 530 | 4x |
data = data, |
| 531 | 4x |
quantiles = c(0.025, 0.975), |
| 532 | 4x |
middle = mean, |
| 533 |
... |
|
| 534 |
) |
|
| 535 | ||
| 536 |
## make the plot |
|
| 537 | 4x |
gdata <- |
| 538 | 4x |
with( |
| 539 | 4x |
plotData, |
| 540 | 4x |
data.frame( |
| 541 | 4x |
x = rep(dose, 3), |
| 542 | 4x |
y = c(middle, lower, upper) * 100, |
| 543 | 4x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotData)),
|
| 544 | 4x |
Type = factor( |
| 545 | 4x |
c( |
| 546 | 4x |
rep( |
| 547 | 4x |
"Estimate", |
| 548 | 4x |
nrow(plotData) |
| 549 |
), |
|
| 550 | 4x |
rep( |
| 551 | 4x |
"95% Credible Interval", |
| 552 | 4x |
nrow(plotData) * 2 |
| 553 |
) |
|
| 554 |
), |
|
| 555 | 4x |
levels = c( |
| 556 | 4x |
"Estimate", |
| 557 | 4x |
"95% Credible Interval" |
| 558 |
) |
|
| 559 |
) |
|
| 560 |
) |
|
| 561 |
) |
|
| 562 | ||
| 563 | 4x |
ret <- gdata %>% |
| 564 | 4x |
ggplot() + |
| 565 | 4x |
geom_line( |
| 566 | 4x |
aes( |
| 567 | 4x |
x = x, |
| 568 | 4x |
y = y, |
| 569 | 4x |
group = group, |
| 570 | 4x |
linetype = Type, |
| 571 |
), |
|
| 572 | 4x |
colour = I("red"),
|
| 573 |
) + |
|
| 574 | 4x |
coord_cartesian(ylim = c(0, 100)) + |
| 575 | 4x |
labs( |
| 576 | 4x |
x = xlab, |
| 577 | 4x |
y = ylab, |
| 578 |
) |
|
| 579 | ||
| 580 | 4x |
ret + |
| 581 | 4x |
scale_linetype_manual( |
| 582 | 4x |
breaks = c( |
| 583 | 4x |
"Estimate", |
| 584 | 4x |
"95% Credible Interval" |
| 585 |
), |
|
| 586 | 4x |
values = c(1, 2), |
| 587 | 4x |
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 | 2x |
assert_logical(extrapolate) |
| 623 | ||
| 624 |
## call the superclass method, to get the toxicity plot |
|
| 625 | 1x |
plot1 <- callNextMethod(x, y, data, showLegend = showLegend, ...) |
| 626 | ||
| 627 |
## only look at these dose levels for the plot: |
|
| 628 | 1x |
xLevels <- |
| 629 | 1x |
if (extrapolate) {
|
| 630 | 1x |
seq_along(data@doseGrid) |
| 631 |
} else {
|
|
| 632 | ! |
1:max(data@xLevel) |
| 633 |
} |
|
| 634 | ||
| 635 |
## get the plot data for the biomarker plot |
|
| 636 | 1x |
functionSamples <- biomarker(xLevel = xLevels, model = y, samples = x) |
| 637 | ||
| 638 |
## extract mean curve |
|
| 639 | 1x |
meanCurve <- colMeans(functionSamples) |
| 640 | ||
| 641 |
## extract quantiles |
|
| 642 | 1x |
quantiles <- c(0.025, 0.975) |
| 643 | 1x |
quantCurve <- apply(functionSamples, 2L, quantile, prob = quantiles) |
| 644 | ||
| 645 |
## now create the data frame |
|
| 646 | 1x |
plotData <- data.frame( |
| 647 | 1x |
dose = data@doseGrid[xLevels], |
| 648 | 1x |
mean = meanCurve, |
| 649 | 1x |
lower = quantCurve[1, ], |
| 650 | 1x |
upper = quantCurve[2, ] |
| 651 |
) |
|
| 652 | ||
| 653 |
## make the second plot |
|
| 654 | 1x |
gdata <- |
| 655 | 1x |
with( |
| 656 | 1x |
plotData, |
| 657 | 1x |
data.frame( |
| 658 | 1x |
x = rep(dose, 3), |
| 659 | 1x |
y = c(mean, lower, upper), |
| 660 | 1x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotData)),
|
| 661 | 1x |
Type = factor( |
| 662 | 1x |
c( |
| 663 | 1x |
rep( |
| 664 | 1x |
"Estimate", |
| 665 | 1x |
nrow(plotData) |
| 666 |
), |
|
| 667 | 1x |
rep( |
| 668 | 1x |
"95% Credible Interval", |
| 669 | 1x |
nrow(plotData) * 2 |
| 670 |
) |
|
| 671 |
), |
|
| 672 | 1x |
levels = c( |
| 673 | 1x |
"Estimate", |
| 674 | 1x |
"95% Credible Interval" |
| 675 |
) |
|
| 676 |
) |
|
| 677 |
) |
|
| 678 |
) |
|
| 679 | 1x |
plot2 <- gdata %>% |
| 680 | 1x |
ggplot() + |
| 681 | 1x |
geom_line( |
| 682 | 1x |
aes( |
| 683 | 1x |
x = x, |
| 684 | 1x |
y = y, |
| 685 | 1x |
group = group, |
| 686 | 1x |
linetype = Type |
| 687 |
), |
|
| 688 | 1x |
colour = I("blue")
|
| 689 |
) + |
|
| 690 | 1x |
labs( |
| 691 | 1x |
x = "Dose level", |
| 692 | 1x |
y = "Biomarker level" |
| 693 |
) |
|
| 694 | ||
| 695 | 1x |
plot2 <- plot2 + |
| 696 | 1x |
scale_linetype_manual( |
| 697 | 1x |
breaks = c( |
| 698 | 1x |
"Estimate", |
| 699 | 1x |
"95% Credible Interval" |
| 700 |
), |
|
| 701 | 1x |
values = c(1, 2), |
| 702 | 1x |
guide = ifelse(showLegend, "legend", "none") |
| 703 |
) |
|
| 704 | ||
| 705 |
## arrange both plots side by side |
|
| 706 | 1x |
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 | 13x |
assert_probability_range(quantiles) |
| 735 | 11x |
assert_numeric(points) |
| 736 | ||
| 737 |
## first we have to get samples from the dose-tox |
|
| 738 |
## curve at the dose grid points. |
|
| 739 | 10x |
probSamples <- matrix( |
| 740 | 10x |
nrow = size(object), |
| 741 | 10x |
ncol = length(points) |
| 742 |
) |
|
| 743 | ||
| 744 |
## evaluate the probs, for all samples. |
|
| 745 | 10x |
for (i in seq_along(points)) {
|
| 746 |
## Now we want to evaluate for the |
|
| 747 |
## following dose: |
|
| 748 | 122x |
probSamples[, i] <- prob( |
| 749 | 122x |
dose = points[i], |
| 750 | 122x |
model, |
| 751 | 122x |
object |
| 752 |
) |
|
| 753 |
} |
|
| 754 | ||
| 755 |
## extract middle curve |
|
| 756 | 10x |
middleCurve <- apply(probSamples, 2L, FUN = middle) |
| 757 | ||
| 758 |
## extract quantiles |
|
| 759 | 10x |
quantCurve <- apply(probSamples, 2L, quantile, prob = quantiles) |
| 760 | ||
| 761 |
## now create the data frame |
|
| 762 | 10x |
data.frame( |
| 763 | 10x |
dose = points, |
| 764 | 10x |
middle = middleCurve, |
| 765 | 10x |
lower = quantCurve[1, ], |
| 766 | 10x |
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 | 9x |
assert_probability_range(quantiles) |
| 796 | 7x |
assert_numeric(points) |
| 797 | ||
| 798 |
## first we have to get samples from the dose-tox |
|
| 799 |
## curve at the dose grid points. |
|
| 800 | 6x |
ExpEffSamples <- matrix( |
| 801 | 6x |
nrow = size(object), |
| 802 | 6x |
ncol = length(points) |
| 803 |
) |
|
| 804 | ||
| 805 |
## evaluate the probs, for all samples. |
|
| 806 | 6x |
for (i in seq_along(points)) {
|
| 807 |
## Now we want to evaluate for the |
|
| 808 |
## following dose: |
|
| 809 | 73x |
ExpEffSamples[, i] <- efficacy( |
| 810 | 73x |
dose = points[i], |
| 811 | 73x |
model, |
| 812 | 73x |
object |
| 813 |
) |
|
| 814 |
} |
|
| 815 | ||
| 816 |
## extract middle curve |
|
| 817 | 6x |
middleCurve <- apply(ExpEffSamples, 2L, FUN = middle) |
| 818 | ||
| 819 |
## extract quantiles |
|
| 820 | 6x |
quantCurve <- apply(ExpEffSamples, 2L, quantile, prob = quantiles) |
| 821 | ||
| 822 |
## now create the data frame |
|
| 823 | 6x |
data.frame( |
| 824 | 6x |
dose = points, |
| 825 | 6x |
middle = middleCurve, |
| 826 | 6x |
lower = quantCurve[1, ], |
| 827 | 6x |
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 | 5x |
assert_probability_range(quantiles) |
| 857 | 3x |
assert_numeric(points) |
| 858 | ||
| 859 |
## first we have to get samples from the dose-tox |
|
| 860 |
## curve at the dose grid points. |
|
| 861 | 2x |
ExpEffSamples <- matrix( |
| 862 | 2x |
nrow = size(object), |
| 863 | 2x |
ncol = length(points) |
| 864 |
) |
|
| 865 | ||
| 866 |
## evaluate the probs, for all samples. |
|
| 867 | 2x |
for (i in seq_along(points)) {
|
| 868 |
## Now we want to evaluate for the |
|
| 869 |
## following dose: |
|
| 870 | 24x |
ExpEffSamples[, i] <- efficacy( |
| 871 | 24x |
dose = points[i], |
| 872 | 24x |
model, |
| 873 | 24x |
object |
| 874 |
) |
|
| 875 |
} |
|
| 876 | ||
| 877 |
## extract middle curve |
|
| 878 | 2x |
middleCurve <- apply(ExpEffSamples, 2L, FUN = middle) |
| 879 | ||
| 880 |
## extract quantiles |
|
| 881 | 2x |
quantCurve <- apply(ExpEffSamples, 2L, quantile, prob = quantiles) |
| 882 | ||
| 883 |
## now create the data frame |
|
| 884 | 2x |
data.frame( |
| 885 | 2x |
dose = points, |
| 886 | 2x |
middle = middleCurve, |
| 887 | 2x |
lower = quantCurve[1, ], |
| 888 | 2x |
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 | 2x |
assert_logical(showLegend) |
| 1080 | ||
| 1081 |
## get the fit |
|
| 1082 | 1x |
plotData <- fit( |
| 1083 | 1x |
x, |
| 1084 | 1x |
model = y, |
| 1085 | 1x |
data = data, |
| 1086 | 1x |
quantiles = c(0.025, 0.975), |
| 1087 | 1x |
middle = mean |
| 1088 |
) |
|
| 1089 | ||
| 1090 |
## make the plot |
|
| 1091 | 1x |
gdata <- |
| 1092 | 1x |
with( |
| 1093 | 1x |
plotData, |
| 1094 | 1x |
data.frame( |
| 1095 | 1x |
x = rep(dose, 3), |
| 1096 | 1x |
y = c(middle, lower, upper) * 100, |
| 1097 | 1x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotData)),
|
| 1098 | 1x |
Type = factor( |
| 1099 | 1x |
c( |
| 1100 | 1x |
rep( |
| 1101 | 1x |
"Estimate", |
| 1102 | 1x |
nrow(plotData) |
| 1103 |
), |
|
| 1104 | 1x |
rep( |
| 1105 | 1x |
"95% Credible Interval", |
| 1106 | 1x |
nrow(plotData) * 2 |
| 1107 |
) |
|
| 1108 |
), |
|
| 1109 | 1x |
levels = c( |
| 1110 | 1x |
"Estimate", |
| 1111 | 1x |
"95% Credible Interval" |
| 1112 |
) |
|
| 1113 |
) |
|
| 1114 |
) |
|
| 1115 |
) |
|
| 1116 | ||
| 1117 | 1x |
ret <- gdata %>% |
| 1118 | 1x |
ggplot() + |
| 1119 | 1x |
geom_line( |
| 1120 | 1x |
aes( |
| 1121 | 1x |
x = x, |
| 1122 | 1x |
y = y, |
| 1123 | 1x |
group = group, |
| 1124 | 1x |
linetype = Type |
| 1125 |
), |
|
| 1126 | 1x |
colour = I("red"),
|
| 1127 |
) + |
|
| 1128 | 1x |
coord_cartesian(ylim = c(0, 100)) + |
| 1129 | 1x |
labs( |
| 1130 | 1x |
x = xlab, |
| 1131 | 1x |
y = ylab |
| 1132 |
) |
|
| 1133 | ||
| 1134 | 1x |
ret + |
| 1135 | 1x |
scale_linetype_manual( |
| 1136 | 1x |
breaks = c( |
| 1137 | 1x |
"Estimate", |
| 1138 | 1x |
"95% Credible Interval" |
| 1139 |
), |
|
| 1140 | 1x |
values = c(1, 2), |
| 1141 | 1x |
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 | 4x |
assert_logical(showLegend) |
| 1183 | ||
| 1184 |
## get the fit |
|
| 1185 | 2x |
plotData <- fit( |
| 1186 | 2x |
x, |
| 1187 | 2x |
model = y, |
| 1188 | 2x |
data = data, |
| 1189 | 2x |
quantiles = c(0.025, 0.975), |
| 1190 | 2x |
middle = mean |
| 1191 |
) |
|
| 1192 | ||
| 1193 |
## make the plot |
|
| 1194 | 2x |
gdata <- |
| 1195 | 2x |
with( |
| 1196 | 2x |
plotData, |
| 1197 | 2x |
data.frame( |
| 1198 | 2x |
x = rep(dose, 3), |
| 1199 | 2x |
y = c(middle, lower, upper), |
| 1200 | 2x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotData)),
|
| 1201 | 2x |
Type = factor( |
| 1202 | 2x |
c( |
| 1203 | 2x |
rep( |
| 1204 | 2x |
"Estimate", |
| 1205 | 2x |
nrow(plotData) |
| 1206 |
), |
|
| 1207 | 2x |
rep( |
| 1208 | 2x |
"95% Credible Interval", |
| 1209 | 2x |
nrow(plotData) * 2 |
| 1210 |
) |
|
| 1211 |
), |
|
| 1212 | 2x |
levels = c( |
| 1213 | 2x |
"Estimate", |
| 1214 | 2x |
"95% Credible Interval" |
| 1215 |
) |
|
| 1216 |
) |
|
| 1217 |
) |
|
| 1218 |
) |
|
| 1219 | ||
| 1220 | 2x |
ret <- gdata %>% |
| 1221 | 2x |
ggplot() + |
| 1222 | 2x |
geom_line( |
| 1223 | 2x |
aes( |
| 1224 | 2x |
x = x, |
| 1225 | 2x |
y = y, |
| 1226 | 2x |
group = group, |
| 1227 | 2x |
linetype = Type |
| 1228 |
), |
|
| 1229 | 2x |
colour = I("blue")
|
| 1230 |
) + |
|
| 1231 | 2x |
labs( |
| 1232 | 2x |
x = xlab, |
| 1233 | 2x |
y = ylab |
| 1234 |
) + |
|
| 1235 | 2x |
coord_cartesian(xlim = c(0, max(data@doseGrid))) |
| 1236 | ||
| 1237 | 2x |
ret + |
| 1238 | 2x |
scale_linetype_manual( |
| 1239 | 2x |
breaks = c( |
| 1240 | 2x |
"Estimate", |
| 1241 | 2x |
"95% Credible Interval" |
| 1242 |
), |
|
| 1243 | 2x |
values = c(1, 2), |
| 1244 | 2x |
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 | 2x |
assert_logical(showLegend) |
| 1282 | ||
| 1283 |
## Make sure the right model estimates are use with the given data |
|
| 1284 | 1x |
y <- update(object = y, data = x) |
| 1285 | ||
| 1286 |
## create data frame |
|
| 1287 | ||
| 1288 | 1x |
plotData <- data.frame( |
| 1289 | 1x |
dose = x@doseGrid, |
| 1290 | 1x |
probDLE = prob( |
| 1291 | 1x |
dose = x@doseGrid, |
| 1292 | 1x |
model = y |
| 1293 |
) |
|
| 1294 |
) |
|
| 1295 |
## Look for TD30 and TD35 |
|
| 1296 | 1x |
TD30 <- dose( |
| 1297 | 1x |
x = 0.30, |
| 1298 | 1x |
model = y |
| 1299 |
) |
|
| 1300 | 1x |
TD35 <- dose( |
| 1301 | 1x |
x = 0.35, |
| 1302 | 1x |
model = y |
| 1303 |
) |
|
| 1304 | ||
| 1305 |
## make the plot |
|
| 1306 | 1x |
gdata <- with( |
| 1307 | 1x |
plotData, |
| 1308 | 1x |
data.frame( |
| 1309 | 1x |
x = dose, |
| 1310 | 1x |
y = probDLE, |
| 1311 | 1x |
group = rep("Estimated DLE", each = nrow(plotData)),
|
| 1312 | 1x |
Type = factor( |
| 1313 | 1x |
rep("Estimated DLE", nrow(plotData)),
|
| 1314 | 1x |
levels = "Estimated DLE" |
| 1315 |
) |
|
| 1316 |
) |
|
| 1317 |
) |
|
| 1318 | ||
| 1319 | 1x |
gdata %>% |
| 1320 | 1x |
ggplot() + |
| 1321 | 1x |
geom_line( |
| 1322 | 1x |
aes( |
| 1323 | 1x |
x = x, |
| 1324 | 1x |
y = y, |
| 1325 | 1x |
group = group, |
| 1326 | 1x |
linetype = Type |
| 1327 |
), |
|
| 1328 | 1x |
colour = I("red"),
|
| 1329 | 1x |
linewidth = 1.5 |
| 1330 |
) + |
|
| 1331 | 1x |
labs( |
| 1332 | 1x |
x = xlab, |
| 1333 | 1x |
y = ylab |
| 1334 |
) + |
|
| 1335 | 1x |
coord_cartesian(ylim = c(0, 1)) + |
| 1336 | 1x |
scale_linetype_manual( |
| 1337 | 1x |
breaks = "Estimated DLE", |
| 1338 | 1x |
values = c(1, 2), |
| 1339 | 1x |
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 | 4x |
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 | 3x |
assert_logical(extrapolate) |
| 1794 | 2x |
assert_logical(showLegend) |
| 1795 |
## Get Toxicity plot |
|
| 1796 |
## get the fit |
|
| 1797 | ||
| 1798 | 1x |
plotDLEData <- fit( |
| 1799 | 1x |
DLEsamples, |
| 1800 | 1x |
model = DLEmodel, |
| 1801 | 1x |
data = data, |
| 1802 | 1x |
quantiles = c(0.025, 0.975), |
| 1803 | 1x |
middle = mean |
| 1804 |
) |
|
| 1805 | ||
| 1806 |
## make the plot |
|
| 1807 | 1x |
gdata <- |
| 1808 | 1x |
with( |
| 1809 | 1x |
plotDLEData, |
| 1810 | 1x |
data.frame( |
| 1811 | 1x |
x = rep(dose, 3), |
| 1812 | 1x |
y = c(middle, lower, upper) * 100, |
| 1813 | 1x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotDLEData)),
|
| 1814 | 1x |
Type = factor( |
| 1815 | 1x |
c( |
| 1816 | 1x |
rep( |
| 1817 | 1x |
"Estimate", |
| 1818 | 1x |
nrow(plotDLEData) |
| 1819 |
), |
|
| 1820 | 1x |
rep( |
| 1821 | 1x |
"95% Credible Interval", |
| 1822 | 1x |
nrow(plotDLEData) * 2 |
| 1823 |
) |
|
| 1824 |
), |
|
| 1825 | 1x |
levels = c( |
| 1826 | 1x |
"Estimate", |
| 1827 | 1x |
"95% Credible Interval" |
| 1828 |
) |
|
| 1829 |
) |
|
| 1830 |
) |
|
| 1831 |
) |
|
| 1832 | ||
| 1833 | 1x |
ret1 <- gdata %>% |
| 1834 | 1x |
ggplot() + |
| 1835 | 1x |
geom_line( |
| 1836 | 1x |
aes( |
| 1837 | 1x |
x = x, |
| 1838 | 1x |
y = y, |
| 1839 | 1x |
group = group, |
| 1840 | 1x |
linetype = Type |
| 1841 |
), |
|
| 1842 | 1x |
colour = I("red"),
|
| 1843 |
) + |
|
| 1844 | 1x |
labs( |
| 1845 | 1x |
x = "Dose Levels", |
| 1846 | 1x |
y = "Probability of DLE [%]" |
| 1847 |
) + |
|
| 1848 | 1x |
coord_cartesian(ylim = c(0, 100)) + |
| 1849 | 1x |
scale_linetype_manual( |
| 1850 | 1x |
breaks = c( |
| 1851 | 1x |
"Estimate", |
| 1852 | 1x |
"95% Credible Interval" |
| 1853 |
), |
|
| 1854 | 1x |
values = c(1, 2), |
| 1855 | 1x |
guide = ifelse(showLegend, "legend", "none") |
| 1856 |
) |
|
| 1857 |
## only look at these dose levels for the plot: |
|
| 1858 | ||
| 1859 | 1x |
xLevels <- if (extrapolate) {
|
| 1860 | 1x |
seq_along(data@doseGrid) |
| 1861 |
} else {
|
|
| 1862 | ! |
1:max(data@xLevel) |
| 1863 |
} |
|
| 1864 | ||
| 1865 |
## get the plot data for the efficacy |
|
| 1866 | 1x |
functionSamples <- matrix( |
| 1867 | 1x |
nrow = size(Effsamples), |
| 1868 | 1x |
ncol = length(xLevels) |
| 1869 |
) |
|
| 1870 |
## evaluate the efficacy for all samples |
|
| 1871 | 1x |
for (i in seq_along(xLevels)) {
|
| 1872 |
## Now we want to evaluate for the following dose |
|
| 1873 | 12x |
functionSamples[, i] <- efficacy( |
| 1874 | 12x |
dose = data@doseGrid[xLevels[i]], |
| 1875 | 12x |
model = Effmodel, |
| 1876 | 12x |
samples = Effsamples |
| 1877 |
) |
|
| 1878 |
} |
|
| 1879 |
## extract mean curve |
|
| 1880 | 1x |
meanCurve <- colMeans(functionSamples) |
| 1881 | ||
| 1882 |
## extract quantiles |
|
| 1883 | 1x |
quantiles <- c(0.025, 0.975) |
| 1884 | 1x |
quantCurve <- apply(functionSamples, 2L, quantile, prob = quantiles) |
| 1885 | ||
| 1886 |
## now create the data frame |
|
| 1887 | 1x |
plotEffData <- data.frame( |
| 1888 | 1x |
dose = data@doseGrid[xLevels], |
| 1889 | 1x |
mean = meanCurve, |
| 1890 | 1x |
lower = quantCurve[1, ], |
| 1891 | 1x |
upper = quantCurve[2, ] |
| 1892 |
) |
|
| 1893 |
## make the second plot |
|
| 1894 | 1x |
ggdata <- with( |
| 1895 | 1x |
plotEffData, |
| 1896 | 1x |
data.frame( |
| 1897 | 1x |
x = rep(dose, 3), |
| 1898 | 1x |
y = c(mean, lower, upper), |
| 1899 | 1x |
group = rep(c("mean", "lower", "upper"), each = nrow(plotEffData)),
|
| 1900 | 1x |
Type = factor( |
| 1901 | 1x |
c( |
| 1902 | 1x |
rep( |
| 1903 | 1x |
"Estimate", |
| 1904 | 1x |
nrow(plotEffData) |
| 1905 |
), |
|
| 1906 | 1x |
rep( |
| 1907 | 1x |
"95% Credible Interval", |
| 1908 | 1x |
nrow(plotEffData) * 2 |
| 1909 |
) |
|
| 1910 |
), |
|
| 1911 | 1x |
levels = c( |
| 1912 | 1x |
"Estimate", |
| 1913 | 1x |
"95% Credible Interval" |
| 1914 |
) |
|
| 1915 |
) |
|
| 1916 |
) |
|
| 1917 |
) |
|
| 1918 | ||
| 1919 | 1x |
plot2 <- ggdata %>% |
| 1920 | 1x |
ggplot() + |
| 1921 | 1x |
geom_line( |
| 1922 | 1x |
aes( |
| 1923 | 1x |
x = x, |
| 1924 | 1x |
y = y, |
| 1925 | 1x |
group = group, |
| 1926 | 1x |
linetype = Type |
| 1927 |
), |
|
| 1928 | 1x |
colour = I("blue"),
|
| 1929 |
) + |
|
| 1930 | 1x |
labs( |
| 1931 | 1x |
x = "Dose level", |
| 1932 | 1x |
y = "Expected Efficacy" |
| 1933 |
) + |
|
| 1934 | 1x |
scale_linetype_manual( |
| 1935 | 1x |
breaks = c( |
| 1936 | 1x |
"Estimate", |
| 1937 | 1x |
"95% Credible Interval" |
| 1938 |
), |
|
| 1939 | 1x |
values = c(1, 2), |
| 1940 | 1x |
guide = ifelse(showLegend, "legend", "none") |
| 1941 |
) |
|
| 1942 | ||
| 1943 |
## arrange both plots side by side |
|
| 1944 | 1x |
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 | 6x |
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 | 6000x |
npiece <- length(lambda) |
| 2099 | 6000x |
h <- seq(from = 0L, to = Tmax, length = npiece + 1) |
| 2100 | ||
| 2101 |
# Length of each time interval; |
|
| 2102 | 6000x |
sT <- rep(0, npiece) |
| 2103 | ||
| 2104 | 6000x |
for (i in 1:npiece) {
|
| 2105 | 60000x |
sT[i] <- h[i + 1] - h[i] |
| 2106 |
} |
|
| 2107 | ||
| 2108 |
# calculate the exponential part of the distribution: |
|
| 2109 | 6000x |
s_ij <- function(t, j) {
|
| 2110 | 3600000x |
if (t > h[j]) {
|
| 2111 | 3e+06x |
min(t - h[j], h[j + 1] - h[j]) |
| 2112 |
} else {
|
|
| 2113 | 6e+05x |
0 |
| 2114 |
} |
|
| 2115 |
} |
|
| 2116 | ||
| 2117 |
# The cumulative hazard function |
|
| 2118 | 6000x |
expNmu <- function(t) {
|
| 2119 | 360000x |
ret <- 1 |
| 2120 | 360000x |
for (j in 1:npiece) {
|
| 2121 | 3600000x |
ret <- ret * exp(-lambda[j] * s_ij(t, j)) |
| 2122 |
} |
|
| 2123 | 360000x |
ret |
| 2124 |
} |
|
| 2125 | ||
| 2126 |
# CDF of the piecewise exponential |
|
| 2127 | 6000x |
piece_exp_cdf <- function(x) {
|
| 2128 | 120000x |
1 - expNmu(x) |
| 2129 |
} |
|
| 2130 | ||
| 2131 | 6000x |
DLTFreeS <- function(x) {
|
| 2132 | 120000x |
(expNmu(x) - expNmu(Tmax)) / piece_exp_cdf(Tmax) |
| 2133 |
} |
|
| 2134 | ||
| 2135 | 6000x |
pDLT <- rep(0, npiece + 1) |
| 2136 | ||
| 2137 | 6000x |
for (i in 1:(npiece)) {
|
| 2138 | 60000x |
pDLT[i] <- DLTFreeS(h[i]) - DLTFreeS(h[i + 1]) |
| 2139 |
} |
|
| 2140 | ||
| 2141 | 6000x |
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 | 6x |
assert_probability_range(quantiles) |
| 2169 | 3x |
assert_logical(hazard) |
| 2170 |
## Plot points |
|
| 2171 | 2x |
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 | 2x |
PEMSamples <- matrix( |
| 2176 | 2x |
nrow = size(object), |
| 2177 | 2x |
ncol = length(points) |
| 2178 |
) |
|
| 2179 | ||
| 2180 | 2x |
i_max <- max(seq_along(points)) |
| 2181 |
## evaluate the probs, for all samples. |
|
| 2182 | ||
| 2183 |
# The PEM |
|
| 2184 | 2x |
if (hazard == FALSE) {
|
| 2185 | 2x |
PEMSamples <- t(apply(object@data$lambda, 1, function(x) {
|
| 2186 | 6000x |
DLTLikelihood(x, data@Tmax) |
| 2187 |
})) |
|
| 2188 | ! |
} else if (hazard == TRUE) {
|
| 2189 | ! |
for (i in seq_along(points)) {
|
| 2190 | ! |
if (i == i_max) {
|
| 2191 | ! |
PEMSamples[, i_max] <- object@data$lambda[, model@npiece] |
| 2192 |
} else {
|
|
| 2193 | ! |
PEMSamples[, i] <- object@data$lambda[, i] |
| 2194 |
} |
|
| 2195 |
} |
|
| 2196 |
} |
|
| 2197 | ||
| 2198 |
## extract middle curve |
|
| 2199 | 2x |
middleCurve <- apply(PEMSamples, 2L, FUN = middle) |
| 2200 | ||
| 2201 |
## extract quantiles |
|
| 2202 | 2x |
quantCurve <- apply(PEMSamples, 2L, quantile, prob = quantiles) |
| 2203 | ||
| 2204 |
## now create the data frame |
|
| 2205 | 2x |
data.frame( |
| 2206 | 2x |
time = points, |
| 2207 | 2x |
middle = middleCurve, |
| 2208 | 2x |
lower = quantCurve[1, ], |
| 2209 | 2x |
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 | 3x |
assert_logical(showLegend) |
| 2242 | 2x |
assert_logical(hazard) |
| 2243 | ||
| 2244 |
## call the superclass method, to get the toxicity plot |
|
| 2245 | 1x |
plot1 <- callNextMethod(x, y, data, showLegend = showLegend, ...) |
| 2246 | ||
| 2247 |
## get the fit |
|
| 2248 | 1x |
fitData <- fitPEM( |
| 2249 | 1x |
x, |
| 2250 | 1x |
model = y, |
| 2251 | 1x |
data = data, |
| 2252 | 1x |
quantiles = c(0.025, 0.975), |
| 2253 | 1x |
middle = mean, |
| 2254 | 1x |
hazard = hazard |
| 2255 |
) |
|
| 2256 | ||
| 2257 |
## make the plot |
|
| 2258 | 1x |
Tpoints <- seq(0, data@Tmax, length = y@npiece + 1) |
| 2259 | 1x |
plotData <- |
| 2260 | 1x |
with( |
| 2261 | 1x |
fitData, |
| 2262 | 1x |
data.frame( |
| 2263 | 1x |
x = rep(Tpoints, 3), |
| 2264 | 1x |
y = c(middle, lower, upper) * 100, |
| 2265 | 1x |
group = rep(c("mean", "lower", "upper"), each = nrow(fitData)),
|
| 2266 | 1x |
Type = factor( |
| 2267 | 1x |
c( |
| 2268 | 1x |
rep( |
| 2269 | 1x |
"Estimate", |
| 2270 | 1x |
nrow(fitData) |
| 2271 |
), |
|
| 2272 | 1x |
rep( |
| 2273 | 1x |
"95% Credible Interval", |
| 2274 | 1x |
nrow(fitData) * 2 |
| 2275 |
) |
|
| 2276 |
), |
|
| 2277 | 1x |
levels = c( |
| 2278 | 1x |
"Estimate", |
| 2279 | 1x |
"95% Credible Interval" |
| 2280 |
) |
|
| 2281 |
) |
|
| 2282 |
) |
|
| 2283 |
) |
|
| 2284 | 1x |
plot2 <- plotData %>% |
| 2285 | 1x |
ggplot() + |
| 2286 | 1x |
geom_step( |
| 2287 | 1x |
aes( |
| 2288 | 1x |
x = x, |
| 2289 | 1x |
y = y, |
| 2290 | 1x |
group = group, |
| 2291 | 1x |
linetype = Type |
| 2292 |
), |
|
| 2293 | 1x |
colour = I("blue")
|
| 2294 |
) + |
|
| 2295 | 1x |
labs( |
| 2296 | 1x |
x = "Time", |
| 2297 | 1x |
y = if (hazard) "Hazard rate*100" else "Probability of DLT [%]" |
| 2298 |
) + |
|
| 2299 | 1x |
coord_cartesian( |
| 2300 | 1x |
ylim = if (hazard) range(plotData$y) else c(0, 100) |
| 2301 |
) |
|
| 2302 | ||
| 2303 | 1x |
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 Model-class.R |
|
| 2 |
#' @include Samples-class.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
# doseFunction ---- |
|
| 6 | ||
| 7 |
## generic ---- |
|
| 8 | ||
| 9 |
#' Getting the Dose Function for a Given Model Type |
|
| 10 |
#' |
|
| 11 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 12 |
#' |
|
| 13 |
#' A function that returns a [dose()] method that computes the dose reaching a |
|
| 14 |
#' specific target value of a given independent variable, based on the model |
|
| 15 |
#' specific parameters. |
|
| 16 |
#' |
|
| 17 |
#' @param model (`GeneralModel` or `ModelPseudo`)\cr the model. |
|
| 18 |
#' @param ... model specific parameters. |
|
| 19 |
#' |
|
| 20 |
#' @return A [dose()] method that computes doses. |
|
| 21 |
#' |
|
| 22 |
#' @seealso [dose()], [probFunction()]. |
|
| 23 |
#' |
|
| 24 |
#' @export |
|
| 25 |
#' @example examples/Model-method-doseFunction.R |
|
| 26 |
#' |
|
| 27 |
setGeneric( |
|
| 28 |
name = "doseFunction", |
|
| 29 |
def = function(model, ...) {
|
|
| 30 | 17x |
standardGeneric("doseFunction")
|
| 31 |
}, |
|
| 32 |
valueClass = "function" |
|
| 33 |
) |
|
| 34 | ||
| 35 |
## GeneralModel ---- |
|
| 36 | ||
| 37 |
#' @describeIn doseFunction |
|
| 38 |
#' |
|
| 39 |
#' @aliases doseFunction-GeneralModel |
|
| 40 |
#' @export |
|
| 41 |
#' |
|
| 42 |
setMethod( |
|
| 43 |
f = "doseFunction", |
|
| 44 |
signature = "GeneralModel", |
|
| 45 |
definition = function(model, ...) {
|
|
| 46 | 7x |
model_params <- list(...) |
| 47 | 7x |
assert_subset(names(model_params), model@sample, empty.ok = FALSE) |
| 48 | ||
| 49 | 5x |
samples <- Samples( |
| 50 | 5x |
data = model_params, |
| 51 | 5x |
options = McmcOptions(samples = NROW(model_params[[1]])) |
| 52 |
) |
|
| 53 | 5x |
function(x, ...) {
|
| 54 | 2x |
dose(x = x, model = model, samples = samples, ...) |
| 55 |
} |
|
| 56 |
} |
|
| 57 |
) |
|
| 58 | ||
| 59 |
## ModelPseudo ---- |
|
| 60 | ||
| 61 |
#' @describeIn doseFunction |
|
| 62 |
#' |
|
| 63 |
#' @aliases doseFunction-ModelPseudo |
|
| 64 |
#' @export |
|
| 65 |
#' |
|
| 66 |
setMethod( |
|
| 67 |
f = "doseFunction", |
|
| 68 |
signature = "ModelPseudo", |
|
| 69 |
definition = function(model, ...) {
|
|
| 70 | 2x |
model_params <- list(...) |
| 71 | 2x |
assert_character( |
| 72 | 2x |
names(model_params), |
| 73 | 2x |
len = length(model_params), |
| 74 | 2x |
any.missing = FALSE, |
| 75 | 2x |
unique = TRUE |
| 76 |
) |
|
| 77 | ||
| 78 | 1x |
samples <- Samples( |
| 79 | 1x |
data = model_params, |
| 80 | 1x |
options = McmcOptions(samples = length(model_params[[1]])) |
| 81 |
) |
|
| 82 | 1x |
function(x) {
|
| 83 | ! |
dose(x = x, model = model, samples = samples) |
| 84 |
} |
|
| 85 |
} |
|
| 86 |
) |
|
| 87 | ||
| 88 |
## LogisticLogNormalOrdinal ---- |
|
| 89 | ||
| 90 |
#' @describeIn doseFunction |
|
| 91 |
#' |
|
| 92 |
#' @param grade (`integer`)\cr the toxicity grade for which the dose function is |
|
| 93 |
#' required |
|
| 94 |
#' |
|
| 95 |
#' @aliases doseFunction-LogisticLogNormalOrdinal |
|
| 96 |
#' @example examples/Model-method-doseFunctionLogisticLogNormalOrdinal.R |
|
| 97 |
#' @export |
|
| 98 |
setMethod( |
|
| 99 |
f = "doseFunction", |
|
| 100 |
signature = "LogisticLogNormalOrdinal", |
|
| 101 |
definition = function(model, grade, ...) {
|
|
| 102 | 8x |
model_params <- list(...) |
| 103 | 8x |
assert_character( |
| 104 | 8x |
names(model_params), |
| 105 | 8x |
len = length(model_params), |
| 106 | 8x |
any.missing = FALSE, |
| 107 | 8x |
unique = TRUE |
| 108 |
) |
|
| 109 | 6x |
assert_integer(grade, lower = 1, len = 1) |
| 110 | 4x |
coll <- makeAssertCollection() |
| 111 | 4x |
if (!(paste0("alpha", grade) %in% names(model_params))) {
|
| 112 | 2x |
coll$push( |
| 113 | 2x |
paste0( |
| 114 | 2x |
"Since grade = ", |
| 115 | 2x |
grade, |
| 116 | 2x |
", a parameter named 'alpha", |
| 117 | 2x |
grade, |
| 118 | 2x |
"' must appear the call" |
| 119 |
) |
|
| 120 |
) |
|
| 121 |
} |
|
| 122 | 4x |
reportAssertions(coll) |
| 123 |
# Create dummy intercept columns if necessary |
|
| 124 | 2x |
for (g in seq_along(grade)) {
|
| 125 | 2x |
if (!(paste0("alpha", g) %in% names(model_params))) {
|
| 126 | 1x |
model_params[[paste0("alpha", g)]] <- rep(0, length(model_params[[1]]))
|
| 127 |
} |
|
| 128 |
} |
|
| 129 | ||
| 130 | 2x |
samples <- Samples( |
| 131 | 2x |
data = model_params, |
| 132 | 2x |
options = McmcOptions(samples = length(model_params[[1]])) |
| 133 |
) |
|
| 134 | 2x |
function(x) {
|
| 135 | 38x |
dose(x = x, model = model, samples = samples, grade = grade) |
| 136 |
} |
|
| 137 |
} |
|
| 138 |
) |
|
| 139 | ||
| 140 |
# probFunction ---- |
|
| 141 | ||
| 142 |
## generic ---- |
|
| 143 | ||
| 144 |
#' Getting the Prob Function for a Given Model Type |
|
| 145 |
#' |
|
| 146 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 147 |
#' |
|
| 148 |
#' A function that returns a [prob()] function that computes the toxicity |
|
| 149 |
#' probabilities for a given dose level, based on the model specific parameters. |
|
| 150 |
#' |
|
| 151 |
#' @param model (`GeneralModel` or `ModelTox`)\cr the model. |
|
| 152 |
#' @param ... model specific parameters. |
|
| 153 |
#' |
|
| 154 |
#' @return A [prob()] function that computes toxicity probabilities. |
|
| 155 |
#' |
|
| 156 |
#' @seealso [prob()], [doseFunction()]. |
|
| 157 |
#' |
|
| 158 |
#' @export |
|
| 159 |
#' @example examples/Model-method-probFunction.R |
|
| 160 |
#' |
|
| 161 |
setGeneric( |
|
| 162 |
name = "probFunction", |
|
| 163 |
def = function(model, ...) {
|
|
| 164 | 41x |
standardGeneric("probFunction")
|
| 165 |
}, |
|
| 166 |
valueClass = "function" |
|
| 167 |
) |
|
| 168 | ||
| 169 |
## GeneralModel ---- |
|
| 170 | ||
| 171 |
#' @describeIn probFunction |
|
| 172 |
#' |
|
| 173 |
#' @aliases probFunction-GeneralModel |
|
| 174 |
#' @export |
|
| 175 |
#' |
|
| 176 |
setMethod( |
|
| 177 |
f = "probFunction", |
|
| 178 |
signature = "GeneralModel", |
|
| 179 |
definition = function(model, ...) {
|
|
| 180 | 21x |
model_params <- list(...) |
| 181 | 21x |
assert_subset(names(model_params), model@sample, empty.ok = FALSE) |
| 182 | ||
| 183 | 19x |
samples <- Samples( |
| 184 | 19x |
data = model_params, |
| 185 | 19x |
options = McmcOptions(samples = NROW(model_params[[1]])) |
| 186 |
) |
|
| 187 | 19x |
function(dose, ...) {
|
| 188 | 244x |
prob(dose = dose, model = model, samples = samples, ...) |
| 189 |
} |
|
| 190 |
} |
|
| 191 |
) |
|
| 192 | ||
| 193 |
## ModelTox ---- |
|
| 194 | ||
| 195 |
#' @describeIn probFunction |
|
| 196 |
#' |
|
| 197 |
#' @aliases probFunction-ModelTox |
|
| 198 |
#' @export |
|
| 199 |
#' |
|
| 200 |
setMethod( |
|
| 201 |
f = "probFunction", |
|
| 202 |
signature = "ModelTox", |
|
| 203 |
definition = function(model, ...) {
|
|
| 204 | 18x |
model_params <- list(...) |
| 205 | 18x |
assert_character( |
| 206 | 18x |
names(model_params), |
| 207 | 18x |
len = length(model_params), |
| 208 | 18x |
any.missing = FALSE, |
| 209 | 18x |
unique = TRUE |
| 210 |
) |
|
| 211 | ||
| 212 | 17x |
samples <- Samples( |
| 213 | 17x |
data = model_params, |
| 214 | 17x |
options = McmcOptions(samples = length(model_params[[1]])) |
| 215 |
) |
|
| 216 | 17x |
function(dose) {
|
| 217 | 121x |
prob(dose = dose, model = model, samples = samples) |
| 218 |
} |
|
| 219 |
} |
|
| 220 |
) |
|
| 221 | ||
| 222 |
## LogisticLogNormalOrdinal ---- |
|
| 223 | ||
| 224 |
#' @describeIn probFunction |
|
| 225 |
#' |
|
| 226 |
#' @param grade (`integer`)\cr the toxicity grade for which the dose function is |
|
| 227 |
#' required |
|
| 228 |
#' |
|
| 229 |
#' @aliases probFunction-LogisticLogNormalOrdinal |
|
| 230 |
#' @example examples/Model-method-probFunctionLogisticLogNormalOrdinal.R |
|
| 231 |
#' @export |
|
| 232 |
setMethod( |
|
| 233 |
f = "probFunction", |
|
| 234 |
signature = "LogisticLogNormalOrdinal", |
|
| 235 |
definition = function(model, grade, ...) {
|
|
| 236 | 2x |
model_params <- list(...) |
| 237 | 2x |
assert_character( |
| 238 | 2x |
names(model_params), |
| 239 | 2x |
len = length(model_params), |
| 240 | 2x |
any.missing = FALSE, |
| 241 | 2x |
unique = TRUE |
| 242 |
) |
|
| 243 | 2x |
assert_integer(grade, lower = 1, len = 1) |
| 244 | 2x |
coll <- makeAssertCollection() |
| 245 | 2x |
if (!(paste0("alpha", grade) %in% names(model_params))) {
|
| 246 | ! |
coll$push( |
| 247 | ! |
paste0( |
| 248 | ! |
"Since grade = ", |
| 249 | ! |
grade, |
| 250 | ! |
", a parameter named 'alpha", |
| 251 | ! |
grade, |
| 252 | ! |
"' must appear the call" |
| 253 |
) |
|
| 254 |
) |
|
| 255 |
} |
|
| 256 | 2x |
reportAssertions(coll) |
| 257 |
# Create dummy intercept columns if necessary |
|
| 258 | 2x |
for (g in seq_along(grade)) {
|
| 259 | 2x |
if (!(paste0("alpha", g) %in% names(model_params))) {
|
| 260 | 1x |
model_params[[paste0("alpha", g)]] <- rep(0, length(model_params[[1]]))
|
| 261 |
} |
|
| 262 |
} |
|
| 263 | ||
| 264 | 2x |
samples <- Samples( |
| 265 | 2x |
data = model_params, |
| 266 | 2x |
options = McmcOptions(samples = length(model_params[[1]])) |
| 267 |
) |
|
| 268 | 2x |
function(dose) {
|
| 269 | 20x |
prob(dose = dose, model = model, samples = samples, grade = grade) |
| 270 |
} |
|
| 271 |
} |
|
| 272 |
) |
|
| 273 | ||
| 274 | ||
| 275 |
# efficacyFunction ---- |
|
| 276 | ||
| 277 |
## generic ---- |
|
| 278 | ||
| 279 |
#' Getting the Efficacy Function for a Given Model Type |
|
| 280 |
#' |
|
| 281 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 282 |
#' |
|
| 283 |
#' A function that returns an [efficacy()] function that computes expected |
|
| 284 |
#' efficacy for a given dose level, based on the model specific parameters. |
|
| 285 |
#' |
|
| 286 |
#' @param model (`ModelEff`)\cr the model. |
|
| 287 |
#' @param ... model specific parameters. |
|
| 288 |
#' |
|
| 289 |
#' @return A [efficacy()] function that computes expected efficacy. |
|
| 290 |
#' |
|
| 291 |
#' @seealso [efficacy()]. |
|
| 292 |
#' |
|
| 293 |
#' @export |
|
| 294 |
#' @example examples/Model-method-efficacyFunction.R |
|
| 295 |
#' |
|
| 296 |
setGeneric( |
|
| 297 |
name = "efficacyFunction", |
|
| 298 |
def = function(model, ...) {
|
|
| 299 | 8x |
standardGeneric("efficacyFunction")
|
| 300 |
}, |
|
| 301 |
valueClass = "function" |
|
| 302 |
) |
|
| 303 | ||
| 304 |
## ModelEff ---- |
|
| 305 | ||
| 306 |
#' @describeIn efficacyFunction |
|
| 307 |
#' |
|
| 308 |
#' @aliases efficacyFunction-ModelEff |
|
| 309 |
#' @export |
|
| 310 |
#' |
|
| 311 |
setMethod( |
|
| 312 |
f = "efficacyFunction", |
|
| 313 |
signature = "ModelEff", |
|
| 314 |
definition = function(model, ...) {
|
|
| 315 | 8x |
model_params <- list(...) |
| 316 | 8x |
assert_character( |
| 317 | 8x |
names(model_params), |
| 318 | 8x |
len = length(model_params), |
| 319 | 8x |
any.missing = FALSE, |
| 320 | 8x |
unique = TRUE |
| 321 |
) |
|
| 322 | ||
| 323 | 7x |
samples <- Samples( |
| 324 | 7x |
data = model_params, |
| 325 | 7x |
options = McmcOptions(samples = NROW(model_params[[1]])) |
| 326 |
) |
|
| 327 | 7x |
function(dose) {
|
| 328 | 34x |
efficacy(dose = dose, model = model, samples = samples) |
| 329 |
} |
|
| 330 |
} |
|
| 331 |
) |
|
| 332 | ||
| 333 |
# dose ---- |
|
| 334 | ||
| 335 |
## generic ---- |
|
| 336 | ||
| 337 |
#' Computing the Doses for a given independent variable, Model and Samples |
|
| 338 |
#' |
|
| 339 |
#' @description `r lifecycle::badge("stable")`
|
|
| 340 |
#' |
|
| 341 |
#' A function that computes the dose reaching a specific target value of a |
|
| 342 |
#' given variable that dose depends on. The meaning of this variable depends |
|
| 343 |
#' on the type of the model. For instance, for single agent dose escalation |
|
| 344 |
#' model or pseudo DLE (dose-limiting events)/toxicity model, this variable |
|
| 345 |
#' represents the a probability of the occurrence of a DLE. For efficacy models, |
|
| 346 |
#' it represents expected efficacy. |
|
| 347 |
#' The doses are computed based on the samples of the model parameters (samples). |
|
| 348 |
#' |
|
| 349 |
#' @details The `dose()` function computes the doses corresponding to a value of |
|
| 350 |
#' a given independent variable, using samples of the model parameter(s). |
|
| 351 |
#' If you work with multivariate model parameters, then assume that your model |
|
| 352 |
#' specific `dose()` method receives a samples matrix where the rows |
|
| 353 |
#' correspond to the sampling index, i.e. the layout is then |
|
| 354 |
#' `nSamples x dimParameter`. |
|
| 355 |
#' |
|
| 356 |
#' @note The [dose()] and [prob()] methods are the inverse of each other, for |
|
| 357 |
#' all [dose()] methods for which its first argument, i.e. a given independent |
|
| 358 |
#' variable that dose depends on, represents toxicity probability. |
|
| 359 |
#' |
|
| 360 |
#' @param x (`proportion` or `numeric`)\cr a value of an independent variable |
|
| 361 |
#' on which dose depends. |
|
| 362 |
#' The following recycling rule applies when `samples` is not missing: vectors |
|
| 363 |
#' of size 1 will be recycled to the size of the sample |
|
| 364 |
#' (i.e. `size(samples)`). Otherwise, `x` must have the same size |
|
| 365 |
#' as the sample. |
|
| 366 |
#' @param model (`GeneralModel` or `ModelPseudo`)\cr the model. |
|
| 367 |
#' @param samples (`Samples`)\cr the samples of model's parameters that will be |
|
| 368 |
#' used to compute the resulting doses. Can also be missing for some models. |
|
| 369 |
#' @param ... model specific parameters when `samples` are not used. |
|
| 370 |
#' |
|
| 371 |
#' @return A `number` or `numeric` vector with the doses. |
|
| 372 |
#' If non-scalar `samples` were used, then every element in the returned vector |
|
| 373 |
#' corresponds to one element of a sample. Hence, in this case, the output |
|
| 374 |
#' vector is of the same length as the sample vector. If scalar `samples` were |
|
| 375 |
#' used or no `samples` were used, e.g. for pseudo DLE/toxicity `model`, |
|
| 376 |
#' then the output is of the same length as the length of the `prob`. |
|
| 377 |
#' |
|
| 378 |
#' @seealso [doseFunction()], [prob()], [efficacy()]. |
|
| 379 |
#' |
|
| 380 |
#' @export |
|
| 381 |
#' @example examples/Model-method-dose.R |
|
| 382 |
#' |
|
| 383 |
setGeneric( |
|
| 384 |
name = "dose", |
|
| 385 |
def = function(x, model, samples, ...) {
|
|
| 386 | 3967x |
standardGeneric("dose")
|
| 387 |
}, |
|
| 388 |
valueClass = "numeric" |
|
| 389 |
) |
|
| 390 | ||
| 391 |
## LogisticNormal ---- |
|
| 392 | ||
| 393 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 394 |
#' probability of the occurrence of a DLE (`x`). |
|
| 395 |
#' |
|
| 396 |
#' @aliases dose-LogisticNormal |
|
| 397 |
#' @export |
|
| 398 |
#' |
|
| 399 |
setMethod( |
|
| 400 |
f = "dose", |
|
| 401 |
signature = signature( |
|
| 402 |
x = "numeric", |
|
| 403 |
model = "LogisticNormal", |
|
| 404 |
samples = "Samples" |
|
| 405 |
), |
|
| 406 |
definition = function(x, model, samples) {
|
|
| 407 | 12x |
assert_probabilities(x) |
| 408 | 10x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 409 | 10x |
assert_length(x, len = size(samples)) |
| 410 | ||
| 411 | 9x |
alpha0 <- samples@data$alpha0 |
| 412 | 9x |
alpha1 <- samples@data$alpha1 |
| 413 | 9x |
ref_dose <- as.numeric(model@ref_dose) |
| 414 | 9x |
exp((logit(x) - alpha0) / alpha1) * ref_dose |
| 415 |
} |
|
| 416 |
) |
|
| 417 | ||
| 418 |
## LogisticLogNormal ---- |
|
| 419 | ||
| 420 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 421 |
#' probability of the occurrence of a DLE (`x`). |
|
| 422 |
#' |
|
| 423 |
#' @aliases dose-LogisticLogNormal |
|
| 424 |
#' @export |
|
| 425 |
#' |
|
| 426 |
setMethod( |
|
| 427 |
f = "dose", |
|
| 428 |
signature = signature( |
|
| 429 |
x = "numeric", |
|
| 430 |
model = "LogisticLogNormal", |
|
| 431 |
samples = "Samples" |
|
| 432 |
), |
|
| 433 |
definition = function(x, model, samples) {
|
|
| 434 | 1539x |
assert_probabilities(x) |
| 435 | 1537x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 436 | 1537x |
assert_length(x, len = size(samples)) |
| 437 | ||
| 438 | 1536x |
alpha0 <- samples@data$alpha0 |
| 439 | 1536x |
alpha1 <- samples@data$alpha1 |
| 440 | 1536x |
ref_dose <- as.numeric(model@ref_dose) |
| 441 | 1536x |
exp((logit(x) - alpha0) / alpha1) * ref_dose |
| 442 |
} |
|
| 443 |
) |
|
| 444 | ||
| 445 |
## LogisticLogNormalOrdinal ---- |
|
| 446 | ||
| 447 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 448 |
#' probability of the occurrence of a DLE (`x`). |
|
| 449 |
#' |
|
| 450 |
#' In the case of a `LogisticLogNormalOrdinal` model, `dose` returns only the |
|
| 451 |
#' probability of toxicity at the given grade or higher |
|
| 452 |
#' |
|
| 453 |
#' @param grade (`integer`)\cr The toxicity grade for which probabilities are required |
|
| 454 |
#' |
|
| 455 |
#' @aliases dose-LogisticLogNormalOrdinal |
|
| 456 |
#' @example examples/Model-method-doseLogisticLogNormalOrdinal.R |
|
| 457 |
#' @export |
|
| 458 |
#' |
|
| 459 |
setMethod( |
|
| 460 |
f = "dose", |
|
| 461 |
signature = signature( |
|
| 462 |
x = "numeric", |
|
| 463 |
model = "LogisticLogNormalOrdinal", |
|
| 464 |
samples = "Samples" |
|
| 465 |
), |
|
| 466 |
definition = function(x, model, samples, grade) {
|
|
| 467 | 97x |
assert_probabilities(x) |
| 468 | 95x |
assert_length(x, len = size(samples)) |
| 469 | 95x |
assert_integer( |
| 470 | 95x |
grade, |
| 471 | 95x |
len = 1, |
| 472 | 95x |
lower = 1, |
| 473 | 95x |
upper = (length(names(samples@data)) - 1) |
| 474 |
) |
|
| 475 | 92x |
a <- paste0("alpha", grade)
|
| 476 | 92x |
assert_subset(c(a, "beta"), names(samples)) |
| 477 | ||
| 478 | 92x |
alpha <- samples@data[[a]] |
| 479 | 92x |
beta <- samples@data$beta |
| 480 | 92x |
ref_dose <- as.numeric(model@ref_dose) |
| 481 | 92x |
exp((logit(x) - alpha) / beta) * ref_dose |
| 482 |
} |
|
| 483 |
) |
|
| 484 | ||
| 485 |
## LogisticLogNormalSub ---- |
|
| 486 | ||
| 487 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 488 |
#' probability of the occurrence of a DLE (`x`). |
|
| 489 |
#' |
|
| 490 |
#' @aliases dose-LogisticLogNormalSub |
|
| 491 |
#' @export |
|
| 492 |
#' |
|
| 493 |
setMethod( |
|
| 494 |
f = "dose", |
|
| 495 |
signature = signature( |
|
| 496 |
x = "numeric", |
|
| 497 |
model = "LogisticLogNormalSub", |
|
| 498 |
samples = "Samples" |
|
| 499 |
), |
|
| 500 |
definition = function(x, model, samples) {
|
|
| 501 | 6x |
assert_probabilities(x) |
| 502 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 503 | 4x |
assert_length(x, len = size(samples)) |
| 504 | ||
| 505 | 3x |
alpha0 <- samples@data$alpha0 |
| 506 | 3x |
alpha1 <- samples@data$alpha1 |
| 507 | 3x |
ref_dose <- model@ref_dose |
| 508 | 3x |
((logit(x) - alpha0) / alpha1) + ref_dose |
| 509 |
} |
|
| 510 |
) |
|
| 511 | ||
| 512 |
## ProbitLogNormal ---- |
|
| 513 | ||
| 514 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 515 |
#' probability of the occurrence of a DLE (`x`). |
|
| 516 |
#' |
|
| 517 |
#' @aliases dose-ProbitLogNormal |
|
| 518 |
#' @export |
|
| 519 |
#' |
|
| 520 |
setMethod( |
|
| 521 |
f = "dose", |
|
| 522 |
signature = signature( |
|
| 523 |
x = "numeric", |
|
| 524 |
model = "ProbitLogNormal", |
|
| 525 |
samples = "Samples" |
|
| 526 |
), |
|
| 527 |
definition = function(x, model, samples) {
|
|
| 528 | 6x |
assert_probabilities(x) |
| 529 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 530 | 4x |
assert_length(x, len = size(samples)) |
| 531 | ||
| 532 | 3x |
alpha0 <- samples@data$alpha0 |
| 533 | 3x |
alpha1 <- samples@data$alpha1 |
| 534 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 535 | 3x |
exp((probit(x) - alpha0) / alpha1) * ref_dose |
| 536 |
} |
|
| 537 |
) |
|
| 538 | ||
| 539 |
## ProbitLogNormalRel ---- |
|
| 540 | ||
| 541 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 542 |
#' probability of the occurrence of a DLE (`x`). |
|
| 543 |
#' |
|
| 544 |
#' @aliases dose-ProbitLogNormalRel |
|
| 545 |
#' @export |
|
| 546 |
#' |
|
| 547 |
setMethod( |
|
| 548 |
f = "dose", |
|
| 549 |
signature = signature( |
|
| 550 |
x = "numeric", |
|
| 551 |
model = "ProbitLogNormalRel", |
|
| 552 |
samples = "Samples" |
|
| 553 |
), |
|
| 554 |
definition = function(x, model, samples) {
|
|
| 555 | 6x |
assert_probabilities(x) |
| 556 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 557 | 4x |
assert_length(x, len = size(samples)) |
| 558 | ||
| 559 | 3x |
alpha0 <- samples@data$alpha0 |
| 560 | 3x |
alpha1 <- samples@data$alpha1 |
| 561 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 562 | 3x |
((probit(x) - alpha0) / alpha1) * ref_dose |
| 563 |
} |
|
| 564 |
) |
|
| 565 | ||
| 566 |
## LogisticLogNormalGrouped ---- |
|
| 567 | ||
| 568 |
#' @describeIn dose method for [`LogisticLogNormalGrouped`] which needs `group` |
|
| 569 |
#' argument in addition. |
|
| 570 |
#' @param group (`character` or `factor`)\cr for [`LogisticLogNormalGrouped`], |
|
| 571 |
#' indicating whether to calculate the dose for the `mono` or for |
|
| 572 |
#' the `combo` arm. |
|
| 573 |
#' @aliases dose-LogisticLogNormalGrouped |
|
| 574 |
#' @export |
|
| 575 |
#' |
|
| 576 |
setMethod( |
|
| 577 |
f = "dose", |
|
| 578 |
signature = signature( |
|
| 579 |
x = "numeric", |
|
| 580 |
model = "LogisticLogNormalGrouped", |
|
| 581 |
samples = "Samples" |
|
| 582 |
), |
|
| 583 |
definition = function(x, model, samples, group) {
|
|
| 584 | 6x |
assert_probabilities(x) |
| 585 | 6x |
assert_subset(c("alpha0", "delta0", "alpha1", "delta1"), names(samples))
|
| 586 | 6x |
assert_length(x, len = size(samples)) |
| 587 | 6x |
assert_multi_class(group, c("character", "factor"))
|
| 588 | 5x |
assert_subset(as.character(group), choices = c("mono", "combo"))
|
| 589 | 5x |
assert_length(group, len = size(samples)) |
| 590 | ||
| 591 | 5x |
alpha0 <- samples@data$alpha0 |
| 592 | 5x |
delta0 <- samples@data$delta0 |
| 593 | 5x |
alpha1 <- samples@data$alpha1 |
| 594 | 5x |
delta1 <- samples@data$delta1 |
| 595 | 5x |
ref_dose <- as.numeric(model@ref_dose) |
| 596 | 5x |
is_combo <- as.integer(group == "combo") |
| 597 | 5x |
exp( |
| 598 | 5x |
(logit(x) - (alpha0 + is_combo * delta0)) / (alpha1 + is_combo * delta1) |
| 599 |
) * |
|
| 600 | 5x |
ref_dose |
| 601 |
} |
|
| 602 |
) |
|
| 603 | ||
| 604 |
## LogisticKadane ---- |
|
| 605 | ||
| 606 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 607 |
#' probability of the occurrence of a DLE (`x`). |
|
| 608 |
#' |
|
| 609 |
#' @aliases dose-LogisticKadane |
|
| 610 |
#' @export |
|
| 611 |
#' |
|
| 612 |
setMethod( |
|
| 613 |
f = "dose", |
|
| 614 |
signature = signature( |
|
| 615 |
x = "numeric", |
|
| 616 |
model = "LogisticKadane", |
|
| 617 |
samples = "Samples" |
|
| 618 |
), |
|
| 619 |
definition = function(x, model, samples) {
|
|
| 620 | 10x |
assert_probabilities(x) |
| 621 | 8x |
assert_subset(c("rho0", "gamma"), names(samples))
|
| 622 | 8x |
assert_length(x, len = size(samples)) |
| 623 | ||
| 624 | 7x |
rho0 <- samples@data$rho0 |
| 625 | 7x |
gamma <- samples@data$gamma |
| 626 | 7x |
theta <- model@theta |
| 627 | 7x |
xmin <- model@xmin |
| 628 | 7x |
num <- gamma * (logit(x) - logit(rho0)) + xmin * (logit(theta) - logit(x)) |
| 629 | 7x |
num / (logit(theta) - logit(rho0)) |
| 630 |
} |
|
| 631 |
) |
|
| 632 | ||
| 633 |
## LogisticKadaneBetaGamma ---- |
|
| 634 | ||
| 635 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 636 |
#' probability of the occurrence of a DLE (`x`). |
|
| 637 |
#' |
|
| 638 |
#' @aliases dose-LogisticKadaneBetaGamma |
|
| 639 |
#' @export |
|
| 640 |
#' |
|
| 641 |
setMethod( |
|
| 642 |
f = "dose", |
|
| 643 |
signature = signature( |
|
| 644 |
x = "numeric", |
|
| 645 |
model = "LogisticKadaneBetaGamma", |
|
| 646 |
samples = "Samples" |
|
| 647 |
), |
|
| 648 |
definition = function(x, model, samples) {
|
|
| 649 | 6x |
assert_probabilities(x) |
| 650 | 4x |
assert_subset(c("rho0", "gamma"), names(samples))
|
| 651 | 4x |
assert_length(x, len = size(samples)) |
| 652 | ||
| 653 | 3x |
rho0 <- samples@data$rho0 |
| 654 | 3x |
gamma <- samples@data$gamma |
| 655 | 3x |
theta <- model@theta |
| 656 | 3x |
xmin <- model@xmin |
| 657 | 3x |
num <- gamma * (logit(x) - logit(rho0)) + xmin * (logit(theta) - logit(x)) |
| 658 | 3x |
num / (logit(theta) - logit(rho0)) |
| 659 |
} |
|
| 660 |
) |
|
| 661 | ||
| 662 |
## LogisticNormalMixture ---- |
|
| 663 | ||
| 664 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 665 |
#' probability of the occurrence of a DLE (`x`). |
|
| 666 |
#' |
|
| 667 |
#' @aliases dose-LogisticNormalMixture |
|
| 668 |
#' @export |
|
| 669 |
#' |
|
| 670 |
setMethod( |
|
| 671 |
f = "dose", |
|
| 672 |
signature = signature( |
|
| 673 |
x = "numeric", |
|
| 674 |
model = "LogisticNormalMixture", |
|
| 675 |
samples = "Samples" |
|
| 676 |
), |
|
| 677 |
definition = function(x, model, samples) {
|
|
| 678 | 6x |
assert_probabilities(x) |
| 679 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 680 | 4x |
assert_length(x, len = size(samples)) |
| 681 | ||
| 682 | 3x |
alpha0 <- samples@data$alpha0 |
| 683 | 3x |
alpha1 <- samples@data$alpha1 |
| 684 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 685 | 3x |
exp((logit(x) - alpha0) / alpha1) * ref_dose |
| 686 |
} |
|
| 687 |
) |
|
| 688 | ||
| 689 |
## LogisticNormalFixedMixture ---- |
|
| 690 | ||
| 691 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 692 |
#' probability of the occurrence of a DLE (`x`). |
|
| 693 |
#' |
|
| 694 |
#' @aliases dose-LogisticNormalFixedMixture |
|
| 695 |
#' @export |
|
| 696 |
#' |
|
| 697 |
setMethod( |
|
| 698 |
f = "dose", |
|
| 699 |
signature = signature( |
|
| 700 |
x = "numeric", |
|
| 701 |
model = "LogisticNormalFixedMixture", |
|
| 702 |
samples = "Samples" |
|
| 703 |
), |
|
| 704 |
definition = function(x, model, samples) {
|
|
| 705 | 6x |
assert_probabilities(x) |
| 706 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 707 | 4x |
assert_length(x, len = size(samples)) |
| 708 | ||
| 709 | 3x |
alpha0 <- samples@data$alpha0 |
| 710 | 3x |
alpha1 <- samples@data$alpha1 |
| 711 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 712 | 3x |
exp((logit(x) - alpha0) / alpha1) * ref_dose |
| 713 |
} |
|
| 714 |
) |
|
| 715 | ||
| 716 |
## LogisticLogNormalMixture ---- |
|
| 717 | ||
| 718 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 719 |
#' probability of the occurrence of a DLE (`x`). |
|
| 720 |
#' |
|
| 721 |
#' @aliases dose-LogisticLogNormalMixture |
|
| 722 |
#' @export |
|
| 723 |
#' |
|
| 724 |
setMethod( |
|
| 725 |
f = "dose", |
|
| 726 |
signature = signature( |
|
| 727 |
x = "numeric", |
|
| 728 |
model = "LogisticLogNormalMixture", |
|
| 729 |
samples = "Samples" |
|
| 730 |
), |
|
| 731 |
definition = function(x, model, samples) {
|
|
| 732 | 1x |
stop("not implemented")
|
| 733 |
} |
|
| 734 |
) |
|
| 735 | ||
| 736 |
## DualEndpoint ---- |
|
| 737 | ||
| 738 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 739 |
#' probability of the occurrence of a DLE (`x`). |
|
| 740 |
#' |
|
| 741 |
#' @aliases dose-DualEndpoint |
|
| 742 |
#' @export |
|
| 743 |
#' |
|
| 744 |
setMethod( |
|
| 745 |
f = "dose", |
|
| 746 |
signature = signature( |
|
| 747 |
x = "numeric", |
|
| 748 |
model = "DualEndpoint", |
|
| 749 |
samples = "Samples" |
|
| 750 |
), |
|
| 751 |
definition = function(x, model, samples) {
|
|
| 752 | 15x |
assert_probabilities(x) |
| 753 | 13x |
assert_subset("betaZ", names(samples))
|
| 754 | 13x |
assert_length(x, len = size(samples)) |
| 755 | ||
| 756 | 12x |
betaZ <- samples@data$betaZ |
| 757 | 12x |
ref_dose <- as.numeric(model@ref_dose) |
| 758 | 12x |
dose_temp <- (qnorm(x) - betaZ[, 1]) / betaZ[, 2] |
| 759 | 12x |
if (model@use_log_dose) {
|
| 760 | 8x |
exp(dose_temp) * ref_dose |
| 761 |
} else {
|
|
| 762 | 4x |
dose_temp * ref_dose |
| 763 |
} |
|
| 764 |
} |
|
| 765 |
) |
|
| 766 | ||
| 767 |
## LogisticIndepBeta ---- |
|
| 768 | ||
| 769 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 770 |
#' probability of the occurrence of a DLE (`x`). |
|
| 771 |
#' |
|
| 772 |
#' @aliases dose-LogisticIndepBeta |
|
| 773 |
#' @export |
|
| 774 |
#' |
|
| 775 |
setMethod( |
|
| 776 |
f = "dose", |
|
| 777 |
signature = signature( |
|
| 778 |
x = "numeric", |
|
| 779 |
model = "LogisticIndepBeta", |
|
| 780 |
samples = "Samples" |
|
| 781 |
), |
|
| 782 |
definition = function(x, model, samples) {
|
|
| 783 | 1639x |
assert_probabilities(x) |
| 784 | 1637x |
assert_subset(c("phi1", "phi2"), names(samples))
|
| 785 | 1637x |
assert_length(x, len = size(samples)) |
| 786 | ||
| 787 | 1636x |
phi1 <- samples@data$phi1 |
| 788 | 1636x |
phi2 <- samples@data$phi2 |
| 789 | 1636x |
log_dose <- (log(x / (1 - x)) - phi1) / phi2 |
| 790 | 1636x |
exp(log_dose) |
| 791 |
} |
|
| 792 |
) |
|
| 793 | ||
| 794 |
## LogisticIndepBeta-noSamples ---- |
|
| 795 | ||
| 796 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 797 |
#' probability of the occurrence of a DLE (`x`). |
|
| 798 |
#' All model parameters (except `x`) should be present in the `model` object. |
|
| 799 |
#' |
|
| 800 |
#' @aliases dose-LogisticIndepBeta-noSamples |
|
| 801 |
#' @export |
|
| 802 |
#' |
|
| 803 |
setMethod( |
|
| 804 |
f = "dose", |
|
| 805 |
signature = signature( |
|
| 806 |
x = "numeric", |
|
| 807 |
model = "LogisticIndepBeta", |
|
| 808 |
samples = "missing" |
|
| 809 |
), |
|
| 810 |
definition = function(x, model) {
|
|
| 811 | 598x |
assert_probabilities(x) |
| 812 | 597x |
model_params <- h_slots(model, c("phi1", "phi2"))
|
| 813 | 597x |
nsamples <- length(model_params[[1]]) |
| 814 | 597x |
samples <- Samples( |
| 815 | 597x |
data = model_params, |
| 816 | 597x |
options = McmcOptions(samples = nsamples) |
| 817 |
) |
|
| 818 | 597x |
assert_length(x, len = nsamples) |
| 819 | ||
| 820 | 597x |
dose(x, model, samples) |
| 821 |
} |
|
| 822 |
) |
|
| 823 | ||
| 824 |
## Effloglog-noSamples ---- |
|
| 825 | ||
| 826 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 827 |
#' probability of the occurrence of a DLE (`x`). |
|
| 828 |
#' All model parameters (except `x`) should be present in the `model` object. |
|
| 829 |
#' |
|
| 830 |
#' @aliases dose-Effloglog-noSamples |
|
| 831 |
#' @export |
|
| 832 |
#' |
|
| 833 |
setMethod( |
|
| 834 |
f = "dose", |
|
| 835 |
signature = signature( |
|
| 836 |
x = "numeric", |
|
| 837 |
model = "Effloglog", |
|
| 838 |
samples = "missing" |
|
| 839 |
), |
|
| 840 |
definition = function(x, model) {
|
|
| 841 | ! |
assert_numeric(x, min.len = 1L, any.missing = FALSE, finite = TRUE) |
| 842 | ! |
theta1 <- model@theta1 |
| 843 | ! |
theta2 <- model@theta2 |
| 844 | ! |
constant <- model@const |
| 845 | ! |
assert_scalar(theta1) |
| 846 | ! |
assert_scalar(theta2) |
| 847 | ! |
assert_scalar(constant) |
| 848 | ||
| 849 | ! |
exp(exp((x - theta1) / theta2)) - constant |
| 850 |
} |
|
| 851 |
) |
|
| 852 | ||
| 853 |
## EffFlexi ---- |
|
| 854 | ||
| 855 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 856 |
#' probability of the occurrence of a DLE (`x`). For this method `x` must |
|
| 857 |
#' be a scalar. |
|
| 858 |
#' |
|
| 859 |
#' @aliases dose-EffFlexi |
|
| 860 |
#' @export |
|
| 861 |
#' |
|
| 862 |
setMethod( |
|
| 863 |
f = "dose", |
|
| 864 |
signature = signature( |
|
| 865 |
x = "numeric", |
|
| 866 |
model = "EffFlexi", |
|
| 867 |
samples = "Samples" |
|
| 868 |
), |
|
| 869 |
definition = function(x, model, samples) {
|
|
| 870 | ! |
assert_number(x) |
| 871 | ! |
assert_subset("ExpEff", names(samples))
|
| 872 | ||
| 873 | ! |
samples_efficacy <- samples@data$ExpEff |
| 874 | ! |
dose_grid <- model@data@doseGrid |
| 875 | ||
| 876 |
# Find dose level for a given expected efficacy level using linear interpolation. |
|
| 877 | ! |
apply(samples_efficacy, 1, function(se) {
|
| 878 | ! |
se_leq_x <- se <= x |
| 879 | ! |
dose_level0 <- max(which(se_leq_x)) |
| 880 | ! |
dose_level1 <- min(which(!se_leq_x)) |
| 881 | ! |
eff0 <- se[dose_level0] |
| 882 | ! |
eff1 <- se[dose_level1] |
| 883 | ! |
dose0 <- dose_grid[dose_level0] |
| 884 | ! |
dose1 <- dose_grid[dose_level1] |
| 885 | ! |
dose0 + (dose1 - dose0) * ((x - eff0) / (eff1 - eff0)) |
| 886 |
}) |
|
| 887 |
} |
|
| 888 |
) |
|
| 889 | ||
| 890 |
## OneParLogNormalPrior ---- |
|
| 891 | ||
| 892 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 893 |
#' probability of the occurrence of a DLT (`x`). |
|
| 894 |
#' |
|
| 895 |
#' @aliases dose-OneParLogNormalPrior |
|
| 896 |
#' @export |
|
| 897 |
#' |
|
| 898 |
setMethod( |
|
| 899 |
f = "dose", |
|
| 900 |
signature = signature( |
|
| 901 |
x = "numeric", |
|
| 902 |
model = "OneParLogNormalPrior", |
|
| 903 |
samples = "Samples" |
|
| 904 |
), |
|
| 905 |
definition = function(x, model, samples) {
|
|
| 906 | 7x |
assert_probabilities(x) |
| 907 | 5x |
assert_subset("alpha", names(samples))
|
| 908 | 5x |
assert_length(x, len = size(samples)) |
| 909 | ||
| 910 | 4x |
alpha <- samples@data$alpha |
| 911 | 4x |
skel_fun_inv <- model@skel_fun_inv |
| 912 | 4x |
skel_fun_inv(x^(1 / exp(alpha))) |
| 913 |
} |
|
| 914 |
) |
|
| 915 | ||
| 916 |
## OneParExpPrior ---- |
|
| 917 | ||
| 918 |
#' @describeIn dose compute the dose level reaching a specific target |
|
| 919 |
#' probability of the occurrence of a DLT (`x`). |
|
| 920 |
#' |
|
| 921 |
#' @aliases dose-OneParExpPrior |
|
| 922 |
#' @export |
|
| 923 |
#' |
|
| 924 |
setMethod( |
|
| 925 |
f = "dose", |
|
| 926 |
signature = signature( |
|
| 927 |
x = "numeric", |
|
| 928 |
model = "OneParExpPrior", |
|
| 929 |
samples = "Samples" |
|
| 930 |
), |
|
| 931 |
definition = function(x, model, samples) {
|
|
| 932 | 7x |
assert_probabilities(x) |
| 933 | 5x |
assert_subset("theta", names(samples))
|
| 934 | 5x |
assert_length(x, len = size(samples)) |
| 935 | ||
| 936 | 4x |
theta <- samples@data$theta |
| 937 | 4x |
skel_fun_inv <- model@skel_fun_inv |
| 938 | 4x |
assert_numeric(theta, lower = .Machine$double.xmin, finite = TRUE) |
| 939 | 4x |
skel_fun_inv(x^(1 / theta)) |
| 940 |
} |
|
| 941 |
) |
|
| 942 | ||
| 943 |
# prob ---- |
|
| 944 | ||
| 945 |
## generic ---- |
|
| 946 | ||
| 947 |
#' Computing Toxicity Probabilities for a Given Dose, Model and Samples |
|
| 948 |
#' |
|
| 949 |
#' @description `r lifecycle::badge("stable")`
|
|
| 950 |
#' |
|
| 951 |
#' A function that computes the probability of the occurrence of a DLE at a |
|
| 952 |
#' specified dose level, based on the model parameters (samples). |
|
| 953 |
#' |
|
| 954 |
#' @details The `prob()` function computes the probability of toxicity for given |
|
| 955 |
#' doses, using samples of the model parameter(s). |
|
| 956 |
#' If you work with multivariate model parameters, then assume that your model |
|
| 957 |
#' specific `prob()` method receives a samples matrix where the rows |
|
| 958 |
#' correspond to the sampling index, i.e. the layout is then |
|
| 959 |
#' `nSamples x dimParameter`. |
|
| 960 |
#' |
|
| 961 |
#' @note The [prob()] and [dose()] functions are the inverse of |
|
| 962 |
#' each other, for all [dose()] methods for which its first argument, i.e. a |
|
| 963 |
#' given independent variable that dose depends on, represents toxicity |
|
| 964 |
#' probability. |
|
| 965 |
#' |
|
| 966 |
#' @param dose (`number` or `numeric`)\cr the dose which is targeted. |
|
| 967 |
#' The following recycling rule applies when `samples` is not missing: vectors |
|
| 968 |
#' of size 1 will be recycled to the size of the sample |
|
| 969 |
#' (i.e. `size(samples)`). Otherwise, `dose` must have the same |
|
| 970 |
#' size as the sample. |
|
| 971 |
#' @param model (`GeneralModel` or `ModelTox`)\cr the model for single agent |
|
| 972 |
#' dose escalation or pseudo DLE (dose-limiting events)/toxicity model. |
|
| 973 |
#' @param samples (`Samples`)\cr the samples of model's parameters that will be |
|
| 974 |
#' used to compute toxicity probabilities. Can also be missing for some models. |
|
| 975 |
#' @param ... model specific parameters when `samples` are not used. |
|
| 976 |
#' |
|
| 977 |
#' @return A `proportion` or `numeric` vector with the toxicity probabilities. |
|
| 978 |
#' If non-scalar `samples` were used, then every element in the returned vector |
|
| 979 |
#' corresponds to one element of a sample. Hence, in this case, the output |
|
| 980 |
#' vector is of the same length as the sample vector. If scalar `samples` were |
|
| 981 |
#' used or no `samples` were used, e.g. for pseudo DLE/toxicity `model`, |
|
| 982 |
#' then the output is of the same length as the length of the `dose`. In the |
|
| 983 |
#' case of `LogisticLogNormalOrdinal`, the probabilities relate to toxicities |
|
| 984 |
#' of grade given by `grade`. |
|
| 985 |
#' |
|
| 986 |
#' @seealso [probFunction()], [dose()], [efficacy()]. |
|
| 987 |
#' |
|
| 988 |
#' @export |
|
| 989 |
#' @example examples/Model-method-prob.R |
|
| 990 |
#' |
|
| 991 |
setGeneric( |
|
| 992 |
name = "prob", |
|
| 993 |
def = function(dose, model, samples, ...) {
|
|
| 994 | 23463x |
standardGeneric("prob")
|
| 995 |
}, |
|
| 996 |
valueClass = c("numeric", "list")
|
|
| 997 |
) |
|
| 998 | ||
| 999 |
## LogisticNormal ---- |
|
| 1000 | ||
| 1001 |
#' @describeIn prob |
|
| 1002 |
#' |
|
| 1003 |
#' @aliases prob-LogisticNormal |
|
| 1004 |
#' @export |
|
| 1005 |
#' |
|
| 1006 |
setMethod( |
|
| 1007 |
f = "prob", |
|
| 1008 |
signature = signature( |
|
| 1009 |
dose = "numeric", |
|
| 1010 |
model = "LogisticNormal", |
|
| 1011 |
samples = "Samples" |
|
| 1012 |
), |
|
| 1013 |
definition = function(dose, model, samples, ...) {
|
|
| 1014 | 244x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1) |
| 1015 | 243x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 1016 | 243x |
assert_length(dose, len = size(samples)) |
| 1017 | ||
| 1018 | 242x |
alpha0 <- samples@data$alpha0 |
| 1019 | 242x |
alpha1 <- samples@data$alpha1 |
| 1020 | 242x |
ref_dose <- as.numeric(model@ref_dose) |
| 1021 | 242x |
plogis(alpha0 + alpha1 * log(dose / ref_dose)) |
| 1022 |
} |
|
| 1023 |
) |
|
| 1024 | ||
| 1025 |
## LogisticLogNormal ---- |
|
| 1026 | ||
| 1027 |
#' @describeIn prob |
|
| 1028 |
#' |
|
| 1029 |
#' @aliases prob-LogisticLogNormal |
|
| 1030 |
#' @export |
|
| 1031 |
#' |
|
| 1032 |
setMethod( |
|
| 1033 |
f = "prob", |
|
| 1034 |
signature = signature( |
|
| 1035 |
dose = "numeric", |
|
| 1036 |
model = "LogisticLogNormal", |
|
| 1037 |
samples = "Samples" |
|
| 1038 |
), |
|
| 1039 |
definition = function(dose, model, samples, ...) {
|
|
| 1040 | 4730x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1041 | 4729x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 1042 | 4729x |
assert_length(dose, len = size(samples)) |
| 1043 | ||
| 1044 | 4728x |
alpha0 <- samples@data$alpha0 |
| 1045 | 4728x |
alpha1 <- samples@data$alpha1 |
| 1046 | 4728x |
ref_dose <- as.numeric(model@ref_dose) |
| 1047 | 4728x |
plogis(alpha0 + alpha1 * log(dose / ref_dose)) |
| 1048 |
} |
|
| 1049 |
) |
|
| 1050 | ||
| 1051 |
## LogisticLogNormalSub ---- |
|
| 1052 | ||
| 1053 |
#' @describeIn prob |
|
| 1054 |
#' |
|
| 1055 |
#' @aliases prob-LogisticLogNormalSub |
|
| 1056 |
#' @export |
|
| 1057 |
#' |
|
| 1058 |
setMethod( |
|
| 1059 |
f = "prob", |
|
| 1060 |
signature = signature( |
|
| 1061 |
dose = "numeric", |
|
| 1062 |
model = "LogisticLogNormalSub", |
|
| 1063 |
samples = "Samples" |
|
| 1064 |
), |
|
| 1065 |
definition = function(dose, model, samples, ...) {
|
|
| 1066 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1067 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 1068 | 4x |
assert_length(dose, len = size(samples)) |
| 1069 | ||
| 1070 | 3x |
alpha0 <- samples@data$alpha0 |
| 1071 | 3x |
alpha1 <- samples@data$alpha1 |
| 1072 | 3x |
ref_dose <- model@ref_dose |
| 1073 | 3x |
plogis(alpha0 + alpha1 * (dose - ref_dose)) |
| 1074 |
} |
|
| 1075 |
) |
|
| 1076 | ||
| 1077 |
## ProbitLogNormal ---- |
|
| 1078 | ||
| 1079 |
#' @describeIn prob |
|
| 1080 |
#' |
|
| 1081 |
#' @aliases prob-ProbitLogNormal |
|
| 1082 |
#' @export |
|
| 1083 |
#' |
|
| 1084 |
setMethod( |
|
| 1085 |
f = "prob", |
|
| 1086 |
signature = signature( |
|
| 1087 |
dose = "numeric", |
|
| 1088 |
model = "ProbitLogNormal", |
|
| 1089 |
samples = "Samples" |
|
| 1090 |
), |
|
| 1091 |
definition = function(dose, model, samples, ...) {
|
|
| 1092 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1093 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 1094 | 4x |
assert_length(dose, len = size(samples)) |
| 1095 | ||
| 1096 | 3x |
alpha0 <- samples@data$alpha0 |
| 1097 | 3x |
alpha1 <- samples@data$alpha1 |
| 1098 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 1099 | 3x |
pnorm(alpha0 + alpha1 * log(dose / ref_dose)) |
| 1100 |
} |
|
| 1101 |
) |
|
| 1102 | ||
| 1103 |
## ProbitLogNormalRel ---- |
|
| 1104 | ||
| 1105 |
#' @describeIn prob |
|
| 1106 |
#' |
|
| 1107 |
#' @aliases prob-ProbitLogNormalRel |
|
| 1108 |
#' @export |
|
| 1109 |
#' |
|
| 1110 |
setMethod( |
|
| 1111 |
f = "prob", |
|
| 1112 |
signature = signature( |
|
| 1113 |
dose = "numeric", |
|
| 1114 |
model = "ProbitLogNormalRel", |
|
| 1115 |
samples = "Samples" |
|
| 1116 |
), |
|
| 1117 |
definition = function(dose, model, samples, ...) {
|
|
| 1118 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1119 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 1120 | 4x |
assert_length(dose, len = size(samples)) |
| 1121 | ||
| 1122 | 3x |
alpha0 <- samples@data$alpha0 |
| 1123 | 3x |
alpha1 <- samples@data$alpha1 |
| 1124 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 1125 | 3x |
pnorm(alpha0 + alpha1 * (dose / ref_dose)) |
| 1126 |
} |
|
| 1127 |
) |
|
| 1128 | ||
| 1129 |
## LogisticLogNormalGrouped ---- |
|
| 1130 | ||
| 1131 |
#' @describeIn prob method for [`LogisticLogNormalGrouped`] which needs `group` |
|
| 1132 |
#' argument in addition. |
|
| 1133 |
#' @param group (`character` or `factor`)\cr for [`LogisticLogNormalGrouped`], |
|
| 1134 |
#' indicating whether to calculate the probability for the `mono` or for |
|
| 1135 |
#' the `combo` arm. |
|
| 1136 |
#' @aliases prob-LogisticLogNormalGrouped |
|
| 1137 |
#' @export |
|
| 1138 |
#' |
|
| 1139 |
setMethod( |
|
| 1140 |
f = "prob", |
|
| 1141 |
signature = signature( |
|
| 1142 |
dose = "numeric", |
|
| 1143 |
model = "LogisticLogNormalGrouped", |
|
| 1144 |
samples = "Samples" |
|
| 1145 |
), |
|
| 1146 |
definition = function(dose, model, samples, group, ...) {
|
|
| 1147 | 15132x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1148 | 15132x |
assert_subset(c("alpha0", "delta0", "alpha1", "delta1"), names(samples))
|
| 1149 | 15132x |
assert_length(dose, len = size(samples)) |
| 1150 | 15132x |
assert_multi_class(group, c("character", "factor"))
|
| 1151 | 15131x |
assert_subset(as.character(group), choices = c("mono", "combo"))
|
| 1152 | 15131x |
assert_length(group, len = size(samples)) |
| 1153 | ||
| 1154 | 15131x |
alpha0 <- samples@data$alpha0 |
| 1155 | 15131x |
delta0 <- samples@data$delta0 |
| 1156 | 15131x |
alpha1 <- samples@data$alpha1 |
| 1157 | 15131x |
delta1 <- samples@data$delta1 |
| 1158 | 15131x |
ref_dose <- as.numeric(model@ref_dose) |
| 1159 | 15131x |
is_combo <- as.integer(group == "combo") |
| 1160 | 15131x |
plogis( |
| 1161 | 15131x |
(alpha0 + is_combo * delta0) + |
| 1162 | 15131x |
(alpha1 + is_combo * delta1) * log(dose / ref_dose) |
| 1163 |
) |
|
| 1164 |
} |
|
| 1165 |
) |
|
| 1166 | ||
| 1167 |
## LogisticKadane ---- |
|
| 1168 | ||
| 1169 |
#' @describeIn prob |
|
| 1170 |
#' |
|
| 1171 |
#' @aliases prob-LogisticKadane |
|
| 1172 |
#' @export |
|
| 1173 |
#' |
|
| 1174 |
setMethod( |
|
| 1175 |
f = "prob", |
|
| 1176 |
signature = signature( |
|
| 1177 |
dose = "numeric", |
|
| 1178 |
model = "LogisticKadane", |
|
| 1179 |
samples = "Samples" |
|
| 1180 |
), |
|
| 1181 |
definition = function(dose, model, samples, ...) {
|
|
| 1182 | 9x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1183 | 8x |
assert_subset(c("rho0", "gamma"), names(samples))
|
| 1184 | 8x |
assert_length(dose, len = size(samples)) |
| 1185 | ||
| 1186 | 7x |
rho0 <- samples@data$rho0 |
| 1187 | 7x |
gamma <- samples@data$gamma |
| 1188 | 7x |
theta <- model@theta |
| 1189 | 7x |
xmin <- model@xmin |
| 1190 | 7x |
num <- gamma * |
| 1191 | 7x |
logit(rho0) - |
| 1192 | 7x |
xmin * logit(theta) + |
| 1193 | 7x |
(logit(theta) - logit(rho0)) * dose |
| 1194 | 7x |
plogis(num / (gamma - xmin)) |
| 1195 |
} |
|
| 1196 |
) |
|
| 1197 | ||
| 1198 |
## LogisticKadaneBetaGamma ---- |
|
| 1199 | ||
| 1200 |
#' @describeIn prob |
|
| 1201 |
#' |
|
| 1202 |
#' @aliases prob-LogisticKadaneBetaGamma |
|
| 1203 |
#' @export |
|
| 1204 |
#' |
|
| 1205 |
setMethod( |
|
| 1206 |
f = "prob", |
|
| 1207 |
signature = signature( |
|
| 1208 |
dose = "numeric", |
|
| 1209 |
model = "LogisticKadaneBetaGamma", |
|
| 1210 |
samples = "Samples" |
|
| 1211 |
), |
|
| 1212 |
definition = function(dose, model, samples, ...) {
|
|
| 1213 | ! |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1214 | ! |
assert_subset(c("rho0", "gamma"), names(samples))
|
| 1215 | ! |
assert_length(dose, len = size(samples)) |
| 1216 | ||
| 1217 | ! |
rho0 <- samples@data$rho0 |
| 1218 | ! |
gamma <- samples@data$gamma |
| 1219 | ! |
theta <- model@theta |
| 1220 | ! |
xmin <- model@xmin |
| 1221 | ! |
num <- gamma * |
| 1222 | ! |
logit(rho0) - |
| 1223 | ! |
xmin * logit(theta) + |
| 1224 | ! |
(logit(theta) - logit(rho0)) * dose |
| 1225 | ! |
plogis(num / (gamma - xmin)) |
| 1226 |
} |
|
| 1227 |
) |
|
| 1228 | ||
| 1229 |
## LogisticNormalMixture ---- |
|
| 1230 | ||
| 1231 |
#' @describeIn prob |
|
| 1232 |
#' |
|
| 1233 |
#' @aliases prob-LogisticNormalMixture |
|
| 1234 |
#' @export |
|
| 1235 |
#' |
|
| 1236 |
setMethod( |
|
| 1237 |
f = "prob", |
|
| 1238 |
signature = signature( |
|
| 1239 |
dose = "numeric", |
|
| 1240 |
model = "LogisticNormalMixture", |
|
| 1241 |
samples = "Samples" |
|
| 1242 |
), |
|
| 1243 |
definition = function(dose, model, samples, ...) {
|
|
| 1244 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1245 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 1246 | 4x |
assert_length(dose, len = size(samples)) |
| 1247 | ||
| 1248 | 3x |
alpha0 <- samples@data$alpha0 |
| 1249 | 3x |
alpha1 <- samples@data$alpha1 |
| 1250 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 1251 | 3x |
plogis(alpha0 + alpha1 * log(dose / ref_dose)) |
| 1252 |
} |
|
| 1253 |
) |
|
| 1254 | ||
| 1255 |
## LogisticNormalFixedMixture ---- |
|
| 1256 | ||
| 1257 |
#' @describeIn prob |
|
| 1258 |
#' |
|
| 1259 |
#' @aliases prob-LogisticNormalFixedMixture |
|
| 1260 |
#' @export |
|
| 1261 |
#' |
|
| 1262 |
setMethod( |
|
| 1263 |
f = "prob", |
|
| 1264 |
signature = signature( |
|
| 1265 |
dose = "numeric", |
|
| 1266 |
model = "LogisticNormalFixedMixture", |
|
| 1267 |
samples = "Samples" |
|
| 1268 |
), |
|
| 1269 |
definition = function(dose, model, samples, ...) {
|
|
| 1270 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1271 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 1272 | 4x |
assert_length(dose, len = size(samples)) |
| 1273 | ||
| 1274 | 3x |
alpha0 <- samples@data$alpha0 |
| 1275 | 3x |
alpha1 <- samples@data$alpha1 |
| 1276 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 1277 | 3x |
plogis(alpha0 + alpha1 * log(dose / ref_dose)) |
| 1278 |
} |
|
| 1279 |
) |
|
| 1280 | ||
| 1281 |
## LogisticLogNormalMixture ---- |
|
| 1282 | ||
| 1283 |
#' @describeIn prob |
|
| 1284 |
#' |
|
| 1285 |
#' @aliases prob-LogisticLogNormalMixture |
|
| 1286 |
#' @export |
|
| 1287 |
#' |
|
| 1288 |
setMethod( |
|
| 1289 |
f = "prob", |
|
| 1290 |
signature = signature( |
|
| 1291 |
dose = "numeric", |
|
| 1292 |
model = "LogisticLogNormalMixture", |
|
| 1293 |
samples = "Samples" |
|
| 1294 |
), |
|
| 1295 |
definition = function(dose, model, samples, ...) {
|
|
| 1296 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1297 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples))
|
| 1298 | 4x |
assert_length(dose, len = size(samples)) |
| 1299 | ||
| 1300 | 3x |
alpha0 <- samples@data$alpha0 |
| 1301 | 3x |
alpha1 <- samples@data$alpha1 |
| 1302 | 3x |
comp <- samples@data$comp |
| 1303 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
| 1304 | 3x |
sel <- cbind(seq_along(comp), comp) |
| 1305 | 3x |
plogis(alpha0[sel] + alpha1[sel] * log(dose / ref_dose)) |
| 1306 |
} |
|
| 1307 |
) |
|
| 1308 | ||
| 1309 |
## DualEndpoint ---- |
|
| 1310 | ||
| 1311 |
#' @describeIn prob |
|
| 1312 |
#' |
|
| 1313 |
#' @aliases prob-DualEndpoint |
|
| 1314 |
#' @export |
|
| 1315 |
#' |
|
| 1316 |
setMethod( |
|
| 1317 |
f = "prob", |
|
| 1318 |
signature = signature( |
|
| 1319 |
dose = "numeric", |
|
| 1320 |
model = "DualEndpoint", |
|
| 1321 |
samples = "Samples" |
|
| 1322 |
), |
|
| 1323 |
definition = function(dose, model, samples, ...) {
|
|
| 1324 | 354x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1325 | 353x |
assert_subset("betaZ", names(samples))
|
| 1326 | 353x |
assert_length(dose, len = size(samples)) |
| 1327 | ||
| 1328 | 352x |
betaZ <- samples@data$betaZ |
| 1329 | 352x |
ref_dose <- as.numeric(model@ref_dose) |
| 1330 | 352x |
stand_dose <- if (model@use_log_dose) {
|
| 1331 | 212x |
log(dose / ref_dose) |
| 1332 |
} else {
|
|
| 1333 | 140x |
dose / ref_dose |
| 1334 |
} |
|
| 1335 | 352x |
pnorm(betaZ[, 1] + betaZ[, 2] * stand_dose) |
| 1336 |
} |
|
| 1337 |
) |
|
| 1338 | ||
| 1339 |
## LogisticIndepBeta ---- |
|
| 1340 | ||
| 1341 |
#' @describeIn prob compute toxicity probabilities of the occurrence of a DLE at |
|
| 1342 |
#' a specified dose level, based on the samples of [`LogisticIndepBeta`] model |
|
| 1343 |
#' parameters. |
|
| 1344 |
#' |
|
| 1345 |
#' @aliases prob-LogisticIndepBeta |
|
| 1346 |
#' @export |
|
| 1347 |
#' |
|
| 1348 |
setMethod( |
|
| 1349 |
f = "prob", |
|
| 1350 |
signature = signature( |
|
| 1351 |
dose = "numeric", |
|
| 1352 |
model = "LogisticIndepBeta", |
|
| 1353 |
samples = "Samples" |
|
| 1354 |
), |
|
| 1355 |
definition = function(dose, model, samples, ...) {
|
|
| 1356 | 1623x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1357 | 1622x |
assert_subset(c("phi1", "phi2"), names(samples))
|
| 1358 | 1622x |
assert_length(dose, len = size(samples)) |
| 1359 | ||
| 1360 | 1620x |
phi1 <- samples@data$phi1 |
| 1361 | 1620x |
phi2 <- samples@data$phi2 |
| 1362 | 1620x |
log_dose <- log(dose) |
| 1363 | 1620x |
exp(phi1 + phi2 * log_dose) / (1 + exp(phi1 + phi2 * log_dose)) |
| 1364 |
} |
|
| 1365 |
) |
|
| 1366 | ||
| 1367 |
## LogisticIndepBeta-noSamples ---- |
|
| 1368 | ||
| 1369 |
#' @describeIn prob compute toxicity probabilities of the occurrence of a DLE at |
|
| 1370 |
#' a specified dose level, based on the [`LogisticIndepBeta`] model parameters. |
|
| 1371 |
#' All model parameters (except `dose`) should be present in the `model` object. |
|
| 1372 |
#' |
|
| 1373 |
#' @aliases prob-LogisticIndepBeta-noSamples |
|
| 1374 |
#' @export |
|
| 1375 |
#' |
|
| 1376 |
setMethod( |
|
| 1377 |
f = "prob", |
|
| 1378 |
signature = signature( |
|
| 1379 |
dose = "numeric", |
|
| 1380 |
model = "LogisticIndepBeta", |
|
| 1381 |
samples = "missing" |
|
| 1382 |
), |
|
| 1383 |
definition = function(dose, model, ...) {
|
|
| 1384 | 1163x |
model_params <- h_slots(model, c("phi1", "phi2"))
|
| 1385 | 1163x |
nsamples <- length(model_params[[1]]) |
| 1386 | 1163x |
samples <- Samples( |
| 1387 | 1163x |
data = model_params, |
| 1388 | 1163x |
options = McmcOptions(samples = nsamples) |
| 1389 |
) |
|
| 1390 | ||
| 1391 | 1163x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1392 | 1162x |
assert_length(dose, len = nsamples) |
| 1393 | ||
| 1394 | 1162x |
prob(dose, model, samples) |
| 1395 |
} |
|
| 1396 |
) |
|
| 1397 | ||
| 1398 |
## OneParLogNormalPrior ---- |
|
| 1399 | ||
| 1400 |
#' @describeIn prob |
|
| 1401 |
#' |
|
| 1402 |
#' @aliases prob-OneParLogNormalPrior |
|
| 1403 |
#' @export |
|
| 1404 |
#' |
|
| 1405 |
setMethod( |
|
| 1406 |
f = "prob", |
|
| 1407 |
signature = signature( |
|
| 1408 |
dose = "numeric", |
|
| 1409 |
model = "OneParLogNormalPrior", |
|
| 1410 |
samples = "Samples" |
|
| 1411 |
), |
|
| 1412 |
definition = function(dose, model, samples, ...) {
|
|
| 1413 | 30x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1414 | 29x |
assert_subset("alpha", names(samples))
|
| 1415 | 29x |
assert_length(dose, len = size(samples)) |
| 1416 | ||
| 1417 | 28x |
alpha <- samples@data$alpha |
| 1418 | 28x |
skel_fun <- model@skel_fun |
| 1419 | 28x |
skel_fun(dose)^exp(alpha) |
| 1420 |
} |
|
| 1421 |
) |
|
| 1422 | ||
| 1423 |
## OneParExpPrior ---- |
|
| 1424 | ||
| 1425 |
#' @describeIn prob |
|
| 1426 |
#' |
|
| 1427 |
#' @aliases prob-OneParExpPrior |
|
| 1428 |
#' @export |
|
| 1429 |
#' |
|
| 1430 |
setMethod( |
|
| 1431 |
f = "prob", |
|
| 1432 |
signature = signature( |
|
| 1433 |
dose = "numeric", |
|
| 1434 |
model = "OneParExpPrior", |
|
| 1435 |
samples = "Samples" |
|
| 1436 |
), |
|
| 1437 |
definition = function(dose, model, samples, ...) {
|
|
| 1438 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1439 | 4x |
assert_subset("theta", names(samples))
|
| 1440 | 4x |
assert_length(dose, len = size(samples)) |
| 1441 | ||
| 1442 | 3x |
theta <- samples@data$theta |
| 1443 | 3x |
skel_fun <- model@skel_fun |
| 1444 | 3x |
skel_fun(dose)^theta |
| 1445 |
} |
|
| 1446 |
) |
|
| 1447 | ||
| 1448 |
## LogisticLogNormalOrdinal ---- |
|
| 1449 | ||
| 1450 |
#' Calculate a grade-specific probability of toxicity for a given dose. |
|
| 1451 |
#' @describeIn prob |
|
| 1452 |
#' |
|
| 1453 |
#' @param grade (`integer` or `integer_vector`)\cr The toxicity grade for which probabilities are required |
|
| 1454 |
#' @param cumulative (`flag`)\cr Should the returned probability be cumulative |
|
| 1455 |
#' (the default) or grade-specific? |
|
| 1456 |
#' @aliases prob-LogisticLogNormalOrdinal |
|
| 1457 |
#' @export |
|
| 1458 |
#' |
|
| 1459 |
setMethod( |
|
| 1460 |
f = "prob", |
|
| 1461 |
signature = signature( |
|
| 1462 |
dose = "numeric", |
|
| 1463 |
model = "LogisticLogNormalOrdinal", |
|
| 1464 |
samples = "Samples" |
|
| 1465 |
), |
|
| 1466 |
definition = function(dose, model, samples, grade, cumulative = TRUE, ...) {
|
|
| 1467 | 143x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1468 | 142x |
assert_integer( |
| 1469 | 142x |
grade, |
| 1470 | 142x |
min.len = 1, |
| 1471 | 142x |
max.len = length(model@params@mean) - 1, |
| 1472 | 142x |
lower = 0, |
| 1473 | 142x |
upper = length(model@params@mean) - 1 |
| 1474 |
) |
|
| 1475 | 140x |
assert_subset( |
| 1476 | 140x |
names(samples), |
| 1477 | 140x |
c(paste0("alpha", 0:(length(model@params@mean) - 1)), "beta")
|
| 1478 |
) |
|
| 1479 | 140x |
assert_length(dose, len = size(samples)) |
| 1480 | 140x |
assert_flag(cumulative) |
| 1481 | ||
| 1482 | 138x |
rv <- lapply( |
| 1483 | 138x |
grade, |
| 1484 | 138x |
function(g) {
|
| 1485 | 148x |
alpha <- samples@data[[paste0("alpha", g)]]
|
| 1486 | 148x |
beta <- samples@data$beta |
| 1487 | 148x |
ref_dose <- as.numeric(model@ref_dose) |
| 1488 | ||
| 1489 | 148x |
cumulative_prob <- plogis(alpha + beta * log(dose / ref_dose)) |
| 1490 | 148x |
if (cumulative | g == as.integer(length(model@params@mean) - 1)) {
|
| 1491 | 138x |
return(cumulative_prob) |
| 1492 |
} |
|
| 1493 | ||
| 1494 |
# Calculate grade-specific probabilities |
|
| 1495 | 10x |
alpha0 <- samples@data[[paste0("alpha", g + 1)]]
|
| 1496 | 10x |
grade_prob <- cumulative_prob - |
| 1497 | 10x |
plogis(alpha0 + beta * log(dose / ref_dose)) |
| 1498 | 10x |
return(grade_prob) |
| 1499 |
} |
|
| 1500 |
) |
|
| 1501 | 138x |
if (length(rv) == 1) {
|
| 1502 | 128x |
return(rv[[1]]) |
| 1503 |
} |
|
| 1504 | 10x |
names(rv) <- as.character(grade) |
| 1505 | 10x |
return(rv) |
| 1506 |
} |
|
| 1507 |
) |
|
| 1508 | ||
| 1509 |
# efficacy ---- |
|
| 1510 | ||
| 1511 |
## generic ---- |
|
| 1512 | ||
| 1513 |
#' Computing Expected Efficacy for a Given Dose, Model and Samples |
|
| 1514 |
#' |
|
| 1515 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1516 |
#' |
|
| 1517 |
#' A function that computes the value of expected efficacy at a specified dose |
|
| 1518 |
#' level, based on the model specific parameters. The model parameters (samples) |
|
| 1519 |
#' are obtained based on prior specified in form of pseudo data combined with |
|
| 1520 |
#' observed responses (if any). |
|
| 1521 |
#' |
|
| 1522 |
#' @details The `efficacy()` function computes the expected efficacy for given |
|
| 1523 |
#' doses, using samples of the model parameter(s). |
|
| 1524 |
#' If you work with multivariate model parameters, then assume that your model |
|
| 1525 |
#' specific `efficacy()` method receives a samples matrix where the rows |
|
| 1526 |
#' correspond to the sampling index, i.e. the layout is then |
|
| 1527 |
#' `nSamples x dimParameter`. |
|
| 1528 |
#' |
|
| 1529 |
#' @param dose (`numeric`)\cr the dose which is targeted. |
|
| 1530 |
#' The following recycling rule applies when `samples` is not missing: vectors |
|
| 1531 |
#' of size 1 will be recycled to the size of the sample |
|
| 1532 |
#' (i.e. `size(samples)`). Otherwise, `dose` must have the same |
|
| 1533 |
#' size as the sample. |
|
| 1534 |
#' @param model (`ModelEff`)\cr the efficacy model with pseudo data prior. |
|
| 1535 |
#' @param samples (`Samples`)\cr samples of model's parameters that will be |
|
| 1536 |
#' used to compute expected efficacy values. Can also be missing for some |
|
| 1537 |
#' models. |
|
| 1538 |
#' @param ... model specific parameters when `samples` are not used. |
|
| 1539 |
#' |
|
| 1540 |
#' @return A `numeric` vector with the values of expected efficacy. |
|
| 1541 |
#' If non-scalar `samples` were used, then every element in the returned vector |
|
| 1542 |
#' corresponds to one element of a sample. Hence, in this case, the output |
|
| 1543 |
#' vector is of the same length as the sample vector. If scalar `samples` were |
|
| 1544 |
#' used or no `samples` were used, e.g. for pseudo DLE/toxicity `model`, |
|
| 1545 |
#' then the output is of the same length as the length of the `dose`. |
|
| 1546 |
#' |
|
| 1547 |
#' @seealso [dose()], [prob()]. |
|
| 1548 |
#' |
|
| 1549 |
#' @export |
|
| 1550 |
#' @example examples/Model-method-efficacy.R |
|
| 1551 |
setGeneric( |
|
| 1552 |
name = "efficacy", |
|
| 1553 |
def = function(dose, model, samples, ...) {
|
|
| 1554 | 2636x |
standardGeneric("efficacy")
|
| 1555 |
}, |
|
| 1556 |
valueClass = "numeric" |
|
| 1557 |
) |
|
| 1558 | ||
| 1559 |
## Effloglog ---- |
|
| 1560 | ||
| 1561 |
#' @describeIn efficacy compute the expected efficacy at a specified dose level, |
|
| 1562 |
#' based on the samples of [`Effloglog`] model parameters. |
|
| 1563 |
#' |
|
| 1564 |
#' @aliases efficacy-Effloglog |
|
| 1565 |
#' @export |
|
| 1566 |
#' |
|
| 1567 |
setMethod( |
|
| 1568 |
f = "efficacy", |
|
| 1569 |
signature = signature( |
|
| 1570 |
dose = "numeric", |
|
| 1571 |
model = "Effloglog", |
|
| 1572 |
samples = "Samples" |
|
| 1573 |
), |
|
| 1574 |
definition = function(dose, model, samples) {
|
|
| 1575 | 1430x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1576 | 1429x |
assert_subset(c("theta1", "theta2"), names(samples))
|
| 1577 | 1428x |
assert_length(dose, len = size(samples)) |
| 1578 | ||
| 1579 | 1427x |
theta1 <- samples@data$theta1 |
| 1580 | 1427x |
theta2 <- samples@data$theta2 |
| 1581 | 1427x |
constant <- model@const |
| 1582 | 1427x |
theta1 + theta2 * log(log(dose + constant)) |
| 1583 |
} |
|
| 1584 |
) |
|
| 1585 | ||
| 1586 |
## Effloglog-noSamples ---- |
|
| 1587 | ||
| 1588 |
#' @describeIn efficacy compute the expected efficacy at a specified dose level, |
|
| 1589 |
#' based on the [`Effloglog`] model parameters. |
|
| 1590 |
#' All model parameters (except `dose`) should be present in the `model` object. |
|
| 1591 |
#' |
|
| 1592 |
#' @aliases efficacy-Effloglog-noSamples |
|
| 1593 |
#' @export |
|
| 1594 |
#' |
|
| 1595 |
setMethod( |
|
| 1596 |
f = "efficacy", |
|
| 1597 |
signature = signature( |
|
| 1598 |
dose = "numeric", |
|
| 1599 |
model = "Effloglog", |
|
| 1600 |
samples = "missing" |
|
| 1601 |
), |
|
| 1602 |
definition = function(dose, model) {
|
|
| 1603 | 1131x |
model_params <- h_slots(model, c("theta1", "theta2"))
|
| 1604 | 1131x |
nsamples <- length(model_params[[1]]) |
| 1605 | 1131x |
samples <- Samples( |
| 1606 | 1131x |
data = model_params, |
| 1607 | 1131x |
options = McmcOptions(samples = nsamples) |
| 1608 |
) |
|
| 1609 | ||
| 1610 | 1131x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1611 | 1130x |
assert_length(dose, len = nsamples) |
| 1612 | ||
| 1613 | 1130x |
efficacy(dose, model, samples) |
| 1614 |
} |
|
| 1615 |
) |
|
| 1616 | ||
| 1617 |
## EffFlexi ---- |
|
| 1618 | ||
| 1619 |
#' @describeIn efficacy compute the expected efficacy at a specified dose level, |
|
| 1620 |
#' based on the samples of [`EffFlexi`] model parameters. If a given dose in |
|
| 1621 |
#' the `dose` vector is from outside of the dose grid range, the `NA_real` is |
|
| 1622 |
#' returned for this dose and the warning is thrown. |
|
| 1623 |
#' |
|
| 1624 |
#' @aliases efficacy-EffFlexi |
|
| 1625 |
#' @export |
|
| 1626 |
#' |
|
| 1627 |
setMethod( |
|
| 1628 |
f = "efficacy", |
|
| 1629 |
signature = signature( |
|
| 1630 |
dose = "numeric", |
|
| 1631 |
model = "EffFlexi", |
|
| 1632 |
samples = "Samples" |
|
| 1633 |
), |
|
| 1634 |
definition = function(dose, model, samples) {
|
|
| 1635 | 75x |
n_samples <- size(samples) |
| 1636 | 75x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
| 1637 | 74x |
assert_true(model@data@nGrid >= 1L) |
| 1638 | 74x |
assert_subset("ExpEff", names(samples))
|
| 1639 | 73x |
assert_length(dose, len = n_samples) |
| 1640 | ||
| 1641 | 72x |
dose_grid <- model@data@doseGrid |
| 1642 | 72x |
dose_level <- match_within_tolerance(dose, dose_grid) |
| 1643 | 72x |
dose[which(!is.na(dose_level))] <- dose_grid[stats::na.omit(dose_level)] |
| 1644 | ||
| 1645 |
# linear interpolation, NA for doses that are outside of the dose_grid range. |
|
| 1646 | 72x |
samples_eff <- samples@data$ExpEff |
| 1647 | 72x |
eff <- if (length(dose) == n_samples) {
|
| 1648 | 4x |
sapply(seq_len(n_samples), function(s) {
|
| 1649 | 16x |
stats::approx(dose_grid, samples_eff[s, ], xout = dose[s])$y |
| 1650 |
}) |
|
| 1651 |
} else {
|
|
| 1652 | 68x |
eff <- apply(samples_eff, 1, function(s) {
|
| 1653 | 122420x |
stats::approx(dose_grid, s, xout = dose)$y |
| 1654 |
}) |
|
| 1655 | 68x |
as.vector(eff) |
| 1656 |
} |
|
| 1657 | ||
| 1658 | 72x |
if (any(is.na(eff))) {
|
| 1659 | 4x |
warning( |
| 1660 | 4x |
paste( |
| 1661 | 4x |
"At least one dose out of", |
| 1662 | 4x |
paste(dose, collapse = ", "), |
| 1663 | 4x |
"is outside of the dose grid range" |
| 1664 |
) |
|
| 1665 |
) |
|
| 1666 |
} |
|
| 1667 | 72x |
eff |
| 1668 |
} |
|
| 1669 |
) |
|
| 1670 | ||
| 1671 |
# biomarker ---- |
|
| 1672 | ||
| 1673 |
## generic ---- |
|
| 1674 | ||
| 1675 |
#' Get the Biomarker Levels for a Given Dual-Endpoint Model, Given Dose Levels and Samples |
|
| 1676 |
#' |
|
| 1677 |
#' @details This function simply returns a specific columns (with the indices equal |
|
| 1678 |
#' to `xLevel`) of the biomarker samples matrix, which is included in the the |
|
| 1679 |
#' `samples` object. |
|
| 1680 |
#' |
|
| 1681 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1682 |
#' |
|
| 1683 |
#' @param xLevel (`integer`)\cr the levels for the doses the patients have been |
|
| 1684 |
#' given w.r.t dose grid. See [`Data`] for more details. |
|
| 1685 |
#' @param model (`DualEndpoint`)\cr the model. |
|
| 1686 |
#' @param samples (`Samples`)\cr the samples of model's parameters that store |
|
| 1687 |
#' the value of biomarker levels for all doses on the dose grid. |
|
| 1688 |
#' @param ... not used. |
|
| 1689 |
#' |
|
| 1690 |
#' @return The biomarker levels. |
|
| 1691 |
#' |
|
| 1692 |
#' @export |
|
| 1693 |
#' @example examples/Model-method-biomarker.R |
|
| 1694 |
#' |
|
| 1695 |
setGeneric( |
|
| 1696 |
name = "biomarker", |
|
| 1697 |
def = function(xLevel, model, samples, ...) {
|
|
| 1698 | 73x |
standardGeneric("biomarker")
|
| 1699 |
}, |
|
| 1700 |
valueClass = c("numeric", "array")
|
|
| 1701 |
) |
|
| 1702 | ||
| 1703 |
## DualEndpoint ---- |
|
| 1704 | ||
| 1705 |
#' @describeIn biomarker |
|
| 1706 |
#' |
|
| 1707 |
#' @aliases biomarker-DualEndpoint |
|
| 1708 |
#' @export |
|
| 1709 |
#' |
|
| 1710 |
setMethod( |
|
| 1711 |
f = "biomarker", |
|
| 1712 |
signature = signature( |
|
| 1713 |
xLevel = "integer", |
|
| 1714 |
model = "DualEndpoint", |
|
| 1715 |
samples = "Samples" |
|
| 1716 |
), |
|
| 1717 |
def = function(xLevel, model, samples, ...) {
|
|
| 1718 | 72x |
assert_integer( |
| 1719 | 72x |
xLevel, |
| 1720 | 72x |
lower = 1, |
| 1721 | 72x |
upper = ncol(samples@data$betaW), |
| 1722 | 72x |
any.missing = FALSE, |
| 1723 | 72x |
min.len = 1 |
| 1724 |
) |
|
| 1725 | ||
| 1726 | 71x |
samples@data$betaW[, xLevel] |
| 1727 |
} |
|
| 1728 |
) |
|
| 1729 | ||
| 1730 |
# gain ---- |
|
| 1731 | ||
| 1732 |
## generic ---- |
|
| 1733 | ||
| 1734 |
#' Compute Gain Values based on Pseudo DLE and a Pseudo Efficacy Models and |
|
| 1735 |
#' Using Optional Samples. |
|
| 1736 |
#' |
|
| 1737 |
#' @details This function computes the gain values for a given dose level, |
|
| 1738 |
#' pseudo DLE and Efficacy models as well as a given DLE and Efficacy samples. |
|
| 1739 |
#' |
|
| 1740 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1741 |
#' |
|
| 1742 |
#' @param dose (`number` or `numeric`)\cr the dose which is targeted. |
|
| 1743 |
#' The following recycling rule applies when samples are not missing: vectors |
|
| 1744 |
#' of size 1 will be recycled to the size of the sample. Otherwise, `dose` |
|
| 1745 |
#' must have the same size as the sample. |
|
| 1746 |
#' @param model_dle (`ModelTox`)\cr pseudo DLE (dose-limiting events)/toxicity |
|
| 1747 |
#' model. |
|
| 1748 |
#' @param samples_dle (`Samples`)\cr the samples of model's |
|
| 1749 |
#' parameters that will be used to compute toxicity probabilities. Can also be |
|
| 1750 |
#' missing for some models. |
|
| 1751 |
#' @param model_eff (`ModelEff`)\cr the efficacy model with pseudo data prior. |
|
| 1752 |
#' @param samples_eff (`Samples`)\cr samples of model's parameters that will be |
|
| 1753 |
#' used to compute expected efficacy values. Can also be missing for some |
|
| 1754 |
#' models. |
|
| 1755 |
#' @param ... not used. |
|
| 1756 |
#' |
|
| 1757 |
#' @return The gain values. |
|
| 1758 |
#' |
|
| 1759 |
#' @export |
|
| 1760 |
#' @example examples/Model-method-gain.R |
|
| 1761 |
#' |
|
| 1762 |
setGeneric( |
|
| 1763 |
name = "gain", |
|
| 1764 |
def = function(dose, model_dle, samples_dle, model_eff, samples_eff, ...) {
|
|
| 1765 | 1309x |
standardGeneric("gain")
|
| 1766 |
}, |
|
| 1767 |
valueClass = "numeric" |
|
| 1768 |
) |
|
| 1769 | ||
| 1770 |
## ModelTox-ModelEff ---- |
|
| 1771 | ||
| 1772 |
#' @describeIn gain |
|
| 1773 |
#' |
|
| 1774 |
#' @aliases gain-ModelTox-ModelEff |
|
| 1775 |
#' @export |
|
| 1776 |
#' |
|
| 1777 |
setMethod( |
|
| 1778 |
f = "gain", |
|
| 1779 |
signature = signature( |
|
| 1780 |
dose = "numeric", |
|
| 1781 |
model_dle = "ModelTox", |
|
| 1782 |
samples_dle = "Samples", |
|
| 1783 |
model_eff = "ModelEff", |
|
| 1784 |
samples_eff = "Samples" |
|
| 1785 |
), |
|
| 1786 |
definition = function( |
|
| 1787 |
dose, |
|
| 1788 |
model_dle, |
|
| 1789 |
samples_dle, |
|
| 1790 |
model_eff, |
|
| 1791 |
samples_eff, |
|
| 1792 |
... |
|
| 1793 |
) {
|
|
| 1794 | 212x |
dle <- prob(dose, model_dle, samples_dle) |
| 1795 | 211x |
eff <- efficacy(dose, model_eff, samples_eff) |
| 1796 | 211x |
assert_length(dle, len = length(eff)) |
| 1797 | 211x |
eff / (1 + (dle / (1 - dle))) |
| 1798 |
} |
|
| 1799 |
) |
|
| 1800 | ||
| 1801 |
## ModelTox-ModelEff-noSamples---- |
|
| 1802 | ||
| 1803 |
#' @describeIn gain Compute the gain value for a given dose level, pseudo DLE |
|
| 1804 |
#' and Efficacy models without DLE and the Efficacy samples. |
|
| 1805 |
#' |
|
| 1806 |
#' @aliases gain-ModelTox-Effloglog-noSamples |
|
| 1807 |
#' @export |
|
| 1808 |
#' @example examples/Model-method-gainNoSamples.R |
|
| 1809 |
#' |
|
| 1810 |
setMethod( |
|
| 1811 |
f = "gain", |
|
| 1812 |
signature = signature( |
|
| 1813 |
dose = "numeric", |
|
| 1814 |
model_dle = "ModelTox", |
|
| 1815 |
samples_dle = "missing", |
|
| 1816 |
model_eff = "Effloglog", |
|
| 1817 |
samples_eff = "missing" |
|
| 1818 |
), |
|
| 1819 |
definition = function(dose, model_dle, model_eff, ...) {
|
|
| 1820 | 1097x |
dle <- prob(dose, model_dle) |
| 1821 | 1097x |
eff <- efficacy(dose, model_eff) |
| 1822 | 1097x |
assert_length(dle, len = length(eff)) |
| 1823 | 1097x |
eff / (1 + (dle / (1 - dle))) |
| 1824 |
} |
|
| 1825 |
) |
|
| 1826 | ||
| 1827 |
# update ---- |
|
| 1828 | ||
| 1829 |
## ModelPseudo ---- |
|
| 1830 | ||
| 1831 |
#' Update method for the [`ModelPseudo`] model class. This is a method to update |
|
| 1832 |
#' the model class slots (estimates, parameters, variables and etc.), when the |
|
| 1833 |
#' new data (e.g. new observations of responses) are available. This method is |
|
| 1834 |
#' mostly used to obtain new modal estimates for pseudo model parameters. |
|
| 1835 |
#' |
|
| 1836 |
#' @param object (`ModelPseudo`)\cr the model to update. |
|
| 1837 |
#' @param data (`Data`)\cr all currently available of data. |
|
| 1838 |
#' @param ... not used. |
|
| 1839 |
#' |
|
| 1840 |
#' @return the new [`ModelPseudo`] class object. |
|
| 1841 |
#' |
|
| 1842 |
#' @aliases update-ModelPseudo |
|
| 1843 |
#' @export |
|
| 1844 |
#' @example examples/Model-method-update.R |
|
| 1845 |
#' |
|
| 1846 |
setMethod( |
|
| 1847 |
f = "update", |
|
| 1848 |
signature = signature( |
|
| 1849 |
object = "ModelPseudo" |
|
| 1850 |
), |
|
| 1851 |
definition = function(object, data, ...) {
|
|
| 1852 | 214x |
assert_class(data, "Data") |
| 1853 | ||
| 1854 | 213x |
constructor_name <- class(object) |
| 1855 | 213x |
arg_names <- setdiff(formalArgs(constructor_name), "data") |
| 1856 | 213x |
do.call( |
| 1857 | 213x |
constructor_name, |
| 1858 | 213x |
c(h_slots(object, arg_names), list(data = data)) |
| 1859 |
) |
|
| 1860 |
} |
|
| 1861 |
) |
|
| 1862 | ||
| 1863 |
# tidy ---- |
|
| 1864 | ||
| 1865 |
# LogisticIndepBeta |
|
| 1866 | ||
| 1867 |
#' Tidy Method for the [`LogisticIndepBeta`] Class |
|
| 1868 |
#' |
|
| 1869 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1870 |
#' |
|
| 1871 |
#' A method that tidies a [`LogisticIndepBeta`] object. |
|
| 1872 |
#' |
|
| 1873 |
#' @return The [`list`] of [`tibble`] objects. |
|
| 1874 |
#' |
|
| 1875 |
#' @aliases tidy-LogisticIndepBeta |
|
| 1876 |
#' @rdname tidy |
|
| 1877 |
#' @method tidy LogisticIndepBeta |
|
| 1878 |
#' @export |
|
| 1879 |
#' @example examples/LogisticIndepBeta-method-tidy.R |
|
| 1880 |
#' |
|
| 1881 |
setMethod( |
|
| 1882 |
f = "tidy", |
|
| 1883 |
signature = signature(x = "LogisticIndepBeta"), |
|
| 1884 |
definition = function(x, ...) {
|
|
| 1885 | 36x |
start <- callNextMethod() |
| 1886 |
# N$DLEweights Dose$DLEdose Tox$binDLE |
|
| 1887 | 36x |
pseudoData <- tibble::tibble( |
| 1888 | 36x |
Dose = dplyr::pull(start$DLEdose), |
| 1889 | 36x |
N = dplyr::pull(start$DLEweights), |
| 1890 | 36x |
Tox = dplyr::pull(start$binDLE) |
| 1891 |
) |
|
| 1892 | 36x |
params <- tibble::tibble( |
| 1893 | 36x |
Param = c("Phi1", "Phi2"),
|
| 1894 | 36x |
mean = c(dplyr::pull(start$phi1), dplyr::pull(start$phi2)), |
| 1895 | 36x |
cov = as.list(start$Pcov) |
| 1896 |
) |
|
| 1897 | 36x |
list( |
| 1898 | 36x |
pseudoData = pseudoData, |
| 1899 | 36x |
data = start$data, |
| 1900 | 36x |
params = params |
| 1901 |
) %>% |
|
| 1902 | 36x |
h_tidy_class(x) |
| 1903 |
} |
|
| 1904 |
) |
|
| 1905 | ||
| 1906 |
# Effloglog |
|
| 1907 | ||
| 1908 |
#' Tidy Method for the [`Effloglog`] Class |
|
| 1909 |
#' |
|
| 1910 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1911 |
#' |
|
| 1912 |
#' A method that tidies a [`Effloglog`] object. |
|
| 1913 |
#' |
|
| 1914 |
#' @return The [`list`] of [`tibble`] objects. |
|
| 1915 |
#' |
|
| 1916 |
#' @aliases tidy-Effloglog |
|
| 1917 |
#' @rdname tidy |
|
| 1918 |
#' @method tidy Edffloglog |
|
| 1919 |
#' @export |
|
| 1920 |
#' @example examples/Effloglog-method-tidy.R |
|
| 1921 |
#' |
|
| 1922 |
setMethod( |
|
| 1923 |
f = "tidy", |
|
| 1924 |
signature = signature(x = "Effloglog"), |
|
| 1925 |
definition = function(x, ...) {
|
|
| 1926 | 22x |
start <- callNextMethod() |
| 1927 | 22x |
pseudoData <- tibble::tibble( |
| 1928 | 22x |
Dose = dplyr::pull(start$eff_dose), |
| 1929 | 22x |
Response = dplyr::pull(start$eff) |
| 1930 |
) |
|
| 1931 | 22x |
params <- tibble::tibble( |
| 1932 | 22x |
Param = c("theta1", "theta2"),
|
| 1933 | 22x |
mean = c(dplyr::pull(start$theta1), dplyr::pull(start$theta2)), |
| 1934 | 22x |
cov = as.list(start$Pcov) |
| 1935 |
) |
|
| 1936 | 22x |
list( |
| 1937 | 22x |
pseudoData = pseudoData, |
| 1938 | 22x |
data = start$data, |
| 1939 | 22x |
params = params |
| 1940 |
) %>% |
|
| 1941 | 22x |
h_tidy_class(x) |
| 1942 |
} |
|
| 1943 |
) |
| 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 | 520x |
if (!missing(doselimit)) {
|
| 45 | 461x |
assert_number(doselimit, lower = 0, finite = FALSE) |
| 46 |
} |
|
| 47 | 520x |
standardGeneric("nextBest")
|
| 48 |
}, |
|
| 49 |
valueClass = "list" |
|
| 50 |
) |
|
| 51 | ||
| 52 |
## NextBestMTD ---- |
|
| 53 | ||
| 54 |
#' @describeIn nextBest find the next best dose based on the MTD rule. |
|
| 55 |
#' |
|
| 56 |
#' @aliases nextBest-NextBestMTD |
|
| 57 |
#' |
|
| 58 |
#' @export |
|
| 59 |
#' @example examples/Rules-method-nextBest-NextBestMTD.R |
|
| 60 |
#' |
|
| 61 |
setMethod( |
|
| 62 |
f = "nextBest", |
|
| 63 |
signature = signature( |
|
| 64 |
nextBest = "NextBestMTD", |
|
| 65 |
doselimit = "numeric", |
|
| 66 |
samples = "Samples", |
|
| 67 |
model = "GeneralModel", |
|
| 68 |
data = "Data" |
|
| 69 |
), |
|
| 70 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 71 |
# Generate the MTD samples and derive the next best dose. |
|
| 72 | 4x |
dose_target_samples <- dose(x = nextBest@target, model, samples, ...) |
| 73 | 4x |
dose_target <- nextBest@derive(dose_target_samples) |
| 74 | ||
| 75 |
# Round to the next possible grid point. |
|
| 76 | 4x |
doses_eligible <- h_next_best_eligible_doses( |
| 77 | 4x |
data@doseGrid, |
| 78 | 4x |
doselimit, |
| 79 | 4x |
data@placebo |
| 80 |
) |
|
| 81 | 4x |
next_dose_level <- which.min(abs(doses_eligible - dose_target)) |
| 82 | 4x |
next_dose <- doses_eligible[next_dose_level] |
| 83 | ||
| 84 |
# Create a plot. |
|
| 85 | 4x |
p <- ggplot( |
| 86 | 4x |
data = data.frame(x = dose_target_samples), |
| 87 | 4x |
aes(.data$x) |
| 88 |
) + |
|
| 89 | 4x |
geom_density(colour = "grey50") + |
| 90 | 4x |
coord_cartesian(xlim = range(data@doseGrid)) + |
| 91 | 4x |
geom_vline(xintercept = dose_target, colour = "black", lwd = 1.1) + |
| 92 | 4x |
geom_text( |
| 93 | 4x |
data = data.frame(x = dose_target), |
| 94 | 4x |
aes(.data$x, 0), |
| 95 | 4x |
label = "Est", |
| 96 | 4x |
vjust = -0.5, |
| 97 | 4x |
hjust = 0.5, |
| 98 | 4x |
colour = "black", |
| 99 | 4x |
angle = 90 |
| 100 |
) + |
|
| 101 | 4x |
xlab("MTD") +
|
| 102 | 4x |
ylab("Posterior density")
|
| 103 | ||
| 104 | 4x |
if (is.finite(doselimit)) {
|
| 105 | 2x |
p <- p + |
| 106 | 2x |
geom_vline(xintercept = doselimit, colour = "red", lwd = 1.1) + |
| 107 | 2x |
geom_text( |
| 108 | 2x |
data = data.frame(x = doselimit), |
| 109 | 2x |
aes(.data$x, 0), |
| 110 | 2x |
label = "Max", |
| 111 | 2x |
vjust = -0.5, |
| 112 | 2x |
hjust = -0.5, |
| 113 | 2x |
colour = "red", |
| 114 | 2x |
angle = 90 |
| 115 |
) |
|
| 116 |
} |
|
| 117 | ||
| 118 | 4x |
p <- p + |
| 119 | 4x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
| 120 | 4x |
geom_text( |
| 121 | 4x |
data = data.frame(x = next_dose), |
| 122 | 4x |
aes(.data$x, 0), |
| 123 | 4x |
label = "Next", |
| 124 | 4x |
vjust = -0.5, |
| 125 | 4x |
hjust = -1.5, |
| 126 | 4x |
colour = "blue", |
| 127 | 4x |
angle = 90 |
| 128 |
) |
|
| 129 | ||
| 130 | 4x |
list(value = next_dose, plot = p) |
| 131 |
} |
|
| 132 |
) |
|
| 133 | ||
| 134 |
## NextBestNCRM ---- |
|
| 135 | ||
| 136 |
#' @describeIn nextBest find the next best dose based on the NCRM method. The |
|
| 137 |
#' additional element `probs` in the output's list contains the target and |
|
| 138 |
#' overdosing probabilities (across all doses in the dose grid) used in the |
|
| 139 |
#' derivation of the next best dose. |
|
| 140 |
#' |
|
| 141 |
#' @aliases nextBest-NextBestNCRM |
|
| 142 |
#' |
|
| 143 |
#' @export |
|
| 144 |
#' @example examples/Rules-method-nextBest-NextBestNCRM.R |
|
| 145 |
#' |
|
| 146 |
setMethod( |
|
| 147 |
f = "nextBest", |
|
| 148 |
signature = signature( |
|
| 149 |
nextBest = "NextBestNCRM", |
|
| 150 |
doselimit = "numeric", |
|
| 151 |
samples = "Samples", |
|
| 152 |
model = "GeneralModel", |
|
| 153 |
data = "Data" |
|
| 154 |
), |
|
| 155 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 156 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 157 | 267x |
prob_samples <- sapply( |
| 158 | 267x |
data@doseGrid, |
| 159 | 267x |
prob, |
| 160 | 267x |
model = model, |
| 161 | 267x |
samples = samples, |
| 162 |
... |
|
| 163 |
) |
|
| 164 | ||
| 165 |
# Estimates of posterior probabilities that are based on the prob. samples |
|
| 166 |
# which are within overdose/target interval. |
|
| 167 | 267x |
prob_overdose <- colMeans(h_in_range( |
| 168 | 267x |
prob_samples, |
| 169 | 267x |
nextBest@overdose, |
| 170 | 267x |
bounds_closed = c(FALSE, TRUE) |
| 171 |
)) |
|
| 172 | 267x |
prob_target <- colMeans(h_in_range(prob_samples, nextBest@target)) |
| 173 | ||
| 174 |
# Eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
| 175 | 267x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 176 | 267x |
data@doseGrid, |
| 177 | 267x |
doselimit, |
| 178 | 267x |
data@placebo, |
| 179 | 267x |
levels = TRUE |
| 180 |
) & |
|
| 181 | 267x |
(prob_overdose <= nextBest@max_overdose_prob) |
| 182 | ||
| 183 | 267x |
next_dose <- if (any(is_dose_eligible)) {
|
| 184 |
# If maximum target probability is higher than some numerical threshold, |
|
| 185 |
# then take that level, otherwise stick to the maximum level that is OK. |
|
| 186 |
# next_best_level is relative to eligible doses. |
|
| 187 | 252x |
next_best_level <- ifelse( |
| 188 | 252x |
test = any(prob_target[is_dose_eligible] > 0.05), |
| 189 | 252x |
yes = which.max(prob_target[is_dose_eligible]), |
| 190 | 252x |
no = sum(is_dose_eligible) |
| 191 |
) |
|
| 192 | 252x |
data@doseGrid[is_dose_eligible][next_best_level] |
| 193 |
} else {
|
|
| 194 | 267x |
NA_real_ |
| 195 |
} |
|
| 196 | ||
| 197 |
# Build plots, first for the target probability. |
|
| 198 | 267x |
p1 <- ggplot() + |
| 199 | 267x |
geom_bar( |
| 200 | 267x |
data = data.frame(Dose = data@doseGrid, y = prob_target * 100), |
| 201 | 267x |
aes(x = .data$Dose, y = .data$y), |
| 202 | 267x |
stat = "identity", |
| 203 | 267x |
position = "identity", |
| 204 | 267x |
width = min(diff(data@doseGrid)) / 2, |
| 205 | 267x |
colour = "darkgreen", |
| 206 | 267x |
fill = "darkgreen" |
| 207 |
) + |
|
| 208 | 267x |
coord_cartesian(ylim = c(0, 100)) + |
| 209 | 267x |
ylab(paste("Target probability [%]"))
|
| 210 | ||
| 211 | 267x |
if (is.finite(doselimit)) {
|
| 212 | 264x |
p1 <- p1 + |
| 213 | 264x |
geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
| 214 |
} |
|
| 215 | ||
| 216 | 267x |
if (any(is_dose_eligible)) {
|
| 217 | 252x |
p1 <- p1 + |
| 218 | 252x |
geom_vline( |
| 219 | 252x |
xintercept = data@doseGrid[sum(is_dose_eligible)], |
| 220 | 252x |
lwd = 1.1, |
| 221 | 252x |
lty = 2, |
| 222 | 252x |
colour = "red" |
| 223 |
) + |
|
| 224 | 252x |
geom_point( |
| 225 | 252x |
data = data.frame( |
| 226 | 252x |
x = next_dose, |
| 227 | 252x |
y = prob_target[is_dose_eligible][next_best_level] * 100 + 0.03 |
| 228 |
), |
|
| 229 | 252x |
aes(x = x, y = y), |
| 230 | 252x |
size = 3, |
| 231 | 252x |
pch = 25, |
| 232 | 252x |
col = "red", |
| 233 | 252x |
bg = "red" |
| 234 |
) |
|
| 235 |
} |
|
| 236 | ||
| 237 |
# Second, for the overdosing probability. |
|
| 238 | 267x |
p2 <- ggplot() + |
| 239 | 267x |
geom_bar( |
| 240 | 267x |
data = data.frame(Dose = data@doseGrid, y = prob_overdose * 100), |
| 241 | 267x |
aes(x = .data$Dose, y = .data$y), |
| 242 | 267x |
stat = "identity", |
| 243 | 267x |
position = "identity", |
| 244 | 267x |
width = min(diff(data@doseGrid)) / 2, |
| 245 | 267x |
colour = "red", |
| 246 | 267x |
fill = "red" |
| 247 |
) + |
|
| 248 | 267x |
geom_hline( |
| 249 | 267x |
yintercept = nextBest@max_overdose_prob * 100, |
| 250 | 267x |
lwd = 1.1, |
| 251 | 267x |
lty = 2, |
| 252 | 267x |
colour = "black" |
| 253 |
) + |
|
| 254 | 267x |
ylim(c(0, 100)) + |
| 255 | 267x |
ylab("Overdose probability [%]")
|
| 256 | ||
| 257 |
# Place them below each other. |
|
| 258 | 267x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, nrow = 2) |
| 259 | ||
| 260 | 267x |
list( |
| 261 | 267x |
value = next_dose, |
| 262 | 267x |
plot = plot_joint, |
| 263 | 267x |
singlePlots = list(plot1 = p1, plot2 = p2), |
| 264 | 267x |
probs = cbind( |
| 265 | 267x |
dose = data@doseGrid, |
| 266 | 267x |
target = prob_target, |
| 267 | 267x |
overdose = prob_overdose |
| 268 |
) |
|
| 269 |
) |
|
| 270 |
} |
|
| 271 |
) |
|
| 272 | ||
| 273 |
## NextBestNCRM-DataParts ---- |
|
| 274 | ||
| 275 |
#' @describeIn nextBest find the next best dose based on the NCRM method when |
|
| 276 |
#' two parts trial is used. |
|
| 277 |
#' |
|
| 278 |
#' @aliases nextBest-NextBestNCRM-DataParts |
|
| 279 |
#' |
|
| 280 |
#' @export |
|
| 281 |
#' @example examples/Rules-method-nextBest-NextBestNCRM-DataParts.R |
|
| 282 |
#' |
|
| 283 |
setMethod( |
|
| 284 |
f = "nextBest", |
|
| 285 |
signature = signature( |
|
| 286 |
nextBest = "NextBestNCRM", |
|
| 287 |
doselimit = "numeric", |
|
| 288 |
samples = "Samples", |
|
| 289 |
model = "GeneralModel", |
|
| 290 |
data = "DataParts" |
|
| 291 |
), |
|
| 292 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 293 |
# Exception when we are in part I or about to start part II! |
|
| 294 | 4x |
if (all(data@part == 1L)) {
|
| 295 |
# Propose the highest possible dose (assuming that the dose limit came |
|
| 296 |
# from reasonable increments rule, i.e. incrementsRelativeParts). |
|
| 297 | 2x |
if (is.infinite(doselimit)) {
|
| 298 | 1x |
stop("A finite doselimit needs to be specified for Part I.")
|
| 299 |
} |
|
| 300 | 1x |
list(value = doselimit, plot = NULL) |
| 301 |
} else {
|
|
| 302 |
# Otherwise we will just do the standard thing. |
|
| 303 | 2x |
callNextMethod(nextBest, doselimit, samples, model, data, ...) |
| 304 |
} |
|
| 305 |
} |
|
| 306 |
) |
|
| 307 | ||
| 308 |
## NextBestNCRMLoss ---- |
|
| 309 | ||
| 310 |
#' @describeIn nextBest find the next best dose based on the NCRM method and |
|
| 311 |
#' loss function. |
|
| 312 |
#' |
|
| 313 |
#' @aliases nextBest-NextBestNCRMLoss |
|
| 314 |
#' |
|
| 315 |
#' @export |
|
| 316 |
#' @example examples/Rules-method-nextBest-NextBestNCRMLoss.R |
|
| 317 |
#' |
|
| 318 |
setMethod( |
|
| 319 |
"nextBest", |
|
| 320 |
signature = signature( |
|
| 321 |
nextBest = "NextBestNCRMLoss", |
|
| 322 |
doselimit = "numeric", |
|
| 323 |
samples = "Samples", |
|
| 324 |
model = "GeneralModel", |
|
| 325 |
data = "Data" |
|
| 326 |
), |
|
| 327 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 328 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 329 | 5x |
prob_samples <- sapply( |
| 330 | 5x |
data@doseGrid, |
| 331 | 5x |
prob, |
| 332 | 5x |
model = model, |
| 333 | 5x |
samples = samples, |
| 334 |
... |
|
| 335 |
) |
|
| 336 |
# Compute probabilities to be in target and overdose tox interval. |
|
| 337 | 5x |
prob_underdosing <- colMeans(prob_samples < nextBest@target[1]) |
| 338 | 5x |
prob_target <- colMeans(h_in_range(prob_samples, nextBest@target)) |
| 339 | 5x |
prob_overdose <- colMeans(h_in_range( |
| 340 | 5x |
prob_samples, |
| 341 | 5x |
nextBest@overdose, |
| 342 | 5x |
bounds_closed = c(FALSE, TRUE) |
| 343 |
)) |
|
| 344 | 5x |
prob_mean <- colMeans(prob_samples) |
| 345 | 5x |
prob_sd <- apply(prob_samples, 2, stats::sd) |
| 346 | ||
| 347 | 5x |
is_unacceptable_specified <- any(nextBest@unacceptable != c(1, 1)) |
| 348 | ||
| 349 | 5x |
prob_mat <- if (!is_unacceptable_specified) {
|
| 350 | 2x |
cbind( |
| 351 | 2x |
underdosing = prob_underdosing, |
| 352 | 2x |
target = prob_target, |
| 353 | 2x |
overdose = prob_overdose |
| 354 |
) |
|
| 355 |
} else {
|
|
| 356 | 3x |
prob_unacceptable <- colMeans( |
| 357 | 3x |
h_in_range( |
| 358 | 3x |
prob_samples, |
| 359 | 3x |
nextBest@unacceptable, |
| 360 | 3x |
bounds_closed = c(FALSE, TRUE) |
| 361 |
) |
|
| 362 |
) |
|
| 363 | 3x |
prob_excessive <- prob_overdose |
| 364 | 3x |
prob_overdose <- prob_excessive + prob_unacceptable |
| 365 | 3x |
cbind( |
| 366 | 3x |
underdosing = prob_underdosing, |
| 367 | 3x |
target = prob_target, |
| 368 | 3x |
excessive = prob_excessive, |
| 369 | 3x |
unacceptable = prob_unacceptable |
| 370 |
) |
|
| 371 |
} |
|
| 372 | ||
| 373 | 5x |
posterior_loss <- as.vector(nextBest@losses %*% t(prob_mat)) |
| 374 | 5x |
names(posterior_loss) <- data@doseGrid |
| 375 | ||
| 376 | 5x |
probs <- cbind( |
| 377 | 5x |
dose = data@doseGrid, |
| 378 | 5x |
prob_mat = prob_mat, |
| 379 | 5x |
mean = prob_mean, |
| 380 | 5x |
std_dev = prob_sd, |
| 381 | 5x |
posterior_loss = posterior_loss |
| 382 |
) |
|
| 383 | ||
| 384 |
# Eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
| 385 | 5x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 386 | 5x |
data@doseGrid, |
| 387 | 5x |
doselimit, |
| 388 | 5x |
data@placebo, |
| 389 | 5x |
levels = TRUE |
| 390 |
) & |
|
| 391 | 5x |
(prob_overdose < nextBest@max_overdose_prob) |
| 392 | ||
| 393 |
# Next best dose is the dose with the minimum loss function. |
|
| 394 | 5x |
next_dose <- if (any(is_dose_eligible)) {
|
| 395 | 5x |
next_best_level <- which.min(posterior_loss[is_dose_eligible]) |
| 396 | 5x |
data@doseGrid[is_dose_eligible][next_best_level] |
| 397 |
} else {
|
|
| 398 | 5x |
NA_real_ |
| 399 |
} |
|
| 400 | ||
| 401 |
# Build plot. |
|
| 402 | 5x |
p <- h_next_best_ncrm_loss_plot( |
| 403 | 5x |
prob_mat = prob_mat, |
| 404 | 5x |
posterior_loss = posterior_loss, |
| 405 | 5x |
max_overdose_prob = nextBest@max_overdose_prob, |
| 406 | 5x |
dose_grid = data@doseGrid, |
| 407 | 5x |
max_eligible_dose_level = sum(is_dose_eligible), |
| 408 | 5x |
doselimit = doselimit, |
| 409 | 5x |
next_dose = next_dose, |
| 410 | 5x |
is_unacceptable_specified = is_unacceptable_specified |
| 411 |
) |
|
| 412 | ||
| 413 | 5x |
c(list(value = next_dose, probs = probs), p) |
| 414 |
} |
|
| 415 |
) |
|
| 416 | ||
| 417 |
## NextBestThreePlusThree ---- |
|
| 418 | ||
| 419 |
#' @describeIn nextBest find the next best dose based on the 3+3 method. |
|
| 420 |
#' |
|
| 421 |
#' @aliases nextBest-NextBestThreePlusThree |
|
| 422 |
#' |
|
| 423 |
#' @export |
|
| 424 |
#' @example examples/Rules-method-nextBest-NextBestThreePlusThree.R |
|
| 425 |
#' |
|
| 426 |
setMethod( |
|
| 427 |
f = "nextBest", |
|
| 428 |
signature = signature( |
|
| 429 |
nextBest = "NextBestThreePlusThree", |
|
| 430 |
doselimit = "missing", |
|
| 431 |
samples = "missing", |
|
| 432 |
model = "missing", |
|
| 433 |
data = "Data" |
|
| 434 |
), |
|
| 435 |
definition = function(nextBest, doselimit, samples, model, data, ...) {
|
|
| 436 |
# The last dose level tested (not necessarily the maximum one). |
|
| 437 | 59x |
last_level <- tail(data@xLevel, 1L) |
| 438 | ||
| 439 |
# Get number of patients per grid's dose and DLT rate at the last level. |
|
| 440 | 59x |
nPatients <- table(factor(data@x, levels = data@doseGrid)) |
| 441 | 59x |
n_dlts_last_level <- sum(data@y[data@xLevel == last_level]) |
| 442 | 59x |
dlt_rate_last_level <- n_dlts_last_level / nPatients[last_level] |
| 443 | ||
| 444 | 59x |
level_change <- if (dlt_rate_last_level < 1 / 3) {
|
| 445 |
# Escalate it, unless this is the highest level or the higher dose was already tried. |
|
| 446 | 23x |
ifelse( |
| 447 | 23x |
(last_level == data@nGrid) || (nPatients[last_level + 1L] > 0), |
| 448 | 23x |
0L, |
| 449 | 23x |
1L |
| 450 |
) |
|
| 451 |
} else {
|
|
| 452 |
# Rate is too high, deescalate it, unless an edge case of 1/3, where the decision |
|
| 453 |
# depends on the num. of patients: if >3, then deescalate it, otherwise stay. |
|
| 454 | 36x |
ifelse( |
| 455 | 36x |
(dlt_rate_last_level == 1 / 3) && (nPatients[last_level] <= 3L), |
| 456 | 36x |
0L, |
| 457 | 36x |
-1L |
| 458 |
) |
|
| 459 |
} |
|
| 460 | 59x |
next_dose_level <- last_level + level_change |
| 461 | ||
| 462 |
# Do we stop here? Only if we have no MTD, or the next level has been tried |
|
| 463 |
# enough (more than three patients already). |
|
| 464 | 59x |
if (next_dose_level == 0L) {
|
| 465 | 3x |
next_dose <- NA |
| 466 | 3x |
stop_here <- TRUE |
| 467 |
} else {
|
|
| 468 | 56x |
next_dose <- data@doseGrid[next_dose_level] |
| 469 | 56x |
stop_here <- nPatients[next_dose_level] > 3L |
| 470 |
} |
|
| 471 | ||
| 472 | 59x |
list(value = next_dose, stopHere = stop_here) |
| 473 |
} |
|
| 474 |
) |
|
| 475 | ||
| 476 |
## NextBestDualEndpoint ---- |
|
| 477 | ||
| 478 |
#' @describeIn nextBest find the next best dose based on the dual endpoint |
|
| 479 |
#' model. The additional list element `probs` contains the target and |
|
| 480 |
#' overdosing probabilities (across all doses in the dose grid) used in the |
|
| 481 |
#' derivation of the next best dose. |
|
| 482 |
#' |
|
| 483 |
#' @aliases nextBest-NextBestDualEndpoint |
|
| 484 |
#' |
|
| 485 |
#' @export |
|
| 486 |
#' @example examples/Rules-method-nextBest-NextBestDualEndpoint.R |
|
| 487 |
#' |
|
| 488 |
setMethod( |
|
| 489 |
f = "nextBest", |
|
| 490 |
signature = signature( |
|
| 491 |
nextBest = "NextBestDualEndpoint", |
|
| 492 |
doselimit = "numeric", |
|
| 493 |
samples = "Samples", |
|
| 494 |
model = "DualEndpoint", |
|
| 495 |
data = "Data" |
|
| 496 |
), |
|
| 497 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 498 |
# Biomarker samples at the dose grid points. |
|
| 499 | 22x |
biom_samples <- samples@data$betaW |
| 500 | ||
| 501 | 22x |
prob_target <- if (nextBest@target_relative) {
|
| 502 |
# If 'Emax' parameter available, target biomarker level will be relative to 'Emax', |
|
| 503 |
# otherwise, it will be relative to the maximum biomarker level achieved |
|
| 504 |
# in dose range for a given sample. |
|
| 505 | 20x |
if ("Emax" %in% names(samples)) {
|
| 506 | 1x |
lwr <- nextBest@target[1] * samples@data$Emax |
| 507 | 1x |
upr <- nextBest@target[2] * samples@data$Emax |
| 508 | 1x |
colMeans(apply(biom_samples, 2L, function(s) (s >= lwr) & (s <= upr))) |
| 509 |
} else {
|
|
| 510 | 19x |
target_levels <- apply(biom_samples, 1L, function(x) {
|
| 511 | 8204x |
rng <- range(x) |
| 512 | 8204x |
min(which(h_in_range( |
| 513 | 8204x |
x, |
| 514 | 8204x |
nextBest@target * diff(rng) + rng[1] + c(0, 1e-10), |
| 515 | 8204x |
bounds_closed = c(FALSE, TRUE) |
| 516 |
))) |
|
| 517 |
}) |
|
| 518 | 19x |
prob_target <- as.vector(table(factor( |
| 519 | 19x |
target_levels, |
| 520 | 19x |
levels = 1:data@nGrid |
| 521 |
))) |
|
| 522 | 19x |
prob_target / nrow(biom_samples) |
| 523 |
} |
|
| 524 |
} else {
|
|
| 525 | 2x |
colMeans(h_in_range(biom_samples, nextBest@target)) |
| 526 |
} |
|
| 527 | ||
| 528 |
# Now, compute probabilities to be in overdose tox interval, then flag |
|
| 529 |
# eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
| 530 | 22x |
prob_samples <- sapply( |
| 531 | 22x |
data@doseGrid, |
| 532 | 22x |
prob, |
| 533 | 22x |
model = model, |
| 534 | 22x |
samples = samples |
| 535 |
) |
|
| 536 | 22x |
prob_overdose <- colMeans(h_in_range( |
| 537 | 22x |
prob_samples, |
| 538 | 22x |
nextBest@overdose, |
| 539 | 22x |
bounds_closed = c(FALSE, TRUE) |
| 540 |
)) |
|
| 541 | ||
| 542 | 22x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 543 | 22x |
data@doseGrid, |
| 544 | 22x |
doselimit, |
| 545 | 22x |
data@placebo, |
| 546 | 22x |
levels = TRUE |
| 547 |
) & |
|
| 548 | 22x |
(prob_overdose < nextBest@max_overdose_prob) |
| 549 | ||
| 550 | 22x |
next_dose <- if (any(is_dose_eligible)) {
|
| 551 |
# If maximum target probability is higher the threshold, then take that |
|
| 552 |
# level, otherwise stick to the maximum level that is eligible. |
|
| 553 |
# next_dose_level is relative to eligible doses. |
|
| 554 | 20x |
next_dose_level <- ifelse( |
| 555 | 20x |
test = any(prob_target[is_dose_eligible] > nextBest@target_thresh), |
| 556 | 20x |
yes = which.max(prob_target[is_dose_eligible]), |
| 557 | 20x |
no = sum(is_dose_eligible) |
| 558 |
) |
|
| 559 | 20x |
data@doseGrid[is_dose_eligible][next_dose_level] |
| 560 |
} else {
|
|
| 561 | 22x |
NA_real_ |
| 562 |
} |
|
| 563 | ||
| 564 |
# Build plots, first for the target probability. |
|
| 565 | 22x |
p1 <- ggplot() + |
| 566 | 22x |
geom_bar( |
| 567 | 22x |
data = data.frame(Dose = data@doseGrid, y = prob_target * 100), |
| 568 | 22x |
aes(x = .data$Dose, y = .data$y), |
| 569 | 22x |
stat = "identity", |
| 570 | 22x |
position = "identity", |
| 571 | 22x |
width = min(diff(data@doseGrid)) / 2, |
| 572 | 22x |
colour = "darkgreen", |
| 573 | 22x |
fill = "darkgreen" |
| 574 |
) + |
|
| 575 | 22x |
ylim(c(0, 100)) + |
| 576 | 22x |
ylab(paste("Target probability [%]"))
|
| 577 | ||
| 578 | 22x |
if (is.finite(doselimit)) {
|
| 579 | 21x |
p1 <- p1 + |
| 580 | 21x |
geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
| 581 |
} |
|
| 582 | ||
| 583 | 22x |
if (any(is_dose_eligible)) {
|
| 584 | 20x |
p1 <- p1 + |
| 585 | 20x |
geom_vline( |
| 586 | 20x |
xintercept = data@doseGrid[sum(is_dose_eligible)], |
| 587 | 20x |
lwd = 1.1, |
| 588 | 20x |
lty = 2, |
| 589 | 20x |
colour = "red" |
| 590 |
) + |
|
| 591 | 20x |
geom_point( |
| 592 | 20x |
data = data.frame( |
| 593 | 20x |
x = next_dose, |
| 594 | 20x |
y = prob_target[is_dose_eligible][next_dose_level] * 100 + 0.03 |
| 595 |
), |
|
| 596 | 20x |
aes(x = x, y = y), |
| 597 | 20x |
size = 3, |
| 598 | 20x |
pch = 25, |
| 599 | 20x |
col = "red", |
| 600 | 20x |
bg = "red" |
| 601 |
) |
|
| 602 |
} |
|
| 603 | ||
| 604 |
# Second, for the overdosing probability. |
|
| 605 | 22x |
p2 <- ggplot() + |
| 606 | 22x |
geom_bar( |
| 607 | 22x |
data = data.frame(Dose = data@doseGrid, y = prob_overdose * 100), |
| 608 | 22x |
aes(x = .data$Dose, y = .data$y), |
| 609 | 22x |
stat = "identity", |
| 610 | 22x |
position = "identity", |
| 611 | 22x |
width = min(diff(data@doseGrid)) / 2, |
| 612 | 22x |
colour = "red", |
| 613 | 22x |
fill = "red" |
| 614 |
) + |
|
| 615 | 22x |
geom_hline( |
| 616 | 22x |
yintercept = nextBest@max_overdose_prob * 100, |
| 617 | 22x |
lwd = 1.1, |
| 618 | 22x |
lty = 2, |
| 619 | 22x |
colour = "black" |
| 620 |
) + |
|
| 621 | 22x |
ylim(c(0, 100)) + |
| 622 | 22x |
ylab("Overdose probability [%]")
|
| 623 | ||
| 624 |
# Place them below each other. |
|
| 625 | 22x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, nrow = 2) |
| 626 | ||
| 627 | 22x |
list( |
| 628 | 22x |
value = next_dose, |
| 629 | 22x |
plot = plot_joint, |
| 630 | 22x |
singlePlots = list(plot1 = p1, plot2 = p2), |
| 631 | 22x |
probs = cbind( |
| 632 | 22x |
dose = data@doseGrid, |
| 633 | 22x |
target = prob_target, |
| 634 | 22x |
overdose = prob_overdose |
| 635 |
) |
|
| 636 |
) |
|
| 637 |
} |
|
| 638 |
) |
|
| 639 | ||
| 640 |
## NextBestMinDist ---- |
|
| 641 | ||
| 642 |
#' @describeIn nextBest gives the dose which is below the dose limit and has an |
|
| 643 |
#' estimated DLT probability which is closest to the target dose. |
|
| 644 |
#' |
|
| 645 |
#' @aliases nextBest-NextBestMinDist |
|
| 646 |
#' |
|
| 647 |
#' @export |
|
| 648 |
#' |
|
| 649 |
setMethod( |
|
| 650 |
f = "nextBest", |
|
| 651 |
signature = signature( |
|
| 652 |
nextBest = "NextBestMinDist", |
|
| 653 |
doselimit = "numeric", |
|
| 654 |
samples = "Samples", |
|
| 655 |
model = "GeneralModel", |
|
| 656 |
data = "Data" |
|
| 657 |
), |
|
| 658 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 659 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 660 | 3x |
prob_samples <- sapply( |
| 661 | 3x |
data@doseGrid, |
| 662 | 3x |
prob, |
| 663 | 3x |
model = model, |
| 664 | 3x |
samples = samples, |
| 665 |
... |
|
| 666 |
) |
|
| 667 | 3x |
dlt_prob <- colMeans(prob_samples) |
| 668 | ||
| 669 |
# Determine the dose with the closest distance. |
|
| 670 | 3x |
dose_target <- data@doseGrid[which.min(abs(dlt_prob - nextBest@target))] |
| 671 | ||
| 672 |
# Determine next dose. |
|
| 673 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
| 674 | 3x |
data@doseGrid, |
| 675 | 3x |
doselimit, |
| 676 | 3x |
data@placebo |
| 677 |
) |
|
| 678 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
| 679 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
| 680 | ||
| 681 |
# Create a plot. |
|
| 682 | 3x |
p <- ggplot( |
| 683 | 3x |
data = data.frame(x = data@doseGrid, y = dlt_prob), |
| 684 | 3x |
aes(.data$x, .data$y) |
| 685 |
) + |
|
| 686 | 3x |
geom_line(colour = "grey50") + |
| 687 | 3x |
geom_point(fill = "grey50", colour = "grey50") + |
| 688 | 3x |
coord_cartesian(xlim = range(data@doseGrid)) + |
| 689 | 3x |
scale_x_continuous( |
| 690 | 3x |
labels = as.character(data@doseGrid), |
| 691 | 3x |
breaks = data@doseGrid, |
| 692 | 3x |
guide = guide_axis(check.overlap = TRUE) |
| 693 |
) + |
|
| 694 | 3x |
geom_hline(yintercept = nextBest@target, linetype = "dotted") + |
| 695 | 3x |
geom_vline(xintercept = dose_target, colour = "black", lwd = 1.1) + |
| 696 | 3x |
geom_text( |
| 697 | 3x |
data = data.frame(x = dose_target), |
| 698 | 3x |
aes(.data$x, 0), |
| 699 | 3x |
label = "Est", |
| 700 | 3x |
vjust = -0.5, |
| 701 | 3x |
hjust = 0.5, |
| 702 | 3x |
colour = "black", |
| 703 | 3x |
angle = 90 |
| 704 |
) + |
|
| 705 | 3x |
xlab("Dose") +
|
| 706 | 3x |
ylab("Posterior toxicity probability")
|
| 707 | ||
| 708 | 3x |
if (is.finite(doselimit)) {
|
| 709 | 2x |
p <- p + |
| 710 | 2x |
geom_vline(xintercept = doselimit, colour = "red", lwd = 1.1) + |
| 711 | 2x |
geom_text( |
| 712 | 2x |
data = data.frame(x = doselimit), |
| 713 | 2x |
aes(.data$x, 0), |
| 714 | 2x |
label = "Max", |
| 715 | 2x |
vjust = -0.5, |
| 716 | 2x |
hjust = -0.5, |
| 717 | 2x |
colour = "red", |
| 718 | 2x |
angle = 90 |
| 719 |
) |
|
| 720 |
} |
|
| 721 | ||
| 722 | 3x |
p <- p + |
| 723 | 3x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
| 724 | 3x |
geom_text( |
| 725 | 3x |
data = data.frame(x = next_dose), |
| 726 | 3x |
aes(.data$x, 0), |
| 727 | 3x |
label = "Next", |
| 728 | 3x |
vjust = -0.5, |
| 729 | 3x |
hjust = -1.5, |
| 730 | 3x |
colour = "blue", |
| 731 | 3x |
angle = 90 |
| 732 |
) |
|
| 733 | ||
| 734 | 3x |
list( |
| 735 | 3x |
value = next_dose, |
| 736 | 3x |
probs = cbind(dose = data@doseGrid, dlt_prob = dlt_prob), |
| 737 | 3x |
plot = p |
| 738 |
) |
|
| 739 |
} |
|
| 740 |
) |
|
| 741 | ||
| 742 |
## NextBestInfTheory ---- |
|
| 743 | ||
| 744 |
#' @describeIn nextBest gives the appropriate dose within an information |
|
| 745 |
#' theoretic framework. |
|
| 746 |
#' |
|
| 747 |
#' @aliases nextBest-NextBestInfTheory |
|
| 748 |
#' |
|
| 749 |
#' @export |
|
| 750 |
#' |
|
| 751 |
setMethod( |
|
| 752 |
f = "nextBest", |
|
| 753 |
signature = signature( |
|
| 754 |
nextBest = "NextBestInfTheory", |
|
| 755 |
doselimit = "numeric", |
|
| 756 |
samples = "Samples", |
|
| 757 |
model = "GeneralModel", |
|
| 758 |
data = "Data" |
|
| 759 |
), |
|
| 760 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 761 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 762 | 57x |
prob_samples <- sapply( |
| 763 | 57x |
data@doseGrid, |
| 764 | 57x |
prob, |
| 765 | 57x |
model = model, |
| 766 | 57x |
samples = samples, |
| 767 |
... |
|
| 768 |
) |
|
| 769 | ||
| 770 | 57x |
criterion <- colMeans(h_info_theory_dist( |
| 771 | 57x |
prob_samples, |
| 772 | 57x |
nextBest@target, |
| 773 | 57x |
nextBest@asymmetry |
| 774 |
)) |
|
| 775 | ||
| 776 | 57x |
is_dose_eligible <- h_next_best_eligible_doses( |
| 777 | 57x |
data@doseGrid, |
| 778 | 57x |
doselimit, |
| 779 | 57x |
data@placebo, |
| 780 | 57x |
levels = TRUE |
| 781 |
) |
|
| 782 | 57x |
doses_eligible <- data@doseGrid[is_dose_eligible] |
| 783 | 57x |
next_best_level <- which.min(criterion[is_dose_eligible]) |
| 784 | 57x |
next_best <- doses_eligible[next_best_level] |
| 785 | 57x |
list(value = next_best) |
| 786 |
} |
|
| 787 |
) |
|
| 788 | ||
| 789 |
## NextBestTD ---- |
|
| 790 | ||
| 791 |
#' @describeIn nextBest find the next best dose based only on the DLT responses |
|
| 792 |
#' and for [`LogisticIndepBeta`] model class object without DLT samples. |
|
| 793 |
#' |
|
| 794 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 795 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
| 796 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
| 797 |
#' are outside of the dose grid range, the information message is printed by |
|
| 798 |
#' this method. |
|
| 799 |
#' |
|
| 800 |
#' @aliases nextBest-NextBestTD |
|
| 801 |
#' |
|
| 802 |
#' @export |
|
| 803 |
#' @example examples/Rules-method-nextBest-NextBestTD.R |
|
| 804 |
#' |
|
| 805 |
setMethod( |
|
| 806 |
f = "nextBest", |
|
| 807 |
signature = signature( |
|
| 808 |
nextBest = "NextBestTD", |
|
| 809 |
doselimit = "numeric", |
|
| 810 |
samples = "missing", |
|
| 811 |
model = "LogisticIndepBeta", |
|
| 812 |
data = "Data" |
|
| 813 |
), |
|
| 814 |
definition = function( |
|
| 815 |
nextBest, |
|
| 816 |
doselimit = Inf, |
|
| 817 |
model, |
|
| 818 |
data, |
|
| 819 |
in_sim = FALSE, |
|
| 820 |
... |
|
| 821 |
) {
|
|
| 822 | 32x |
assert_flag(in_sim) |
| 823 | ||
| 824 |
# 'drt' - during the trial, 'eot' end of trial. |
|
| 825 | 32x |
prob_target_drt <- nextBest@prob_target_drt |
| 826 | 32x |
prob_target_eot <- nextBest@prob_target_eot |
| 827 | ||
| 828 |
# Target dose estimates, i.e. the dose with probability of the occurrence of |
|
| 829 |
# a DLT that equals to the prob_target_drt or prob_target_eot. |
|
| 830 | 32x |
dose_target_drt <- dose(x = prob_target_drt, model, ...) |
| 831 | 32x |
dose_target_eot <- dose(x = prob_target_eot, model, ...) |
| 832 | ||
| 833 |
# Find the next best doses in the doseGrid. The next best dose is the dose |
|
| 834 |
# at level closest and below the target dose estimate. |
|
| 835 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
| 836 | 32x |
doses_eligible <- h_next_best_eligible_doses( |
| 837 | 32x |
data@doseGrid, |
| 838 | 32x |
doselimit, |
| 839 | 32x |
data@placebo |
| 840 |
) |
|
| 841 | ||
| 842 | 32x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
| 843 | 32x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
| 844 | ||
| 845 | 32x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
| 846 | 32x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
| 847 | ||
| 848 |
# Find the variance of the log of the dose_target_eot. |
|
| 849 | 32x |
mat <- matrix( |
| 850 | 32x |
c( |
| 851 | 32x |
-1 / (model@phi2), |
| 852 | 32x |
-(log(prob_target_eot / (1 - prob_target_eot)) - model@phi1) / |
| 853 | 32x |
(model@phi2)^2 |
| 854 |
), |
|
| 855 | 32x |
nrow = 1 |
| 856 |
) |
|
| 857 | 32x |
var_dose_target_eot <- as.vector(mat %*% model@Pcov %*% t(mat)) |
| 858 | ||
| 859 |
# 95% credibility interval. |
|
| 860 | 32x |
ci_dose_target_eot <- exp( |
| 861 | 32x |
log(dose_target_eot) + c(-1, 1) * 1.96 * sqrt(var_dose_target_eot) |
| 862 |
) |
|
| 863 | 32x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
| 864 | ||
| 865 |
# Build plot. |
|
| 866 | 32x |
p <- h_next_best_td_plot( |
| 867 | 32x |
prob_target_drt = prob_target_drt, |
| 868 | 32x |
dose_target_drt = dose_target_drt, |
| 869 | 32x |
prob_target_eot = prob_target_eot, |
| 870 | 32x |
dose_target_eot = dose_target_eot, |
| 871 | 32x |
data = data, |
| 872 | 32x |
prob_dlt = prob(dose = data@doseGrid, model = model, ...), |
| 873 | 32x |
doselimit = doselimit, |
| 874 | 32x |
next_dose = next_dose_drt |
| 875 |
) |
|
| 876 | ||
| 877 |
if ( |
|
| 878 | 32x |
!h_in_range( |
| 879 | 32x |
dose_target_drt, |
| 880 | 32x |
range = dose_grid_range(data), |
| 881 | 32x |
bounds_closed = TRUE |
| 882 |
) && |
|
| 883 | 32x |
!in_sim |
| 884 |
) {
|
|
| 885 | 2x |
warning(paste( |
| 886 | 2x |
"TD", |
| 887 | 2x |
prob_target_drt * 100, |
| 888 |
"=", |
|
| 889 | 2x |
dose_target_drt, |
| 890 | 2x |
"not within dose grid" |
| 891 |
)) |
|
| 892 |
} |
|
| 893 |
if ( |
|
| 894 | 32x |
!h_in_range( |
| 895 | 32x |
dose_target_eot, |
| 896 | 32x |
range = dose_grid_range(data), |
| 897 | 32x |
bounds_closed = TRUE |
| 898 |
) && |
|
| 899 | 32x |
!in_sim |
| 900 |
) {
|
|
| 901 | 2x |
warning(paste( |
| 902 | 2x |
"TD", |
| 903 | 2x |
prob_target_eot * 100, |
| 904 |
"=", |
|
| 905 | 2x |
dose_target_eot, |
| 906 | 2x |
"not within dose grid" |
| 907 |
)) |
|
| 908 |
} |
|
| 909 | ||
| 910 | 32x |
list( |
| 911 | 32x |
next_dose_drt = next_dose_drt, |
| 912 | 32x |
prob_target_drt = prob_target_drt, |
| 913 | 32x |
dose_target_drt = dose_target_drt, |
| 914 | 32x |
next_dose_eot = next_dose_eot, |
| 915 | 32x |
prob_target_eot = prob_target_eot, |
| 916 | 32x |
dose_target_eot = dose_target_eot, |
| 917 | 32x |
ci_dose_target_eot = ci_dose_target_eot, |
| 918 | 32x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
| 919 | 32x |
plot = p |
| 920 |
) |
|
| 921 |
} |
|
| 922 |
) |
|
| 923 | ||
| 924 |
## NextBestTDsamples ---- |
|
| 925 | ||
| 926 |
#' @describeIn nextBest find the next best dose based only on the DLT responses |
|
| 927 |
#' and for [`LogisticIndepBeta`] model class object involving DLT samples. |
|
| 928 |
#' |
|
| 929 |
#' @aliases nextBest-NextBestTDsamples |
|
| 930 |
#' |
|
| 931 |
#' @export |
|
| 932 |
#' @example examples/Rules-method-nextBest-NextBestTDsamples.R |
|
| 933 |
#' |
|
| 934 |
setMethod( |
|
| 935 |
f = "nextBest", |
|
| 936 |
signature = signature( |
|
| 937 |
nextBest = "NextBestTDsamples", |
|
| 938 |
doselimit = "numeric", |
|
| 939 |
samples = "Samples", |
|
| 940 |
model = "LogisticIndepBeta", |
|
| 941 |
data = "Data" |
|
| 942 |
), |
|
| 943 |
definition = function( |
|
| 944 |
nextBest, |
|
| 945 |
doselimit = Inf, |
|
| 946 |
samples, |
|
| 947 |
model, |
|
| 948 |
data, |
|
| 949 |
in_sim, |
|
| 950 |
... |
|
| 951 |
) {
|
|
| 952 |
# Generate target dose samples, i.e. the doses with probability of the |
|
| 953 |
# occurrence of a DLT that equals to the nextBest@prob_target_drt |
|
| 954 |
# (or nextBest@prob_target_eot, respectively). |
|
| 955 | 24x |
dose_target_drt_samples <- dose( |
| 956 | 24x |
x = nextBest@prob_target_drt, |
| 957 | 24x |
model, |
| 958 | 24x |
samples, |
| 959 |
... |
|
| 960 |
) |
|
| 961 | 24x |
dose_target_eot_samples <- dose( |
| 962 | 24x |
x = nextBest@prob_target_eot, |
| 963 | 24x |
model, |
| 964 | 24x |
samples, |
| 965 |
... |
|
| 966 |
) |
|
| 967 | ||
| 968 |
# Derive the prior/posterior estimates based on two above samples. |
|
| 969 | 24x |
dose_target_drt <- nextBest@derive(dose_target_drt_samples) |
| 970 | 24x |
dose_target_eot <- nextBest@derive(dose_target_eot_samples) |
| 971 | ||
| 972 |
# Find the next doses in the doseGrid. The next dose is the dose at level |
|
| 973 |
# closest and below the dose_target_drt (or dose_target_eot, respectively). |
|
| 974 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
| 975 | 24x |
doses_eligible <- h_next_best_eligible_doses( |
| 976 | 24x |
data@doseGrid, |
| 977 | 24x |
doselimit, |
| 978 | 24x |
data@placebo |
| 979 |
) |
|
| 980 | ||
| 981 | 24x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
| 982 | 24x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
| 983 | ||
| 984 | 24x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
| 985 | 24x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
| 986 | ||
| 987 |
# 95% credibility interval. |
|
| 988 | 24x |
ci_dose_target_eot <- as.numeric(quantile( |
| 989 | 24x |
dose_target_eot_samples, |
| 990 | 24x |
probs = c(0.025, 0.975) |
| 991 |
)) |
|
| 992 | 24x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
| 993 | ||
| 994 |
# Build plot. |
|
| 995 | 24x |
p <- h_next_best_tdsamples_plot( |
| 996 | 24x |
dose_target_drt_samples = dose_target_drt_samples, |
| 997 | 24x |
dose_target_eot_samples = dose_target_eot_samples, |
| 998 | 24x |
dose_target_drt = dose_target_drt, |
| 999 | 24x |
dose_target_eot = dose_target_eot, |
| 1000 | 24x |
dose_grid_range = range(data@doseGrid), |
| 1001 | 24x |
nextBest = nextBest, |
| 1002 | 24x |
doselimit = doselimit, |
| 1003 | 24x |
next_dose = next_dose_drt |
| 1004 |
) |
|
| 1005 | ||
| 1006 | 24x |
list( |
| 1007 | 24x |
next_dose_drt = next_dose_drt, |
| 1008 | 24x |
prob_target_drt = nextBest@prob_target_drt, |
| 1009 | 24x |
dose_target_drt = dose_target_drt, |
| 1010 | 24x |
next_dose_eot = next_dose_eot, |
| 1011 | 24x |
prob_target_eot = nextBest@prob_target_eot, |
| 1012 | 24x |
dose_target_eot = dose_target_eot, |
| 1013 | 24x |
ci_dose_target_eot = ci_dose_target_eot, |
| 1014 | 24x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
| 1015 | 24x |
plot = p |
| 1016 |
) |
|
| 1017 |
} |
|
| 1018 |
) |
|
| 1019 | ||
| 1020 |
## NextBestMaxGain ---- |
|
| 1021 | ||
| 1022 |
#' @describeIn nextBest find the next best dose based only on pseudo DLT model |
|
| 1023 |
#' [`ModelTox`] and [`Effloglog`] efficacy model without samples. |
|
| 1024 |
#' |
|
| 1025 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 1026 |
#' @param model_eff (`Effloglog`)\cr the efficacy model. |
|
| 1027 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
| 1028 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
| 1029 |
#' are outside of the dose grid range, the information message is printed by |
|
| 1030 |
#' this method. |
|
| 1031 |
#' |
|
| 1032 |
#' @aliases nextBest-NextBestMaxGain |
|
| 1033 |
#' |
|
| 1034 |
#' @export |
|
| 1035 |
#' @example examples/Rules-method-nextBest-NextBestMaxGain.R |
|
| 1036 |
#' |
|
| 1037 |
setMethod( |
|
| 1038 |
f = "nextBest", |
|
| 1039 |
signature = signature( |
|
| 1040 |
nextBest = "NextBestMaxGain", |
|
| 1041 |
doselimit = "numeric", |
|
| 1042 |
samples = "missing", |
|
| 1043 |
model = "ModelTox", |
|
| 1044 |
data = "DataDual" |
|
| 1045 |
), |
|
| 1046 |
definition = function( |
|
| 1047 |
nextBest, |
|
| 1048 |
doselimit = Inf, |
|
| 1049 |
model, |
|
| 1050 |
data, |
|
| 1051 |
model_eff, |
|
| 1052 |
in_sim = FALSE, |
|
| 1053 |
... |
|
| 1054 |
) {
|
|
| 1055 | 24x |
assert_class(model_eff, "Effloglog") |
| 1056 | 24x |
assert_flag(in_sim) |
| 1057 | ||
| 1058 |
# 'drt' - during the trial, 'eot' end of trial. |
|
| 1059 | 24x |
prob_target_drt <- nextBest@prob_target_drt |
| 1060 | 24x |
prob_target_eot <- nextBest@prob_target_eot |
| 1061 | ||
| 1062 |
# Target dose estimates, i.e. the dose with probability of the occurrence of |
|
| 1063 |
# a DLT that equals to the prob_target_drt or prob_target_eot. |
|
| 1064 | 24x |
dose_target_drt <- dose(x = prob_target_drt, model, ...) |
| 1065 | 24x |
dose_target_eot <- dose(x = prob_target_eot, model, ...) |
| 1066 | ||
| 1067 |
# Find the dose which gives the maximum gain. |
|
| 1068 | 24x |
dosegrid_range <- dose_grid_range(data) |
| 1069 | 24x |
opt <- optim( |
| 1070 | 24x |
par = dosegrid_range[1], |
| 1071 | 24x |
fn = function(DOSE) {
|
| 1072 | 1002x |
-gain(DOSE, model_dle = model, model_eff = model_eff, ...) |
| 1073 |
}, |
|
| 1074 | 24x |
method = "L-BFGS-B", |
| 1075 | 24x |
lower = dosegrid_range[1], |
| 1076 | 24x |
upper = dosegrid_range[2] |
| 1077 |
) |
|
| 1078 | 24x |
dose_mg <- opt$par # this is G*. # no lintr |
| 1079 | 24x |
max_gain <- -opt$value |
| 1080 | ||
| 1081 |
# Print info message if dose target is outside of the range. |
|
| 1082 |
if ( |
|
| 1083 | 24x |
!h_in_range( |
| 1084 | 24x |
dose_target_drt, |
| 1085 | 24x |
range = dose_grid_range(data), |
| 1086 | 24x |
bounds_closed = FALSE |
| 1087 |
) && |
|
| 1088 | 24x |
!in_sim |
| 1089 |
) {
|
|
| 1090 | ! |
print(paste( |
| 1091 | ! |
"Estimated TD", |
| 1092 | ! |
prob_target_drt * 100, |
| 1093 |
"=", |
|
| 1094 | ! |
dose_target_drt, |
| 1095 | ! |
"not within dose grid" |
| 1096 |
)) |
|
| 1097 |
} |
|
| 1098 |
if ( |
|
| 1099 | 24x |
!h_in_range( |
| 1100 | 24x |
dose_target_eot, |
| 1101 | 24x |
range = dose_grid_range(data), |
| 1102 | 24x |
bounds_closed = FALSE |
| 1103 |
) && |
|
| 1104 | 24x |
!in_sim |
| 1105 |
) {
|
|
| 1106 | ! |
print(paste( |
| 1107 | ! |
"Estimated TD", |
| 1108 | ! |
prob_target_eot * 100, |
| 1109 |
"=", |
|
| 1110 | ! |
dose_target_eot, |
| 1111 | ! |
"not within dose grid" |
| 1112 |
)) |
|
| 1113 |
} |
|
| 1114 |
if ( |
|
| 1115 | 24x |
!h_in_range( |
| 1116 | 24x |
dose_mg, |
| 1117 | 24x |
range = dose_grid_range(data), |
| 1118 | 24x |
bounds_closed = FALSE |
| 1119 |
) && |
|
| 1120 | 24x |
!in_sim |
| 1121 |
) {
|
|
| 1122 | ! |
print(paste("Estimated max gain dose =", dose_mg, "not within dose grid"))
|
| 1123 |
} |
|
| 1124 | ||
| 1125 |
# Get closest grid doses for a given target doses. |
|
| 1126 | 24x |
nb_doses_at_grid <- h_next_best_mg_doses_at_grid( |
| 1127 | 24x |
dose_target_drt = dose_target_drt, |
| 1128 | 24x |
dose_target_eot = dose_target_eot, |
| 1129 | 24x |
dose_mg = dose_mg, |
| 1130 | 24x |
dose_grid = data@doseGrid, |
| 1131 | 24x |
doselimit = doselimit, |
| 1132 | 24x |
placebo = data@placebo |
| 1133 |
) |
|
| 1134 | ||
| 1135 |
# 95% credibility intervals and corresponding ratios for maximum gain dose and target dose eot. |
|
| 1136 | 24x |
ci <- h_next_best_mg_ci( |
| 1137 | 24x |
dose_target = dose_target_eot, |
| 1138 | 24x |
dose_mg = dose_mg, |
| 1139 | 24x |
prob_target = prob_target_eot, |
| 1140 | 24x |
placebo = data@placebo, |
| 1141 | 24x |
model = model, |
| 1142 | 24x |
model_eff = model_eff |
| 1143 |
) |
|
| 1144 | ||
| 1145 |
# Build plot. |
|
| 1146 | 24x |
p <- h_next_best_mg_plot( |
| 1147 | 24x |
prob_target_drt = prob_target_drt, |
| 1148 | 24x |
dose_target_drt = dose_target_drt, |
| 1149 | 24x |
prob_target_eot = prob_target_eot, |
| 1150 | 24x |
dose_target_eot = dose_target_eot, |
| 1151 | 24x |
dose_mg = dose_mg, |
| 1152 | 24x |
max_gain = max_gain, |
| 1153 | 24x |
next_dose = nb_doses_at_grid$next_dose, |
| 1154 | 24x |
doselimit = doselimit, |
| 1155 | 24x |
data = data, |
| 1156 | 24x |
model = model, |
| 1157 | 24x |
model_eff = model_eff |
| 1158 |
) |
|
| 1159 | ||
| 1160 | 24x |
list( |
| 1161 | 24x |
next_dose = nb_doses_at_grid$next_dose, |
| 1162 | 24x |
prob_target_drt = prob_target_drt, |
| 1163 | 24x |
dose_target_drt = dose_target_drt, |
| 1164 | 24x |
next_dose_drt = nb_doses_at_grid$next_dose_drt, |
| 1165 | 24x |
prob_target_eot = prob_target_eot, |
| 1166 | 24x |
dose_target_eot = dose_target_eot, |
| 1167 | 24x |
next_dose_eot = nb_doses_at_grid$next_dose_eot, |
| 1168 | 24x |
dose_max_gain = dose_mg, |
| 1169 | 24x |
next_dose_max_gain = nb_doses_at_grid$next_dose_mg, |
| 1170 | 24x |
ci_dose_target_eot = ci$ci_dose_target, |
| 1171 | 24x |
ci_ratio_dose_target_eot = ci$ci_ratio_dose_target, |
| 1172 | 24x |
ci_dose_max_gain = ci$ci_dose_mg, |
| 1173 | 24x |
ci_ratio_dose_max_gain = ci$ci_ratio_dose_mg, |
| 1174 | 24x |
plot = p |
| 1175 |
) |
|
| 1176 |
} |
|
| 1177 |
) |
|
| 1178 | ||
| 1179 |
## NextBestMaxGainSamples ---- |
|
| 1180 | ||
| 1181 |
#' @describeIn nextBest find the next best dose based on DLT and efficacy |
|
| 1182 |
#' responses with DLT and efficacy samples. |
|
| 1183 |
#' |
|
| 1184 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 1185 |
#' @param model_eff (`Effloglog` or `EffFlexi`)\cr the efficacy model. |
|
| 1186 |
#' @param samples_eff (`Samples`)\cr posterior samples from `model_eff` parameters |
|
| 1187 |
#' given `data`. |
|
| 1188 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
| 1189 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
| 1190 |
#' are outside of the dose grid range, the information message is printed by |
|
| 1191 |
#' this method. |
|
| 1192 |
#' |
|
| 1193 |
#' @aliases nextBest-NextBestMaxGainSamples |
|
| 1194 |
#' |
|
| 1195 |
#' @export |
|
| 1196 |
#' @example examples/Rules-method-nextBest-NextBestMaxGainSamples.R |
|
| 1197 |
#' |
|
| 1198 |
setMethod( |
|
| 1199 |
f = "nextBest", |
|
| 1200 |
signature = signature( |
|
| 1201 |
nextBest = "NextBestMaxGainSamples", |
|
| 1202 |
doselimit = "numeric", |
|
| 1203 |
samples = "Samples", |
|
| 1204 |
model = "ModelTox", |
|
| 1205 |
data = "DataDual" |
|
| 1206 |
), |
|
| 1207 |
definition = function( |
|
| 1208 |
nextBest, |
|
| 1209 |
doselimit = Inf, |
|
| 1210 |
samples, |
|
| 1211 |
model, |
|
| 1212 |
data, |
|
| 1213 |
model_eff, |
|
| 1214 |
samples_eff, |
|
| 1215 |
in_sim = FALSE, |
|
| 1216 |
... |
|
| 1217 |
) {
|
|
| 1218 | 13x |
assert_true( |
| 1219 | 13x |
test_class(model_eff, "Effloglog") || test_class(model_eff, "EffFlexi") |
| 1220 |
) |
|
| 1221 | 13x |
assert_class(samples_eff, "Samples") |
| 1222 | 13x |
assert_flag(in_sim) |
| 1223 | ||
| 1224 |
# 'drt' - during the trial, 'eot' end of trial. |
|
| 1225 | 13x |
prob_target_drt <- nextBest@prob_target_drt |
| 1226 | 13x |
prob_target_eot <- nextBest@prob_target_eot |
| 1227 | ||
| 1228 |
# Generate target dose samples, i.e. the doses with probability of the |
|
| 1229 |
# occurrence of a DLT that equals to the prob_target_drt or prob_target_eot. |
|
| 1230 | 13x |
dose_target_drt_samples <- dose( |
| 1231 | 13x |
x = prob_target_drt, |
| 1232 | 13x |
model, |
| 1233 | 13x |
samples = samples, |
| 1234 |
... |
|
| 1235 |
) |
|
| 1236 | 13x |
dose_target_eot_samples <- dose( |
| 1237 | 13x |
x = prob_target_eot, |
| 1238 | 13x |
model, |
| 1239 | 13x |
samples = samples, |
| 1240 |
... |
|
| 1241 |
) |
|
| 1242 | ||
| 1243 |
# Derive the prior/posterior estimates based on two above samples. |
|
| 1244 | 13x |
dose_target_drt <- nextBest@derive(dose_target_drt_samples) |
| 1245 | 13x |
dose_target_eot <- nextBest@derive(dose_target_eot_samples) |
| 1246 | ||
| 1247 |
# Gain samples. |
|
| 1248 | 13x |
gain_samples <- sapply( |
| 1249 | 13x |
data@doseGrid, |
| 1250 | 13x |
gain, |
| 1251 | 13x |
model, |
| 1252 | 13x |
samples, |
| 1253 | 13x |
model_eff, |
| 1254 | 13x |
samples_eff, |
| 1255 |
... |
|
| 1256 |
) |
|
| 1257 |
# For every sample, get the dose (from the dose grid) that gives the maximum gain value. |
|
| 1258 | 13x |
dose_lev_mg_samples <- apply(gain_samples, 1, which.max) |
| 1259 | 13x |
dose_mg_samples <- data@doseGrid[dose_lev_mg_samples] |
| 1260 |
# Maximum gain dose estimate is the nth percentile of the maximum gain dose samples. |
|
| 1261 | 13x |
dose_mg <- nextBest@mg_derive(dose_mg_samples) |
| 1262 | 13x |
gain_values <- apply(gain_samples, 2, FUN = nextBest@mg_derive) |
| 1263 | ||
| 1264 |
# Print info message if dose target is outside of the range. |
|
| 1265 | 13x |
dosegrid_range <- dose_grid_range(data) |
| 1266 |
if ( |
|
| 1267 | 13x |
!h_in_range( |
| 1268 | 13x |
dose_target_drt, |
| 1269 | 13x |
range = dosegrid_range, |
| 1270 | 13x |
bounds_closed = FALSE |
| 1271 |
) && |
|
| 1272 | 13x |
!in_sim |
| 1273 |
) {
|
|
| 1274 | ! |
print(paste( |
| 1275 | ! |
"Estimated TD", |
| 1276 | ! |
prob_target_drt * 100, |
| 1277 |
"=", |
|
| 1278 | ! |
dose_target_drt, |
| 1279 | ! |
"not within dose grid" |
| 1280 |
)) |
|
| 1281 |
} |
|
| 1282 |
if ( |
|
| 1283 | 13x |
!h_in_range( |
| 1284 | 13x |
dose_target_eot, |
| 1285 | 13x |
range = dosegrid_range, |
| 1286 | 13x |
bounds_closed = FALSE |
| 1287 |
) && |
|
| 1288 | 13x |
!in_sim |
| 1289 |
) {
|
|
| 1290 | ! |
print(paste( |
| 1291 | ! |
"Estimated TD", |
| 1292 | ! |
prob_target_eot * 100, |
| 1293 |
"=", |
|
| 1294 | ! |
dose_target_eot, |
| 1295 | ! |
"not within dose grid" |
| 1296 |
)) |
|
| 1297 |
} |
|
| 1298 |
if ( |
|
| 1299 | 13x |
!h_in_range(dose_mg, range = dosegrid_range, bounds_closed = FALSE) && |
| 1300 | 13x |
!in_sim |
| 1301 |
) {
|
|
| 1302 | ! |
print(paste("Estimated max gain dose =", dose_mg, "not within dose grid"))
|
| 1303 |
} |
|
| 1304 | ||
| 1305 |
# Get closest grid doses for a given target doses. |
|
| 1306 | 13x |
nb_doses_at_grid <- h_next_best_mg_doses_at_grid( |
| 1307 | 13x |
dose_target_drt = dose_target_drt, |
| 1308 | 13x |
dose_target_eot = dose_target_eot, |
| 1309 | 13x |
dose_mg = dose_mg, |
| 1310 | 13x |
dose_grid = data@doseGrid, |
| 1311 | 13x |
doselimit = doselimit, |
| 1312 | 13x |
placebo = data@placebo |
| 1313 |
) |
|
| 1314 | ||
| 1315 |
# 95% credibility intervals and corresponding ratios for maximum gain dose and target dose eot. |
|
| 1316 | 13x |
ci_dose_mg <- as.numeric(quantile(dose_mg_samples, probs = c(0.025, 0.975))) |
| 1317 | 13x |
cir_dose_mg <- ci_dose_mg[2] / ci_dose_mg[1] |
| 1318 | ||
| 1319 | 13x |
ci_dose_target_eot <- as.numeric(quantile( |
| 1320 | 13x |
dose_target_eot, |
| 1321 | 13x |
probs = c(0.025, 0.975) |
| 1322 |
)) |
|
| 1323 | 13x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
| 1324 | ||
| 1325 |
# Build plot. |
|
| 1326 | 13x |
p <- h_next_best_mgsamples_plot( |
| 1327 | 13x |
prob_target_drt = prob_target_drt, |
| 1328 | 13x |
dose_target_drt = dose_target_drt, |
| 1329 | 13x |
prob_target_eot = prob_target_eot, |
| 1330 | 13x |
dose_target_eot = dose_target_eot, |
| 1331 | 13x |
dose_mg = dose_mg, |
| 1332 | 13x |
dose_mg_samples = dose_mg_samples, |
| 1333 | 13x |
next_dose = nb_doses_at_grid$next_dose, |
| 1334 | 13x |
doselimit = doselimit, |
| 1335 | 13x |
dose_grid_range = dosegrid_range |
| 1336 |
) |
|
| 1337 | ||
| 1338 | 13x |
list( |
| 1339 | 13x |
next_dose = nb_doses_at_grid$next_dose, |
| 1340 | 13x |
prob_target_drt = prob_target_drt, |
| 1341 | 13x |
dose_target_drt = dose_target_drt, |
| 1342 | 13x |
next_dose_drt = nb_doses_at_grid$next_dose_drt, |
| 1343 | 13x |
prob_target_eot = prob_target_eot, |
| 1344 | 13x |
dose_target_eot = dose_target_eot, |
| 1345 | 13x |
next_dose_eot = nb_doses_at_grid$next_dose_eot, |
| 1346 | 13x |
dose_max_gain = dose_mg, |
| 1347 | 13x |
next_dose_max_gain = nb_doses_at_grid$next_dose_mg, |
| 1348 | 13x |
ci_dose_target_eot = ci_dose_target_eot, |
| 1349 | 13x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
| 1350 | 13x |
ci_dose_max_gain = ci_dose_mg, |
| 1351 | 13x |
ci_ratio_dose_max_gain = cir_dose_mg, |
| 1352 | 13x |
plot = p |
| 1353 |
) |
|
| 1354 |
} |
|
| 1355 |
) |
|
| 1356 | ||
| 1357 |
## NextBestProbMTDLTE ---- |
|
| 1358 | ||
| 1359 |
#' @describeIn nextBest find the next best dose based with the highest |
|
| 1360 |
#' probability of having a toxicity rate less or equal to the target toxicity |
|
| 1361 |
#' level. |
|
| 1362 |
#' |
|
| 1363 |
#' @aliases nextBest-NextBestProbMTDLTE |
|
| 1364 |
#' |
|
| 1365 |
#' @export |
|
| 1366 |
#' @example examples/Rules-method-nextBest-NextBestProbMTDLTE.R |
|
| 1367 |
#' |
|
| 1368 |
setMethod( |
|
| 1369 |
f = "nextBest", |
|
| 1370 |
signature = signature( |
|
| 1371 |
nextBest = "NextBestProbMTDLTE", |
|
| 1372 |
doselimit = "numeric", |
|
| 1373 |
samples = "Samples", |
|
| 1374 |
model = "GeneralModel", |
|
| 1375 |
data = "Data" |
|
| 1376 |
), |
|
| 1377 |
definition = function(nextBest, doselimit, samples, model, data, ...) {
|
|
| 1378 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 1379 | 3x |
prob_samples <- sapply( |
| 1380 | 3x |
data@doseGrid, |
| 1381 | 3x |
prob, |
| 1382 | 3x |
model = model, |
| 1383 | 3x |
samples = samples, |
| 1384 |
... |
|
| 1385 |
) |
|
| 1386 | ||
| 1387 |
# Determine the maximum dose level with a toxicity probability below or |
|
| 1388 |
# equal to the target and calculate how often a dose is selected as MTD |
|
| 1389 |
# across iterations. |
|
| 1390 |
# The first element of the vector is the relative frequency that no |
|
| 1391 |
# dose in the grid is below or equal to the target, the |
|
| 1392 |
# second element that the 1st dose of the grid is the MTD, etc.. |
|
| 1393 | 3x |
prob_mtd_lte <- prop.table( |
| 1394 | 3x |
table(factor( |
| 1395 | 3x |
rowSums(prob_samples <= nextBest@target), |
| 1396 | 3x |
levels = 0:data@nGrid |
| 1397 |
)) |
|
| 1398 |
) |
|
| 1399 | ||
| 1400 | 3x |
allocation_crit <- as.vector(prob_mtd_lte) |
| 1401 | 3x |
names(allocation_crit) <- as.character(c(0, data@doseGrid)) |
| 1402 | ||
| 1403 |
# In case that placebo is used, placebo and the portion that is not assigned |
|
| 1404 |
# to any dose of the grid are merged. |
|
| 1405 | 3x |
if (data@placebo) {
|
| 1406 | 1x |
allocation_crit[1] <- sum(allocation_crit[1:2]) |
| 1407 | 1x |
allocation_crit <- allocation_crit[-2] |
| 1408 |
} |
|
| 1409 | ||
| 1410 |
# Handling of the portion that is not assigned to an active dose of |
|
| 1411 |
# the dose grid. The portion is added to the minimum active dose |
|
| 1412 |
# of the dose grid. |
|
| 1413 | 3x |
allocation_crit[2] <- sum(allocation_crit[1:2]) |
| 1414 | 3x |
allocation_crit <- allocation_crit[-1] |
| 1415 | ||
| 1416 |
# Determine the dose with the highest relative frequency. |
|
| 1417 | 3x |
allocation_crit_dose <- as.numeric(names(allocation_crit)) |
| 1418 | 3x |
dose_target <- allocation_crit_dose[which.max(allocation_crit)] |
| 1419 | ||
| 1420 |
# Determine next dose. |
|
| 1421 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
| 1422 | 3x |
data@doseGrid, |
| 1423 | 3x |
doselimit, |
| 1424 | 3x |
data@placebo |
| 1425 |
) |
|
| 1426 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
| 1427 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
| 1428 | ||
| 1429 |
# Create a plot. |
|
| 1430 | 3x |
plt_data <- if (data@placebo && (data@doseGrid[1] == next_dose)) {
|
| 1431 | ! |
data.frame( |
| 1432 | ! |
x = as.factor(data@doseGrid), |
| 1433 | ! |
y = c(0, as.numeric(allocation_crit)) * 100 |
| 1434 |
) |
|
| 1435 |
} else {
|
|
| 1436 | 3x |
data.frame( |
| 1437 | 3x |
x = as.factor(allocation_crit_dose), |
| 1438 | 3x |
y = as.numeric(allocation_crit) * 100 |
| 1439 |
) |
|
| 1440 |
} |
|
| 1441 | ||
| 1442 | 3x |
p <- ggplot( |
| 1443 | 3x |
data = plt_data |
| 1444 |
) + |
|
| 1445 | 3x |
geom_col(aes(x, y), fill = "grey75") + |
| 1446 | 3x |
scale_x_discrete(drop = FALSE, guide = guide_axis(check.overlap = TRUE)) + |
| 1447 | 3x |
geom_vline( |
| 1448 | 3x |
xintercept = as.factor(dose_target), |
| 1449 | 3x |
lwd = 1.1, |
| 1450 | 3x |
colour = "black" |
| 1451 |
) + |
|
| 1452 | 3x |
geom_text( |
| 1453 | 3x |
data = data.frame(x = as.factor(dose_target)), |
| 1454 | 3x |
aes(.data$x, 0), |
| 1455 | 3x |
label = "Est", |
| 1456 | 3x |
vjust = -0.5, |
| 1457 | 3x |
hjust = -0.5, |
| 1458 | 3x |
colour = "black", |
| 1459 | 3x |
angle = 90 |
| 1460 |
) + |
|
| 1461 | 3x |
xlab("Dose") +
|
| 1462 | 3x |
ylab(paste("Allocation criterion [%]"))
|
| 1463 | ||
| 1464 | 3x |
if (is.finite(doselimit)) {
|
| 1465 | 2x |
doselimit_level <- if (sum(allocation_crit_dose == doselimit) > 0) {
|
| 1466 | ! |
which(allocation_crit_dose == doselimit) |
| 1467 |
} else {
|
|
| 1468 | 2x |
ifelse( |
| 1469 | 2x |
test = data@placebo && (data@doseGrid[1] == next_dose), |
| 1470 | 2x |
yes = 1.5, |
| 1471 | 2x |
no = sum(allocation_crit_dose < doselimit) + 0.5 |
| 1472 |
) |
|
| 1473 |
} |
|
| 1474 | ||
| 1475 | 2x |
p <- p + |
| 1476 | 2x |
geom_vline( |
| 1477 | 2x |
xintercept = doselimit_level, |
| 1478 | 2x |
colour = "red", |
| 1479 | 2x |
lwd = 1.1 |
| 1480 |
) + |
|
| 1481 | 2x |
geom_text( |
| 1482 | 2x |
data = data.frame(x = doselimit_level), |
| 1483 | 2x |
aes(.data$x, 0), |
| 1484 | 2x |
label = "Max", |
| 1485 | 2x |
vjust = -0.5, |
| 1486 | 2x |
hjust = -1.5, |
| 1487 | 2x |
colour = "red", |
| 1488 | 2x |
angle = 90 |
| 1489 |
) |
|
| 1490 |
} |
|
| 1491 | ||
| 1492 | 3x |
p <- p + |
| 1493 | 3x |
geom_vline( |
| 1494 | 3x |
xintercept = as.factor(next_dose), |
| 1495 | 3x |
colour = "blue", |
| 1496 | 3x |
lwd = 1.1 |
| 1497 |
) + |
|
| 1498 | 3x |
geom_text( |
| 1499 | 3x |
data = data.frame(x = as.factor(next_dose)), |
| 1500 | 3x |
aes(.data$x, 0), |
| 1501 | 3x |
label = "Next", |
| 1502 | 3x |
vjust = -0.5, |
| 1503 | 3x |
hjust = -2.5, |
| 1504 | 3x |
colour = "blue", |
| 1505 | 3x |
angle = 90 |
| 1506 |
) |
|
| 1507 | ||
| 1508 | 3x |
list( |
| 1509 | 3x |
value = next_dose, |
| 1510 | 3x |
allocation = cbind( |
| 1511 | 3x |
dose = allocation_crit_dose, |
| 1512 | 3x |
allocation = allocation_crit |
| 1513 |
), |
|
| 1514 | 3x |
plot = p |
| 1515 |
) |
|
| 1516 |
} |
|
| 1517 |
) |
|
| 1518 | ||
| 1519 |
## NextBestProbMTDMinDist ---- |
|
| 1520 | ||
| 1521 |
#' @describeIn nextBest find the next best dose based with the highest |
|
| 1522 |
#' probability of having a toxicity rate with minimum distance to the |
|
| 1523 |
#' target toxicity level. |
|
| 1524 |
#' |
|
| 1525 |
#' @aliases nextBest-NextBestProbMTDMinDist |
|
| 1526 |
#' |
|
| 1527 |
#' @export |
|
| 1528 |
#' @example examples/Rules-method-nextBest-NextBestProbMtdMinDist.R |
|
| 1529 |
#' |
|
| 1530 |
setMethod( |
|
| 1531 |
f = "nextBest", |
|
| 1532 |
signature = signature( |
|
| 1533 |
nextBest = "NextBestProbMTDMinDist", |
|
| 1534 |
doselimit = "numeric", |
|
| 1535 |
samples = "Samples", |
|
| 1536 |
model = "GeneralModel", |
|
| 1537 |
data = "Data" |
|
| 1538 |
), |
|
| 1539 |
definition = function(nextBest, doselimit, samples, model, data, ...) {
|
|
| 1540 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
| 1541 | 3x |
prob_samples <- sapply( |
| 1542 | 3x |
data@doseGrid, |
| 1543 | 3x |
prob, |
| 1544 | 3x |
model = model, |
| 1545 | 3x |
samples = samples, |
| 1546 |
... |
|
| 1547 |
) |
|
| 1548 | ||
| 1549 |
# Determine which dose level has the minimum distance to target. |
|
| 1550 | 3x |
dose_min_mtd_dist <- apply( |
| 1551 | 3x |
prob_samples, |
| 1552 | 3x |
1, |
| 1553 | 3x |
function(x) which.min(abs(x - nextBest@target)) |
| 1554 |
) |
|
| 1555 | ||
| 1556 | 3x |
allocation_crit <- prop.table( |
| 1557 | 3x |
table(factor(dose_min_mtd_dist, levels = 1:data@nGrid)) |
| 1558 |
) |
|
| 1559 | 3x |
names(allocation_crit) <- as.character(data@doseGrid) |
| 1560 | ||
| 1561 |
# In case that placebo is used, placebo and the first non-placebo dose |
|
| 1562 |
# of the grid are merged. |
|
| 1563 | 3x |
if (data@placebo) {
|
| 1564 | 1x |
allocation_crit[2] <- sum(allocation_crit[1:2]) |
| 1565 | 1x |
allocation_crit <- allocation_crit[-1] |
| 1566 |
} |
|
| 1567 | ||
| 1568 |
# Determine the dose with the highest relative frequency. |
|
| 1569 | 3x |
allocation_crit_dose <- as.numeric(names(allocation_crit)) |
| 1570 | 3x |
dose_target <- allocation_crit_dose[which.max(allocation_crit)] |
| 1571 | ||
| 1572 |
# Determine next dose. |
|
| 1573 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
| 1574 | 3x |
data@doseGrid, |
| 1575 | 3x |
doselimit, |
| 1576 | 3x |
data@placebo |
| 1577 |
) |
|
| 1578 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
| 1579 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
| 1580 | ||
| 1581 |
# Create a plot. |
|
| 1582 | 3x |
plt_data <- if (data@placebo && data@doseGrid[1] == next_dose) {
|
| 1583 | ! |
data.frame( |
| 1584 | ! |
x = as.factor(data@doseGrid), |
| 1585 | ! |
y = c(0, as.numeric(allocation_crit)) * 100 |
| 1586 |
) |
|
| 1587 |
} else {
|
|
| 1588 | 3x |
data.frame( |
| 1589 | 3x |
x = as.factor(allocation_crit_dose), |
| 1590 | 3x |
y = as.numeric(allocation_crit) * 100 |
| 1591 |
) |
|
| 1592 |
} |
|
| 1593 | ||
| 1594 | 3x |
p <- ggplot( |
| 1595 | 3x |
data = plt_data |
| 1596 |
) + |
|
| 1597 | 3x |
geom_col(aes(x, y), fill = "grey75") + |
| 1598 | 3x |
scale_x_discrete(guide = guide_axis(check.overlap = TRUE)) + |
| 1599 | 3x |
geom_vline( |
| 1600 | 3x |
xintercept = as.factor(dose_target), |
| 1601 | 3x |
lwd = 1.1, |
| 1602 | 3x |
colour = "black" |
| 1603 |
) + |
|
| 1604 | 3x |
geom_text( |
| 1605 | 3x |
data = data.frame(x = as.factor(dose_target)), |
| 1606 | 3x |
aes(.data$x, 0), |
| 1607 | 3x |
label = "Est", |
| 1608 | 3x |
vjust = -0.5, |
| 1609 | 3x |
hjust = -0.5, |
| 1610 | 3x |
colour = "black", |
| 1611 | 3x |
angle = 90 |
| 1612 |
) + |
|
| 1613 | 3x |
xlab("Dose") +
|
| 1614 | 3x |
ylab(paste("Allocation criterion [%]"))
|
| 1615 | ||
| 1616 | 3x |
if (is.finite(doselimit)) {
|
| 1617 | 2x |
doselimit_level <- if (any(allocation_crit_dose == doselimit)) {
|
| 1618 | ! |
which(allocation_crit_dose == doselimit) |
| 1619 |
} else {
|
|
| 1620 | 2x |
ifelse( |
| 1621 | 2x |
test = data@placebo && data@doseGrid[1] == next_dose, |
| 1622 | 2x |
yes = 1.5, |
| 1623 | 2x |
no = sum(allocation_crit_dose < doselimit) + 0.5 |
| 1624 |
) |
|
| 1625 |
} |
|
| 1626 | ||
| 1627 | 2x |
p <- p + |
| 1628 | 2x |
geom_vline( |
| 1629 | 2x |
xintercept = doselimit_level, |
| 1630 | 2x |
colour = "red", |
| 1631 | 2x |
lwd = 1.1 |
| 1632 |
) + |
|
| 1633 | 2x |
geom_text( |
| 1634 | 2x |
data = data.frame(x = doselimit_level), |
| 1635 | 2x |
aes(.data$x, 0), |
| 1636 | 2x |
label = "Max", |
| 1637 | 2x |
vjust = -0.5, |
| 1638 | 2x |
hjust = -1.5, |
| 1639 | 2x |
colour = "red", |
| 1640 | 2x |
angle = 90 |
| 1641 |
) |
|
| 1642 |
} |
|
| 1643 | ||
| 1644 | 3x |
p <- p + |
| 1645 | 3x |
geom_vline( |
| 1646 | 3x |
xintercept = as.factor(next_dose), |
| 1647 | 3x |
colour = "blue", |
| 1648 | 3x |
lwd = 1.1 |
| 1649 |
) + |
|
| 1650 | 3x |
geom_text( |
| 1651 | 3x |
data = data.frame(x = as.factor(next_dose)), |
| 1652 | 3x |
aes(.data$x, 0), |
| 1653 | 3x |
label = "Next", |
| 1654 | 3x |
vjust = -0.5, |
| 1655 | 3x |
hjust = -2.5, |
| 1656 | 3x |
colour = "blue", |
| 1657 | 3x |
angle = 90 |
| 1658 |
) |
|
| 1659 | ||
| 1660 | 3x |
list( |
| 1661 | 3x |
value = next_dose, |
| 1662 | 3x |
allocation = cbind( |
| 1663 | 3x |
dose = allocation_crit_dose, |
| 1664 | 3x |
allocation = allocation_crit |
| 1665 |
), |
|
| 1666 | 3x |
plot = p |
| 1667 |
) |
|
| 1668 |
} |
|
| 1669 |
) |
|
| 1670 | ||
| 1671 |
## NextBestOrdinal ---- |
|
| 1672 | ||
| 1673 |
#' @describeIn nextBest find the next best dose for ordinal CRM models. |
|
| 1674 |
#' |
|
| 1675 |
#' @aliases nextBest-NextBestOrdinal |
|
| 1676 |
#' |
|
| 1677 |
#' @export |
|
| 1678 |
#' @example examples/Rules-method-nextBest-NextBestOrdinal.R |
|
| 1679 |
#' |
|
| 1680 |
setMethod( |
|
| 1681 |
f = "nextBest", |
|
| 1682 |
signature = signature( |
|
| 1683 |
nextBest = "NextBestOrdinal", |
|
| 1684 |
doselimit = "numeric", |
|
| 1685 |
samples = "Samples", |
|
| 1686 |
model = "GeneralModel", |
|
| 1687 |
data = "Data" |
|
| 1688 |
), |
|
| 1689 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 1690 | 1x |
stop( |
| 1691 | 1x |
paste0( |
| 1692 | 1x |
"NextBestOrdinal objects can only be used with LogisticLogNormalOrdinal ", |
| 1693 | 1x |
"models and DataOrdinal data objects. In this case, the model is a '", |
| 1694 | 1x |
class(model), |
| 1695 | 1x |
"' object and the data is in a ", |
| 1696 | 1x |
class(data), |
| 1697 | 1x |
" object." |
| 1698 |
) |
|
| 1699 |
) |
|
| 1700 |
} |
|
| 1701 |
) |
|
| 1702 | ||
| 1703 |
#' @describeIn nextBest find the next best dose for ordinal CRM models. |
|
| 1704 |
#' |
|
| 1705 |
#' @aliases nextBest-NextBestOrdinal |
|
| 1706 |
#' |
|
| 1707 |
#' @export |
|
| 1708 |
#' @example examples/Rules-method-nextBest-NextBestOrdinal.R |
|
| 1709 |
#' |
|
| 1710 |
setMethod( |
|
| 1711 |
f = "nextBest", |
|
| 1712 |
signature = signature( |
|
| 1713 |
nextBest = "NextBestOrdinal", |
|
| 1714 |
doselimit = "numeric", |
|
| 1715 |
samples = "Samples", |
|
| 1716 |
model = "LogisticLogNormalOrdinal", |
|
| 1717 |
data = "DataOrdinal" |
|
| 1718 |
), |
|
| 1719 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) {
|
|
| 1720 | 1x |
nextBest( |
| 1721 | 1x |
nextBest = nextBest@rule, |
| 1722 | 1x |
doselimit = doselimit, |
| 1723 | 1x |
samples = h_convert_ordinal_samples(samples, nextBest@grade), |
| 1724 | 1x |
model = h_convert_ordinal_model(model, nextBest@grade), |
| 1725 | 1x |
data = h_convert_ordinal_data(data, nextBest@grade), |
| 1726 |
... |
|
| 1727 |
) |
|
| 1728 |
} |
|
| 1729 |
) |
|
| 1730 | ||
| 1731 |
# maxDose ---- |
|
| 1732 | ||
| 1733 |
## generic ---- |
|
| 1734 | ||
| 1735 |
#' Determine the Maximum Possible Next Dose |
|
| 1736 |
#' |
|
| 1737 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1738 |
#' |
|
| 1739 |
#' This function determines the upper limit of the next dose based on the |
|
| 1740 |
#' `increments`and the `data`. |
|
| 1741 |
#' |
|
| 1742 |
#' @param increments (`Increments`)\cr the rule for the next best dose. |
|
| 1743 |
#' @param data (`Data`)\cr input data. |
|
| 1744 |
#' @param ... additional arguments without method dispatch. |
|
| 1745 |
#' |
|
| 1746 |
#' @return A `number`, the maximum possible next dose. |
|
| 1747 |
#' |
|
| 1748 |
#' @export |
|
| 1749 |
#' |
|
| 1750 |
setGeneric( |
|
| 1751 |
name = "maxDose", |
|
| 1752 |
def = function(increments, data, ...) {
|
|
| 1753 | 496x |
standardGeneric("maxDose")
|
| 1754 |
}, |
|
| 1755 |
valueClass = "numeric" |
|
| 1756 |
) |
|
| 1757 | ||
| 1758 |
## IncrementsRelative ---- |
|
| 1759 | ||
| 1760 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1761 |
#' relative increments. |
|
| 1762 |
#' |
|
| 1763 |
#' @aliases maxDose-IncrementsRelative |
|
| 1764 |
#' |
|
| 1765 |
#' @export |
|
| 1766 |
#' @example examples/Rules-method-maxDose-IncrementsRelative.R |
|
| 1767 |
#' |
|
| 1768 |
setMethod( |
|
| 1769 |
f = "maxDose", |
|
| 1770 |
signature = signature( |
|
| 1771 |
increments = "IncrementsRelative", |
|
| 1772 |
data = "Data" |
|
| 1773 |
), |
|
| 1774 |
definition = function(increments, data, ...) {
|
|
| 1775 | 335x |
if (data@nObs == 0L) {
|
| 1776 |
# In this case we return Inf, because there is no restriction |
|
| 1777 |
# from this stopping rule because we cannot reference any |
|
| 1778 |
# previous dose. In practice this does not matter because |
|
| 1779 |
# there is a starting dose fixed externally anyway. |
|
| 1780 | 1x |
return(Inf) |
| 1781 |
} |
|
| 1782 | 334x |
last_dose <- data@x[data@nObs] |
| 1783 |
# Determine in which interval the `last_dose` is. |
|
| 1784 | 334x |
assert_true(last_dose >= head(increments@intervals, 1)) |
| 1785 | 332x |
last_dose_interval <- findInterval( |
| 1786 | 332x |
x = last_dose, |
| 1787 | 332x |
vec = increments@intervals |
| 1788 |
) |
|
| 1789 | 332x |
(1 + increments@increments[last_dose_interval]) * last_dose |
| 1790 |
} |
|
| 1791 |
) |
|
| 1792 | ||
| 1793 |
## IncrementsRelativeDLT ---- |
|
| 1794 | ||
| 1795 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1796 |
#' relative increments determined by DLTs so far. |
|
| 1797 |
#' |
|
| 1798 |
#' @aliases maxDose-IncrementsRelativeDLT |
|
| 1799 |
#' |
|
| 1800 |
#' @export |
|
| 1801 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeDLT.R |
|
| 1802 |
#' |
|
| 1803 |
setMethod( |
|
| 1804 |
f = "maxDose", |
|
| 1805 |
signature = signature( |
|
| 1806 |
increments = "IncrementsRelativeDLT", |
|
| 1807 |
data = "Data" |
|
| 1808 |
), |
|
| 1809 |
definition = function(increments, data, ...) {
|
|
| 1810 | 11x |
dlt_count <- sum(data@y) |
| 1811 |
# Determine in which interval the `dlt_count` is. |
|
| 1812 | 11x |
assert_true(dlt_count >= increments@intervals[1]) |
| 1813 | 8x |
dlt_count_interval <- findInterval( |
| 1814 | 8x |
x = dlt_count, |
| 1815 | 8x |
vec = increments@intervals |
| 1816 |
) |
|
| 1817 | 8x |
(1 + increments@increments[dlt_count_interval]) * data@x[data@nObs] |
| 1818 |
} |
|
| 1819 |
) |
|
| 1820 | ||
| 1821 |
## IncrementsRelativeDLTCurrent ---- |
|
| 1822 | ||
| 1823 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1824 |
#' relative increments determined by DLTs in the current cohort. |
|
| 1825 |
#' |
|
| 1826 |
#' @aliases maxDose-IncrementsRelativeDLTCurrent |
|
| 1827 |
#' |
|
| 1828 |
#' @export |
|
| 1829 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeDLTCurrent.R |
|
| 1830 |
#' |
|
| 1831 |
setMethod( |
|
| 1832 |
f = "maxDose", |
|
| 1833 |
signature = signature( |
|
| 1834 |
increments = "IncrementsRelativeDLTCurrent", |
|
| 1835 |
data = "Data" |
|
| 1836 |
), |
|
| 1837 |
definition = function(increments, data, ...) {
|
|
| 1838 | 12x |
last_dose <- data@x[data@nObs] |
| 1839 | ||
| 1840 |
# Determine how many DLTs have occurred in the last cohort. |
|
| 1841 | 12x |
last_cohort <- data@cohort[data@nObs] |
| 1842 | 12x |
last_cohort_indices <- which(data@cohort == last_cohort) |
| 1843 | 12x |
dlt_count_lcohort <- sum(data@y[last_cohort_indices]) |
| 1844 | ||
| 1845 |
# Determine in which interval the `dlt_count_lcohort` is. |
|
| 1846 | 12x |
assert_true(dlt_count_lcohort >= increments@intervals[1]) |
| 1847 | 9x |
dlt_count_lcohort_int <- findInterval( |
| 1848 | 9x |
x = dlt_count_lcohort, |
| 1849 | 9x |
vec = increments@intervals |
| 1850 |
) |
|
| 1851 | 9x |
(1 + increments@increments[dlt_count_lcohort_int]) * last_dose |
| 1852 |
} |
|
| 1853 |
) |
|
| 1854 | ||
| 1855 |
## IncrementsRelativeParts ---- |
|
| 1856 | ||
| 1857 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1858 |
#' relative increments as well as part 1 and beginning of part 2. |
|
| 1859 |
#' |
|
| 1860 |
#' @aliases maxDose-IncrementsRelativeParts |
|
| 1861 |
#' |
|
| 1862 |
#' @export |
|
| 1863 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeParts.R |
|
| 1864 |
#' |
|
| 1865 |
setMethod( |
|
| 1866 |
f = "maxDose", |
|
| 1867 |
signature = signature( |
|
| 1868 |
increments = "IncrementsRelativeParts", |
|
| 1869 |
data = "DataParts" |
|
| 1870 |
), |
|
| 1871 |
definition = function(increments, data, ...) {
|
|
| 1872 | 10x |
all_in_part1 <- all(data@part == 1L) |
| 1873 | 10x |
incrmnt <- if (all_in_part1) {
|
| 1874 | 9x |
part2_started <- data@nextPart == 2L |
| 1875 | 9x |
if (part2_started) {
|
| 1876 | 7x |
any_dlt <- any(data@y == 1L) |
| 1877 | 7x |
if (any_dlt) {
|
| 1878 | 4x |
increments@dlt_start |
| 1879 | 3x |
} else if (increments@clean_start <= 0L) {
|
| 1880 | 2x |
increments@clean_start |
| 1881 |
} |
|
| 1882 |
} else {
|
|
| 1883 | 2x |
1L |
| 1884 |
} |
|
| 1885 |
} |
|
| 1886 | ||
| 1887 | 10x |
if (is.null(incrmnt)) {
|
| 1888 | 2x |
callNextMethod(increments, data, ...) |
| 1889 |
} else {
|
|
| 1890 | 8x |
max_dose_lev_part1 <- match_within_tolerance( |
| 1891 | 8x |
max(data@x), |
| 1892 | 8x |
data@part1Ladder |
| 1893 |
) |
|
| 1894 | 8x |
new_max_dose_level <- max_dose_lev_part1 + incrmnt |
| 1895 | 8x |
assert_true(new_max_dose_level >= 0L) |
| 1896 | 6x |
assert_true(new_max_dose_level <= length(data@part1Ladder)) |
| 1897 | 3x |
data@part1Ladder[new_max_dose_level] |
| 1898 |
} |
|
| 1899 |
} |
|
| 1900 |
) |
|
| 1901 | ||
| 1902 |
## IncrementsDoseLevels ---- |
|
| 1903 | ||
| 1904 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1905 |
#' the number of dose grid levels. That is, the max dose is determined as |
|
| 1906 |
#' the one which level is equal to: base dose level + level increment. |
|
| 1907 |
#' The base dose level is the level of the last dose in grid or the level |
|
| 1908 |
#' of the maximum dose applied, which is defined in `increments` object. |
|
| 1909 |
#' Find out more in [`IncrementsDoseLevels`]. |
|
| 1910 |
#' |
|
| 1911 |
#' @aliases maxDose-IncrementsDoseLevels |
|
| 1912 |
#' |
|
| 1913 |
#' @export |
|
| 1914 |
#' @example examples/Rules-method-maxDose-IncrementsDoseLevels.R |
|
| 1915 |
#' |
|
| 1916 |
setMethod( |
|
| 1917 |
f = "maxDose", |
|
| 1918 |
signature = signature( |
|
| 1919 |
increments = "IncrementsDoseLevels", |
|
| 1920 |
data = "Data" |
|
| 1921 |
), |
|
| 1922 |
definition = function(increments, data, ...) {
|
|
| 1923 |
# Determine what is the basis level for increment, |
|
| 1924 |
# i.e. the last dose or the max dose applied. |
|
| 1925 | 106x |
basis_dose_level <- ifelse( |
| 1926 | 106x |
increments@basis_level == "last", |
| 1927 | 106x |
data@xLevel[data@nObs], |
| 1928 | 106x |
max(data@xLevel) |
| 1929 |
) |
|
| 1930 | 106x |
max_dose_level <- min(basis_dose_level + increments@levels, data@nGrid) |
| 1931 | 106x |
data@doseGrid[max_dose_level] |
| 1932 |
} |
|
| 1933 |
) |
|
| 1934 | ||
| 1935 |
## IncrementsHSRBeta ---- |
|
| 1936 | ||
| 1937 |
#' @describeIn maxDose determine the maximum possible next dose for escalation. |
|
| 1938 |
#' |
|
| 1939 |
#' @aliases maxDose-IncrementsHSRBeta |
|
| 1940 |
#' |
|
| 1941 |
#' @export |
|
| 1942 |
#' @example examples/Rules-method-maxDose-IncrementsHSRBeta.R |
|
| 1943 |
#' |
|
| 1944 |
setMethod( |
|
| 1945 |
f = "maxDose", |
|
| 1946 |
signature = signature( |
|
| 1947 |
increments = "IncrementsHSRBeta", |
|
| 1948 |
data = "Data" |
|
| 1949 |
), |
|
| 1950 |
definition = function(increments, data, ...) {
|
|
| 1951 |
# Summary of observed data per dose level. |
|
| 1952 | 7x |
y <- factor(data@y, levels = c("0", "1"))
|
| 1953 | 7x |
dlt_tab <- table(y, data@x) |
| 1954 | ||
| 1955 |
# Ignore placebo if applied. |
|
| 1956 | 7x |
if (data@placebo == TRUE & min(data@x) == data@doseGrid[1]) {
|
| 1957 | 4x |
dlt_tab <- dlt_tab[, -1] |
| 1958 |
} |
|
| 1959 | ||
| 1960 |
# Extract dose names as these get lost if only one dose available. |
|
| 1961 | 7x |
non_plcb_doses <- unique(sort(as.numeric(colnames(dlt_tab)))) |
| 1962 | ||
| 1963 |
# Toxicity probability per dose level. |
|
| 1964 | 7x |
x <- dlt_tab[2, ] |
| 1965 | 7x |
n <- apply(dlt_tab, 2, sum) |
| 1966 | 7x |
tox_prob <- pbeta( |
| 1967 | 7x |
increments@target, |
| 1968 | 7x |
x + increments@a, |
| 1969 | 7x |
n - x + increments@b, |
| 1970 | 7x |
lower.tail = FALSE |
| 1971 |
) |
|
| 1972 | ||
| 1973 |
# Return the min toxic dose level or maximum dose level if no dose is toxic, |
|
| 1974 |
# while ignoring placebo. |
|
| 1975 | 7x |
dose_tox <- if (sum(tox_prob > increments@prob) > 0) {
|
| 1976 | 5x |
min(non_plcb_doses[which(tox_prob > increments@prob)]) |
| 1977 |
} else {
|
|
| 1978 |
# Add small value to max dose, so that the max dose is always smaller. |
|
| 1979 | 2x |
max(data@doseGrid) + 0.01 |
| 1980 |
} |
|
| 1981 | ||
| 1982 |
# Determine the next maximum possible dose. |
|
| 1983 |
# In case that the first active dose is above probability threshold, |
|
| 1984 |
# the first active dose is reported as maximum. I.e. in case that placebo is used, |
|
| 1985 |
# the second dose is reported. Please note that this rule should be used together |
|
| 1986 |
# with the hard safety stopping rule to avoid inconsistent results. |
|
| 1987 | 7x |
max( |
| 1988 | 7x |
data@doseGrid[data@doseGrid < dose_tox], |
| 1989 | 7x |
data@doseGrid[data@placebo + 1] |
| 1990 |
) |
|
| 1991 |
} |
|
| 1992 |
) |
|
| 1993 | ||
| 1994 |
## IncrementsMin ---- |
|
| 1995 | ||
| 1996 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 1997 |
#' multiple increment rules, taking the minimum across individual increments. |
|
| 1998 |
#' |
|
| 1999 |
#' @aliases maxDose-IncrementsMin |
|
| 2000 |
#' |
|
| 2001 |
#' @export |
|
| 2002 |
#' @example examples/Rules-method-maxDose-IncrementsMin.R |
|
| 2003 |
#' |
|
| 2004 |
setMethod( |
|
| 2005 |
f = "maxDose", |
|
| 2006 |
signature = signature( |
|
| 2007 |
increments = "IncrementsMin", |
|
| 2008 |
data = "Data" |
|
| 2009 |
), |
|
| 2010 |
definition = function(increments, data, ...) {
|
|
| 2011 | 2x |
individual_results <- sapply( |
| 2012 | 2x |
increments@increments_list, |
| 2013 | 2x |
maxDose, |
| 2014 | 2x |
data = data, |
| 2015 |
... |
|
| 2016 |
) |
|
| 2017 | 2x |
min(individual_results) |
| 2018 |
} |
|
| 2019 |
) |
|
| 2020 | ||
| 2021 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
| 2022 |
#' multiple increment rules, taking the minimum across individual increments. |
|
| 2023 |
#' |
|
| 2024 |
#' @aliases maxDose-IncrementsMin |
|
| 2025 |
#' |
|
| 2026 |
#' @export |
|
| 2027 |
setMethod( |
|
| 2028 |
f = "maxDose", |
|
| 2029 |
signature = signature( |
|
| 2030 |
increments = "IncrementsMin", |
|
| 2031 |
data = "DataOrdinal" |
|
| 2032 |
), |
|
| 2033 |
definition = function(increments, data, ...) {
|
|
| 2034 | 3x |
individual_results <- sapply( |
| 2035 | 3x |
increments@increments_list, |
| 2036 | 3x |
maxDose, |
| 2037 | 3x |
data = data, |
| 2038 |
... |
|
| 2039 |
) |
|
| 2040 | 3x |
min(individual_results) |
| 2041 |
} |
|
| 2042 |
) |
|
| 2043 | ||
| 2044 |
## IncrementsOrdinal ---- |
|
| 2045 | ||
| 2046 |
#' @describeIn maxDose determine the maximum possible next dose in an ordinal |
|
| 2047 |
#' CRM trial |
|
| 2048 |
#' |
|
| 2049 |
#' @aliases maxDose-IncrementsOrdinal |
|
| 2050 |
#' |
|
| 2051 |
#' @export |
|
| 2052 |
#' @example examples/Rules-method-maxDose-IncrementsOrdinal.R |
|
| 2053 |
#' |
|
| 2054 |
setMethod( |
|
| 2055 |
f = "maxDose", |
|
| 2056 |
signature = signature( |
|
| 2057 |
increments = "IncrementsOrdinal", |
|
| 2058 |
data = "DataOrdinal" |
|
| 2059 |
), |
|
| 2060 |
definition = function(increments, data, ...) {
|
|
| 2061 | 8x |
maxDose( |
| 2062 | 8x |
increments = increments@rule, |
| 2063 | 8x |
data = h_convert_ordinal_data( |
| 2064 | 8x |
data, |
| 2065 | 8x |
increments@grade, |
| 2066 |
... |
|
| 2067 |
) |
|
| 2068 |
) |
|
| 2069 |
} |
|
| 2070 |
) |
|
| 2071 | ||
| 2072 |
## IncrementsMaxToxProb ---- |
|
| 2073 | ||
| 2074 |
#' @describeIn maxDose determine the maximum possible next dose based on the |
|
| 2075 |
#' probability of toxicity |
|
| 2076 |
#' @param model (`GeneralModel`)\cr The model on which probabilities will be based |
|
| 2077 |
#' @param samples (`Samples`)\cr The MCMC samples to which `model` will be applied |
|
| 2078 |
#' |
|
| 2079 |
#' @aliases maxDose-IncrementsMaxToxProb |
|
| 2080 |
#' |
|
| 2081 |
#' @export |
|
| 2082 |
#' @example examples/Rules-method-maxDose-IncrementsMaxToxProb.R |
|
| 2083 |
#' |
|
| 2084 |
setMethod( |
|
| 2085 |
f = "maxDose", |
|
| 2086 |
signature = signature( |
|
| 2087 |
increments = "IncrementsMaxToxProb", |
|
| 2088 |
data = "DataOrdinal" |
|
| 2089 |
), |
|
| 2090 |
definition = function(increments, data, model, samples, ...) {
|
|
| 2091 | 3x |
assert_class(samples, "Samples") |
| 2092 | 3x |
assert_true(length(increments@prob) == length(data@yCategories) - 1) |
| 2093 | 3x |
nm <- utils::tail(names(data@yCategories), -1) |
| 2094 | 3x |
assert_set_equal(names(increments@prob), nm) |
| 2095 | ||
| 2096 | 3x |
probs <- dplyr::bind_rows( |
| 2097 | 3x |
lapply( |
| 2098 | 3x |
seq_along(increments@prob), |
| 2099 | 3x |
function(g) {
|
| 2100 | 6x |
fitted_probs <- fit(samples, model, data, grade = g, ...) |
| 2101 | 6x |
safe_fitted_probs <- dplyr::filter( |
| 2102 | 6x |
fitted_probs, |
| 2103 | 6x |
middle < increments@prob[nm[g]] |
| 2104 |
) |
|
| 2105 | 6x |
highest_safe_fitted_prob <- utils::tail(safe_fitted_probs, 1) |
| 2106 |
} |
|
| 2107 |
) |
|
| 2108 |
) |
|
| 2109 | 3x |
min(probs$dose) |
| 2110 |
} |
|
| 2111 |
) |
|
| 2112 |
#' @describeIn maxDose determine the maximum possible next dose based on the |
|
| 2113 |
#' probability of toxicity |
|
| 2114 |
#' @param model (`GeneralModel`)\cr The model on which probabilities will be based |
|
| 2115 |
#' @param samples (`Samples`)\cr The MCMC samples to which `model` will be applied |
|
| 2116 |
#' |
|
| 2117 |
#' @aliases maxDose-IncrementsMaxToxProb |
|
| 2118 |
#' |
|
| 2119 |
#' @export |
|
| 2120 |
#' @example examples/Rules-method-maxDose-IncrementsMaxToxProb.R |
|
| 2121 |
#' |
|
| 2122 |
setMethod( |
|
| 2123 |
f = "maxDose", |
|
| 2124 |
signature = signature( |
|
| 2125 |
increments = "IncrementsMaxToxProb", |
|
| 2126 |
data = "Data" |
|
| 2127 |
), |
|
| 2128 |
definition = function(increments, data, model, samples, ...) {
|
|
| 2129 | 1x |
assert_class(samples, "Samples") |
| 2130 | 1x |
assert_true(length(increments@prob) == 1) |
| 2131 | ||
| 2132 | 1x |
fitted_prob <- fit(samples, model, data, ...) |
| 2133 | 1x |
safe_fitted_prob <- dplyr::filter(fitted_prob, middle < increments@prob) |
| 2134 | 1x |
highest_safe_fitted_prob <- utils::tail(safe_fitted_prob, 1) |
| 2135 | 1x |
highest_safe_fitted_prob$dose |
| 2136 |
} |
|
| 2137 |
) |
|
| 2138 | ||
| 2139 |
## tidy-IncrementsMaxToxProb ---- |
|
| 2140 | ||
| 2141 |
#' @rdname tidy |
|
| 2142 |
#' @aliases tidy-IncrementsMaxToxProb |
|
| 2143 |
#' @example examples/Rules-method-tidyIncrementsMaxToxProb.R |
|
| 2144 |
#' @export |
|
| 2145 |
setMethod( |
|
| 2146 |
f = "tidy", |
|
| 2147 |
signature = signature(x = "IncrementsMaxToxProb"), |
|
| 2148 |
definition = function(x, ...) {
|
|
| 2149 | 2x |
grades <- names(x@prob) |
| 2150 | 2x |
if (is.null(grades)) {
|
| 2151 | ! |
grades <- "1" |
| 2152 |
} |
|
| 2153 | 2x |
tibble( |
| 2154 | 2x |
Grade = grades, |
| 2155 | 2x |
Prob = x@prob |
| 2156 |
) %>% |
|
| 2157 | 2x |
h_tidy_class(x) |
| 2158 |
} |
|
| 2159 |
) |
|
| 2160 | ||
| 2161 |
# nolint start |
|
| 2162 | ||
| 2163 |
## ============================================================ |
|
| 2164 | ||
| 2165 |
## -------------------------------------------------- |
|
| 2166 |
## "AND" combination of stopping rules |
|
| 2167 |
## -------------------------------------------------- |
|
| 2168 | ||
| 2169 |
##' The method combining two atomic stopping rules |
|
| 2170 |
##' |
|
| 2171 |
##' @param e1 First \code{\linkS4class{Stopping}} object
|
|
| 2172 |
##' @param e2 Second \code{\linkS4class{Stopping}} object
|
|
| 2173 |
##' @return The \code{\linkS4class{StoppingAll}} object
|
|
| 2174 |
##' |
|
| 2175 |
##' @example examples/Rules-method-and-stopping-stopping.R |
|
| 2176 |
##' @keywords methods |
|
| 2177 |
setMethod( |
|
| 2178 |
"&", |
|
| 2179 |
signature( |
|
| 2180 |
e1 = "Stopping", |
|
| 2181 |
e2 = "Stopping" |
|
| 2182 |
), |
|
| 2183 |
def = function(e1, e2) {
|
|
| 2184 | 21x |
StoppingAll(list(e1, e2)) |
| 2185 |
} |
|
| 2186 |
) |
|
| 2187 | ||
| 2188 |
##' The method combining a stopping list and an atomic |
|
| 2189 |
##' |
|
| 2190 |
##' @param e1 \code{\linkS4class{StoppingAll}} object
|
|
| 2191 |
##' @param e2 \code{\linkS4class{Stopping}} object
|
|
| 2192 |
##' @return The modified \code{\linkS4class{StoppingAll}} object
|
|
| 2193 |
##' |
|
| 2194 |
##' @example examples/Rules-method-and-stoppingAll-stopping.R |
|
| 2195 |
##' @keywords methods |
|
| 2196 |
setMethod( |
|
| 2197 |
"&", |
|
| 2198 |
signature( |
|
| 2199 |
e1 = "StoppingAll", |
|
| 2200 |
e2 = "Stopping" |
|
| 2201 |
), |
|
| 2202 |
def = function(e1, e2) {
|
|
| 2203 | 1x |
e1@stop_list <- c( |
| 2204 | 1x |
e1@stop_list, |
| 2205 | 1x |
e2 |
| 2206 |
) |
|
| 2207 | 1x |
return(e1) |
| 2208 |
} |
|
| 2209 |
) |
|
| 2210 | ||
| 2211 |
##' The method combining an atomic and a stopping list |
|
| 2212 |
##' |
|
| 2213 |
##' @param e1 \code{\linkS4class{Stopping}} object
|
|
| 2214 |
##' @param e2 \code{\linkS4class{StoppingAll}} object
|
|
| 2215 |
##' @return The modified \code{\linkS4class{StoppingAll}} object
|
|
| 2216 |
##' |
|
| 2217 |
##' @example examples/Rules-method-and-stopping-stoppingAll.R |
|
| 2218 |
##' @keywords methods |
|
| 2219 |
setMethod( |
|
| 2220 |
"&", |
|
| 2221 |
signature( |
|
| 2222 |
e1 = "Stopping", |
|
| 2223 |
e2 = "StoppingAll" |
|
| 2224 |
), |
|
| 2225 |
def = function(e1, e2) {
|
|
| 2226 | 1x |
e2@stop_list <- c( |
| 2227 | 1x |
e1, |
| 2228 | 1x |
e2@stop_list |
| 2229 |
) |
|
| 2230 | 1x |
return(e2) |
| 2231 |
} |
|
| 2232 |
) |
|
| 2233 | ||
| 2234 |
## -------------------------------------------------- |
|
| 2235 |
## "OR" combination of stopping rules |
|
| 2236 |
## -------------------------------------------------- |
|
| 2237 | ||
| 2238 |
##' The method combining two atomic stopping rules |
|
| 2239 |
##' |
|
| 2240 |
##' @param e1 First \code{\linkS4class{Stopping}} object
|
|
| 2241 |
##' @param e2 Second \code{\linkS4class{Stopping}} object
|
|
| 2242 |
##' @return The \code{\linkS4class{StoppingAny}} object
|
|
| 2243 |
##' |
|
| 2244 |
##' @aliases |,Stopping,Stopping-method |
|
| 2245 |
##' @name or-Stopping-Stopping |
|
| 2246 |
##' @example examples/Rules-method-or-stopping-stopping.R |
|
| 2247 |
##' @keywords methods |
|
| 2248 |
setMethod( |
|
| 2249 |
"|", |
|
| 2250 |
signature( |
|
| 2251 |
e1 = "Stopping", |
|
| 2252 |
e2 = "Stopping" |
|
| 2253 |
), |
|
| 2254 |
def = function(e1, e2) {
|
|
| 2255 | 50x |
StoppingAny(list(e1, e2)) |
| 2256 |
} |
|
| 2257 |
) |
|
| 2258 | ||
| 2259 |
##' The method combining a stopping list and an atomic |
|
| 2260 |
##' |
|
| 2261 |
##' @param e1 \code{\linkS4class{StoppingAny}} object
|
|
| 2262 |
##' @param e2 \code{\linkS4class{Stopping}} object
|
|
| 2263 |
##' @return The modified \code{\linkS4class{StoppingAny}} object
|
|
| 2264 |
##' |
|
| 2265 |
##' @aliases |,StoppingAny,Stopping-method |
|
| 2266 |
##' @name or-Stopping-StoppingAny |
|
| 2267 |
##' @example examples/Rules-method-or-stoppingAny-stopping.R |
|
| 2268 |
##' @keywords methods |
|
| 2269 |
setMethod( |
|
| 2270 |
"|", |
|
| 2271 |
signature( |
|
| 2272 |
e1 = "StoppingAny", |
|
| 2273 |
e2 = "Stopping" |
|
| 2274 |
), |
|
| 2275 |
def = function(e1, e2) {
|
|
| 2276 | 14x |
e1@stop_list <- c( |
| 2277 | 14x |
e1@stop_list, |
| 2278 | 14x |
e2 |
| 2279 |
) |
|
| 2280 | 14x |
return(e1) |
| 2281 |
} |
|
| 2282 |
) |
|
| 2283 | ||
| 2284 |
##' The method combining an atomic and a stopping list |
|
| 2285 |
##' |
|
| 2286 |
##' @param e1 \code{\linkS4class{Stopping}} object
|
|
| 2287 |
##' @param e2 \code{\linkS4class{StoppingAny}} object
|
|
| 2288 |
##' @return The modified \code{\linkS4class{StoppingAny}} object
|
|
| 2289 |
##' |
|
| 2290 |
##' @aliases |,Stopping,StoppingAny-method |
|
| 2291 |
##' @name or-StoppingAny-Stopping |
|
| 2292 |
##' @example examples/Rules-method-or-stopping-stoppingAny.R |
|
| 2293 |
##' @keywords methods |
|
| 2294 |
setMethod( |
|
| 2295 |
"|", |
|
| 2296 |
signature( |
|
| 2297 |
e1 = "Stopping", |
|
| 2298 |
e2 = "StoppingAny" |
|
| 2299 |
), |
|
| 2300 |
def = function(e1, e2) {
|
|
| 2301 | 1x |
e2@stop_list <- c( |
| 2302 | 1x |
e1, |
| 2303 | 1x |
e2@stop_list |
| 2304 |
) |
|
| 2305 | 1x |
return(e2) |
| 2306 |
} |
|
| 2307 |
) |
|
| 2308 | ||
| 2309 |
# nolint end |
|
| 2310 | ||
| 2311 |
# Stopping ---- |
|
| 2312 | ||
| 2313 |
## generic ---- |
|
| 2314 | ||
| 2315 |
#' Stop the trial? |
|
| 2316 |
#' |
|
| 2317 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2318 |
#' |
|
| 2319 |
#' This function returns whether to stop the trial. |
|
| 2320 |
#' |
|
| 2321 |
#' @param stopping (`Stopping`)\cr the rule for stopping the trial. |
|
| 2322 |
#' @param dose the recommended next best dose. |
|
| 2323 |
#' @param samples (`Samples`)\cr the mcmc samples. |
|
| 2324 |
#' @param model (`GeneralModel`)\cr the model. |
|
| 2325 |
#' @param data (`Data`)\cr input data. |
|
| 2326 |
#' @param ... additional arguments without method dispatch. |
|
| 2327 |
#' |
|
| 2328 |
#' @return logical value: `TRUE` if the trial can be stopped, `FALSE` |
|
| 2329 |
#' otherwise. It should have an attribute `message` which gives the reason |
|
| 2330 |
#' for the decision. |
|
| 2331 |
#' |
|
| 2332 |
#' @export |
|
| 2333 |
#' @example examples/Rules-method-CombiningStoppingRulesAndOr.R |
|
| 2334 |
setGeneric( |
|
| 2335 |
name = "stopTrial", |
|
| 2336 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2337 | 2893x |
standardGeneric("stopTrial")
|
| 2338 |
}, |
|
| 2339 |
valueClass = "logical" |
|
| 2340 |
) |
|
| 2341 | ||
| 2342 |
## StoppingMissingDose ---- |
|
| 2343 | ||
| 2344 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
| 2345 |
#' |
|
| 2346 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 2347 |
#' |
|
| 2348 |
#' @aliases stopTrial-StoppingMissingDose |
|
| 2349 |
#' @example examples/Rules-method-stopTrial-StoppingMissingDose.R |
|
| 2350 |
#' |
|
| 2351 |
setMethod( |
|
| 2352 |
f = "stopTrial", |
|
| 2353 |
signature = signature( |
|
| 2354 |
stopping = "StoppingMissingDose", |
|
| 2355 |
dose = "numeric", |
|
| 2356 |
samples = "ANY", |
|
| 2357 |
model = "ANY", |
|
| 2358 |
data = "Data" |
|
| 2359 |
), |
|
| 2360 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2361 | 112x |
do_stop <- is.na(dose) || (data@placebo && dose == min(data@doseGrid)) |
| 2362 | ||
| 2363 | 112x |
msg <- paste( |
| 2364 | 112x |
"Next dose is", |
| 2365 | 112x |
ifelse( |
| 2366 | 112x |
do_stop, |
| 2367 | 112x |
paste( |
| 2368 | 112x |
ifelse( |
| 2369 | 112x |
data@placebo && dose == min(data@doseGrid), |
| 2370 | 112x |
"placebo dose", |
| 2371 | 112x |
"NA" |
| 2372 |
), |
|
| 2373 | 112x |
", i.e., no active dose is safe enough according to the NextBest rule." |
| 2374 |
), |
|
| 2375 | 112x |
"available at the dose grid." |
| 2376 |
) |
|
| 2377 |
) |
|
| 2378 | ||
| 2379 | 112x |
structure(do_stop, message = msg, report_label = stopping@report_label) |
| 2380 |
} |
|
| 2381 |
) |
|
| 2382 | ||
| 2383 |
# nolint start |
|
| 2384 | ||
| 2385 |
## -------------------------------------------------- |
|
| 2386 |
## Stopping based on multiple stopping rules |
|
| 2387 |
## -------------------------------------------------- |
|
| 2388 | ||
| 2389 |
##' @describeIn stopTrial Stop based on multiple stopping rules |
|
| 2390 |
##' @example examples/Rules-method-stopTrial-StoppingList.R |
|
| 2391 |
setMethod( |
|
| 2392 |
"stopTrial", |
|
| 2393 |
signature = signature( |
|
| 2394 |
stopping = "StoppingList", |
|
| 2395 |
dose = "ANY", |
|
| 2396 |
samples = "ANY", |
|
| 2397 |
model = "ANY", |
|
| 2398 |
data = "ANY" |
|
| 2399 |
), |
|
| 2400 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2401 |
## evaluate the individual stopping rules |
|
| 2402 |
## in the list |
|
| 2403 | 14x |
individualResults <- |
| 2404 | 14x |
if (missing(samples)) {
|
| 2405 | 7x |
lapply( |
| 2406 | 7x |
stopping@stop_list, |
| 2407 | 7x |
stopTrial, |
| 2408 | 7x |
dose = dose, |
| 2409 | 7x |
model = model, |
| 2410 | 7x |
data = data, |
| 2411 |
... |
|
| 2412 |
) |
|
| 2413 |
} else {
|
|
| 2414 | 7x |
lapply( |
| 2415 | 7x |
stopping@stop_list, |
| 2416 | 7x |
stopTrial, |
| 2417 | 7x |
dose = dose, |
| 2418 | 7x |
samples = samples, |
| 2419 | 7x |
model = model, |
| 2420 | 7x |
data = data, |
| 2421 |
... |
|
| 2422 |
) |
|
| 2423 |
} |
|
| 2424 | ||
| 2425 |
## summarize to obtain overall result |
|
| 2426 | 14x |
overallResult <- stopping@summary(as.logical(individualResults)) |
| 2427 | ||
| 2428 |
## retrieve individual text messages, |
|
| 2429 |
## but let them in the list structure |
|
| 2430 | 14x |
overallText <- lapply(individualResults, attr, "message") |
| 2431 | ||
| 2432 | 14x |
return(structure( |
| 2433 | 14x |
overallResult, |
| 2434 | 14x |
message = overallText, |
| 2435 | 14x |
individual = individualResults |
| 2436 |
)) |
|
| 2437 |
} |
|
| 2438 |
) |
|
| 2439 | ||
| 2440 |
## -------------------------------------------------- |
|
| 2441 |
## Stopping based on fulfillment of all multiple stopping rules |
|
| 2442 |
## -------------------------------------------------- |
|
| 2443 | ||
| 2444 |
##' @describeIn stopTrial Stop based on fulfillment of all multiple stopping |
|
| 2445 |
##' rules |
|
| 2446 |
##' |
|
| 2447 |
##' @example examples/Rules-method-stopTrial-StoppingAll.R |
|
| 2448 |
setMethod( |
|
| 2449 |
"stopTrial", |
|
| 2450 |
signature = signature( |
|
| 2451 |
stopping = "StoppingAll", |
|
| 2452 |
dose = "ANY", |
|
| 2453 |
samples = "ANY", |
|
| 2454 |
model = "ANY", |
|
| 2455 |
data = "ANY" |
|
| 2456 |
), |
|
| 2457 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2458 |
## evaluate the individual stopping rules |
|
| 2459 |
## in the list |
|
| 2460 | 88x |
individualResults <- |
| 2461 | 88x |
if (missing(samples)) {
|
| 2462 | 6x |
lapply( |
| 2463 | 6x |
stopping@stop_list, |
| 2464 | 6x |
stopTrial, |
| 2465 | 6x |
dose = dose, |
| 2466 | 6x |
model = model, |
| 2467 | 6x |
data = data, |
| 2468 |
... |
|
| 2469 |
) |
|
| 2470 |
} else {
|
|
| 2471 | 82x |
lapply( |
| 2472 | 82x |
stopping@stop_list, |
| 2473 | 82x |
stopTrial, |
| 2474 | 82x |
dose = dose, |
| 2475 | 82x |
samples = samples, |
| 2476 | 82x |
model = model, |
| 2477 | 82x |
data = data, |
| 2478 |
... |
|
| 2479 |
) |
|
| 2480 |
} |
|
| 2481 | ||
| 2482 |
## summarize to obtain overall result |
|
| 2483 | 88x |
overallResult <- all(as.logical(individualResults)) |
| 2484 | ||
| 2485 |
## retrieve individual text messages, |
|
| 2486 |
## but let them in the list structure |
|
| 2487 | 88x |
overallText <- lapply(individualResults, attr, "message") |
| 2488 | ||
| 2489 | 88x |
return(structure( |
| 2490 | 88x |
overallResult, |
| 2491 | 88x |
message = overallText, |
| 2492 | 88x |
individual = individualResults, |
| 2493 | 88x |
report_label = stopping@report_label |
| 2494 |
)) |
|
| 2495 |
} |
|
| 2496 |
) |
|
| 2497 | ||
| 2498 | ||
| 2499 |
## -------------------------------------------------- |
|
| 2500 |
## Stopping based on fulfillment of any stopping rule |
|
| 2501 |
## -------------------------------------------------- |
|
| 2502 | ||
| 2503 |
##' @describeIn stopTrial Stop based on fulfillment of any stopping rule |
|
| 2504 |
##' |
|
| 2505 |
##' @example examples/Rules-method-stopTrial-StoppingAny.R |
|
| 2506 |
setMethod( |
|
| 2507 |
"stopTrial", |
|
| 2508 |
signature = signature( |
|
| 2509 |
stopping = "StoppingAny", |
|
| 2510 |
dose = "ANY", |
|
| 2511 |
samples = "ANY", |
|
| 2512 |
model = "ANY", |
|
| 2513 |
data = "ANY" |
|
| 2514 |
), |
|
| 2515 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2516 |
## evaluate the individual stopping rules |
|
| 2517 |
## in the list |
|
| 2518 | 148x |
individualResults <- |
| 2519 | 148x |
if (missing(samples)) {
|
| 2520 | 6x |
lapply( |
| 2521 | 6x |
stopping@stop_list, |
| 2522 | 6x |
stopTrial, |
| 2523 | 6x |
dose = dose, |
| 2524 | 6x |
model = model, |
| 2525 | 6x |
data = data, |
| 2526 |
... |
|
| 2527 |
) |
|
| 2528 |
} else {
|
|
| 2529 | 142x |
lapply( |
| 2530 | 142x |
stopping@stop_list, |
| 2531 | 142x |
stopTrial, |
| 2532 | 142x |
dose = dose, |
| 2533 | 142x |
samples = samples, |
| 2534 | 142x |
model = model, |
| 2535 | 142x |
data = data, |
| 2536 |
... |
|
| 2537 |
) |
|
| 2538 |
} |
|
| 2539 | ||
| 2540 |
## summarize to obtain overall result |
|
| 2541 | 148x |
overallResult <- any(as.logical(individualResults)) |
| 2542 | ||
| 2543 |
## retrieve individual text messages, |
|
| 2544 |
## but let them in the list structure |
|
| 2545 | 148x |
overallText <- lapply(individualResults, attr, "message") |
| 2546 | ||
| 2547 | 148x |
return(structure( |
| 2548 | 148x |
overallResult, |
| 2549 | 148x |
message = overallText, |
| 2550 | 148x |
individual = individualResults, |
| 2551 | 148x |
report_label = stopping@report_label |
| 2552 |
)) |
|
| 2553 |
} |
|
| 2554 |
) |
|
| 2555 | ||
| 2556 | ||
| 2557 |
## -------------------------------------------------- |
|
| 2558 |
## Stopping based on number of cohorts near to next best dose |
|
| 2559 |
## -------------------------------------------------- |
|
| 2560 | ||
| 2561 |
##' @describeIn stopTrial Stop based on number of cohorts near to next best dose |
|
| 2562 |
##' |
|
| 2563 |
##' @example examples/Rules-method-stopTrial-StoppingCohortsNearDose.R |
|
| 2564 |
setMethod( |
|
| 2565 |
"stopTrial", |
|
| 2566 |
signature = signature( |
|
| 2567 |
stopping = "StoppingCohortsNearDose", |
|
| 2568 |
dose = "numeric", |
|
| 2569 |
samples = "ANY", |
|
| 2570 |
model = "ANY", |
|
| 2571 |
data = "Data" |
|
| 2572 |
), |
|
| 2573 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2574 |
## determine the range where the cohorts must lie in |
|
| 2575 | 14x |
lower <- (100 - stopping@percentage) / 100 * dose |
| 2576 | 14x |
upper <- (100 + stopping@percentage) / 100 * dose |
| 2577 | ||
| 2578 |
## which patients lie there? |
|
| 2579 | 14x |
indexPatients <- which((data@x >= lower) & (data@x <= upper)) |
| 2580 | ||
| 2581 |
## how many cohorts? |
|
| 2582 | 14x |
nCohorts <- length(unique(data@cohort[indexPatients])) |
| 2583 | ||
| 2584 |
## so can we stop? |
|
| 2585 | 14x |
doStop <- nCohorts >= stopping@nCohorts |
| 2586 | ||
| 2587 |
## generate message |
|
| 2588 | 14x |
text <- paste( |
| 2589 | 14x |
nCohorts, |
| 2590 | 14x |
" cohorts lie within ", |
| 2591 | 14x |
stopping@percentage, |
| 2592 | 14x |
"% of the next best dose ", |
| 2593 | 14x |
dose, |
| 2594 | 14x |
". This ", |
| 2595 | 14x |
ifelse(doStop, "reached", "is below"), |
| 2596 | 14x |
" the required ", |
| 2597 | 14x |
stopping@nCohorts, |
| 2598 | 14x |
" cohorts", |
| 2599 | 14x |
sep = "" |
| 2600 |
) |
|
| 2601 | ||
| 2602 |
## return both |
|
| 2603 | 14x |
return(structure( |
| 2604 | 14x |
doStop, |
| 2605 | 14x |
message = text, |
| 2606 | 14x |
report_label = stopping@report_label |
| 2607 |
)) |
|
| 2608 |
} |
|
| 2609 |
) |
|
| 2610 | ||
| 2611 | ||
| 2612 |
## ------------------------------------------------------------- |
|
| 2613 |
## Stopping based on number of patients near to next best dose |
|
| 2614 |
## ------------------------------------------------------------- |
|
| 2615 | ||
| 2616 |
##' @describeIn stopTrial Stop based on number of patients near to next best |
|
| 2617 |
##' dose |
|
| 2618 |
##' |
|
| 2619 |
##' @example examples/Rules-method-stopTrial-StoppingPatientsNearDose.R |
|
| 2620 |
setMethod( |
|
| 2621 |
"stopTrial", |
|
| 2622 |
signature = signature( |
|
| 2623 |
stopping = "StoppingPatientsNearDose", |
|
| 2624 |
dose = "numeric", |
|
| 2625 |
samples = "ANY", |
|
| 2626 |
model = "ANY", |
|
| 2627 |
data = "Data" |
|
| 2628 |
), |
|
| 2629 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2630 |
## determine the range where the cohorts must lie in |
|
| 2631 | 2x |
lower <- (100 - stopping@percentage) / 100 * dose |
| 2632 | 2x |
upper <- (100 + stopping@percentage) / 100 * dose |
| 2633 | ||
| 2634 |
## how many patients lie there? |
|
| 2635 | 2x |
nPatients <- ifelse( |
| 2636 | 2x |
is.na(dose), |
| 2637 | 2x |
0, |
| 2638 | 2x |
sum((data@x >= lower) & (data@x <= upper)) |
| 2639 |
) |
|
| 2640 | ||
| 2641 |
## so can we stop? |
|
| 2642 | 2x |
doStop <- nPatients >= stopping@nPatients |
| 2643 | ||
| 2644 |
## generate message |
|
| 2645 | 2x |
text <- paste( |
| 2646 | 2x |
nPatients, |
| 2647 | 2x |
" patients lie within ", |
| 2648 | 2x |
stopping@percentage, |
| 2649 | 2x |
"% of the next best dose ", |
| 2650 | 2x |
dose, |
| 2651 | 2x |
". This ", |
| 2652 | 2x |
ifelse(doStop, "reached", "is below"), |
| 2653 | 2x |
" the required ", |
| 2654 | 2x |
stopping@nPatients, |
| 2655 | 2x |
" patients", |
| 2656 | 2x |
sep = "" |
| 2657 |
) |
|
| 2658 | ||
| 2659 |
## return both |
|
| 2660 | 2x |
return(structure( |
| 2661 | 2x |
doStop, |
| 2662 | 2x |
message = text, |
| 2663 | 2x |
report_label = stopping@report_label |
| 2664 |
)) |
|
| 2665 |
} |
|
| 2666 |
) |
|
| 2667 | ||
| 2668 |
## -------------------------------------------------- |
|
| 2669 |
## Stopping based on minimum number of cohorts |
|
| 2670 |
## -------------------------------------------------- |
|
| 2671 | ||
| 2672 |
##' @describeIn stopTrial Stop based on minimum number of cohorts |
|
| 2673 |
##' |
|
| 2674 |
##' @example examples/Rules-method-stopTrial-StoppingMinCohorts.R |
|
| 2675 |
setMethod( |
|
| 2676 |
"stopTrial", |
|
| 2677 |
signature = signature( |
|
| 2678 |
stopping = "StoppingMinCohorts", |
|
| 2679 |
dose = "ANY", |
|
| 2680 |
samples = "ANY", |
|
| 2681 |
model = "ANY", |
|
| 2682 |
data = "Data" |
|
| 2683 |
), |
|
| 2684 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2685 |
## determine number of cohorts |
|
| 2686 | 115x |
nCohorts <- length(unique(data@cohort)) |
| 2687 | ||
| 2688 |
## so can we stop? |
|
| 2689 | 115x |
doStop <- nCohorts >= stopping@nCohorts |
| 2690 | ||
| 2691 |
## generate message |
|
| 2692 | 115x |
text <- |
| 2693 | 115x |
paste( |
| 2694 | 115x |
"Number of cohorts is", |
| 2695 | 115x |
nCohorts, |
| 2696 | 115x |
"and thus", |
| 2697 | 115x |
ifelse(doStop, "reached", "below"), |
| 2698 | 115x |
"the prespecified minimum number", |
| 2699 | 115x |
stopping@nCohorts |
| 2700 |
) |
|
| 2701 | ||
| 2702 |
## return both |
|
| 2703 | 115x |
return(structure( |
| 2704 | 115x |
doStop, |
| 2705 | 115x |
message = text, |
| 2706 | 115x |
report_label = stopping@report_label |
| 2707 |
)) |
|
| 2708 |
} |
|
| 2709 |
) |
|
| 2710 | ||
| 2711 |
## -------------------------------------------------- |
|
| 2712 |
## Stopping based on minimum number of patients |
|
| 2713 |
## -------------------------------------------------- |
|
| 2714 | ||
| 2715 |
##' @describeIn stopTrial Stop based on minimum number of patients |
|
| 2716 |
##' |
|
| 2717 |
##' @example examples/Rules-method-stopTrial-StoppingMinPatients.R |
|
| 2718 |
setMethod( |
|
| 2719 |
"stopTrial", |
|
| 2720 |
signature = signature( |
|
| 2721 |
stopping = "StoppingMinPatients", |
|
| 2722 |
dose = "ANY", |
|
| 2723 |
samples = "ANY", |
|
| 2724 |
model = "ANY", |
|
| 2725 |
data = "Data" |
|
| 2726 |
), |
|
| 2727 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2728 |
## so can we stop? |
|
| 2729 | 404x |
doStop <- data@nObs >= stopping@nPatients |
| 2730 | ||
| 2731 |
## generate message |
|
| 2732 | 404x |
text <- |
| 2733 | 404x |
paste( |
| 2734 | 404x |
"Number of patients is", |
| 2735 | 404x |
data@nObs, |
| 2736 | 404x |
"and thus", |
| 2737 | 404x |
ifelse(doStop, "reached", "below"), |
| 2738 | 404x |
"the prespecified minimum number", |
| 2739 | 404x |
stopping@nPatients |
| 2740 |
) |
|
| 2741 | ||
| 2742 |
## return both |
|
| 2743 | 404x |
return(structure( |
| 2744 | 404x |
doStop, |
| 2745 | 404x |
message = text, |
| 2746 | 404x |
report_label = stopping@report_label |
| 2747 |
)) |
|
| 2748 |
} |
|
| 2749 |
) |
|
| 2750 | ||
| 2751 |
# nolint end |
|
| 2752 | ||
| 2753 |
## StoppingTargetProb ---- |
|
| 2754 | ||
| 2755 |
#' @describeIn stopTrial Stop based on probability of target tox interval |
|
| 2756 |
#' |
|
| 2757 |
#' @aliases stopTrial-StoppingTargetProb |
|
| 2758 |
#' @example examples/Rules-method-stopTrial-StoppingTargetProb.R |
|
| 2759 |
setMethod( |
|
| 2760 |
f = "stopTrial", |
|
| 2761 |
signature = signature( |
|
| 2762 |
stopping = "StoppingTargetProb", |
|
| 2763 |
dose = "numeric", |
|
| 2764 |
samples = "Samples", |
|
| 2765 |
model = "GeneralModel", |
|
| 2766 |
data = "ANY" |
|
| 2767 |
), |
|
| 2768 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2769 |
# Compute probability to be in target interval. |
|
| 2770 | 146x |
prob_target <- ifelse( |
| 2771 | 146x |
is.na(dose), |
| 2772 | 146x |
0, |
| 2773 | 146x |
mean( |
| 2774 | 146x |
prob(dose = dose, model, samples, ...) >= stopping@target[1] & |
| 2775 | 146x |
prob(dose = dose, model, samples, ...) <= stopping@target[2] |
| 2776 |
) |
|
| 2777 |
) |
|
| 2778 | ||
| 2779 | 146x |
do_stop <- prob_target >= stopping@prob |
| 2780 | ||
| 2781 | 146x |
msg <- paste( |
| 2782 | 146x |
"Probability for target toxicity is", |
| 2783 | 146x |
round(prob_target * 100), |
| 2784 | 146x |
"% for dose", |
| 2785 | 146x |
dose, |
| 2786 | 146x |
"and thus", |
| 2787 | 146x |
ifelse(do_stop, "above", "below"), |
| 2788 | 146x |
"the required", |
| 2789 | 146x |
round(stopping@prob * 100), |
| 2790 |
"%" |
|
| 2791 |
) |
|
| 2792 | ||
| 2793 | 146x |
structure( |
| 2794 | 146x |
do_stop, |
| 2795 | 146x |
message = msg, |
| 2796 | 146x |
report_label = stopping@report_label |
| 2797 |
) |
|
| 2798 |
} |
|
| 2799 |
) |
|
| 2800 | ||
| 2801 |
# nolint start |
|
| 2802 | ||
| 2803 |
## -------------------------------------------------- |
|
| 2804 |
## Stopping based on MTD distribution |
|
| 2805 |
## -------------------------------------------------- |
|
| 2806 | ||
| 2807 |
##' @describeIn stopTrial Stop based on MTD distribution |
|
| 2808 |
##' |
|
| 2809 |
##' @example examples/Rules-method-stopTrial-StoppingMTDdistribution.R |
|
| 2810 |
setMethod( |
|
| 2811 |
"stopTrial", |
|
| 2812 |
signature = signature( |
|
| 2813 |
stopping = "StoppingMTDdistribution", |
|
| 2814 |
dose = "numeric", |
|
| 2815 |
samples = "Samples", |
|
| 2816 |
model = "GeneralModel", |
|
| 2817 |
data = "ANY" |
|
| 2818 |
), |
|
| 2819 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 2820 |
## First, generate the MTD samples. |
|
| 2821 | ||
| 2822 |
## add prior data and samples to the |
|
| 2823 |
## function environment so that they |
|
| 2824 |
## can be used. |
|
| 2825 | 751x |
mtdSamples <- dose( |
| 2826 | 751x |
x = stopping@target, |
| 2827 | 751x |
model, |
| 2828 | 751x |
samples, |
| 2829 |
... |
|
| 2830 |
) |
|
| 2831 | ||
| 2832 |
## what is the absolute threshold? |
|
| 2833 | 751x |
absThresh <- stopping@thresh * dose |
| 2834 | ||
| 2835 |
## what is the probability to be above this dose? |
|
| 2836 | 751x |
prob <- ifelse( |
| 2837 | 751x |
is.na(absThresh), |
| 2838 | 751x |
0, |
| 2839 | 751x |
mean(mtdSamples > absThresh) |
| 2840 |
) |
|
| 2841 | ||
| 2842 |
## so can we stop? |
|
| 2843 | 751x |
doStop <- prob >= stopping@prob |
| 2844 | ||
| 2845 |
## generate message |
|
| 2846 | 751x |
text <- |
| 2847 | 751x |
paste( |
| 2848 | 751x |
"Probability of MTD above", |
| 2849 | 751x |
round(stopping@thresh * 100), |
| 2850 | 751x |
"% of current dose", |
| 2851 | 751x |
dose, |
| 2852 | 751x |
"is", |
| 2853 | 751x |
round(prob * 100), |
| 2854 | 751x |
"% and thus", |
| 2855 | 751x |
ifelse(doStop, "greater than or equal to", "strictly less than"), |
| 2856 | 751x |
"the required", |
| 2857 | 751x |
round(stopping@prob * 100), |
| 2858 |
"%" |
|
| 2859 |
) |
|
| 2860 | ||
| 2861 |
## return both |
|
| 2862 | 751x |
return(structure( |
| 2863 | 751x |
doStop, |
| 2864 | 751x |
message = text, |
| 2865 | 751x |
report_label = stopping@report_label |
| 2866 |
)) |
|
| 2867 |
} |
|
| 2868 |
) |
|
| 2869 | ||
| 2870 |
# nolint end |
|
| 2871 | ||
| 2872 |
## StoppingMTDCV ---- |
|
| 2873 | ||
| 2874 |
#' @rdname stopTrial |
|
| 2875 |
#' |
|
| 2876 |
#' @description Stopping rule based precision of the MTD estimation. |
|
| 2877 |
#' The trial is stopped, when the MTD can be estimated with sufficient precision. |
|
| 2878 |
#' The criteria is based on the robust coefficient of variation (CV) calculated |
|
| 2879 |
#' from the posterior distribution. |
|
| 2880 |
#' The robust CV is defined `mad(MTD) / median(MTD)`, where `mad` is the median |
|
| 2881 |
#' absolute deviation. |
|
| 2882 |
#' |
|
| 2883 |
#' @aliases stopTrial-StoppingMTDCV |
|
| 2884 |
#' @example examples/Rules-method-stopTrial-StoppingMTDCV.R |
|
| 2885 |
#' @export |
|
| 2886 |
#' |
|
| 2887 |
setMethod( |
|
| 2888 |
f = "stopTrial", |
|
| 2889 |
signature = signature( |
|
| 2890 |
stopping = "StoppingMTDCV", |
|
| 2891 |
dose = "numeric", |
|
| 2892 |
samples = "Samples", |
|
| 2893 |
model = "GeneralModel", |
|
| 2894 |
data = "ANY" |
|
| 2895 |
), |
|
| 2896 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2897 | 3x |
mtd_samples <- dose( |
| 2898 | 3x |
x = stopping@target, |
| 2899 | 3x |
model, |
| 2900 | 3x |
samples, |
| 2901 |
... |
|
| 2902 |
) |
|
| 2903 |
# CV of MTD expressed as percentage, derived based on MTD posterior samples. |
|
| 2904 | 3x |
mtd_cv <- (mad(mtd_samples) / median(mtd_samples)) * 100 |
| 2905 | 3x |
do_stop <- mtd_cv <= stopping@thresh_cv |
| 2906 | ||
| 2907 | 3x |
msg <- paste( |
| 2908 | 3x |
"CV of MTD is", |
| 2909 | 3x |
round(mtd_cv), |
| 2910 | 3x |
"% and thus", |
| 2911 | 3x |
ifelse(do_stop, "below", "above"), |
| 2912 | 3x |
"the required precision threshold of", |
| 2913 | 3x |
round(stopping@thresh_cv), |
| 2914 |
"%" |
|
| 2915 |
) |
|
| 2916 | ||
| 2917 | 3x |
structure( |
| 2918 | 3x |
do_stop, |
| 2919 | 3x |
message = msg, |
| 2920 | 3x |
report_label = stopping@report_label |
| 2921 |
) |
|
| 2922 |
} |
|
| 2923 |
) |
|
| 2924 | ||
| 2925 | ||
| 2926 |
## StoppingLowestDoseHSRBeta ---- |
|
| 2927 | ||
| 2928 |
#' @rdname stopTrial |
|
| 2929 |
#' |
|
| 2930 |
#' @description Stopping based based on the lowest non placebo dose. The trial is |
|
| 2931 |
#' stopped when the lowest non placebo dose meets the Hard |
|
| 2932 |
#' Safety Rule, i.e. it is deemed to be overly toxic. Stopping is based on the |
|
| 2933 |
#' observed data at the lowest dose level using a Bin-Beta model |
|
| 2934 |
#' based on DLT probability. |
|
| 2935 |
#' |
|
| 2936 |
#' @aliases stopTrial-StoppingLowestDoseHSRBeta |
|
| 2937 |
#' @example examples/Rules-method-stopTrial-StoppingLowestDoseHSRBeta.R |
|
| 2938 |
#' @export |
|
| 2939 |
setMethod( |
|
| 2940 |
f = "stopTrial", |
|
| 2941 |
signature = signature( |
|
| 2942 |
stopping = "StoppingLowestDoseHSRBeta", |
|
| 2943 |
dose = "numeric", |
|
| 2944 |
samples = "Samples" |
|
| 2945 |
), |
|
| 2946 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 2947 |
# Actual number of patients at first active dose. |
|
| 2948 | 7x |
n <- sum(data@x == data@doseGrid[data@placebo + 1]) |
| 2949 | ||
| 2950 |
# Determine toxicity probability of the first active dose. |
|
| 2951 | 7x |
tox_prob_first_dose <- |
| 2952 | 7x |
if (n > 0) {
|
| 2953 | 5x |
x <- sum(data@y[which(data@x == data@doseGrid[data@placebo + 1])]) |
| 2954 | 5x |
pbeta( |
| 2955 | 5x |
stopping@target, |
| 2956 | 5x |
x + stopping@a, |
| 2957 | 5x |
n - x + stopping@b, |
| 2958 | 5x |
lower.tail = FALSE |
| 2959 |
) |
|
| 2960 |
} else {
|
|
| 2961 | 2x |
0 |
| 2962 |
} |
|
| 2963 | ||
| 2964 | 7x |
do_stop <- tox_prob_first_dose > stopping@prob |
| 2965 | ||
| 2966 |
# generate message |
|
| 2967 | 7x |
msg <- if (n == 0) {
|
| 2968 | 2x |
"Lowest active dose not tested, stopping rule not applied." |
| 2969 |
} else {
|
|
| 2970 | 5x |
paste( |
| 2971 | 5x |
"Probability that the lowest active dose of ", |
| 2972 | 5x |
data@doseGrid[data@placebo + 1], |
| 2973 | 5x |
" being toxic based on posterior Beta distribution using a Beta(",
|
| 2974 | 5x |
stopping@a, |
| 2975 |
",", |
|
| 2976 | 5x |
stopping@b, |
| 2977 | 5x |
") prior is ", |
| 2978 | 5x |
round(tox_prob_first_dose * 100), |
| 2979 | 5x |
"% and thus ", |
| 2980 | 5x |
ifelse(do_stop, "above", "below"), |
| 2981 | 5x |
" the required ", |
| 2982 | 5x |
round(stopping@prob * 100), |
| 2983 | 5x |
"% threshold.", |
| 2984 | 5x |
sep = "" |
| 2985 |
) |
|
| 2986 |
} |
|
| 2987 | ||
| 2988 | 7x |
structure( |
| 2989 | 7x |
do_stop, |
| 2990 | 7x |
message = msg, |
| 2991 | 7x |
report_label = stopping@report_label |
| 2992 |
) |
|
| 2993 |
} |
|
| 2994 |
) |
|
| 2995 | ||
| 2996 |
## StoppingTargetBiomarker ---- |
|
| 2997 | ||
| 2998 |
#' @describeIn stopTrial Stop based on probability of targeting biomarker |
|
| 2999 |
#' |
|
| 3000 |
#' @aliases stopTrial-StoppingTargetBiomarker |
|
| 3001 |
#' @example examples/Rules-method-stopTrial-StoppingTargetBiomarker.R |
|
| 3002 |
setMethod( |
|
| 3003 |
f = "stopTrial", |
|
| 3004 |
signature = signature( |
|
| 3005 |
stopping = "StoppingTargetBiomarker", |
|
| 3006 |
dose = "numeric", |
|
| 3007 |
samples = "Samples", |
|
| 3008 |
model = "DualEndpoint", |
|
| 3009 |
data = "ANY" |
|
| 3010 |
), |
|
| 3011 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3012 |
# Compute the target biomarker prob at this dose. |
|
| 3013 |
# Get the biomarker level samples at the dose grid points. |
|
| 3014 | 60x |
biom_level_samples <- biomarker( |
| 3015 | 60x |
xLevel = seq_len(data@nGrid), |
| 3016 | 60x |
model, |
| 3017 | 60x |
samples, |
| 3018 |
... |
|
| 3019 |
) |
|
| 3020 | ||
| 3021 |
# If target is relative to maximum. |
|
| 3022 | 60x |
if (stopping@is_relative) {
|
| 3023 |
# If there is an 'Emax' parameter, target biomarker level will |
|
| 3024 |
# be relative to 'Emax', otherwise will be relative to the |
|
| 3025 |
# maximum biomarker level achieved in the given dose range. |
|
| 3026 | 60x |
if ("Emax" %in% names(samples)) {
|
| 3027 |
# For each sample, look which dose is maximizing the |
|
| 3028 |
# simultaneous probability to be in the target biomarker |
|
| 3029 |
# range and below overdose toxicity. |
|
| 3030 | ! |
prob_target <- numeric(ncol(biom_level_samples)) |
| 3031 | ! |
prob_target <- sapply( |
| 3032 | ! |
seq(1, ncol(biom_level_samples)), |
| 3033 | ! |
function(x) {
|
| 3034 | ! |
sum( |
| 3035 | ! |
biom_level_samples[, x] >= |
| 3036 | ! |
stopping@target[1] * samples@data$Emax & |
| 3037 | ! |
biom_level_samples[, x] <= |
| 3038 | ! |
stopping@target[2] * samples@data$Emax |
| 3039 |
) / |
|
| 3040 | ! |
nrow(biom_level_samples) |
| 3041 |
} |
|
| 3042 |
) |
|
| 3043 |
} else {
|
|
| 3044 |
# For each sample, look which was the minimum dose giving |
|
| 3045 |
# relative target level. |
|
| 3046 | 60x |
targetIndex <- apply( |
| 3047 | 60x |
biom_level_samples, |
| 3048 | 60x |
1L, |
| 3049 | 60x |
function(x) {
|
| 3050 | 28704x |
rnx <- range(x) |
| 3051 | 28704x |
min(which( |
| 3052 | 28704x |
(x >= stopping@target[1] * diff(rnx) + rnx[1]) & |
| 3053 | 28704x |
(x <= stopping@target[2] * diff(rnx) + rnx[1] + 1e-10) |
| 3054 |
)) |
|
| 3055 |
} |
|
| 3056 |
) |
|
| 3057 | 60x |
prob_target <- numeric(ncol(biom_level_samples)) |
| 3058 | 60x |
tab <- table(targetIndex) |
| 3059 | 60x |
prob_target[as.numeric(names(tab))] <- tab |
| 3060 | 60x |
prob_target <- prob_target / nrow(biom_level_samples) |
| 3061 |
} |
|
| 3062 |
} else {
|
|
| 3063 |
# Otherwise the target is absolute. |
|
| 3064 |
# For each sample, look which dose is maximizing the |
|
| 3065 |
# simultaneous probability to be in the target biomarker |
|
| 3066 |
# range and below overdose toxicity. |
|
| 3067 | ! |
prob_target <- numeric(ncol(biom_level_samples)) |
| 3068 | ! |
prob_target <- sapply( |
| 3069 | ! |
seq(1, ncol(biom_level_samples)), |
| 3070 | ! |
function(x) {
|
| 3071 | ! |
sum( |
| 3072 | ! |
biom_level_samples[, x] >= stopping@target[1] & |
| 3073 | ! |
biom_level_samples[, x] <= stopping@target[2] |
| 3074 |
) / |
|
| 3075 | ! |
nrow(biom_level_samples) |
| 3076 |
} |
|
| 3077 |
) |
|
| 3078 |
} |
|
| 3079 | ||
| 3080 | 60x |
prob_target <- ifelse( |
| 3081 | 60x |
is.na(dose), |
| 3082 | 60x |
0, |
| 3083 | 60x |
prob_target[which(data@doseGrid == dose)] |
| 3084 |
) |
|
| 3085 | ||
| 3086 | 60x |
do_stop <- prob_target >= stopping@prob |
| 3087 | ||
| 3088 | 60x |
msg <- paste( |
| 3089 | 60x |
"Probability for target biomarker is", |
| 3090 | 60x |
round(prob_target * 100), |
| 3091 | 60x |
"% for dose", |
| 3092 | 60x |
dose, |
| 3093 | 60x |
"and thus", |
| 3094 | 60x |
ifelse(do_stop, "above", "below"), |
| 3095 | 60x |
"the required", |
| 3096 | 60x |
round(stopping@prob * 100), |
| 3097 |
"%" |
|
| 3098 |
) |
|
| 3099 | ||
| 3100 | 60x |
structure( |
| 3101 | 60x |
do_stop, |
| 3102 | 60x |
message = msg, |
| 3103 | 60x |
report_label = stopping@report_label |
| 3104 |
) |
|
| 3105 |
} |
|
| 3106 |
) |
|
| 3107 | ||
| 3108 |
## StoppingSpecificDose ---- |
|
| 3109 | ||
| 3110 |
#' @describeIn stopTrial if Stopping rule is met for specific dose of the planned |
|
| 3111 |
#' dose grid and not just for the default next best dose. |
|
| 3112 |
#' |
|
| 3113 |
#' @aliases stopTrial-StoppingSpecificDose |
|
| 3114 |
#' |
|
| 3115 |
#' @export |
|
| 3116 |
#' @example examples/Rules-method-stopTrial-StoppingSpecificDose.R |
|
| 3117 |
#' |
|
| 3118 |
setMethod( |
|
| 3119 |
f = "stopTrial", |
|
| 3120 |
signature = signature( |
|
| 3121 |
stopping = "StoppingSpecificDose", |
|
| 3122 |
dose = "numeric", |
|
| 3123 |
samples = "ANY", |
|
| 3124 |
model = "ANY", |
|
| 3125 |
data = "Data" |
|
| 3126 |
), |
|
| 3127 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3128 |
# Specific dose must be a part of the dose grid. |
|
| 3129 | 6x |
assert_subset(x = stopping@dose@.Data, choices = data@doseGrid) |
| 3130 | ||
| 3131 |
# Evaluate the original (wrapped) stopping rule at the specific dose. |
|
| 3132 | 6x |
result <- stopTrial( |
| 3133 | 6x |
stopping = stopping@rule, |
| 3134 | 6x |
dose = stopping@dose@.Data, |
| 3135 | 6x |
samples = samples, |
| 3136 | 6x |
model = model, |
| 3137 | 6x |
data = data, |
| 3138 |
... |
|
| 3139 |
) |
|
| 3140 |
# Correct the text message from the original stopping rule. |
|
| 3141 | 6x |
attr(result, "message") <- gsub( |
| 3142 | 6x |
pattern = "next best", |
| 3143 | 6x |
replacement = "specific", |
| 3144 | 6x |
x = attr(result, "message"), |
| 3145 | 6x |
ignore.case = TRUE |
| 3146 |
) |
|
| 3147 | ||
| 3148 | 6x |
attr(result, "report_label") <- stopping@report_label |
| 3149 | ||
| 3150 | 6x |
result |
| 3151 |
} |
|
| 3152 |
) |
|
| 3153 | ||
| 3154 |
# nolint start |
|
| 3155 | ||
| 3156 |
## -------------------------------------------------- |
|
| 3157 |
## Stopping when the highest dose is reached |
|
| 3158 |
## -------------------------------------------------- |
|
| 3159 | ||
| 3160 |
##' @describeIn stopTrial Stop when the highest dose is reached |
|
| 3161 |
##' |
|
| 3162 |
##' @example examples/Rules-method-stopTrial-StoppingHighestDose.R |
|
| 3163 |
setMethod( |
|
| 3164 |
"stopTrial", |
|
| 3165 |
signature = signature( |
|
| 3166 |
stopping = "StoppingHighestDose", |
|
| 3167 |
dose = "numeric", |
|
| 3168 |
samples = "ANY", |
|
| 3169 |
model = "ANY", |
|
| 3170 |
data = "Data" |
|
| 3171 |
), |
|
| 3172 |
def = function(stopping, dose, samples, model, data, ...) {
|
|
| 3173 | 34x |
isHighestDose <- ifelse( |
| 3174 | 34x |
is.na(dose), |
| 3175 | 34x |
FALSE, |
| 3176 | 34x |
(dose == data@doseGrid[data@nGrid]) |
| 3177 |
) |
|
| 3178 | 34x |
return(structure( |
| 3179 | 34x |
isHighestDose, |
| 3180 | 34x |
message = paste( |
| 3181 | 34x |
"Next best dose is", |
| 3182 | 34x |
dose, |
| 3183 | 34x |
"and thus", |
| 3184 | 34x |
ifelse(isHighestDose, "the", "not the"), |
| 3185 | 34x |
"highest dose" |
| 3186 |
), |
|
| 3187 | 34x |
report_label = stopping@report_label |
| 3188 |
)) |
|
| 3189 |
} |
|
| 3190 |
) |
|
| 3191 | ||
| 3192 |
## StoppingOrdinal ---- |
|
| 3193 | ||
| 3194 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
| 3195 |
#' |
|
| 3196 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3197 |
#' |
|
| 3198 |
#' @aliases stopTrial-StoppingOrdinal |
|
| 3199 |
#' @example examples/Rules-method-stopTrial-StoppingOrdinal.R |
|
| 3200 |
#' |
|
| 3201 |
setMethod( |
|
| 3202 |
f = "stopTrial", |
|
| 3203 |
signature = signature( |
|
| 3204 |
stopping = "StoppingOrdinal", |
|
| 3205 |
dose = "numeric", |
|
| 3206 |
samples = "ANY", |
|
| 3207 |
model = "LogisticLogNormalOrdinal", |
|
| 3208 |
data = "DataOrdinal" |
|
| 3209 |
), |
|
| 3210 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3211 | 22x |
stopTrial( |
| 3212 | 22x |
stopping = stopping@rule, |
| 3213 | 22x |
dose = dose, |
| 3214 | 22x |
samples = h_convert_ordinal_samples(samples, stopping@grade), |
| 3215 | 22x |
model = h_convert_ordinal_model(model, stopping@grade), |
| 3216 | 22x |
data = h_convert_ordinal_data(data, stopping@grade), |
| 3217 |
... |
|
| 3218 |
) |
|
| 3219 |
} |
|
| 3220 |
) |
|
| 3221 | ||
| 3222 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
| 3223 |
#' |
|
| 3224 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3225 |
#' |
|
| 3226 |
#' @aliases stopTrial-StoppingOrdinal |
|
| 3227 |
#' @example examples/Rules-method-stopTrial-StoppingOrdinal.R |
|
| 3228 |
#' |
|
| 3229 |
setMethod( |
|
| 3230 |
f = "stopTrial", |
|
| 3231 |
signature = signature( |
|
| 3232 |
stopping = "StoppingOrdinal", |
|
| 3233 |
dose = "numeric", |
|
| 3234 |
samples = "ANY", |
|
| 3235 |
model = "ANY", |
|
| 3236 |
data = "ANY" |
|
| 3237 |
), |
|
| 3238 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3239 | ! |
stop( |
| 3240 | ! |
paste0( |
| 3241 | ! |
"StoppingOrdinal objects can only be used with LogisticLogNormalOrdinal ", |
| 3242 | ! |
"models and DataOrdinal data objects. In this case, the model is a '", |
| 3243 | ! |
class(model), |
| 3244 | ! |
"' object and the data is in a ", |
| 3245 | ! |
class(data), |
| 3246 | ! |
" object." |
| 3247 |
) |
|
| 3248 |
) |
|
| 3249 |
} |
|
| 3250 |
) |
|
| 3251 | ||
| 3252 |
## StoppingExternal ---- |
|
| 3253 | ||
| 3254 |
#' @describeIn stopTrial Stop based on an external flag. |
|
| 3255 |
#' |
|
| 3256 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3257 |
#' @param external (`flag`)\cr whether to stop based on the external |
|
| 3258 |
#' result or not. |
|
| 3259 |
#' |
|
| 3260 |
#' @aliases stopTrial-StoppingExternal |
|
| 3261 |
#' @example examples/Rules-method-stopTrial-StoppingExternal.R |
|
| 3262 |
#' |
|
| 3263 |
setMethod( |
|
| 3264 |
f = "stopTrial", |
|
| 3265 |
signature = signature( |
|
| 3266 |
stopping = "StoppingExternal", |
|
| 3267 |
dose = "numeric", |
|
| 3268 |
samples = "ANY", |
|
| 3269 |
model = "ANY", |
|
| 3270 |
data = "ANY" |
|
| 3271 |
), |
|
| 3272 |
definition = function(stopping, dose, samples, model, data, external, ...) {
|
|
| 3273 | 6x |
assert_flag(external) |
| 3274 | ||
| 3275 | 6x |
msg <- paste( |
| 3276 | 6x |
"Based on external result", |
| 3277 | 6x |
ifelse(external, "stop", "continue") |
| 3278 |
) |
|
| 3279 | ||
| 3280 | 6x |
structure( |
| 3281 | 6x |
external, |
| 3282 | 6x |
message = msg, |
| 3283 | 6x |
report_label = stopping@report_label |
| 3284 |
) |
|
| 3285 |
} |
|
| 3286 |
) |
|
| 3287 | ||
| 3288 | ||
| 3289 |
## ============================================================ |
|
| 3290 | ||
| 3291 |
## -------------------------------------------------- |
|
| 3292 |
## "MAX" combination of cohort size rules |
|
| 3293 |
## -------------------------------------------------- |
|
| 3294 | ||
| 3295 |
##' "MAX" combination of cohort size rules |
|
| 3296 |
##' |
|
| 3297 |
##' This function combines cohort size rules by taking |
|
| 3298 |
##' the maximum of all sizes. |
|
| 3299 |
##' |
|
| 3300 |
##' @param \dots Objects of class \code{\linkS4class{CohortSize}}
|
|
| 3301 |
##' @return the combination as an object of class |
|
| 3302 |
##' \code{\linkS4class{CohortSizeMax}}
|
|
| 3303 |
##' |
|
| 3304 |
##' @seealso \code{\link{minSize}}
|
|
| 3305 |
##' @export |
|
| 3306 |
##' @keywords methods |
|
| 3307 |
setGeneric( |
|
| 3308 |
"maxSize", |
|
| 3309 |
def = function(...) {
|
|
| 3310 |
## there should be no default method, |
|
| 3311 |
## therefore just forward to next method! |
|
| 3312 | 34x |
standardGeneric("maxSize")
|
| 3313 |
}, |
|
| 3314 |
valueClass = "CohortSizeMax" |
|
| 3315 |
) |
|
| 3316 | ||
| 3317 |
##' @describeIn maxSize The method combining cohort size rules by taking maximum |
|
| 3318 |
##' @example examples/Rules-method-maxSize.R |
|
| 3319 |
setMethod("maxSize", "CohortSize", def = function(...) {
|
|
| 3320 | 34x |
CohortSizeMax(list(...)) |
| 3321 |
}) |
|
| 3322 | ||
| 3323 |
## -------------------------------------------------- |
|
| 3324 |
## "MIN" combination of cohort size rules |
|
| 3325 |
## -------------------------------------------------- |
|
| 3326 | ||
| 3327 |
##' "MIN" combination of cohort size rules |
|
| 3328 |
##' |
|
| 3329 |
##' This function combines cohort size rules by taking |
|
| 3330 |
##' the minimum of all sizes. |
|
| 3331 |
##' |
|
| 3332 |
##' @param \dots Objects of class \code{\linkS4class{CohortSize}}
|
|
| 3333 |
##' @return the combination as an object of class |
|
| 3334 |
##' \code{\linkS4class{CohortSizeMin}}
|
|
| 3335 |
##' |
|
| 3336 |
##' @seealso \code{\link{maxSize}}
|
|
| 3337 |
##' @export |
|
| 3338 |
##' @keywords methods |
|
| 3339 |
setGeneric( |
|
| 3340 |
"minSize", |
|
| 3341 |
def = function(...) {
|
|
| 3342 |
## there should be no default method, |
|
| 3343 |
## therefore just forward to next method! |
|
| 3344 | 1x |
standardGeneric("minSize")
|
| 3345 |
}, |
|
| 3346 |
valueClass = "CohortSizeMin" |
|
| 3347 |
) |
|
| 3348 | ||
| 3349 |
##' @describeIn minSize The method combining cohort size rules by taking minimum |
|
| 3350 |
##' @example examples/Rules-method-minSize.R |
|
| 3351 |
setMethod("minSize", "CohortSize", def = function(...) {
|
|
| 3352 | 1x |
CohortSizeMin(list(...)) |
| 3353 |
}) |
|
| 3354 | ||
| 3355 |
# size ---- |
|
| 3356 | ||
| 3357 |
## CohortSizeRange ---- |
|
| 3358 | ||
| 3359 |
#' @describeIn size Determines the size of the next cohort based on the range |
|
| 3360 |
#' into which the next dose falls into. |
|
| 3361 |
#' |
|
| 3362 |
#' @param dose the next dose. |
|
| 3363 |
#' @param data the data input, an object of class [`Data`]. |
|
| 3364 |
#' |
|
| 3365 |
#' @aliases size-CohortSizeRange |
|
| 3366 |
#' @example examples/Rules-method-size-CohortSizeRange.R |
|
| 3367 |
#' |
|
| 3368 |
setMethod( |
|
| 3369 |
f = "size", |
|
| 3370 |
signature = signature( |
|
| 3371 |
object = "CohortSizeRange" |
|
| 3372 |
), |
|
| 3373 |
definition = function(object, dose, data) {
|
|
| 3374 |
# If the recommended next dose is NA, don't check it and return 0. |
|
| 3375 | 104x |
if (is.na(dose)) {
|
| 3376 | 1x |
return(0L) |
| 3377 |
} |
|
| 3378 | 103x |
assert_class(data, "Data") |
| 3379 | ||
| 3380 |
# Determine in which interval the next dose is. |
|
| 3381 | 103x |
interval <- findInterval(x = dose, vec = object@intervals) |
| 3382 | 103x |
object@cohort_size[interval] |
| 3383 |
} |
|
| 3384 |
) |
|
| 3385 | ||
| 3386 |
## CohortSizeDLT ---- |
|
| 3387 | ||
| 3388 |
#' @describeIn size Determines the size of the next cohort based on the number |
|
| 3389 |
#' of DLTs so far. |
|
| 3390 |
#' |
|
| 3391 |
#' @param dose the next dose. |
|
| 3392 |
#' @param data the data input, an object of class [`Data`]. |
|
| 3393 |
#' |
|
| 3394 |
#' @aliases size-CohortSizeDLT |
|
| 3395 |
#' @example examples/Rules-method-size-CohortSizeDLT.R |
|
| 3396 |
#' |
|
| 3397 |
setMethod( |
|
| 3398 |
f = "size", |
|
| 3399 |
signature = signature( |
|
| 3400 |
object = "CohortSizeDLT" |
|
| 3401 |
), |
|
| 3402 |
definition = function(object, dose, data) {
|
|
| 3403 |
# If the recommended next dose is NA, don't check it and return 0. |
|
| 3404 | 77x |
if (is.na(dose)) {
|
| 3405 | 1x |
return(0L) |
| 3406 |
} |
|
| 3407 | 76x |
assert_class(data, "Data") |
| 3408 | ||
| 3409 |
# Determine how many DLTs have occurred so far. |
|
| 3410 | 76x |
dlt_happened <- sum(data@y) |
| 3411 | ||
| 3412 |
# Determine in which interval this is. |
|
| 3413 | 76x |
interval <- findInterval(x = dlt_happened, vec = object@intervals) |
| 3414 | 76x |
object@cohort_size[interval] |
| 3415 |
} |
|
| 3416 |
) |
|
| 3417 | ||
| 3418 |
## CohortSizeMax ---- |
|
| 3419 | ||
| 3420 |
#' @describeIn size Determines the size of the next cohort based on maximum of |
|
| 3421 |
#' multiple cohort size rules. |
|
| 3422 |
#' |
|
| 3423 |
#' @param dose the next dose. |
|
| 3424 |
#' @param data the data input, an object of class [`Data`]. |
|
| 3425 |
#' |
|
| 3426 |
#' @aliases size-CohortSizeMax |
|
| 3427 |
#' @example examples/Rules-method-size-CohortSizeMax.R |
|
| 3428 |
#' |
|
| 3429 |
setMethod( |
|
| 3430 |
f = "size", |
|
| 3431 |
signature = signature( |
|
| 3432 |
object = "CohortSizeMax" |
|
| 3433 |
), |
|
| 3434 |
definition = function(object, dose, data) {
|
|
| 3435 |
# If the recommended next dose is NA, don't check it and return 0. |
|
| 3436 | 46x |
if (is.na(dose)) {
|
| 3437 | 1x |
return(0L) |
| 3438 |
} |
|
| 3439 | 45x |
assert_multi_class(data, c("Data", "DataOrdinal"))
|
| 3440 | ||
| 3441 |
# Evaluate the individual cohort size rules in the list. |
|
| 3442 | 45x |
individual_results <- sapply( |
| 3443 | 45x |
object@cohort_sizes, |
| 3444 | 45x |
size, |
| 3445 | 45x |
dose = dose, |
| 3446 | 45x |
data = data |
| 3447 |
) |
|
| 3448 |
# The overall result. |
|
| 3449 | 45x |
max(individual_results) |
| 3450 |
} |
|
| 3451 |
) |
|
| 3452 | ||
| 3453 |
## CohortSizeMin ---- |
|
| 3454 | ||
| 3455 |
#' @describeIn size Determines the size of the next cohort based on minimum of |
|
| 3456 |
#' multiple cohort size rules. |
|
| 3457 |
#' |
|
| 3458 |
#' @param dose the next dose. |
|
| 3459 |
#' @param data the data input, an object of class [`Data`]. |
|
| 3460 |
#' |
|
| 3461 |
#' @aliases size-CohortSizeMin |
|
| 3462 |
#' @example examples/Rules-method-size-CohortSizeMin.R |
|
| 3463 |
#' |
|
| 3464 |
setMethod( |
|
| 3465 |
f = "size", |
|
| 3466 |
signature = signature( |
|
| 3467 |
object = "CohortSizeMin" |
|
| 3468 |
), |
|
| 3469 |
definition = function(object, dose, data) {
|
|
| 3470 |
# If the recommended next dose is NA, don't check it and return 0. |
|
| 3471 | 23x |
if (is.na(dose)) {
|
| 3472 | 1x |
return(0L) |
| 3473 |
} |
|
| 3474 | 22x |
assert_multi_class(data, c("Data", "DataOrdinal"))
|
| 3475 | ||
| 3476 |
# Evaluate the individual cohort size rules in the list. |
|
| 3477 | 22x |
individual_results <- sapply( |
| 3478 | 22x |
object@cohort_sizes, |
| 3479 | 22x |
size, |
| 3480 | 22x |
dose = dose, |
| 3481 | 22x |
data = data |
| 3482 |
) |
|
| 3483 |
# The overall result. |
|
| 3484 | 22x |
min(individual_results) |
| 3485 |
} |
|
| 3486 |
) |
|
| 3487 | ||
| 3488 |
## CohortSizeConst ---- |
|
| 3489 | ||
| 3490 |
#' @describeIn size Constant cohort size. |
|
| 3491 |
#' |
|
| 3492 |
#' @param dose the next dose. |
|
| 3493 |
#' @param ... not used. |
|
| 3494 |
#' |
|
| 3495 |
#' @aliases size-CohortSizeConst |
|
| 3496 |
#' @example examples/Rules-method-size-CohortSizeConst.R |
|
| 3497 |
#' |
|
| 3498 |
setMethod( |
|
| 3499 |
f = "size", |
|
| 3500 |
signature = signature( |
|
| 3501 |
object = "CohortSizeConst" |
|
| 3502 |
), |
|
| 3503 |
definition = function(object, dose, ...) {
|
|
| 3504 |
# If the recommended next dose is NA, don't check it and return 0. |
|
| 3505 | 435x |
if (is.na(dose)) {
|
| 3506 | 1x |
0L |
| 3507 |
} else {
|
|
| 3508 | 434x |
object@size |
| 3509 |
} |
|
| 3510 |
} |
|
| 3511 |
) |
|
| 3512 | ||
| 3513 |
## CohortSizeParts ---- |
|
| 3514 | ||
| 3515 |
#' @describeIn size Determines the size of the next cohort based on the parts. |
|
| 3516 |
#' |
|
| 3517 |
#' @param dose the next dose. |
|
| 3518 |
#' @param data the data input, an object of class [`Data`]. |
|
| 3519 |
#' |
|
| 3520 |
#' @aliases size-CohortSizeParts |
|
| 3521 |
#' @example examples/Rules-method-size-CohortSizeParts.R |
|
| 3522 |
#' |
|
| 3523 |
setMethod( |
|
| 3524 |
f = "size", |
|
| 3525 |
signature = signature( |
|
| 3526 |
object = "CohortSizeParts" |
|
| 3527 |
), |
|
| 3528 |
definition = function(object, dose, data) {
|
|
| 3529 |
# If the recommended next dose is NA, don't check it and return 0. |
|
| 3530 | 12x |
if (is.na(dose)) {
|
| 3531 | 2x |
return(0L) |
| 3532 |
} else {
|
|
| 3533 | 10x |
assert_class(data, "DataParts") |
| 3534 | 10x |
object@cohort_sizes[data@nextPart] |
| 3535 |
} |
|
| 3536 |
} |
|
| 3537 |
) |
|
| 3538 | ||
| 3539 |
## CohortSizeOrdinal ---- |
|
| 3540 | ||
| 3541 |
#' @describeIn size Determines the size of the next cohort in a ordinal CRM trial. |
|
| 3542 |
#' |
|
| 3543 |
#' @param dose (`numeric`) the next dose. |
|
| 3544 |
#' @param data the data input, an object of class [`DataOrdinal`]. |
|
| 3545 |
#' |
|
| 3546 |
#' @aliases size-CohortSizeOrdinal |
|
| 3547 |
#' @example examples/Rules-method-size-CohortSizeOrdinal.R |
|
| 3548 |
#' |
|
| 3549 |
setMethod( |
|
| 3550 |
f = "size", |
|
| 3551 |
signature = signature( |
|
| 3552 |
object = "CohortSizeOrdinal" |
|
| 3553 |
), |
|
| 3554 |
definition = function(object, dose, data, ...) {
|
|
| 3555 |
# Validate |
|
| 3556 | 4x |
assert_numeric(dose, len = 1, lower = 0) |
| 3557 | 4x |
assert_class(data, "DataOrdinal") |
| 3558 |
# Execute |
|
| 3559 | ||
| 3560 | 4x |
size( |
| 3561 | 4x |
object@rule, |
| 3562 | 4x |
dose = dose, |
| 3563 | 4x |
data = h_convert_ordinal_data(data, object@grade), |
| 3564 |
... |
|
| 3565 |
) |
|
| 3566 |
} |
|
| 3567 |
) |
|
| 3568 | ||
| 3569 |
## ------------------------------------------------------------------------------------------------ |
|
| 3570 |
## Stopping based on a target ratio of the upper to the lower 95% credibility interval |
|
| 3571 |
## ------------------------------------------------------------------------------------------------ |
|
| 3572 |
##' @describeIn stopTrial Stop based on 'StoppingTDCIRatio' class when |
|
| 3573 |
##' reaching the target ratio of the upper to the lower 95% credibility |
|
| 3574 |
##' interval of the estimate (TDtargetEndOfTrial). This is a stopping rule which incorporate only |
|
| 3575 |
##' DLE responses and DLE samples are given |
|
| 3576 |
##' |
|
| 3577 |
##' @example examples/Rules-method-stopTrialCITDsamples.R |
|
| 3578 |
##' |
|
| 3579 |
##' @export |
|
| 3580 |
##' @keywords methods |
|
| 3581 |
setMethod( |
|
| 3582 |
f = "stopTrial", |
|
| 3583 |
signature = signature( |
|
| 3584 |
stopping = "StoppingTDCIRatio", |
|
| 3585 |
dose = "ANY", |
|
| 3586 |
samples = "Samples", |
|
| 3587 |
model = "ModelTox", |
|
| 3588 |
data = "ANY" |
|
| 3589 |
), |
|
| 3590 |
definition = function(stopping, dose, samples, model, data, ...) {
|
|
| 3591 | 481x |
assert_probability(stopping@prob_target) |
| 3592 | ||
| 3593 | 481x |
dose_target_samples <- dose( |
| 3594 | 481x |
x = stopping@prob_target, |
| 3595 | 481x |
model = model, |
| 3596 | 481x |
samples = samples, |
| 3597 |
... |
|
| 3598 |
) |
|
| 3599 |
# 95% credibility interval. |
|
| 3600 | 481x |
dose_target_ci <- quantile(dose_target_samples, probs = c(0.025, 0.975)) |
| 3601 | 481x |
dose_target_ci_ratio <- dose_target_ci[[2]] / dose_target_ci[[1]] |
| 3602 | ||
| 3603 | 481x |
do_stop <- dose_target_ci_ratio <= stopping@target_ratio |
| 3604 | 481x |
text <- paste0( |
| 3605 | 481x |
"95% CI is (",
|
| 3606 | 481x |
paste(dose_target_ci, collapse = ", "), |
| 3607 | 481x |
"), Ratio = ", |
| 3608 | 481x |
round(dose_target_ci_ratio, 4), |
| 3609 | 481x |
" is ", |
| 3610 | 481x |
ifelse(do_stop, "less than or equal to ", "greater than "), |
| 3611 | 481x |
"target_ratio = ", |
| 3612 | 481x |
stopping@target_ratio |
| 3613 |
) |
|
| 3614 | 481x |
structure(do_stop, message = text, report_label = stopping@report_label) |
| 3615 |
} |
|
| 3616 |
) |
|
| 3617 | ||
| 3618 |
## ---------------------------------------------------------------------------------------------- |
|
| 3619 |
## Stopping based on a target ratio of the upper to the lower 95% credibility interval |
|
| 3620 |
## ------------------------------------------------------------------------------------------------ |
|
| 3621 |
##' @describeIn stopTrial Stop based on 'StoppingTDCIRatio' class |
|
| 3622 |
##' when reaching the target ratio of the upper to the lower 95% credibility |
|
| 3623 |
##' interval of the estimate (TDtargetEndOfTrial). This is a stopping rule which incorporate only |
|
| 3624 |
##' DLE responses and no DLE samples are involved |
|
| 3625 |
##' @example examples/Rules-method-stopTrialCITD.R |
|
| 3626 |
setMethod( |
|
| 3627 |
"stopTrial", |
|
| 3628 |
signature = signature( |
|
| 3629 |
stopping = "StoppingTDCIRatio", |
|
| 3630 |
dose = "ANY", |
|
| 3631 |
samples = "missing", |
|
| 3632 |
model = "ModelTox", |
|
| 3633 |
data = "ANY" |
|
| 3634 |
), |
|
| 3635 |
def = function(stopping, dose, model, data, ...) {
|
|
| 3636 | 480x |
assert_probability(stopping@prob_target) |
| 3637 | ||
| 3638 | 480x |
prob_target <- stopping@prob_target |
| 3639 | 480x |
dose_target_samples <- dose(x = prob_target, model = model, ...) |
| 3640 |
## Find the variance of the log of the dose_target_samples(eta) |
|
| 3641 | 480x |
M1 <- matrix( |
| 3642 | 480x |
c( |
| 3643 | 480x |
-1 / (model@phi2), |
| 3644 | 480x |
-(log(prob_target / (1 - prob_target)) - model@phi1) / (model@phi2)^2 |
| 3645 |
), |
|
| 3646 | 480x |
1, |
| 3647 | 480x |
2 |
| 3648 |
) |
|
| 3649 | 480x |
M2 <- model@Pcov |
| 3650 | 480x |
varEta <- as.vector(M1 %*% M2 %*% t(M1)) |
| 3651 | ||
| 3652 |
## Find the upper and lower limit of the 95% credibility interval |
|
| 3653 | 480x |
CI <- exp(log(dose_target_samples) + c(-1, 1) * 1.96 * sqrt(varEta)) |
| 3654 | 480x |
ratio <- CI[2] / CI[1] |
| 3655 | ||
| 3656 |
## so can we stop? |
|
| 3657 | 480x |
doStop <- ratio <= stopping@target_ratio |
| 3658 |
## generate message |
|
| 3659 | 480x |
text <- paste( |
| 3660 | 480x |
"95% CI is (",
|
| 3661 | 480x |
round(CI[1], 4), |
| 3662 |
",", |
|
| 3663 | 480x |
round(CI[2], 4), |
| 3664 | 480x |
"), Ratio =", |
| 3665 | 480x |
round(ratio, 4), |
| 3666 | 480x |
"is ", |
| 3667 | 480x |
ifelse(doStop, "is less than or equal to", "greater than"), |
| 3668 | 480x |
"target_ratio =", |
| 3669 | 480x |
stopping@target_ratio |
| 3670 |
) |
|
| 3671 |
## return both |
|
| 3672 | 480x |
return(structure( |
| 3673 | 480x |
doStop, |
| 3674 | 480x |
message = text, |
| 3675 | 480x |
report_label = stopping@report_label |
| 3676 |
)) |
|
| 3677 |
} |
|
| 3678 |
) |
|
| 3679 | ||
| 3680 |
## -------------------------------------------------------------------------------------------------- |
|
| 3681 |
## Stopping based on a target ratio of the upper to the lower 95% credibility interval |
|
| 3682 |
## ------------------------------------------------------------------------------------------------ |
|
| 3683 |
##' @describeIn stopTrial Stop based on reaching the target ratio of the upper to the lower 95% credibility |
|
| 3684 |
##' interval of the estimate (the minimum of Gstar and TDtargetEndOfTrial). This is a stopping rule which |
|
| 3685 |
##' incorporate DLE and efficacy responses and DLE and efficacy samples are also used. |
|
| 3686 |
##' |
|
| 3687 |
##' @param TDderive the function which derives from the input, a vector of the posterior samples called |
|
| 3688 |
##' \code{TDsamples} of the dose
|
|
| 3689 |
##' which has the probability of the occurrence of DLE equals to either the targetDuringTrial or |
|
| 3690 |
##' targetEndOfTrial, the final next best TDtargetDuringTrial (the dose with probability of the |
|
| 3691 |
##' occurrence of DLE equals to the targetDuringTrial)and TDtargetEndOfTrial estimate. |
|
| 3692 |
##' @param Effmodel the efficacy model of \code{\linkS4class{ModelEff}} class object
|
|
| 3693 |
##' @param Effsamples the efficacy samples of \code{\linkS4class{Samples}} class object
|
|
| 3694 |
##' @param Gstarderive the function which derives from the input, a vector of the posterior Gstar (the dose |
|
| 3695 |
##' which gives the maximum gain value) samples |
|
| 3696 |
##' called \code{Gstarsamples}, the final next best Gstar estimate.
|
|
| 3697 |
##' |
|
| 3698 |
##' @example examples/Rules-method-stopTrialCIMaxGainSamples.R |
|
| 3699 |
setMethod( |
|
| 3700 |
"stopTrial", |
|
| 3701 |
signature = signature( |
|
| 3702 |
stopping = "StoppingMaxGainCIRatio", |
|
| 3703 |
dose = "ANY", |
|
| 3704 |
samples = "Samples", |
|
| 3705 |
model = "ModelTox", |
|
| 3706 |
data = "DataDual" |
|
| 3707 |
), |
|
| 3708 |
def = function( |
|
| 3709 |
stopping, |
|
| 3710 |
dose, |
|
| 3711 |
samples, |
|
| 3712 |
model, |
|
| 3713 |
data, |
|
| 3714 |
TDderive, |
|
| 3715 |
Effmodel, |
|
| 3716 |
Effsamples, |
|
| 3717 |
Gstarderive, |
|
| 3718 |
... |
|
| 3719 |
) {
|
|
| 3720 | ! |
prob_target <- stopping@prob_target |
| 3721 | ||
| 3722 |
## checks |
|
| 3723 | ! |
assert_probability(prob_target) |
| 3724 | ! |
stopifnot(is(Effmodel, "ModelEff")) |
| 3725 | ! |
stopifnot(is(Effsamples, "Samples")) |
| 3726 | ! |
stopifnot(is.function(TDderive)) |
| 3727 | ! |
stopifnot(is.function(Gstarderive)) |
| 3728 | ||
| 3729 |
## find the TDtarget End of Trial samples |
|
| 3730 | ! |
TDtargetEndOfTrialSamples <- dose( |
| 3731 | ! |
x = prob_target, |
| 3732 | ! |
model = model, |
| 3733 | ! |
samples = samples, |
| 3734 |
... |
|
| 3735 |
) |
|
| 3736 |
## Find the TDtarget End of trial estimate |
|
| 3737 | ! |
TDtargetEndOfTrialEstimate <- TDderive(TDtargetEndOfTrialSamples) |
| 3738 | ||
| 3739 |
## Find the gain value samples then the GstarSamples |
|
| 3740 | ! |
points <- data@doseGrid |
| 3741 | ||
| 3742 | ! |
GainSamples <- matrix( |
| 3743 | ! |
nrow = size(samples), |
| 3744 | ! |
ncol = length(points) |
| 3745 |
) |
|
| 3746 | ||
| 3747 |
## evaluate the probs, for all gain samples. |
|
| 3748 | ! |
for (i in seq_along(points)) {
|
| 3749 |
## Now we want to evaluate for the |
|
| 3750 |
## following dose: |
|
| 3751 | ! |
GainSamples[, i] <- gain( |
| 3752 | ! |
dose = points[i], |
| 3753 | ! |
model, |
| 3754 | ! |
samples, |
| 3755 | ! |
Effmodel, |
| 3756 | ! |
Effsamples, |
| 3757 |
... |
|
| 3758 |
) |
|
| 3759 |
} |
|
| 3760 | ||
| 3761 |
## Find the maximum gain value samples |
|
| 3762 | ! |
MaxGainSamples <- apply(GainSamples, 1, max) |
| 3763 | ||
| 3764 |
## Obtain Gstar samples, samples for the dose level which gives the maximum gain value |
|
| 3765 | ! |
IndexG <- apply(GainSamples, 1, which.max) |
| 3766 | ! |
GstarSamples <- data@doseGrid[IndexG] |
| 3767 | ||
| 3768 |
## Find the Gstar estimate |
|
| 3769 | ||
| 3770 | ! |
Gstar <- Gstarderive(GstarSamples) |
| 3771 |
## Find the 95% credibility interval of Gstar and its ratio of the upper to the lower limit |
|
| 3772 | ! |
CIGstar <- quantile(GstarSamples, probs = c(0.025, 0.975)) |
| 3773 | ! |
ratioGstar <- as.numeric(CIGstar[2] / CIGstar[1]) |
| 3774 | ||
| 3775 |
## Find the 95% credibility interval of TDtargetEndOfTrial and its ratio of the upper to the lower limit |
|
| 3776 | ! |
CITDEOT <- quantile(TDtargetEndOfTrialSamples, probs = c(0.025, 0.975)) |
| 3777 | ! |
ratioTDEOT <- as.numeric(CITDEOT[2] / CITDEOT[1]) |
| 3778 | ||
| 3779 |
## Find which is smaller (TDtargetEndOfTrialEstimate or Gstar) |
|
| 3780 | ||
| 3781 | ! |
if (TDtargetEndOfTrialEstimate <= Gstar) {
|
| 3782 |
## Find the upper and lower limit of the 95% credibility interval and its ratio of the smaller |
|
| 3783 | ! |
CI <- CITDEOT |
| 3784 | ! |
ratio <- ratioTDEOT |
| 3785 | ! |
chooseTD <- TRUE |
| 3786 |
} else {
|
|
| 3787 | ! |
CI <- CIGstar |
| 3788 | ! |
ratio <- ratioGstar |
| 3789 | ! |
chooseTD <- FALSE |
| 3790 |
} |
|
| 3791 | ||
| 3792 |
## so can we stop? |
|
| 3793 | ! |
doStop <- ratio <= stopping@target_ratio |
| 3794 |
## generate message |
|
| 3795 | ! |
text1 <- paste( |
| 3796 | ! |
"Gstar estimate is", |
| 3797 | ! |
round(Gstar, 4), |
| 3798 | ! |
"with 95% CI (",
|
| 3799 | ! |
round(CIGstar[1], 4), |
| 3800 |
",", |
|
| 3801 | ! |
round(CIGstar[2], 4), |
| 3802 | ! |
") and its ratio =", |
| 3803 | ! |
round(ratioGstar, 4) |
| 3804 |
) |
|
| 3805 | ! |
text2 <- paste( |
| 3806 | ! |
"TDtargetEndOfTrial estimate is ", |
| 3807 | ! |
round(TDtargetEndOfTrialEstimate, 4), |
| 3808 | ! |
"with 95% CI (",
|
| 3809 | ! |
round(CITDEOT[1], 4), |
| 3810 |
",", |
|
| 3811 | ! |
round(CITDEOT[2], 4), |
| 3812 | ! |
") and its ratio=", |
| 3813 | ! |
round(ratioTDEOT, 4) |
| 3814 |
) |
|
| 3815 | ! |
text3 <- paste( |
| 3816 | ! |
ifelse(chooseTD, "TDtargetEndOfTrial estimate", "Gstar estimate"), |
| 3817 | ! |
"is smaller with ratio =", |
| 3818 | ! |
round(ratio, 4), |
| 3819 | ! |
" which is ", |
| 3820 | ! |
ifelse(doStop, "is less than or equal to", "greater than"), |
| 3821 | ! |
"target_ratio =", |
| 3822 | ! |
stopping@target_ratio |
| 3823 |
) |
|
| 3824 | ! |
text <- c(text1, text2, text3) |
| 3825 |
## return both |
|
| 3826 | ! |
return(structure( |
| 3827 | ! |
doStop, |
| 3828 | ! |
message = text, |
| 3829 | ! |
report_label = stopping@report_label |
| 3830 |
)) |
|
| 3831 |
} |
|
| 3832 |
) |
|
| 3833 | ||
| 3834 |
## ----------------------------------------------------------------------------------------------- |
|
| 3835 |
## Stopping based on a target ratio of the upper to the lower 95% credibility interval |
|
| 3836 |
## -------------------------------------------------------------------------------------------- |
|
| 3837 |
##' @describeIn stopTrial Stop based on reaching the target ratio of the upper to the lower 95% credibility |
|
| 3838 |
##' interval of the estimate (the minimum of Gstar and TDtargetEndOfTrial). This is a stopping rule which |
|
| 3839 |
##' incorporate DLE and efficacy responses without DLE and efficacy samples involved. |
|
| 3840 |
##' @example examples/Rules-method-stopTrialCIMaxGain.R |
|
| 3841 |
setMethod( |
|
| 3842 |
"stopTrial", |
|
| 3843 |
signature = signature( |
|
| 3844 |
stopping = "StoppingMaxGainCIRatio", |
|
| 3845 |
dose = "ANY", |
|
| 3846 |
samples = "missing", |
|
| 3847 |
model = "ModelTox", |
|
| 3848 |
data = "DataDual" |
|
| 3849 |
), |
|
| 3850 |
def = function(stopping, dose, model, data, Effmodel, ...) {
|
|
| 3851 | ! |
prob_target <- stopping@prob_target |
| 3852 | ||
| 3853 |
## checks |
|
| 3854 | ! |
assert_probability(prob_target) |
| 3855 | ! |
stopifnot(is(Effmodel, "ModelEff")) |
| 3856 | ||
| 3857 |
## find the TDtarget End of Trial |
|
| 3858 | ! |
TDtargetEndOfTrial <- dose( |
| 3859 | ! |
x = prob_target, |
| 3860 | ! |
model = model, |
| 3861 |
... |
|
| 3862 |
) |
|
| 3863 | ||
| 3864 |
## Find the dose with maximum gain value |
|
| 3865 | ! |
Gainfun <- function(DOSE) {
|
| 3866 | ! |
-gain(DOSE, model_dle = model, model_eff = Effmodel, ...) |
| 3867 |
} |
|
| 3868 | ||
| 3869 |
# if(data@placebo) {
|
|
| 3870 |
# n <- length(data@doseGrid) |
|
| 3871 |
# LowestDose <- sort(data@doseGrid)[2]} else {
|
|
| 3872 | ! |
LowestDose <- min(data@doseGrid) |
| 3873 |
# } |
|
| 3874 | ||
| 3875 | ! |
Gstar <- (optim( |
| 3876 | ! |
LowestDose, |
| 3877 | ! |
Gainfun, |
| 3878 | ! |
method = "L-BFGS-B", |
| 3879 | ! |
lower = LowestDose, |
| 3880 | ! |
upper = max(data@doseGrid) |
| 3881 | ! |
)$par) |
| 3882 | ! |
MaxGain <- -(optim( |
| 3883 | ! |
LowestDose, |
| 3884 | ! |
Gainfun, |
| 3885 | ! |
method = "L-BFGS-B", |
| 3886 | ! |
lower = LowestDose, |
| 3887 | ! |
upper = max(data@doseGrid) |
| 3888 | ! |
)$value) |
| 3889 | ! |
if (data@placebo) {
|
| 3890 | ! |
logGstar <- log(Gstar + Effmodel@const) |
| 3891 |
} else {
|
|
| 3892 | ! |
logGstar <- log(Gstar) |
| 3893 |
} |
|
| 3894 | ||
| 3895 |
## From paper (Yeung et. al 2015) |
|
| 3896 | ||
| 3897 | ! |
meanEffGstar <- Effmodel@theta1 + Effmodel@theta2 * log(logGstar) |
| 3898 | ||
| 3899 | ! |
denom <- (model@phi2) * (meanEffGstar) * (1 + logGstar * model@phi2) |
| 3900 | ||
| 3901 | ! |
dgphi1 <- -(meanEffGstar * logGstar * model@phi2 - Effmodel@theta2) / denom |
| 3902 | ||
| 3903 | ! |
dgphi2 <- -((meanEffGstar) * |
| 3904 | ! |
logGstar + |
| 3905 | ! |
meanEffGstar * (logGstar)^2 * model@phi2 - |
| 3906 | ! |
Effmodel@theta2 * logGstar) / |
| 3907 | ! |
denom |
| 3908 | ||
| 3909 | ! |
dgtheta1 <- -(logGstar * model@phi2) / denom |
| 3910 | ||
| 3911 | ! |
dgtheta2 <- -(logGstar * |
| 3912 | ! |
exp(model@phi1 + model@phi2 * logGstar) * |
| 3913 | ! |
model@phi2 * |
| 3914 | ! |
log(logGstar) - |
| 3915 | ! |
1 - |
| 3916 | ! |
exp(model@phi1 + model@phi2 * logGstar)) / |
| 3917 | ! |
denom |
| 3918 | ||
| 3919 |
# DLEPRO <- exp(model@phi1+model@phi2*logGstar) |
|
| 3920 | ||
| 3921 |
# dgphi1 <- Effmodel@theta2*DLEPRO - logGstar*model@phi2*meanEffGstar*DLEPRO |
|
| 3922 | ||
| 3923 |
# dgphi2 <- logGstar*DLEPRO *(Effmodel@theta2-(meanEffGstar)+model@phi2) |
|
| 3924 | ||
| 3925 |
# dgtheta1 <- -logGstar*DLEPRO*model@phi2 |
|
| 3926 | ||
| 3927 |
# dgtheta2 <- 1+DLEPRO-logGstar*DLEPRO*model@phi2*log(logGstar) |
|
| 3928 | ||
| 3929 | ! |
deltaG <- matrix(c(dgphi1, dgphi2, dgtheta1, dgtheta2), 4, 1) |
| 3930 | ||
| 3931 |
## Find the variance of the log Gstar |
|
| 3932 |
## First find the covariance matrix of all the parameters, phi1, phi2, theta1 and theta2 |
|
| 3933 |
## such that phi1 and phi2 and independent of theta1 and theta2 |
|
| 3934 | ! |
emptyMatrix <- matrix(0, 2, 2) |
| 3935 | ! |
covBETA <- cbind( |
| 3936 | ! |
rbind(model@Pcov, emptyMatrix), |
| 3937 | ! |
rbind(emptyMatrix, Effmodel@Pcov) |
| 3938 |
) |
|
| 3939 | ! |
varlogGstar <- as.vector(t(deltaG) %*% covBETA %*% deltaG) |
| 3940 | ||
| 3941 |
## Find the upper and lower limit of the 95% credibility interval of Gstar |
|
| 3942 | ! |
CIGstar <- exp(logGstar + c(-1, 1) * 1.96 * sqrt(varlogGstar)) |
| 3943 | ||
| 3944 |
## The ratio of the upper to the lower 95% credibility interval |
|
| 3945 | ! |
ratioGstar <- CIGstar[2] / CIGstar[1] |
| 3946 | ||
| 3947 |
## Find the variance of the log of the TDtargetEndOfTrial(eta) |
|
| 3948 | ! |
M1 <- matrix( |
| 3949 | ! |
c( |
| 3950 | ! |
-1 / (model@phi2), |
| 3951 | ! |
-(log(prob_target / (1 - prob_target)) - model@phi1) / (model@phi2)^2 |
| 3952 |
), |
|
| 3953 | ! |
1, |
| 3954 | ! |
2 |
| 3955 |
) |
|
| 3956 | ! |
M2 <- model@Pcov |
| 3957 | ||
| 3958 | ! |
varEta <- as.vector(M1 %*% M2 %*% t(M1)) |
| 3959 | ||
| 3960 |
## Find the upper and lower limit of the 95% credibility interval of |
|
| 3961 |
## TDtargetEndOfTrial |
|
| 3962 | ! |
CITDEOT <- exp(log(TDtargetEndOfTrial) + c(-1, 1) * 1.96 * sqrt(varEta)) |
| 3963 | ||
| 3964 |
## The ratio of the upper to the lower 95% credibility interval |
|
| 3965 | ! |
ratioTDEOT <- CITDEOT[2] / CITDEOT[1] |
| 3966 | ||
| 3967 | ! |
if (Gstar <= TDtargetEndOfTrial) {
|
| 3968 | ! |
chooseTD <- FALSE |
| 3969 | ! |
CI <- c() |
| 3970 | ! |
CI[2] <- CIGstar[2] |
| 3971 | ! |
CI[1] <- CIGstar[1] |
| 3972 | ! |
ratio <- ratioGstar |
| 3973 |
} else {
|
|
| 3974 | ! |
chooseTD <- TRUE |
| 3975 | ! |
CI <- c() |
| 3976 | ! |
CI[2] <- CITDEOT[2] |
| 3977 | ! |
CI[1] <- CITDEOT[1] |
| 3978 | ! |
ratio <- ratioTDEOT |
| 3979 |
} |
|
| 3980 |
## so can we stop? |
|
| 3981 | ! |
doStop <- ratio <= stopping@target_ratio |
| 3982 |
## generate message |
|
| 3983 | ||
| 3984 | ! |
text1 <- paste( |
| 3985 | ! |
"Gstar estimate is", |
| 3986 | ! |
round(Gstar, 4), |
| 3987 | ! |
"with 95% CI (",
|
| 3988 | ! |
round(CIGstar[1], 4), |
| 3989 |
",", |
|
| 3990 | ! |
round(CIGstar[2], 4), |
| 3991 | ! |
") and its ratio =", |
| 3992 | ! |
round(ratioGstar, 4) |
| 3993 |
) |
|
| 3994 | ! |
text2 <- paste( |
| 3995 | ! |
"TDtargetEndOfTrial estimate is ", |
| 3996 | ! |
round(TDtargetEndOfTrial, 4), |
| 3997 | ! |
"with 95% CI (",
|
| 3998 | ! |
round(CITDEOT[1], 4), |
| 3999 |
",", |
|
| 4000 | ! |
round(CITDEOT[2], 4), |
| 4001 | ! |
") and its ratio=", |
| 4002 | ! |
round(ratioTDEOT, 4) |
| 4003 |
) |
|
| 4004 | ! |
text3 <- paste( |
| 4005 | ! |
ifelse(chooseTD, "TDatrgetEndOfTrial estimate", "Gstar estimate"), |
| 4006 | ! |
"is smaller with ratio =", |
| 4007 | ! |
round(ratio, 4), |
| 4008 | ! |
"which is ", |
| 4009 | ! |
ifelse(doStop, "is less than or equal to", "greater than"), |
| 4010 | ! |
"target_ratio =", |
| 4011 | ! |
stopping@target_ratio |
| 4012 |
) |
|
| 4013 | ! |
text <- c(text1, text2, text3) |
| 4014 |
## return both |
|
| 4015 | ! |
return(structure( |
| 4016 | ! |
doStop, |
| 4017 | ! |
message = text, |
| 4018 | ! |
report_label = stopping@report_label |
| 4019 |
)) |
|
| 4020 |
} |
|
| 4021 |
) |
|
| 4022 | ||
| 4023 | ||
| 4024 |
## ============================================================ |
|
| 4025 | ||
| 4026 |
## ----------------------------------------------------- |
|
| 4027 |
## Determine the safety window length of the next cohort |
|
| 4028 |
## ----------------------------------------------------- |
|
| 4029 | ||
| 4030 |
##' Determine the safety window length of the next cohort |
|
| 4031 |
##' |
|
| 4032 |
##' This function determines the safety window length of |
|
| 4033 |
##' the next cohort. |
|
| 4034 |
##' |
|
| 4035 |
##' @param safetyWindow The rule, an object of class |
|
| 4036 |
##' \code{\linkS4class{SafetyWindow}}
|
|
| 4037 |
##' @param size The next cohort size |
|
| 4038 |
##' @param data The data input, an object of class \code{\linkS4class{DataDA}}
|
|
| 4039 |
##' @param \dots additional arguments |
|
| 4040 |
##' |
|
| 4041 |
##' @return the `windowLength` as a list of safety window parameters |
|
| 4042 |
##' (`gap`, `follow`, `follow_min`) |
|
| 4043 |
##' |
|
| 4044 |
##' @export |
|
| 4045 |
##' @keywords methods |
|
| 4046 |
setGeneric( |
|
| 4047 |
"windowLength", |
|
| 4048 |
def = function(safetyWindow, size, ...) {
|
|
| 4049 |
## there should be no default method, |
|
| 4050 |
## therefore just forward to next method! |
|
| 4051 | 85x |
standardGeneric("windowLength")
|
| 4052 |
}, |
|
| 4053 |
valueClass = "list" |
|
| 4054 |
) |
|
| 4055 | ||
| 4056 | ||
| 4057 |
## ============================================================ |
|
| 4058 | ||
| 4059 |
## -------------------------------------------------- |
|
| 4060 |
## The SafetyWindowSize method |
|
| 4061 |
## -------------------------------------------------- |
|
| 4062 | ||
| 4063 |
##' @describeIn windowLength Determine safety window length based |
|
| 4064 |
##' on the cohort size |
|
| 4065 |
##' |
|
| 4066 |
##' @example examples/Rules-method-windowLength-SafetyWindowSize.R |
|
| 4067 |
setMethod( |
|
| 4068 |
"windowLength", |
|
| 4069 |
signature = signature( |
|
| 4070 |
safetyWindow = "SafetyWindowSize", |
|
| 4071 |
size = "ANY" |
|
| 4072 |
), |
|
| 4073 |
def = function(safetyWindow, size, data, ...) {
|
|
| 4074 |
## determine in which interval the next size is |
|
| 4075 | 30x |
interval <- |
| 4076 | 30x |
findInterval( |
| 4077 | 30x |
x = size, |
| 4078 | 30x |
vec = safetyWindow@size |
| 4079 |
) |
|
| 4080 | ||
| 4081 |
## so the safety window length is |
|
| 4082 | 30x |
patientGap <- head( |
| 4083 | 30x |
c( |
| 4084 | 30x |
0, |
| 4085 | 30x |
safetyWindow@gap[[interval]], |
| 4086 | 30x |
rep(tail(safetyWindow@gap[[interval]], 1), 100) |
| 4087 |
), |
|
| 4088 | 30x |
size |
| 4089 |
) |
|
| 4090 | 30x |
patientFollow <- safetyWindow@follow |
| 4091 | 30x |
patientFollowMin <- safetyWindow@follow_min |
| 4092 | ||
| 4093 | 30x |
ret <- list( |
| 4094 | 30x |
patientGap = patientGap, |
| 4095 | 30x |
patientFollow = patientFollow, |
| 4096 | 30x |
patientFollowMin = patientFollowMin |
| 4097 |
) |
|
| 4098 | ||
| 4099 | 30x |
return(ret) |
| 4100 |
} |
|
| 4101 |
) |
|
| 4102 | ||
| 4103 |
## ============================================================ |
|
| 4104 | ||
| 4105 |
## -------------------------------------------------- |
|
| 4106 |
## Constant safety window length |
|
| 4107 |
## -------------------------------------------------- |
|
| 4108 | ||
| 4109 |
##' @describeIn windowLength Constant safety window length |
|
| 4110 |
##' @example examples/Rules-method-windowLength-SafetyWindowConst.R |
|
| 4111 |
setMethod( |
|
| 4112 |
"windowLength", |
|
| 4113 |
signature = signature( |
|
| 4114 |
safetyWindow = "SafetyWindowConst", |
|
| 4115 |
size = "ANY" |
|
| 4116 |
), |
|
| 4117 |
def = function(safetyWindow, size, ...) {
|
|
| 4118 |
## first element should be 0. |
|
| 4119 | 55x |
patientGap <- head( |
| 4120 | 55x |
c( |
| 4121 | 55x |
0, |
| 4122 | 55x |
safetyWindow@gap, |
| 4123 | 55x |
rep(tail(safetyWindow@gap, 1), 100) |
| 4124 |
), |
|
| 4125 | 55x |
size |
| 4126 |
) |
|
| 4127 | 55x |
patientFollow <- safetyWindow@follow |
| 4128 | 55x |
patientFollowMin <- safetyWindow@follow_min |
| 4129 | ||
| 4130 | 55x |
ret <- list( |
| 4131 | 55x |
patientGap = patientGap, |
| 4132 | 55x |
patientFollow = patientFollow, |
| 4133 | 55x |
patientFollowMin = patientFollowMin |
| 4134 |
) |
|
| 4135 | ||
| 4136 | 55x |
return(ret) |
| 4137 |
} |
|
| 4138 |
) |
|
| 4139 | ||
| 4140 |
# nolint end |
|
| 4141 | ||
| 4142 |
# tidy ---- |
|
| 4143 | ||
| 4144 |
## tidy-IncrementsRelative ---- |
|
| 4145 | ||
| 4146 |
#' @rdname tidy |
|
| 4147 |
#' @aliases tidy-IncrementsRelative |
|
| 4148 |
#' @example examples/Rules-method-tidyIncrementsRelative.R |
|
| 4149 |
#' @export |
|
| 4150 |
setMethod( |
|
| 4151 |
f = "tidy", |
|
| 4152 |
signature = signature(x = "IncrementsRelative"), |
|
| 4153 |
definition = function(x, ...) {
|
|
| 4154 |
h_tidy_all_slots(x) %>% |
|
| 4155 |
dplyr::bind_cols() %>% |
|
| 4156 |
h_range_to_minmax(.data$intervals) %>% |
|
| 4157 |
dplyr::filter(max > 0) %>% |
|
| 4158 |
tibble::add_column(increment = x@increments) %>% |
|
| 4159 |
h_tidy_class(x) |
|
| 4160 |
} |
|
| 4161 |
) |
|
| 4162 | ||
| 4163 |
## tidy-CohortSizeDLT ---- |
|
| 4164 | ||
| 4165 |
#' @rdname tidy |
|
| 4166 |
#' @aliases tidy-CohortSizeDLT |
|
| 4167 |
#' @example examples/Rules-method-tidyCohortSizeDLT.R |
|
| 4168 |
#' @export |
|
| 4169 |
setMethod( |
|
| 4170 |
f = "tidy", |
|
| 4171 |
signature = signature(x = "CohortSizeDLT"), |
|
| 4172 |
definition = function(x, ...) {
|
|
| 4173 | 52x |
h_tidy_all_slots(x) %>% |
| 4174 | 52x |
dplyr::bind_cols() %>% |
| 4175 | 52x |
h_range_to_minmax(.data$intervals) %>% |
| 4176 | 52x |
dplyr::filter(max > 0) %>% |
| 4177 | 52x |
tibble::add_column(cohort_size = x@cohort_size) %>% |
| 4178 | 52x |
h_tidy_class(x) |
| 4179 |
} |
|
| 4180 |
) |
|
| 4181 | ||
| 4182 |
## tidy-CohortSizeMin ---- |
|
| 4183 | ||
| 4184 |
#' @rdname tidy |
|
| 4185 |
#' @aliases tidy-CohortSizeMin |
|
| 4186 |
#' @example examples/Rules-method-tidyCohortSizeMin.R |
|
| 4187 |
#' @export |
|
| 4188 |
setMethod( |
|
| 4189 |
f = "tidy", |
|
| 4190 |
signature = signature(x = "CohortSizeMin"), |
|
| 4191 |
definition = function(x, ...) {
|
|
| 4192 | 3x |
callNextMethod() %>% h_tidy_class(x) |
| 4193 |
} |
|
| 4194 |
) |
|
| 4195 | ||
| 4196 |
## tidy-CohortSizeMax ---- |
|
| 4197 | ||
| 4198 |
#' @rdname tidy |
|
| 4199 |
#' @aliases tidy-CohortSizeMax |
|
| 4200 |
#' @example examples/Rules-method-tidyCohortSizeMax.R |
|
| 4201 |
#' @export |
|
| 4202 |
setMethod( |
|
| 4203 |
f = "tidy", |
|
| 4204 |
signature = signature(x = "CohortSizeMax"), |
|
| 4205 |
definition = function(x, ...) {
|
|
| 4206 | 12x |
callNextMethod() %>% h_tidy_class(x) |
| 4207 |
} |
|
| 4208 |
) |
|
| 4209 | ||
| 4210 |
## tidy-CohortSizeRange ---- |
|
| 4211 | ||
| 4212 |
#' @rdname tidy |
|
| 4213 |
#' @aliases tidy-CohortSizeRange |
|
| 4214 |
#' @example examples/Rules-method-tidyCohortSizeRange.R |
|
| 4215 |
#' @export |
|
| 4216 |
setMethod( |
|
| 4217 |
f = "tidy", |
|
| 4218 |
signature = signature(x = "CohortSizeRange"), |
|
| 4219 |
definition = function(x, ...) {
|
|
| 4220 | 58x |
h_tidy_all_slots(x) %>% |
| 4221 | 58x |
dplyr::bind_cols() %>% |
| 4222 | 58x |
h_range_to_minmax(.data$intervals) %>% |
| 4223 | 58x |
dplyr::filter(max > 0) %>% |
| 4224 | 58x |
tibble::add_column(cohort_size = x@cohort_size) %>% |
| 4225 | 58x |
h_tidy_class(x) |
| 4226 |
} |
|
| 4227 |
) |
|
| 4228 | ||
| 4229 |
## tidy-CohortSizeParts ---- |
|
| 4230 | ||
| 4231 |
#' @rdname tidy |
|
| 4232 |
#' @aliases tidy-CohortSizeParts |
|
| 4233 |
#' @example examples/Rules-method-tidyCohortSizeParts.R |
|
| 4234 |
#' @export |
|
| 4235 |
setMethod( |
|
| 4236 |
f = "tidy", |
|
| 4237 |
signature = signature(x = "CohortSizeParts"), |
|
| 4238 |
definition = function(x, ...) {
|
|
| 4239 | 3x |
tibble::tibble( |
| 4240 | 3x |
part = seq_along(x@cohort_sizes), |
| 4241 | 3x |
cohort_size = x@cohort_sizes |
| 4242 |
) %>% |
|
| 4243 | 3x |
h_tidy_class(x) |
| 4244 |
} |
|
| 4245 |
) |
|
| 4246 | ||
| 4247 |
## tidy-IncrementsMin ---- |
|
| 4248 | ||
| 4249 |
#' @rdname tidy |
|
| 4250 |
#' @aliases tidy-IncrementsMin |
|
| 4251 |
#' @example examples/Rules-method-tidyIncrementsMin.R |
|
| 4252 |
#' @export |
|
| 4253 |
setMethod( |
|
| 4254 |
f = "tidy", |
|
| 4255 |
signature = signature(x = "IncrementsMin"), |
|
| 4256 |
definition = function(x, ...) {
|
|
| 4257 | 3x |
callNextMethod() %>% h_tidy_class(x) |
| 4258 |
} |
|
| 4259 |
) |
|
| 4260 | ||
| 4261 |
## tidy-IncrementsRelative ---- |
|
| 4262 | ||
| 4263 |
#' @rdname tidy |
|
| 4264 |
#' @aliases tidy-IncrementsRelative |
|
| 4265 |
#' @example examples/Rules-method-tidyIncrementsRelative.R |
|
| 4266 |
#' @export |
|
| 4267 |
setMethod( |
|
| 4268 |
f = "tidy", |
|
| 4269 |
signature = signature(x = "IncrementsRelative"), |
|
| 4270 |
definition = function(x, ...) {
|
|
| 4271 | 94x |
h_tidy_all_slots(x) %>% |
| 4272 | 94x |
h_range_to_minmax(.data$intervals) %>% |
| 4273 | 94x |
dplyr::filter(dplyr::row_number() > 1) %>% |
| 4274 | 94x |
tibble::add_column(increment = x@increments) %>% |
| 4275 | 94x |
h_tidy_class(x) |
| 4276 |
} |
|
| 4277 |
) |
|
| 4278 | ||
| 4279 |
## tidy-IncrementsRelativeDLT ---- |
|
| 4280 | ||
| 4281 |
#' @rdname tidy |
|
| 4282 |
#' @aliases tidy-IncrementsRelativeDLT |
|
| 4283 |
#' @example examples/Rules-method-tidyIncrementsRelativeDLT.R |
|
| 4284 |
#' @export |
|
| 4285 |
setMethod( |
|
| 4286 |
f = "tidy", |
|
| 4287 |
signature = signature(x = "IncrementsRelativeDLT"), |
|
| 4288 |
definition = function(x, ...) {
|
|
| 4289 | 20x |
h_tidy_all_slots(x) %>% |
| 4290 | 20x |
h_range_to_minmax(.data$intervals) %>% |
| 4291 | 20x |
dplyr::filter(dplyr::row_number() > 1) %>% |
| 4292 | 20x |
tibble::add_column(increment = x@increments) %>% |
| 4293 | 20x |
h_tidy_class(x) |
| 4294 |
} |
|
| 4295 |
) |
|
| 4296 | ||
| 4297 |
## tidy-IncrementsRelative ---- |
|
| 4298 | ||
| 4299 |
#' @rdname tidy |
|
| 4300 |
#' @aliases tidy-IncrementsRelativeParts |
|
| 4301 |
#' @example examples/Rules-method-tidyIncrementsRelativeParts.R |
|
| 4302 |
#' @export |
|
| 4303 |
setMethod( |
|
| 4304 |
f = "tidy", |
|
| 4305 |
signature = signature(x = "IncrementsRelativeParts"), |
|
| 4306 |
definition = function(x, ...) {
|
|
| 4307 | 3x |
slot_names <- slotNames(x) |
| 4308 | 3x |
rv <- list() |
| 4309 | 3x |
for (nm in slot_names) {
|
| 4310 | 12x |
if (!is.function(slot(x, nm))) {
|
| 4311 | 12x |
rv[[nm]] <- h_tidy_slot(x, nm, ...) |
| 4312 |
} |
|
| 4313 |
} |
|
| 4314 |
# Column bind of all list elements have the same number of rows. |
|
| 4315 | 3x |
if (length(rv) > 1 & length(unique(sapply(rv, nrow))) == 1) {
|
| 4316 | ! |
rv <- rv %>% dplyr::bind_cols() |
| 4317 |
} |
|
| 4318 | 3x |
rv <- rv %>% h_tidy_class(x) |
| 4319 | 3x |
if (length(rv) == 1) {
|
| 4320 | ! |
rv[[names(rv)[1]]] %>% h_tidy_class(x) |
| 4321 |
} else {
|
|
| 4322 | 3x |
rv |
| 4323 |
} |
|
| 4324 |
} |
|
| 4325 |
) |
|
| 4326 | ||
| 4327 |
## tidy-NextBestNCRM ---- |
|
| 4328 | ||
| 4329 |
#' @rdname tidy |
|
| 4330 |
#' @aliases tidy-NextBestNCRM |
|
| 4331 |
#' @example examples/Rules-method-tidyNextBestNCRM.R |
|
| 4332 |
#' @export |
|
| 4333 |
setMethod( |
|
| 4334 |
f = "tidy", |
|
| 4335 |
signature = signature(x = "NextBestNCRM"), |
|
| 4336 |
definition = function(x, ...) {
|
|
| 4337 | 13x |
h_tidy_all_slots(x) %>% |
| 4338 | 13x |
dplyr::bind_cols() %>% |
| 4339 | 13x |
h_range_to_minmax(.data$target, range_min = 0, range_max = 1) %>% |
| 4340 | 13x |
add_column(max_prob = c(NA, NA, x@max_overdose_prob)) %>% |
| 4341 | 13x |
add_column(Range = c("Underdose", "Target", "Overdose"), .before = 1) %>%
|
| 4342 | 13x |
h_tidy_class(x) |
| 4343 |
} |
|
| 4344 |
) |
|
| 4345 | ||
| 4346 |
## tidy-NextBestNCRMLoss ---- |
|
| 4347 | ||
| 4348 |
#' @rdname tidy |
|
| 4349 |
#' @aliases tidy-NextBestNCRMLoss |
|
| 4350 |
#' @example examples/Rules-method-tidyNextBestNCRMLoss.R |
|
| 4351 |
#' @export |
|
| 4352 |
setMethod( |
|
| 4353 |
f = "tidy", |
|
| 4354 |
signature = signature(x = "NextBestNCRMLoss"), |
|
| 4355 |
definition = function(x, ...) {
|
|
| 4356 | 9x |
tibble( |
| 4357 | 9x |
Range = "Underdose", |
| 4358 | 9x |
Lower = 0, |
| 4359 | 9x |
Upper = x@target[1] |
| 4360 |
) %>% |
|
| 4361 | 9x |
dplyr::bind_rows( |
| 4362 | 9x |
lapply( |
| 4363 | 9x |
c("target", "overdose", "unacceptable"),
|
| 4364 | 9x |
function(nm, obj) {
|
| 4365 | 27x |
tibble::tibble( |
| 4366 | 27x |
Range = stringr::str_to_sentence(nm), |
| 4367 | 27x |
Lower = slot(obj, nm)[1], |
| 4368 | 27x |
Upper = slot(obj, nm)[2] |
| 4369 |
) |
|
| 4370 |
}, |
|
| 4371 | 9x |
obj = x |
| 4372 |
) %>% |
|
| 4373 | 9x |
dplyr::bind_rows() |
| 4374 |
) %>% |
|
| 4375 | 9x |
add_column(LossCoefficient = x@losses) %>% |
| 4376 | 9x |
add_column(MaxOverdoseProb = x@max_overdose_prob) %>% |
| 4377 | 9x |
h_tidy_class(x) |
| 4378 |
} |
|
| 4379 |
) |
| 1 |
# nolint start |
|
| 2 |
##################################################################################### |
|
| 3 |
## Author: Daniel Sabanes Bove [sabanesd *a*t* roche *.* com], |
|
| 4 |
## Wai Yin Yeung [ w *.* yeung1 *a*t* lancaster *.* ac *.* uk] |
|
| 5 |
## Project: Object-oriented implementation of CRM designs |
|
| 6 |
## |
|
| 7 |
## Time-stamp: <[Simulations-methods.R] by DSB Fre 16/01/2015 13:41> |
|
| 8 |
## |
|
| 9 |
## Description: |
|
| 10 |
## Methods for handling the simulations output. |
|
| 11 |
## |
|
| 12 |
## History: |
|
| 13 |
## 19/02/2014 file creation |
|
| 14 |
## 30/07/2014 added in methods for pseudo models simulations |
|
| 15 |
################################################################################### |
|
| 16 | ||
| 17 |
##' @include Simulations-class.R |
|
| 18 |
##' @include helpers.R |
|
| 19 |
{}
|
|
| 20 | ||
| 21 | ||
| 22 |
##' Plot simulations |
|
| 23 |
##' |
|
| 24 |
##' Summarize the simulations with plots |
|
| 25 |
##' |
|
| 26 |
##' This plot method can be applied to \code{\linkS4class{GeneralSimulations}}
|
|
| 27 |
##' objects in order to summarize them graphically. Possible \code{type}s of
|
|
| 28 |
##' plots at the moment are: \describe{ \item{trajectory}{Summary of the
|
|
| 29 |
##' trajectory of the simulated trials} \item{dosesTried}{Average proportions of
|
|
| 30 |
##' the doses tested in patients} } You can specify one or both of these in the |
|
| 31 |
##' \code{type} argument.
|
|
| 32 |
##' |
|
| 33 |
##' @param x the \code{\linkS4class{GeneralSimulations}} object we want
|
|
| 34 |
##' to plot from |
|
| 35 |
##' @param y missing |
|
| 36 |
##' @param type the type of plots you want to obtain. |
|
| 37 |
##' @param \dots not used |
|
| 38 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 39 |
##' asked for, otherwise a `gtable` object. |
|
| 40 |
##' |
|
| 41 |
##' @importFrom ggplot2 ggplot geom_step geom_bar aes xlab ylab |
|
| 42 |
##' scale_linetype_manual |
|
| 43 |
##' @importFrom gridExtra arrangeGrob |
|
| 44 |
##' |
|
| 45 |
##' @example examples/Simulations-method-plotSIMsingle.R |
|
| 46 |
##' @export |
|
| 47 |
##' @keywords methods |
|
| 48 |
setMethod( |
|
| 49 |
"plot", |
|
| 50 |
signature = signature( |
|
| 51 |
x = "GeneralSimulations", |
|
| 52 |
y = "missing" |
|
| 53 |
), |
|
| 54 |
def = function( |
|
| 55 |
x, |
|
| 56 |
y, |
|
| 57 |
type = c( |
|
| 58 |
"trajectory", |
|
| 59 |
"dosesTried" |
|
| 60 |
), |
|
| 61 |
... |
|
| 62 |
) {
|
|
| 63 |
## which plots should be produced? |
|
| 64 | 16x |
type <- match.arg(type, several.ok = TRUE) |
| 65 | 14x |
stopifnot(length(type) > 0L) |
| 66 | ||
| 67 |
## start the plot list |
|
| 68 | 14x |
plotList <- list() |
| 69 | 14x |
plotIndex <- 0L |
| 70 | ||
| 71 |
## summary of the trajectories |
|
| 72 | 14x |
if ("trajectory" %in% type) {
|
| 73 |
## get a matrix of the simulated dose trajectories, |
|
| 74 |
## where the rows correspond to the simulations and |
|
| 75 |
## the columns to the patient index: |
|
| 76 | ||
| 77 |
## If design with placebo, then exclude placebo Patients |
|
| 78 | 11x |
if (x@data[[1]]@placebo) {
|
| 79 | ! |
PL <- x@data[[1]]@doseGrid[1] |
| 80 | ! |
simDoses <- lapply( |
| 81 | ! |
x@data, |
| 82 | ! |
function(y) {
|
| 83 | ! |
y@x[y@x != PL] |
| 84 |
} |
|
| 85 |
) |
|
| 86 |
} else {
|
|
| 87 | 11x |
simDoses <- lapply( |
| 88 | 11x |
x@data, |
| 89 | 11x |
slot, |
| 90 | 11x |
"x" |
| 91 |
) |
|
| 92 |
} |
|
| 93 | ||
| 94 | 11x |
maxPatients <- max(sapply(simDoses, length)) |
| 95 | ||
| 96 | 11x |
simDosesMat <- matrix( |
| 97 | 11x |
data = NA, |
| 98 | 11x |
nrow = length(simDoses), |
| 99 | 11x |
ncol = maxPatients |
| 100 |
) |
|
| 101 | ||
| 102 | 11x |
for (i in seq_along(simDoses)) {
|
| 103 | 11x |
simDosesMat[i, seq_along(simDoses[[i]])] <- |
| 104 | 11x |
simDoses[[i]] |
| 105 |
} |
|
| 106 | ||
| 107 |
## extract statistics |
|
| 108 | 11x |
stats <- c( |
| 109 | 11x |
"Minimum", |
| 110 | 11x |
"Lower Quartile", |
| 111 | 11x |
"Median", |
| 112 | 11x |
"Upper Quartile", |
| 113 | 11x |
"Maximum" |
| 114 |
) |
|
| 115 | 11x |
traj.df <- |
| 116 | 11x |
data.frame( |
| 117 | 11x |
patient = rep(seq_len(maxPatients), each = 5L), |
| 118 | 11x |
Statistic = factor( |
| 119 | 11x |
rep( |
| 120 | 11x |
stats, |
| 121 | 11x |
maxPatients |
| 122 |
), |
|
| 123 | 11x |
levels = stats |
| 124 |
), |
|
| 125 | 11x |
traj = c(apply(simDosesMat, 2L, quantile, na.rm = TRUE)) |
| 126 |
) |
|
| 127 | ||
| 128 |
## linetypes for the plot |
|
| 129 | 11x |
lt <- c( |
| 130 | 11x |
"Median" = 1, |
| 131 | 11x |
"Lower Quartile" = 2, |
| 132 | 11x |
"Upper Quartile" = 2, |
| 133 | 11x |
"Minimum" = 4, |
| 134 | 11x |
"Maximum" = 4 |
| 135 |
) |
|
| 136 | ||
| 137 |
## save the plot |
|
| 138 | 11x |
if (x@data[[1]]@placebo) {
|
| 139 | ! |
myTitle <- "Patient (placebo were excluded)" |
| 140 |
} else {
|
|
| 141 | 11x |
myTitle <- "Patient" |
| 142 |
} |
|
| 143 | 11x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 144 | 11x |
ggplot() + |
| 145 | 11x |
geom_step( |
| 146 | 11x |
aes( |
| 147 | 11x |
x = patient, |
| 148 | 11x |
y = traj, |
| 149 | 11x |
group = Statistic, |
| 150 | 11x |
linetype = Statistic |
| 151 |
), |
|
| 152 | 11x |
linewidth = 1.2, |
| 153 | 11x |
colour = "blue", |
| 154 | 11x |
data = traj.df |
| 155 |
) + |
|
| 156 |
## scale_linetype_manual(values=lt) + |
|
| 157 | 11x |
xlab(myTitle) + |
| 158 | 11x |
ylab("Dose Level")
|
| 159 |
} |
|
| 160 | ||
| 161 |
## average distribution of the doses tried |
|
| 162 | 14x |
if ("dosesTried" %in% type) {
|
| 163 |
## get the doses tried |
|
| 164 | 8x |
simDoses <- lapply( |
| 165 | 8x |
x@data, |
| 166 | 8x |
slot, |
| 167 | 8x |
"x" |
| 168 |
) |
|
| 169 | ||
| 170 |
## get the dose distributions by trial |
|
| 171 | 8x |
doseDistributions <- |
| 172 | 8x |
sapply( |
| 173 | 8x |
simDoses, |
| 174 | 8x |
function(s) {
|
| 175 | 8x |
if (length(s) > 0) {
|
| 176 | 8x |
prop.table(table(factor(s, levels = x@data[[1]]@doseGrid))) |
| 177 |
} else {
|
|
| 178 | ! |
rep(0, length(x@data[[1]]@doseGrid)) |
| 179 |
} |
|
| 180 |
} |
|
| 181 |
) |
|
| 182 | ||
| 183 |
## derive the average dose distribution across trial |
|
| 184 |
## simulations |
|
| 185 | 8x |
averageDoseDist <- rowMeans(doseDistributions) |
| 186 | ||
| 187 |
## get in data frame shape |
|
| 188 | 8x |
dat <- data.frame( |
| 189 | 8x |
dose = as.numeric(names(averageDoseDist)), |
| 190 | 8x |
perc = averageDoseDist * 100 |
| 191 |
) |
|
| 192 | ||
| 193 |
## produce and save the plot |
|
| 194 | 8x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 195 | 8x |
ggplot() + |
| 196 | 8x |
geom_bar( |
| 197 | 8x |
data = as.data.frame(dat), |
| 198 | 8x |
aes(x = dose, y = perc), |
| 199 | 8x |
stat = "identity", |
| 200 | 8x |
position = "identity", |
| 201 | 8x |
width = min(diff(x@data[[1]]@doseGrid)) / 2 |
| 202 |
) + |
|
| 203 | 8x |
xlab("Dose level") +
|
| 204 | 8x |
ylab("Average proportion [%]")
|
| 205 |
} |
|
| 206 |
## then finally plot everything |
|
| 207 | ||
| 208 |
## if there is only one plot |
|
| 209 |
if ( |
|
| 210 | 14x |
identical( |
| 211 | 14x |
length(plotList), |
| 212 | 14x |
1L |
| 213 |
) |
|
| 214 |
) {
|
|
| 215 |
## just return it |
|
| 216 | 9x |
return(plotList[[1L]]) |
| 217 |
} else {
|
|
| 218 |
## otherwise arrange them |
|
| 219 | 5x |
ret <- do.call( |
| 220 | 5x |
gridExtra::arrangeGrob, |
| 221 | 5x |
plotList |
| 222 |
) |
|
| 223 | 5x |
return(ret) |
| 224 |
} |
|
| 225 |
} |
|
| 226 |
) |
|
| 227 | ||
| 228 | ||
| 229 |
##' Plot dual-endpoint simulations |
|
| 230 |
##' |
|
| 231 |
##' This plot method can be applied to \code{\linkS4class{DualSimulations}}
|
|
| 232 |
##' objects in order to summarize them graphically. In addition to the standard |
|
| 233 |
##' plot types, there is |
|
| 234 |
##' \describe{
|
|
| 235 |
##' \item{sigma2W}{Plot a boxplot of the final biomarker variance estimates in
|
|
| 236 |
##' the simulated trials} |
|
| 237 |
##' \item{rho}{Plot a boxplot of the final correlation estimates in
|
|
| 238 |
##' the simulated trials} |
|
| 239 |
##' } |
|
| 240 |
##' |
|
| 241 |
##' @param x the \code{\linkS4class{DualSimulations}} object we want
|
|
| 242 |
##' to plot from |
|
| 243 |
##' @param y missing |
|
| 244 |
##' @param type the type of plots you want to obtain. |
|
| 245 |
##' @param \dots not used |
|
| 246 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 247 |
##' asked for, otherwise a `gtable` object. |
|
| 248 |
##' |
|
| 249 |
##' @importFrom ggplot2 ggplot geom_boxplot coord_flip scale_x_discrete |
|
| 250 |
##' @importFrom gridExtra arrangeGrob |
|
| 251 |
##' |
|
| 252 |
##' @example examples/Simulations-method-plot-DualSimulations.R |
|
| 253 |
##' @export |
|
| 254 |
##' @keywords methods |
|
| 255 |
setMethod( |
|
| 256 |
"plot", |
|
| 257 |
signature = signature( |
|
| 258 |
x = "DualSimulations", |
|
| 259 |
y = "missing" |
|
| 260 |
), |
|
| 261 |
def = function( |
|
| 262 |
x, |
|
| 263 |
y, |
|
| 264 |
type = c( |
|
| 265 |
"trajectory", |
|
| 266 |
"dosesTried", |
|
| 267 |
"sigma2W", |
|
| 268 |
"rho" |
|
| 269 |
), |
|
| 270 |
... |
|
| 271 |
) {
|
|
| 272 |
## start the plot list |
|
| 273 | 3x |
plotList <- list() |
| 274 | 3x |
plotIndex <- 0L |
| 275 | ||
| 276 |
## which plots should be produced? |
|
| 277 | 3x |
type <- match.arg(type, several.ok = TRUE) |
| 278 | 3x |
stopifnot(length(type) > 0L) |
| 279 | ||
| 280 |
## substract the specific plot types for |
|
| 281 |
## dual-endpoint simulation results |
|
| 282 | 3x |
typeReduced <- setdiff( |
| 283 | 3x |
type, |
| 284 | 3x |
c("sigma2W", "rho")
|
| 285 |
) |
|
| 286 | ||
| 287 |
## are there more plots from general? |
|
| 288 | 3x |
moreFromGeneral <- (length(typeReduced) > 0) |
| 289 | ||
| 290 |
## if so, then produce these plots |
|
| 291 | 3x |
if (moreFromGeneral) {
|
| 292 | 1x |
genPlot <- callNextMethod(x = x, y = y, type = typeReduced) |
| 293 |
} |
|
| 294 | ||
| 295 |
## now to the specific dual-endpoint plots: |
|
| 296 | ||
| 297 |
## biomarker variance estimates boxplot |
|
| 298 | 3x |
if ("sigma2W" %in% type) {
|
| 299 |
## save the plot |
|
| 300 | 2x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 301 | 2x |
ggplot( |
| 302 | 2x |
data = data.frame(y = x@sigma2w_est), |
| 303 | 2x |
aes(x = factor(0), y = y) |
| 304 |
) + |
|
| 305 | 2x |
geom_boxplot() + |
| 306 | 2x |
coord_flip() + |
| 307 | 2x |
scale_x_discrete(breaks = NULL) + |
| 308 | 2x |
xlab("") +
|
| 309 | 2x |
ylab("Biomarker variance estimates")
|
| 310 |
} |
|
| 311 | ||
| 312 |
## correlation estimates boxplot |
|
| 313 | 3x |
if ("rho" %in% type) {
|
| 314 |
## save the plot |
|
| 315 | 1x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 316 | 1x |
ggplot(data = data.frame(y = x@rho_est), aes(x = factor(0), y = y)) + |
| 317 | 1x |
geom_boxplot() + |
| 318 | 1x |
coord_flip() + |
| 319 | 1x |
scale_x_discrete(breaks = NULL) + |
| 320 | 1x |
xlab("") +
|
| 321 | 1x |
ylab("Correlation estimates")
|
| 322 |
} |
|
| 323 | ||
| 324 |
## then finally plot everything |
|
| 325 |
if ( |
|
| 326 | 3x |
identical( |
| 327 | 3x |
length(plotList), |
| 328 | 3x |
0L |
| 329 |
) |
|
| 330 |
) {
|
|
| 331 | ! |
return(genPlot) |
| 332 |
} else if ( |
|
| 333 | 3x |
identical( |
| 334 | 3x |
length(plotList), |
| 335 | 3x |
1L |
| 336 |
) |
|
| 337 |
) {
|
|
| 338 | 3x |
ret <- plotList[[1L]] |
| 339 |
} else {
|
|
| 340 | ! |
ret <- do.call( |
| 341 | ! |
gridExtra::arrangeGrob, |
| 342 | ! |
plotList |
| 343 |
) |
|
| 344 |
} |
|
| 345 | ||
| 346 | 3x |
if (moreFromGeneral) {
|
| 347 | 1x |
ret <- gridExtra::arrangeGrob(genPlot, ret) |
| 348 |
} |
|
| 349 | ||
| 350 | 3x |
return(ret) |
| 351 |
} |
|
| 352 |
) |
|
| 353 | ||
| 354 | ||
| 355 |
##' Summarize the simulations, relative to a given truth |
|
| 356 |
##' |
|
| 357 |
##' @param object the \code{\linkS4class{GeneralSimulations}} object we want to
|
|
| 358 |
##' summarize |
|
| 359 |
##' @param truth a function which takes as input a dose (vector) and returns the |
|
| 360 |
##' true probability (vector) for toxicity |
|
| 361 |
##' @param target the target toxicity interval (default: 20-35%) used for the |
|
| 362 |
##' computations |
|
| 363 |
##' @param \dots Additional arguments can be supplied here for \code{truth}
|
|
| 364 |
##' @return an object of class \code{\linkS4class{GeneralSimulationsSummary}}
|
|
| 365 |
##' |
|
| 366 |
##' @export |
|
| 367 |
##' @keywords methods |
|
| 368 |
setMethod( |
|
| 369 |
"summary", |
|
| 370 |
signature = signature(object = "GeneralSimulations"), |
|
| 371 |
def = function(object, truth, target = c(0.2, 0.35), ...) {
|
|
| 372 |
## extract dose grid |
|
| 373 | 17x |
doseGrid <- object@data[[1]]@doseGrid |
| 374 | ||
| 375 |
## evaluate true toxicity at doseGrid |
|
| 376 | 17x |
trueTox <- truth(doseGrid, ...) |
| 377 | ||
| 378 |
## find dose interval corresponding to target tox interval |
|
| 379 | 16x |
targetDoseInterval <- |
| 380 | 16x |
sapply( |
| 381 | 16x |
target, |
| 382 | 16x |
function(t) {
|
| 383 |
## we have to be careful because it might be |
|
| 384 |
## that in the range of the dose grid, no |
|
| 385 |
## doses can be found that match the target |
|
| 386 |
## interval boundaries! |
|
| 387 |
## In that case we want to return NA |
|
| 388 | 30x |
r <- try( |
| 389 | 30x |
uniroot( |
| 390 | 30x |
f = function(x) {
|
| 391 | 139x |
truth(x, ...) - t |
| 392 |
}, |
|
| 393 | 30x |
interval = range(doseGrid) |
| 394 | 30x |
)$root, |
| 395 | 30x |
silent = TRUE |
| 396 |
) |
|
| 397 | 30x |
if (inherits(r, "try-error")) {
|
| 398 | 20x |
return(NA_real_) |
| 399 |
} else {
|
|
| 400 | 10x |
return(r) |
| 401 |
} |
|
| 402 |
} |
|
| 403 |
) |
|
| 404 | ||
| 405 |
## what are the levels above target interval? |
|
| 406 | 16x |
xAboveTarget <- which(trueTox > target[2]) |
| 407 | ||
| 408 |
## proportion of DLTs in a trial: |
|
| 409 | 16x |
if (object@data[[1]]@placebo) {
|
| 410 | ! |
if (sum(object@data[[1]]@x == doseGrid[1])) {
|
| 411 | ! |
propDLTs <- sapply( |
| 412 | ! |
object@data, |
| 413 | ! |
function(d) {
|
| 414 | ! |
tapply( |
| 415 | ! |
d@y, |
| 416 | ! |
factor(d@x == d@doseGrid[1], labels = c("ACTV", "PLCB")),
|
| 417 | ! |
mean |
| 418 |
) |
|
| 419 |
} |
|
| 420 |
) |
|
| 421 |
} else {
|
|
| 422 | ! |
propDLTs <- sapply( |
| 423 | ! |
object@data, |
| 424 | ! |
function(d) {
|
| 425 | ! |
c("ACTV" = mean(d@y), "PLCB" = NA)
|
| 426 |
} |
|
| 427 |
) |
|
| 428 |
} |
|
| 429 |
} else {
|
|
| 430 | 16x |
propDLTs <- sapply( |
| 431 | 16x |
object@data, |
| 432 | 16x |
function(d) {
|
| 433 | 30x |
mean(d@y) |
| 434 |
} |
|
| 435 |
) |
|
| 436 |
} |
|
| 437 | ||
| 438 |
## mean toxicity risk |
|
| 439 | 16x |
if (object@data[[1]]@placebo) {
|
| 440 | ! |
meanToxRisk <- sapply( |
| 441 | ! |
object@data, |
| 442 | ! |
function(d) {
|
| 443 | ! |
mean(trueTox[d@xLevel[d@xLevel != 1]]) |
| 444 |
} |
|
| 445 |
) |
|
| 446 |
} else {
|
|
| 447 | 16x |
meanToxRisk <- sapply( |
| 448 | 16x |
object@data, |
| 449 | 16x |
function(d) {
|
| 450 | 30x |
mean(trueTox[d@xLevel]) |
| 451 |
} |
|
| 452 |
) |
|
| 453 |
} |
|
| 454 | ||
| 455 |
## doses selected for MTD |
|
| 456 | 16x |
doseSelected <- object@doses |
| 457 | ||
| 458 |
## replace NA by 0 |
|
| 459 | 16x |
doseSelected[is.na(doseSelected)] <- 0 |
| 460 | ||
| 461 |
## dose most often selected as MTD |
|
| 462 | 16x |
doseMostSelected <- |
| 463 | 16x |
as.numeric(names(which.max(table(doseSelected)))) |
| 464 | 16x |
xMostSelected <- |
| 465 | 16x |
match_within_tolerance(doseMostSelected, table = doseGrid) |
| 466 | ||
| 467 |
## observed toxicity rate at dose most often selected |
|
| 468 |
## Note: this does not seem very useful! |
|
| 469 |
## Reason: In case of a fine grid, few patients if any |
|
| 470 |
## will have been treated at this dose. |
|
| 471 | 16x |
tmp <- |
| 472 | 16x |
sapply( |
| 473 | 16x |
object@data, |
| 474 | 16x |
function(d) {
|
| 475 | 30x |
whichAtThisDose <- which(d@x == doseMostSelected) |
| 476 | 30x |
nAtThisDose <- length(whichAtThisDose) |
| 477 | 30x |
nDLTatThisDose <- sum(d@y[whichAtThisDose]) |
| 478 | 30x |
return(c( |
| 479 | 30x |
nAtThisDose = nAtThisDose, |
| 480 | 30x |
nDLTatThisDose = nDLTatThisDose |
| 481 |
)) |
|
| 482 |
} |
|
| 483 |
) |
|
| 484 | ||
| 485 | 16x |
obsToxRateAtDoseMostSelected <- |
| 486 | 16x |
mean(tmp["nDLTatThisDose", ]) / mean(tmp["nAtThisDose", ]) |
| 487 | ||
| 488 |
## number of patients overall |
|
| 489 | 16x |
if (object@data[[1]]@placebo) {
|
| 490 | ! |
nObs <- sapply( |
| 491 | ! |
object@data, |
| 492 | ! |
function(x) {
|
| 493 | ! |
data.frame( |
| 494 | ! |
n.ACTV = sum(x@xLevel != 1L), |
| 495 | ! |
n.PLCB = sum(x@xLevel == 1L) |
| 496 |
) |
|
| 497 |
} |
|
| 498 |
) |
|
| 499 | ! |
nObs <- matrix(unlist(nObs), dim(nObs)) |
| 500 |
} else {
|
|
| 501 | 16x |
nObs <- sapply( |
| 502 | 16x |
object@data, |
| 503 | 16x |
slot, |
| 504 | 16x |
"nObs" |
| 505 |
) |
|
| 506 |
} |
|
| 507 | ||
| 508 |
## number of patients treated above target tox interval |
|
| 509 | 16x |
nAboveTarget <- sapply( |
| 510 | 16x |
object@data, |
| 511 | 16x |
function(d) {
|
| 512 | 30x |
sum(d@xLevel %in% xAboveTarget) |
| 513 |
} |
|
| 514 |
) |
|
| 515 | ||
| 516 |
## Proportion of trials selecting target MTD |
|
| 517 | 16x |
toxAtDoses <- truth(doseSelected, ...) |
| 518 | 16x |
propAtTarget <- mean( |
| 519 | 16x |
(toxAtDoses > target[1]) & |
| 520 | 16x |
(toxAtDoses < target[2]) |
| 521 |
) |
|
| 522 | ||
| 523 |
## give back an object of class GeneralSimulationsSummary, |
|
| 524 |
## for which we then define a print / plot method |
|
| 525 | 16x |
ret <- |
| 526 | 16x |
.GeneralSimulationsSummary( |
| 527 | 16x |
target = target, |
| 528 | 16x |
target_dose_interval = targetDoseInterval, |
| 529 | 16x |
nsim = length(object@data), |
| 530 | 16x |
prop_dlts = propDLTs, |
| 531 | 16x |
mean_tox_risk = meanToxRisk, |
| 532 | 16x |
dose_selected = doseSelected, |
| 533 | 16x |
dose_most_selected = doseMostSelected, |
| 534 | 16x |
obs_tox_rate_at_dose_most_selected = obsToxRateAtDoseMostSelected, |
| 535 | 16x |
n_obs = nObs, |
| 536 | 16x |
n_above_target = nAboveTarget, |
| 537 | 16x |
tox_at_doses_selected = toxAtDoses, |
| 538 | 16x |
prop_at_target = propAtTarget, |
| 539 | 16x |
dose_grid = doseGrid, |
| 540 | 16x |
placebo = object@data[[1]]@placebo |
| 541 |
) |
|
| 542 | 16x |
return(ret) |
| 543 |
} |
|
| 544 |
) |
|
| 545 | ||
| 546 | ||
| 547 |
##' Summarize the model-based design simulations, relative to a given truth |
|
| 548 |
##' |
|
| 549 |
##' @param object the \code{\linkS4class{Simulations}} object we want to
|
|
| 550 |
##' summarize |
|
| 551 |
##' @param truth a function which takes as input a dose (vector) and returns the |
|
| 552 |
##' true probability (vector) for toxicity |
|
| 553 |
##' @param target the target toxicity interval (default: 20-35%) used for the |
|
| 554 |
##' computations |
|
| 555 |
##' @param \dots Additional arguments can be supplied here for \code{truth}
|
|
| 556 |
##' @return an object of class \code{\linkS4class{SimulationsSummary}}
|
|
| 557 |
##' |
|
| 558 |
##' @example examples/Simulations-method-summary.R |
|
| 559 |
##' @export |
|
| 560 |
##' @keywords methods |
|
| 561 |
setMethod( |
|
| 562 |
"summary", |
|
| 563 |
signature = signature(object = "Simulations"), |
|
| 564 |
def = function(object, truth, target = c(0.2, 0.35), ...) {
|
|
| 565 |
## call the parent method |
|
| 566 | 17x |
start <- callNextMethod( |
| 567 | 17x |
object = object, |
| 568 | 17x |
truth = truth, |
| 569 | 17x |
target = target, |
| 570 |
... |
|
| 571 |
) |
|
| 572 | ||
| 573 | 16x |
doseGrid <- object@data[[1]]@doseGrid |
| 574 | ||
| 575 |
## dose level most often selected as MTD |
|
| 576 | 16x |
xMostSelected <- |
| 577 | 16x |
match_within_tolerance(start@dose_most_selected, table = doseGrid) |
| 578 | ||
| 579 |
## fitted toxicity rate at dose most often selected |
|
| 580 | 16x |
fitAtDoseMostSelected <- |
| 581 | 16x |
sapply( |
| 582 | 16x |
object@fit, |
| 583 | 16x |
function(f) {
|
| 584 | 30x |
f$middle[xMostSelected] |
| 585 |
} |
|
| 586 |
) |
|
| 587 | ||
| 588 |
## mean fitted toxicity (average, lower and upper quantiles) |
|
| 589 |
## at each dose level |
|
| 590 |
## (this is required for plotting) |
|
| 591 | 16x |
meanFitMatrix <- sapply( |
| 592 | 16x |
object@fit, |
| 593 |
"[[", |
|
| 594 | 16x |
"middle" |
| 595 |
) |
|
| 596 | 16x |
meanFit <- list( |
| 597 | 16x |
truth = truth(doseGrid, ...), |
| 598 | 16x |
average = rowMeans(meanFitMatrix), |
| 599 | 16x |
lower = apply( |
| 600 | 16x |
meanFitMatrix, |
| 601 | 16x |
1L, |
| 602 | 16x |
quantile, |
| 603 | 16x |
0.025 |
| 604 |
), |
|
| 605 | 16x |
upper = apply( |
| 606 | 16x |
meanFitMatrix, |
| 607 | 16x |
1L, |
| 608 | 16x |
quantile, |
| 609 | 16x |
0.975 |
| 610 |
) |
|
| 611 |
) |
|
| 612 | ||
| 613 |
## give back an object of class SimulationsSummary, |
|
| 614 |
## for which we then define a print / plot method |
|
| 615 | 16x |
ret <- .SimulationsSummary( |
| 616 | 16x |
start, |
| 617 | 16x |
stop_report = object@stop_report, |
| 618 | 16x |
additional_stats = object@additional_stats, |
| 619 | 16x |
fit_at_dose_most_selected = fitAtDoseMostSelected, |
| 620 | 16x |
mean_fit = meanFit |
| 621 |
) |
|
| 622 | ||
| 623 | 16x |
return(ret) |
| 624 |
} |
|
| 625 |
) |
|
| 626 | ||
| 627 |
##' Summarize the dual-endpoint design simulations, relative to given true |
|
| 628 |
##' dose-toxicity and dose-biomarker curves |
|
| 629 |
##' |
|
| 630 |
##' @param object the \code{\linkS4class{DualSimulations}} object we want to
|
|
| 631 |
##' summarize |
|
| 632 |
##' @param trueTox a function which takes as input a dose (vector) and returns the |
|
| 633 |
##' true probability (vector) for toxicity. |
|
| 634 |
##' @param trueBiomarker a function which takes as input a dose (vector) and |
|
| 635 |
##' returns the true biomarker level (vector). |
|
| 636 |
##' @param target the target toxicity interval (default: 20-35%) used for the |
|
| 637 |
##' computations |
|
| 638 |
##' @param \dots Additional arguments can be supplied here for \code{trueTox}
|
|
| 639 |
##' and \code{trueBiomarker}
|
|
| 640 |
##' @return an object of class \code{\linkS4class{DualSimulationsSummary}}
|
|
| 641 |
##' |
|
| 642 |
##' @example examples/Simulations-method-summary-DualSimulations.R |
|
| 643 |
##' @export |
|
| 644 |
##' @keywords methods |
|
| 645 |
setMethod( |
|
| 646 |
"summary", |
|
| 647 |
signature = signature(object = "DualSimulations"), |
|
| 648 |
def = function(object, trueTox, trueBiomarker, target = c(0.2, 0.35), ...) {
|
|
| 649 |
## call the parent method |
|
| 650 | 3x |
start <- callNextMethod( |
| 651 | 3x |
object = object, |
| 652 | 3x |
truth = trueTox, |
| 653 | 3x |
target = target, |
| 654 |
... |
|
| 655 |
) |
|
| 656 | ||
| 657 | 3x |
doseGrid <- object@data[[1]]@doseGrid |
| 658 | ||
| 659 |
## dose level most often selected as MTD |
|
| 660 | 3x |
xMostSelected <- |
| 661 | 3x |
match_within_tolerance(start@dose_most_selected, table = doseGrid) |
| 662 | ||
| 663 |
## fitted biomarker level at dose most often selected |
|
| 664 | 3x |
biomarkerFitAtDoseMostSelected <- |
| 665 | 3x |
sapply( |
| 666 | 3x |
object@fit_biomarker, |
| 667 | 3x |
function(f) {
|
| 668 | 3x |
f$middleBiomarker[xMostSelected] |
| 669 |
} |
|
| 670 |
) |
|
| 671 | ||
| 672 |
## mean fitted biomarker curve (average, lower and upper quantiles) |
|
| 673 |
## at each dose level |
|
| 674 |
## (this is required for plotting) |
|
| 675 | 3x |
meanBiomarkerFitMatrix <- sapply( |
| 676 | 3x |
object@fit_biomarker, |
| 677 |
"[[", |
|
| 678 | 3x |
"middleBiomarker" |
| 679 |
) |
|
| 680 | 3x |
meanBiomarkerFit <- list( |
| 681 | 3x |
truth = trueBiomarker(doseGrid, ...), |
| 682 | 3x |
average = rowMeans(meanBiomarkerFitMatrix), |
| 683 | 3x |
lower = apply( |
| 684 | 3x |
meanBiomarkerFitMatrix, |
| 685 | 3x |
1L, |
| 686 | 3x |
quantile, |
| 687 | 3x |
0.025 |
| 688 |
), |
|
| 689 | 3x |
upper = apply( |
| 690 | 3x |
meanBiomarkerFitMatrix, |
| 691 | 3x |
1L, |
| 692 | 3x |
quantile, |
| 693 | 3x |
0.975 |
| 694 |
) |
|
| 695 |
) |
|
| 696 | ||
| 697 |
## give back an object of class DualSimulationsSummary, |
|
| 698 |
## for which we then define a print / plot method |
|
| 699 | 3x |
ret <- .DualSimulationsSummary( |
| 700 | 3x |
start, |
| 701 | 3x |
biomarker_fit_at_dose_most_selected = biomarkerFitAtDoseMostSelected, |
| 702 | 3x |
mean_biomarker_fit = meanBiomarkerFit |
| 703 |
) |
|
| 704 | ||
| 705 | 3x |
return(ret) |
| 706 |
} |
|
| 707 |
) |
|
| 708 | ||
| 709 |
##' A Reference Class to represent sequentially updated reporting objects. |
|
| 710 |
##' @name Report |
|
| 711 |
##' @field object The object from which to report |
|
| 712 |
##' @field df the data frame to which columns are sequentially added |
|
| 713 |
##' @field dfNames the names to which strings are sequentially added |
|
| 714 |
Report <- |
|
| 715 |
setRefClass( |
|
| 716 |
"Report", |
|
| 717 |
fields = list( |
|
| 718 |
object = "ANY", |
|
| 719 |
df = "data.frame", |
|
| 720 |
dfNames = "character" |
|
| 721 |
), |
|
| 722 |
methods = list( |
|
| 723 |
dfSave = function(res, name) {
|
|
| 724 | 1004x |
df <<- cbind(df, res) |
| 725 | 1004x |
dfNames <<- c(dfNames, name) |
| 726 | 1004x |
return(res) |
| 727 |
}, |
|
| 728 |
report = function( |
|
| 729 |
slotName, |
|
| 730 |
description, |
|
| 731 |
percent = TRUE, |
|
| 732 |
digits = 0, |
|
| 733 |
quantiles = c(0.1, 0.9), |
|
| 734 |
subset = NULL, |
|
| 735 |
doSum = FALSE |
|
| 736 |
) {
|
|
| 737 | 171x |
vals <- slot(object, name = slotName) |
| 738 | 171x |
if (!is.null(subset)) {
|
| 739 | ! |
vals <- vals[subset, ] |
| 740 |
} |
|
| 741 | 171x |
if (doSum) {
|
| 742 | ! |
vals <- apply(vals, 2, sum) |
| 743 |
} |
|
| 744 | 171x |
if (percent) {
|
| 745 | 84x |
unit <- " %" |
| 746 | 84x |
vals <- vals * 100 |
| 747 |
} else {
|
|
| 748 | 87x |
unit <- "" |
| 749 |
} |
|
| 750 | ||
| 751 | 171x |
res <- paste( |
| 752 | 171x |
round(mean(vals), digits), |
| 753 | 171x |
unit, |
| 754 |
" (",
|
|
| 755 | 171x |
paste( |
| 756 | 171x |
round( |
| 757 | 171x |
quantile(vals, quantiles, na.rm = TRUE), |
| 758 | 171x |
digits |
| 759 |
), |
|
| 760 | 171x |
unit, |
| 761 | 171x |
collapse = ", ", |
| 762 | 171x |
sep = "" |
| 763 |
), |
|
| 764 |
")", |
|
| 765 | 171x |
sep = "" |
| 766 |
) |
|
| 767 | ||
| 768 |
## print result to the buffer |
|
| 769 | 171x |
cat( |
| 770 | 171x |
description, |
| 771 |
":", |
|
| 772 | 171x |
"mean", |
| 773 | 171x |
dfSave(res, slotName), |
| 774 | 171x |
"\n" |
| 775 |
) |
|
| 776 |
} |
|
| 777 |
) |
|
| 778 |
) |
|
| 779 | ||
| 780 | ||
| 781 |
##' Show the summary of the simulations |
|
| 782 |
##' |
|
| 783 |
##' @param object the \code{\linkS4class{GeneralSimulationsSummary}} object we want
|
|
| 784 |
##' to print |
|
| 785 |
##' @return invisibly returns a data frame of the results with one row and |
|
| 786 |
##' appropriate column names |
|
| 787 |
##' |
|
| 788 |
##' @export |
|
| 789 |
##' @keywords methods |
|
| 790 |
setMethod( |
|
| 791 |
"show", |
|
| 792 |
signature = signature(object = "GeneralSimulationsSummary"), |
|
| 793 |
def = function(object) {
|
|
| 794 | 6x |
r <- Report$new( |
| 795 | 6x |
object = object, |
| 796 | 6x |
df = as.data.frame(matrix( |
| 797 | 6x |
nrow = 1, |
| 798 | 6x |
ncol = 0 |
| 799 |
)), |
|
| 800 | 6x |
dfNames = character() |
| 801 |
) |
|
| 802 | ||
| 803 | 6x |
cat( |
| 804 | 6x |
"Summary of", |
| 805 | 6x |
r$dfSave(object@nsim, "nsim"), |
| 806 | 6x |
"simulations\n\n" |
| 807 |
) |
|
| 808 | ||
| 809 | 6x |
cat( |
| 810 | 6x |
"Target toxicity interval was", |
| 811 | 6x |
r$dfSave( |
| 812 | 6x |
paste(round(object@target * 100), collapse = ", "), |
| 813 | 6x |
"target" |
| 814 |
), |
|
| 815 | 6x |
"%\n" |
| 816 |
) |
|
| 817 | 6x |
cat( |
| 818 | 6x |
"Target dose interval corresponding to this was", |
| 819 | 6x |
r$dfSave( |
| 820 | 6x |
paste(round(object@target_dose_interval, 1), collapse = ", "), |
| 821 | 6x |
"target_dose_interval" |
| 822 |
), |
|
| 823 | 6x |
"\n" |
| 824 |
) |
|
| 825 | 6x |
cat( |
| 826 | 6x |
"Intervals are corresponding to", |
| 827 | 6x |
"10 and 90 % quantiles\n\n" |
| 828 |
) |
|
| 829 | ||
| 830 | 6x |
if (object@placebo) {
|
| 831 | ! |
r$report( |
| 832 | ! |
"n_obs", |
| 833 | ! |
"Number of patients on placebo", |
| 834 | ! |
percent = FALSE, |
| 835 | ! |
subset = 2 |
| 836 |
) |
|
| 837 | ! |
r$report( |
| 838 | ! |
"n_obs", |
| 839 | ! |
"Number of patients on active", |
| 840 | ! |
percent = FALSE, |
| 841 | ! |
subset = 1 |
| 842 |
) |
|
| 843 | ! |
r$report( |
| 844 | ! |
"n_obs", |
| 845 | ! |
"Number of patients overall", |
| 846 | ! |
percent = FALSE, |
| 847 | ! |
doSum = TRUE |
| 848 |
) |
|
| 849 |
} else {
|
|
| 850 | 6x |
r$report("n_obs", "Number of patients overall", percent = FALSE)
|
| 851 |
} |
|
| 852 | 6x |
r$report( |
| 853 | 6x |
"n_above_target", |
| 854 | 6x |
"Number of patients treated above target tox interval", |
| 855 | 6x |
percent = FALSE |
| 856 |
) |
|
| 857 | ||
| 858 | 6x |
if (object@placebo) {
|
| 859 | ! |
r$report( |
| 860 | ! |
"prop_dlts", |
| 861 | ! |
"Proportions of DLTs in the trials for patients on placebo", |
| 862 | ! |
subset = 2 |
| 863 |
) |
|
| 864 | ! |
r$report( |
| 865 | ! |
"prop_dlts", |
| 866 | ! |
"Proportions of DLTs in the trials for patients on active", |
| 867 | ! |
subset = 1 |
| 868 |
) |
|
| 869 |
} else {
|
|
| 870 | 6x |
r$report( |
| 871 | 6x |
"prop_dlts", |
| 872 | 6x |
"Proportions of DLTs in the trials" |
| 873 |
) |
|
| 874 |
} |
|
| 875 | 6x |
r$report( |
| 876 | 6x |
"mean_tox_risk", |
| 877 | 6x |
"Mean toxicity risks for the patients on active" |
| 878 |
) |
|
| 879 | 6x |
r$report( |
| 880 | 6x |
"dose_selected", |
| 881 | 6x |
"Doses selected as MTD", |
| 882 | 6x |
percent = FALSE, |
| 883 | 6x |
digits = 1 |
| 884 |
) |
|
| 885 | 6x |
r$report( |
| 886 | 6x |
"tox_at_doses_selected", |
| 887 | 6x |
"True toxicity at doses selected" |
| 888 |
) |
|
| 889 | 6x |
cat( |
| 890 | 6x |
"Proportion of trials selecting target MTD:", |
| 891 | 6x |
r$dfSave( |
| 892 | 6x |
object@prop_at_target * 100, |
| 893 | 6x |
"prop_at_target" |
| 894 |
), |
|
| 895 | 6x |
"%\n" |
| 896 |
) |
|
| 897 | 6x |
cat( |
| 898 | 6x |
"Dose most often selected as MTD:", |
| 899 | 6x |
r$dfSave( |
| 900 | 6x |
object@dose_most_selected, |
| 901 | 6x |
"dose_most_selected" |
| 902 |
), |
|
| 903 | 6x |
"\n" |
| 904 |
) |
|
| 905 | 6x |
cat( |
| 906 | 6x |
"Observed toxicity rate at dose most often selected:", |
| 907 | 6x |
r$dfSave( |
| 908 | 6x |
round(object@obs_tox_rate_at_dose_most_selected * 100), |
| 909 | 6x |
"obs_tox_rate_at_dose_most_selected" |
| 910 |
), |
|
| 911 | 6x |
"%\n" |
| 912 |
) |
|
| 913 | ||
| 914 |
## finally assign names to the df |
|
| 915 |
## and return it invisibly |
|
| 916 | 6x |
names(r$df) <- r$dfNames |
| 917 | 6x |
invisible(r$df) |
| 918 |
} |
|
| 919 |
) |
|
| 920 | ||
| 921 |
##' Show the summary of the simulations |
|
| 922 |
##' |
|
| 923 |
##' @param object the \code{\linkS4class{SimulationsSummary}} object we want
|
|
| 924 |
##' to print |
|
| 925 |
##' @return invisibly returns a data frame of the results with one row and |
|
| 926 |
##' appropriate column names |
|
| 927 |
##' |
|
| 928 |
##' @example examples/Simulations-method-show-SimulationsSummary.R |
|
| 929 |
##' @export |
|
| 930 |
##' @keywords methods |
|
| 931 |
setMethod( |
|
| 932 |
"show", |
|
| 933 |
signature = signature(object = "SimulationsSummary"), |
|
| 934 |
def = function(object) {
|
|
| 935 |
## call the parent method |
|
| 936 | 6x |
df <- callNextMethod(object) |
| 937 | 6x |
dfNames <- names(df) |
| 938 | ||
| 939 |
## start report object |
|
| 940 | 6x |
r <- Report$new( |
| 941 | 6x |
object = object, |
| 942 | 6x |
df = df, |
| 943 | 6x |
dfNames = dfNames |
| 944 |
) |
|
| 945 | ||
| 946 |
## add one reporting line |
|
| 947 | 6x |
r$report( |
| 948 | 6x |
"fit_at_dose_most_selected", |
| 949 | 6x |
"Fitted toxicity rate at dose most often selected" |
| 950 |
) |
|
| 951 | ||
| 952 |
# Report results of additional statistics summary |
|
| 953 | ||
| 954 | 6x |
if (length(unlist(object@additional_stats)) > 0) {
|
| 955 | ! |
param_names <- h_summarize_add_stats( |
| 956 | ! |
stats_list = object@additional_stats |
| 957 | ! |
)[[1]] |
| 958 | ! |
averages <- h_summarize_add_stats(stats_list = object@additional_stats)[[ |
| 959 | ! |
2 |
| 960 |
]] |
|
| 961 | ||
| 962 | ! |
for (i in seq_along(param_names)) {
|
| 963 | ! |
cat(param_names[i], ":", round(averages[[i]], 2), "\n") |
| 964 |
} |
|
| 965 |
} |
|
| 966 | ||
| 967 |
# Report individual stopping rules with non-<NA> labels. |
|
| 968 | ||
| 969 | 6x |
stop_pct_to_print <- h_calc_report_label_percentage(object@stop_report) |
| 970 | ||
| 971 | 6x |
if (length(stop_pct_to_print) > 0) {
|
| 972 | 6x |
cat( |
| 973 | 6x |
"Stop reason triggered:\n", |
| 974 | 6x |
paste( |
| 975 | 6x |
names(stop_pct_to_print), |
| 976 |
": ", |
|
| 977 | 6x |
round(stop_pct_to_print, 2), |
| 978 | 6x |
"%\n" |
| 979 |
) |
|
| 980 |
) |
|
| 981 |
} |
|
| 982 | ||
| 983 |
## and return the updated information |
|
| 984 | 6x |
names(r$df) <- r$dfNames |
| 985 | 6x |
invisible(r$df) |
| 986 |
} |
|
| 987 |
) |
|
| 988 | ||
| 989 |
##' Show the summary of the dual-endpoint simulations |
|
| 990 |
##' |
|
| 991 |
##' @param object the \code{\linkS4class{DualSimulationsSummary}} object we want
|
|
| 992 |
##' to print |
|
| 993 |
##' @return invisibly returns a data frame of the results with one row and |
|
| 994 |
##' appropriate column names |
|
| 995 |
##' |
|
| 996 |
##' @example examples/Simulations-method-show-DualSimulationsSummary.R |
|
| 997 |
##' @export |
|
| 998 |
##' @keywords methods |
|
| 999 |
setMethod( |
|
| 1000 |
"show", |
|
| 1001 |
signature = signature(object = "DualSimulationsSummary"), |
|
| 1002 |
def = function(object) {
|
|
| 1003 |
## call the parent method |
|
| 1004 | 2x |
df <- callNextMethod(object) |
| 1005 | 2x |
dfNames <- names(df) |
| 1006 | ||
| 1007 |
## start report object |
|
| 1008 | 2x |
r <- Report$new( |
| 1009 | 2x |
object = object, |
| 1010 | 2x |
df = df, |
| 1011 | 2x |
dfNames = dfNames |
| 1012 |
) |
|
| 1013 | ||
| 1014 |
## add one reporting line |
|
| 1015 | 2x |
r$report( |
| 1016 | 2x |
"biomarker_fit_at_dose_most_selected", |
| 1017 | 2x |
"Fitted biomarker level at dose most often selected", |
| 1018 | 2x |
percent = FALSE, |
| 1019 | 2x |
digits = 1 |
| 1020 |
) |
|
| 1021 | ||
| 1022 |
## and return the updated information |
|
| 1023 | 2x |
names(r$df) <- r$dfNames |
| 1024 | 2x |
invisible(r$df) |
| 1025 |
} |
|
| 1026 |
) |
|
| 1027 | ||
| 1028 | ||
| 1029 |
##' Graphical display of the general simulation summary |
|
| 1030 |
##' |
|
| 1031 |
##' This plot method can be applied to |
|
| 1032 |
##' \code{\linkS4class{GeneralSimulationsSummary}} objects in order to
|
|
| 1033 |
##' summarize them graphically. Possible \code{type}s of plots at the moment
|
|
| 1034 |
##' are: |
|
| 1035 |
##' |
|
| 1036 |
##' \describe{
|
|
| 1037 |
##' \item{nObs}{Distribution of the number of patients in the simulated trials}
|
|
| 1038 |
##' \item{doseSelected}{Distribution of the final selected doses in the trials.
|
|
| 1039 |
##' Note that this can include zero entries, meaning that the trial was stopped |
|
| 1040 |
##' because all doses in the dose grid appeared too toxic.} |
|
| 1041 |
##' \item{propDLTs}{Distribution of the proportion of patients with DLTs in the
|
|
| 1042 |
##' trials} |
|
| 1043 |
##' \item{nAboveTarget}{Distribution of the number of patients treated at doses
|
|
| 1044 |
##' which are above the target toxicity interval (as specified by the |
|
| 1045 |
##' \code{truth} and \code{target} arguments to
|
|
| 1046 |
##' \code{\link{summary,GeneralSimulations-method}})}
|
|
| 1047 |
##' } |
|
| 1048 |
##' You can specify any subset of these in the \code{type} argument.
|
|
| 1049 |
##' |
|
| 1050 |
##' @param x the \code{\linkS4class{GeneralSimulationsSummary}} object we want
|
|
| 1051 |
##' to plot from |
|
| 1052 |
##' @param y missing |
|
| 1053 |
##' @param type the types of plots you want to obtain. |
|
| 1054 |
##' @param \dots not used |
|
| 1055 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 1056 |
##' asked for, otherwise a `gtable` object. |
|
| 1057 |
##' |
|
| 1058 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
| 1059 |
##' scale_linetype_manual scale_colour_manual |
|
| 1060 |
##' @importFrom gridExtra arrangeGrob |
|
| 1061 |
##' @export |
|
| 1062 |
##' @keywords methods |
|
| 1063 |
setMethod( |
|
| 1064 |
"plot", |
|
| 1065 |
signature = signature( |
|
| 1066 |
x = "GeneralSimulationsSummary", |
|
| 1067 |
y = "missing" |
|
| 1068 |
), |
|
| 1069 |
def = function( |
|
| 1070 |
x, |
|
| 1071 |
y, |
|
| 1072 |
type = c( |
|
| 1073 |
"nObs", |
|
| 1074 |
"doseSelected", |
|
| 1075 |
"propDLTs", |
|
| 1076 |
"nAboveTarget" |
|
| 1077 |
), |
|
| 1078 |
... |
|
| 1079 |
) {
|
|
| 1080 |
## which plots should be produced? |
|
| 1081 | 8x |
type <- match.arg(type, several.ok = TRUE) |
| 1082 | 8x |
stopifnot(length(type) > 0L) |
| 1083 | ||
| 1084 |
## start the plot list |
|
| 1085 | 8x |
plotList <- list() |
| 1086 | 8x |
plotIndex <- 0L |
| 1087 | ||
| 1088 |
## distribution of overall sample size |
|
| 1089 | 8x |
if (x@placebo) {
|
| 1090 | ! |
if ("nObs" %in% type) {
|
| 1091 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 1092 | ! |
h_barplot_percentages( |
| 1093 | ! |
x = x@n_obs[2, ], |
| 1094 | ! |
description = "Number of patients on active in total" |
| 1095 |
) |
|
| 1096 |
} |
|
| 1097 |
} else {
|
|
| 1098 | 8x |
if ("nObs" %in% type) {
|
| 1099 | 4x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 1100 | 4x |
h_barplot_percentages( |
| 1101 | 4x |
x = x@n_obs, |
| 1102 | 4x |
description = "Number of patients in total" |
| 1103 |
) |
|
| 1104 |
} |
|
| 1105 |
} |
|
| 1106 | ||
| 1107 |
## distribution of final MTD estimate |
|
| 1108 | 8x |
if ("doseSelected" %in% type) {
|
| 1109 | 3x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 1110 | 3x |
h_barplot_percentages( |
| 1111 | 3x |
x = x@dose_selected, |
| 1112 | 3x |
description = "MTD estimate" |
| 1113 |
) |
|
| 1114 |
} |
|
| 1115 | ||
| 1116 |
## distribution of proportion of DLTs |
|
| 1117 | 8x |
if (x@placebo) {
|
| 1118 | ! |
if ("propDLTs" %in% type) {
|
| 1119 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 1120 | ! |
h_barplot_percentages( |
| 1121 | ! |
x = x@prop_dlts[1, ] * 100, |
| 1122 | ! |
description = "Proportion of DLTs [%] on active", |
| 1123 | ! |
xaxisround = 1 |
| 1124 |
) |
|
| 1125 |
} |
|
| 1126 |
} else {
|
|
| 1127 | 8x |
if ("propDLTs" %in% type) {
|
| 1128 | 1x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 1129 | 1x |
h_barplot_percentages( |
| 1130 | 1x |
x = x@prop_dlts * 100, |
| 1131 | 1x |
description = "Proportion of DLTs [%]", |
| 1132 | 1x |
xaxisround = 1 |
| 1133 |
) |
|
| 1134 |
} |
|
| 1135 |
} |
|
| 1136 | ||
| 1137 |
## distribution of number of patients treated at too much tox |
|
| 1138 | 8x |
if ("nAboveTarget" %in% type) {
|
| 1139 | 1x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 1140 | 1x |
h_barplot_percentages( |
| 1141 | 1x |
x = x@n_above_target, |
| 1142 | 1x |
description = "Number of patients above target" |
| 1143 |
) |
|
| 1144 |
} |
|
| 1145 | ||
| 1146 |
## first combine these small plots |
|
| 1147 | 8x |
if (length(plotList)) {
|
| 1148 | 8x |
ret <- |
| 1149 |
## if there is only one plot |
|
| 1150 | 8x |
if ( |
| 1151 | 8x |
identical( |
| 1152 | 8x |
length(plotList), |
| 1153 | 8x |
1L |
| 1154 |
) |
|
| 1155 |
) {
|
|
| 1156 |
## just use that |
|
| 1157 | 7x |
plotList[[1L]] |
| 1158 |
} else {
|
|
| 1159 |
## multiple plots in this case |
|
| 1160 | 1x |
do.call( |
| 1161 | 1x |
gridExtra::arrangeGrob, |
| 1162 | 1x |
plotList |
| 1163 |
) |
|
| 1164 |
} |
|
| 1165 |
} |
|
| 1166 | ||
| 1167 |
## then return |
|
| 1168 | 8x |
ret |
| 1169 |
} |
|
| 1170 |
) |
|
| 1171 | ||
| 1172 | ||
| 1173 |
##' Plot summaries of the model-based design simulations |
|
| 1174 |
##' |
|
| 1175 |
##' Graphical display of the simulation summary |
|
| 1176 |
##' |
|
| 1177 |
##' This plot method can be applied to \code{\linkS4class{SimulationsSummary}}
|
|
| 1178 |
##' objects in order to summarize them graphically. Possible \code{type} of
|
|
| 1179 |
##' plots at the moment are those listed in |
|
| 1180 |
##' \code{\link{plot,GeneralSimulationsSummary,missing-method}} plus:
|
|
| 1181 |
##' \describe{
|
|
| 1182 |
##' \item{meanFit}{Plot showing the average fitted dose-toxicity curve across
|
|
| 1183 |
##' the trials, together with 95% credible intervals, and comparison with the |
|
| 1184 |
##' assumed truth (as specified by the \code{truth} argument to
|
|
| 1185 |
##' \code{\link{summary,Simulations-method}})}
|
|
| 1186 |
##' } |
|
| 1187 |
##' You can specify any subset of these in the \code{type} argument.
|
|
| 1188 |
##' |
|
| 1189 |
##' @param x the \code{\linkS4class{SimulationsSummary}} object we want
|
|
| 1190 |
##' to plot from |
|
| 1191 |
##' @param y missing |
|
| 1192 |
##' @param type the types of plots you want to obtain. |
|
| 1193 |
##' @param \dots not used |
|
| 1194 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 1195 |
##' asked for, otherwise a `gtable` object. |
|
| 1196 |
##' |
|
| 1197 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
| 1198 |
##' scale_linetype_manual scale_colour_manual |
|
| 1199 |
##' @importFrom gridExtra arrangeGrob |
|
| 1200 |
##' |
|
| 1201 |
##' @example examples/Simulations-method-plot-SimulationsSummary.R |
|
| 1202 |
##' @export |
|
| 1203 |
##' @keywords methods |
|
| 1204 |
setMethod( |
|
| 1205 |
"plot", |
|
| 1206 |
signature = signature( |
|
| 1207 |
x = "SimulationsSummary", |
|
| 1208 |
y = "missing" |
|
| 1209 |
), |
|
| 1210 |
def = function( |
|
| 1211 |
x, |
|
| 1212 |
y, |
|
| 1213 |
type = c( |
|
| 1214 |
"nObs", |
|
| 1215 |
"doseSelected", |
|
| 1216 |
"propDLTs", |
|
| 1217 |
"nAboveTarget", |
|
| 1218 |
"meanFit" |
|
| 1219 |
), |
|
| 1220 |
... |
|
| 1221 |
) {
|
|
| 1222 |
## which plots should be produced? |
|
| 1223 | 10x |
type <- match.arg(type, several.ok = TRUE) |
| 1224 | 10x |
stopifnot(length(type) > 0L) |
| 1225 | ||
| 1226 |
## substract the specific plot types for model-based |
|
| 1227 |
## designs |
|
| 1228 | 10x |
typeReduced <- setdiff( |
| 1229 | 10x |
type, |
| 1230 | 10x |
"meanFit" |
| 1231 |
) |
|
| 1232 | ||
| 1233 |
## are there more plots from general? |
|
| 1234 | 10x |
moreFromGeneral <- (length(typeReduced) > 0) |
| 1235 | ||
| 1236 |
## if so, then produce these plots |
|
| 1237 | 10x |
if (moreFromGeneral) {
|
| 1238 | 8x |
ret <- callNextMethod(x = x, y = y, type = typeReduced) |
| 1239 |
} |
|
| 1240 | ||
| 1241 |
## is the meanFit plot requested? |
|
| 1242 | 10x |
if ("meanFit" %in% type) {
|
| 1243 |
## which types of lines do we have? |
|
| 1244 | 3x |
linetype <- c( |
| 1245 | 3x |
"True toxicity", |
| 1246 | 3x |
"Average estimated toxicity", |
| 1247 | 3x |
"95% interval for estimated toxicity" |
| 1248 |
) |
|
| 1249 | ||
| 1250 |
## create the data frame, with |
|
| 1251 |
## true tox, average estimated tox, and 95% (lower, upper) |
|
| 1252 |
## estimated tox (in percentage) stacked below each other |
|
| 1253 | 3x |
dat <- data.frame( |
| 1254 | 3x |
dose = rep(x@dose_grid, 4L), |
| 1255 | 3x |
group = rep(1:4, each = length(x@dose_grid)), |
| 1256 | 3x |
linetype = factor( |
| 1257 | 3x |
rep(linetype[c(1, 2, 3, 3)], each = length(x@dose_grid)), |
| 1258 | 3x |
levels = linetype |
| 1259 |
), |
|
| 1260 | 3x |
lines = unlist(x@mean_fit) * 100 |
| 1261 |
) |
|
| 1262 | ||
| 1263 |
## linetypes for the plot |
|
| 1264 | 3x |
lt <- c( |
| 1265 | 3x |
"True toxicity" = 1, |
| 1266 | 3x |
"Average estimated toxicity" = 1, |
| 1267 | 3x |
"95% interval for estimated toxicity" = 2 |
| 1268 |
) |
|
| 1269 | ||
| 1270 |
## colour for the plot |
|
| 1271 | 3x |
col <- c( |
| 1272 | 3x |
"True toxicity" = 1, |
| 1273 | 3x |
"Average estimated toxicity" = 2, |
| 1274 | 3x |
"95% interval for estimated toxicity" = 2 |
| 1275 |
) |
|
| 1276 | ||
| 1277 |
## now create and save the plot |
|
| 1278 | 3x |
thisPlot <- ggplot() + |
| 1279 | 3x |
geom_line( |
| 1280 | 3x |
aes( |
| 1281 | 3x |
x = dose, |
| 1282 | 3x |
y = lines, |
| 1283 | 3x |
group = group, |
| 1284 | 3x |
linetype = linetype, |
| 1285 | 3x |
col = linetype |
| 1286 |
), |
|
| 1287 | 3x |
data = dat |
| 1288 |
) |
|
| 1289 | ||
| 1290 | 3x |
thisPlot <- thisPlot + |
| 1291 | 3x |
scale_linetype_manual(values = lt) + |
| 1292 | 3x |
scale_colour_manual(values = col) + |
| 1293 | 3x |
xlab("Dose level") +
|
| 1294 | 3x |
ylab("Probability of DLT [%]")
|
| 1295 | ||
| 1296 |
## add this plot to the bottom |
|
| 1297 | 3x |
ret <- |
| 1298 | 3x |
if (moreFromGeneral) {
|
| 1299 | 1x |
gridExtra::arrangeGrob(ret, thisPlot) |
| 1300 |
} else {
|
|
| 1301 | 2x |
thisPlot |
| 1302 |
} |
|
| 1303 |
} |
|
| 1304 | ||
| 1305 |
## then finally plot everything |
|
| 1306 | 10x |
ret |
| 1307 |
} |
|
| 1308 |
) |
|
| 1309 | ||
| 1310 | ||
| 1311 |
##' Plot summaries of the dual-endpoint design simulations |
|
| 1312 |
##' |
|
| 1313 |
##' This plot method can be applied to \code{\linkS4class{DualSimulationsSummary}}
|
|
| 1314 |
##' objects in order to summarize them graphically. Possible \code{type} of
|
|
| 1315 |
##' plots at the moment are those listed in |
|
| 1316 |
##' \code{\link{plot,SimulationsSummary,missing-method}} plus:
|
|
| 1317 |
##' \describe{
|
|
| 1318 |
##' \item{meanBiomarkerFit}{Plot showing the average fitted dose-biomarker curve across
|
|
| 1319 |
##' the trials, together with 95% credible intervals, and comparison with the |
|
| 1320 |
##' assumed truth (as specified by the \code{trueBiomarker} argument to
|
|
| 1321 |
##' \code{\link{summary,DualSimulations-method}})}
|
|
| 1322 |
##' } |
|
| 1323 |
##' You can specify any subset of these in the \code{type} argument.
|
|
| 1324 |
##' |
|
| 1325 |
##' @param x the \code{\linkS4class{DualSimulationsSummary}} object we want
|
|
| 1326 |
##' to plot from |
|
| 1327 |
##' @param y missing |
|
| 1328 |
##' @param type the types of plots you want to obtain. |
|
| 1329 |
##' @param \dots not used |
|
| 1330 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 1331 |
##' asked for, otherwise a `gtable` object. |
|
| 1332 |
##' |
|
| 1333 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
| 1334 |
##' scale_linetype_manual scale_colour_manual |
|
| 1335 |
##' @importFrom gridExtra arrangeGrob |
|
| 1336 |
##' |
|
| 1337 |
##' @example examples/Simulations-method-plot-DualSimulationsSummary.R |
|
| 1338 |
##' @export |
|
| 1339 |
##' @keywords methods |
|
| 1340 |
setMethod( |
|
| 1341 |
"plot", |
|
| 1342 |
signature = signature( |
|
| 1343 |
x = "DualSimulationsSummary", |
|
| 1344 |
y = "missing" |
|
| 1345 |
), |
|
| 1346 |
def = function( |
|
| 1347 |
x, |
|
| 1348 |
y, |
|
| 1349 |
type = c( |
|
| 1350 |
"nObs", |
|
| 1351 |
"doseSelected", |
|
| 1352 |
"propDLTs", |
|
| 1353 |
"nAboveTarget", |
|
| 1354 |
"meanFit", |
|
| 1355 |
"meanBiomarkerFit" |
|
| 1356 |
), |
|
| 1357 |
... |
|
| 1358 |
) {
|
|
| 1359 |
## which plots should be produced? |
|
| 1360 | 4x |
type <- match.arg(type, several.ok = TRUE) |
| 1361 | 4x |
stopifnot(length(type) > 0L) |
| 1362 | ||
| 1363 |
## substract the specific plot types for dual-endpoint |
|
| 1364 |
## designs |
|
| 1365 | 4x |
typeReduced <- setdiff( |
| 1366 | 4x |
type, |
| 1367 | 4x |
"meanBiomarkerFit" |
| 1368 |
) |
|
| 1369 | ||
| 1370 |
## are there more plots from general? |
|
| 1371 | 4x |
moreFromGeneral <- (length(typeReduced) > 0) |
| 1372 | ||
| 1373 |
## if so, then produce these plots |
|
| 1374 | 4x |
if (moreFromGeneral) {
|
| 1375 | 3x |
ret <- callNextMethod(x = x, y = y, type = typeReduced) |
| 1376 |
} |
|
| 1377 | ||
| 1378 |
## is the meanBiomarkerFit plot requested? |
|
| 1379 | 4x |
if ("meanBiomarkerFit" %in% type) {
|
| 1380 |
## which types of lines do we have? |
|
| 1381 | 2x |
linetype <- c( |
| 1382 | 2x |
"True biomarker", |
| 1383 | 2x |
"Average estimated biomarker", |
| 1384 | 2x |
"95% interval for estimated biomarker" |
| 1385 |
) |
|
| 1386 | ||
| 1387 |
## create the data frame, with |
|
| 1388 |
## true biomarker, average estimated biomarker, and 95% (lower, upper) |
|
| 1389 |
## estimated biomarker stacked below each other |
|
| 1390 | 2x |
dat <- data.frame( |
| 1391 | 2x |
dose = rep(x@dose_grid, 4L), |
| 1392 | 2x |
group = rep(1:4, each = length(x@dose_grid)), |
| 1393 | 2x |
linetype = factor( |
| 1394 | 2x |
rep(linetype[c(1, 2, 3, 3)], each = length(x@dose_grid)), |
| 1395 | 2x |
levels = linetype |
| 1396 |
), |
|
| 1397 | 2x |
lines = unlist(x@mean_biomarker_fit) |
| 1398 |
) |
|
| 1399 | ||
| 1400 |
## linetypes for the plot |
|
| 1401 | 2x |
lt <- c( |
| 1402 | 2x |
"True biomarker" = 1, |
| 1403 | 2x |
"Average estimated biomarker" = 1, |
| 1404 | 2x |
"95% interval for estimated biomarker" = 2 |
| 1405 |
) |
|
| 1406 | ||
| 1407 |
## colour for the plot |
|
| 1408 | 2x |
col <- c( |
| 1409 | 2x |
"True biomarker" = 1, |
| 1410 | 2x |
"Average estimated biomarker" = 2, |
| 1411 | 2x |
"95% interval for estimated biomarker" = 2 |
| 1412 |
) |
|
| 1413 | ||
| 1414 |
## now create and save the plot |
|
| 1415 | 2x |
thisPlot <- ggplot() + |
| 1416 | 2x |
geom_line( |
| 1417 | 2x |
aes( |
| 1418 | 2x |
x = dose, |
| 1419 | 2x |
y = lines, |
| 1420 | 2x |
group = group, |
| 1421 | 2x |
linetype = linetype, |
| 1422 | 2x |
col = linetype |
| 1423 |
), |
|
| 1424 | 2x |
data = dat |
| 1425 |
) |
|
| 1426 | ||
| 1427 | 2x |
thisPlot <- thisPlot + |
| 1428 | 2x |
scale_linetype_manual(values = lt) + |
| 1429 | 2x |
scale_colour_manual(values = col) + |
| 1430 | 2x |
xlab("Dose level") +
|
| 1431 | 2x |
ylab("Biomarker level")
|
| 1432 | ||
| 1433 |
## add this plot to the bottom |
|
| 1434 | 2x |
ret <- |
| 1435 | 2x |
if (moreFromGeneral) {
|
| 1436 | 1x |
gridExtra::arrangeGrob(ret, thisPlot, heights = c(2 / 3, 1 / 3)) |
| 1437 |
} else {
|
|
| 1438 | 1x |
thisPlot |
| 1439 |
} |
|
| 1440 |
} |
|
| 1441 | ||
| 1442 |
## then finally plot everything |
|
| 1443 | 4x |
ret |
| 1444 |
} |
|
| 1445 |
) |
|
| 1446 | ||
| 1447 | ||
| 1448 |
## -------------------------------------------------------------------------------------------------------- |
|
| 1449 |
##' Summarize the simulations, relative to a given truth |
|
| 1450 |
##' |
|
| 1451 |
##' @param object the \code{\linkS4class{PseudoSimulations}} object we want to
|
|
| 1452 |
##' summarize |
|
| 1453 |
##' @param truth a function which takes as input a dose (vector) and returns the |
|
| 1454 |
##' true probability (vector) for toxicity |
|
| 1455 |
##' @param targetEndOfTrial the target probability of DLE wanted to achieve at the end of a trial |
|
| 1456 |
##' @param targetDuringTrial the target probability of DLE wanted to achieve during a trial |
|
| 1457 |
##' |
|
| 1458 |
##' @param \dots Additional arguments can be supplied here for \code{truth}
|
|
| 1459 |
##' @return an object of class \code{\linkS4class{PseudoSimulationsSummary}}
|
|
| 1460 |
##' |
|
| 1461 |
##' @example examples/Simulations-method-summarySIMsingle.R |
|
| 1462 |
##' @export |
|
| 1463 |
##' @keywords methods |
|
| 1464 |
setMethod( |
|
| 1465 |
"summary", |
|
| 1466 |
signature = signature(object = "PseudoSimulations"), |
|
| 1467 |
def = function( |
|
| 1468 |
object, |
|
| 1469 |
truth, |
|
| 1470 |
targetEndOfTrial = 0.3, |
|
| 1471 |
targetDuringTrial = 0.35, |
|
| 1472 |
... |
|
| 1473 |
) {
|
|
| 1474 |
## extract dose grid |
|
| 1475 | 15x |
doseGrid <- object@data[[1]]@doseGrid |
| 1476 | ||
| 1477 |
## evaluate true DLE at doseGrid |
|
| 1478 | 15x |
trueDLE <- truth(doseGrid) |
| 1479 | ||
| 1480 |
## Inverse function of the truth function |
|
| 1481 | 15x |
inverse <- function(f, lower = -100, upper = 100) {
|
| 1482 | 15x |
function(y) {
|
| 1483 | 30x |
uniroot((function(x) f(x) - y), lower = lower, upper = upper)[1] |
| 1484 |
} |
|
| 1485 |
} |
|
| 1486 | ||
| 1487 |
## Function to obtain corresponsing dose level given target prob |
|
| 1488 | 15x |
TD <- inverse(truth, 0, max(doseGrid)) |
| 1489 | ||
| 1490 |
## Find the dose corresponding to the target dose during trial |
|
| 1491 | 15x |
targetDoseEndOfTrial <- as.numeric(TD(targetEndOfTrial)) |
| 1492 | ||
| 1493 |
## Find the dose corresponding to the target does end of trial |
|
| 1494 | 15x |
targetDoseDuringTrial <- as.numeric(TD(targetDuringTrial)) |
| 1495 | ||
| 1496 |
## Find the dose at doseGrid corresponding to the above two quantities |
|
| 1497 | 15x |
targetDoseEndOfTrialAtDoseGrid <- doseGrid[max(which( |
| 1498 | 15x |
targetDoseEndOfTrial - doseGrid >= 0 |
| 1499 |
))] |
|
| 1500 | 15x |
targetDoseDuringTrialAtDoseGrid <- doseGrid[max(which( |
| 1501 | 15x |
targetDoseDuringTrial - doseGrid >= 0 |
| 1502 |
))] |
|
| 1503 | ||
| 1504 |
## A summary for all TDtargetEndOfTrial dose obtained |
|
| 1505 | 15x |
TDEOTSummary <- summary(object@final_td_target_end_of_trial_estimates) |
| 1506 | ||
| 1507 | 15x |
FinalDoseRecSummary <- TDEOTSummary |
| 1508 | ||
| 1509 | 15x |
ratioTDEOTSummary <- summary(object@final_tdeot_ratios) |
| 1510 | 15x |
FinalRatioSummary <- ratioTDEOTSummary |
| 1511 | ||
| 1512 |
## A summary for all TDtargetDuringTrial dose obtained |
|
| 1513 | 15x |
TDDTSummary <- summary(object@final_td_target_during_trial_estimates) |
| 1514 |
## what are the levels above target End of Trial? |
|
| 1515 | 15x |
xAboveTargetEndOfTrial <- which(trueDLE > targetEndOfTrial) |
| 1516 | ||
| 1517 |
## what are the levels above target During Trial? |
|
| 1518 | 15x |
xAboveTargetDuringTrial <- which(trueDLE > targetDuringTrial) |
| 1519 | ||
| 1520 |
## proportion of DLEs in this trial |
|
| 1521 | 15x |
propDLE <- sapply( |
| 1522 | 15x |
object@data, |
| 1523 | 15x |
function(d) {
|
| 1524 | 16x |
mean(d@y) |
| 1525 |
} |
|
| 1526 |
) |
|
| 1527 |
### mean toxicity risk |
|
| 1528 | 15x |
meanToxRisk <- sapply( |
| 1529 | 15x |
object@data, |
| 1530 | 15x |
function(d) {
|
| 1531 | 16x |
mean(trueDLE[d@xLevel]) |
| 1532 |
} |
|
| 1533 |
) |
|
| 1534 | ||
| 1535 |
## doses selected for MTD |
|
| 1536 | 15x |
doseSelected <- object@doses |
| 1537 | ||
| 1538 |
## replace NA by 0 |
|
| 1539 | 15x |
doseSelected[is.na(doseSelected)] <- 0 |
| 1540 | ||
| 1541 |
## dose most often selected as MTD |
|
| 1542 | 15x |
doseMostSelected <- |
| 1543 | 15x |
as.numeric(names(which.max(table(doseSelected)))) |
| 1544 | ||
| 1545 |
# doseRec <- doseMostSelected |
|
| 1546 | ||
| 1547 | 15x |
xMostSelected <- |
| 1548 | 15x |
match_within_tolerance(doseMostSelected, table = doseGrid) |
| 1549 | ||
| 1550 |
## observed toxicity rate at dose most often selected |
|
| 1551 |
## Note: this does not seem very useful! |
|
| 1552 |
## Reason: In case of a fine grid, few patients if any |
|
| 1553 |
## will have been treated at this dose. |
|
| 1554 | 15x |
tmp <- |
| 1555 | 15x |
sapply( |
| 1556 | 15x |
object@data, |
| 1557 | 15x |
function(d) {
|
| 1558 | 16x |
whichAtThisDose <- which(d@x == doseMostSelected) |
| 1559 | 16x |
nAtThisDose <- length(whichAtThisDose) |
| 1560 | 16x |
nDLTatThisDose <- sum(d@y[whichAtThisDose]) |
| 1561 | 16x |
return(c( |
| 1562 | 16x |
nAtThisDose = nAtThisDose, |
| 1563 | 16x |
nDLTatThisDose = nDLTatThisDose |
| 1564 |
)) |
|
| 1565 |
} |
|
| 1566 |
) |
|
| 1567 | ||
| 1568 | 15x |
obsToxRateAtDoseMostSelected <- |
| 1569 | 15x |
mean(tmp["nDLTatThisDose", ]) / mean(tmp["nAtThisDose", ]) |
| 1570 | ||
| 1571 |
## number of patients overall |
|
| 1572 | 15x |
nObs <- sapply( |
| 1573 | 15x |
object@data, |
| 1574 | 15x |
slot, |
| 1575 | 15x |
"nObs" |
| 1576 |
) |
|
| 1577 | ||
| 1578 |
## number of patients treated above target End of trial |
|
| 1579 | 15x |
nAboveTargetEndOfTrial <- sapply( |
| 1580 | 15x |
object@data, |
| 1581 | 15x |
function(d) {
|
| 1582 | 16x |
sum(d@xLevel %in% xAboveTargetEndOfTrial) |
| 1583 |
} |
|
| 1584 |
) |
|
| 1585 | ||
| 1586 |
## number of patients treated above target During trial |
|
| 1587 | 15x |
nAboveTargetDuringTrial <- sapply( |
| 1588 | 15x |
object@data, |
| 1589 | 15x |
function(d) {
|
| 1590 | 16x |
sum(d@xLevel %in% xAboveTargetDuringTrial) |
| 1591 |
} |
|
| 1592 |
) |
|
| 1593 | ||
| 1594 | 15x |
toxAtDoses <- truth(doseSelected) |
| 1595 | ||
| 1596 |
## Proportion of trials selecting target TDEndOfTrial and TDDuringTrial |
|
| 1597 | 15x |
nsim <- length(object@data) |
| 1598 | ||
| 1599 | 15x |
propAtTargetEndOfTrial <- (length(which( |
| 1600 | 15x |
object@doses == targetDoseEndOfTrialAtDoseGrid |
| 1601 |
))) / |
|
| 1602 | 15x |
nsim |
| 1603 | 15x |
propAtTargetDuringTrial <- (length(which( |
| 1604 | 15x |
object@doses == targetDoseDuringTrialAtDoseGrid |
| 1605 |
))) / |
|
| 1606 | 15x |
nsim |
| 1607 | ||
| 1608 | 15x |
RecDoseSummary <- TDEOTSummary |
| 1609 | ||
| 1610 |
## fitted probDLE at dose most often selected |
|
| 1611 |
## find names in the fit list (check it is with or without samples) |
|
| 1612 | 15x |
FitNames <- sapply(object@fit, names) |
| 1613 | ||
| 1614 | 15x |
if ("probDLE" %in% FitNames) {
|
| 1615 | 8x |
fitAtDoseMostSelected <- sapply( |
| 1616 | 8x |
object@fit, |
| 1617 | 8x |
function(f) {
|
| 1618 | 9x |
f$probDLE[xMostSelected] |
| 1619 |
} |
|
| 1620 |
) |
|
| 1621 | 8x |
meanFitMatrix <- sapply( |
| 1622 | 8x |
object@fit, |
| 1623 |
"[[", |
|
| 1624 | 8x |
"probDLE" |
| 1625 |
) |
|
| 1626 | ||
| 1627 | 8x |
meanFit <- list( |
| 1628 | 8x |
truth = truth(doseGrid), |
| 1629 | 8x |
average = rowMeans(meanFitMatrix) |
| 1630 |
) |
|
| 1631 |
} else {
|
|
| 1632 |
## fitted toxicity rate at dose most often selected |
|
| 1633 | 7x |
fitAtDoseMostSelected <- |
| 1634 | 7x |
sapply( |
| 1635 | 7x |
object@fit, |
| 1636 | 7x |
function(f) {
|
| 1637 | 7x |
f$middle[xMostSelected] |
| 1638 |
} |
|
| 1639 |
) |
|
| 1640 | ||
| 1641 |
## mean fitted toxicity (average, lower and upper quantiles) |
|
| 1642 |
## at each dose level |
|
| 1643 |
## (this is required for plotting) |
|
| 1644 | 7x |
meanFitMatrix <- sapply( |
| 1645 | 7x |
object@fit, |
| 1646 |
"[[", |
|
| 1647 | 7x |
"middle" |
| 1648 |
) |
|
| 1649 | 7x |
meanFit <- list( |
| 1650 | 7x |
truth = truth(doseGrid), |
| 1651 | 7x |
average = rowMeans(meanFitMatrix), |
| 1652 | 7x |
lower = apply( |
| 1653 | 7x |
meanFitMatrix, |
| 1654 | 7x |
1L, |
| 1655 | 7x |
quantile, |
| 1656 | 7x |
0.025 |
| 1657 |
), |
|
| 1658 | 7x |
upper = apply( |
| 1659 | 7x |
meanFitMatrix, |
| 1660 | 7x |
1L, |
| 1661 | 7x |
quantile, |
| 1662 | 7x |
0.975 |
| 1663 |
) |
|
| 1664 |
) |
|
| 1665 |
} |
|
| 1666 | ||
| 1667 |
## give back an object of class GeneralSimulationsSummary, |
|
| 1668 |
## for which we then define a print / plot method |
|
| 1669 | 15x |
ret <- .PseudoSimulationsSummary( |
| 1670 | 15x |
target_end_of_trial = targetEndOfTrial, |
| 1671 | 15x |
target_dose_end_of_trial = targetDoseEndOfTrial, |
| 1672 | 15x |
target_during_trial = targetDuringTrial, |
| 1673 | 15x |
target_dose_during_trial = targetDoseDuringTrial, |
| 1674 | 15x |
target_dose_end_of_trial_at_dose_grid = targetDoseEndOfTrialAtDoseGrid, |
| 1675 | 15x |
target_dose_during_trial_at_dose_grid = targetDoseDuringTrialAtDoseGrid, |
| 1676 | 15x |
tdeot_summary = TDEOTSummary, |
| 1677 | 15x |
tddt_summary = TDDTSummary, |
| 1678 | 15x |
final_dose_rec_summary = FinalDoseRecSummary, |
| 1679 | 15x |
ratio_tdeot_summary = ratioTDEOTSummary, |
| 1680 | 15x |
final_ratio_summary = FinalRatioSummary, |
| 1681 | 15x |
nsim = length(object@data), |
| 1682 | 15x |
prop_dle = propDLE, |
| 1683 | 15x |
mean_tox_risk = meanToxRisk, |
| 1684 | 15x |
dose_selected = doseSelected, |
| 1685 | 15x |
dose_most_selected = doseMostSelected, |
| 1686 |
# doseRec=doseRec, |
|
| 1687 | 15x |
obs_tox_rate_at_dose_most_selected = obsToxRateAtDoseMostSelected, |
| 1688 | 15x |
n_obs = nObs, |
| 1689 | 15x |
n_above_target_end_of_trial = nAboveTargetEndOfTrial, |
| 1690 | 15x |
n_above_target_during_trial = nAboveTargetDuringTrial, |
| 1691 | 15x |
tox_at_doses_selected = toxAtDoses, |
| 1692 | 15x |
prop_at_target_end_of_trial = propAtTargetEndOfTrial, |
| 1693 | 15x |
prop_at_target_during_trial = propAtTargetDuringTrial, |
| 1694 | 15x |
dose_grid = doseGrid, |
| 1695 | 15x |
fit_at_dose_most_selected = fitAtDoseMostSelected, |
| 1696 | 15x |
stop_report = object@stop_report, |
| 1697 | 15x |
mean_fit = meanFit |
| 1698 |
) |
|
| 1699 | ||
| 1700 | 15x |
return(ret) |
| 1701 |
} |
|
| 1702 |
) |
|
| 1703 |
## ======================================================================================================== |
|
| 1704 |
##' Show the summary of the simulations |
|
| 1705 |
##' |
|
| 1706 |
##' @param object the \code{\linkS4class{PseudoSimulationsSummary}} object we want
|
|
| 1707 |
##' to print |
|
| 1708 |
##' @return invisibly returns a data frame of the results with one row and |
|
| 1709 |
##' appropriate column names |
|
| 1710 |
##' |
|
| 1711 |
##' @example examples/Simulations-method-showSIMsingle.R |
|
| 1712 |
##' @export |
|
| 1713 |
##' @keywords methods |
|
| 1714 | ||
| 1715 |
setMethod( |
|
| 1716 |
"show", |
|
| 1717 |
signature = signature(object = "PseudoSimulationsSummary"), |
|
| 1718 |
def = function(object) {
|
|
| 1719 | 15x |
r <- Report$new( |
| 1720 | 15x |
object = object, |
| 1721 | 15x |
df = as.data.frame(matrix( |
| 1722 | 15x |
nrow = 1, |
| 1723 | 15x |
ncol = 0 |
| 1724 |
)), |
|
| 1725 | 15x |
dfNames = character() |
| 1726 |
) |
|
| 1727 | 15x |
cat( |
| 1728 | 15x |
"Summary of", |
| 1729 | 15x |
r$dfSave(object@nsim, "nsim"), |
| 1730 | 15x |
"simulations\n\n" |
| 1731 |
) |
|
| 1732 | ||
| 1733 | 15x |
cat( |
| 1734 | 15x |
"Target probability of DLE p(DLE) used at the end of a trial was", |
| 1735 | 15x |
r$dfSave( |
| 1736 | 15x |
object@target_end_of_trial * 100, |
| 1737 | 15x |
"target_end_of_trial" |
| 1738 |
), |
|
| 1739 | 15x |
"%\n" |
| 1740 |
) |
|
| 1741 | ||
| 1742 | 15x |
cat( |
| 1743 | 15x |
"The dose level corresponds to the target p(DLE) used at the end of a trial, TDEOT, was", |
| 1744 | 15x |
r$dfSave( |
| 1745 | 15x |
object@target_dose_end_of_trial, |
| 1746 | 15x |
"target_dose_end_of_trial" |
| 1747 |
), |
|
| 1748 | 15x |
"\n" |
| 1749 |
) |
|
| 1750 | 15x |
cat( |
| 1751 | 15x |
"TDEOT at dose Grid was", |
| 1752 | 15x |
r$dfSave( |
| 1753 | 15x |
object@target_dose_end_of_trial_at_dose_grid, |
| 1754 | 15x |
"target_dose_end_of_trial_at_dose_grid" |
| 1755 |
), |
|
| 1756 | 15x |
"\n" |
| 1757 |
) |
|
| 1758 | ||
| 1759 | 15x |
cat( |
| 1760 | 15x |
"Target p(DLE) used during a trial was", |
| 1761 | 15x |
r$dfSave( |
| 1762 | 15x |
object@target_during_trial * 100, |
| 1763 | 15x |
"target_during_trial" |
| 1764 |
), |
|
| 1765 | 15x |
"%\n" |
| 1766 |
) |
|
| 1767 | ||
| 1768 | 15x |
cat( |
| 1769 | 15x |
"The dose level corresponds to the target p(DLE) used during a trial, TDDT, was", |
| 1770 | 15x |
r$dfSave( |
| 1771 | 15x |
object@target_dose_during_trial, |
| 1772 | 15x |
"target_dose_during_trial" |
| 1773 |
), |
|
| 1774 | 15x |
"\n" |
| 1775 |
) |
|
| 1776 | ||
| 1777 | 15x |
cat( |
| 1778 | 15x |
"TDDT at dose Grid was", |
| 1779 | 15x |
r$dfSave( |
| 1780 | 15x |
object@target_dose_during_trial_at_dose_grid, |
| 1781 | 15x |
"target_dose_during_trial_at_dose_grid" |
| 1782 |
), |
|
| 1783 | 15x |
"\n" |
| 1784 |
) |
|
| 1785 | ||
| 1786 | 15x |
r$report("n_obs", "Number of patients overall", percent = FALSE)
|
| 1787 | 15x |
r$report( |
| 1788 | 15x |
"n_above_target_end_of_trial", |
| 1789 | 15x |
"Number of patients treated above the target p(DLE) used at the end of a trial", |
| 1790 | 15x |
percent = FALSE |
| 1791 |
) |
|
| 1792 | ||
| 1793 | 15x |
r$report( |
| 1794 | 15x |
"n_above_target_during_trial", |
| 1795 | 15x |
"Number of patients treated above the target p(DLE) used during a trial", |
| 1796 | 15x |
percent = FALSE |
| 1797 |
) |
|
| 1798 | ||
| 1799 | 15x |
r$report( |
| 1800 | 15x |
"prop_dle", |
| 1801 | 15x |
"Proportions of observed DLT in the trials" |
| 1802 |
) |
|
| 1803 | 15x |
r$report( |
| 1804 | 15x |
"mean_tox_risk", |
| 1805 | 15x |
"Mean toxicity risks for the patients" |
| 1806 |
) |
|
| 1807 | 15x |
r$report( |
| 1808 | 15x |
"dose_selected", |
| 1809 | 15x |
"Doses selected as TDEOT", |
| 1810 | 15x |
percent = FALSE, |
| 1811 | 15x |
digits = 1 |
| 1812 |
) |
|
| 1813 |
# r$report("doseRec",
|
|
| 1814 |
# "Doses to recommend to subsequent study", |
|
| 1815 |
# percent=FALSE, digits=1) |
|
| 1816 | ||
| 1817 | 15x |
r$report( |
| 1818 | 15x |
"tox_at_doses_selected", |
| 1819 | 15x |
"True toxicity at TDEOT" |
| 1820 |
) |
|
| 1821 | ||
| 1822 | 15x |
cat( |
| 1823 | 15x |
"Proportion of trials selecting the TDEOT:", |
| 1824 | 15x |
r$dfSave( |
| 1825 | 15x |
object@prop_at_target_end_of_trial * 100, |
| 1826 | 15x |
"percentAtTarget" |
| 1827 |
), |
|
| 1828 | 15x |
"%\n" |
| 1829 |
) |
|
| 1830 | ||
| 1831 | 15x |
cat( |
| 1832 | 15x |
"Proportion of trials selecting the TDDT:", |
| 1833 | 15x |
r$dfSave( |
| 1834 | 15x |
object@prop_at_target_during_trial * 100, |
| 1835 | 15x |
"percentAtTarget" |
| 1836 |
), |
|
| 1837 | 15x |
"%\n" |
| 1838 |
) |
|
| 1839 | ||
| 1840 | 15x |
cat( |
| 1841 | 15x |
"Dose most often selected as TDEOT:", |
| 1842 | 15x |
r$dfSave( |
| 1843 | 15x |
object@dose_most_selected, |
| 1844 | 15x |
"doseMostSelected" |
| 1845 |
), |
|
| 1846 | 15x |
"\n" |
| 1847 |
) |
|
| 1848 | 15x |
cat( |
| 1849 | 15x |
"Observed toxicity rate at dose most often selected:", |
| 1850 | 15x |
r$dfSave( |
| 1851 | 15x |
round(object@obs_tox_rate_at_dose_most_selected * 100), |
| 1852 | 15x |
"obsToxRateAtDoseMostSelected" |
| 1853 |
), |
|
| 1854 | 15x |
"%\n" |
| 1855 |
) |
|
| 1856 | 15x |
r$report( |
| 1857 | 15x |
"fit_at_dose_most_selected", |
| 1858 | 15x |
"Fitted probabilities of DLE at dose most often selected" |
| 1859 |
) |
|
| 1860 | ||
| 1861 | 15x |
TDEOTSum <- object@tdeot_summary |
| 1862 | ||
| 1863 | 15x |
r$dfSave(as.numeric(TDEOTSum[1]), "TDEOTMin") |
| 1864 | 15x |
r$dfSave(as.numeric(TDEOTSum[2]), "TDEOTlower") |
| 1865 | 15x |
r$dfSave(as.numeric(TDEOTSum[3]), "TDEOTMedian") |
| 1866 | 15x |
r$dfSave(as.numeric(TDEOTSum[4]), "TDEOTMean") |
| 1867 | 15x |
r$dfSave(as.numeric(TDEOTSum[5]), "TDEOTUpper") |
| 1868 | 15x |
r$dfSave(as.numeric(TDEOTSum[6]), "TDEOTMax") |
| 1869 | ||
| 1870 | 15x |
cat( |
| 1871 | 15x |
"The summary table of the final TDEOT across all simulations\n", |
| 1872 | 15x |
capture.output(TDEOTSum)[1], |
| 1873 | 15x |
"\n", |
| 1874 | 15x |
capture.output(TDEOTSum)[2], |
| 1875 | 15x |
"\n" |
| 1876 |
) |
|
| 1877 | ||
| 1878 | 15x |
ratioTDEOTSum <- object@ratio_tdeot_summary |
| 1879 | ||
| 1880 | 15x |
r$dfSave(as.numeric(ratioTDEOTSum[1]), "ratioTDEOTMin") |
| 1881 | 15x |
r$dfSave(as.numeric(ratioTDEOTSum[2]), "ratioTDEOTlower") |
| 1882 | 15x |
r$dfSave(as.numeric(ratioTDEOTSum[3]), "ratioTDEOTMedian") |
| 1883 | 15x |
r$dfSave(as.numeric(ratioTDEOTSum[4]), "ratioTDEOTMean") |
| 1884 | 15x |
r$dfSave(as.numeric(ratioTDEOTSum[5]), "ratioTDEOTUpper") |
| 1885 | 15x |
r$dfSave(as.numeric(ratioTDEOTSum[6]), "ratioTDEOTMax") |
| 1886 | ||
| 1887 | 15x |
cat( |
| 1888 | 15x |
"The summary table of the final ratios of the TDEOT across all simulations\n", |
| 1889 | 15x |
capture.output(ratioTDEOTSum)[1], |
| 1890 | 15x |
"\n", |
| 1891 | 15x |
capture.output(ratioTDEOTSum)[2], |
| 1892 | 15x |
"\n" |
| 1893 |
) |
|
| 1894 | ||
| 1895 | 15x |
TDDTSum <- object@tddt_summary |
| 1896 | ||
| 1897 | 15x |
r$dfSave(as.numeric(TDDTSum[1]), "TDDTMin") |
| 1898 | 15x |
r$dfSave(as.numeric(TDDTSum[2]), "TDDTlower") |
| 1899 | 15x |
r$dfSave(as.numeric(TDDTSum[3]), "TDDTMedian") |
| 1900 | 15x |
r$dfSave(as.numeric(TDDTSum[4]), "TDDTMean") |
| 1901 | 15x |
r$dfSave(as.numeric(TDDTSum[5]), "TDDTUpper") |
| 1902 | 15x |
r$dfSave(as.numeric(TDDTSum[6]), "TDDTMax") |
| 1903 | ||
| 1904 | 15x |
cat( |
| 1905 | 15x |
"The summary table of the final TDDT across all simulations\n", |
| 1906 | 15x |
capture.output(TDDTSum)[1], |
| 1907 | 15x |
"\n", |
| 1908 | 15x |
capture.output(TDDTSum)[2], |
| 1909 | 15x |
"\n" |
| 1910 |
) |
|
| 1911 | ||
| 1912 | 15x |
FinalDoseRecSum <- object@final_dose_rec_summary |
| 1913 | ||
| 1914 | 15x |
r$dfSave(as.numeric(FinalDoseRecSum[1]), "FinalDoseRecMin") |
| 1915 | 15x |
r$dfSave(as.numeric(FinalDoseRecSum[2]), "FinalDoseReclower") |
| 1916 | 15x |
r$dfSave(as.numeric(FinalDoseRecSum[3]), "FinalDoseRecMedian") |
| 1917 | 15x |
r$dfSave(as.numeric(FinalDoseRecSum[4]), "FinalDoseRecMean") |
| 1918 | 15x |
r$dfSave(as.numeric(FinalDoseRecSum[5]), "FinalDoseRecUpper") |
| 1919 | 15x |
r$dfSave(as.numeric(FinalDoseRecSum[6]), "FinalDoseRecMax") |
| 1920 | ||
| 1921 | 15x |
cat( |
| 1922 | 15x |
"The summary table of dose levels, the optimal dose\n to recommend for subsequent study across all simulations\n", |
| 1923 | 15x |
capture.output(FinalDoseRecSum)[1], |
| 1924 | 15x |
"\n", |
| 1925 | 15x |
capture.output(FinalDoseRecSum)[2], |
| 1926 | 15x |
"\n" |
| 1927 |
) |
|
| 1928 | ||
| 1929 | 15x |
FinalratioSum <- object@final_ratio_summary |
| 1930 | ||
| 1931 | 15x |
r$dfSave(as.numeric(FinalratioSum[1]), "FinalratioMin") |
| 1932 | 15x |
r$dfSave(as.numeric(FinalratioSum[2]), "Finalratiolower") |
| 1933 | 15x |
r$dfSave(as.numeric(FinalratioSum[3]), "FinalratioMedian") |
| 1934 | 15x |
r$dfSave(as.numeric(FinalratioSum[4]), "FinalratioMean") |
| 1935 | 15x |
r$dfSave(as.numeric(FinalratioSum[5]), "FinalratioUpper") |
| 1936 | 15x |
r$dfSave(as.numeric(FinalratioSum[6]), "FinalratioMax") |
| 1937 | ||
| 1938 | 15x |
cat( |
| 1939 | 15x |
"The summary table of the final ratios of the optimal dose for stopping across |
| 1940 | 15x |
all simulations\n", |
| 1941 | 15x |
capture.output(FinalratioSum)[1], |
| 1942 | 15x |
"\n", |
| 1943 | 15x |
capture.output(FinalratioSum)[2], |
| 1944 | 15x |
"\n\n" |
| 1945 |
) |
|
| 1946 | ||
| 1947 |
# Report individual stopping rules with non-<NA> labels. |
|
| 1948 | ||
| 1949 | 15x |
stop_pct_to_print <- h_calc_report_label_percentage(object@stop_report) |
| 1950 | ||
| 1951 | 15x |
if (length(stop_pct_to_print) > 0) {
|
| 1952 | 15x |
cat( |
| 1953 | 15x |
"Stop reason triggered:\n", |
| 1954 | 15x |
paste(names(stop_pct_to_print), ": ", stop_pct_to_print, "%\n") |
| 1955 |
) |
|
| 1956 |
} |
|
| 1957 | ||
| 1958 |
## finally assign names to the df |
|
| 1959 |
## and return it invisibly |
|
| 1960 | 15x |
names(r$df) <- r$dfNames |
| 1961 | 15x |
invisible(r$df) |
| 1962 |
} |
|
| 1963 |
) |
|
| 1964 |
## ------------------------------------------------------------------------------------------- |
|
| 1965 |
##' Plot summaries of the pseudo simulations |
|
| 1966 |
##' |
|
| 1967 |
##' Graphical display of the simulation summary |
|
| 1968 |
##' |
|
| 1969 |
##' This plot method can be applied to \code{\linkS4class{PseudoSimulationsSummary}}
|
|
| 1970 |
##' objects in order to summarize them graphically. This can be used when only DLE responses are involved |
|
| 1971 |
##' in the simulations. This also applied to results with or without samples generated during the simulations |
|
| 1972 |
##' |
|
| 1973 |
##' @param x the \code{\linkS4class{PseudoSimulationsSummary}} object we want
|
|
| 1974 |
##' to plot from |
|
| 1975 |
##' @param y missing |
|
| 1976 |
##' @param type the types of plots you want to obtain. |
|
| 1977 |
##' @param \dots not used |
|
| 1978 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 1979 |
##' asked for, otherwise a `gtable` object. |
|
| 1980 |
##' |
|
| 1981 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
| 1982 |
##' scale_linetype_manual scale_colour_manual |
|
| 1983 |
##' @importFrom gridExtra arrangeGrob |
|
| 1984 |
##' |
|
| 1985 |
##' @example examples/Simulations-method-plotSUMsingle.R |
|
| 1986 |
##' @export |
|
| 1987 |
##' @keywords methods |
|
| 1988 |
##' |
|
| 1989 | ||
| 1990 |
setMethod( |
|
| 1991 |
"plot", |
|
| 1992 |
signature = signature( |
|
| 1993 |
x = "PseudoSimulationsSummary", |
|
| 1994 |
y = "missing" |
|
| 1995 |
), |
|
| 1996 |
def = function( |
|
| 1997 |
x, |
|
| 1998 |
y, |
|
| 1999 |
type = c( |
|
| 2000 |
"nObs", |
|
| 2001 |
"doseSelected", |
|
| 2002 |
"propDLE", |
|
| 2003 |
"nAboveTargetEndOfTrial", |
|
| 2004 |
"meanFit" |
|
| 2005 |
), |
|
| 2006 |
... |
|
| 2007 |
) {
|
|
| 2008 |
## which plots should be produced? |
|
| 2009 | 13x |
type <- match.arg(type, several.ok = TRUE) |
| 2010 | 11x |
stopifnot(length(type) > 0L) |
| 2011 | ||
| 2012 |
## start the plot list |
|
| 2013 | 11x |
plotList <- list() |
| 2014 | 11x |
plotIndex <- 0L |
| 2015 | ||
| 2016 |
## distribution of overall sample size |
|
| 2017 | 11x |
if ("nObs" %in% type) {
|
| 2018 | 7x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 2019 | 7x |
h_barplot_percentages( |
| 2020 | 7x |
x = x@n_obs, |
| 2021 | 7x |
description = "Number of patients in total" |
| 2022 |
) |
|
| 2023 |
} |
|
| 2024 | ||
| 2025 |
## distribution of final MTD estimate |
|
| 2026 | 11x |
if ("doseSelected" %in% type) {
|
| 2027 | 6x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 2028 | 6x |
h_barplot_percentages( |
| 2029 | 6x |
x = x@dose_selected, |
| 2030 | 6x |
description = "MTD estimate" |
| 2031 |
) |
|
| 2032 |
} |
|
| 2033 | ||
| 2034 |
## distribution of proportion of DLTs |
|
| 2035 | 11x |
if ("propDLE" %in% type) {
|
| 2036 | 5x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 2037 | 5x |
h_barplot_percentages( |
| 2038 | 5x |
x = x@prop_dle * 100, |
| 2039 | 5x |
description = "Proportion of DLE [%]", |
| 2040 | 5x |
xaxisround = 1 |
| 2041 |
) |
|
| 2042 |
} |
|
| 2043 | ||
| 2044 |
## distribution of number of patients treated at too much tox |
|
| 2045 | 11x |
if ("nAboveTargetEndOfTrial" %in% type) {
|
| 2046 | 5x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 2047 | 5x |
h_barplot_percentages( |
| 2048 | 5x |
x = x@n_above_target_end_of_trial, |
| 2049 | 5x |
description = "Number of patients above target" |
| 2050 |
) |
|
| 2051 |
} |
|
| 2052 | ||
| 2053 |
## first combine these small plots |
|
| 2054 | 11x |
if (length(plotList)) {
|
| 2055 | 10x |
ret <- |
| 2056 |
## if there is only one plot |
|
| 2057 | 10x |
if ( |
| 2058 | 10x |
identical( |
| 2059 | 10x |
length(plotList), |
| 2060 | 10x |
1L |
| 2061 |
) |
|
| 2062 |
) {
|
|
| 2063 |
## just use that |
|
| 2064 | 5x |
plotList[[1L]] |
| 2065 |
} else {
|
|
| 2066 |
## multiple plots in this case |
|
| 2067 | 5x |
do.call( |
| 2068 | 5x |
gridExtra::arrangeGrob, |
| 2069 | 5x |
plotList |
| 2070 |
) |
|
| 2071 |
} |
|
| 2072 |
} |
|
| 2073 | ||
| 2074 |
## the meanFit plot |
|
| 2075 | ||
| 2076 | 11x |
if ("meanFit" %in% type) {
|
| 2077 |
## Find if DLE samples are generated in the simulations |
|
| 2078 |
## by checking if there the lower limits of the 95% Credibility |
|
| 2079 |
## interval are calculated |
|
| 2080 | 5x |
if (!is.null(x@mean_fit$lower)) {
|
| 2081 |
## which types of lines do we have? |
|
| 2082 | 3x |
linetype <- c( |
| 2083 | 3x |
"True toxicity", |
| 2084 | 3x |
"Average estimated toxicity", |
| 2085 | 3x |
"95% interval for estimated toxicity" |
| 2086 |
) |
|
| 2087 |
## create the data frame, with |
|
| 2088 |
## true tox, average estimated tox, and 95% (lower, upper) |
|
| 2089 |
## estimated tox (in percentage) stacked below each other |
|
| 2090 | 3x |
dat <- data.frame( |
| 2091 | 3x |
dose = rep(x@dose_grid, 4L), |
| 2092 | 3x |
group = rep(1:4, each = length(x@dose_grid)), |
| 2093 | 3x |
linetype = factor( |
| 2094 | 3x |
rep(linetype[c(1, 2, 3, 3)], each = length(x@dose_grid)), |
| 2095 | 3x |
levels = linetype |
| 2096 |
), |
|
| 2097 | 3x |
lines = unlist(x@mean_fit) * 100 |
| 2098 |
) |
|
| 2099 | ||
| 2100 |
## linetypes for the plot |
|
| 2101 | 3x |
lt <- c( |
| 2102 | 3x |
"True toxicity" = 1, |
| 2103 | 3x |
"Average estimated toxicity" = 1, |
| 2104 | 3x |
"95% interval for estimated toxicity" = 2 |
| 2105 |
) |
|
| 2106 | ||
| 2107 |
## colour for the plot |
|
| 2108 | 3x |
col <- c( |
| 2109 | 3x |
"True toxicity" = 1, |
| 2110 | 3x |
"Average estimated toxicity" = 2, |
| 2111 | 3x |
"95% interval for estimated toxicity" = 2 |
| 2112 |
) |
|
| 2113 | ||
| 2114 |
## now create and save the plot |
|
| 2115 | 3x |
thisPlot <- ggplot() + |
| 2116 | 3x |
geom_line( |
| 2117 | 3x |
aes( |
| 2118 | 3x |
x = dose, |
| 2119 | 3x |
y = lines, |
| 2120 | 3x |
group = group, |
| 2121 | 3x |
linetype = linetype, |
| 2122 | 3x |
col = linetype |
| 2123 |
), |
|
| 2124 | 3x |
data = dat |
| 2125 |
) |
|
| 2126 | ||
| 2127 | 3x |
thisPlot <- thisPlot + |
| 2128 | 3x |
scale_linetype_manual(values = lt) + |
| 2129 | 3x |
scale_colour_manual(values = col) + |
| 2130 | 3x |
xlab("Dose level") +
|
| 2131 | 3x |
ylab("Probability of DLE [%]")
|
| 2132 |
} else {
|
|
| 2133 |
## which types of lines do we have? |
|
| 2134 | 2x |
linetype <- c( |
| 2135 | 2x |
"True toxicity", |
| 2136 | 2x |
"Average estimated toxicity" |
| 2137 |
) |
|
| 2138 | ||
| 2139 |
## create the data frame, with |
|
| 2140 |
## true tox, average estimated tox |
|
| 2141 |
## estimated tox (in percentage) stacked below each other |
|
| 2142 | 2x |
dat <- data.frame( |
| 2143 | 2x |
dose = rep(x@dose_grid, 2L), |
| 2144 | 2x |
group = rep(1:2, each = length(x@dose_grid)), |
| 2145 | 2x |
linetype = factor( |
| 2146 | 2x |
rep(linetype[c(1, 2)], each = length(x@dose_grid)), |
| 2147 | 2x |
levels = linetype |
| 2148 |
), |
|
| 2149 | 2x |
lines = unlist(x@mean_fit) * 100 |
| 2150 |
) |
|
| 2151 | ||
| 2152 |
## linetypes for the plot |
|
| 2153 | 2x |
lt <- c( |
| 2154 | 2x |
"True toxicity" = 1, |
| 2155 | 2x |
"Average estimated toxicity" = 1 |
| 2156 |
) |
|
| 2157 | ||
| 2158 |
## colour for the plot |
|
| 2159 | 2x |
col <- c( |
| 2160 | 2x |
"True toxicity" = 1, |
| 2161 | 2x |
"Average estimated toxicity" = 2 |
| 2162 |
) |
|
| 2163 | ||
| 2164 |
## now create and save the plot |
|
| 2165 | 2x |
thisPlot <- ggplot() + |
| 2166 | 2x |
geom_line( |
| 2167 | 2x |
aes( |
| 2168 | 2x |
x = dose, |
| 2169 | 2x |
y = lines, |
| 2170 | 2x |
group = group, |
| 2171 | 2x |
linetype = linetype, |
| 2172 | 2x |
col = linetype |
| 2173 |
), |
|
| 2174 | 2x |
data = dat |
| 2175 |
) |
|
| 2176 | ||
| 2177 | 2x |
thisPlot <- thisPlot + |
| 2178 | 2x |
scale_linetype_manual(values = lt) + |
| 2179 | 2x |
scale_colour_manual(values = col) + |
| 2180 | 2x |
xlab("Dose level") +
|
| 2181 | 2x |
ylab("Probability of DLE [%]")
|
| 2182 |
} |
|
| 2183 | ||
| 2184 |
## then add this plot at the bottom |
|
| 2185 | 5x |
ret <- if (exists("ret")) {
|
| 2186 | 4x |
gridExtra::arrangeGrob(ret, thisPlot) |
| 2187 |
} else {
|
|
| 2188 | 1x |
thisPlot |
| 2189 |
} |
|
| 2190 |
} |
|
| 2191 | 11x |
ret |
| 2192 |
} |
|
| 2193 |
) |
|
| 2194 |
## -------------------------------------------------------------------------------------- |
|
| 2195 |
##' Plot simulations |
|
| 2196 |
##' |
|
| 2197 |
##' Summarize the simulations with plots |
|
| 2198 |
##' |
|
| 2199 |
##' This plot method can be applied to \code{\linkS4class{PseudoDualSimulations}}
|
|
| 2200 |
##' objects in order to summarize them graphically. Possible \code{type}s of
|
|
| 2201 |
##' plots at the moment are: \describe{ \item{trajectory}{Summary of the
|
|
| 2202 |
##' trajectory of the simulated trials} \item{dosesTried}{Average proportions of
|
|
| 2203 |
##' the doses tested in patients} \item{sigma2}{The variance of the efficacy responses}}
|
|
| 2204 |
##' You can specify one or both of these in the |
|
| 2205 |
##' \code{type} argument.
|
|
| 2206 |
##' |
|
| 2207 |
##' @param x the \code{\linkS4class{PseudoDualSimulations}} object we want
|
|
| 2208 |
##' to plot from |
|
| 2209 |
##' @param y missing |
|
| 2210 |
##' @param type the type of plots you want to obtain. |
|
| 2211 |
##' @param \dots not used |
|
| 2212 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 2213 |
##' asked for, otherwise a `gtable` object. |
|
| 2214 |
##' |
|
| 2215 |
##' @importFrom ggplot2 ggplot geom_step geom_bar aes xlab ylab |
|
| 2216 |
##' scale_linetype_manual |
|
| 2217 |
##' @importFrom gridExtra arrangeGrob |
|
| 2218 |
##' |
|
| 2219 |
##' @example examples/Simulations-method-plotSIMDual.R |
|
| 2220 |
##' @export |
|
| 2221 |
##' @keywords methods |
|
| 2222 |
setMethod( |
|
| 2223 |
"plot", |
|
| 2224 |
signature = signature( |
|
| 2225 |
x = "PseudoDualSimulations", |
|
| 2226 |
y = "missing" |
|
| 2227 |
), |
|
| 2228 |
def = function( |
|
| 2229 |
x, |
|
| 2230 |
y, |
|
| 2231 |
type = c( |
|
| 2232 |
"trajectory", |
|
| 2233 |
"dosesTried", |
|
| 2234 |
"sigma2" |
|
| 2235 |
), |
|
| 2236 |
... |
|
| 2237 |
) {
|
|
| 2238 |
## start the plot list |
|
| 2239 | 12x |
plotList <- list() |
| 2240 | 12x |
plotIndex <- 0L |
| 2241 | ||
| 2242 |
## which plots should be produced? |
|
| 2243 | 12x |
type <- match.arg(type, several.ok = TRUE) |
| 2244 | 10x |
stopifnot(length(type) > 0L) |
| 2245 | ||
| 2246 |
## substract the specific plot types for |
|
| 2247 |
## dual-endpoint simulation results |
|
| 2248 | 10x |
typeReduced <- setdiff( |
| 2249 | 10x |
type, |
| 2250 | 10x |
"sigma2" |
| 2251 |
) |
|
| 2252 | ||
| 2253 |
## are there more plots from general? |
|
| 2254 | 10x |
moreFromGeneral <- (length(typeReduced) > 0) |
| 2255 | ||
| 2256 |
## if so, then produce these plots |
|
| 2257 | 10x |
if (moreFromGeneral) {
|
| 2258 | 8x |
genPlot <- callNextMethod(x = x, y = y, type = typeReduced) |
| 2259 |
} |
|
| 2260 | ||
| 2261 |
## now to the specific dual-endpoint plots: |
|
| 2262 | ||
| 2263 |
## Efficacy variance estimates boxplot |
|
| 2264 | 10x |
if ("sigma2" %in% type) {
|
| 2265 |
## save the plot |
|
| 2266 | 6x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 2267 | 6x |
ggplot(data = data.frame(y = x@sigma2_est), aes(x = factor(0), y = y)) + |
| 2268 | 6x |
geom_boxplot() + |
| 2269 | 6x |
coord_flip() + |
| 2270 | 6x |
scale_x_discrete(breaks = NULL) + |
| 2271 | 6x |
xlab("") +
|
| 2272 | 6x |
ylab("Efficacy variance estimates")
|
| 2273 |
} |
|
| 2274 | ||
| 2275 |
## then finally plot everything |
|
| 2276 |
if ( |
|
| 2277 | 10x |
identical( |
| 2278 | 10x |
length(plotList), |
| 2279 | 10x |
0L |
| 2280 |
) |
|
| 2281 |
) {
|
|
| 2282 | 4x |
return(genPlot) |
| 2283 |
} else if ( |
|
| 2284 | 6x |
identical( |
| 2285 | 6x |
length(plotList), |
| 2286 | 6x |
1L |
| 2287 |
) |
|
| 2288 |
) {
|
|
| 2289 | 6x |
ret <- plotList[[1L]] |
| 2290 |
} else {
|
|
| 2291 | ! |
ret <- do.call( |
| 2292 | ! |
gridExtra::arrangeGrob, |
| 2293 | ! |
plotList |
| 2294 |
) |
|
| 2295 |
} |
|
| 2296 | ||
| 2297 | 6x |
if (moreFromGeneral) {
|
| 2298 | 4x |
ret <- gridExtra::arrangeGrob(genPlot, ret, heights = c(2 / 3, 1 / 3)) |
| 2299 |
} |
|
| 2300 | ||
| 2301 | 6x |
return(ret) |
| 2302 |
} |
|
| 2303 |
) |
|
| 2304 |
## --------------------------------------------------------------------------------- |
|
| 2305 |
##' |
|
| 2306 |
##' This plot method can be applied to \code{\linkS4class{PseudoDualFlexiSimulations}}
|
|
| 2307 |
##' objects in order to summarize them graphically. Possible \code{type}s of
|
|
| 2308 |
##' plots at the moment are: \describe{ \item{trajectory}{Summary of the
|
|
| 2309 |
##' trajectory of the simulated trials} \item{dosesTried}{Average proportions of
|
|
| 2310 |
##' the doses tested in patients} \item{sigma2}{The variance of the efficacy responses}
|
|
| 2311 |
##' \item{sigma2betaW}{The variance of the random walk model}}
|
|
| 2312 |
##' You can specify one or both of these in the |
|
| 2313 |
##' \code{type} argument.
|
|
| 2314 |
##' |
|
| 2315 |
##' @param x the \code{\linkS4class{PseudoDualFlexiSimulations}} object we want
|
|
| 2316 |
##' to plot from |
|
| 2317 |
##' @param y missing |
|
| 2318 |
##' @param type the type of plots you want to obtain. |
|
| 2319 |
##' @param \dots not used |
|
| 2320 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 2321 |
##' asked for, otherwise a `gtable` object. |
|
| 2322 |
##' |
|
| 2323 |
##' @importFrom ggplot2 ggplot geom_step geom_bar aes xlab ylab |
|
| 2324 |
##' scale_linetype_manual |
|
| 2325 |
##' @importFrom gridExtra arrangeGrob |
|
| 2326 |
##' |
|
| 2327 |
##' @example examples/Simulations-method-plotSIMDualFlexi.R |
|
| 2328 |
##' @export |
|
| 2329 |
##' @keywords methods |
|
| 2330 |
setMethod( |
|
| 2331 |
"plot", |
|
| 2332 |
signature = signature( |
|
| 2333 |
x = "PseudoDualFlexiSimulations", |
|
| 2334 |
y = "missing" |
|
| 2335 |
), |
|
| 2336 |
def = function( |
|
| 2337 |
x, |
|
| 2338 |
y, |
|
| 2339 |
type = c( |
|
| 2340 |
"trajectory", |
|
| 2341 |
"dosesTried", |
|
| 2342 |
"sigma2", |
|
| 2343 |
"sigma2betaW" |
|
| 2344 |
), |
|
| 2345 |
... |
|
| 2346 |
) {
|
|
| 2347 |
## start the plot list |
|
| 2348 | 5x |
plotList <- list() |
| 2349 | 5x |
plotIndex <- 0L |
| 2350 | ||
| 2351 |
## which plots should be produced? |
|
| 2352 | 5x |
type <- match.arg(type, several.ok = TRUE) |
| 2353 | 5x |
stopifnot(length(type) > 0L) |
| 2354 | ||
| 2355 |
## substract the specific plot types for |
|
| 2356 |
## dual-endpoint simulation results |
|
| 2357 | 5x |
typeReduced <- setdiff(type, "sigma2betaW") |
| 2358 | ||
| 2359 |
## are there more plots from general? |
|
| 2360 | 5x |
moreFromGeneral <- (length(typeReduced) > 0) |
| 2361 | ||
| 2362 |
## if so, then produce these plots |
|
| 2363 | 5x |
if (moreFromGeneral) {
|
| 2364 | 4x |
genPlot <- callNextMethod(x = x, y = y, type = typeReduced) |
| 2365 |
} |
|
| 2366 | ||
| 2367 |
## now to the specific dual-endpoint plots: |
|
| 2368 |
## random walk model variance estimates boxplot |
|
| 2369 | ||
| 2370 | 5x |
if ("sigma2betaW" %in% type) {
|
| 2371 |
## save the plot |
|
| 2372 | 2x |
plotList[[plotIndex <- plotIndex + 1L]] <- |
| 2373 | 2x |
ggplot( |
| 2374 | 2x |
data = data.frame(y = x@sigma2_beta_w_est), |
| 2375 | 2x |
aes(x = factor(0), y = y) |
| 2376 |
) + |
|
| 2377 | 2x |
geom_boxplot() + |
| 2378 | 2x |
coord_flip() + |
| 2379 | 2x |
scale_x_discrete(breaks = NULL) + |
| 2380 | 2x |
xlab("") +
|
| 2381 | 2x |
ylab("Random walk model variance estimates")
|
| 2382 |
} |
|
| 2383 | ||
| 2384 |
## then finally plot everything |
|
| 2385 |
if ( |
|
| 2386 | 5x |
identical( |
| 2387 | 5x |
length(plotList), |
| 2388 | 5x |
0L |
| 2389 |
) |
|
| 2390 |
) {
|
|
| 2391 | 3x |
return(genPlot) |
| 2392 |
} else if ( |
|
| 2393 | 2x |
identical( |
| 2394 | 2x |
length(plotList), |
| 2395 | 2x |
1L |
| 2396 |
) |
|
| 2397 |
) {
|
|
| 2398 | 2x |
ret <- plotList[[1L]] |
| 2399 |
} else {
|
|
| 2400 | ! |
ret <- do.call( |
| 2401 | ! |
gridExtra::arrangeGrob, |
| 2402 | ! |
plotList |
| 2403 |
) |
|
| 2404 |
} |
|
| 2405 | ||
| 2406 | 2x |
if (moreFromGeneral) {
|
| 2407 | 1x |
ret <- gridExtra::arrangeGrob(genPlot, ret, heights = c(2 / 3, 1 / 3)) |
| 2408 |
} |
|
| 2409 | ||
| 2410 | 2x |
return(ret) |
| 2411 |
} |
|
| 2412 |
) |
|
| 2413 | ||
| 2414 |
## ----------------------------------------------------------------------------------------- |
|
| 2415 |
##' Summary for Pseudo Dual responses simulations, relative to a given pseudo DLE and efficacy model |
|
| 2416 |
##' (except the EffFlexi class model) |
|
| 2417 |
##' |
|
| 2418 |
##' @param object the \code{\linkS4class{PseudoDualSimulations}} object we want to summarize
|
|
| 2419 |
##' @param trueDLE a function which takes as input a dose (vector) and returns the true probability (vector) |
|
| 2420 |
##' of DLE |
|
| 2421 |
##' @param trueEff a function which takes as input a dose (vector) and returns the mean efficacy value(s) (vector). |
|
| 2422 |
##' @param targetEndOfTrial the target probability of DLE that are used at the end of a trial. Default at 0.3. |
|
| 2423 |
##' @param targetDuringTrial the target probability of DLE that are used during the trial. Default at 0.35. |
|
| 2424 |
##' @param \dots Additional arguments can be supplied here for \code{trueDLE} and \code{trueEff}
|
|
| 2425 |
##' @return an object of class \code{\linkS4class{PseudoDualSimulationsSummary}}
|
|
| 2426 |
##' |
|
| 2427 |
##' @example examples/Simulations-method-summarySIMDual.R |
|
| 2428 |
##' @export |
|
| 2429 |
##' @keywords methods |
|
| 2430 |
setMethod( |
|
| 2431 |
"summary", |
|
| 2432 |
signature = signature(object = "PseudoDualSimulations"), |
|
| 2433 |
def = function( |
|
| 2434 |
object, |
|
| 2435 |
trueDLE, |
|
| 2436 |
trueEff, |
|
| 2437 |
targetEndOfTrial = 0.3, |
|
| 2438 |
targetDuringTrial = 0.35, |
|
| 2439 |
... |
|
| 2440 |
) {
|
|
| 2441 |
## call the parent method |
|
| 2442 | 9x |
start <- callNextMethod( |
| 2443 | 9x |
object = object, |
| 2444 | 9x |
truth = trueDLE, |
| 2445 | 9x |
targetEndOfTrial = targetEndOfTrial, |
| 2446 | 9x |
targetDuringTrial = targetDuringTrial, |
| 2447 |
... |
|
| 2448 |
) |
|
| 2449 | 9x |
doseGrid <- object@data[[1]]@doseGrid |
| 2450 | ||
| 2451 |
## ## dose level most often selected as MTD (TDtargetEnd of Trial) |
|
| 2452 | 9x |
xMostSelected <- |
| 2453 | 9x |
match_within_tolerance(start@dose_most_selected, table = doseGrid) |
| 2454 | ||
| 2455 |
## check if true Eff is a function |
|
| 2456 |
## check if special case applies |
|
| 2457 | 9x |
isTrueEffFx <- is.function(trueEff) |
| 2458 | ||
| 2459 | 9x |
TDtargetEndOfTrial <- start@target_dose_end_of_trial |
| 2460 | ||
| 2461 | 9x |
if (isTrueEffFx) {
|
| 2462 | 9x |
negtrueGainfn <- function(dose) {
|
| 2463 | 414x |
return(-(trueEff(dose)) / (1 + (trueDLE(dose) / (1 - trueDLE(dose))))) |
| 2464 |
} |
|
| 2465 | 9x |
Gstar <- optim(exp(1), negtrueGainfn, method = "BFGS")$par |
| 2466 | 9x |
maxGainValue <- -(optim(exp(1), negtrueGainfn, method = "BFGS")$value) |
| 2467 | 9x |
GstarAtDoseGrid <- doseGrid[max(which(Gstar - doseGrid >= 0))] |
| 2468 |
} else {
|
|
| 2469 | ! |
trueGain <- (trueEff) / |
| 2470 | ! |
(1 + (trueDLE(doseGrid) / (1 - trueDLE(doseGrid)))) |
| 2471 | ! |
maxGainValue <- max(trueGain) |
| 2472 | ! |
Gstar <- doseGrid[which.max(trueGain)] |
| 2473 | ! |
GstarAtDoseGrid <- Gstar |
| 2474 |
} |
|
| 2475 | ||
| 2476 |
## A summary for all final Gstar obtained |
|
| 2477 | 9x |
GstarSummary <- summary(object@final_gstar_estimates) |
| 2478 | 9x |
ratioGstarSummary <- summary(object@final_gstar_ratios) |
| 2479 | ||
| 2480 | 9x |
FinalDoseRecSummary <- summary(object@final_optimal_dose) |
| 2481 | 9x |
FinalRatioSummary <- summary(object@final_ratios) |
| 2482 | ||
| 2483 |
## find names in the fit efficacy list (check it is with or without samples) |
|
| 2484 | 9x |
FitNames <- sapply(object@fit_eff, names) |
| 2485 | 9x |
if ("ExpEff" %in% FitNames) {
|
| 2486 |
## fitted efficacy level at dose most often selected |
|
| 2487 | 7x |
EffFitAtDoseMostSelected <- sapply( |
| 2488 | 7x |
object@fit_eff, |
| 2489 | 7x |
function(f) {
|
| 2490 | 7x |
f$ExpEff[xMostSelected] |
| 2491 |
} |
|
| 2492 |
) |
|
| 2493 | 7x |
meanEffFitMatrix <- sapply( |
| 2494 | 7x |
object@fit_eff, |
| 2495 |
"[[", |
|
| 2496 | 7x |
"ExpEff" |
| 2497 |
) |
|
| 2498 | ||
| 2499 | 7x |
meanEffFit <- list( |
| 2500 | 7x |
truth = trueEff(doseGrid), |
| 2501 | 7x |
average = rowMeans(meanEffFitMatrix) |
| 2502 |
) |
|
| 2503 |
} else {
|
|
| 2504 |
## fitted efficacy level at dose most often selected |
|
| 2505 | 2x |
EffFitAtDoseMostSelected <- |
| 2506 | 2x |
sapply( |
| 2507 | 2x |
object@fit_eff, |
| 2508 | 2x |
function(f) {
|
| 2509 | 2x |
f$middle[xMostSelected] |
| 2510 |
} |
|
| 2511 |
) |
|
| 2512 | ||
| 2513 |
## mean fitted curve (average, lower and upper quantiles) |
|
| 2514 |
## at each dose level |
|
| 2515 |
## (this is required for plotting) |
|
| 2516 | 2x |
meanEffFitMatrix <- sapply( |
| 2517 | 2x |
object@fit_eff, |
| 2518 |
"[[", |
|
| 2519 | 2x |
"middle" |
| 2520 |
) |
|
| 2521 | ||
| 2522 |
## check if special case applies |
|
| 2523 | ||
| 2524 | 2x |
if (isTrueEffFx) {
|
| 2525 | 2x |
TRUTHeff <- trueEff(doseGrid) |
| 2526 |
} else {
|
|
| 2527 | ! |
TRUTHeff <- trueEff |
| 2528 |
} |
|
| 2529 | ||
| 2530 | 2x |
meanEffFit <- list( |
| 2531 | 2x |
truth = TRUTHeff, |
| 2532 | 2x |
average = rowMeans(meanEffFitMatrix), |
| 2533 | 2x |
lower = apply( |
| 2534 | 2x |
meanEffFitMatrix, |
| 2535 | 2x |
1L, |
| 2536 | 2x |
quantile, |
| 2537 | 2x |
0.025 |
| 2538 |
), |
|
| 2539 | 2x |
upper = apply( |
| 2540 | 2x |
meanEffFitMatrix, |
| 2541 | 2x |
1L, |
| 2542 | 2x |
quantile, |
| 2543 | 2x |
0.975 |
| 2544 |
) |
|
| 2545 |
) |
|
| 2546 |
} |
|
| 2547 | ||
| 2548 |
## give back an object of class PseudoDualSimulationsSummary, |
|
| 2549 |
## for which we then define a print / plot method |
|
| 2550 | 9x |
ret <- .PseudoDualSimulationsSummary( |
| 2551 | 9x |
start, |
| 2552 | 9x |
target_gstar = Gstar, |
| 2553 | 9x |
target_gstar_at_dose_grid = GstarAtDoseGrid, |
| 2554 | 9x |
gstar_summary = GstarSummary, |
| 2555 | 9x |
ratio_gstar_summary = ratioGstarSummary, |
| 2556 | 9x |
final_dose_rec_summary = FinalDoseRecSummary, |
| 2557 | 9x |
final_ratio_summary = FinalRatioSummary, |
| 2558 | 9x |
eff_fit_at_dose_most_selected = EffFitAtDoseMostSelected, |
| 2559 | 9x |
mean_eff_fit = meanEffFit, |
| 2560 | 9x |
stop_report = object@stop_report |
| 2561 |
) |
|
| 2562 | ||
| 2563 | 9x |
return(ret) |
| 2564 |
} |
|
| 2565 |
) |
|
| 2566 |
## -------------------------------------------------------------------------------------------------- |
|
| 2567 |
##' Summary for Pseudo Dual responses simulations given a pseudo DLE model and the Flexible efficacy model. |
|
| 2568 |
##' |
|
| 2569 |
##' @param object the \code{\linkS4class{PseudoDualFlexiSimulations}} object we want to summarize
|
|
| 2570 |
##' @param trueDLE a function which takes as input a dose (vector) and returns the true probability of DLE (vector) |
|
| 2571 |
##' @param trueEff a vector which takes as input the true mean efficacy values at all dose levels (in order) |
|
| 2572 |
##' @param targetEndOfTrial the target probability of DLE that are used at the end of a trial. Default at 0.3. |
|
| 2573 |
##' @param targetDuringTrial the target probability of DLE that are used during the trial. Default at 0.35. |
|
| 2574 |
##' @param \dots Additional arguments can be supplied here for \code{trueDLE} and \code{trueEff}
|
|
| 2575 |
##' @return an object of class \code{\linkS4class{PseudoDualSimulationsSummary}}
|
|
| 2576 |
##' |
|
| 2577 |
##' @example examples/Simulations-method-summarySIMDualFlexi.R |
|
| 2578 |
##' @export |
|
| 2579 |
##' @keywords methods |
|
| 2580 |
setMethod( |
|
| 2581 |
"summary", |
|
| 2582 |
signature = signature(object = "PseudoDualFlexiSimulations"), |
|
| 2583 |
def = function( |
|
| 2584 |
object, |
|
| 2585 |
trueDLE, |
|
| 2586 |
trueEff, |
|
| 2587 |
targetEndOfTrial = 0.3, |
|
| 2588 |
targetDuringTrial = 0.35, |
|
| 2589 |
... |
|
| 2590 |
) {
|
|
| 2591 |
## call the parent method |
|
| 2592 | 2x |
start <- callNextMethod( |
| 2593 | 2x |
object = object, |
| 2594 | 2x |
trueDLE = trueDLE, |
| 2595 | 2x |
trueEff = trueEff, |
| 2596 | 2x |
targetEndOfTrial = targetEndOfTrial, |
| 2597 | 2x |
targetDuringTrial = targetDuringTrial, |
| 2598 |
... |
|
| 2599 |
) |
|
| 2600 | ||
| 2601 |
## give back an object of class PseudoDualSimulationsSummary, |
|
| 2602 |
## for which we then define a print / plot method |
|
| 2603 | 2x |
ret <- .PseudoDualSimulationsSummary(start) |
| 2604 | ||
| 2605 | 2x |
return(ret) |
| 2606 |
} |
|
| 2607 |
) |
|
| 2608 | ||
| 2609 |
## ---------------------------------------------------------------------------------------- |
|
| 2610 |
##' Show the summary of Pseudo Dual simulations summary |
|
| 2611 |
##' |
|
| 2612 |
##' @param object the \code{\linkS4class{PseudoDualSimulationsSummary}} object we want to print
|
|
| 2613 |
##' @return invisibly returns a data frame of the results with one row and appropriate column names |
|
| 2614 |
##' |
|
| 2615 |
##' |
|
| 2616 |
##' @example examples/Simulations-method-showSIMDual.R |
|
| 2617 |
##' @export |
|
| 2618 |
##' @keywords methods |
|
| 2619 |
setMethod( |
|
| 2620 |
"show", |
|
| 2621 |
signature = signature(object = "PseudoDualSimulationsSummary"), |
|
| 2622 |
def = function(object) {
|
|
| 2623 |
## call the parent method |
|
| 2624 | 7x |
df <- callNextMethod(object) |
| 2625 | 7x |
dfNames <- names(df) |
| 2626 | ||
| 2627 |
## start report object |
|
| 2628 | 7x |
r <- Report$new( |
| 2629 | 7x |
object = object, |
| 2630 | 7x |
df = df, |
| 2631 | 7x |
dfNames = dfNames |
| 2632 |
) |
|
| 2633 | ||
| 2634 |
## add three reporting lines |
|
| 2635 | 7x |
cat( |
| 2636 | 7x |
"Target Gstar, the dose which gives the maximum gain value was", |
| 2637 | 7x |
r$dfSave( |
| 2638 | 7x |
object@target_gstar, |
| 2639 | 7x |
"targetGstar" |
| 2640 |
), |
|
| 2641 | 7x |
"\n" |
| 2642 |
) |
|
| 2643 | 7x |
cat( |
| 2644 | 7x |
"Target Gstar at dose Grid was", |
| 2645 | 7x |
r$dfSave( |
| 2646 | 7x |
object@target_gstar_at_dose_grid, |
| 2647 | 7x |
"targetGstarAtDoseGrid" |
| 2648 |
), |
|
| 2649 | 7x |
"\n" |
| 2650 |
) |
|
| 2651 | ||
| 2652 | 7x |
GstarSum <- object@gstar_summary |
| 2653 | ||
| 2654 | 7x |
r$dfSave(as.numeric(GstarSum[1]), "GstarMin") |
| 2655 | 7x |
r$dfSave(as.numeric(GstarSum[2]), "Gstarlower") |
| 2656 | 7x |
r$dfSave(as.numeric(GstarSum[3]), "GstarMedian") |
| 2657 | 7x |
r$dfSave(as.numeric(GstarSum[4]), "GstarMean") |
| 2658 | 7x |
r$dfSave(as.numeric(GstarSum[5]), "GstarUpper") |
| 2659 | 7x |
r$dfSave(as.numeric(GstarSum[6]), "GstarMax") |
| 2660 | ||
| 2661 | 7x |
cat( |
| 2662 | 7x |
"The summary table of the final Gstar across all simulations\n", |
| 2663 | 7x |
capture.output(GstarSum)[1], |
| 2664 | 7x |
"\n", |
| 2665 | 7x |
capture.output(GstarSum)[2], |
| 2666 | 7x |
"\n" |
| 2667 |
) |
|
| 2668 | ||
| 2669 | 7x |
ratioGstarSum <- object@ratio_gstar_summary |
| 2670 | ||
| 2671 | 7x |
r$dfSave(as.numeric(ratioGstarSum[1]), "ratioGstarMin") |
| 2672 | 7x |
r$dfSave(as.numeric(ratioGstarSum[2]), "ratioGstarlower") |
| 2673 | 7x |
r$dfSave(as.numeric(ratioGstarSum[3]), "ratioGstarMedian") |
| 2674 | 7x |
r$dfSave(as.numeric(ratioGstarSum[4]), "ratioGstarMean") |
| 2675 | 7x |
r$dfSave(as.numeric(ratioGstarSum[5]), "ratioGstarUpper") |
| 2676 | 7x |
r$dfSave(as.numeric(ratioGstarSum[6]), "ratioGstarMax") |
| 2677 | ||
| 2678 | 7x |
cat( |
| 2679 | 7x |
"The summary table of the final ratios of the Gstar across all simulations\n", |
| 2680 | 7x |
capture.output(ratioGstarSum)[1], |
| 2681 | 7x |
"\n", |
| 2682 | 7x |
capture.output(ratioGstarSum)[2], |
| 2683 | 7x |
"\n" |
| 2684 |
) |
|
| 2685 | ||
| 2686 | 7x |
FinalDoseRecSum <- object@final_dose_rec_summary |
| 2687 | ||
| 2688 | 7x |
r$dfSave(as.numeric(FinalDoseRecSum[1]), "FinalDoseRecMin") |
| 2689 | 7x |
r$dfSave(as.numeric(FinalDoseRecSum[2]), "FinalDoseReclower") |
| 2690 | 7x |
r$dfSave(as.numeric(FinalDoseRecSum[3]), "FinalDoseRecMedian") |
| 2691 | 7x |
r$dfSave(as.numeric(FinalDoseRecSum[4]), "FinalDoseRecMean") |
| 2692 | 7x |
r$dfSave(as.numeric(FinalDoseRecSum[5]), "FinalDoseRecUpper") |
| 2693 | 7x |
r$dfSave(as.numeric(FinalDoseRecSum[6]), "FinalDoseRecMax") |
| 2694 | ||
| 2695 | 7x |
cat( |
| 2696 | 7x |
"The summary table of dose levels, the optimal dose\n to recommend for subsequent study across all simulations\n", |
| 2697 | 7x |
capture.output(FinalDoseRecSum)[1], |
| 2698 | 7x |
"\n", |
| 2699 | 7x |
capture.output(FinalDoseRecSum)[2], |
| 2700 | 7x |
"\n" |
| 2701 |
) |
|
| 2702 | ||
| 2703 | 7x |
FinalratioSum <- object@final_ratio_summary |
| 2704 | ||
| 2705 | 7x |
r$dfSave(as.numeric(FinalratioSum[1]), "FinalratioMin") |
| 2706 | 7x |
r$dfSave(as.numeric(FinalratioSum[2]), "Finalratiolower") |
| 2707 | 7x |
r$dfSave(as.numeric(FinalratioSum[3]), "FinalratioMedian") |
| 2708 | 7x |
r$dfSave(as.numeric(FinalratioSum[4]), "FinalratioMean") |
| 2709 | 7x |
r$dfSave(as.numeric(FinalratioSum[5]), "FinalratioUpper") |
| 2710 | 7x |
r$dfSave(as.numeric(FinalratioSum[6]), "FinalratioMax") |
| 2711 | ||
| 2712 | 7x |
cat( |
| 2713 | 7x |
"The summary table of the final ratios of the optimal dose for stopping across |
| 2714 | 7x |
all simulations\n", |
| 2715 | 7x |
capture.output(FinalratioSum)[1], |
| 2716 | 7x |
"\n", |
| 2717 | 7x |
capture.output(FinalratioSum)[2], |
| 2718 | 7x |
"\n" |
| 2719 |
) |
|
| 2720 | ||
| 2721 | 7x |
r$report( |
| 2722 | 7x |
"eff_fit_at_dose_most_selected", |
| 2723 | 7x |
"Fitted expected efficacy level at dose most often selected", |
| 2724 | 7x |
percent = FALSE, |
| 2725 | 7x |
digits = 1 |
| 2726 |
) |
|
| 2727 | ||
| 2728 |
# Report individual stopping rules with non-<NA> labels. |
|
| 2729 | ||
| 2730 | 7x |
stop_pct_to_print <- h_calc_report_label_percentage(object@stop_report) |
| 2731 | ||
| 2732 | 7x |
if (length(stop_pct_to_print) > 0) {
|
| 2733 | 7x |
cat( |
| 2734 | 7x |
"Stop reason triggered:\n", |
| 2735 | 7x |
paste(names(stop_pct_to_print), ": ", stop_pct_to_print, "%\n") |
| 2736 |
) |
|
| 2737 |
} |
|
| 2738 | ||
| 2739 |
## and return the updated information |
|
| 2740 | 7x |
names(r$df) <- r$dfNames |
| 2741 | 7x |
invisible(r$df) |
| 2742 |
} |
|
| 2743 |
) |
|
| 2744 | ||
| 2745 |
## -------------------------------------------------------------------------------------------------- |
|
| 2746 |
##' Plot the summary of Pseudo Dual Simulations summary |
|
| 2747 |
##' |
|
| 2748 |
##' This plot method can be applied to \code{\linkS4class{PseudoDualSimulationsSummary}} objects in order
|
|
| 2749 |
##' to summarize them graphically. Possible \code{type} of plots at the moment are those listed in
|
|
| 2750 |
##' \code{\link{plot,PseudoSimulationsSummary,missing-method}} plus:
|
|
| 2751 |
##' \describe{\item{meanEffFit}{Plot showing the fitted dose-efficacy curve. If no samples are involved, only the
|
|
| 2752 |
##' average fitted dose-efficacy curve across the trials will be plotted. If samples (DLE and efficacy) are involved, |
|
| 2753 |
##' the average fitted dose-efficacy curve across the trials, together with the 95% credibility interval; and comparison |
|
| 2754 |
##' with the assumed truth (as specified by the \code{trueEff} argument to
|
|
| 2755 |
##' \code{\link{summary,PseudoDualSimulations-method}})}}
|
|
| 2756 |
##' You can specify any subset of these in the \code{type} argument.
|
|
| 2757 |
##' |
|
| 2758 |
##' @param x the \code{\linkS4class{PseudoDualSimulationsSummary}} object we want to plot from
|
|
| 2759 |
##' @param y missing |
|
| 2760 |
##' @param type the types of plots you want to obtain. |
|
| 2761 |
##' @param \dots not used |
|
| 2762 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is
|
|
| 2763 |
##' asked for, otherwise a `gtable` object. |
|
| 2764 |
##' |
|
| 2765 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
| 2766 |
##' scale_linetype_manual scale_colour_manual |
|
| 2767 |
##' @importFrom gridExtra arrangeGrob |
|
| 2768 |
##' |
|
| 2769 |
##' @example examples/Simulations-method-plotSUMDual.R |
|
| 2770 |
##' @export |
|
| 2771 |
##' @keywords methods |
|
| 2772 |
setMethod( |
|
| 2773 |
"plot", |
|
| 2774 |
signature = signature( |
|
| 2775 |
x = "PseudoDualSimulationsSummary", |
|
| 2776 |
y = "missing" |
|
| 2777 |
), |
|
| 2778 |
def = function( |
|
| 2779 |
x, |
|
| 2780 |
y, |
|
| 2781 |
type = c( |
|
| 2782 |
"nObs", |
|
| 2783 |
"doseSelected", |
|
| 2784 |
"propDLE", |
|
| 2785 |
"nAboveTargetEndOfTrial", |
|
| 2786 |
"meanFit", |
|
| 2787 |
"meanEffFit" |
|
| 2788 |
), |
|
| 2789 |
... |
|
| 2790 |
) {
|
|
| 2791 |
## which plots should be produced? |
|
| 2792 | 7x |
type <- match.arg(type, several.ok = TRUE) |
| 2793 | 5x |
stopifnot(length(type) > 0L) |
| 2794 | ||
| 2795 |
## substract the specific plot types for dual-endpoint |
|
| 2796 |
## designs |
|
| 2797 | 5x |
typeReduced <- setdiff( |
| 2798 | 5x |
type, |
| 2799 | 5x |
"meanEffFit" |
| 2800 |
) |
|
| 2801 | ||
| 2802 |
## are there more plots from general? |
|
| 2803 | 5x |
moreFromGeneral <- (length(typeReduced) > 0) |
| 2804 | ||
| 2805 |
## if so, then produce these plots |
|
| 2806 | 5x |
if (moreFromGeneral) {
|
| 2807 | 3x |
ret <- callNextMethod(x = x, y = y, type = typeReduced) |
| 2808 |
} |
|
| 2809 | ||
| 2810 |
## is the meanBiomarkerFit plot requested? |
|
| 2811 | 5x |
if ("meanEffFit" %in% type) {
|
| 2812 |
## Find if Effsamples are generated in the simulations |
|
| 2813 |
## by checking if there the lower limits of the 95% Credibility |
|
| 2814 |
## interval are calculated |
|
| 2815 | 5x |
if (!is.null(x@mean_eff_fit$lower)) {
|
| 2816 |
## which types of lines do we have? |
|
| 2817 | ! |
linetype <- c( |
| 2818 | ! |
"True Expected Efficacy", |
| 2819 | ! |
"Average estimated expected efficacy", |
| 2820 | ! |
"95% interval for estimated expected efficacy" |
| 2821 |
) |
|
| 2822 | ||
| 2823 |
## create the data frame, with |
|
| 2824 |
## true biomarker, average estimated expected efficacy, and 95% (lower, upper) |
|
| 2825 |
## estimated biomarker stacked below each other |
|
| 2826 | ! |
dat <- data.frame( |
| 2827 | ! |
dose = rep(x@dose_grid, 4L), |
| 2828 | ! |
group = rep(1:4, each = length(x@dose_grid)), |
| 2829 | ! |
linetype = factor( |
| 2830 | ! |
rep(linetype[c(1, 2, 3, 3)], each = length(x@dose_grid)), |
| 2831 | ! |
levels = linetype |
| 2832 |
), |
|
| 2833 | ! |
lines = unlist(x@mean_eff_fit) |
| 2834 |
) |
|
| 2835 | ||
| 2836 |
## linetypes for the plot |
|
| 2837 | ! |
lt <- c( |
| 2838 | ! |
"True Expected Efficacy" = 1, |
| 2839 | ! |
"Average estimated expected efficacy" = 1, |
| 2840 | ! |
"95% interval for estimated expected efficacy" = 2 |
| 2841 |
) |
|
| 2842 | ||
| 2843 |
## colour for the plot |
|
| 2844 | ! |
col <- c( |
| 2845 | ! |
"True Expected Efficacy" = 1, |
| 2846 | ! |
"Average estimated expected efficacy" = 4, |
| 2847 | ! |
"95% interval for estimated expected efficacy" = 4 |
| 2848 |
) |
|
| 2849 | ||
| 2850 |
## now create and save the plot |
|
| 2851 | ! |
thisPlot <- ggplot() + |
| 2852 | ! |
geom_line( |
| 2853 | ! |
aes( |
| 2854 | ! |
x = dose, |
| 2855 | ! |
y = lines, |
| 2856 | ! |
group = group, |
| 2857 | ! |
linetype = linetype, |
| 2858 | ! |
col = linetype |
| 2859 |
), |
|
| 2860 | ! |
data = dat |
| 2861 |
) |
|
| 2862 | ||
| 2863 | ! |
thisPlot <- thisPlot + |
| 2864 | ! |
scale_linetype_manual(values = lt) + |
| 2865 | ! |
scale_colour_manual(values = col) + |
| 2866 | ! |
xlab("Dose level") +
|
| 2867 | ! |
ylab("Expected Efficacy level")
|
| 2868 |
} else {
|
|
| 2869 | 5x |
linetype <- c( |
| 2870 | 5x |
"True Expected Efficacy", |
| 2871 | 5x |
"Average estimated expected efficacy" |
| 2872 |
) |
|
| 2873 | ||
| 2874 |
## create the data frame, with |
|
| 2875 |
## true biomarker, average estimated expected efficacy |
|
| 2876 | 5x |
dat <- data.frame( |
| 2877 | 5x |
dose = rep(x@dose_grid, 2L), |
| 2878 | 5x |
group = rep(1:2, each = length(x@dose_grid)), |
| 2879 | 5x |
linetype = factor( |
| 2880 | 5x |
rep(linetype[c(1, 2)], each = length(x@dose_grid)), |
| 2881 | 5x |
levels = linetype |
| 2882 |
), |
|
| 2883 | 5x |
lines = unlist(x@mean_eff_fit) |
| 2884 |
) |
|
| 2885 | ||
| 2886 |
## linetypes for the plot |
|
| 2887 | 5x |
lt <- c( |
| 2888 | 5x |
"True Expected Efficacy" = 1, |
| 2889 | 5x |
"Average estimated expected efficacy" = 1 |
| 2890 |
) |
|
| 2891 | ||
| 2892 |
## colour for the plot |
|
| 2893 | 5x |
col <- c( |
| 2894 | 5x |
"True Expected Efficacy" = 1, |
| 2895 | 5x |
"Average estimated expected efficacy" = 4 |
| 2896 |
) |
|
| 2897 | ||
| 2898 |
## now create and save the plot |
|
| 2899 | 5x |
thisPlot <- ggplot() + |
| 2900 | 5x |
geom_line( |
| 2901 | 5x |
aes( |
| 2902 | 5x |
x = dose, |
| 2903 | 5x |
y = lines, |
| 2904 | 5x |
group = group, |
| 2905 | 5x |
linetype = linetype, |
| 2906 | 5x |
col = linetype |
| 2907 |
), |
|
| 2908 | 5x |
data = dat |
| 2909 |
) |
|
| 2910 | ||
| 2911 | 5x |
thisPlot <- thisPlot + |
| 2912 | 5x |
scale_linetype_manual(values = lt) + |
| 2913 | 5x |
scale_colour_manual(values = col) + |
| 2914 | 5x |
xlab("Dose level") +
|
| 2915 | 5x |
ylab("Expected Efficacy level")
|
| 2916 |
} |
|
| 2917 | ||
| 2918 |
## add this plot to the bottom |
|
| 2919 | 5x |
ret <- |
| 2920 | 5x |
if (moreFromGeneral) {
|
| 2921 | 3x |
gridExtra::arrangeGrob(ret, thisPlot, heights = c(2 / 3, 1 / 3)) |
| 2922 |
} else {
|
|
| 2923 | 2x |
thisPlot |
| 2924 |
} |
|
| 2925 |
} |
|
| 2926 | ||
| 2927 |
## then finally plot everything |
|
| 2928 | 5x |
ret |
| 2929 |
} |
|
| 2930 |
) |
|
| 2931 | ||
| 2932 |
# nolint end |
| 1 |
#' @include helpers.R |
|
| 2 |
#' @include helpers_covr.R |
|
| 3 |
#' @include logger.R |
|
| 4 |
#' @include Samples-class.R |
|
| 5 |
NULL |
|
| 6 | ||
| 7 |
# mcmc ---- |
|
| 8 | ||
| 9 |
#' Obtaining Posterior Samples for all Model Parameters |
|
| 10 |
#' |
|
| 11 |
#' @description `r lifecycle::badge("stable")`
|
|
| 12 |
#' |
|
| 13 |
#' This is the function that actually runs the `JAGS` MCMC machinery to produce |
|
| 14 |
#' posterior samples from all model parameters and required derived values. |
|
| 15 |
#' It is a generic function, so that customized versions may be conveniently |
|
| 16 |
#' defined for specific subclasses of [`GeneralData`], [`GeneralModel`], and |
|
| 17 |
#' [`McmcOptions`] input. |
|
| 18 |
#' |
|
| 19 |
#' @note The type of Random Number Generator (RNG) and its initial seed used by |
|
| 20 |
#' `JAGS` are taken from the `options` argument. If no initial values are |
|
| 21 |
#' supplied (i.e RNG kind or seed slot in `options` has `NA`), then they will |
|
| 22 |
#' be generated automatically by `JAGS`. |
|
| 23 |
#' |
|
| 24 |
#' @param data (`GeneralData`)\cr an input data. |
|
| 25 |
#' @param model (`GeneralModel`)\cr an input model. |
|
| 26 |
#' @param options (`McmcOptions`)\cr MCMC options. |
|
| 27 |
#' @param ... not used. |
|
| 28 |
#' |
|
| 29 |
#' @return The posterior samples, an object of class [`Samples`]. |
|
| 30 |
#' @export |
|
| 31 |
#' |
|
| 32 |
setGeneric( |
|
| 33 |
name = "mcmc", |
|
| 34 |
def = function(data, model, options, ...) {
|
|
| 35 | 537x |
standardGeneric("mcmc")
|
| 36 |
}, |
|
| 37 |
valueClass = "Samples" |
|
| 38 |
) |
|
| 39 | ||
| 40 |
# mcmc-GeneralData ---- |
|
| 41 | ||
| 42 |
#' @describeIn mcmc Standard method which uses JAGS. |
|
| 43 |
#' |
|
| 44 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
| 45 |
#' when number of observations in `data` is `0`. For some models it might be |
|
| 46 |
#' necessary to specify it manually here though. |
|
| 47 |
#' |
|
| 48 |
#' @aliases mcmc-GeneralData |
|
| 49 |
#' @example examples/mcmc.R |
|
| 50 |
#' |
|
| 51 |
setMethod( |
|
| 52 |
f = "mcmc", |
|
| 53 |
signature = signature( |
|
| 54 |
data = "GeneralData", |
|
| 55 |
model = "GeneralModel", |
|
| 56 |
options = "McmcOptions" |
|
| 57 |
), |
|
| 58 |
def = function(data, model, options, from_prior = data@nObs == 0L, ...) {
|
|
| 59 | 434x |
assert_flag(from_prior) |
| 60 | ||
| 61 | 434x |
model_fun <- if (from_prior) {
|
| 62 | 39x |
model@priormodel |
| 63 |
} else {
|
|
| 64 | 395x |
h_jags_join_models(model@datamodel, model@priormodel) |
| 65 |
} |
|
| 66 | ||
| 67 | 434x |
model_file <- h_jags_write_model(model_fun) |
| 68 | 434x |
model_inits <- h_jags_get_model_inits(model, data) |
| 69 | 434x |
model_data <- h_jags_get_data(model, data, from_prior) |
| 70 | ||
| 71 | 434x |
jags_model <- rjags::jags.model( |
| 72 | 434x |
file = model_file, |
| 73 | 434x |
data = model_data, |
| 74 | 434x |
inits = c( |
| 75 | 434x |
model_inits, |
| 76 | 434x |
.RNG.name = h_null_if_na(options@rng_kind), |
| 77 | 434x |
.RNG.seed = h_null_if_na(options@rng_seed) |
| 78 |
), |
|
| 79 | 434x |
quiet = !is_logging_enabled(), |
| 80 | 434x |
n.adapt = 0 # No adaptation. Important for reproducibility. |
| 81 |
) |
|
| 82 | 434x |
update(jags_model, n.iter = options@burnin, progress.bar = "none") |
| 83 | ||
| 84 |
# This is necessary as some outputs are written directly from the JAGS |
|
| 85 |
# compiled code to the outstream. |
|
| 86 | 434x |
log_trace("Running rjags::jags.samples")
|
| 87 | 434x |
if (is_logging_enabled()) {
|
| 88 | ! |
jags_samples <- rjags::jags.samples( |
| 89 | ! |
model = jags_model, |
| 90 | ! |
variable.names = model@sample, |
| 91 | ! |
n.iter = (options@iterations - options@burnin), |
| 92 | ! |
thin = options@step |
| 93 |
) |
|
| 94 |
} else {
|
|
| 95 | 434x |
invisible( |
| 96 | 434x |
capture.output( |
| 97 | 434x |
jags_samples <- rjags::jags.samples( |
| 98 | 434x |
model = jags_model, |
| 99 | 434x |
variable.names = model@sample, |
| 100 | 434x |
n.iter = (options@iterations - options@burnin), |
| 101 | 434x |
thin = options@step, |
| 102 | 434x |
progress.bar = "none" |
| 103 |
) |
|
| 104 |
) |
|
| 105 |
) |
|
| 106 |
} |
|
| 107 | 434x |
log_trace("JAGS samples: ", jags_samples, capture = TRUE)
|
| 108 | 434x |
samples <- lapply(jags_samples, h_jags_extract_samples) |
| 109 | ||
| 110 | 434x |
Samples(data = samples, options = options) |
| 111 |
} |
|
| 112 |
) |
|
| 113 | ||
| 114 |
# mcmc-GeneralData-DualEndpointRW ---- |
|
| 115 | ||
| 116 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
| 117 |
#' [`DualEndpointRW`] model, it is required that there are at least two (in |
|
| 118 |
#' case of random walk prior of the first order on the biomarker level) or |
|
| 119 |
#' three doses in the grid. |
|
| 120 |
#' |
|
| 121 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
| 122 |
#' when number of observations in `data` is `0`. For some models it might be |
|
| 123 |
#' necessary to specify it manually here though. |
|
| 124 |
#' |
|
| 125 |
#' @aliases mcmc-GeneralData-DualEndpointRW |
|
| 126 |
#' @example examples/mcmc-DualEndpointRW.R |
|
| 127 |
#' |
|
| 128 |
setMethod( |
|
| 129 |
f = "mcmc", |
|
| 130 |
signature = signature( |
|
| 131 |
data = "GeneralData", |
|
| 132 |
model = "DualEndpointRW", |
|
| 133 |
options = "McmcOptions" |
|
| 134 |
), |
|
| 135 |
def = function(data, model, options, from_prior = data@nObs == 0L, ...) {
|
|
| 136 | 44x |
if (model@rw1) {
|
| 137 | 34x |
assert_true(data@nGrid >= 2) |
| 138 |
} else {
|
|
| 139 | 10x |
assert_true(data@nGrid >= 3) |
| 140 |
} |
|
| 141 | ||
| 142 | 41x |
callNextMethod( |
| 143 | 41x |
data = data, |
| 144 | 41x |
model = model, |
| 145 | 41x |
options = options, |
| 146 | 41x |
from_prior = from_prior, |
| 147 |
... |
|
| 148 |
) |
|
| 149 |
} |
|
| 150 |
) |
|
| 151 | ||
| 152 |
# mcmc-GeneralData-DualEndpointBeta ---- |
|
| 153 | ||
| 154 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
| 155 |
#' [`DualEndpointBeta`] model, it is required that the value of `ref_dose_beta` |
|
| 156 |
#' slot is greater than the maximum dose in a grid. This requirement comes from |
|
| 157 |
#' definition of the beta function that is used to model dose-biomarker |
|
| 158 |
#' relationship in [`DualEndpointBeta`] model. The other requirement is that |
|
| 159 |
#' there must be at least one dose in the grid. |
|
| 160 |
#' |
|
| 161 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
| 162 |
#' when number of observations in `data` is `0`. For some models it might be |
|
| 163 |
#' necessary to specify it manually here though. |
|
| 164 |
#' |
|
| 165 |
#' @aliases mcmc-GeneralData-DualEndpointBeta |
|
| 166 |
#' @example examples/mcmc-DualEndpointBeta.R |
|
| 167 |
#' |
|
| 168 |
setMethod( |
|
| 169 |
f = "mcmc", |
|
| 170 |
signature = signature( |
|
| 171 |
data = "GeneralData", |
|
| 172 |
model = "DualEndpointBeta", |
|
| 173 |
options = "McmcOptions" |
|
| 174 |
), |
|
| 175 |
def = function(data, model, options, from_prior = data@nObs == 0L, ...) {
|
|
| 176 | 10x |
assert_true(data@nGrid >= 1) |
| 177 | 9x |
assert_true(model@ref_dose_beta > data@doseGrid[data@nGrid]) |
| 178 | ||
| 179 | 8x |
callNextMethod( |
| 180 | 8x |
data = data, |
| 181 | 8x |
model = model, |
| 182 | 8x |
options = options, |
| 183 | 8x |
from_prior = from_prior, |
| 184 |
... |
|
| 185 |
) |
|
| 186 |
} |
|
| 187 |
) |
|
| 188 | ||
| 189 |
# mcmc-GeneralData-DualEndpointEmax ---- |
|
| 190 | ||
| 191 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
| 192 |
#' [`DualEndpointEmax`] model, it is required that there is at least one dose |
|
| 193 |
#' in the grid. |
|
| 194 |
#' |
|
| 195 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
| 196 |
#' when number of observations in `data` is `0`. For some models it might be |
|
| 197 |
#' necessary to specify it manually here though. |
|
| 198 |
#' |
|
| 199 |
#' @aliases mcmc-GeneralData-DualEndpointEmax |
|
| 200 |
#' |
|
| 201 |
setMethod( |
|
| 202 |
f = "mcmc", |
|
| 203 |
signature = signature( |
|
| 204 |
data = "GeneralData", |
|
| 205 |
model = "DualEndpointEmax", |
|
| 206 |
options = "McmcOptions" |
|
| 207 |
), |
|
| 208 |
def = function(data, model, options, from_prior = data@nObs == 0L, ...) {
|
|
| 209 | 9x |
assert_true(data@nGrid >= 1) |
| 210 | ||
| 211 | 8x |
callNextMethod( |
| 212 | 8x |
data = data, |
| 213 | 8x |
model = model, |
| 214 | 8x |
options = options, |
| 215 | 8x |
from_prior = from_prior, |
| 216 |
... |
|
| 217 |
) |
|
| 218 |
} |
|
| 219 |
) |
|
| 220 | ||
| 221 |
# mcmc-GeneralData-OneParLogNormalPrior ---- |
|
| 222 | ||
| 223 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
| 224 |
#' [`OneParLogNormalPrior`] model, it is required that the length of |
|
| 225 |
#' skeleton prior probabilities vector should be equal to the length of the |
|
| 226 |
#' number of doses. |
|
| 227 |
#' |
|
| 228 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
| 229 |
#' when number of observations in `data` is `0`. For some models it might be |
|
| 230 |
#' necessary to specify it manually here though. |
|
| 231 |
#' |
|
| 232 |
#' @aliases mcmc-GeneralData-OneParLogNormalPrior |
|
| 233 |
#' |
|
| 234 |
setMethod( |
|
| 235 |
f = "mcmc", |
|
| 236 |
signature = signature( |
|
| 237 |
data = "GeneralData", |
|
| 238 |
model = "OneParLogNormalPrior", |
|
| 239 |
options = "McmcOptions" |
|
| 240 |
), |
|
| 241 |
def = function(data, model, options, from_prior = data@nObs == 0L, ...) {
|
|
| 242 | 9x |
if (!from_prior) {
|
| 243 | 6x |
assert_true(length(model@skel_probs) == data@nGrid) |
| 244 |
} |
|
| 245 | ||
| 246 | 7x |
callNextMethod( |
| 247 | 7x |
data = data, |
| 248 | 7x |
model = model, |
| 249 | 7x |
options = options, |
| 250 | 7x |
from_prior = from_prior, |
| 251 |
... |
|
| 252 |
) |
|
| 253 |
} |
|
| 254 |
) |
|
| 255 | ||
| 256 |
# mcmc-GeneralData-OneParExpPrior ---- |
|
| 257 | ||
| 258 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
| 259 |
#' [`OneParExpPrior`] model, it is required that the length of |
|
| 260 |
#' skeleton prior probabilities vector should be equal to the length of the |
|
| 261 |
#' number of doses. |
|
| 262 |
#' |
|
| 263 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
| 264 |
#' when number of observations in `data` is `0`. For some models it might be |
|
| 265 |
#' necessary to specify it manually here though. |
|
| 266 |
#' |
|
| 267 |
#' @aliases mcmc-GeneralData-OneParExpPrior |
|
| 268 |
#' |
|
| 269 |
setMethod( |
|
| 270 |
f = "mcmc", |
|
| 271 |
signature = signature( |
|
| 272 |
data = "GeneralData", |
|
| 273 |
model = "OneParExpPrior", |
|
| 274 |
options = "McmcOptions" |
|
| 275 |
), |
|
| 276 |
def = function(data, model, options, from_prior = data@nObs == 0L, ...) {
|
|
| 277 | 3x |
if (!from_prior) {
|
| 278 | 2x |
assert_true(length(model@skel_probs) == data@nGrid) |
| 279 |
} |
|
| 280 | ||
| 281 | 2x |
callNextMethod( |
| 282 | 2x |
data = data, |
| 283 | 2x |
model = model, |
| 284 | 2x |
options = options, |
| 285 | 2x |
from_prior = from_prior, |
| 286 |
... |
|
| 287 |
) |
|
| 288 |
} |
|
| 289 |
) |
|
| 290 | ||
| 291 |
# nolint start |
|
| 292 | ||
| 293 |
## -------------------------------------------------- |
|
| 294 |
## The method for DataMixture usage |
|
| 295 |
## -------------------------------------------------- |
|
| 296 | ||
| 297 |
##' @describeIn mcmc Method for DataMixture with different from_prior default |
|
| 298 |
setMethod( |
|
| 299 |
"mcmc", |
|
| 300 |
signature = signature( |
|
| 301 |
data = "DataMixture", |
|
| 302 |
model = "GeneralModel", |
|
| 303 |
options = "McmcOptions" |
|
| 304 |
), |
|
| 305 |
def = function( |
|
| 306 |
data, |
|
| 307 |
model, |
|
| 308 |
options, |
|
| 309 |
from_prior = data@nObs == 0L & data@nObsshare == 0L, |
|
| 310 |
... |
|
| 311 |
) {
|
|
| 312 | 8x |
callNextMethod(data, model, options, from_prior = from_prior, ...) |
| 313 |
} |
|
| 314 |
) |
|
| 315 | ||
| 316 | ||
| 317 |
## -------------------------------------------------- |
|
| 318 |
## Replacement for BayesLogit::logit |
|
| 319 |
## -------------------------------------------------- |
|
| 320 | ||
| 321 |
#' Do MCMC sampling for Bayesian logistic regression model |
|
| 322 |
#' |
|
| 323 |
#' @param y 0/1 vector of responses |
|
| 324 |
#' @param X design matrix |
|
| 325 |
#' @param m0 prior mean vector |
|
| 326 |
#' @param P0 precision matrix |
|
| 327 |
#' @param options McmcOptions object |
|
| 328 |
#' |
|
| 329 |
#' @importFrom rjags jags.model jags.samples |
|
| 330 |
#' @return the matrix of samples (samples x parameters) |
|
| 331 |
#' @keywords internal |
|
| 332 |
myBayesLogit <- function(y, X, m0, P0, options) {
|
|
| 333 |
## assertions |
|
| 334 | 46x |
p <- length(m0) |
| 335 | 46x |
nObs <- length(y) |
| 336 | 46x |
stopifnot( |
| 337 | 46x |
is.vector(y), |
| 338 | 46x |
all(y %in% c(0, 1)), |
| 339 | 46x |
is.matrix(P0), |
| 340 | 46x |
identical(dim(P0), c(p, p)), |
| 341 | 46x |
is.matrix(X), |
| 342 | 46x |
identical(dim(X), c(nObs, p)), |
| 343 | 46x |
is(options, "McmcOptions") |
| 344 |
) |
|
| 345 | ||
| 346 |
## get or set the seed |
|
| 347 | 46x |
rSeed <- try(get(".Random.seed", envir = .GlobalEnv), silent = TRUE)
|
| 348 | 46x |
if (is(rSeed, "try-error")) {
|
| 349 | ! |
set.seed(floor(runif(n = 1, min = 0, max = 1e4))) |
| 350 | ! |
rSeed <- get(".Random.seed", envir = .GlobalEnv)
|
| 351 |
} |
|
| 352 |
## .Random.seed contains two leading integers where the second |
|
| 353 |
## gives the position in the following 624 long vector (see |
|
| 354 |
## ?set.seed). Take the current position and ensure positivity |
|
| 355 | 46x |
rSeed <- abs(rSeed[-c(1:2)][rSeed[2]]) |
| 356 | ||
| 357 |
## build the model according to whether we sample from prior |
|
| 358 |
## or not: |
|
| 359 | 46x |
bugsModel <- function() {
|
| 360 | ! |
for (i in 1:nObs) {
|
| 361 | ! |
y[i] ~ dbern(p[i]) |
| 362 | ! |
logit(p[i]) <- mu[i] |
| 363 |
} |
|
| 364 | ||
| 365 | ! |
mu <- X[,] %*% beta |
| 366 | ||
| 367 |
## the multivariate normal prior on the coefficients |
|
| 368 | ! |
beta ~ dmnorm(priorMean[], priorPrec[,]) |
| 369 |
} |
|
| 370 | ||
| 371 |
## write the model file into it |
|
| 372 | 46x |
modelFileName <- h_jags_write_model(bugsModel) |
| 373 | ||
| 374 | 46x |
jagsModel <- rjags::jags.model( |
| 375 | 46x |
modelFileName, |
| 376 | 46x |
data = list( |
| 377 | 46x |
"X" = X, |
| 378 | 46x |
"y" = y, |
| 379 | 46x |
"nObs" = nObs, |
| 380 | 46x |
priorMean = m0, |
| 381 | 46x |
priorPrec = P0 |
| 382 |
), |
|
| 383 | 46x |
quiet = TRUE, |
| 384 |
## add the RNG seed to the inits list: |
|
| 385 |
## (use Mersenne Twister as per R |
|
| 386 |
## default) |
|
| 387 | 46x |
inits = list( |
| 388 | 46x |
.RNG.name = "base::Mersenne-Twister", |
| 389 | 46x |
.RNG.seed = rSeed |
| 390 |
), |
|
| 391 | 46x |
n.chains = 1, |
| 392 | 46x |
n.adapt = 0 |
| 393 |
) |
|
| 394 |
## burn in |
|
| 395 | 46x |
update(jagsModel, n.iter = options@burnin, progress.bar = "none") |
| 396 | ||
| 397 |
## samples |
|
| 398 | 46x |
samplesCode <- "samples <- |
| 399 | 46x |
rjags::jags.samples(model=jagsModel, |
| 400 | 46x |
variable.names='beta', |
| 401 | 46x |
n.iter= |
| 402 | 46x |
(options@iterations - options@burnin), |
| 403 | 46x |
thin=options@step, |
| 404 | 46x |
progress.bar='none')" |
| 405 | ||
| 406 |
## this is necessary because some outputs |
|
| 407 |
## are written directly from the JAGS compiled |
|
| 408 |
## code to the outstream |
|
| 409 | 46x |
capture.output(eval(parse(text = samplesCode))) |
| 410 | ||
| 411 | 46x |
return(t(samples$beta[,, 1L])) |
| 412 |
} |
|
| 413 | ||
| 414 | ||
| 415 |
## ---------------------------------------------------------------------------------- |
|
| 416 |
## Obtain posterior samples for the two-parameter logistic pseudo DLE model |
|
| 417 |
## ------------------------------------------------------------------------------- |
|
| 418 | ||
| 419 |
##' @describeIn mcmc Obtain posterior samples for the model parameters based on the pseudo 'LogisticsIndepBeta' |
|
| 420 |
##' DLE model. The joint prior and posterior probability density function of |
|
| 421 |
##' the intercept \eqn{\phi_1} (phi1) and the slope \eqn{\phi_2} (phi2) are given in Whitehead and
|
|
| 422 |
##' Williamson (1998) and TsuTakawa (1975). However, since asymptotically, the joint posterior probability density |
|
| 423 |
##' will be bivariate normal and we will use the bivariate normal distribution to |
|
| 424 |
##' generate posterior samples of the intercept and the slope parameters. For the prior samples of |
|
| 425 |
##' of the intercept and the slope a bivariate normal distribution with mean and the covariance matrix given in Whitehead and |
|
| 426 |
##' Williamson (1998) is used. |
|
| 427 |
##' |
|
| 428 |
##' @importFrom mvtnorm rmvnorm |
|
| 429 |
##' @example examples/mcmc-LogisticIndepBeta.R |
|
| 430 |
setMethod( |
|
| 431 |
"mcmc", |
|
| 432 |
signature = signature( |
|
| 433 |
data = "Data", |
|
| 434 |
model = "LogisticIndepBeta", |
|
| 435 |
options = "McmcOptions" |
|
| 436 |
), |
|
| 437 |
def = function(data, model, options, ...) {
|
|
| 438 |
## update the DLE model first |
|
| 439 | 55x |
thismodel <- update(object = model, data = data) |
| 440 | ||
| 441 |
## decide whether we sample from the prior or not |
|
| 442 | 55x |
from_prior <- data@nObs == 0L |
| 443 | ||
| 444 |
## probabilities of risk of DLE at all dose levels |
|
| 445 | 55x |
pi <- (thismodel@binDLE) / (thismodel@DLEweights) |
| 446 |
## scalar term for the covariance matrix |
|
| 447 | 55x |
scalarI <- thismodel@DLEweights * pi * (1 - pi) |
| 448 |
## |
|
| 449 | 55x |
precision <- matrix(rep(0, 4), nrow = 2, ncol = 2) |
| 450 | ||
| 451 | 55x |
for (i in (1:(length(thismodel@binDLE)))) {
|
| 452 | 110x |
precisionmat <- scalarI[i] * |
| 453 | 110x |
matrix( |
| 454 | 110x |
c( |
| 455 | 110x |
1, |
| 456 | 110x |
log(thismodel@DLEdose[i]), |
| 457 | 110x |
log(thismodel@DLEdose[i]), |
| 458 | 110x |
(log(thismodel@DLEdose[i]))^2 |
| 459 |
), |
|
| 460 | 110x |
2, |
| 461 | 110x |
2 |
| 462 |
) |
|
| 463 | 110x |
precision <- precision + precisionmat |
| 464 |
} |
|
| 465 | ||
| 466 | 55x |
if (from_prior) {
|
| 467 |
## sample from the (asymptotic) bivariate normal prior for theta |
|
| 468 | ||
| 469 | 9x |
tmp <- mvtnorm::rmvnorm( |
| 470 | 9x |
n = size(options), |
| 471 | 9x |
mean = c(slot(thismodel, "phi1"), slot(thismodel, "phi2")), |
| 472 | 9x |
sigma = solve(precision) |
| 473 |
) |
|
| 474 | ||
| 475 | 9x |
samples <- list( |
| 476 | 9x |
phi1 = tmp[, 1], |
| 477 | 9x |
phi2 = tmp[, 2] |
| 478 |
) |
|
| 479 |
} else {
|
|
| 480 | 46x |
weights <- rep(1, length(data@y)) |
| 481 |
## probabilities of risk of DLE at all dose levels |
|
| 482 | 46x |
pi <- (data@y) / weights |
| 483 |
## scalar term for the covariance matrix |
|
| 484 | 46x |
scalarI <- weights * pi * (1 - pi) |
| 485 |
## |
|
| 486 | ||
| 487 | 46x |
priordle <- thismodel@binDLE |
| 488 | 46x |
priorw1 <- thismodel@DLEweights |
| 489 | ||
| 490 | 46x |
priordose <- thismodel@DLEdose |
| 491 | 46x |
FitDLE <- suppressWarnings(glm( |
| 492 | 46x |
priordle / priorw1 ~ log(priordose), |
| 493 | 46x |
family = binomial(link = "logit"), |
| 494 | 46x |
weights = priorw1 |
| 495 |
)) |
|
| 496 | 46x |
SFitDLE <- summary(FitDLE) |
| 497 |
## Obtain parameter estimates for dose-DLE curve |
|
| 498 | 46x |
priorphi1 <- coef(SFitDLE)[1, 1] |
| 499 | 46x |
priorphi2 <- coef(SFitDLE)[2, 1] |
| 500 | ||
| 501 |
## use fast special sampler here |
|
| 502 |
## set up design matrix |
|
| 503 | 46x |
X <- cbind(1, log(data@x)) |
| 504 | 46x |
initRes <- myBayesLogit( |
| 505 | 46x |
y = data@y, |
| 506 | 46x |
X = X, |
| 507 | 46x |
m0 = c(priorphi1, priorphi2), |
| 508 | 46x |
P0 = precision, |
| 509 | 46x |
options = options |
| 510 |
) |
|
| 511 | ||
| 512 |
## then form the samples list |
|
| 513 | 46x |
samples <- list( |
| 514 | 46x |
phi1 = initRes[, 1], |
| 515 | 46x |
phi2 = initRes[, 2] |
| 516 |
) |
|
| 517 |
} |
|
| 518 | ||
| 519 |
## form a Samples object for return: |
|
| 520 | 55x |
ret <- Samples( |
| 521 | 55x |
data = samples, |
| 522 | 55x |
options = options |
| 523 |
) |
|
| 524 | ||
| 525 | 55x |
return(ret) |
| 526 |
} |
|
| 527 |
) |
|
| 528 | ||
| 529 |
## ================================================================================ |
|
| 530 | ||
| 531 |
## ----------------------------------------------------------------------------------- |
|
| 532 |
## obtain the posterior samples for the Pseudo Efficacy log log model |
|
| 533 |
## ---------------------------------------------------------------------------- |
|
| 534 |
## |
|
| 535 |
##' @describeIn mcmc Obtain the posterior samples for the model parameters in the |
|
| 536 |
##' Efficacy log log model. Given the value of \eqn{\nu}, the precision of the efficacy responses,
|
|
| 537 |
##' the joint prior or the posterior probability of the intercept \eqn{\theta_1} (theta1) and
|
|
| 538 |
##' the slope \eqn{\theta_2} (theta2) is a bivariate normal distribution. The \eqn{\nu} (nu),
|
|
| 539 |
##' the precision of the efficacy responses is either a fixed value or has a gamma distribution. |
|
| 540 |
##' If a gamma distribution is used, the samples of nu will be first generated. |
|
| 541 |
##' Then the mean of the of the nu samples |
|
| 542 |
##' will be used the generate samples of the intercept and slope parameters of the model |
|
| 543 |
##' @example examples/mcmc-Effloglog.R |
|
| 544 |
##' @importFrom mvtnorm rmvnorm |
|
| 545 |
setMethod( |
|
| 546 |
f = "mcmc", |
|
| 547 |
signature = signature( |
|
| 548 |
data = "DataDual", |
|
| 549 |
model = "Effloglog", |
|
| 550 |
options = "McmcOptions" |
|
| 551 |
), |
|
| 552 |
definition = function(data, model, options, ...) {
|
|
| 553 | 30x |
model <- update(object = model, data = data) |
| 554 | 30x |
sample_size <- size(options) |
| 555 | ||
| 556 | 30x |
if (model@use_fixed) {
|
| 557 | ! |
nu <- model@nu |
| 558 | ! |
nu_samples <- rep(nu, sample_size) |
| 559 |
} else {
|
|
| 560 | 30x |
nu_samples <- rgamma( |
| 561 | 30x |
sample_size, |
| 562 | 30x |
shape = model@nu["a"], |
| 563 | 30x |
rate = model@nu["b"] |
| 564 |
) |
|
| 565 | 30x |
nu <- mean(nu_samples) |
| 566 |
} |
|
| 567 | ||
| 568 |
# Sample from the (asymptotic) bivariate normal prior for theta1 and theta2. |
|
| 569 | 30x |
tmp <- mvtnorm::rmvnorm( |
| 570 | 30x |
n = sample_size, |
| 571 | 30x |
mean = model@mu, |
| 572 | 30x |
sigma = solve(nu * model@Q) |
| 573 |
) |
|
| 574 | ||
| 575 | 30x |
samples <- list( |
| 576 | 30x |
theta1 = tmp[, 1], |
| 577 | 30x |
theta2 = tmp[, 2], |
| 578 | 30x |
nu = nu_samples |
| 579 |
) |
|
| 580 | ||
| 581 | 30x |
Samples( |
| 582 | 30x |
data = samples, |
| 583 | 30x |
options = options |
| 584 |
) |
|
| 585 |
} |
|
| 586 |
) |
|
| 587 |
## ====================================================================================== |
|
| 588 |
## ----------------------------------------------------------------------------------- |
|
| 589 |
## obtain the posterior samples for the Pseudo Efficacy Flexible form |
|
| 590 |
## ---------------------------------------------------------------------------- |
|
| 591 |
## |
|
| 592 |
##' @describeIn mcmc Obtain the posterior samples for the estimates in the Efficacy Flexible form. |
|
| 593 |
##' This is the mcmc procedure based on what is described in Lang and Brezger (2004) such that |
|
| 594 |
##' samples of the mean efficacy responses at all dose levels, samples of sigma2 \eqn{sigma^2},
|
|
| 595 |
##' the variance of the efficacy response and samples of sigma2betaW \eqn{sigma^2_{beta_W}}, the variance of
|
|
| 596 |
##' the random walk model will |
|
| 597 |
##' be generated. Please refer to Lang and Brezger (2004) for the procedures and the form of |
|
| 598 |
##' the joint prior and posterior probability density for the mean efficacy responses. In addition, |
|
| 599 |
##' both sigma2 and sigma2betaW can be fixed or having an inverse-gamma prior and posterior distribution. |
|
| 600 |
##' Therefore, if the inverse gamma distribution(s) are used, the parameters in the distribution will be |
|
| 601 |
##' first updated and then samples of sigma2 and sigma2betaW will be generated using the updated parameters. |
|
| 602 |
##' @example examples/mcmc-EffFlexi.R |
|
| 603 |
setMethod( |
|
| 604 |
"mcmc", |
|
| 605 |
signature = signature( |
|
| 606 |
data = "DataDual", |
|
| 607 |
model = "EffFlexi", |
|
| 608 |
options = "McmcOptions" |
|
| 609 |
), |
|
| 610 |
def = function(data, model, options, ...) {
|
|
| 611 |
## update the model |
|
| 612 | 9x |
thismodel <- update(object = model, data = data) |
| 613 | ||
| 614 | 9x |
nSamples <- size(options) |
| 615 | ||
| 616 |
## Prepare samples container |
|
| 617 |
### List parameter samples to save |
|
| 618 | 9x |
samples <- list( |
| 619 | 9x |
ExpEff = matrix(ncol = data@nGrid, nrow = nSamples), |
| 620 | 9x |
sigma2W = matrix(nrow = nSamples), |
| 621 | 9x |
sigma2betaW = matrix(nrow = nSamples) |
| 622 |
) |
|
| 623 |
## Prepare starting values |
|
| 624 |
## Index of the next sample to be saved: |
|
| 625 | ||
| 626 | 9x |
iterSave <- 1L |
| 627 |
## Monitoring the Metropolis-Hastings update for sigma2 |
|
| 628 | ||
| 629 | 9x |
acceptHistory <- list(sigma2W = logical(options@iterations)) |
| 630 | ||
| 631 |
## Current parameter values and also the starting values for the MCMC are set |
|
| 632 |
## EstEff: constant, the average of the observed efficacy values |
|
| 633 | ||
| 634 | 9x |
if (length(data@w) == 0) {
|
| 635 | 2x |
w1 <- thismodel@eff |
| 636 | 2x |
x1 <- thismodel@eff_dose |
| 637 |
} else {
|
|
| 638 |
## Combine pseudo data with observed efficacy responses and no DLT observed |
|
| 639 | 7x |
eff_obsrv <- getEff(data, no_dlt = TRUE) |
| 640 | 7x |
w1 <- c(thismodel@eff, eff_obsrv$w_no_dlt) |
| 641 | 7x |
x1 <- c(thismodel@eff_dose, eff_obsrv$x_no_dlt) |
| 642 |
} |
|
| 643 | 9x |
x1Level <- match_within_tolerance(x1, data@doseGrid) |
| 644 |
## betaW is constant, the average of the efficacy values |
|
| 645 | 9x |
betaW <- rep(mean(w1), data@nGrid) |
| 646 |
## sigma2betaW use fixed value or prior mean |
|
| 647 | 9x |
sigma2betaW <- |
| 648 | 9x |
if (thismodel@use_fixed[["sigma2betaW"]]) {
|
| 649 | 3x |
thismodel@sigma2betaW |
| 650 |
} else {
|
|
| 651 | 6x |
thismodel@sigma2betaW["b"] / (thismodel@sigma2betaW["a"] - 1) |
| 652 |
} |
|
| 653 |
## sigma2: fixed value or just the empirical variance |
|
| 654 | 9x |
sigma2W <- if (thismodel@use_fixed[["sigma2W"]]) {
|
| 655 | ! |
thismodel@sigma2W |
| 656 |
} else {
|
|
| 657 | 9x |
var(w1) |
| 658 |
} |
|
| 659 |
## Set up diagonal matrix with the number of patients in the corresponding dose levels on the diagonal |
|
| 660 | 9x |
designWcrossprod <- crossprod(thismodel@X) |
| 661 | ||
| 662 |
### The MCMC cycle |
|
| 663 | ||
| 664 | 9x |
for (iterMcmc in seq_len(options@iterations)) {
|
| 665 |
## 1) Generate coefficients for the Flexible Efficacy model |
|
| 666 |
## the variance |
|
| 667 | 21912x |
adjustedVar <- sigma2W |
| 668 |
## New precision matrix |
|
| 669 | 21912x |
thisPrecW <- designWcrossprod / adjustedVar + thismodel@RW / sigma2betaW |
| 670 |
## draw random normal vector |
|
| 671 | 21912x |
normVec <- rnorm(data@nGrid) |
| 672 |
## and its Cholesky factor |
|
| 673 | 21912x |
thisPrecWchol <- chol(thisPrecW) |
| 674 |
## solve betaW for L^T * betaW = normVec |
|
| 675 | 21912x |
betaW <- backsolve(r = thisPrecWchol, x = normVec) |
| 676 |
## the residual |
|
| 677 | 21912x |
adjustedW <- w1 - thismodel@X %*% betaW |
| 678 | ||
| 679 |
## forward substitution |
|
| 680 |
## solve L^T * tmp = designW ^T * adjustedW/ adjustedVar |
|
| 681 | ||
| 682 | 21912x |
tmp <- forwardsolve( |
| 683 | 21912x |
l = thisPrecWchol, |
| 684 | 21912x |
x = crossprod(thismodel@X, adjustedW) / adjustedVar, |
| 685 | 21912x |
upper.tri = TRUE, |
| 686 | 21912x |
transpose = TRUE |
| 687 |
) |
|
| 688 |
## Backward substitution solve R*tepNew =tmp |
|
| 689 | 21912x |
tmp <- backsolve( |
| 690 | 21912x |
r = thisPrecWchol, |
| 691 | 21912x |
x = tmp |
| 692 |
) |
|
| 693 | ||
| 694 |
## tmp is the mean vector of the distribution |
|
| 695 |
## add tmp to betaW to obtain final sample |
|
| 696 | ||
| 697 | 21912x |
betaW <- betaW + tmp |
| 698 | ||
| 699 |
## 2) Generate prior variance factor for the random walk |
|
| 700 |
## if fixed, do nothing |
|
| 701 |
## Otherwise sample from full condition |
|
| 702 | ||
| 703 | 21912x |
if (!thismodel@use_fixed[["sigma2betaW"]]) {
|
| 704 | 21732x |
sigma2betaW <- rinvGamma( |
| 705 | 21732x |
n = 1L, |
| 706 | 21732x |
a = thismodel@sigma2betaW["a"] + thismodel@RW_rank / 2, |
| 707 | 21732x |
b = thismodel@sigma2betaW["b"] + |
| 708 | 21732x |
crossprod(betaW, thismodel@RW %*% betaW) / 2 |
| 709 |
) |
|
| 710 |
} |
|
| 711 |
## 3) Generate variance for the flexible efficacy model |
|
| 712 |
## if fixed variance is used |
|
| 713 | 21912x |
if (thismodel@use_fixed[["sigma2W"]]) {
|
| 714 |
## do nothing |
|
| 715 | ! |
acceptHistory$sigma2W[iterMcmc] <- TRUE |
| 716 |
} else {
|
|
| 717 |
## Metropolis-Hastings update step here, using |
|
| 718 |
## an inverse gamma distribution |
|
| 719 | 21912x |
aStar <- thismodel@sigma2W["a"] + length(x1) / 2 |
| 720 |
## Second parameter bStar depends on the value for sigma2W |
|
| 721 | 21912x |
bStar <- function(x) {
|
| 722 | 21912x |
adjW <- w1 |
| 723 | 21912x |
ret <- sum((adjW - betaW[x1Level])^2) / 2 + thismodel@sigma2W["b"] |
| 724 | 21912x |
return(ret) |
| 725 |
} |
|
| 726 |
### Draw proposal: |
|
| 727 | 21912x |
bStarProposal <- bStar(sigma2W) |
| 728 | 21912x |
sigma2W <- rinvGamma(n = 1L, a = aStar, b = bStarProposal) |
| 729 |
} |
|
| 730 | ||
| 731 |
## 4)Save Samples |
|
| 732 | ||
| 733 | 21912x |
if (saveSample(options, iterMcmc)) {
|
| 734 | 10366x |
samples$ExpEff[iterSave, ] <- betaW |
| 735 | 10366x |
samples$sigma2W[iterSave, 1] <- sigma2W |
| 736 | 10366x |
samples$sigma2betaW[iterSave, 1] <- sigma2betaW |
| 737 | 10366x |
iterSave <- iterSave + 1L |
| 738 |
} |
|
| 739 |
} |
|
| 740 | ||
| 741 | 9x |
ret <- Samples( |
| 742 | 9x |
data = samples, |
| 743 | 9x |
options = options |
| 744 |
) |
|
| 745 | 9x |
return(ret) |
| 746 |
} |
|
| 747 |
) |
|
| 748 |
# nolint end |
|
| 749 | ||
| 750 |
## ----------------------------------------------------------------------------------- |
|
| 751 |
## obtain the posterior samples for ordinal models |
|
| 752 |
## ---------------------------------------------------------------------------- |
|
| 753 |
## |
|
| 754 |
##' @describeIn mcmc Obtain the posterior samples for the model parameters in the |
|
| 755 |
##' `LogisticLogNormalOrdinal`. |
|
| 756 |
##' |
|
| 757 |
##' The generic `mcmc` method returns a `Samples` object with elements of the |
|
| 758 |
##' `data` slot named `alpha[1]`, `alpha[2]`, ..., `alpha[k]` and `beta` when |
|
| 759 |
##' passed a `LogisticLogNormalOrdinal` object. This makes the "alpha elements" |
|
| 760 |
##' awkward to access and is inconsistent with other `Model` objects. So rename |
|
| 761 |
##' the alpha elements to `alpha1`, `alpha2`, ..., `alpha<k>` for ease and |
|
| 762 |
##' consistency. |
|
| 763 |
##' |
|
| 764 |
##' @example examples/mcmc-LogisticLogNormalOrdinal.R |
|
| 765 |
setMethod( |
|
| 766 |
f = "mcmc", |
|
| 767 |
signature = signature( |
|
| 768 |
data = "DataOrdinal", |
|
| 769 |
model = "LogisticLogNormalOrdinal", |
|
| 770 |
options = "McmcOptions" |
|
| 771 |
), |
|
| 772 |
definition = function(data, model, options, ...) {
|
|
| 773 |
# Obtain samples using the default method, but ... |
|
| 774 | 17x |
return_value <- callNextMethod() |
| 775 |
# ... rename the alpha elements from alpha[<k>] to alpha<k>, where <k> is an |
|
| 776 |
# integer |
|
| 777 | 17x |
names(return_value@data) <- gsub( |
| 778 | 17x |
"\\[(\\d+)\\]", |
| 779 | 17x |
"\\1", |
| 780 | 17x |
names(return_value@data) |
| 781 |
) |
|
| 782 | 17x |
return_value |
| 783 |
} |
|
| 784 |
) |
| 1 |
# These functions will need to be amended to support the report_label slot if |
|
| 2 |
# and when it is added to NextBest classes |
|
| 3 | ||
| 4 |
# NextBestMTD ---- |
|
| 5 | ||
| 6 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 7 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 8 |
#' @param target_label (`character`)\cr the term used to describe the target |
|
| 9 |
#' toxicity rate |
|
| 10 |
#' @rdname knit_print |
|
| 11 |
#' @export |
|
| 12 |
#' @method knit_print NextBestMTD |
|
| 13 |
knit_print.NextBestMTD <- function( |
|
| 14 |
x, |
|
| 15 |
..., |
|
| 16 |
target_label = "the 25th centile", |
|
| 17 |
tox_label = "toxicity", |
|
| 18 |
asis = TRUE |
|
| 19 |
) {
|
|
| 20 |
# Validate |
|
| 21 | 21x |
assert_flag(asis) |
| 22 | 19x |
assert_character(target_label, len = 1, any.missing = FALSE) |
| 23 | ||
| 24 | 19x |
tox_label <- h_prepare_labels(tox_label) |
| 25 |
# Execute |
|
| 26 | 19x |
rv <- paste0( |
| 27 | 19x |
"The dose level recommended for the next cohort will be selected as follows:\n\n", |
| 28 | 19x |
"- First, ", |
| 29 | 19x |
target_label, |
| 30 | 19x |
" of the posterior distribution of ", |
| 31 | 19x |
tox_label[1], |
| 32 | 19x |
" will be calculated for all dose levels that are eligible according to the ", |
| 33 | 19x |
" Increments rule.\n", |
| 34 | 19x |
"- Next, the \"target dose\" (which may not be part of the dose grid) for which ", |
| 35 | 19x |
target_label, |
| 36 | 19x |
" of the posterior distribution of ", |
| 37 | 19x |
tox_label[1], |
| 38 | 19x |
" is exactly equal to the target rate of ", |
| 39 | 19x |
x@target, |
| 40 | 19x |
" will be determined.\n", |
| 41 | 19x |
"- Finally, the dose level whose absolute distance from the target dose ", |
| 42 | 19x |
"is smallest will be selected as the recommended dose for the next cohort\n\n" |
| 43 |
) |
|
| 44 | ||
| 45 | 19x |
if (asis) {
|
| 46 | 4x |
rv <- knitr::asis_output(rv) |
| 47 |
} |
|
| 48 | 19x |
rv |
| 49 |
} |
|
| 50 | ||
| 51 |
# NextBestNCRM ---- |
|
| 52 | ||
| 53 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 54 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 55 |
#' @rdname knit_print |
|
| 56 |
#' @export |
|
| 57 |
#' @method knit_print NextBestNCRM |
|
| 58 |
knit_print.NextBestNCRM <- function( |
|
| 59 |
x, |
|
| 60 |
..., |
|
| 61 |
tox_label = "toxicity", |
|
| 62 |
asis = TRUE |
|
| 63 |
) {
|
|
| 64 |
# Validate |
|
| 65 | 32x |
assert_flag(asis) |
| 66 | 30x |
assert_character(tox_label, max.len = 2, any.missing = FALSE) |
| 67 | ||
| 68 |
# Execute |
|
| 69 | 30x |
rv <- paste0( |
| 70 | 30x |
"The dose recommended for the next cohort will be chosen in the following ", |
| 71 | 30x |
"way. First, doses that are ineligible according to the increments rule ", |
| 72 | 30x |
"will be discarded. Next, any dose for which the mean posterior probability of ", |
| 73 | 30x |
tox_label, |
| 74 | 30x |
" being in the overdose range - (",
|
| 75 | 30x |
x@overdose[1], |
| 76 |
", ", |
|
| 77 | 30x |
x@overdose[2], |
| 78 | 30x |
"] - is ", |
| 79 | 30x |
x@max_overdose_prob, |
| 80 | 30x |
" or more will also be discarded. Finally, the dose amongst those remaining ", |
| 81 | 30x |
"which has the highest chance that the mean posterior probability of ", |
| 82 | 30x |
tox_label, |
| 83 | 30x |
" is in the target ", |
| 84 | 30x |
tox_label, |
| 85 | 30x |
" range of ", |
| 86 | 30x |
x@target[1], |
| 87 | 30x |
" to ", |
| 88 | 30x |
x@target[2], |
| 89 | 30x |
" (inclusive) will be selected.\n\n" |
| 90 |
) |
|
| 91 | ||
| 92 | 30x |
if (asis) {
|
| 93 | 2x |
rv <- knitr::asis_output(rv) |
| 94 |
} |
|
| 95 | 30x |
rv |
| 96 |
} |
|
| 97 | ||
| 98 |
# NextBestThreePlusThree ---- |
|
| 99 | ||
| 100 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 101 |
#' @param label (`character`)\cr The term used to label the participants. |
|
| 102 |
#' @param tox_label (`character`)\cr the term used to describe toxicity. See |
|
| 103 |
#' Usage Notes below. |
|
| 104 |
#' See Usage Notes below. |
|
| 105 |
#' @section Usage Notes: |
|
| 106 |
#' This section describes the use of `label` and `tox_label`, collectively |
|
| 107 |
#' referred to as `label`s. |
|
| 108 |
#' A `label` should be a scalar or a vector of length 2. If a scalar, it is |
|
| 109 |
#' converted by adding a second element that is equal to the first, suffixed by `s`. |
|
| 110 |
#' For example, `tox_label = "DLT"` becomes `tox_label = c("DLT", "DLTs")`. The
|
|
| 111 |
#' first element of the vector is used to describe a count of 1. The second |
|
| 112 |
#' is used in all other cases. |
|
| 113 |
#' @rdname knit_print |
|
| 114 |
#' @export |
|
| 115 |
#' @method knit_print NextBestThreePlusThree |
|
| 116 |
knit_print.NextBestThreePlusThree <- function( |
|
| 117 |
x, |
|
| 118 |
..., |
|
| 119 |
tox_label = c("toxicity", "toxicities"),
|
|
| 120 |
label = "participant", |
|
| 121 |
asis = TRUE |
|
| 122 |
) {
|
|
| 123 |
# Validate |
|
| 124 | 14x |
assert_flag(asis) |
| 125 | ||
| 126 |
# Prepare |
|
| 127 | 12x |
tox_label <- h_prepare_labels(tox_label) |
| 128 | 12x |
label <- h_prepare_labels(label) |
| 129 | ||
| 130 |
# Execute |
|
| 131 | 12x |
rv <- paste0( |
| 132 | 12x |
"The dose recommended for the next cohort will be chosen using the \"Three ", |
| 133 | 12x |
"Plus Three\" rule.\n\n- If no ", |
| 134 | 12x |
tox_label[2], |
| 135 | 12x |
" have been reported at the current dose level, escalate by one dose level.\n", |
| 136 | 12x |
"- If the observed ", |
| 137 | 12x |
tox_label[1], |
| 138 | 12x |
" rate at the current dose level is exactly 1/3 and no more than three ", |
| 139 | 12x |
label[2], |
| 140 | 12x |
" treated at the current dose level are evaluable, remain at the current ", |
| 141 | 12x |
"dose level.\n", |
| 142 | 12x |
"- Otherwise, recommend that the trial stops and identify the MTD as dose ", |
| 143 | 12x |
"level immediately below the current one.\n\n" |
| 144 |
) |
|
| 145 | ||
| 146 | 12x |
if (asis) {
|
| 147 | 2x |
rv <- knitr::asis_output(rv) |
| 148 |
} |
|
| 149 | 12x |
rv |
| 150 |
} |
|
| 151 | ||
| 152 |
# NextBestDualEndpoint ---- |
|
| 153 | ||
| 154 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 155 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 156 |
#' @param biomarker_label (`character`)\cr the term used to describe the biomarker |
|
| 157 |
#' @param biomarker_units (`character`)\cr the units in which the biomarker is |
|
| 158 |
#' measured |
|
| 159 |
#' @rdname knit_print |
|
| 160 |
#' @export |
|
| 161 |
#' @method knit_print NextBestDualEndpoint |
|
| 162 |
knit_print.NextBestDualEndpoint <- function( |
|
| 163 |
x, |
|
| 164 |
..., |
|
| 165 |
tox_label = "toxicity", |
|
| 166 |
biomarker_label = "the biomarker", |
|
| 167 |
biomarker_units = ifelse(x@target_relative, "%", ""), |
|
| 168 |
asis = TRUE |
|
| 169 |
) {
|
|
| 170 | 14x |
assert_flag(asis) |
| 171 | 12x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 172 | 12x |
assert_character(biomarker_label, len = 1, any.missing = FALSE) |
| 173 | 12x |
assert_character(biomarker_units, len = 1, any.missing = FALSE) |
| 174 | ||
| 175 | 12x |
rv <- paste0( |
| 176 | 12x |
"The dose recommended for the next cohort will be chosen in the following ", |
| 177 | 12x |
"way. First, doses that are ineligible according to the increments rule ", |
| 178 | 12x |
"will be discarded. Next, any dose for which the mean posterior probability of ", |
| 179 | 12x |
tox_label, |
| 180 | 12x |
" being in the overdose range - (",
|
| 181 | 12x |
x@overdose[1], |
| 182 |
", ", |
|
| 183 | 12x |
x@overdose[2], |
| 184 | 12x |
"] - is ", |
| 185 | 12x |
x@max_overdose_prob, |
| 186 | 12x |
" or more will also be discarded. Finally, the dose amongst those remaining ", |
| 187 | 12x |
"which has the highest chance that the mean posterior probability that ", |
| 188 | 12x |
biomarker_label, |
| 189 | 12x |
" is in the target range for ", |
| 190 | 12x |
biomarker_label, |
| 191 | 12x |
", which is ", |
| 192 | 12x |
x@target[1], |
| 193 | 12x |
ifelse( |
| 194 | 12x |
x@target_relative, |
| 195 |
"", |
|
| 196 | 12x |
stringr::str_squish(paste0(" ", biomarker_units))
|
| 197 |
), |
|
| 198 | 12x |
" to ", |
| 199 | 12x |
x@target[2], |
| 200 | 12x |
ifelse( |
| 201 | 12x |
x@target_relative, |
| 202 |
"", |
|
| 203 | 12x |
stringr::str_squish(paste0(" ", biomarker_units))
|
| 204 |
), |
|
| 205 | 12x |
" (inclusive),", |
| 206 | 12x |
ifelse( |
| 207 | 12x |
x@target_relative, |
| 208 | 12x |
paste0(" of the maximum ", biomarker_label, " value"),
|
| 209 |
"" |
|
| 210 |
), |
|
| 211 | 12x |
" will be selected, provided that this probability exceeds ", |
| 212 | 12x |
x@target_thresh, |
| 213 | 12x |
". If no dose meets this threshold, then the highest eligible dose will ", |
| 214 | 12x |
"be selected.\n\n" |
| 215 |
) |
|
| 216 | ||
| 217 | 12x |
if (asis) {
|
| 218 | 2x |
rv <- knitr::asis_output(rv) |
| 219 |
} |
|
| 220 | 12x |
rv |
| 221 |
} |
|
| 222 | ||
| 223 |
# NextBestMinDist ---- |
|
| 224 | ||
| 225 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 226 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 227 |
#' @rdname knit_print |
|
| 228 |
#' @export |
|
| 229 |
#' @method knit_print NextBestMinDist |
|
| 230 |
knit_print.NextBestMinDist <- function( |
|
| 231 |
x, |
|
| 232 |
..., |
|
| 233 |
tox_label = "toxicity", |
|
| 234 |
asis = TRUE |
|
| 235 |
) {
|
|
| 236 |
# Validate |
|
| 237 | 8x |
assert_flag(asis) |
| 238 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 239 | ||
| 240 |
# Execute |
|
| 241 | 6x |
rv <- paste0( |
| 242 | 6x |
"The dose recommended for the next cohort will be the one which is both ", |
| 243 | 6x |
"eligible and which has the smallest absolute difference between ", |
| 244 | 6x |
"its mean posterior estimate of the probability of ", |
| 245 | 6x |
tox_label, |
| 246 | 6x |
" and the target ", |
| 247 | 6x |
tox_label, |
| 248 | 6x |
" rate [", |
| 249 | 6x |
x@target, |
| 250 | 6x |
"].\n\n" |
| 251 |
) |
|
| 252 | ||
| 253 | 6x |
if (asis) {
|
| 254 | 2x |
rv <- knitr::asis_output(rv) |
| 255 |
} |
|
| 256 | 6x |
rv |
| 257 |
} |
|
| 258 | ||
| 259 |
# NextBestInfTheory ---- |
|
| 260 | ||
| 261 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 262 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 263 |
#' @param citation_text (`character`)\cr the text used to cite Mozgunov & Jaki |
|
| 264 |
#' @param citation_link (`character`)\cr the link to Mozgunov & Jaki |
|
| 265 |
#' @section Usage Notes: |
|
| 266 |
#' To use a BibTeX-style citation, specify (for example) `citation_text = |
|
| 267 |
#' "@MOZGUNOV", citation_link = ""`. |
|
| 268 |
#' @rdname knit_print |
|
| 269 |
#' @export |
|
| 270 |
#' @method knit_print NextBestInfTheory |
|
| 271 |
knit_print.NextBestInfTheory <- function( |
|
| 272 |
x, |
|
| 273 |
..., |
|
| 274 |
tox_label = "toxicity", |
|
| 275 |
citation_text = "Mozgunov & Jaki (2019)", |
|
| 276 |
citation_link = "https://doi.org/10.1002/sim.8450", |
|
| 277 |
asis = TRUE |
|
| 278 |
) {
|
|
| 279 |
# Validate |
|
| 280 | 8x |
assert_flag(asis) |
| 281 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 282 | 6x |
assert_character(citation_text, len = 1, any.missing = FALSE) |
| 283 | 6x |
assert_character(citation_link, len = 1, any.missing = FALSE) |
| 284 | ||
| 285 |
# Execute |
|
| 286 | 6x |
rv <- paste0( |
| 287 | 6x |
"The recommended dose for the next cohort will be chosen using the ", |
| 288 | 6x |
"complex infinite bounds penalisation (CIBP) criterion of ", |
| 289 |
"[", |
|
| 290 | 6x |
citation_text, |
| 291 |
"]", |
|
| 292 | 6x |
ifelse(nchar(citation_link) > 0, paste0("(", citation_link, ")"), ""),
|
| 293 | 6x |
". Let\n\n", |
| 294 | 6x |
"$$ \\delta(\\hat{p}_d, \\gamma) = \\frac{(\\hat{p}_d - \\gamma)^2}",
|
| 295 | 6x |
"{\\hat{p}_d^a \\cdot (1 - \\hat{p}_d)^{2 - a}} $$\n\n",
|
| 296 | 6x |
"where a is the non-centrality parameter with a value of ", |
| 297 | 6x |
x@asymmetry, |
| 298 | 6x |
", γ is the target ", |
| 299 | 6x |
tox_label, |
| 300 | 6x |
" rate with a value of ", |
| 301 | 6x |
x@target, |
| 302 | 6x |
" and $\\hat{p}_d$ is the mean posterior estimate of the probability of ",
|
| 303 | 6x |
tox_label, |
| 304 | 6x |
" at dose level d.\n\n", |
| 305 | 6x |
"The recommended dose for the next cohort will be ", |
| 306 | 6x |
"the value of d that minimises $\\delta(\\hat{p}_d, \\gamma)$.\n\n"
|
| 307 |
) |
|
| 308 | ||
| 309 | 6x |
if (asis) {
|
| 310 | 2x |
rv <- knitr::asis_output(rv) |
| 311 |
} |
|
| 312 | 6x |
rv |
| 313 |
} |
|
| 314 | ||
| 315 |
# NextBestTD ---- |
|
| 316 | ||
| 317 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 318 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 319 |
#' @rdname knit_print |
|
| 320 |
#' @export |
|
| 321 |
#' @method knit_print NextBestTD |
|
| 322 |
knit_print.NextBestTD <- function( |
|
| 323 |
x, |
|
| 324 |
..., |
|
| 325 |
tox_label = "toxicity", |
|
| 326 |
asis = TRUE |
|
| 327 |
) {
|
|
| 328 |
# Validate |
|
| 329 | 14x |
assert_flag(asis) |
| 330 | 12x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 331 | ||
| 332 |
# Execute |
|
| 333 | 12x |
rv <- paste0( |
| 334 | 12x |
"The dose recommended for the next cohort will be the one which is both ", |
| 335 | 12x |
"eligible and which is the highest dose in the dose grid strictly less than ", |
| 336 | 12x |
"the dose (which may not be in the dose grid) that has a posterior plug-in ", |
| 337 | 12x |
"estimate of the probability of ", |
| 338 | 12x |
tox_label, |
| 339 | 12x |
" exactly equal to the target ", |
| 340 | 12x |
tox_label, |
| 341 | 12x |
" rate, either during [", |
| 342 | 12x |
x@prob_target_drt, |
| 343 | 12x |
"] or at the end of the trial [", |
| 344 | 12x |
x@prob_target_eot, |
| 345 | 12x |
"].\n\n" |
| 346 |
) |
|
| 347 | ||
| 348 | 12x |
if (asis) {
|
| 349 | 2x |
rv <- knitr::asis_output(rv) |
| 350 |
} |
|
| 351 | 12x |
rv |
| 352 |
} |
|
| 353 | ||
| 354 |
# NextBestMaxGain ---- |
|
| 355 | ||
| 356 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 357 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 358 |
#' @rdname knit_print |
|
| 359 |
#' @export |
|
| 360 |
#' @method knit_print NextBestMaxGain |
|
| 361 |
knit_print.NextBestMaxGain <- function( |
|
| 362 |
x, |
|
| 363 |
..., |
|
| 364 |
tox_label = "toxicity", |
|
| 365 |
asis = TRUE |
|
| 366 |
) {
|
|
| 367 |
# Validate |
|
| 368 | 14x |
assert_flag(asis) |
| 369 | 12x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 370 | ||
| 371 |
# Execute |
|
| 372 | 12x |
rv <- paste0( |
| 373 | 12x |
"The dose recommended for the next cohort will be the one which is closest to ", |
| 374 | 12x |
"Gstar, the dose that maximises the gain for probability of ", |
| 375 | 12x |
tox_label, |
| 376 | 12x |
" exactly equal to the target ", |
| 377 | 12x |
tox_label, |
| 378 | 12x |
" rate, either during [", |
| 379 | 12x |
x@prob_target_drt, |
| 380 | 12x |
"] or at the end of the trial [", |
| 381 | 12x |
x@prob_target_eot, |
| 382 | 12x |
"].\n\n" |
| 383 |
) |
|
| 384 | ||
| 385 | 12x |
if (asis) {
|
| 386 | 2x |
rv <- knitr::asis_output(rv) |
| 387 |
} |
|
| 388 | 12x |
rv |
| 389 |
} |
|
| 390 | ||
| 391 |
# NextBestProbMTDLTE ---- |
|
| 392 | ||
| 393 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 394 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 395 |
#' @rdname knit_print |
|
| 396 |
#' @export |
|
| 397 |
#' @method knit_print NextBestProbMTDLTE |
|
| 398 |
knit_print.NextBestProbMTDLTE <- function( |
|
| 399 |
x, |
|
| 400 |
..., |
|
| 401 |
tox_label = "toxicity", |
|
| 402 |
asis = TRUE |
|
| 403 |
) {
|
|
| 404 |
# Validate |
|
| 405 | 8x |
assert_flag(asis) |
| 406 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 407 | ||
| 408 |
# Execute |
|
| 409 | 6x |
rv <- paste0( |
| 410 | 6x |
"The dose recommended for the next cohort will be the dose level with ", |
| 411 | 6x |
"the highest probability of being the highest dose with an estimated ", |
| 412 | 6x |
"probability of ", |
| 413 | 6x |
tox_label, |
| 414 | 6x |
" less than or equal to ", |
| 415 | 6x |
x@target, |
| 416 | 6x |
".\n\n" |
| 417 |
) |
|
| 418 | ||
| 419 | 6x |
if (asis) {
|
| 420 | 2x |
rv <- knitr::asis_output(rv) |
| 421 |
} |
|
| 422 | 6x |
rv |
| 423 |
} |
|
| 424 | ||
| 425 |
# NextBestProbMTDMinDist ---- |
|
| 426 | ||
| 427 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 428 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 429 |
#' @rdname knit_print |
|
| 430 |
#' @export |
|
| 431 |
#' @method knit_print NextBestProbMTDMinDist |
|
| 432 |
knit_print.NextBestProbMTDMinDist <- function( |
|
| 433 |
x, |
|
| 434 |
..., |
|
| 435 |
tox_label = "toxicity", |
|
| 436 |
asis = TRUE |
|
| 437 |
) {
|
|
| 438 |
# Validate |
|
| 439 | 8x |
assert_flag(asis) |
| 440 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 441 | ||
| 442 |
# Execute |
|
| 443 | 6x |
rv <- paste0( |
| 444 | 6x |
"The dose recommended for the next cohort will be the dose level with ", |
| 445 | 6x |
"the highest probability of being the highest dose with an estimated ", |
| 446 | 6x |
"probability of ", |
| 447 | 6x |
tox_label, |
| 448 | 6x |
" closest to ", |
| 449 | 6x |
x@target, |
| 450 | 6x |
".\n\n" |
| 451 |
) |
|
| 452 | ||
| 453 | 6x |
if (asis) {
|
| 454 | 2x |
rv <- knitr::asis_output(rv) |
| 455 |
} |
|
| 456 | 6x |
rv |
| 457 |
} |
|
| 458 | ||
| 459 |
# NextBestNCRMLoss ---- |
|
| 460 | ||
| 461 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 462 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 463 |
#' @param format_func (`function`)\cr The function used to format the range table. |
|
| 464 |
#' @importFrom rlang .data |
|
| 465 |
#' @rdname knit_print |
|
| 466 |
#' @export |
|
| 467 |
#' @method knit_print NextBestNCRMLoss |
|
| 468 |
knit_print.NextBestNCRMLoss <- function( |
|
| 469 |
x, |
|
| 470 |
..., |
|
| 471 |
tox_label = "toxicity", |
|
| 472 |
asis = TRUE, |
|
| 473 |
format_func = h_knit_format_func |
|
| 474 |
) {
|
|
| 475 |
# Validate |
|
| 476 | 8x |
assert_flag(asis) |
| 477 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 478 | ||
| 479 |
# Execute |
|
| 480 | 6x |
param <- list(...) |
| 481 | 6x |
param[["x"]] <- x %>% |
| 482 | 6x |
tidy() %>% |
| 483 | 6x |
dplyr::select(-MaxOverdoseProb) |
| 484 | 6x |
param[["col.names"]] <- c("Range", "Lower", "Upper", "Loss Coefficient")
|
| 485 | 6x |
rv <- paste0( |
| 486 | 6x |
"The dose recommended for the next cohort will be chosen in the following ", |
| 487 | 6x |
"way:\n\n- First, the chance that the probability of ", |
| 488 | 6x |
tox_label, |
| 489 | 6x |
" falls into each of the underdose, target ", |
| 490 | 6x |
ifelse( |
| 491 | 6x |
any(x@unacceptable != c(1, 1)), |
| 492 | 6x |
", overdose and unacceptable", |
| 493 | 6x |
" and overdose" |
| 494 |
), |
|
| 495 | 6x |
" dose ranges is calculated for element of the dose grid.\n", |
| 496 | 6x |
"- Next, the loss associated with each dose is calculated by multiplying ", |
| 497 | 6x |
"these probabilities by the corresponding loss coefficient and summing the result.\n", |
| 498 | 6x |
"- Then ineligible doses, and those with a probability of being in the ", |
| 499 | 6x |
ifelse( |
| 500 | 6x |
length(x@losses) == 3, |
| 501 | 6x |
"overdose range", |
| 502 | 6x |
"overdose or unaccaptable ranges" |
| 503 |
), |
|
| 504 | 6x |
" that is greater than ", |
| 505 | 6x |
x@max_overdose_prob, |
| 506 | 6x |
", are discarded.\n", |
| 507 | 6x |
"- Finally, the dose level with the smallest loss is selected as the ", |
| 508 | 6x |
"recommended dose for the next cohort.\n\n", |
| 509 | 6x |
ifelse( |
| 510 | 6x |
toupper(tox_label) == tox_label, |
| 511 | 6x |
tox_label, |
| 512 | 6x |
stringr::str_to_sentence(tox_label) |
| 513 |
), |
|
| 514 | 6x |
" ranges and loss coefficients are given in the following table:\n\n", |
| 515 | 6x |
paste((do.call(knitr::kable, param)) %>% format_func(), collapse = "\n"), |
| 516 | 6x |
"\n\n" |
| 517 |
) |
|
| 518 | ||
| 519 | 6x |
if (asis) {
|
| 520 | 2x |
rv <- knitr::asis_output(rv) |
| 521 |
} |
|
| 522 | 6x |
rv |
| 523 |
} |
|
| 524 | ||
| 525 |
# NextBestTDsamples ---- |
|
| 526 | ||
| 527 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 528 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 529 |
#' @rdname knit_print |
|
| 530 |
#' @export |
|
| 531 |
#' @method knit_print NextBestTDsamples |
|
| 532 |
knit_print.NextBestTDsamples <- function( |
|
| 533 |
x, |
|
| 534 |
..., |
|
| 535 |
tox_label = "toxicity", |
|
| 536 |
asis = TRUE |
|
| 537 |
) {
|
|
| 538 |
# Validate |
|
| 539 | 12x |
assert_flag(asis) |
| 540 | 10x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 541 | ||
| 542 |
# Execute |
|
| 543 | 10x |
rv <- paste0( |
| 544 | 10x |
"The dose recommended for the next cohort will be the one which is both ", |
| 545 | 10x |
"eligible and which is the highest dose in the dose grid strictly less than ", |
| 546 | 10x |
"the dose (which may not be in the dose grid) that has a full Bayes posterior ", |
| 547 | 10x |
"estimate of the probability of ", |
| 548 | 10x |
tox_label, |
| 549 | 10x |
" exactly equal to the target ", |
| 550 | 10x |
tox_label, |
| 551 | 10x |
" rate, either during [", |
| 552 | 10x |
x@prob_target_drt, |
| 553 | 10x |
"] or at the end of the trial [", |
| 554 | 10x |
x@prob_target_eot, |
| 555 | 10x |
"].\n\n" |
| 556 |
) |
|
| 557 | ||
| 558 | 10x |
if (asis) {
|
| 559 | 2x |
rv <- knitr::asis_output(rv) |
| 560 |
} |
|
| 561 | 10x |
rv |
| 562 |
} |
|
| 563 | ||
| 564 | ||
| 565 |
# NextBestMaxGainSamples ---- |
|
| 566 | ||
| 567 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 568 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 569 |
#' @rdname knit_print |
|
| 570 |
#' @export |
|
| 571 |
#' @method knit_print NextBestMaxGainSamples |
|
| 572 |
knit_print.NextBestMaxGainSamples <- function( |
|
| 573 |
x, |
|
| 574 |
..., |
|
| 575 |
tox_label = "toxicity", |
|
| 576 |
asis = TRUE |
|
| 577 |
) {
|
|
| 578 |
# Validate |
|
| 579 | 14x |
assert_flag(asis) |
| 580 | 12x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 581 | ||
| 582 |
# Execute |
|
| 583 | 12x |
rv <- paste0( |
| 584 | 12x |
"The dose recommended for the next cohort will be the one which is closest to ", |
| 585 | 12x |
"Gstar, the dose for which the full Bayes posterior estimate of the probability of ", |
| 586 | 12x |
tox_label, |
| 587 | 12x |
" maximises the gain relative to the target ", |
| 588 | 12x |
tox_label, |
| 589 | 12x |
" rate, either during [", |
| 590 | 12x |
x@prob_target_drt, |
| 591 | 12x |
"] or at the end of the trial [", |
| 592 | 12x |
x@prob_target_eot, |
| 593 | 12x |
"].\n\n" |
| 594 |
) |
|
| 595 | ||
| 596 | 12x |
if (asis) {
|
| 597 | 2x |
rv <- knitr::asis_output(rv) |
| 598 |
} |
|
| 599 | 12x |
rv |
| 600 |
} |
|
| 601 | ||
| 602 |
# NextBestOrdinal ---- |
|
| 603 | ||
| 604 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 605 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 606 |
#' @rdname knit_print |
|
| 607 |
#' @export |
|
| 608 |
#' @method knit_print NextBestOrdinal |
|
| 609 |
knit_print.NextBestOrdinal <- function( |
|
| 610 |
x, |
|
| 611 |
..., |
|
| 612 |
tox_label = "toxicity", |
|
| 613 |
asis = TRUE |
|
| 614 |
) {
|
|
| 615 | 19x |
assert_flag(asis) |
| 616 | 17x |
assert_character(tox_label, max.len = 2, any.missing = FALSE) |
| 617 | ||
| 618 | 17x |
tox_label <- h_prepare_labels(tox_label) |
| 619 | 17x |
rv <- paste0( |
| 620 | 17x |
"Based on a ", |
| 621 | 17x |
tox_label[1], |
| 622 | 17x |
" grade of ", |
| 623 | 17x |
x@grade, |
| 624 |
": ", |
|
| 625 | 17x |
paste0( |
| 626 | 17x |
knit_print(x@rule, asis = asis, tox_label = tox_label, ...), |
| 627 | 17x |
collapse = "\n" |
| 628 |
), |
|
| 629 | 17x |
"\n\n" |
| 630 |
) |
|
| 631 | ||
| 632 | 17x |
if (asis) {
|
| 633 | 2x |
rv <- knitr::asis_output(rv) |
| 634 |
} |
|
| 635 | 17x |
rv |
| 636 |
} |
| 1 |
# Generics ---- |
|
| 2 | ||
| 3 |
#' Obtain a Text Representation of the Reference Dose |
|
| 4 |
#' |
|
| 5 |
#' This is a helper method used `knit_print` for `crmPack` classes. |
|
| 6 |
#' |
|
| 7 |
#' @param x (`GeneralModel`)\cr the model object that will be printed |
|
| 8 |
#' @param ... Not used at present |
|
| 9 |
#' @return A character string containing a LaTeX rendition of the model. |
|
| 10 |
#' @noRd |
|
| 11 |
h_knit_print_render_ref_dose <- function(x, ...) {
|
|
| 12 | 90x |
UseMethod("h_knit_print_render_ref_dose")
|
| 13 |
} |
|
| 14 | ||
| 15 |
#' Render a Model Function in a `knit_print` Method |
|
| 16 |
#' |
|
| 17 |
#' This is a helper method used `knit_print` for `crmPack` classes. |
|
| 18 |
#' |
|
| 19 |
#' @param x (`GeneralModel`)\cr the model object that will be printed |
|
| 20 |
#' @param ... Not used at present |
|
| 21 |
#' @return A character string containing a LaTeX rendition of the model. |
|
| 22 |
#' @noRd |
|
| 23 |
h_knit_print_render_model <- function(x, ...) {
|
|
| 24 | 90x |
UseMethod("h_knit_print_render_model")
|
| 25 |
} |
|
| 26 | ||
| 27 |
#' Obtain a Text Representation of a Biomarker Model |
|
| 28 |
#' |
|
| 29 |
#' This is a helper method used `knit_print` for `DualEndpoint` classes. |
|
| 30 |
#' |
|
| 31 |
#' @param x (`DualEndpoint`)\cr the model object containing the biomarker model |
|
| 32 |
#' @param use_values (`flag`)\cr print the values associated with hyperparameters, |
|
| 33 |
#' or the symbols used to define the hyper-parameters. That is, for example, mu or 1. |
|
| 34 |
#' @param ... Not used at present |
|
| 35 |
#' @return A character string containing a LaTeX rendition of the model. |
|
| 36 |
#' @noRd |
|
| 37 |
h_knit_print_render_biomarker_model <- function(x, use_values = TRUE, ...) {
|
|
| 38 | 18x |
UseMethod("h_knit_print_render_biomarker_model")
|
| 39 |
} |
|
| 40 | ||
| 41 |
# Methods ---- |
|
| 42 | ||
| 43 |
# DualEndpoint ---- |
|
| 44 | ||
| 45 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 46 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 47 |
#' @param biomarker_label (`character`)\cr A description of the biomarker |
|
| 48 |
#' @rdname knit_print |
|
| 49 |
#' @export |
|
| 50 |
#' @method knit_print DualEndpoint |
|
| 51 |
knit_print.DualEndpoint <- function( |
|
| 52 |
x, |
|
| 53 |
..., |
|
| 54 |
asis = TRUE, |
|
| 55 |
use_values = TRUE, |
|
| 56 |
fmt = "%5.2f", |
|
| 57 |
units = NA, |
|
| 58 |
tox_label = "toxicity", |
|
| 59 |
biomarker_label = "PD biomarker" |
|
| 60 |
) {
|
|
| 61 |
# Validate |
|
| 62 | 24x |
assert_flag(asis) |
| 63 | 18x |
assert_flag(use_values) |
| 64 | 18x |
assert_format(fmt) |
| 65 |
# Initialise |
|
| 66 | 18x |
biomarker_label <- h_prepare_labels(biomarker_label) |
| 67 | 18x |
tox_label <- h_prepare_labels(tox_label) |
| 68 | 18x |
units <- h_prepare_units(units) |
| 69 |
# Execute |
|
| 70 | 18x |
toxModel <- ProbitLogNormal( |
| 71 | 18x |
cov = x@betaZ_params@cov, |
| 72 | 18x |
mean = x@betaZ_params@mean, |
| 73 | 18x |
ref_dose = x@ref_dose |
| 74 |
) |
|
| 75 | 18x |
rv <- paste0( |
| 76 | 18x |
"The relationships between dose and ", |
| 77 | 18x |
tox_label[1], |
| 78 | 18x |
" and between dose and ", |
| 79 | 18x |
biomarker_label[1], |
| 80 | 18x |
" will be modelled simultaneously.\n\n", |
| 81 | 18x |
knit_print( |
| 82 | 18x |
toxModel, |
| 83 | 18x |
asis = asis, |
| 84 | 18x |
tox_label = tox_label, |
| 85 | 18x |
use_values = use_values, |
| 86 | 18x |
fmt = fmt, |
| 87 | 18x |
units = units, |
| 88 |
... |
|
| 89 |
), |
|
| 90 | 18x |
"\n\n", |
| 91 | 18x |
"The ", |
| 92 | 18x |
biomarker_label[1], |
| 93 | 18x |
" response `w` at dose `d` is modelled as ", |
| 94 | 18x |
"$$ w(d) \\sim N(f(d), \\sigma_w^2) $$ \n\nwhere ", |
| 95 | 18x |
h_knit_print_render_biomarker_model(x, use_values = use_values, ...) |
| 96 |
) |
|
| 97 | 18x |
if (asis) {
|
| 98 | 6x |
rv <- knitr::asis_output(rv) |
| 99 |
} |
|
| 100 | 18x |
rv |
| 101 |
} |
|
| 102 | ||
| 103 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 104 |
#' @noRd |
|
| 105 |
h_knit_print_render_biomarker_model.DualEndpoint <- function( |
|
| 106 |
x, |
|
| 107 |
..., |
|
| 108 |
use_values = TRUE |
|
| 109 |
) {
|
|
| 110 | ! |
"f(d) is a function of dose that is defined elsewhere." |
| 111 |
} |
|
| 112 | ||
| 113 |
# DualEndpointBeta ---- |
|
| 114 | ||
| 115 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 116 |
#' @noRd |
|
| 117 |
h_knit_print_render_biomarker_model.DualEndpointBeta <- function(x, ...) {
|
|
| 118 | 4x |
paste0( |
| 119 | 4x |
"f(d) is a parametric rescaled beta density function such that\n\n", |
| 120 | 4x |
"$$ f(d) = ", |
| 121 | 4x |
"E_0 + (E_{max} - E_0) \\times Beta(\\delta_1, \\delta_2) \\times ",
|
| 122 | 4x |
"\\left(\\frac{d}{d_{max}}\\right)^{\\delta_1} \\times \\left(1 - ",
|
| 123 | 4x |
"\\frac{d}{d_{max}}\\right)^{\\delta_2} $$\n\n",
|
| 124 | 4x |
"where d~max~ is the maximum dose in the dose grid, δ~1~ and ", |
| 125 | 4x |
"δ~2~ are the parameters of the Beta function and ", |
| 126 | 4x |
"E~0~ and E~max~ are, respectively, the minimum and maximum levels of the ", |
| 127 | 4x |
"biomarker. The mode can be written as \n\n", |
| 128 | 4x |
"$$ \\text{mode} = \\frac{\\delta_1}{\\delta_1 + \\delta_2} $$\n\n",
|
| 129 | 4x |
" and this is the parameterisation used to define the model.\n\n", |
| 130 | 4x |
"In this case, \n\n", |
| 131 | 4x |
ifelse( |
| 132 | 4x |
length(x@E0) == 1, |
| 133 | 4x |
paste0("$$ E_0 = ", x@E0, " $$\n\n)"),
|
| 134 | 4x |
paste0("$$ E_0 \\sim U(", x@E0[1], ", ", x@E0[2], ") $$\n\n")
|
| 135 |
), |
|
| 136 | 4x |
ifelse( |
| 137 | 4x |
length(x@Emax) == 1, |
| 138 | 4x |
paste0("$$ E_{max} = ", x@Emax, " $$\n\n)"),
|
| 139 | 4x |
paste0("$$ E_{max} \\sim U(", x@Emax[1], ", ", x@Emax[2], ") $$\n\n")
|
| 140 |
), |
|
| 141 | 4x |
ifelse( |
| 142 | 4x |
length(x@delta1) == 1, |
| 143 | 4x |
paste0("$$ \\delta_1 = ", x@delta1, " $$\n\n)"),
|
| 144 | 4x |
paste0( |
| 145 | 4x |
"$$ \\delta_1 \\sim U(",
|
| 146 | 4x |
x@delta1[1], |
| 147 |
", ", |
|
| 148 | 4x |
x@delta1[2], |
| 149 | 4x |
") $$\n\n" |
| 150 |
) |
|
| 151 |
), |
|
| 152 | 4x |
ifelse( |
| 153 | 4x |
length(x@mode) == 1, |
| 154 | 4x |
paste0("$$ \\text{mode} = ", x@mode, " $$\n\n)"),
|
| 155 | 4x |
paste0( |
| 156 | 4x |
"$$ \\text{mode} \\sim U(",
|
| 157 | 4x |
x@mode[1], |
| 158 |
", ", |
|
| 159 | 4x |
x@mode[2], |
| 160 | 4x |
") $$\n\n" |
| 161 |
) |
|
| 162 |
), |
|
| 163 | 4x |
" and \n\n", |
| 164 | 4x |
ifelse( |
| 165 | 4x |
length(x@ref_dose_beta) == 1, |
| 166 | 4x |
paste0("$$ d_{max} = ", x@ref_dose_beta, " $$\n\n"),
|
| 167 | 4x |
paste0( |
| 168 | 4x |
"$$ d_{max} \\sim U(",
|
| 169 | 4x |
x@ref_dose_beta[1], |
| 170 |
", ", |
|
| 171 | 4x |
x@ref_dose_beta[2], |
| 172 | 4x |
") $$\n\n" |
| 173 |
) |
|
| 174 |
) |
|
| 175 |
) |
|
| 176 |
} |
|
| 177 | ||
| 178 |
# DualEndpointEmax ---- |
|
| 179 | ||
| 180 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 181 |
#' @noRd |
|
| 182 |
h_knit_print_render_biomarker_model.DualEndpointEmax <- function(x, ...) {
|
|
| 183 | 4x |
paste0( |
| 184 | 4x |
"f(d) is a parametric Emax density function such that\n\n", |
| 185 | 4x |
"$$ f(d) = ", |
| 186 | 4x |
"E_0 + \\frac{(E_{max} - E_0) \\times \\frac{d}{d^*}}{\\text{ED}_{50} + \\frac{d}{d^*}} $$\n\n",
|
| 187 | 4x |
"where d* is the reference dose, E~0~ and E~max~ are, respectively, the ", |
| 188 | 4x |
"minimum and maximum levels of the biomarker and ED~50~ is the dose achieving ", |
| 189 | 4x |
"half the maximum effect, 0.5 × E~max~.\n\n", |
| 190 | 4x |
"In this case, \n\n", |
| 191 | 4x |
ifelse( |
| 192 | 4x |
length(x@E0) == 1, |
| 193 | 4x |
paste0("$$ E_0 = ", x@E0, " $$\n\n)"),
|
| 194 | 4x |
paste0("$$ E_0 \\sim U(", x@E0[1], ", ", x@E0[2], ") $$\n\n")
|
| 195 |
), |
|
| 196 | 4x |
ifelse( |
| 197 | 4x |
length(x@Emax) == 1, |
| 198 | 4x |
paste0("$$ E_{max} = ", x@Emax, " $$\n\n)"),
|
| 199 | 4x |
paste0("$$ E_{max} \\sim U(", x@Emax[1], ", ", x@Emax[2], ") $$\n\n")
|
| 200 |
), |
|
| 201 | 4x |
ifelse( |
| 202 | 4x |
length(x@ED50) == 1, |
| 203 | 4x |
paste0("$$ \\text{ED}_{50} = ", x@ED50, " $$\n\n)"),
|
| 204 | 4x |
paste0( |
| 205 | 4x |
"$$ \\text{ED}_{50} \\sim U(",
|
| 206 | 4x |
x@ED50[1], |
| 207 |
", ", |
|
| 208 | 4x |
x@ED50[2], |
| 209 | 4x |
") $$\n\n" |
| 210 |
) |
|
| 211 |
), |
|
| 212 | 4x |
" and \n\n", |
| 213 | 4x |
ifelse( |
| 214 | 4x |
length(x@ref_dose_emax) == 1, |
| 215 | 4x |
paste0("$$ d^* = ", x@ref_dose_emax, " $$\n\n"),
|
| 216 | 4x |
paste0( |
| 217 | 4x |
"$$ d^* \\sim U(",
|
| 218 | 4x |
x@ref_dose_emax[1], |
| 219 |
", ", |
|
| 220 | 4x |
x@ref_dose_emax[2], |
| 221 | 4x |
") $$\n\n" |
| 222 |
) |
|
| 223 |
) |
|
| 224 |
) |
|
| 225 |
} |
|
| 226 | ||
| 227 |
# DualEndpointRW ---- |
|
| 228 | ||
| 229 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 230 |
#' @noRd |
|
| 231 |
h_knit_print_render_biomarker_model.DualEndpointRW <- function( |
|
| 232 |
x, |
|
| 233 |
..., |
|
| 234 |
use_values = TRUE |
|
| 235 |
) {
|
|
| 236 | 10x |
paste0( |
| 237 | 10x |
"f(d) is a ", |
| 238 | 10x |
ifelse(x@rw1, "first", "second"), |
| 239 | 10x |
" order random walk such that\n\n", |
| 240 | 10x |
"$$ f(d) = ", |
| 241 | 10x |
"\\beta_{W_i} - \\beta_{W_{i - ",
|
| 242 | 10x |
ifelse(x@rw1, "1", "2"), |
| 243 |
"}}", |
|
| 244 | 10x |
"\\sim N(0, ", |
| 245 | 10x |
ifelse(x@rw1, "", "2 \\times "), |
| 246 | 10x |
ifelse( |
| 247 | 10x |
use_values & length(x@sigma2betaW) == 1, |
| 248 | 10x |
x@sigma2betaW, |
| 249 | 10x |
"\\sigma_{\\beta_W}^2"
|
| 250 |
), |
|
| 251 | 10x |
" \\times (d_i - d_{i - ",
|
| 252 | 10x |
ifelse(x@rw1, "1", "2"), |
| 253 |
"})", |
|
| 254 |
")", |
|
| 255 | 10x |
" $$\n\n", |
| 256 | 10x |
ifelse( |
| 257 | 10x |
length(x@sigma2betaW) == 1, |
| 258 | 10x |
ifelse( |
| 259 | 10x |
use_values, |
| 260 |
"", |
|
| 261 | 10x |
paste0(" and $\\sigma_{\\beta_W}^2$ is fixed at ", x@sigma2betaW)
|
| 262 |
), |
|
| 263 | 10x |
paste0( |
| 264 | 10x |
" and the prior for $\\sigma_{\\beta_W}^2$ is an inverse-gamma distribution with parameters ",
|
| 265 | 10x |
"a = ", |
| 266 | 10x |
x@sigma2betaW["a"], |
| 267 | 10x |
" and b = ", |
| 268 | 10x |
x@sigma2betaW["b"] |
| 269 |
) |
|
| 270 |
) |
|
| 271 |
) |
|
| 272 |
} |
|
| 273 | ||
| 274 |
# ModelParamsNormal ---- |
|
| 275 | ||
| 276 |
#' Render a Normal Prior |
|
| 277 |
#' |
|
| 278 |
#' @param x (`ModelParamsNormal`)\cr the object to be rendered |
|
| 279 |
#' @param use_values (`flag`)\cr print the values associated with hyperparameters, |
|
| 280 |
#' or the symbols used to define the hyper-parameters. That is, for example, mu or 1. |
|
| 281 |
#' @param fmt (`character`)\cr the `sprintf` format string used to render |
|
| 282 |
#' numerical values. Ignored if `use_values` is `FALSE`. |
|
| 283 |
#' @param params (`character`)\cr The names of the model parameters. See Usage |
|
| 284 |
#' Notes below. |
|
| 285 |
#' @param preamble (`character`)\cr The text used to introduce the LaTeX representation |
|
| 286 |
#' of the model |
|
| 287 |
#' @param asis (`flag`)\cr wrap the return value in a call to `knitr::asis_output`? |
|
| 288 |
#' @param theta (`character`)\cr the LaTeX representation of the theta vector |
|
| 289 |
#' @param ... Not used at present |
|
| 290 |
#' @section Usage Notes: |
|
| 291 |
#' `params` must be a character vector of length equal to that of `x@mean` (and |
|
| 292 |
#' `x@cov`). Its values represent the parameters of the model as entries in the |
|
| 293 |
#' vector `theta`, on the left-hand side of "~" in the definition of the prior. |
|
| 294 |
#' If named, names should be valid LaTeX, escaped as usual for R character variables. |
|
| 295 |
#' For example, `"\\alpha"` or `"\\beta_0"`. If unnamed, names are constructed by |
|
| 296 |
#' pre-pending an escaped backslash to each value provided. |
|
| 297 |
#' @return A character string containing a LaTeX rendition of the object. |
|
| 298 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 299 |
#' @export |
|
| 300 |
#' @rdname knit_print |
|
| 301 |
#' @method knit_print ModelParamsNormal |
|
| 302 |
knit_print.ModelParamsNormal <- function( |
|
| 303 |
x, |
|
| 304 |
use_values = TRUE, |
|
| 305 |
fmt = "%5.2f", |
|
| 306 |
params = c("alpha", "beta"),
|
|
| 307 |
preamble = "The prior for θ is given by\\n", |
|
| 308 |
asis = TRUE, |
|
| 309 |
theta = "\\theta", |
|
| 310 |
... |
|
| 311 |
) {
|
|
| 312 |
# Validate |
|
| 313 | 146x |
assert_class(x, "ModelParamsNormal") |
| 314 | 146x |
assert_format(fmt) |
| 315 | 146x |
assert_character(preamble, len = 1) |
| 316 | 146x |
assert_true(length(x@mean) == length(params)) |
| 317 | 146x |
assert_flag(asis) |
| 318 |
# Initialise |
|
| 319 | 144x |
n <- length(params) |
| 320 | 144x |
if (is.null(names(params))) {
|
| 321 | 42x |
names(params) <- paste0("\\", params)
|
| 322 |
} |
|
| 323 |
# Execute |
|
| 324 |
# Construct LaTeX representation of mean vector |
|
| 325 | 144x |
mu <- sapply( |
| 326 | 144x |
1:n, |
| 327 | 144x |
function(i) {
|
| 328 | 312x |
ifelse( |
| 329 | 312x |
use_values, |
| 330 | 312x |
sprintf(fmt, x@mean[i]), |
| 331 | 312x |
paste0("\\mu_{\\", params[i], "}")
|
| 332 |
) |
|
| 333 |
} |
|
| 334 |
) |
|
| 335 |
# Construct LaTeX representation of covariance matrix |
|
| 336 | 144x |
cov <- sapply( |
| 337 | 144x |
1:n, |
| 338 | 144x |
function(i) {
|
| 339 | 312x |
sapply( |
| 340 | 312x |
1:n, |
| 341 | 312x |
function(j) {
|
| 342 | 712x |
ifelse( |
| 343 | 712x |
use_values, |
| 344 | 712x |
sprintf(fmt, x@cov[i, j]), |
| 345 | 712x |
ifelse( |
| 346 | 712x |
i == j, |
| 347 | 712x |
paste0("\\sigma_{\\", params[i], "}^2"),
|
| 348 | 712x |
paste0( |
| 349 | 712x |
"\\rho\\sigma_{\\",
|
| 350 | 712x |
params[i], |
| 351 | 712x |
"}\\sigma_{\\",
|
| 352 | 712x |
params[j], |
| 353 |
"}" |
|
| 354 |
) |
|
| 355 |
) |
|
| 356 |
) |
|
| 357 |
} |
|
| 358 |
) |
|
| 359 |
} |
|
| 360 |
) |
|
| 361 |
# Construct LaTeX representation of prior |
|
| 362 | 144x |
rv <- paste0( |
| 363 | 144x |
preamble, |
| 364 | 144x |
"$$ \\boldsymbol", |
| 365 | 144x |
theta, |
| 366 | 144x |
" = \\begin{bmatrix}",
|
| 367 | 144x |
paste0(names(params), collapse = " \\\\ "), |
| 368 | 144x |
"\\end{bmatrix}",
|
| 369 | 144x |
"\\sim N \\left(\\begin{bmatrix}",
|
| 370 | 144x |
paste0(mu, collapse = " \\\\ "), |
| 371 | 144x |
"\\end{bmatrix} , ",
|
| 372 | 144x |
"\\begin{bmatrix} ",
|
| 373 | 144x |
paste0( |
| 374 | 144x |
sapply( |
| 375 | 144x |
1:n, |
| 376 | 144x |
function(j) {
|
| 377 | 312x |
stringr::str_trim(paste0(cov[, j], collapse = " & ")) |
| 378 |
} |
|
| 379 |
), |
|
| 380 | 144x |
collapse = " \\\\ " |
| 381 |
), |
|
| 382 | 144x |
"\\end{bmatrix}",
|
| 383 | 144x |
" \\right)", |
| 384 | 144x |
" $$\n\n" |
| 385 |
) |
|
| 386 | 144x |
if (asis) {
|
| 387 | 46x |
rv <- knitr::asis_output(rv) |
| 388 |
} |
|
| 389 | 144x |
rv |
| 390 |
} |
|
| 391 | ||
| 392 |
# GeneralModel ---- |
|
| 393 | ||
| 394 |
#' @export |
|
| 395 |
#' @rdname knit_print |
|
| 396 |
#' @method knit_print GeneralModel |
|
| 397 |
knit_print.GeneralModel <- function( |
|
| 398 |
x, |
|
| 399 |
..., |
|
| 400 |
params = c("alpha", "beta"),
|
|
| 401 |
asis = TRUE, |
|
| 402 |
use_values = TRUE, |
|
| 403 |
fmt = "%5.2f", |
|
| 404 |
units = NA |
|
| 405 |
) {
|
|
| 406 |
# Validate |
|
| 407 | 90x |
assert_flag(asis) |
| 408 | 78x |
assert_flag(use_values) |
| 409 | 78x |
assert_format(fmt) |
| 410 |
# Execute |
|
| 411 | 78x |
rv <- paste0( |
| 412 | 78x |
h_knit_print_render_model(x, use_values = use_values, fmt = fmt, ...), |
| 413 | 78x |
knit_print( |
| 414 | 78x |
x@params, |
| 415 |
..., |
|
| 416 | 78x |
asis = asis, |
| 417 | 78x |
use_values = use_values, |
| 418 | 78x |
fmt = fmt, |
| 419 | 78x |
params = params |
| 420 |
), |
|
| 421 | 78x |
"\\n\\n", |
| 422 | 78x |
h_knit_print_render_ref_dose( |
| 423 | 78x |
x, |
| 424 | 78x |
use_values = use_values, |
| 425 | 78x |
fmt = fmt, |
| 426 | 78x |
unit = unit |
| 427 |
) |
|
| 428 |
) |
|
| 429 | 78x |
if (asis) {
|
| 430 | 26x |
rv <- knitr::asis_output(rv) |
| 431 |
} |
|
| 432 | 78x |
rv |
| 433 |
} |
|
| 434 | ||
| 435 |
#' @keywords internal |
|
| 436 |
h_knit_print_render_ref_dose.GeneralModel <- function( |
|
| 437 |
x, |
|
| 438 |
..., |
|
| 439 |
use_values = TRUE, |
|
| 440 |
fmt = "%5.2f", |
|
| 441 |
units = NA |
|
| 442 |
) {
|
|
| 443 |
# Validate |
|
| 444 | 90x |
assert_character(units, len = 1) |
| 445 |
# Initialise |
|
| 446 | 90x |
units <- h_prepare_units(units) |
| 447 |
# Execute |
|
| 448 | 90x |
ref_dose <- ifelse( |
| 449 | 90x |
use_values, |
| 450 | 90x |
paste0( |
| 451 | 90x |
" The reference dose will be ", |
| 452 | 90x |
stringr::str_trim(sprintf(fmt, x@ref_dose)), |
| 453 | 90x |
units, |
| 454 | 90x |
".\n\n" |
| 455 |
), |
|
| 456 |
"" |
|
| 457 |
) |
|
| 458 | 90x |
ref_dose |
| 459 |
} |
|
| 460 | ||
| 461 |
# LogisticKadane ---- |
|
| 462 | ||
| 463 |
#' @keywords internal |
|
| 464 |
h_knit_print_render_ref_dose.LogisticKadane <- function(x, ...) {
|
|
| 465 |
# The LogisticKadane class has no reference dose slot |
|
| 466 |
"" |
|
| 467 |
} |
|
| 468 | ||
| 469 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 470 |
#' @rdname knit_print |
|
| 471 |
#' @export |
|
| 472 |
#' @method knit_print LogisticKadane |
|
| 473 |
knit_print.LogisticKadane <- function( |
|
| 474 |
x, |
|
| 475 |
..., |
|
| 476 |
asis = TRUE, |
|
| 477 |
use_values = TRUE, |
|
| 478 |
fmt = "%5.2f", |
|
| 479 |
units = NA, |
|
| 480 |
tox_label = "toxicity" |
|
| 481 |
) {
|
|
| 482 |
# Validate |
|
| 483 | 6x |
assert_flag(asis) |
| 484 | 4x |
assert_flag(use_values) |
| 485 | 4x |
assert_format(fmt) |
| 486 |
# Initialise |
|
| 487 | 4x |
tox_label <- h_prepare_labels(tox_label) |
| 488 | 4x |
units <- h_prepare_units(units) |
| 489 |
# Execute |
|
| 490 | 4x |
rv <- paste0( |
| 491 | 4x |
"A logistic model using the parameterisation of Kadane (1980) will ", |
| 492 | 4x |
"describe the relationship between dose and ", |
| 493 | 4x |
tox_label[1], |
| 494 | 4x |
".\n\n ", |
| 495 | 4x |
ifelse( |
| 496 | 4x |
use_values, |
| 497 | 4x |
paste0( |
| 498 | 4x |
"Let the minimum (x~min~) and maximum (x~max~) doses be ", |
| 499 | 4x |
paste0(stringr::str_trim(sprintf(fmt, x@xmin)), units), |
| 500 | 4x |
" and ", |
| 501 | 4x |
paste0(stringr::str_trim(sprintf(fmt, x@xmax)), units), |
| 502 | 4x |
".\n\n" |
| 503 |
), |
|
| 504 | 4x |
"Let x~min~ and x~max~ denote, respectively, the minimum and maximum doses.\n\n " |
| 505 |
), |
|
| 506 | 4x |
"Further, let θ denote the target toxicity rate and ρ~0~ = p(DLT | D = x~min~).\n\n", |
| 507 | 4x |
"Let γ be the dose with target toxicity rate θ, so that p(DLT | D = γ) = θ", |
| 508 | 4x |
ifelse( |
| 509 | 4x |
use_values, |
| 510 | 4x |
paste0(" = ", x@theta, ".\n\n"),
|
| 511 | 4x |
".\n\n" |
| 512 |
), |
|
| 513 | 4x |
"Using this parameterisation, standard logistic regression model has slope ", |
| 514 | 4x |
"$$ \\frac{\\gamma \\text{logit}(\\rho_0) - x_{min} \\text{logit}(\\theta)}{\\gamma - x_{min}} $$",
|
| 515 | 4x |
" and intercept ", |
| 516 | 4x |
"$$ \\frac{\\text{logit}(\\theta) - logit(\\rho_0)}{\\gamma - x_{min}} $$",
|
| 517 | 4x |
" The priors for Γ and Ρ~0~ are ", |
| 518 | 4x |
ifelse( |
| 519 | 4x |
use_values, |
| 520 | 4x |
paste0( |
| 521 | 4x |
"$$ \\Gamma \\sim U(",
|
| 522 | 4x |
sprintf(fmt, x@xmin), |
| 523 |
", ", |
|
| 524 | 4x |
sprintf(fmt, x@xmax), |
| 525 |
") $$" |
|
| 526 |
), |
|
| 527 | 4x |
"$$ \\Gamma \\sim U(x_{min}, x_{max}) $$"
|
| 528 |
), |
|
| 529 | 4x |
" and, independently, ", |
| 530 | 4x |
ifelse( |
| 531 | 4x |
use_values, |
| 532 | 4x |
paste0("$$ \\mathrm{P}_0 \\sim U(0, ", x@theta, ") $$"),
|
| 533 | 4x |
"$$ \\mathrm{P}_0 \\sim U(0, \\theta) $$"
|
| 534 |
), |
|
| 535 | 4x |
"\n\n Note that x~min~ and x~max~ need not be equal to the smallest and ", |
| 536 | 4x |
"largest values in the `doseGrid` slot of the corresponding `Data` object.\n\n" |
| 537 |
) |
|
| 538 | ||
| 539 | 4x |
if (asis) {
|
| 540 | 2x |
rv <- knitr::asis_output(rv) |
| 541 |
} |
|
| 542 | 4x |
rv |
| 543 |
} |
|
| 544 | ||
| 545 |
# LogisticKadaneBetaGamma ---- |
|
| 546 | ||
| 547 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 548 |
#' @rdname knit_print |
|
| 549 |
#' @export |
|
| 550 |
#' @method knit_print LogisticKadaneBetaGamma |
|
| 551 |
knit_print.LogisticKadaneBetaGamma <- function( |
|
| 552 |
x, |
|
| 553 |
..., |
|
| 554 |
asis = TRUE, |
|
| 555 |
use_values = TRUE, |
|
| 556 |
fmt = "%5.2f", |
|
| 557 |
tox_label = "toxicity", |
|
| 558 |
units = NA |
|
| 559 |
) {
|
|
| 560 |
# Validate |
|
| 561 | 6x |
assert_flag(asis) |
| 562 | 4x |
assert_flag(use_values) |
| 563 | 4x |
assert_format(fmt) |
| 564 |
# Initialise |
|
| 565 | 4x |
units <- h_prepare_units(units) |
| 566 | 4x |
tox_label <- h_prepare_labels(tox_label) |
| 567 |
# Execute |
|
| 568 | 4x |
rv <- paste0( |
| 569 | 4x |
"A logistic model using the parameterisation of Kadane (1980) will ", |
| 570 | 4x |
"describe the relationship between dose and ", |
| 571 | 4x |
tox_label[1], |
| 572 | 4x |
", using a Beta ", |
| 573 | 4x |
"distribution as the prior for ρ~0~ and a Gamma distribution as the prior ", |
| 574 | 4x |
"for γ.\n\n ", |
| 575 | 4x |
ifelse( |
| 576 | 4x |
use_values, |
| 577 | 4x |
paste0( |
| 578 | 4x |
"Let the minimum (x~min~) and maximum (x~max~) doses be ", |
| 579 | 4x |
paste0(stringr::str_trim(sprintf(fmt, x@xmin)), units), |
| 580 | 4x |
" and ", |
| 581 | 4x |
paste0(stringr::str_trim(sprintf(fmt, x@xmax)), units), |
| 582 | 4x |
".\n\n" |
| 583 |
), |
|
| 584 | 4x |
"Let x~min~ and x~max~ denote, respectively, the minimum and maximum doses.\n\n " |
| 585 |
), |
|
| 586 | 4x |
"Further, let θ denote the target toxicity rate and ρ~0~ = p(DLT | D = x~min~).\n\n", |
| 587 | 4x |
"Let γ be the dose with target toxicity rate θ, so that p(DLT | D = γ) = θ", |
| 588 | 4x |
ifelse( |
| 589 | 4x |
use_values, |
| 590 | 4x |
paste0(" = ", x@theta, ".\n\n"),
|
| 591 | 4x |
".\n\n" |
| 592 |
), |
|
| 593 | 4x |
"Using this parameterisation, standard logistic regression model has slope ", |
| 594 | 4x |
"$$ \\frac{\\gamma \\text{logit}(\\rho_0) - x_{min} \\text{logit}(\\theta)}{\\gamma - x_{min}} $$",
|
| 595 | 4x |
" and intercept ", |
| 596 | 4x |
"$$ \\frac{\\text{logit}(\\theta) - logit(\\rho_0)}{\\gamma - x_{min}} $$",
|
| 597 | 4x |
" The priors for Γ and Ρ~0~ are ", |
| 598 | 4x |
ifelse( |
| 599 | 4x |
use_values, |
| 600 | 4x |
paste0( |
| 601 | 4x |
"$$ \\Gamma \\sim U(",
|
| 602 | 4x |
sprintf(fmt, x@shape), |
| 603 |
", ", |
|
| 604 | 4x |
sprintf(fmt, x@rate), |
| 605 |
") $$" |
|
| 606 |
), |
|
| 607 | 4x |
"$$ \\Gamma \\sim Gamma( \\text{shape}, \\text{rate}) $$"
|
| 608 |
), |
|
| 609 | 4x |
" and, independently, ", |
| 610 | 4x |
ifelse( |
| 611 | 4x |
use_values, |
| 612 | 4x |
paste0("$$ \\mathrm{P}_0 \\sim Beta(", x@alpha, ", ", x@beta, ") $$"),
|
| 613 | 4x |
"$$ \\mathrm{P}_0 \\sim Beta(\\alpha, \\beta) $$"
|
| 614 |
), |
|
| 615 | 4x |
"\n\n Note that x~min~ and x~max~ need not be equal to the smallest and ", |
| 616 | 4x |
"largest values in the `doseGrid` slot of the corresponding `Data` object.\n\n" |
| 617 |
) |
|
| 618 | ||
| 619 | 4x |
if (asis) {
|
| 620 | 2x |
rv <- knitr::asis_output(rv) |
| 621 |
} |
|
| 622 | 4x |
rv |
| 623 |
} |
|
| 624 | ||
| 625 |
# LogisticLogNormal ---- |
|
| 626 | ||
| 627 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 628 |
#' @noRd |
|
| 629 |
h_knit_print_render_model.LogisticLogNormal <- function( |
|
| 630 |
x, |
|
| 631 |
tox_label = "toxicity", |
|
| 632 |
... |
|
| 633 |
) {
|
|
| 634 | 24x |
tox_label <- h_prepare_labels(tox_label) |
| 635 | 24x |
z <- "e^{\\alpha + \\beta \\cdot log(d/d_{ref})}"
|
| 636 | 24x |
paste0( |
| 637 | 24x |
"A logistic log normal model will describe the relationship between dose and ", |
| 638 | 24x |
tox_label[1], |
| 639 |
": ", |
|
| 640 | 24x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{",
|
| 641 | 24x |
z, |
| 642 | 24x |
"}{1 + ",
|
| 643 | 24x |
z, |
| 644 | 24x |
"} $$\\n ", |
| 645 | 24x |
"where d~ref~ denotes a reference dose.\n\n" |
| 646 |
) |
|
| 647 |
} |
|
| 648 | ||
| 649 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 650 |
#' @rdname knit_print |
|
| 651 |
#' @export |
|
| 652 |
#' @method knit_print LogisticLogNormal |
|
| 653 |
knit_print.LogisticLogNormal <- function( |
|
| 654 |
x, |
|
| 655 |
..., |
|
| 656 |
use_values = TRUE, |
|
| 657 |
fmt = "%5.2f", |
|
| 658 |
params = c( |
|
| 659 |
"\\alpha" = "alpha", |
|
| 660 |
"log(\\beta)" = "beta" |
|
| 661 |
), |
|
| 662 |
preamble = "The prior for θ is given by\\n", |
|
| 663 |
asis = TRUE |
|
| 664 |
) {
|
|
| 665 | 30x |
assert_flag(asis) |
| 666 |
# Can't use NextMethod() on a S4 class |
|
| 667 | 24x |
knit_print.GeneralModel( |
| 668 | 24x |
x, |
| 669 |
..., |
|
| 670 | 24x |
use_values = use_values, |
| 671 | 24x |
fmt = fmt, |
| 672 | 24x |
params = params, |
| 673 | 24x |
preamble = preamble, |
| 674 | 24x |
asis = asis |
| 675 |
) |
|
| 676 |
} |
|
| 677 | ||
| 678 |
# LogisticLogNormalMixture ---- |
|
| 679 | ||
| 680 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 681 |
#' @noRd |
|
| 682 |
h_knit_print_render_model.LogisticLogNormalMixture <- function( |
|
| 683 |
x, |
|
| 684 |
use_values = TRUE, |
|
| 685 |
tox_label = "toxicity", |
|
| 686 |
... |
|
| 687 |
) {
|
|
| 688 | 4x |
tox_label <- h_prepare_labels(tox_label) |
| 689 | 4x |
z1 <- "e^{\\alpha_1 + \\beta_1 \\cdot log(d/d^*)}"
|
| 690 | 4x |
z2 <- "e^{\\alpha_2 + \\beta_2 \\cdot log(d/d^*)}"
|
| 691 | 4x |
pi_text <- ifelse( |
| 692 | 4x |
use_values, |
| 693 | 4x |
x@share_weight, |
| 694 | 4x |
"\\pi" |
| 695 |
) |
|
| 696 | 4x |
paste0( |
| 697 | 4x |
"A mixture of two logistic log normal models will describe the relationship between dose and ", |
| 698 | 4x |
tox_label[1], |
| 699 |
": ", |
|
| 700 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = ", |
| 701 | 4x |
pi_text, |
| 702 | 4x |
" \\times \\frac{",
|
| 703 | 4x |
z1, |
| 704 | 4x |
"}{1 + ",
|
| 705 | 4x |
z1, |
| 706 | 4x |
"} + (1 - ", |
| 707 | 4x |
pi_text, |
| 708 | 4x |
") \\times \\frac{",
|
| 709 | 4x |
z2, |
| 710 | 4x |
"}{1 + ",
|
| 711 | 4x |
z2, |
| 712 |
"} $$", |
|
| 713 | 4x |
ifelse( |
| 714 | 4x |
use_values, |
| 715 | 4x |
"where d* denotes a reference dose.\n\n", |
| 716 | 4x |
"where d* denotes a reference dose and π is a fixed value between 0 and 1.\n\n" |
| 717 |
) |
|
| 718 |
) |
|
| 719 |
} |
|
| 720 | ||
| 721 |
#' @export |
|
| 722 |
#' @rdname knit_print |
|
| 723 |
#' @method knit_print LogisticLogNormalMixture |
|
| 724 |
knit_print.LogisticLogNormalMixture <- function( |
|
| 725 |
x, |
|
| 726 |
..., |
|
| 727 |
asis = TRUE, |
|
| 728 |
use_values = TRUE, |
|
| 729 |
fmt = "%5.2f", |
|
| 730 |
units = NA |
|
| 731 |
) {
|
|
| 732 |
# Validate |
|
| 733 | 6x |
assert_flag(asis) |
| 734 | 4x |
assert_flag(use_values) |
| 735 | 4x |
assert_format(fmt) |
| 736 |
# Execute |
|
| 737 | 4x |
rv <- paste0( |
| 738 | 4x |
h_knit_print_render_model(x, use_values = use_values, fmt = fmt, ...), |
| 739 | 4x |
knit_print( |
| 740 | 4x |
x@params, |
| 741 |
..., |
|
| 742 | 4x |
asis = asis, |
| 743 | 4x |
use_values = use_values, |
| 744 | 4x |
fmt = fmt, |
| 745 | 4x |
preamble = "The priors for both θ~1~ and θ~2~ are given by\\n" |
| 746 |
), |
|
| 747 | 4x |
"\\n\\n", |
| 748 | 4x |
h_knit_print_render_ref_dose( |
| 749 | 4x |
x, |
| 750 | 4x |
use_values = use_values, |
| 751 | 4x |
fmt = fmt, |
| 752 | 4x |
unit = unit |
| 753 |
) |
|
| 754 |
) |
|
| 755 | 4x |
if (asis) {
|
| 756 | 2x |
rv <- knitr::asis_output(rv) |
| 757 |
} |
|
| 758 | 4x |
rv |
| 759 |
} |
|
| 760 | ||
| 761 |
# LogisticLogNormalSub ---- |
|
| 762 | ||
| 763 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 764 |
#' @noRd |
|
| 765 |
h_knit_print_render_model.LogisticLogNormalSub <- function( |
|
| 766 |
x, |
|
| 767 |
..., |
|
| 768 |
tox_label = "toxicity" |
|
| 769 |
) {
|
|
| 770 | 4x |
tox_label <- h_prepare_labels(tox_label) |
| 771 | 4x |
z <- "e^{\\alpha + \\beta \\cdot (d \\, - \\, d^*)}"
|
| 772 | 4x |
paste0( |
| 773 | 4x |
"A logistic log normal model with subtractive dose normalisation will ", |
| 774 | 4x |
"describe the relationship between dose and ", |
| 775 | 4x |
tox_label[1], |
| 776 | 4x |
": \n\n", |
| 777 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{",
|
| 778 | 4x |
z, |
| 779 | 4x |
"}{1 + ",
|
| 780 | 4x |
z, |
| 781 | 4x |
"} $$\\n ", |
| 782 | 4x |
"where d* denotes a reference dose.\n\n" |
| 783 |
) |
|
| 784 |
} |
|
| 785 | ||
| 786 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 787 |
#' @rdname knit_print |
|
| 788 |
#' @export |
|
| 789 |
#' @method knit_print LogisticLogNormalSub |
|
| 790 |
knit_print.LogisticLogNormalSub <- function( |
|
| 791 |
x, |
|
| 792 |
..., |
|
| 793 |
use_values = TRUE, |
|
| 794 |
fmt = "%5.2f", |
|
| 795 |
params = c( |
|
| 796 |
"\\alpha" = "alpha", |
|
| 797 |
"log(\\beta)" = "beta" |
|
| 798 |
), |
|
| 799 |
preamble = "The prior for θ is given by\\n", |
|
| 800 |
asis = TRUE |
|
| 801 |
) {
|
|
| 802 | 6x |
NextMethod(params = params) |
| 803 |
} |
|
| 804 | ||
| 805 |
# LogisticNormal ---- |
|
| 806 | ||
| 807 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 808 |
#' @noRd |
|
| 809 |
h_knit_print_render_model.LogisticNormal <- function( |
|
| 810 |
x, |
|
| 811 |
..., |
|
| 812 |
tox_label = "toxicity" |
|
| 813 |
) {
|
|
| 814 | 4x |
tox_label <- h_prepare_labels(tox_label) |
| 815 | 4x |
z <- "e^{\\alpha + \\beta \\cdot d/d^*}"
|
| 816 | 4x |
paste0( |
| 817 | 4x |
"A logistic log normal model will describe the relationship between dose and ", |
| 818 | 4x |
tox_label[1], |
| 819 |
": ", |
|
| 820 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{",
|
| 821 | 4x |
z, |
| 822 | 4x |
"}{1 + ",
|
| 823 | 4x |
z, |
| 824 | 4x |
"} $$\\n ", |
| 825 | 4x |
"where d* denotes a reference dose.\n\n" |
| 826 |
) |
|
| 827 |
} |
|
| 828 | ||
| 829 |
# ProbitLogNormal ---- |
|
| 830 | ||
| 831 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 832 |
#' @noRd |
|
| 833 |
h_knit_print_render_model.ProbitLogNormal <- function( |
|
| 834 |
x, |
|
| 835 |
tox_label = "toxicity", |
|
| 836 |
... |
|
| 837 |
) {
|
|
| 838 | 22x |
tox_label <- h_prepare_labels(tox_label) |
| 839 | 22x |
paste0( |
| 840 | 22x |
"A probit log normal model will describe the relationship between dose and ", |
| 841 | 22x |
tox_label[1], |
| 842 |
": ", |
|
| 843 | 22x |
"$$ \\Phi^{-1}(Tox | d) = f(X = 1 | \\theta, d) = \\alpha + \\beta \\cdot log(d/d^*) $$\\n ",
|
| 844 | 22x |
"where d* denotes a reference dose.\n\n" |
| 845 |
) |
|
| 846 |
} |
|
| 847 | ||
| 848 |
# ProbitLogNormalRel ---- |
|
| 849 | ||
| 850 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 851 |
#' @noRd |
|
| 852 |
h_knit_print_render_model.ProbitLogNormalRel <- function( |
|
| 853 |
x, |
|
| 854 |
..., |
|
| 855 |
tox_label = "toxicity", |
|
| 856 |
asis = TRUE |
|
| 857 |
) {
|
|
| 858 | 4x |
assert_flag(asis) |
| 859 | 4x |
tox_label <- h_prepare_labels(tox_label) |
| 860 | 4x |
paste0( |
| 861 | 4x |
"A probit log normal model will describe the relationship between dose and ", |
| 862 | 4x |
tox_label[1], |
| 863 |
": ", |
|
| 864 | 4x |
"$$ \\Phi^{-1}(Tox | d) = f(X = 1 | \\theta, d) = \\alpha + \\beta \\cdot d/d^* $$\\n ",
|
| 865 | 4x |
"where d* denotes a reference dose.\n\n" |
| 866 |
) |
|
| 867 |
} |
|
| 868 | ||
| 869 |
# LogisticNormalMixture ---- |
|
| 870 | ||
| 871 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 872 |
#' @noRd |
|
| 873 |
h_knit_print_render_model.LogisticNormalMixture <- function( |
|
| 874 |
x, |
|
| 875 |
..., |
|
| 876 |
tox_label = "toxicity" |
|
| 877 |
) {
|
|
| 878 | 4x |
tox_label <- h_prepare_labels(tox_label) |
| 879 | 4x |
z <- "e^{\\alpha + \\beta \\cdot log(d/d^*)}"
|
| 880 | 4x |
paste0( |
| 881 | 4x |
"A mixture of two logistic log normal models will describe the relationship between dose and ", |
| 882 | 4x |
tox_label[1], |
| 883 |
": ", |
|
| 884 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{",
|
| 885 | 4x |
z, |
| 886 | 4x |
"}{1 + ",
|
| 887 | 4x |
z, |
| 888 | 4x |
"} $$\\n ", |
| 889 | 4x |
"where d* denotes a reference dose.\n\n" |
| 890 |
) |
|
| 891 |
} |
|
| 892 | ||
| 893 |
#' @export |
|
| 894 |
#' @rdname knit_print |
|
| 895 |
#' @method knit_print LogisticNormalMixture |
|
| 896 |
knit_print.LogisticNormalMixture <- function( |
|
| 897 |
x, |
|
| 898 |
..., |
|
| 899 |
asis = TRUE, |
|
| 900 |
use_values = TRUE, |
|
| 901 |
fmt = "%5.2f", |
|
| 902 |
units = NA |
|
| 903 |
) {
|
|
| 904 |
# Validate |
|
| 905 | 6x |
assert_flag(asis) |
| 906 | 4x |
assert_flag(use_values) |
| 907 | 4x |
assert_format(fmt) |
| 908 |
# Execute |
|
| 909 | 4x |
rv <- paste0( |
| 910 | 4x |
h_knit_print_render_model(x, use_values = use_values, fmt = fmt, ...), |
| 911 | 4x |
"The prior for θ is given by\\n", |
| 912 | 4x |
"$$ \\theta = \\begin{bmatrix} \\alpha \\\\ log(\\beta) \\end{bmatrix}",
|
| 913 | 4x |
" \\sim ", |
| 914 | 4x |
"w \\cdot ", |
| 915 | 4x |
knit_print( |
| 916 | 4x |
x@comp1, |
| 917 | 4x |
params = c("\\alpha" = "alpha", "\\beta" = "beta")
|
| 918 |
), |
|
| 919 | 4x |
" + (1 - w) \\cdot ", |
| 920 | 4x |
knit_print( |
| 921 | 4x |
x@comp2, |
| 922 | 4x |
params = c("\\alpha" = "alpha", "\\beta" = "beta")
|
| 923 |
), |
|
| 924 | 4x |
" $$\\n\\n", |
| 925 | 4x |
" and the prior for w is given by \n\n", |
| 926 | 4x |
" $$ w \\sim Beta(",
|
| 927 | 4x |
x@weightpar[1], |
| 928 |
", ", |
|
| 929 | 4x |
x@weightpar[2], |
| 930 | 4x |
") $$\\n\\n", |
| 931 | 4x |
h_knit_print_render_ref_dose( |
| 932 | 4x |
x, |
| 933 | 4x |
units = units, |
| 934 | 4x |
fmt = fmt, |
| 935 | 4x |
use_values = use_values, |
| 936 |
... |
|
| 937 |
) |
|
| 938 |
) |
|
| 939 | 4x |
if (asis) {
|
| 940 | 2x |
rv <- knitr::asis_output(rv) |
| 941 |
} |
|
| 942 | 4x |
rv |
| 943 |
} |
|
| 944 | ||
| 945 |
# LogisticNormalFixedMixture ---- |
|
| 946 | ||
| 947 |
#' @export |
|
| 948 |
#' @rdname knit_print |
|
| 949 |
#' @method knit_print LogisticNormalFixedMixture |
|
| 950 |
knit_print.LogisticNormalFixedMixture <- function( |
|
| 951 |
x, |
|
| 952 |
..., |
|
| 953 |
asis = TRUE, |
|
| 954 |
use_values = TRUE, |
|
| 955 |
fmt = "%5.2f", |
|
| 956 |
units = NA |
|
| 957 |
) {
|
|
| 958 |
# Validate |
|
| 959 | 6x |
assert_flag(asis) |
| 960 | 4x |
assert_flag(use_values) |
| 961 | 4x |
assert_format(fmt) |
| 962 |
# Execute |
|
| 963 | 4x |
beta <- ifelse(x@log_normal, "log(\\beta)", "\\beta") |
| 964 | 4x |
rv <- paste0( |
| 965 | 4x |
h_knit_print_render_model(x, use_values = use_values, fmt = fmt, ...), |
| 966 | 4x |
" The prior for θ is given by\\n\\n", |
| 967 | 4x |
"$$ \\theta = \\begin{bmatrix} \\alpha \\\\ ",
|
| 968 | 4x |
beta, |
| 969 | 4x |
" \\end{bmatrix}",
|
| 970 | 4x |
" \\sim \\sum_{i=1}^{",
|
| 971 | 4x |
length(x@components), |
| 972 |
"}", |
|
| 973 | 4x |
"w_i \\cdot N \\left( \\mathbf{\\mu}_i , \\mathbf{\\Sigma}_i \\right)",
|
| 974 | 4x |
" $$ \\n\\n", |
| 975 | 4x |
" with \\n\\n", |
| 976 | 4x |
"$$ \\sum_{i=1}^{",
|
| 977 | 4x |
length(x@components), |
| 978 | 4x |
"} w_i = 1 $$ \\n\\n", |
| 979 | 4x |
" The individual components of the mixture are " |
| 980 |
) |
|
| 981 | 4x |
if (x@log_normal) {
|
| 982 | ! |
params <- c("\\alpha" = "alpha", "log(\\beta)" = "beta")
|
| 983 |
} else {
|
|
| 984 | 4x |
params <- c("\\alpha" = "alpha", "\\beta" = "beta")
|
| 985 |
} |
|
| 986 | 4x |
for (i in seq_along(x@components)) {
|
| 987 | 8x |
comp <- x@components[[i]] |
| 988 | 8x |
rv <- paste0( |
| 989 | 8x |
rv, |
| 990 | 8x |
knit_print( |
| 991 | 8x |
comp, |
| 992 | 8x |
params = params, |
| 993 | 8x |
preamble = " ", |
| 994 | 8x |
use_values = use_values, |
| 995 | 8x |
fmt = fmt, |
| 996 | 8x |
theta = paste0("\\theta_", i)
|
| 997 |
), |
|
| 998 | 8x |
" with weight ", |
| 999 | 8x |
x@weights[i], |
| 1000 | 8x |
ifelse( |
| 1001 | 8x |
i < length(x@components), |
| 1002 | 8x |
" and", |
| 1003 |
" " |
|
| 1004 |
) |
|
| 1005 |
) |
|
| 1006 |
} |
|
| 1007 | 4x |
rv <- paste0( |
| 1008 | 4x |
rv, |
| 1009 | 4x |
" \\n\\n ", |
| 1010 | 4x |
h_knit_print_render_ref_dose( |
| 1011 | 4x |
x, |
| 1012 | 4x |
units = units, |
| 1013 | 4x |
fmt = fmt, |
| 1014 | 4x |
use_values = use_values, |
| 1015 |
... |
|
| 1016 |
) |
|
| 1017 |
) |
|
| 1018 | 4x |
if (asis) {
|
| 1019 | 2x |
rv <- knitr::asis_output(rv) |
| 1020 |
} |
|
| 1021 | 4x |
rv |
| 1022 |
} |
|
| 1023 | ||
| 1024 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1025 |
#' @noRd |
|
| 1026 |
h_knit_print_render_model.LogisticNormalFixedMixture <- function( |
|
| 1027 |
x, |
|
| 1028 |
..., |
|
| 1029 |
tox_label = "toxicity" |
|
| 1030 |
) {
|
|
| 1031 | 4x |
tox_label <- h_prepare_labels(tox_label) |
| 1032 | 4x |
z <- "e^{\\alpha + \\beta \\cdot log(d/d^*)}"
|
| 1033 | 4x |
paste0( |
| 1034 | 4x |
"A mixture of ", |
| 1035 | 4x |
length(x@components), |
| 1036 | 4x |
" logistic log normal models with fixed weights will describe the relationship ", |
| 1037 | 4x |
"between dose and ", |
| 1038 | 4x |
tox_label[1], |
| 1039 |
": ", |
|
| 1040 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{",
|
| 1041 | 4x |
z, |
| 1042 | 4x |
"}{1 + ",
|
| 1043 | 4x |
z, |
| 1044 | 4x |
"} $$\\n ", |
| 1045 | 4x |
"where d* denotes a reference dose.\n\n" |
| 1046 |
) |
|
| 1047 |
} |
|
| 1048 | ||
| 1049 |
# ModelLogNormal ---- |
|
| 1050 | ||
| 1051 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1052 |
#' @noRd |
|
| 1053 |
h_knit_print_render_model.ModelLogNormal <- function(x, ...) {
|
|
| 1054 | 4x |
"The model used to characterise the dose toxicity relationship is defined in subclasses.\n\n" |
| 1055 |
} |
|
| 1056 | ||
| 1057 |
# OneParLogNormalPrior ---- |
|
| 1058 | ||
| 1059 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1060 |
#' @rdname knit_print |
|
| 1061 |
#' @export |
|
| 1062 |
#' @method knit_print OneParLogNormalPrior |
|
| 1063 |
knit_print.OneParLogNormalPrior <- function( |
|
| 1064 |
x, |
|
| 1065 |
..., |
|
| 1066 |
tox_label = "toxicity", |
|
| 1067 |
asis = TRUE, |
|
| 1068 |
use_values = TRUE, |
|
| 1069 |
fmt = "%5.2f" |
|
| 1070 |
) {
|
|
| 1071 | 12x |
assert_flag(asis) |
| 1072 | ||
| 1073 | 8x |
tox_label <- h_prepare_labels(tox_label) |
| 1074 | 8x |
s2text <- ifelse( |
| 1075 | 8x |
use_values, |
| 1076 | 8x |
stringr::str_trim(sprintf(fmt, x@sigma2)), |
| 1077 | 8x |
"\\sigma^2" |
| 1078 |
) |
|
| 1079 | 8x |
rv <- paste0( |
| 1080 | 8x |
"The relationship between dose and ", |
| 1081 | 8x |
tox_label[1], |
| 1082 | 8x |
" will be modelled using a version ", |
| 1083 | 8x |
"of the one parameter CRM of O'Quigley et al (1990) with an exponential prior on the ", |
| 1084 | 8x |
"power parameter for the skeleton prior probabilities, with", |
| 1085 | 8x |
ifelse( |
| 1086 | 8x |
use_values, |
| 1087 | 8x |
paste0("$$ \\Theta \\sim Exp(", s2text, ") $$"),
|
| 1088 | 8x |
"$$ \\Theta \\sim Exp(\\lambda) $$" |
| 1089 |
), |
|
| 1090 | 8x |
"and skeleton probabilities as in the table below.\n\n" |
| 1091 |
) |
|
| 1092 | 8x |
if (asis) {
|
| 1093 | 4x |
rv <- knitr::asis_output(rv) |
| 1094 |
} |
|
| 1095 | 8x |
rv |
| 1096 |
} |
|
| 1097 | ||
| 1098 |
# OneParExpPrior ---- |
|
| 1099 | ||
| 1100 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1101 |
#' @rdname knit_print |
|
| 1102 |
#' @export |
|
| 1103 |
#' @method knit_print OneParExpPrior |
|
| 1104 |
knit_print.OneParExpPrior <- function(x, ..., asis = TRUE) {
|
|
| 1105 | 6x |
assert_flag(asis) |
| 1106 | 4x |
rv <- "TODO\n\n" |
| 1107 | 4x |
if (asis) {
|
| 1108 | 2x |
rv <- knitr::asis_output(rv) |
| 1109 |
} |
|
| 1110 | 4x |
rv |
| 1111 |
} |
|
| 1112 | ||
| 1113 |
# LogisticLogNormalGrouped ---- |
|
| 1114 | ||
| 1115 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1116 |
#' @rdname knit_print |
|
| 1117 |
#' @export |
|
| 1118 |
#' @method knit_print LogisticLogNormalGrouped |
|
| 1119 |
knit_print.LogisticLogNormalGrouped <- function( |
|
| 1120 |
x, |
|
| 1121 |
..., |
|
| 1122 |
use_values = TRUE, |
|
| 1123 |
fmt = "%5.2f", |
|
| 1124 |
params = c( |
|
| 1125 |
"\\alpha" = "alpha", |
|
| 1126 |
"\\beta" = "beta", |
|
| 1127 |
"log(\\delta_0)" = "delta_0", |
|
| 1128 |
"log(\\delta_1)" = "delta_1" |
|
| 1129 |
), |
|
| 1130 |
preamble = "The prior for θ is given by\\n", |
|
| 1131 |
asis = TRUE |
|
| 1132 |
) {
|
|
| 1133 | 10x |
NextMethod(params = params) |
| 1134 |
} |
|
| 1135 | ||
| 1136 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1137 |
#' @noRd |
|
| 1138 |
h_knit_print_render_model.LogisticLogNormalGrouped <- function( |
|
| 1139 |
x, |
|
| 1140 |
tox_label = "toxicity", |
|
| 1141 |
... |
|
| 1142 |
) {
|
|
| 1143 | 8x |
tox_label <- h_prepare_labels(tox_label) |
| 1144 | 8x |
z <- "e^{(\\alpha + I_c \\times \\delta_0) + (\\beta + I_c \\times \\delta_1) \\cdot log(d/d^*)}"
|
| 1145 | 8x |
paste0( |
| 1146 | 8x |
"A logistic log normal model will describe the relationship between dose and ", |
| 1147 | 8x |
tox_label[1], |
| 1148 |
": ", |
|
| 1149 | 8x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{",
|
| 1150 | 8x |
z, |
| 1151 | 8x |
"}{1 + ",
|
| 1152 | 8x |
z, |
| 1153 | 8x |
"} $$\\n ", |
| 1154 | 8x |
"where d* denotes a reference dose and I~c~ is a binary indicator which ", |
| 1155 | 8x |
"is 1 for the combo arm and 0 for the mono arm.\n\n" |
| 1156 |
) |
|
| 1157 |
} |
|
| 1158 | ||
| 1159 |
# LogisticLogNormalOrdinal ---- |
|
| 1160 | ||
| 1161 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1162 |
#' @noRd |
|
| 1163 |
h_knit_print_render_model.LogisticLogNormalOrdinal <- function(x, ...) {
|
|
| 1164 | 8x |
z <- "e^{\\alpha_k + \\beta \\cdot log(d/d^*)}"
|
| 1165 | 8x |
paste0( |
| 1166 | 8x |
"Let p~k~(d) be the probability that the response of a patient treated at ", |
| 1167 | 8x |
"dose d is in category k *_or higher_*, k=0, ..., K; d=1, ..., D.\n\nThen ", |
| 1168 | 8x |
"$$ p_k(d) = f(X \\ge k \\; | \\; \\theta, d) = \\begin{cases} 1 & k = 0 \\\\ ",
|
| 1169 | 8x |
"\\frac{",
|
| 1170 | 8x |
z, |
| 1171 | 8x |
"}{1 + ",
|
| 1172 | 8x |
z, |
| 1173 | 8x |
"} & k=1, ..., K", |
| 1174 | 8x |
"\\end{cases} $$\n\n",
|
| 1175 | 8x |
"where d* denotes a reference dose.\n\nThe αs are constrained ", |
| 1176 | 8x |
"such that α~1~ > α~2~ > ... > α~K~.\n\n" |
| 1177 |
) |
|
| 1178 |
} |
|
| 1179 | ||
| 1180 |
# LogisticLogNormalOrdinal ---- |
|
| 1181 | ||
| 1182 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1183 |
#' @rdname knit_print |
|
| 1184 |
#' @export |
|
| 1185 |
#' @method knit_print LogisticLogNormalOrdinal |
|
| 1186 |
knit_print.LogisticLogNormalOrdinal <- function( |
|
| 1187 |
x, |
|
| 1188 |
..., |
|
| 1189 |
use_values = TRUE, |
|
| 1190 |
fmt = "%5.2f", |
|
| 1191 |
params = NA, |
|
| 1192 |
preamble = "The prior for θ is given by\\n", |
|
| 1193 |
asis = TRUE |
|
| 1194 |
) {
|
|
| 1195 | 10x |
assert_flag(asis) |
| 1196 | 8x |
if (is.na(params)) {
|
| 1197 | 8x |
params <- c( |
| 1198 | 8x |
paste0("alpha_", 1:(length(x@params@mean) - 1)),
|
| 1199 | 8x |
"beta" |
| 1200 |
) |
|
| 1201 | 8x |
names(params) <- paste0("\\", params)
|
| 1202 |
} |
|
| 1203 | 8x |
NextMethod(params = params) |
| 1204 |
} |
|
| 1205 | ||
| 1206 |
# LogisticIndepBeta ---- |
|
| 1207 | ||
| 1208 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1209 |
#' @rdname knit_print |
|
| 1210 |
#' @export |
|
| 1211 |
#' @method knit_print LogisticIndepBeta |
|
| 1212 |
knit_print.LogisticIndepBeta <- function( |
|
| 1213 |
x, |
|
| 1214 |
..., |
|
| 1215 |
use_values = TRUE, |
|
| 1216 |
fmt = "%5.2f", |
|
| 1217 |
params = NA, |
|
| 1218 |
tox_label = "DLAE", |
|
| 1219 |
preamble = "The prior for θ is given by\\n", |
|
| 1220 |
asis = TRUE |
|
| 1221 |
) {
|
|
| 1222 | 28x |
assert_flag(asis) |
| 1223 | ||
| 1224 | 26x |
tox_label <- h_prepare_labels(tox_label) |
| 1225 | 26x |
y <- tidy(x) |
| 1226 | 26x |
z <- "e^{\\phi_1 + \\phi_2 \\cdot log(d)}"
|
| 1227 | 26x |
posterior <- ModelParamsNormal(mean = c(x@phi1, x@phi2), cov = x@Pcov) |
| 1228 |
# knit_print.ModelParamsNormal expects no row or column names |
|
| 1229 | 26x |
rownames(posterior@cov) <- NULL |
| 1230 | 26x |
colnames(posterior@cov) <- NULL |
| 1231 | ||
| 1232 | 26x |
rv <- paste0( |
| 1233 | 26x |
"A logistic log normal model will describe the relationship between dose and ", |
| 1234 | 26x |
tox_label[1], |
| 1235 |
": ", |
|
| 1236 | 26x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{",
|
| 1237 | 26x |
z, |
| 1238 | 26x |
"}{1 + ",
|
| 1239 | 26x |
z, |
| 1240 | 26x |
"} $$\\n ", |
| 1241 | 26x |
"The prior is expressed in terms of pseudo data and, consequently, the number ", |
| 1242 | 26x |
" of cases and of ", |
| 1243 | 26x |
tox_label[2], |
| 1244 | 26x |
" need not be whole numbers.\n\nThe pseudo data are ", |
| 1245 | 26x |
"defined in the following table:\n\n", |
| 1246 | 26x |
paste0( |
| 1247 | 26x |
do.call( |
| 1248 | 26x |
function(x) {
|
| 1249 | 26x |
kableExtra::kable_styling( |
| 1250 | 26x |
knitr::kable(x, col.names = c("Dose", "N", tox_label[2])),
|
| 1251 | 26x |
bootstrap_options = c("striped", "hover", "condensed")
|
| 1252 |
) |
|
| 1253 |
}, |
|
| 1254 | 26x |
list(x = y$pseudoData) |
| 1255 |
), |
|
| 1256 | 26x |
collapse = "\n" |
| 1257 |
), |
|
| 1258 | 26x |
ifelse( |
| 1259 | 26x |
nrow(y$data) == 0, |
| 1260 | 26x |
"\n\nNo observed data has yet been recorded.\n", |
| 1261 | 26x |
paste( |
| 1262 | 26x |
"\n\nThe observed data are given in the following table:\n\n", |
| 1263 | 26x |
paste((do.call(knitr::kable, list(x = y$data))), collapse = "\n") |
| 1264 |
) |
|
| 1265 |
), |
|
| 1266 | 26x |
knit_print( |
| 1267 | 26x |
posterior, |
| 1268 | 26x |
preamble = paste0( |
| 1269 | 26x |
"\n\nTogether, the pseudo and observed data give rise to ", |
| 1270 | 26x |
"the following posterior for the model parameters:\n\n" |
| 1271 |
), |
|
| 1272 | 26x |
params = c("\\phi_1" = "phi1", "\\phi_2" = "phi2"),
|
| 1273 | 26x |
theta = "\\phi", |
| 1274 | 26x |
asis = FALSE, |
| 1275 |
... |
|
| 1276 |
), |
|
| 1277 | 26x |
"\n\n" |
| 1278 |
) |
|
| 1279 | ||
| 1280 | 26x |
if (asis) {
|
| 1281 | 2x |
rv <- knitr::asis_output(rv) |
| 1282 |
} |
|
| 1283 | 26x |
rv |
| 1284 |
} |
|
| 1285 | ||
| 1286 |
# Effloglog ---- |
|
| 1287 | ||
| 1288 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1289 |
#' @param eff_label (`character`)\cr the term used to describe efficacy |
|
| 1290 |
#' @rdname knit_print |
|
| 1291 |
#' @export |
|
| 1292 |
#' @method knit_print Effloglog |
|
| 1293 |
knit_print.Effloglog <- function( |
|
| 1294 |
x, |
|
| 1295 |
..., |
|
| 1296 |
use_values = TRUE, |
|
| 1297 |
fmt = "%5.2f", |
|
| 1298 |
params = NA, |
|
| 1299 |
tox_label = "DLAE", |
|
| 1300 |
eff_label = "efficacy", |
|
| 1301 |
label = "participant", |
|
| 1302 |
preamble = "The prior for θ is given by\\n", |
|
| 1303 |
asis = TRUE |
|
| 1304 |
) {
|
|
| 1305 | 18x |
assert_flag(asis) |
| 1306 | 16x |
assert_character(eff_label, len = 1, any.missing = FALSE) |
| 1307 | ||
| 1308 |
# Prepare |
|
| 1309 | 16x |
tox_label <- h_prepare_labels(tox_label) |
| 1310 | 16x |
eff_label <- h_prepare_labels(eff_label) |
| 1311 | 16x |
label <- h_prepare_labels(label) |
| 1312 | ||
| 1313 | 16x |
y <- tidy(x) |
| 1314 |
# knit_print.ModelParamsNormal expects no row or column names |
|
| 1315 | 16x |
posterior <- ModelParamsNormal(mean = c(x@theta1, x@theta2), cov = x@Q) |
| 1316 | 16x |
rownames(posterior@cov) <- NULL |
| 1317 | 16x |
colnames(posterior@cov) <- NULL |
| 1318 | ||
| 1319 | 16x |
rv <- paste0( |
| 1320 | 16x |
"A linear log-log model with a pseudo data prior will describe the ", |
| 1321 | 16x |
"relationship between dose and ", |
| 1322 | 16x |
eff_label[1], |
| 1323 | 16x |
". The model is given by\n ", |
| 1324 | 16x |
"$$ y_i = \\theta_1 + \\theta_2 \\cdot \\log(\\log(d_i + k)) + \\epsilon_i $$\\n ", |
| 1325 | 16x |
"where k is a constant (equal to ", |
| 1326 | 16x |
x@const, |
| 1327 | 16x |
"), y~i~ is the ", |
| 1328 | 16x |
eff_label[1], |
| 1329 | 16x |
" response for ", |
| 1330 | 16x |
label[1], |
| 1331 | 16x |
" i, treated at dose d~i~ and ε~i~ is an error term. ", |
| 1332 | 16x |
"The εs are iid N(0, ν^-1^).\n\n ", |
| 1333 | 16x |
"The ", |
| 1334 | 16x |
ifelse( |
| 1335 | 16x |
length(x@nu) == 1, |
| 1336 | 16x |
paste0( |
| 1337 | 16x |
ifelse(nrow(y$data) == 0, "prior", "posterior"), |
| 1338 | 16x |
" value of ν is ", |
| 1339 | 16x |
x@nu, |
| 1340 |
"." |
|
| 1341 |
), |
|
| 1342 | 16x |
paste0( |
| 1343 | 16x |
ifelse(nrow(y$data) == 0, "prior", "posterior"), |
| 1344 | 16x |
" distribution of ν is currently Γ(",
|
| 1345 | 16x |
sprintf(fmt, x@nu[1]), |
| 1346 |
", ", |
|
| 1347 | 16x |
sprintf(fmt, x@nu[2]), |
| 1348 |
")." |
|
| 1349 |
) |
|
| 1350 |
), |
|
| 1351 | 16x |
"\n\nThe joint distribution of ", |
| 1352 | 16x |
"θ~1~ and θ~2~ is given by\n\n", |
| 1353 | 16x |
"$$ \\boldsymbol\\theta = \\begin{bmatrix}\\theta_1 \\\\ \\theta_2\\end{bmatrix} ",
|
| 1354 | 16x |
"\\sim N\\left(\\mu, \\nu \\boldsymbol{Q}^\\intercal \\right) $$ \nwhere ",
|
| 1355 | 16x |
"$\\boldsymbol{Q} = \\boldsymbol{X_0}^\\intercal\\boldsymbol{X_0} + ",
|
| 1356 | 16x |
"\\boldsymbol{X}^\\intercal\\boldsymbol{X}$ and **X~0~** is a design matrix ",
|
| 1357 | 16x |
"based on the dose levels in the pseudo data and **X** is a design matrix ", |
| 1358 | 16x |
"based on the dose levels of ", |
| 1359 | 16x |
label[2], |
| 1360 | 16x |
"' no-", |
| 1361 | 16x |
tox_label[1], |
| 1362 |
" ", |
|
| 1363 | 16x |
eff_label[1], |
| 1364 | 16x |
" responses in the observed data, if any.\n\n", |
| 1365 | 16x |
ifelse( |
| 1366 | 16x |
nrow(y$data) == 0, |
| 1367 | 16x |
"\n\nNo observed data has yet been recorded.\n", |
| 1368 | 16x |
paste( |
| 1369 | 16x |
"\n\nThe data observed to date are given in the following table:\n\n", |
| 1370 | 16x |
paste( |
| 1371 | 16x |
(do.call( |
| 1372 | 16x |
function(z) {
|
| 1373 | 4x |
z %>% |
| 1374 | 4x |
dplyr::select(-c(NObs, NGrid, DoseGrid, XLevel)) %>% |
| 1375 | 4x |
knitr::kable() %>% |
| 1376 | 4x |
kableExtra::kable_styling( |
| 1377 | 4x |
bootstrap_options = c("striped", "hover", "condensed")
|
| 1378 |
) |
|
| 1379 |
}, |
|
| 1380 | 16x |
list(z = y$data) |
| 1381 |
)), |
|
| 1382 | 16x |
collapse = "\n" |
| 1383 |
) |
|
| 1384 |
) |
|
| 1385 |
), |
|
| 1386 | 16x |
knit_print( |
| 1387 | 16x |
posterior, |
| 1388 | 16x |
preamble = paste0( |
| 1389 | 16x |
"\n\nTogether, the pseudo and observed data give rise to ", |
| 1390 | 16x |
"the following posterior for the model parameters:\n\n" |
| 1391 |
), |
|
| 1392 | 16x |
params = c("\\theta_1" = "theta1", "\\theta_2" = "theta2"),
|
| 1393 | 16x |
asis = FALSE, |
| 1394 |
... |
|
| 1395 |
), |
|
| 1396 | 16x |
"\n\n" |
| 1397 |
) |
|
| 1398 | ||
| 1399 | 16x |
if (asis) {
|
| 1400 | 2x |
rv <- knitr::asis_output(rv) |
| 1401 |
} |
|
| 1402 | 16x |
rv |
| 1403 |
} |
| 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 | 309x |
params <- ModelParamsNormal(mean, cov) |
| 143 | 309x |
.ModelLogNormal( |
| 144 | 309x |
params = params, |
| 145 | 309x |
ref_dose = positive_number(ref_dose), |
| 146 | 309x |
priormodel = function() {
|
| 147 | ! |
theta ~ dmnorm(mean, prec) |
| 148 | ! |
alpha0 <- theta[1] |
| 149 | ! |
alpha1 <- exp(theta[2]) |
| 150 |
}, |
|
| 151 | 309x |
modelspecs = function(from_prior) {
|
| 152 | 199x |
ms <- list(mean = params@mean, prec = params@prec) |
| 153 | 199x |
if (!from_prior) {
|
| 154 | 188x |
ms$ref_dose <- ref_dose |
| 155 |
} |
|
| 156 | 199x |
ms |
| 157 |
}, |
|
| 158 | 309x |
init = function() {
|
| 159 | 238x |
list(theta = c(0, 1)) |
| 160 |
}, |
|
| 161 | 309x |
datanames = c("nObs", "y", "x"),
|
| 162 | 309x |
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 | 6x |
ModelLogNormal( |
| 173 | 6x |
mean = c(-0.85, 1), |
| 174 | 6x |
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 | 24x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
| 217 | ||
| 218 | 24x |
.LogisticNormal( |
| 219 | 24x |
model_ln, |
| 220 | 24x |
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 | 24x |
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 | 7x |
LogisticNormal( |
| 241 | 7x |
mean = c(-0.85, 1), |
| 242 | 7x |
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 | 221x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
| 287 | ||
| 288 | 221x |
.LogisticLogNormal( |
| 289 | 221x |
model_ln, |
| 290 | 221x |
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 | 14x |
LogisticLogNormal( |
| 306 | 14x |
mean = c(-0.85, 1), |
| 307 | 14x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 308 | 14x |
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 | 19x |
params <- ModelParamsNormal(mean, cov) |
| 361 | 19x |
.LogisticLogNormalSub( |
| 362 | 19x |
params = params, |
| 363 | 19x |
ref_dose = ref_dose, |
| 364 | 19x |
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 | 19x |
priormodel = function() {
|
| 371 | ! |
theta ~ dmnorm(mean, prec) |
| 372 | ! |
alpha0 <- theta[1] |
| 373 | ! |
alpha1 <- exp(theta[2]) |
| 374 |
}, |
|
| 375 | 19x |
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 | 19x |
init = function() {
|
| 383 | 2x |
list(theta = c(0, -20)) |
| 384 |
}, |
|
| 385 | 19x |
datanames = c("nObs", "y", "x"),
|
| 386 | 19x |
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 | 7x |
LogisticLogNormalSub( |
| 398 | 7x |
mean = c(-0.85, 1), |
| 399 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 400 | 7x |
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 This model is also used in the [`DualEndpoint`] classes, so this class |
|
| 422 |
#' can be used to check the prior assumptions on the dose-toxicity model, even |
|
| 423 |
#' when sampling from the prior distribution of the dual endpoint model is not |
|
| 424 |
#' possible. |
|
| 425 |
#' |
|
| 426 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`], |
|
| 427 |
#' [`LogisticLogNormalSub`], [`ProbitLogNormalRel`]. |
|
| 428 |
#' |
|
| 429 |
#' @aliases ProbitLogNormalLogDose |
|
| 430 |
#' @export |
|
| 431 |
#' |
|
| 432 |
.ProbitLogNormal <- setClass( |
|
| 433 |
Class = "ProbitLogNormal", |
|
| 434 |
contains = "ModelLogNormal" |
|
| 435 |
) |
|
| 436 | ||
| 437 |
## constructor ---- |
|
| 438 | ||
| 439 |
#' @rdname ProbitLogNormal-class |
|
| 440 |
#' |
|
| 441 |
#' @inheritParams ModelLogNormal |
|
| 442 |
#' |
|
| 443 |
#' @export |
|
| 444 |
#' @example examples/Model-class-ProbitLogNormal.R |
|
| 445 |
#' |
|
| 446 |
ProbitLogNormal <- function(mean, cov, ref_dose = 1) {
|
|
| 447 | 37x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
| 448 | ||
| 449 | 37x |
.ProbitLogNormal( |
| 450 | 37x |
model_ln, |
| 451 | 37x |
datamodel = function() {
|
| 452 | ! |
for (i in 1:nObs) {
|
| 453 | ! |
probit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 454 | ! |
y[i] ~ dbern(p[i]) |
| 455 |
} |
|
| 456 |
} |
|
| 457 |
) |
|
| 458 |
} |
|
| 459 | ||
| 460 |
## default constructor ---- |
|
| 461 | ||
| 462 |
#' @rdname ProbitLogNormal-class |
|
| 463 |
#' @note Typically, end users will not use the `.DefaultProbitLogNormal()` function. |
|
| 464 |
#' @export |
|
| 465 |
.DefaultProbitLogNormal <- function() {
|
|
| 466 | 7x |
ProbitLogNormal( |
| 467 | 7x |
mean = c(-0.85, 1), |
| 468 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 469 | 7x |
ref_dose = 7.2 |
| 470 |
) |
|
| 471 |
} |
|
| 472 | ||
| 473 |
# ProbitLogNormalRel ---- |
|
| 474 | ||
| 475 |
## class ---- |
|
| 476 | ||
| 477 |
#' `ProbitLogNormalRel` |
|
| 478 |
#' |
|
| 479 |
#' @description `r lifecycle::badge("stable")`
|
|
| 480 |
#' |
|
| 481 |
#' [`ProbitLogNormalRel`] is the class for probit regression model with a bivariate |
|
| 482 |
#' normal prior on the intercept and log slope. |
|
| 483 |
#' |
|
| 484 |
#' @details The covariate is the dose \eqn{x} divided by a reference dose \eqn{x*},
|
|
| 485 |
#' i.e.: |
|
| 486 |
#' \deqn{probit[p(x)] = alpha0 + alpha1 * x/x*,}
|
|
| 487 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 488 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).}
|
|
| 489 |
#' |
|
| 490 |
#' @note This model is also used in the [`DualEndpoint`] classes, so this class |
|
| 491 |
#' can be used to check the prior assumptions on the dose-toxicity model, even |
|
| 492 |
#' when sampling from the prior distribution of the dual endpoint model is not |
|
| 493 |
#' possible. |
|
| 494 |
#' |
|
| 495 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`], |
|
| 496 |
#' [`LogisticLogNormalSub`], [`ProbitLogNormal`]. |
|
| 497 |
#' |
|
| 498 |
#' @aliases ProbitLogNormalRel |
|
| 499 |
#' @export |
|
| 500 |
#' |
|
| 501 |
.ProbitLogNormalRel <- setClass( |
|
| 502 |
Class = "ProbitLogNormalRel", |
|
| 503 |
contains = "ModelLogNormal" |
|
| 504 |
) |
|
| 505 | ||
| 506 |
## constructor ---- |
|
| 507 | ||
| 508 |
#' @rdname ProbitLogNormalRel-class |
|
| 509 |
#' |
|
| 510 |
#' @inheritParams ModelLogNormal |
|
| 511 |
#' |
|
| 512 |
#' @export |
|
| 513 |
#' @example examples/Model-class-ProbitLogNormalRel.R |
|
| 514 |
#' |
|
| 515 |
ProbitLogNormalRel <- function(mean, cov, ref_dose = 1) {
|
|
| 516 | 19x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
| 517 | ||
| 518 | 19x |
.ProbitLogNormalRel( |
| 519 | 19x |
model_ln, |
| 520 | 19x |
datamodel = function() {
|
| 521 | ! |
for (i in 1:nObs) {
|
| 522 | ! |
probit(p[i]) <- alpha0 + alpha1 * (x[i] / ref_dose) |
| 523 | ! |
y[i] ~ dbern(p[i]) |
| 524 |
} |
|
| 525 |
} |
|
| 526 |
) |
|
| 527 |
} |
|
| 528 | ||
| 529 |
## default constructor ---- |
|
| 530 | ||
| 531 |
#' @rdname ProbitLogNormalRel-class |
|
| 532 |
#' @note Typically, end users will not use the `.DefaultProbitLogNormalRel()` function. |
|
| 533 |
#' @export |
|
| 534 |
.DefaultProbitLogNormalRel <- function() {
|
|
| 535 | 7x |
ProbitLogNormalRel( |
| 536 | 7x |
mean = c(-0.85, 1), |
| 537 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
| 538 |
) |
|
| 539 |
} |
|
| 540 | ||
| 541 |
# LogisticLogNormalGrouped ---- |
|
| 542 | ||
| 543 |
## class ---- |
|
| 544 | ||
| 545 |
#' `LogisticLogNormalGrouped` |
|
| 546 |
#' |
|
| 547 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 548 |
#' |
|
| 549 |
#' [`LogisticLogNormalGrouped`] is the class for a logistic regression model |
|
| 550 |
#' for both the mono and the combo arms of the simultaneous dose escalation |
|
| 551 |
#' design. |
|
| 552 |
#' |
|
| 553 |
#' @details The continuous covariate is the natural logarithm of the dose \eqn{x} divided by
|
|
| 554 |
#' the reference dose \eqn{x*} as in [`LogisticLogNormal`]. In addition,
|
|
| 555 |
#' \eqn{I_c} is a binary indicator covariate which is 1 for the combo arm and 0 for the mono arm.
|
|
| 556 |
#' The model is then defined as: |
|
| 557 |
#' \deqn{logit[p(x)] = (alpha0 + I_c * delta0) + (alpha1 + I_c * delta1) * log(x / x*),}
|
|
| 558 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x},
|
|
| 559 |
#' and `delta0` and `delta1` are the differences in the combo arm compared to the mono intercept |
|
| 560 |
#' and slope parameters `alpha0` and `alpha1`. |
|
| 561 |
#' The prior is defined as \deqn{(alpha0, log(delta0), log(alpha1), log(delta1)) ~ Normal(mean, cov).}
|
|
| 562 |
#' |
|
| 563 |
#' @seealso [`ModelLogNormal`], [`LogisticLogNormal`]. |
|
| 564 |
#' |
|
| 565 |
#' @aliases LogisticLogNormalGrouped |
|
| 566 |
#' @export |
|
| 567 |
#' |
|
| 568 |
.LogisticLogNormalGrouped <- setClass( |
|
| 569 |
Class = "LogisticLogNormalGrouped", |
|
| 570 |
contains = "ModelLogNormal" |
|
| 571 |
) |
|
| 572 | ||
| 573 |
## constructor ---- |
|
| 574 | ||
| 575 |
#' @rdname LogisticLogNormalGrouped-class |
|
| 576 |
#' |
|
| 577 |
#' @inheritParams ModelLogNormal |
|
| 578 |
#' |
|
| 579 |
#' @export |
|
| 580 |
#' @example examples/Model-class-LogisticLogNormalGrouped.R |
|
| 581 |
#' |
|
| 582 |
LogisticLogNormalGrouped <- function(mean, cov, ref_dose = 1) {
|
|
| 583 | 32x |
params <- ModelParamsNormal(mean, cov) |
| 584 | 32x |
.LogisticLogNormalGrouped( |
| 585 | 32x |
params = params, |
| 586 | 32x |
ref_dose = positive_number(ref_dose), |
| 587 | 32x |
priormodel = function() {
|
| 588 | ! |
theta ~ dmnorm(mean, prec) |
| 589 | ! |
alpha0 <- theta[1] |
| 590 | ! |
delta0 <- exp(theta[2]) |
| 591 | ! |
alpha1 <- exp(theta[3]) |
| 592 | ! |
delta1 <- exp(theta[4]) |
| 593 |
}, |
|
| 594 | 32x |
datamodel = function() {
|
| 595 | ! |
for (i in 1:nObs) {
|
| 596 | ! |
logit(p[i]) <- (alpha0 + is_combo[i] * delta0) + |
| 597 | ! |
(alpha1 + is_combo[i] * delta1) * log(x[i] / ref_dose) |
| 598 | ! |
y[i] ~ dbern(p[i]) |
| 599 |
} |
|
| 600 |
}, |
|
| 601 | 32x |
modelspecs = function(group, from_prior) {
|
| 602 | 75x |
ms <- list( |
| 603 | 75x |
mean = params@mean, |
| 604 | 75x |
prec = params@prec |
| 605 |
) |
|
| 606 | 75x |
if (!from_prior) {
|
| 607 | 74x |
ms$ref_dose <- ref_dose |
| 608 | 74x |
ms$is_combo <- as.integer(group == "combo") |
| 609 |
} |
|
| 610 | 75x |
ms |
| 611 |
}, |
|
| 612 | 32x |
init = function() {
|
| 613 | 75x |
list(theta = c(0, 1, 1, 1)) |
| 614 |
}, |
|
| 615 | 32x |
datanames = c("nObs", "y", "x"),
|
| 616 | 32x |
sample = c("alpha0", "delta0", "alpha1", "delta1")
|
| 617 |
) |
|
| 618 |
} |
|
| 619 | ||
| 620 |
## default constructor ---- |
|
| 621 | ||
| 622 |
#' @rdname LogisticLogNormalGrouped-class |
|
| 623 |
#' @note Typically, end users will not use the `.DefaultLogisticLogNormalGrouped()` function. |
|
| 624 |
#' @export |
|
| 625 |
.DefaultLogisticLogNormalGrouped <- function() {
|
|
| 626 | 22x |
LogisticLogNormalGrouped( |
| 627 | 22x |
mean = rep(0, 4), |
| 628 | 22x |
cov = diag(rep(1, 4)), |
| 629 |
) |
|
| 630 |
} |
|
| 631 | ||
| 632 |
# LogisticKadane ---- |
|
| 633 | ||
| 634 |
## class ---- |
|
| 635 | ||
| 636 |
#' `LogisticKadane` |
|
| 637 |
#' |
|
| 638 |
#' @description `r lifecycle::badge("stable")`
|
|
| 639 |
#' |
|
| 640 |
#' [`LogisticKadane`] is the class for the logistic model in the parametrization |
|
| 641 |
#' of Kadane et al. (1980). |
|
| 642 |
#' |
|
| 643 |
#' @details Let `rho0 = p(xmin)` be the probability of a DLT at the minimum dose |
|
| 644 |
#' `xmin`, and let `gamma` be the dose with target toxicity probability `theta`, |
|
| 645 |
#' i.e. \eqn{p(gamma) = theta}. Then it can easily be shown that the logistic
|
|
| 646 |
#' regression model has intercept |
|
| 647 |
#' \deqn{[gamma * logit(rho0) - xmin * logit(theta)] / [gamma - xmin]}
|
|
| 648 |
#' and slope |
|
| 649 |
#' \deqn{[logit(theta) - logit(rho0)] / [gamma - xmin].}
|
|
| 650 |
#' |
|
| 651 |
#' The priors are \deqn{gamma ~ Unif(xmin, xmax).} and
|
|
| 652 |
#' \deqn{rho0 ~ Unif(0, theta).}
|
|
| 653 |
#' |
|
| 654 |
#' @note The slots of this class, required for creating the model, are the target |
|
| 655 |
#' toxicity, as well as the minimum and maximum of the dose range. Note that |
|
| 656 |
#' these can be different from the minimum and maximum of the dose grid in the |
|
| 657 |
#' data later on. |
|
| 658 |
#' |
|
| 659 |
#' @slot theta (`proportion`)\cr the target toxicity probability. |
|
| 660 |
#' @slot xmin (`number`)\cr the minimum of the dose range. |
|
| 661 |
#' @slot xmax (`number`)\cr the maximum of the dose range. |
|
| 662 |
#' |
|
| 663 |
#' @seealso [`ModelLogNormal`] |
|
| 664 |
#' |
|
| 665 |
#' @aliases LogisticKadane |
|
| 666 |
#' @export |
|
| 667 |
#' |
|
| 668 |
.LogisticKadane <- setClass( |
|
| 669 |
Class = "LogisticKadane", |
|
| 670 |
contains = "GeneralModel", |
|
| 671 |
slots = c( |
|
| 672 |
theta = "numeric", |
|
| 673 |
xmin = "numeric", |
|
| 674 |
xmax = "numeric" |
|
| 675 |
), |
|
| 676 |
prototype = prototype( |
|
| 677 |
theta = 0.3, |
|
| 678 |
xmin = 0.1, |
|
| 679 |
xmax = 1 |
|
| 680 |
), |
|
| 681 |
validity = v_model_logistic_kadane |
|
| 682 |
) |
|
| 683 | ||
| 684 |
## constructor ---- |
|
| 685 | ||
| 686 |
#' @rdname LogisticKadane-class |
|
| 687 |
#' |
|
| 688 |
#' @param theta (`proportion`)\cr the target toxicity probability. |
|
| 689 |
#' @param xmin (`number`)\cr the minimum of the dose range. |
|
| 690 |
#' @param xmax (`number`)\cr the maximum of the dose range. |
|
| 691 |
#' |
|
| 692 |
#' @export |
|
| 693 |
#' @example examples/Model-class-LogisticKadane.R |
|
| 694 |
#' |
|
| 695 |
LogisticKadane <- function(theta, xmin, xmax) {
|
|
| 696 | 72x |
.LogisticKadane( |
| 697 | 72x |
theta = theta, |
| 698 | 72x |
xmin = xmin, |
| 699 | 72x |
xmax = xmax, |
| 700 | 72x |
datamodel = function() {
|
| 701 | ! |
for (i in 1:nObs) {
|
| 702 | ! |
logit(p[i]) <- (1 / (gamma - xmin)) * |
| 703 | ! |
(gamma * |
| 704 | ! |
logit(rho0) - |
| 705 | ! |
xmin * logit(theta) + |
| 706 | ! |
x[i] * (logit(theta) - logit(rho0))) |
| 707 | ! |
y[i] ~ dbern(p[i]) |
| 708 |
} |
|
| 709 |
}, |
|
| 710 | 72x |
priormodel = function() {
|
| 711 | ! |
rho0 ~ dunif(0, theta) |
| 712 | ! |
gamma ~ dunif(xmin, xmax) |
| 713 |
}, |
|
| 714 | 72x |
modelspecs = function() {
|
| 715 | 27x |
list(theta = theta, xmin = xmin, xmax = xmax) |
| 716 |
}, |
|
| 717 | 72x |
init = function() {
|
| 718 | 29x |
list(rho0 = theta / 10, gamma = (xmax - xmin) / 2) |
| 719 |
}, |
|
| 720 | 72x |
datanames = c("nObs", "y", "x"),
|
| 721 | 72x |
sample = c("rho0", "gamma")
|
| 722 |
) |
|
| 723 |
} |
|
| 724 | ||
| 725 |
## default constructor ---- |
|
| 726 | ||
| 727 |
#' @rdname LogisticKadane-class |
|
| 728 |
#' @note Typically, end-users will not use the `.DefaultLogisticKadane()` function. |
|
| 729 |
#' @export |
|
| 730 |
.DefaultLogisticKadane <- function() {
|
|
| 731 | 7x |
LogisticKadane(theta = 0.33, xmin = 1, xmax = 200) |
| 732 |
} |
|
| 733 | ||
| 734 | ||
| 735 |
# LogisticKadaneBetaGamma ---- |
|
| 736 | ||
| 737 |
## class ---- |
|
| 738 | ||
| 739 |
#' `LogisticKadaneBetaGamma` |
|
| 740 |
#' |
|
| 741 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 742 |
#' |
|
| 743 |
#' [`LogisticKadaneBetaGamma`] is the class for the logistic model in the parametrization |
|
| 744 |
#' of Kadane et al. (1980), using a beta and a gamma distribution as the model priors. |
|
| 745 |
#' |
|
| 746 |
#' @details Let `rho0 = p(xmin)` be the probability of a DLT at the minimum dose |
|
| 747 |
#' `xmin`, and let `gamma` be the dose with target toxicity probability `theta`, |
|
| 748 |
#' i.e. \eqn{p(gamma) = theta}. Then it can easily be shown that the logistic
|
|
| 749 |
#' regression model has intercept |
|
| 750 |
#' \deqn{[gamma * logit(rho0) - xmin * logit(theta)] / [gamma - xmin]}
|
|
| 751 |
#' and slope |
|
| 752 |
#' \deqn{[logit(theta) - logit(rho0)] / [gamma - xmin].}
|
|
| 753 |
#' |
|
| 754 |
#' The prior for `gamma`, is \deqn{gamma ~ Gamma(shape, rate).}.
|
|
| 755 |
#' The prior for `rho0 = p(xmin)`, is \deqn{rho0 ~ Beta(alpha, beta).}
|
|
| 756 |
#' |
|
| 757 |
#' @note The slots of this class, required for creating the model, are the same |
|
| 758 |
#' as in the `LogisticKadane` class. In addition, the shape parameters of the |
|
| 759 |
#' Beta prior distribution of `rho0` and the shape and rate parameters of the |
|
| 760 |
#' Gamma prior distribution of `gamma`, are required for creating the prior model. |
|
| 761 |
#' |
|
| 762 |
#' @slot theta (`proportion`)\cr the target toxicity probability. |
|
| 763 |
#' @slot xmin (`number`)\cr the minimum of the dose range. |
|
| 764 |
#' @slot xmax (`number`)\cr the maximum of the dose range. |
|
| 765 |
#' @slot alpha (`number`)\cr the first shape parameter of the Beta prior distribution |
|
| 766 |
#' of `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
| 767 |
#' @slot beta (`number`)\cr the second shape parameter of the Beta prior distribution |
|
| 768 |
#' of `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
| 769 |
#' @slot shape (`number`)\cr the shape parameter of the Gamma prior distribution |
|
| 770 |
#' of `gamma` the dose with target toxicity probability `theta`. |
|
| 771 |
#' @slot rate (`number`)\cr the rate parameter of the Gamma prior distribution |
|
| 772 |
#' of `gamma` the dose with target toxicity probability `theta`. |
|
| 773 |
#' |
|
| 774 |
#' @seealso [`ModelLogNormal`], [`LogisticKadane`]. |
|
| 775 |
#' |
|
| 776 |
#' @aliases LogisticKadaneBetaGamma |
|
| 777 |
#' @export |
|
| 778 |
#' |
|
| 779 |
.LogisticKadaneBetaGamma <- setClass( |
|
| 780 |
Class = "LogisticKadaneBetaGamma", |
|
| 781 |
contains = "LogisticKadane", |
|
| 782 |
slots = c( |
|
| 783 |
alpha = "numeric", |
|
| 784 |
beta = "numeric", |
|
| 785 |
shape = "numeric", |
|
| 786 |
rate = "numeric" |
|
| 787 |
), |
|
| 788 |
prototype = prototype( |
|
| 789 |
theta = 0.3, |
|
| 790 |
xmin = 0.1, |
|
| 791 |
xmax = 1, |
|
| 792 |
alpha = 1, |
|
| 793 |
beta = 0.5, |
|
| 794 |
shape = 1.2, |
|
| 795 |
rate = 2.5 |
|
| 796 |
), |
|
| 797 |
validity = v_model_logistic_kadane_beta_gamma |
|
| 798 |
) |
|
| 799 | ||
| 800 |
## constructor ---- |
|
| 801 | ||
| 802 |
#' @rdname LogisticKadaneBetaGamma-class |
|
| 803 |
#' |
|
| 804 |
#' @inheritParams LogisticKadane |
|
| 805 |
#' |
|
| 806 |
#' @param alpha (`number`)\cr the first shape parameter of the Beta prior distribution |
|
| 807 |
#' `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
| 808 |
#' @param beta (`number`)\cr the second shape parameter of the Beta prior distribution |
|
| 809 |
#' `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
| 810 |
#' @param shape (`number`)\cr the shape parameter of the Gamma prior distribution |
|
| 811 |
#' `gamma` the dose with target toxicity probability `theta`. |
|
| 812 |
#' @param rate (`number`)\cr the rate parameter of the Gamma prior distribution |
|
| 813 |
#' `gamma` the dose with target toxicity probability `theta`. |
|
| 814 |
#' |
|
| 815 |
#' @export |
|
| 816 |
#' @example examples/Model-class-LogisticKadaneBetaGamma.R |
|
| 817 |
#' |
|
| 818 |
LogisticKadaneBetaGamma <- function( |
|
| 819 |
theta, |
|
| 820 |
xmin, |
|
| 821 |
xmax, |
|
| 822 |
alpha, |
|
| 823 |
beta, |
|
| 824 |
shape, |
|
| 825 |
rate |
|
| 826 |
) {
|
|
| 827 | 24x |
model_lk <- LogisticKadane(theta = theta, xmin = xmin, xmax = xmax) |
| 828 | 24x |
.LogisticKadaneBetaGamma( |
| 829 | 24x |
model_lk, |
| 830 | 24x |
alpha = alpha, |
| 831 | 24x |
beta = beta, |
| 832 | 24x |
shape = shape, |
| 833 | 24x |
rate = rate, |
| 834 | 24x |
priormodel = function() {
|
| 835 | ! |
rho0 ~ dbeta(alpha, beta) |
| 836 | ! |
gamma ~ dgamma(shape, rate) |
| 837 | ! |
lowestdose <- xmin |
| 838 | ! |
highestdose <- xmax |
| 839 | ! |
DLTtarget <- theta |
| 840 |
}, |
|
| 841 | 24x |
modelspecs = function() {
|
| 842 | 2x |
list( |
| 843 | 2x |
theta = theta, |
| 844 | 2x |
xmin = xmin, |
| 845 | 2x |
xmax = xmax, |
| 846 | 2x |
alpha = alpha, |
| 847 | 2x |
beta = beta, |
| 848 | 2x |
shape = shape, |
| 849 | 2x |
rate = rate |
| 850 |
) |
|
| 851 |
} |
|
| 852 |
) |
|
| 853 |
} |
|
| 854 | ||
| 855 |
## default constructor ---- |
|
| 856 | ||
| 857 |
#' @rdname LogisticKadaneBetaGamma-class |
|
| 858 |
#' @note Typically, end users will not use the `.Default()` function. |
|
| 859 |
#' @export |
|
| 860 |
.DefaultLogisticKadaneBetaGamma <- function() {
|
|
| 861 | 7x |
LogisticKadaneBetaGamma( |
| 862 | 7x |
theta = 0.3, |
| 863 | 7x |
xmin = 0, |
| 864 | 7x |
xmax = 7, |
| 865 | 7x |
alpha = 1, |
| 866 | 7x |
beta = 19, |
| 867 | 7x |
shape = 0.5625, |
| 868 | 7x |
rate = 0.125 |
| 869 |
) |
|
| 870 |
} |
|
| 871 | ||
| 872 |
# LogisticNormalMixture ---- |
|
| 873 | ||
| 874 |
## class ---- |
|
| 875 | ||
| 876 |
#' `LogisticNormalMixture` |
|
| 877 |
#' |
|
| 878 |
#' @description `r lifecycle::badge("stable")`
|
|
| 879 |
#' |
|
| 880 |
#' [`LogisticNormalMixture`] is the class for standard logistic regression model |
|
| 881 |
#' with a mixture of two bivariate normal priors on the intercept and slope parameters. |
|
| 882 |
#' |
|
| 883 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided by
|
|
| 884 |
#' the reference dose \eqn{x*}, i.e.:
|
|
| 885 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),}
|
|
| 886 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 887 |
#' The prior |
|
| 888 |
#' \deqn{(alpha0, alpha1) ~ w * Normal(mean1, cov1) + (1 - w) * Normal(mean2, cov2).}
|
|
| 889 |
#' The weight w for the first component is assigned a beta prior `B(a, b)`. |
|
| 890 |
#' |
|
| 891 |
#' @note The weight of the two normal priors is a model parameter, hence it is a |
|
| 892 |
#' flexible mixture. This type of prior is often used with a mixture of a minimal |
|
| 893 |
#' informative and an informative component, in order to make the CRM more robust |
|
| 894 |
#' to data deviations from the informative component. |
|
| 895 |
#' |
|
| 896 |
#' @slot comp1 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
| 897 |
#' the first component. |
|
| 898 |
#' @slot comp2 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
| 899 |
#' the second component. |
|
| 900 |
#' @slot weightpar (`numeric`)\cr the beta parameters for the weight of the |
|
| 901 |
#' first component. It must a be a named vector of length 2 with names `a` and |
|
| 902 |
#' `b` and with strictly positive values. |
|
| 903 |
#' @slot ref_dose (`positive_number`)\cr the reference dose. |
|
| 904 |
#' |
|
| 905 |
#' @seealso [`ModelParamsNormal`], [`ModelLogNormal`], |
|
| 906 |
#' [`LogisticNormalFixedMixture`], [`LogisticLogNormalMixture`]. |
|
| 907 |
#' |
|
| 908 |
#' @aliases LogisticNormalMixture |
|
| 909 |
#' @export |
|
| 910 |
#' |
|
| 911 |
.LogisticNormalMixture <- setClass( |
|
| 912 |
Class = "LogisticNormalMixture", |
|
| 913 |
contains = "GeneralModel", |
|
| 914 |
slots = c( |
|
| 915 |
comp1 = "ModelParamsNormal", |
|
| 916 |
comp2 = "ModelParamsNormal", |
|
| 917 |
weightpar = "numeric", |
|
| 918 |
ref_dose = "numeric" |
|
| 919 |
), |
|
| 920 |
prototype = prototype( |
|
| 921 |
comp1 = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
| 922 |
comp2 = ModelParamsNormal(mean = c(-1, 1), cov = diag(2)), |
|
| 923 |
weightpar = c(a = 1, b = 1), |
|
| 924 |
ref_dose = 1 |
|
| 925 |
), |
|
| 926 |
validity = v_model_logistic_normal_mix |
|
| 927 |
) |
|
| 928 | ||
| 929 |
## constructor ---- |
|
| 930 | ||
| 931 |
#' @rdname LogisticNormalMixture-class |
|
| 932 |
#' |
|
| 933 |
#' @param comp1 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
| 934 |
#' the first component. See [`ModelParamsNormal`] for more details. |
|
| 935 |
#' @param comp2 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
| 936 |
#' the second component. See [`ModelParamsNormal`] for more details. |
|
| 937 |
#' @param weightpar (`numeric`)\cr the beta parameters for the weight of the |
|
| 938 |
#' first component. It must a be a named vector of length 2 with names `a` and |
|
| 939 |
#' `b` and with strictly positive values. |
|
| 940 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*}
|
|
| 941 |
#' (strictly positive number). |
|
| 942 |
#' |
|
| 943 |
#' @export |
|
| 944 |
#' @example examples/Model-class-LogisticNormalMixture.R |
|
| 945 |
#' |
|
| 946 |
LogisticNormalMixture <- function(comp1, comp2, weightpar, ref_dose) {
|
|
| 947 | 21x |
assert_number(ref_dose) |
| 948 | ||
| 949 | 21x |
.LogisticNormalMixture( |
| 950 | 21x |
comp1 = comp1, |
| 951 | 21x |
comp2 = comp2, |
| 952 | 21x |
weightpar = weightpar, |
| 953 | 21x |
ref_dose = ref_dose, |
| 954 | 21x |
datamodel = function() {
|
| 955 |
# The logistic likelihood - the same as for non-mixture case. |
|
| 956 | ! |
for (i in 1:nObs) {
|
| 957 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 958 | ! |
y[i] ~ dbern(p[i]) |
| 959 |
} |
|
| 960 |
}, |
|
| 961 | 21x |
priormodel = function() {
|
| 962 | ! |
w ~ dbeta(weightpar[1], weightpar[2]) |
| 963 | ! |
wc <- 1 - w |
| 964 | ! |
comp0 ~ dbern(wc) |
| 965 | ! |
comp <- comp0 + 1 |
| 966 |
# Conditional on the component index "comp", which is 1 or 2. |
|
| 967 |
# comp = 1 with probability "w" and comp = 2 with probability "1 - w". |
|
| 968 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
| 969 | ! |
alpha0 <- theta[1] |
| 970 | ! |
alpha1 <- theta[2] |
| 971 |
}, |
|
| 972 | 21x |
modelspecs = function(from_prior) {
|
| 973 | 2x |
ms <- list( |
| 974 | 2x |
mean = cbind(comp1@mean, comp2@mean), |
| 975 | 2x |
prec = array(data = c(comp1@prec, comp2@prec), dim = c(2, 2, 2)), |
| 976 | 2x |
weightpar = weightpar |
| 977 |
) |
|
| 978 | 2x |
if (!from_prior) {
|
| 979 | 1x |
ms$ref_dose <- ref_dose |
| 980 |
} |
|
| 981 | 2x |
ms |
| 982 |
}, |
|
| 983 | 21x |
init = function() {
|
| 984 | 2x |
list(theta = c(0, 1)) |
| 985 |
}, |
|
| 986 | 21x |
datanames = c("nObs", "y", "x"),
|
| 987 | 21x |
sample = c("alpha0", "alpha1", "w")
|
| 988 |
) |
|
| 989 |
} |
|
| 990 | ||
| 991 |
## default constructor ---- |
|
| 992 | ||
| 993 |
#' @rdname LogisticNormalMixture-class |
|
| 994 |
#' @note Typically, end-users will not use the `.DefaultLogisticNormalMixture()` function. |
|
| 995 |
#' @export |
|
| 996 |
.DefaultLogisticNormalMixture <- function() {
|
|
| 997 |
# nolint |
|
| 998 | 7x |
LogisticNormalMixture( |
| 999 | 7x |
comp1 = ModelParamsNormal( |
| 1000 | 7x |
mean = c(-0.85, 1), |
| 1001 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
| 1002 |
), |
|
| 1003 | 7x |
comp2 = ModelParamsNormal( |
| 1004 | 7x |
mean = c(1, 1.5), |
| 1005 | 7x |
cov = matrix(c(1.2, -0.45, -0.45, 0.6), nrow = 2) |
| 1006 |
), |
|
| 1007 | 7x |
weightpar = c(a = 1, b = 1), |
| 1008 | 7x |
ref_dose = 50 |
| 1009 |
) |
|
| 1010 |
} |
|
| 1011 | ||
| 1012 |
# LogisticNormalFixedMixture ---- |
|
| 1013 | ||
| 1014 |
## class ---- |
|
| 1015 | ||
| 1016 |
#' `LogisticNormalFixedMixture` |
|
| 1017 |
#' |
|
| 1018 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1019 |
#' |
|
| 1020 |
#' [`LogisticNormalFixedMixture`] is the class for standard logistic regression |
|
| 1021 |
#' model with fixed mixture of multiple bivariate (log) normal priors on the |
|
| 1022 |
#' intercept and slope parameters. The weights of the normal priors are fixed, |
|
| 1023 |
#' hence no additional model parameters are introduced. This type of prior is |
|
| 1024 |
#' often used to better approximate a given posterior distribution, or when the |
|
| 1025 |
#' information is given in terms of a mixture. |
|
| 1026 |
#' |
|
| 1027 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided
|
|
| 1028 |
#' by the reference dose \eqn{x*}, i.e.:
|
|
| 1029 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),}
|
|
| 1030 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}.
|
|
| 1031 |
#' The prior |
|
| 1032 |
#' \deqn{(alpha0, alpha1) ~ w1 * Normal(mean1, cov1) + ... + wK * Normal(meanK, covK),}
|
|
| 1033 |
#' if a normal prior is used and |
|
| 1034 |
#' \deqn{(alpha0, log(alpha1)) ~ w1 * Normal(mean1, cov1) + ... + wK * Normal(meanK, covK),}
|
|
| 1035 |
#' if a log normal prior is used. |
|
| 1036 |
#' The weights \eqn{w1, ..., wK} of the components are fixed and sum to 1.
|
|
| 1037 |
#' |
|
| 1038 |
#' The slots of this class comprise a list with components parameters. Every |
|
| 1039 |
#' single component contains the mean vector and the covariance matrix of |
|
| 1040 |
#' bivariate normal distributions. Remaining slots are the weights of the |
|
| 1041 |
#' components as well as the reference dose. Moreover, a special indicator |
|
| 1042 |
#' slot specifies whether a log normal prior is used. |
|
| 1043 |
#' |
|
| 1044 |
#' @slot components (`list`)\cr the specifications of the mixture components, |
|
| 1045 |
#' a list with [`ModelParamsNormal`] objects for each bivariate (log) normal |
|
| 1046 |
#' prior. |
|
| 1047 |
#' @slot weights (`numeric`)\cr the weights of the components; these must be |
|
| 1048 |
#' positive and must sum to 1. |
|
| 1049 |
#' @slot ref_dose (`positive_number`)\cr the reference dose. |
|
| 1050 |
#' @slot log_normal (`flag`)\cr should a log normal prior be used, such |
|
| 1051 |
#' that the mean vectors and covariance matrices are valid for the intercept |
|
| 1052 |
#' and log slope? |
|
| 1053 |
#' |
|
| 1054 |
#' @seealso [`ModelParamsNormal`], [`ModelLogNormal`], |
|
| 1055 |
#' [`LogisticNormalMixture`], [`LogisticLogNormalMixture`]. |
|
| 1056 |
#' |
|
| 1057 |
#' @aliases LogisticNormalFixedMixture |
|
| 1058 |
#' @export |
|
| 1059 |
#' |
|
| 1060 |
.LogisticNormalFixedMixture <- setClass( |
|
| 1061 |
Class = "LogisticNormalFixedMixture", |
|
| 1062 |
contains = "GeneralModel", |
|
| 1063 |
slots = c( |
|
| 1064 |
components = "list", |
|
| 1065 |
weights = "numeric", |
|
| 1066 |
ref_dose = "numeric", |
|
| 1067 |
log_normal = "logical" |
|
| 1068 |
), |
|
| 1069 |
prototype = prototype( |
|
| 1070 |
components = list( |
|
| 1071 |
comp1 = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
| 1072 |
comp2 = ModelParamsNormal(mean = c(-1, 1), cov = diag(2)) |
|
| 1073 |
), |
|
| 1074 |
weights = c(0.5, 0.5), |
|
| 1075 |
ref_dose = 1, |
|
| 1076 |
log_normal = FALSE |
|
| 1077 |
), |
|
| 1078 |
validity = v_model_logistic_normal_fixed_mix |
|
| 1079 |
) |
|
| 1080 | ||
| 1081 |
## constructor ---- |
|
| 1082 | ||
| 1083 |
#' @rdname LogisticNormalFixedMixture-class |
|
| 1084 |
#' |
|
| 1085 |
#' @param components (`list`)\cr the specifications of the mixture components, |
|
| 1086 |
#' a list with [`ModelParamsNormal`] objects for each bivariate (log) normal |
|
| 1087 |
#' prior. |
|
| 1088 |
#' @param weights (`numeric`)\cr the weights of the components; these must be |
|
| 1089 |
#' positive and will be normalized to sum to 1. |
|
| 1090 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*}
|
|
| 1091 |
#' (strictly positive number). |
|
| 1092 |
#' @param log_normal (`flag`)\cr should a log normal prior be specified, such |
|
| 1093 |
#' that the mean vectors and covariance matrices are valid for the intercept |
|
| 1094 |
#' and log slope? |
|
| 1095 |
#' |
|
| 1096 |
#' @export |
|
| 1097 |
#' @example examples/Model-class-LogisticNormalFixedMixture.R |
|
| 1098 |
#' |
|
| 1099 |
LogisticNormalFixedMixture <- function( |
|
| 1100 |
components, |
|
| 1101 |
weights, |
|
| 1102 |
ref_dose, |
|
| 1103 |
log_normal = FALSE |
|
| 1104 |
) {
|
|
| 1105 | 28x |
assert_numeric(weights) |
| 1106 | 28x |
assert_number(ref_dose) |
| 1107 | 28x |
assert_flag(log_normal) |
| 1108 | ||
| 1109 |
# Normalize weights to sum to 1. |
|
| 1110 | 28x |
weights <- weights / sum(weights) |
| 1111 | ||
| 1112 | 28x |
.LogisticNormalFixedMixture( |
| 1113 | 28x |
components = components, |
| 1114 | 28x |
weights = weights, |
| 1115 | 28x |
ref_dose = positive_number(ref_dose), |
| 1116 | 28x |
log_normal = log_normal, |
| 1117 | 28x |
datamodel = function() {
|
| 1118 | ! |
for (i in 1:nObs) {
|
| 1119 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 1120 | ! |
y[i] ~ dbern(p[i]) |
| 1121 |
} |
|
| 1122 |
}, |
|
| 1123 | 28x |
priormodel = if (log_normal) {
|
| 1124 | 2x |
function() {
|
| 1125 | ! |
comp ~ dcat(weights) |
| 1126 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
| 1127 | ! |
alpha0 <- theta[1] |
| 1128 | ! |
alpha1 <- exp(theta[2]) |
| 1129 |
} |
|
| 1130 |
} else {
|
|
| 1131 | 26x |
function() {
|
| 1132 | ! |
comp ~ dcat(weights) |
| 1133 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
| 1134 | ! |
alpha0 <- theta[1] |
| 1135 | ! |
alpha1 <- theta[2] |
| 1136 |
} |
|
| 1137 |
}, |
|
| 1138 | 28x |
modelspecs = function(from_prior) {
|
| 1139 | 4x |
ms <- list( |
| 1140 | 4x |
weights = weights, |
| 1141 | 4x |
mean = do.call( |
| 1142 | 4x |
cbind, |
| 1143 | 4x |
lapply(components, h_slots, "mean", simplify = TRUE) |
| 1144 |
), |
|
| 1145 | 4x |
prec = array( |
| 1146 | 4x |
do.call(c, lapply(components, h_slots, "prec", simplify = TRUE)), |
| 1147 | 4x |
dim = c(2, 2, length(components)) |
| 1148 |
) |
|
| 1149 |
) |
|
| 1150 | 4x |
if (!from_prior) {
|
| 1151 | 2x |
ms$ref_dose <- ref_dose |
| 1152 |
} |
|
| 1153 | 4x |
ms |
| 1154 |
}, |
|
| 1155 | 28x |
init = function() {
|
| 1156 | 4x |
list(theta = c(0, 1)) |
| 1157 |
}, |
|
| 1158 | 28x |
datanames = c("nObs", "y", "x"),
|
| 1159 | 28x |
sample = c("alpha0", "alpha1")
|
| 1160 |
) |
|
| 1161 |
} |
|
| 1162 | ||
| 1163 |
## default constructor ---- |
|
| 1164 | ||
| 1165 |
#' @rdname LogisticNormalFixedMixture-class |
|
| 1166 |
#' @note Typically, end-users will not use the `.DefaultLogisticNormalFixedMixture()` |
|
| 1167 |
#' function. |
|
| 1168 |
#' @export |
|
| 1169 |
.DefaultLogisticNormalFixedMixture <- function() {
|
|
| 1170 |
# nolint |
|
| 1171 | 7x |
LogisticNormalFixedMixture( |
| 1172 | 7x |
components = list( |
| 1173 | 7x |
comp1 = ModelParamsNormal( |
| 1174 | 7x |
mean = c(-0.85, 1), |
| 1175 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
| 1176 |
), |
|
| 1177 | 7x |
comp2 = ModelParamsNormal( |
| 1178 | 7x |
mean = c(1, 1.5), |
| 1179 | 7x |
cov = matrix(c(1.2, -0.45, -0.45, 0.6), nrow = 2) |
| 1180 |
) |
|
| 1181 |
), |
|
| 1182 | 7x |
weights = c(0.3, 0.7), |
| 1183 | 7x |
ref_dose = 50 |
| 1184 |
) |
|
| 1185 |
} |
|
| 1186 | ||
| 1187 |
# LogisticLogNormalMixture ---- |
|
| 1188 | ||
| 1189 |
## class ---- |
|
| 1190 | ||
| 1191 |
#' `LogisticLogNormalMixture` |
|
| 1192 |
#' |
|
| 1193 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1194 |
#' |
|
| 1195 |
#' [`LogisticLogNormalMixture`] is the class for standard logistic model with |
|
| 1196 |
#' online mixture of two bivariate log normal priors. |
|
| 1197 |
#' |
|
| 1198 |
#' @details This model can be used when data is arising online from the informative |
|
| 1199 |
#' component of the prior, at the same time with the data of the trial of |
|
| 1200 |
#' main interest. Formally, this is achieved by assuming that the probability |
|
| 1201 |
#' of a DLT at dose \eqn{x} is given by
|
|
| 1202 |
#' \deqn{p(x) = \pi * p1(x) + (1 - \pi) * p2(x)}
|
|
| 1203 |
#' where \eqn{\pi} is the probability for the model \eqn{p(x)} being the same
|
|
| 1204 |
#' as the model \eqn{p1(x)}, which is the informative component of the prior.
|
|
| 1205 |
#' From this model data arises in parallel: at doses `xshare`, DLT information |
|
| 1206 |
#' `yshare` is observed, in total `nObsshare` data points (see [`DataMixture`]). |
|
| 1207 |
#' On the other hand, \eqn{1 - \pi}, is the probability of a separate model
|
|
| 1208 |
#' \eqn{p2(x)}. Both components have the same log normal prior distribution,
|
|
| 1209 |
#' which can be specified by the user, and which is inherited from the |
|
| 1210 |
#' [`LogisticLogNormal`] class. |
|
| 1211 |
#' |
|
| 1212 |
#' @slot share_weight (`proportion`)\cr the prior weight for the share component |
|
| 1213 |
#' \eqn{p_{1}(x)}.
|
|
| 1214 |
#' |
|
| 1215 |
#' @seealso [`ModelLogNormal`], [`LogisticNormalMixture`], |
|
| 1216 |
#' [`LogisticNormalFixedMixture`]. |
|
| 1217 |
#' |
|
| 1218 |
#' @aliases LogisticLogNormalMixture |
|
| 1219 |
#' @export |
|
| 1220 |
#' |
|
| 1221 |
.LogisticLogNormalMixture <- setClass( |
|
| 1222 |
Class = "LogisticLogNormalMixture", |
|
| 1223 |
contains = "LogisticLogNormal", |
|
| 1224 |
slots = c( |
|
| 1225 |
share_weight = "numeric" |
|
| 1226 |
), |
|
| 1227 |
prototype = prototype( |
|
| 1228 |
share_weight = 0.1 |
|
| 1229 |
), |
|
| 1230 |
validity = v_model_logistic_log_normal_mix |
|
| 1231 |
) |
|
| 1232 | ||
| 1233 |
## constructor ---- |
|
| 1234 | ||
| 1235 |
#' @rdname LogisticLogNormalMixture-class |
|
| 1236 |
#' |
|
| 1237 |
#' @inheritParams ModelLogNormal |
|
| 1238 |
#' @param share_weight (`proportion`)\cr the prior weight for the share component. |
|
| 1239 |
#' |
|
| 1240 |
#' @export |
|
| 1241 |
#' @example examples/Model-class-LogisticLogNormalMixture.R |
|
| 1242 |
#' |
|
| 1243 |
LogisticLogNormalMixture <- function(mean, cov, ref_dose, share_weight) {
|
|
| 1244 | 20x |
assert_number(ref_dose) |
| 1245 | ||
| 1246 | 20x |
params <- ModelParamsNormal(mean, cov) |
| 1247 | 20x |
.LogisticLogNormalMixture( |
| 1248 | 20x |
params = params, |
| 1249 | 20x |
ref_dose = positive_number(ref_dose), |
| 1250 | 20x |
share_weight = share_weight, |
| 1251 | 20x |
datamodel = function() {
|
| 1252 | ! |
for (i in 1:nObs) {
|
| 1253 |
# comp gives the component: non-informative (1) or share (2) the two components. |
|
| 1254 | ! |
stand_log_dose[i] <- log(x[i] / ref_dose) |
| 1255 | ! |
logit(p[i]) <- alpha0[comp] + alpha1[comp] * stand_log_dose[i] |
| 1256 | ! |
y[i] ~ dbern(p[i]) |
| 1257 |
} |
|
| 1258 | ! |
for (j in 1:nObsshare) {
|
| 1259 | ! |
stand_log_dose_share[j] <- log(xshare[j] / ref_dose) |
| 1260 | ! |
logit(pshare[j]) <- alpha0[2] + alpha1[2] * stand_log_dose_share[j] |
| 1261 | ! |
yshare[j] ~ dbern(pshare[j]) |
| 1262 |
} |
|
| 1263 |
}, |
|
| 1264 | 20x |
priormodel = function() {
|
| 1265 | ! |
for (k in 1:2) {
|
| 1266 | ! |
theta[k, 1:2] ~ dmnorm(mean, prec) |
| 1267 | ! |
alpha0[k] <- theta[k, 1] |
| 1268 | ! |
alpha1[k] <- exp(theta[k, 2]) |
| 1269 |
} |
|
| 1270 |
# The component indicator. |
|
| 1271 | ! |
comp ~ dcat(cat_probs) |
| 1272 |
}, |
|
| 1273 | 20x |
modelspecs = function(from_prior) {
|
| 1274 | 2x |
ms <- list( |
| 1275 | 2x |
cat_probs = c(1 - share_weight, share_weight), |
| 1276 | 2x |
mean = params@mean, |
| 1277 | 2x |
prec = params@prec |
| 1278 |
) |
|
| 1279 | 2x |
if (!from_prior) {
|
| 1280 | 1x |
ms$ref_dose <- ref_dose |
| 1281 |
} |
|
| 1282 | 2x |
ms |
| 1283 |
}, |
|
| 1284 | 20x |
init = function() {
|
| 1285 | 2x |
list(theta = matrix(c(0, 0, 1, 1), nrow = 2)) |
| 1286 |
}, |
|
| 1287 | 20x |
datanames = c("nObs", "y", "x", "nObsshare", "yshare", "xshare"),
|
| 1288 | 20x |
sample = c("alpha0", "alpha1", "comp")
|
| 1289 |
) |
|
| 1290 |
} |
|
| 1291 | ||
| 1292 |
## default constructor ---- |
|
| 1293 | ||
| 1294 |
#' @rdname LogisticLogNormalMixture-class |
|
| 1295 |
#' @note Typically, end users will not use the `.DefaultLogNormalMixture()` function. |
|
| 1296 |
#' @export |
|
| 1297 |
.DefaultLogisticLogNormalMixture <- function() {
|
|
| 1298 |
# nolint |
|
| 1299 | 7x |
LogisticLogNormalMixture( |
| 1300 | 7x |
share_weight = 0.1, |
| 1301 | 7x |
mean = c(-0.85, 1), |
| 1302 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 1303 | 7x |
ref_dose = 50 |
| 1304 |
) |
|
| 1305 |
} |
|
| 1306 | ||
| 1307 |
# DualEndpoint ---- |
|
| 1308 | ||
| 1309 |
## class ---- |
|
| 1310 | ||
| 1311 |
#' `DualEndpoint` |
|
| 1312 |
#' |
|
| 1313 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1314 |
#' |
|
| 1315 |
#' [`DualEndpoint`] is the general class for the dual endpoint model. |
|
| 1316 |
#' |
|
| 1317 |
#' @details The idea of the dual-endpoint models is to model not only the |
|
| 1318 |
#' dose-toxicity relationship, but also to model, at the same time, the |
|
| 1319 |
#' relationship of a PD biomarker with the dose. The sub-classes of this class |
|
| 1320 |
#' define how the dose-biomarker relationship is parametrized. This class here |
|
| 1321 |
#' shall contain all the common features to reduce duplicate code. |
|
| 1322 |
#' (This class however, must not be virtual as we need to create objects |
|
| 1323 |
#' of it during the construction of subclass objects.) |
|
| 1324 |
#' |
|
| 1325 |
#' The dose-toxicity relationship is modeled with probit regression model |
|
| 1326 |
#' \deqn{probit[p(x)] = betaZ1 + betaZ2 * x/x*,}
|
|
| 1327 |
#' or |
|
| 1328 |
#' \deqn{probit[p(x)] = betaZ1 + betaZ2 * log(x/x*),}
|
|
| 1329 |
#' in case when the option `use_log_dose` is `TRUE`. |
|
| 1330 |
#' Here, \eqn{p(x)} is the probability of observing a DLT for a given
|
|
| 1331 |
#' dose \eqn{x} and \eqn{x*} is the reference dose.
|
|
| 1332 |
#' The prior \deqn{(betaZ1, log(betaZ2)) ~ Normal(mean, cov).}
|
|
| 1333 |
#' |
|
| 1334 |
#' For the biomarker response \eqn{w} at a dose \eqn{x}, we assume
|
|
| 1335 |
#' \deqn{w(x) ~ Normal(f(x), sigma2W),}
|
|
| 1336 |
#' where \eqn{f(x)} is a function of the dose \eqn{x}, which is further
|
|
| 1337 |
#' specified in sub-classes. The biomarker variance \eqn{sigma2W} can be fixed
|
|
| 1338 |
#' or assigned an Inverse-Gamma prior distribution; see the details below under |
|
| 1339 |
#' slot `sigma2W`. |
|
| 1340 |
#' |
|
| 1341 |
#' Finally, the two endpoints \eqn{y} (the binary DLT variable) and \eqn{w}
|
|
| 1342 |
#' (the biomarker) can be correlated, by assuming a correlation of level |
|
| 1343 |
#' \eqn{rho} between the underlying continuous latent toxicity variable \eqn{z}
|
|
| 1344 |
#' and the biomarker \eqn{w}. Again, this correlation can be fixed or assigned
|
|
| 1345 |
#' a prior distribution from the scaled Beta family; see the details below |
|
| 1346 |
#' under slot `rho`. |
|
| 1347 |
#' |
|
| 1348 |
#' Please see the example vignette by typing `crmPackExample()` for a full example. |
|
| 1349 |
#' |
|
| 1350 |
#' @slot betaZ_params (`ModelParamsNormal`)\cr for the probit toxicity model, it |
|
| 1351 |
#' contains the prior mean, covariance matrix and precision matrix which is |
|
| 1352 |
#' internally calculated as an inverse of the covariance matrix. |
|
| 1353 |
#' @slot ref_dose (`positive_number`)\cr for the probit toxicity model, the |
|
| 1354 |
#' reference dose. |
|
| 1355 |
#' @slot use_log_dose (`flag`)\cr for the probit toxicity model, whether a log |
|
| 1356 |
#' transformation of the (standardized) dose should be used? |
|
| 1357 |
#' @slot sigma2W (`numeric`)\cr the biomarker variance. Either a fixed value or |
|
| 1358 |
#' Inverse-Gamma distribution parameters, i.e. vector with two elements named |
|
| 1359 |
#' `a` and `b`. |
|
| 1360 |
#' @slot rho (`numeric`)\cr either a fixed value for the correlation |
|
| 1361 |
#' (between `-1` and `1`), or a named vector with two elements named `a` and `b` |
|
| 1362 |
#' for the Beta prior on the transformation `kappa = (rho + 1) / 2`, which is |
|
| 1363 |
#' in `(0, 1)`. For example, `a = 1, b = 1` leads to a uniform prior on `rho`. |
|
| 1364 |
#' @slot use_fixed (`logical`)\cr indicates whether a fixed value for `sigma2W` |
|
| 1365 |
#' or `rho` (for each parameter separately) is used or not. This slot is |
|
| 1366 |
#' needed for internal purposes and must not be touched by the user. |
|
| 1367 |
#' |
|
| 1368 |
#' @seealso [`DualEndpointRW`], [`DualEndpointBeta`], [`DualEndpointEmax`]. |
|
| 1369 |
#' |
|
| 1370 |
#' @aliases DualEndpoint |
|
| 1371 |
#' @export |
|
| 1372 |
#' |
|
| 1373 |
.DualEndpoint <- setClass( |
|
| 1374 |
Class = "DualEndpoint", |
|
| 1375 |
slots = c( |
|
| 1376 |
betaZ_params = "ModelParamsNormal", |
|
| 1377 |
ref_dose = "positive_number", |
|
| 1378 |
use_log_dose = "logical", |
|
| 1379 |
sigma2W = "numeric", |
|
| 1380 |
rho = "numeric", |
|
| 1381 |
use_fixed = "logical" |
|
| 1382 |
), |
|
| 1383 |
prototype = prototype( |
|
| 1384 |
betaZ_params = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
| 1385 |
ref_dose = positive_number(1), |
|
| 1386 |
use_log_dose = FALSE, |
|
| 1387 |
sigma2W = 1, |
|
| 1388 |
rho = 0, |
|
| 1389 |
use_fixed = c(sigma2W = TRUE, rho = TRUE) |
|
| 1390 |
), |
|
| 1391 |
contains = "GeneralModel", |
|
| 1392 |
validity = v_model_dual_endpoint |
|
| 1393 |
) |
|
| 1394 | ||
| 1395 |
## constructor ---- |
|
| 1396 | ||
| 1397 |
#' @rdname DualEndpoint-class |
|
| 1398 |
#' |
|
| 1399 |
#' @param mean (`numeric`)\cr for the probit toxicity model, the prior mean vector. |
|
| 1400 |
#' @param cov (`matrix`)\cr for the probit toxicity model, the prior covariance |
|
| 1401 |
#' matrix. The precision matrix is internally calculated as an inverse of `cov`. |
|
| 1402 |
#' @param ref_dose (`number`)\cr for the probit toxicity model, the reference |
|
| 1403 |
#' dose \eqn{x*} (strictly positive number).
|
|
| 1404 |
#' @param use_log_dose (`flag`)\cr for the probit toxicity model, whether a log |
|
| 1405 |
#' transformation of the (standardized) dose should be used? |
|
| 1406 |
#' @param sigma2W (`numeric`)\cr the biomarker variance. Either a fixed value or |
|
| 1407 |
#' Inverse-Gamma distribution parameters, i.e. vector with two elements named |
|
| 1408 |
#' `a` and `b`. |
|
| 1409 |
#' @param rho (`numeric`)\cr either a fixed value for the correlation |
|
| 1410 |
#' (between `-1` and `1`), or a named vector with two elements named `a` and `b` |
|
| 1411 |
#' for the Beta prior on the transformation `kappa = (rho + 1) / 2`, which is |
|
| 1412 |
#' in `(0, 1)`. For example, `a = 1, b = 1` leads to a uniform prior on `rho`. |
|
| 1413 |
#' |
|
| 1414 |
#' @export |
|
| 1415 |
#' |
|
| 1416 |
DualEndpoint <- function( |
|
| 1417 |
mean, |
|
| 1418 |
cov, |
|
| 1419 |
ref_dose = 1, |
|
| 1420 |
use_log_dose = FALSE, |
|
| 1421 |
sigma2W, |
|
| 1422 |
rho |
|
| 1423 |
) {
|
|
| 1424 | 206x |
assert_number(ref_dose) |
| 1425 | 206x |
assert_numeric(sigma2W, min.len = 1, max.len = 2) |
| 1426 | 206x |
assert_numeric(rho, min.len = 1, max.len = 2) |
| 1427 | ||
| 1428 | 206x |
use_fixed <- c( |
| 1429 | 206x |
sigma2W = test_number(sigma2W), |
| 1430 | 206x |
rho = test_number(rho) |
| 1431 |
) |
|
| 1432 | 206x |
beta_z_params <- ModelParamsNormal(mean, cov) |
| 1433 | ||
| 1434 | 206x |
datamodel <- function() {
|
| 1435 | ! |
for (i in 1:nObs) {
|
| 1436 |
# The toxicity model. |
|
| 1437 | ! |
stand_dose_temp[i] <- x[i] / ref_dose |
| 1438 | ! |
stand_dose[i] <- ifelse( |
| 1439 | ! |
use_log_dose, |
| 1440 | ! |
log(stand_dose_temp[i]), |
| 1441 | ! |
stand_dose_temp[i] |
| 1442 |
) |
|
| 1443 | ! |
meanZ[i] <- betaZ[1] + betaZ[2] * stand_dose[i] |
| 1444 | ! |
z[i] ~ dnorm(meanZ[i], 1) |
| 1445 | ! |
y[i] ~ dinterval(z[i], 0) |
| 1446 | ||
| 1447 |
# The conditional biomarker model; betaW defined in subclasses! |
|
| 1448 | ! |
condMeanW[i] <- betaW[xLevel[i]] + rho / sqrt(precW) * (z[i] - meanZ[i]) |
| 1449 | ! |
w[i] ~ dnorm(condMeanW[i], condPrecW) |
| 1450 |
} |
|
| 1451 |
} |
|
| 1452 | 206x |
priormodel <- function() {
|
| 1453 |
# Priors for betaW defined in subclasses! |
|
| 1454 | ! |
theta ~ dmnorm(betaZ_mean, betaZ_prec) |
| 1455 | ! |
betaZ[1] <- theta[1] |
| 1456 | ! |
betaZ[2] <- exp(theta[2]) |
| 1457 |
# Conditional precision for biomarker. |
|
| 1458 |
# Code for `precW` and `rho` will be added by |
|
| 1459 |
# `h_model_dual_endpoint_sigma2W()`, `h_model_dual_endpoint_rho()` helpers, below. |
|
| 1460 | ! |
condPrecW <- precW / (1 - pow(rho, 2)) |
| 1461 |
} |
|
| 1462 | 206x |
modelspecs_prior <- list( |
| 1463 | 206x |
betaZ_mean = beta_z_params@mean, |
| 1464 | 206x |
betaZ_prec = beta_z_params@prec |
| 1465 |
) |
|
| 1466 | ||
| 1467 | 206x |
comp <- list( |
| 1468 | 206x |
priormodel = priormodel, |
| 1469 | 206x |
modelspecs = modelspecs_prior, |
| 1470 | 206x |
init = NULL, |
| 1471 | 206x |
sample = "betaZ" |
| 1472 |
) |
|
| 1473 | ||
| 1474 |
# Update model components with regard to biomarker regression variance. |
|
| 1475 | 206x |
comp <- h_model_dual_endpoint_sigma2W( |
| 1476 | 206x |
use_fixed["sigma2W"], |
| 1477 | 206x |
sigma2W = sigma2W, |
| 1478 | 206x |
comp = comp |
| 1479 |
) |
|
| 1480 | ||
| 1481 |
# Update model components with regard to DLT and biomarker correlation. |
|
| 1482 | 206x |
comp <- h_model_dual_endpoint_rho( |
| 1483 | 206x |
use_fixed["rho"], |
| 1484 | 206x |
rho = rho, |
| 1485 | 206x |
comp = comp |
| 1486 |
) |
|
| 1487 | ||
| 1488 | 206x |
.DualEndpoint( |
| 1489 | 206x |
betaZ_params = beta_z_params, |
| 1490 | 206x |
ref_dose = positive_number(ref_dose), |
| 1491 | 206x |
use_log_dose = use_log_dose, |
| 1492 | 206x |
sigma2W = sigma2W, |
| 1493 | 206x |
rho = rho, |
| 1494 | 206x |
use_fixed = use_fixed, |
| 1495 | 206x |
datamodel = datamodel, |
| 1496 | 206x |
priormodel = comp$priormodel, |
| 1497 | 206x |
modelspecs = function(from_prior) {
|
| 1498 | 73x |
if (!from_prior) {
|
| 1499 | 48x |
comp$modelspecs$ref_dose <- ref_dose |
| 1500 | 48x |
comp$modelspecs$use_log_dose <- use_log_dose |
| 1501 |
} |
|
| 1502 | 73x |
comp$modelspecs |
| 1503 |
}, |
|
| 1504 | 206x |
init = function(y) {
|
| 1505 | 63x |
c(comp$init, list(z = ifelse(y == 0, -1, 1), theta = c(0, 1))) |
| 1506 |
}, |
|
| 1507 | 206x |
datanames = c("nObs", "w", "x", "xLevel", "y"),
|
| 1508 | 206x |
sample = comp$sample |
| 1509 |
) |
|
| 1510 |
} |
|
| 1511 | ||
| 1512 |
## default constructor ---- |
|
| 1513 | ||
| 1514 |
#' @rdname DualEndpoint-class |
|
| 1515 |
#' @note Typically, end users will not use the `.DefaultDualEndpoint()` function. |
|
| 1516 |
#' @export |
|
| 1517 |
.DefaultDualEndpoint <- function() {
|
|
| 1518 | 4x |
stop(paste0( |
| 1519 | 4x |
"Class DualEndpoint cannot be instantiated directly. Please use one of its subclasses instead." |
| 1520 |
)) |
|
| 1521 |
} |
|
| 1522 | ||
| 1523 |
# DualEndpointRW ---- |
|
| 1524 | ||
| 1525 |
## class ---- |
|
| 1526 | ||
| 1527 |
#' `DualEndpointRW` |
|
| 1528 |
#' |
|
| 1529 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1530 |
#' |
|
| 1531 |
#' [`DualEndpointRW`] is the class for the dual endpoint model with random walk |
|
| 1532 |
#' prior for biomarker. |
|
| 1533 |
#' |
|
| 1534 |
#' |
|
| 1535 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
| 1536 |
#' relationship \eqn{f(x)} is modelled by a non-parametric random walk of first
|
|
| 1537 |
#' or second order. That means, for the first order random walk we assume |
|
| 1538 |
#' \deqn{betaW_i - betaW_i-1 ~ Normal(0, (x_i - x_i-1) * sigma2betaW),}
|
|
| 1539 |
#' where \eqn{betaW_i = f(x_i)} is the biomarker mean at the \eqn{i}-th dose
|
|
| 1540 |
#' gridpoint \eqn{x_i}.
|
|
| 1541 |
#' For the second order random walk, the second-order differences instead of |
|
| 1542 |
#' the first-order differences of the biomarker means follow the normal distribution |
|
| 1543 |
#' with \eqn{0} mean and \eqn{2 * (x_i - x_i-2) * sigma2betaW} variance.
|
|
| 1544 |
#' |
|
| 1545 |
#' The variance parameter \eqn{sigma2betaW} is important because it steers the
|
|
| 1546 |
#' smoothness of the function \eqn{f(x)}, i.e.: if it is large, then \eqn{f(x)}
|
|
| 1547 |
#' will be very wiggly; if it is small, then \eqn{f(x)} will be smooth.
|
|
| 1548 |
#' This parameter can either be a fixed value or assigned an inverse gamma prior |
|
| 1549 |
#' distribution. |
|
| 1550 |
#' |
|
| 1551 |
#' @note Non-equidistant dose grids can be used now, because the difference |
|
| 1552 |
#' \eqn{x_i - x_i-1} is included in the modelling assumption above.
|
|
| 1553 |
#' Please note that due to impropriety of the random walk prior distributions, |
|
| 1554 |
#' it is not possible to produce MCMC samples with empty data objects (i.e., |
|
| 1555 |
#' sample from the prior). This is not a bug, but a theoretical feature of this |
|
| 1556 |
#' model. |
|
| 1557 |
#' |
|
| 1558 |
#' @slot sigma2betaW (`numeric`)\cr the prior variance factor of the random walk |
|
| 1559 |
#' prior for the biomarker model. Either a fixed value or Inverse-Gamma distribution |
|
| 1560 |
#' parameters, i.e. vector with two elements named `a` and `b`. |
|
| 1561 |
#' @slot rw1 (`flag`)\cr for specifying the random walk prior on the biomarker |
|
| 1562 |
#' level. When `TRUE`, random walk of first order is used. Otherwise, the |
|
| 1563 |
#' random walk of second order is used. |
|
| 1564 |
#' |
|
| 1565 |
#' @seealso [`DualEndpoint`], [`DualEndpointBeta`], [`DualEndpointEmax`]. |
|
| 1566 |
#' |
|
| 1567 |
#' @aliases DualEndpointRW |
|
| 1568 |
#' @export |
|
| 1569 |
#' |
|
| 1570 |
.DualEndpointRW <- setClass( |
|
| 1571 |
Class = "DualEndpointRW", |
|
| 1572 |
slots = c( |
|
| 1573 |
sigma2betaW = "numeric", |
|
| 1574 |
rw1 = "logical" |
|
| 1575 |
), |
|
| 1576 |
prototype = prototype( |
|
| 1577 |
sigma2betaW = 1, |
|
| 1578 |
rw1 = TRUE, |
|
| 1579 |
use_fixed = c( |
|
| 1580 |
sigma2W = TRUE, |
|
| 1581 |
rho = TRUE, |
|
| 1582 |
sigma2betaW = TRUE |
|
| 1583 |
) |
|
| 1584 |
), |
|
| 1585 |
contains = "DualEndpoint", |
|
| 1586 |
validity = v_model_dual_endpoint_rw |
|
| 1587 |
) |
|
| 1588 | ||
| 1589 |
## constructor ---- |
|
| 1590 | ||
| 1591 |
#' @rdname DualEndpointRW-class |
|
| 1592 |
#' |
|
| 1593 |
#' @param sigma2betaW (`numeric`)\cr the prior variance factor of the random walk |
|
| 1594 |
#' prior for the biomarker model. Either a fixed value or Inverse-Gamma distribution |
|
| 1595 |
#' parameters, i.e. vector with two elements named `a` and `b`. |
|
| 1596 |
#' @param rw1 (`flag`)\cr for specifying the random walk prior on the biomarker |
|
| 1597 |
#' level. When `TRUE`, random walk of first order is used. Otherwise, the |
|
| 1598 |
#' random walk of second order is used. |
|
| 1599 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
| 1600 |
#' |
|
| 1601 |
#' @export |
|
| 1602 |
#' @example examples/Model-class-DualEndpointRW.R |
|
| 1603 |
#' |
|
| 1604 |
DualEndpointRW <- function(sigma2betaW, rw1 = TRUE, ...) {
|
|
| 1605 | 57x |
assert_numeric(sigma2betaW, min.len = 1, max.len = 2) |
| 1606 | 57x |
assert_flag(rw1) |
| 1607 | ||
| 1608 | 57x |
start <- DualEndpoint(...) |
| 1609 | 57x |
start@use_fixed["sigma2betaW"] <- length(sigma2betaW) == 1L |
| 1610 | ||
| 1611 | 57x |
priormodel <- if (rw1) {
|
| 1612 | 48x |
function() {
|
| 1613 |
# The 1st order differences. |
|
| 1614 |
# Essentially dflat(), which is not available in JAGS. |
|
| 1615 | ! |
betaW[1] ~ dnorm(0, 0.000001) |
| 1616 | ! |
for (i in 2:nGrid) {
|
| 1617 | ! |
delta[i - 1] ~ dnorm(0, precBetaW / (doseGrid[i] - doseGrid[i - 1])) |
| 1618 | ! |
betaW[i] <- betaW[i - 1] + delta[i - 1] |
| 1619 |
} |
|
| 1620 |
} |
|
| 1621 |
} else {
|
|
| 1622 | 9x |
function() {
|
| 1623 |
# The 2nd order differences. |
|
| 1624 | ! |
delta[1] ~ dnorm(0, 0.000001) |
| 1625 | ! |
betaW[1] ~ dnorm(0, 0.000001) |
| 1626 | ! |
betaW[2] <- betaW[1] + delta[1] |
| 1627 | ! |
for (i in 3:nGrid) {
|
| 1628 |
# delta2: differences of the differences of betaW follow normal dist. |
|
| 1629 | ! |
delta2[i - 2] ~ |
| 1630 | ! |
dnorm(0, 2 * precBetaW / (doseGrid[i] - doseGrid[i - 2])) |
| 1631 | ! |
delta[i - 1] <- delta[i - 2] + delta2[i - 2] |
| 1632 | ! |
betaW[i] <- betaW[i - 1] + delta[i - 1] |
| 1633 |
} |
|
| 1634 |
} |
|
| 1635 |
} |
|
| 1636 | 57x |
start@priormodel <- h_jags_join_models(start@priormodel, priormodel) |
| 1637 | 57x |
start@datanames_prior <- c("nGrid", "doseGrid")
|
| 1638 | 57x |
start@sample <- c(start@sample, "betaW", "delta") |
| 1639 | ||
| 1640 |
# Update model components with regard to biomarker regression variance. |
|
| 1641 | 57x |
start <- h_model_dual_endpoint_sigma2betaW( |
| 1642 | 57x |
start@use_fixed["sigma2betaW"], |
| 1643 | 57x |
sigma2betaW = sigma2betaW, |
| 1644 | 57x |
de = start |
| 1645 |
) |
|
| 1646 | ||
| 1647 | 57x |
.DualEndpointRW( |
| 1648 | 57x |
start, |
| 1649 | 57x |
sigma2betaW = sigma2betaW, |
| 1650 | 57x |
rw1 = rw1 |
| 1651 |
) |
|
| 1652 |
} |
|
| 1653 | ||
| 1654 |
## default constructor ---- |
|
| 1655 | ||
| 1656 |
#' @rdname DualEndpointRW-class |
|
| 1657 |
#' @note Typically, end users will not use the `.DefaultDualEndpointRW()` function. |
|
| 1658 |
#' @export |
|
| 1659 |
.DefaultDualEndpointRW <- function() {
|
|
| 1660 | 7x |
DualEndpointRW( |
| 1661 | 7x |
mean = c(0, 1), |
| 1662 | 7x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 1663 | 7x |
sigma2W = c(a = 0.1, b = 0.1), |
| 1664 | 7x |
rho = c(a = 1, b = 1), |
| 1665 | 7x |
sigma2betaW = 0.01, |
| 1666 | 7x |
rw1 = TRUE |
| 1667 |
) |
|
| 1668 |
} |
|
| 1669 | ||
| 1670 |
# DualEndpointBeta ---- |
|
| 1671 | ||
| 1672 |
## class ---- |
|
| 1673 | ||
| 1674 |
#' `DualEndpointBeta` |
|
| 1675 |
#' |
|
| 1676 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1677 |
#' |
|
| 1678 |
#' [`DualEndpointBeta`] is the class for the dual endpoint model with beta |
|
| 1679 |
#' function for dose-biomarker relationship. |
|
| 1680 |
#' |
|
| 1681 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
| 1682 |
#' relationship \eqn{f(x)} is modelled by a parametric, rescaled beta density
|
|
| 1683 |
#' function: |
|
| 1684 |
#' \deqn{f(x) = E0 + (Emax - E0) * Beta(delta1, delta2) * (x/x*)^{delta1} * (1 - x/x*)^{delta2},}
|
|
| 1685 |
#' where \eqn{x*} is the maximum dose (end of the dose range to be considered),
|
|
| 1686 |
#' \eqn{delta1} and \eqn{delta2} are the two beta function parameters, and
|
|
| 1687 |
#' \eqn{E0}, \eqn{Emax} are the minimum and maximum levels, respectively.
|
|
| 1688 |
#' For ease of interpretation, we use the parametrization based on \eqn{delta1}
|
|
| 1689 |
#' and the mode, where |
|
| 1690 |
#' \deqn{mode = delta1 / (delta1 + delta2),}
|
|
| 1691 |
#' so that multiplying this by \eqn{x*} gives the mode on the dose grid.
|
|
| 1692 |
#' |
|
| 1693 |
#' All parameters can currently be assigned uniform distributions or be fixed |
|
| 1694 |
#' in advance. Note that \code{E0} and \code{Emax} can have negative values or
|
|
| 1695 |
#' uniform distributions reaching into negative range, while \code{delta1} and
|
|
| 1696 |
#' \code{mode} must be positive or have uniform distributions in the positive
|
|
| 1697 |
#' range. |
|
| 1698 |
#' |
|
| 1699 |
#' @slot E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1700 |
#' parameters. |
|
| 1701 |
#' @slot Emax (`numeric`)\cr either a fixed number or the two uniform |
|
| 1702 |
#' distribution parameters. |
|
| 1703 |
#' @slot delta1 (`numeric`)\cr either a fixed positive number or the two |
|
| 1704 |
#' parameters of the uniform distribution, that can take only positive values. |
|
| 1705 |
#' @slot mode (`numeric`)\cr either a fixed positive number or the two |
|
| 1706 |
#' parameters of the uniform distribution, that can take only positive values. |
|
| 1707 |
#' @slot ref_dose_beta (`positive_number`)\cr the reference dose \eqn{x*} (note
|
|
| 1708 |
#' that this is different from the `ref_dose` in the inherited [`DualEndpoint`] |
|
| 1709 |
#' model). |
|
| 1710 |
#' |
|
| 1711 |
#' @seealso [`DualEndpoint`], [`DualEndpointRW`], [`DualEndpointEmax`]. |
|
| 1712 |
#' |
|
| 1713 |
#' @aliases DualEndpointBeta |
|
| 1714 |
#' @export |
|
| 1715 |
#' |
|
| 1716 |
.DualEndpointBeta <- setClass( |
|
| 1717 |
Class = "DualEndpointBeta", |
|
| 1718 |
slots = c( |
|
| 1719 |
E0 = "numeric", |
|
| 1720 |
Emax = "numeric", |
|
| 1721 |
delta1 = "numeric", |
|
| 1722 |
mode = "numeric", |
|
| 1723 |
ref_dose_beta = "positive_number" |
|
| 1724 |
), |
|
| 1725 |
prototype = prototype( |
|
| 1726 |
E0 = c(0, 100), |
|
| 1727 |
Emax = c(0, 500), |
|
| 1728 |
delta1 = c(0, 5), |
|
| 1729 |
mode = c(1, 15), |
|
| 1730 |
ref_dose_beta = positive_number(1), |
|
| 1731 |
use_fixed = c( |
|
| 1732 |
sigma2W = TRUE, |
|
| 1733 |
rho = TRUE, |
|
| 1734 |
E0 = FALSE, |
|
| 1735 |
Emax = FALSE, |
|
| 1736 |
delta1 = FALSE, |
|
| 1737 |
mode = FALSE |
|
| 1738 |
) |
|
| 1739 |
), |
|
| 1740 |
contains = "DualEndpoint", |
|
| 1741 |
validity = v_model_dual_endpoint_beta |
|
| 1742 |
) |
|
| 1743 | ||
| 1744 |
## constructor ---- |
|
| 1745 | ||
| 1746 |
#' @rdname DualEndpointBeta-class |
|
| 1747 |
#' |
|
| 1748 |
#' @param E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1749 |
#' parameters. |
|
| 1750 |
#' @param Emax (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1751 |
#' parameters. |
|
| 1752 |
#' @param delta1 (`numeric`)\cr either a fixed positive number or the two parameters |
|
| 1753 |
#' of the uniform distribution, that can take only positive values. |
|
| 1754 |
#' @param mode (`numeric`)\cr either a fixed positive number or the two parameters |
|
| 1755 |
#' of the uniform distribution, that can take only positive values. |
|
| 1756 |
#' @param ref_dose_beta (`number`)\cr the reference dose \eqn{x*} (strictly
|
|
| 1757 |
#' positive number). Note that this is different from the `ref_dose` in the |
|
| 1758 |
#' inherited [`DualEndpoint`] model). |
|
| 1759 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
| 1760 |
#' |
|
| 1761 |
#' @export |
|
| 1762 |
#' @example examples/Model-class-DualEndpointBeta.R |
|
| 1763 |
#' |
|
| 1764 |
DualEndpointBeta <- function(E0, Emax, delta1, mode, ref_dose_beta = 1, ...) {
|
|
| 1765 | 26x |
assert_numeric(E0, min.len = 1, max.len = 2) |
| 1766 | 26x |
assert_numeric(Emax, min.len = 1, max.len = 2) |
| 1767 | 26x |
assert_numeric(delta1, min.len = 1, max.len = 2) |
| 1768 | 26x |
assert_numeric(mode, min.len = 1, max.len = 2) |
| 1769 | 26x |
assert_number(ref_dose_beta) |
| 1770 | ||
| 1771 | 26x |
start <- DualEndpoint(...) |
| 1772 | ||
| 1773 | 26x |
ms <- start@modelspecs |
| 1774 | 26x |
start@modelspecs <- function(from_prior) {
|
| 1775 | 8x |
c(list(ref_dose_beta = ref_dose_beta), ms(from_prior)) |
| 1776 |
} |
|
| 1777 | 26x |
start@datanames_prior <- c("nGrid", "doseGrid")
|
| 1778 | 26x |
start@sample <- c(start@sample, "betaW") |
| 1779 | ||
| 1780 | 26x |
start <- h_model_dual_endpoint_beta( |
| 1781 | 26x |
param = E0, |
| 1782 | 26x |
param_name = "E0", |
| 1783 | 26x |
priormodel = function() {
|
| 1784 | ! |
E0 ~ dunif(E0_low, E0_high) |
| 1785 |
}, |
|
| 1786 | 26x |
de = start |
| 1787 |
) |
|
| 1788 | ||
| 1789 | 26x |
start <- h_model_dual_endpoint_beta( |
| 1790 | 26x |
param = Emax, |
| 1791 | 26x |
param_name = "Emax", |
| 1792 | 26x |
priormodel = function() {
|
| 1793 | ! |
Emax ~ dunif(Emax_low, Emax_high) |
| 1794 |
}, |
|
| 1795 | 26x |
de = start |
| 1796 |
) |
|
| 1797 | ||
| 1798 | 26x |
start <- h_model_dual_endpoint_beta( |
| 1799 | 26x |
param = delta1, |
| 1800 | 26x |
param_name = "delta1", |
| 1801 | 26x |
priormodel = function() {
|
| 1802 | ! |
delta1 ~ dunif(delta1_low, delta1_high) |
| 1803 |
}, |
|
| 1804 | 26x |
de = start |
| 1805 |
) |
|
| 1806 | ||
| 1807 | 26x |
start <- h_model_dual_endpoint_beta( |
| 1808 | 26x |
param = mode, |
| 1809 | 26x |
param_name = "mode", |
| 1810 | 26x |
priormodel = function() {
|
| 1811 | ! |
mode ~ dunif(mode_low, mode_high) |
| 1812 |
}, |
|
| 1813 | 26x |
de = start |
| 1814 |
) |
|
| 1815 | ||
| 1816 | 26x |
start@priormodel <- h_jags_join_models( |
| 1817 | 26x |
start@priormodel, |
| 1818 | 26x |
function() {
|
| 1819 |
# delta2 <- delta1 * (1 - (mode/ref_dose_beta)) / (mode/ref_dose_beta) # nolint |
|
| 1820 | ! |
delta2 <- delta1 * (ref_dose_beta / mode - 1) |
| 1821 |
# betafun <- (delta1 + delta2)^(delta1 + delta2) * delta1^(- delta1) * delta2^(- delta2) # nolint |
|
| 1822 | ! |
betafun <- (1 + delta2 / delta1)^delta1 * (delta1 / delta2 + 1)^delta2 |
| 1823 | ! |
for (i in 1:nGrid) {
|
| 1824 | ! |
stand_dose_beta[i] <- doseGrid[i] / ref_dose_beta |
| 1825 | ! |
betaW[i] <- E0 + |
| 1826 | ! |
(Emax - E0) * |
| 1827 | ! |
betafun * |
| 1828 | ! |
stand_dose_beta[i]^delta1 * |
| 1829 | ! |
(1 - stand_dose_beta[i])^delta2 |
| 1830 |
} |
|
| 1831 |
} |
|
| 1832 |
) |
|
| 1833 | ||
| 1834 | 26x |
.DualEndpointBeta( |
| 1835 | 26x |
start, |
| 1836 | 26x |
E0 = E0, |
| 1837 | 26x |
Emax = Emax, |
| 1838 | 26x |
delta1 = delta1, |
| 1839 | 26x |
mode = mode, |
| 1840 | 26x |
ref_dose_beta = positive_number(ref_dose_beta) |
| 1841 |
) |
|
| 1842 |
} |
|
| 1843 | ||
| 1844 |
## default constructor ---- |
|
| 1845 | ||
| 1846 |
#' @rdname DualEndpointBeta-class |
|
| 1847 |
#' @note Typically, end users will not use the `.DefaultDualEndpointBeta()` function. |
|
| 1848 |
#' @export |
|
| 1849 |
.DefaultDualEndpointBeta <- function() {
|
|
| 1850 | 7x |
DualEndpointBeta( |
| 1851 | 7x |
mean = c(0, 1), |
| 1852 | 7x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 1853 | 7x |
ref_dose = 10, |
| 1854 | 7x |
use_log_dose = TRUE, |
| 1855 | 7x |
sigma2W = c(a = 0.1, b = 0.1), |
| 1856 | 7x |
rho = c(a = 1, b = 1), |
| 1857 | 7x |
E0 = c(0, 100), |
| 1858 | 7x |
Emax = c(0, 500), |
| 1859 | 7x |
delta1 = c(0, 5), |
| 1860 | 7x |
mode = c(1, 15), |
| 1861 | 7x |
ref_dose_beta = 1000 |
| 1862 |
) |
|
| 1863 |
} |
|
| 1864 | ||
| 1865 |
# DualEndpointEmax ---- |
|
| 1866 | ||
| 1867 |
## class ---- |
|
| 1868 | ||
| 1869 |
#' `DualEndpointEmax` |
|
| 1870 |
#' |
|
| 1871 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1872 |
#' |
|
| 1873 |
#' [`DualEndpointEmax`] is the class for the dual endpoint model with `Emax` |
|
| 1874 |
#' function for dose-biomarker relationship. |
|
| 1875 |
#' |
|
| 1876 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
| 1877 |
#' relationship \eqn{f(x)} is modelled by a parametric `Emax` function:
|
|
| 1878 |
#' \deqn{f(x) = E0 + [(Emax - E0) * (x/x*)]/[ED50 + (x/x*)],}
|
|
| 1879 |
#' where \eqn{x*} is a reference dose, \eqn{E0} and \eqn{Emax} are the minimum
|
|
| 1880 |
#' and maximum levels for the biomarker, and \eqn{ED50} is the dose achieving
|
|
| 1881 |
#' half of the maximum effect \eqn{0.5 * Emax}.
|
|
| 1882 |
#' All parameters can currently be assigned uniform distributions or be fixed. |
|
| 1883 |
#' |
|
| 1884 |
#' @slot E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1885 |
#' parameters. |
|
| 1886 |
#' @slot Emax (`numeric`)\cr either a fixed number or the two uniform |
|
| 1887 |
#' distribution parameters. |
|
| 1888 |
#' @slot ED50 (`numeric`)\cr either a fixed number or the two uniform |
|
| 1889 |
#' distribution parameters. |
|
| 1890 |
#' @slot ref_dose_emax (`positive_number`)\cr the reference dose \eqn{x*} (note
|
|
| 1891 |
#' that this is different from the `ref_dose` in the inherited [`DualEndpoint`] |
|
| 1892 |
#' model). |
|
| 1893 |
#' |
|
| 1894 |
#' @seealso [`DualEndpoint`], [`DualEndpointRW`], [`DualEndpointBeta`]. |
|
| 1895 |
#' |
|
| 1896 |
#' @aliases DualEndpointEmax |
|
| 1897 |
#' @export |
|
| 1898 |
#' |
|
| 1899 |
.DualEndpointEmax <- setClass( |
|
| 1900 |
Class = "DualEndpointEmax", |
|
| 1901 |
slots = c( |
|
| 1902 |
E0 = "numeric", |
|
| 1903 |
Emax = "numeric", |
|
| 1904 |
ED50 = "numeric", |
|
| 1905 |
ref_dose_emax = "numeric" |
|
| 1906 |
), |
|
| 1907 |
prototype = prototype( |
|
| 1908 |
E0 = c(0, 100), |
|
| 1909 |
Emax = c(0, 500), |
|
| 1910 |
ED50 = c(0, 500), |
|
| 1911 |
ref_dose_emax = positive_number(1), |
|
| 1912 |
use_fixed = c( |
|
| 1913 |
sigma2W = TRUE, |
|
| 1914 |
rho = TRUE, |
|
| 1915 |
E0 = FALSE, |
|
| 1916 |
Emax = FALSE, |
|
| 1917 |
ED50 = FALSE |
|
| 1918 |
) |
|
| 1919 |
), |
|
| 1920 |
contains = "DualEndpoint", |
|
| 1921 |
validity = v_model_dual_endpoint_emax |
|
| 1922 |
) |
|
| 1923 | ||
| 1924 |
## constructor ---- |
|
| 1925 | ||
| 1926 |
#' @rdname DualEndpointEmax-class |
|
| 1927 |
#' |
|
| 1928 |
#' @param E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1929 |
#' parameters. |
|
| 1930 |
#' @param Emax (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1931 |
#' parameters. |
|
| 1932 |
#' @param ED50 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
| 1933 |
#' parameters. |
|
| 1934 |
#' @param ref_dose_emax (`number`)\cr the reference dose \eqn{x*} (strictly
|
|
| 1935 |
#' positive number). Note that this is different from the `ref_dose` in the |
|
| 1936 |
#' inherited [`DualEndpoint`] model). |
|
| 1937 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
| 1938 |
#' |
|
| 1939 |
#' @export |
|
| 1940 |
#' @example examples/Model-class-DualEndpointEmax.R |
|
| 1941 |
#' |
|
| 1942 |
DualEndpointEmax <- function(E0, Emax, ED50, ref_dose_emax = 1, ...) {
|
|
| 1943 | 23x |
assert_numeric(E0, min.len = 1, max.len = 2) |
| 1944 | 23x |
assert_numeric(Emax, min.len = 1, max.len = 2) |
| 1945 | 23x |
assert_numeric(ED50, min.len = 1, max.len = 2) |
| 1946 | 23x |
assert_number(ref_dose_emax) |
| 1947 | ||
| 1948 | 23x |
start <- DualEndpoint(...) |
| 1949 | ||
| 1950 | 23x |
start@sample <- c(start@sample, "betaW") |
| 1951 | 23x |
start@datanames_prior <- c("nGrid", "doseGrid")
|
| 1952 | 23x |
ms <- start@modelspecs |
| 1953 | 23x |
start@modelspecs <- function(from_prior) {
|
| 1954 | 8x |
c(list(ref_dose_emax = ref_dose_emax), ms(from_prior)) |
| 1955 |
} |
|
| 1956 | ||
| 1957 | 23x |
start <- h_model_dual_endpoint_beta( |
| 1958 | 23x |
param = E0, |
| 1959 | 23x |
param_name = "E0", |
| 1960 | 23x |
priormodel = function() {
|
| 1961 | ! |
E0 ~ dunif(E0_low, E0_high) |
| 1962 |
}, |
|
| 1963 | 23x |
de = start |
| 1964 |
) |
|
| 1965 | ||
| 1966 | 23x |
start <- h_model_dual_endpoint_beta( |
| 1967 | 23x |
param = Emax, |
| 1968 | 23x |
param_name = "Emax", |
| 1969 | 23x |
priormodel = function() {
|
| 1970 | ! |
Emax ~ dunif(Emax_low, Emax_high) |
| 1971 |
}, |
|
| 1972 | 23x |
de = start |
| 1973 |
) |
|
| 1974 | ||
| 1975 | 23x |
start <- h_model_dual_endpoint_beta( |
| 1976 | 23x |
param = ED50, |
| 1977 | 23x |
param_name = "ED50", |
| 1978 | 23x |
priormodel = function() {
|
| 1979 | ! |
ED50 ~ dunif(ED50_low, ED50_high) |
| 1980 |
}, |
|
| 1981 | 23x |
de = start |
| 1982 |
) |
|
| 1983 | ||
| 1984 | 23x |
start@priormodel <- h_jags_join_models( |
| 1985 | 23x |
start@priormodel, |
| 1986 | 23x |
function() {
|
| 1987 | ! |
for (i in 1:nGrid) {
|
| 1988 | ! |
stand_dose_emax[i] <- doseGrid[i] / ref_dose_emax |
| 1989 | ! |
betaW[i] <- E0 + |
| 1990 | ! |
(Emax - E0) * stand_dose_emax[i] / (ED50 + stand_dose_emax[i]) |
| 1991 |
} |
|
| 1992 |
} |
|
| 1993 |
) |
|
| 1994 | ||
| 1995 | 23x |
.DualEndpointEmax( |
| 1996 | 23x |
start, |
| 1997 | 23x |
E0 = E0, |
| 1998 | 23x |
Emax = Emax, |
| 1999 | 23x |
ED50 = ED50, |
| 2000 | 23x |
ref_dose_emax = positive_number(ref_dose_emax) |
| 2001 |
) |
|
| 2002 |
} |
|
| 2003 | ||
| 2004 |
## default constructor ---- |
|
| 2005 | ||
| 2006 |
#' @rdname DualEndpointEmax-class |
|
| 2007 |
#' @note Typically, end users will not use the `.DefaultDualEndpointEmax()` function. |
|
| 2008 |
#' @export |
|
| 2009 |
.DefaultDualEndpointEmax <- function() {
|
|
| 2010 | 7x |
DualEndpointEmax( |
| 2011 | 7x |
mean = c(0, 1), |
| 2012 | 7x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 2013 | 7x |
sigma2W = c(a = 0.1, b = 0.1), |
| 2014 | 7x |
rho = c(a = 1, b = 1), |
| 2015 | 7x |
E0 = c(0, 100), |
| 2016 | 7x |
Emax = c(0, 500), |
| 2017 | 7x |
ED50 = c(10, 200), |
| 2018 | 7x |
ref_dose_emax = 1000 |
| 2019 |
) |
|
| 2020 |
} |
|
| 2021 | ||
| 2022 |
# ModelPseudo ---- |
|
| 2023 | ||
| 2024 |
## class ---- |
|
| 2025 | ||
| 2026 |
#' `ModelPseudo` |
|
| 2027 |
#' |
|
| 2028 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2029 |
#' |
|
| 2030 |
#' [`ModelPseudo`] is the parent class for models that express their prior in |
|
| 2031 |
#' the form of pseudo data (as if there is some data before the trial starts). |
|
| 2032 |
#' |
|
| 2033 |
#' @seealso [`GeneralModel`]. |
|
| 2034 |
#' |
|
| 2035 |
#' @aliases ModelPseudo |
|
| 2036 |
#' @export |
|
| 2037 |
#' |
|
| 2038 |
.ModelPseudo <- setClass( |
|
| 2039 |
Class = "ModelPseudo", |
|
| 2040 |
contains = "CrmPackClass" |
|
| 2041 |
) |
|
| 2042 | ||
| 2043 |
## default constructor ---- |
|
| 2044 | ||
| 2045 |
#' @rdname ModelPseudo-class |
|
| 2046 |
#' @note Typically, end users will not use the `.DefaultModelPseudo()` function. |
|
| 2047 |
#' @export |
|
| 2048 |
.DefaultModelPseudo <- function() {
|
|
| 2049 | 1x |
stop(paste0( |
| 2050 | 1x |
"Class ModelPseudo should not be instantiated directly. Please use one of its subclasses instead." |
| 2051 |
)) |
|
| 2052 |
} |
|
| 2053 | ||
| 2054 |
# ModelTox ---- |
|
| 2055 | ||
| 2056 |
## class ---- |
|
| 2057 | ||
| 2058 |
#' `ModelTox` |
|
| 2059 |
#' |
|
| 2060 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2061 |
#' |
|
| 2062 |
#' [`ModelTox`] is the parent class for DLE (dose-limiting events) models using |
|
| 2063 |
#' pseudo data prior. It is dedicated for DLE models or toxicity models that |
|
| 2064 |
#' have their prior specified in the form of pseudo data (as if there is some |
|
| 2065 |
#' data before the trial starts). |
|
| 2066 |
#' |
|
| 2067 |
#' The `data` must obey the convention of the [`Data`] class. This refers to any |
|
| 2068 |
#' observed DLE responses (`y` in [`Data`]), the dose levels (`x` in [`Data`]) |
|
| 2069 |
#' at which these responses are observed, all dose levels considered in the |
|
| 2070 |
#' study (`doseGrid` in [`Data`]), and finally other specifications in [`Data`] |
|
| 2071 |
#' class that can be used to generate prior or posterior modal estimates or |
|
| 2072 |
#' samples estimates for model parameter(s). |
|
| 2073 |
#' If no responses are observed, at least `doseGrid` has to be specified |
|
| 2074 |
#' in `data` for which prior modal estimates or samples can be obtained for |
|
| 2075 |
#' model parameters based on the specified pseudo data. |
|
| 2076 |
#' |
|
| 2077 |
#' @slot data (`Data`)\cr observed data that is used to obtain model parameters |
|
| 2078 |
#' estimates or samples (see details above). |
|
| 2079 |
#' |
|
| 2080 |
#' @seealso [`ModelEff`]. |
|
| 2081 |
#' |
|
| 2082 |
#' @aliases ModelTox |
|
| 2083 |
#' @export |
|
| 2084 |
#' |
|
| 2085 |
.ModelTox <- setClass( |
|
| 2086 |
Class = "ModelTox", |
|
| 2087 |
slots = c( |
|
| 2088 |
data = "Data" |
|
| 2089 |
), |
|
| 2090 |
contains = "ModelPseudo" |
|
| 2091 |
) |
|
| 2092 | ||
| 2093 |
## default constructor ---- |
|
| 2094 | ||
| 2095 |
#' @rdname ModelTox-class |
|
| 2096 |
#' @note Typically, end users will not use the `.DefaultModelTox()` function. |
|
| 2097 |
#' @export |
|
| 2098 |
.DefaultModelTox <- function() {
|
|
| 2099 | 1x |
stop(paste0( |
| 2100 | 1x |
"Class ModelTox should not be instantiated directly. Please use one of its subclasses instead." |
| 2101 |
)) |
|
| 2102 |
} |
|
| 2103 | ||
| 2104 |
# ModelEff ---- |
|
| 2105 | ||
| 2106 |
## class ---- |
|
| 2107 | ||
| 2108 |
#' `ModelEff` |
|
| 2109 |
#' |
|
| 2110 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2111 |
#' |
|
| 2112 |
#' [`ModelEff`] is the parent class for efficacy models using pseudo data prior. |
|
| 2113 |
#' It is dedicated all efficacy models that have their prior specified in the |
|
| 2114 |
#' form of pseudo data (as if there is some data before the trial starts). |
|
| 2115 |
#' |
|
| 2116 |
#' The `data` must obey the convention of the [`DataDual`] class. This refers to |
|
| 2117 |
#' any observed efficacy/biomarker responses (`w` in [`DataDual`]), the dose |
|
| 2118 |
#' levels at which these responses are observed (`x` in [`DataDual`]), all dose |
|
| 2119 |
#' levels considered in the study (`doseGrid` in [`DataDual`]), and finally |
|
| 2120 |
#' other specifications in [`DataDual`] class that can be used to generate prior |
|
| 2121 |
#' or posterior modal estimates or samples estimates for model parameter(s). |
|
| 2122 |
#' If no responses are observed, at least `doseGrid` has to be specified |
|
| 2123 |
#' in `data` for which prior modal estimates or samples can be obtained for |
|
| 2124 |
#' model parameters based on the specified pseudo data. |
|
| 2125 |
#' |
|
| 2126 |
#' @slot data (`DataDual`)\cr observed data that is used to obtain model |
|
| 2127 |
#' parameters estimates or samples (see details above). |
|
| 2128 |
#' |
|
| 2129 |
#' @seealso [`ModelTox`]. |
|
| 2130 |
#' |
|
| 2131 |
#' @aliases ModelEff |
|
| 2132 |
#' @export |
|
| 2133 |
#' |
|
| 2134 |
.ModelEff <- setClass( |
|
| 2135 |
Class = "ModelEff", |
|
| 2136 |
slots = c( |
|
| 2137 |
data = "DataDual" |
|
| 2138 |
), |
|
| 2139 |
contains = "ModelPseudo" |
|
| 2140 |
) |
|
| 2141 | ||
| 2142 |
## default constructor ---- |
|
| 2143 | ||
| 2144 |
#' @rdname ModelEff-class |
|
| 2145 |
#' @note Typically, end users will not use the `.DefaultModelEff()` function. |
|
| 2146 |
#' @export |
|
| 2147 |
.DefaultModelEff <- function() {
|
|
| 2148 | 1x |
stop(paste0( |
| 2149 | 1x |
"Class ModelEff should not be instantiated directly. Please use one of its subclasses instead." |
| 2150 |
)) |
|
| 2151 |
} |
|
| 2152 | ||
| 2153 |
# LogisticIndepBeta ---- |
|
| 2154 | ||
| 2155 |
## class ---- |
|
| 2156 | ||
| 2157 |
#' `LogisticIndepBeta` |
|
| 2158 |
#' |
|
| 2159 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2160 |
#' |
|
| 2161 |
#' [`LogisticIndepBeta`] is the class for the two-parameters logistic regression |
|
| 2162 |
#' dose-limiting events (DLE) model with prior expressed in form of pseudo data. |
|
| 2163 |
#' This model describes the relationship between the binary DLE responses |
|
| 2164 |
#' and the dose levels. More specifically, it represents the relationship of the |
|
| 2165 |
#' probabilities of the occurrence of a DLE for corresponding dose levels in log |
|
| 2166 |
#' scale. This model is specified as |
|
| 2167 |
#' \deqn{p(x) = exp(phi1 + phi2 * log(x)) / (1 + exp(phi1 + phi2 * log(x)))}
|
|
| 2168 |
#' where \eqn{p(x)} is the probability of the occurrence of a DLE at dose \eqn{x}.
|
|
| 2169 |
#' The two parameters of this model are the intercept \eqn{phi1} and the slope
|
|
| 2170 |
#' \eqn{phi2}. The `LogisticIndepBeta` inherits all slots from [`ModelTox`] class.
|
|
| 2171 |
#' |
|
| 2172 |
#' In the context of pseudo data, the following three arguments are used, |
|
| 2173 |
#' `binDLE`, `DLEdose` and `DLEweights`. The `DLEdose` represents fixed dose |
|
| 2174 |
#' levels at which the pseudo DLE responses `binDLE` are observed. `DLEweights` |
|
| 2175 |
#' represents total number of subjects treated per each dose level in `DLEdose`. |
|
| 2176 |
#' The `binDLE` represents the number of subjects observed with DLE per each |
|
| 2177 |
#' dose level in `DLEdose`. Hence, all these three vectors must be of the same |
|
| 2178 |
#' length and the order of the elements in any of the vectors `binDLE`, |
|
| 2179 |
#' `DLEdose` and `DLEweights` must be kept, so that an element of a given vector |
|
| 2180 |
#' corresponds to the elements of the remaining two vectors (see the example for |
|
| 2181 |
#' more insight). |
|
| 2182 |
#' Finally, since at least two DLE pseudo responses are needed to |
|
| 2183 |
#' obtain prior modal estimates (same as the maximum likelihood estimates) for |
|
| 2184 |
#' the model parameters, the `binDLE`, `DLEdose` and `DLEweights` must all be |
|
| 2185 |
#' vectors of at least length 2. |
|
| 2186 |
#' |
|
| 2187 |
#' @details The pseudo data can be interpreted as if we obtain some observations |
|
| 2188 |
#' before the trial starts. It can be used to express our prior, i.e. the |
|
| 2189 |
#' initial beliefs for the model parameters. The pseudo data is expressed in |
|
| 2190 |
#' the following way. First, fix at least two dose levels, then ask for experts' |
|
| 2191 |
#' opinion on how many subjects are to be treated at each of these dose levels |
|
| 2192 |
#' and on the number of subjects observed with a DLE. At each dose level, the |
|
| 2193 |
#' number of subjects observed with a DLE, divided by the total number of |
|
| 2194 |
#' subjects treated, is the probability of the occurrence of a DLE at that |
|
| 2195 |
#' particular dose level. The probabilities of the occurrence of a DLE based |
|
| 2196 |
#' on this pseudo data are independent and they follow Beta distributions. |
|
| 2197 |
#' Therefore, the joint prior probability density function of all these |
|
| 2198 |
#' probabilities can be obtained. Hence, by a change of variable, the joint |
|
| 2199 |
#' prior probability density function of the two parameters in this model can |
|
| 2200 |
#' also be obtained. In addition, a conjugate joint prior density function of |
|
| 2201 |
#' the two parameters in the model is used. For details about the form of all |
|
| 2202 |
#' these joint prior and posterior probability density functions, please refer |
|
| 2203 |
#' to Whitehead and Willamson (1998). |
|
| 2204 |
#' |
|
| 2205 |
#' @slot binDLE (`numeric`)\cr a vector of total numbers of DLE responses. |
|
| 2206 |
#' It must be at least of length 2 and the order of its elements must |
|
| 2207 |
#' correspond to values specified in `DLEdose` and `DLEweights`. |
|
| 2208 |
#' @slot DLEdose (`numeric`)\cr a vector of the dose levels corresponding to |
|
| 2209 |
#' It must be at least of length 2 and the order of its elements must |
|
| 2210 |
#' correspond to values specified in `binDLE` and `DLEweights`. |
|
| 2211 |
#' @slot DLEweights (`integer`)\cr total number of subjects treated at each of |
|
| 2212 |
#' the pseudo dose level `DLEdose`. |
|
| 2213 |
#' It must be at least of length 2 and the order of its elements must |
|
| 2214 |
#' correspond to values specified in `binDLE` and `DLEdose`. |
|
| 2215 |
#' @slot phi1 (`number`)\cr the intercept of the model. This slot is used in |
|
| 2216 |
#' output to display the resulting prior or posterior modal estimate of the |
|
| 2217 |
#' intercept obtained based on the pseudo data and (if any) observed data/responses. |
|
| 2218 |
#' @slot phi2 (`number`)\cr the slope of the model. This slot is used in output |
|
| 2219 |
#' to display the resulting prior or posterior modal estimate of the slope |
|
| 2220 |
#' obtained based on the pseudo data and (if any) the observed data/responses. |
|
| 2221 |
#' @slot Pcov (`matrix`)\cr refers to the 2x2 covariance matrix of the intercept |
|
| 2222 |
#' (\eqn{phi1}) and the slope parameters (\eqn{phi2}) of the model.
|
|
| 2223 |
#' This is used in output to display the resulting prior and posterior |
|
| 2224 |
#' covariance matrix of \eqn{phi1} and \eqn{phi2} obtained, based on the
|
|
| 2225 |
#' pseudo data and (if any) the observed data and responses. This slot is |
|
| 2226 |
#' needed for internal purposes. |
|
| 2227 |
#' |
|
| 2228 |
#' @aliases LogisticIndepBeta |
|
| 2229 |
#' @export |
|
| 2230 |
#' |
|
| 2231 |
.LogisticIndepBeta <- setClass( |
|
| 2232 |
Class = "LogisticIndepBeta", |
|
| 2233 |
slots = c( |
|
| 2234 |
binDLE = "numeric", |
|
| 2235 |
DLEdose = "numeric", |
|
| 2236 |
DLEweights = "integer", |
|
| 2237 |
phi1 = "numeric", |
|
| 2238 |
phi2 = "numeric", |
|
| 2239 |
Pcov = "matrix" |
|
| 2240 |
), |
|
| 2241 |
prototype = prototype( |
|
| 2242 |
binDLE = c(0, 0), |
|
| 2243 |
DLEdose = c(1, 1), |
|
| 2244 |
DLEweights = c(1L, 1L) |
|
| 2245 |
), |
|
| 2246 |
contains = "ModelTox", |
|
| 2247 |
validity = v_model_logistic_indep_beta |
|
| 2248 |
) |
|
| 2249 | ||
| 2250 |
## constructor ---- |
|
| 2251 | ||
| 2252 |
#' @rdname LogisticIndepBeta-class |
|
| 2253 |
#' |
|
| 2254 |
#' @param binDLE (`numeric`)\cr the number of subjects observed with a DLE, the |
|
| 2255 |
#' pseudo DLE responses, depending on dose levels `DLEdose`. |
|
| 2256 |
#' Elements of `binDLE` must correspond to the elements of `DLEdose` and |
|
| 2257 |
#' `DLEweights`. |
|
| 2258 |
#' @param DLEdose (`numeric`)\cr dose levels for the pseudo DLE responses. |
|
| 2259 |
#' Elements of `DLEdose` must correspond to the elements of `binDLE` and |
|
| 2260 |
#' `DLEweights`. |
|
| 2261 |
#' @param DLEweights (`numeric`)\cr the total number of subjects treated at each |
|
| 2262 |
#' of the dose levels `DLEdose`, pseudo weights. |
|
| 2263 |
#' Elements of `DLEweights` must correspond to the elements of `binDLE` and |
|
| 2264 |
#' `DLEdose`. |
|
| 2265 |
#' @param data (`Data`)\cr the input data to update estimates of the model |
|
| 2266 |
#' parameters. |
|
| 2267 |
#' |
|
| 2268 |
#' @export |
|
| 2269 |
#' @example examples/Model-class-LogisticIndepBeta.R |
|
| 2270 |
#' |
|
| 2271 |
LogisticIndepBeta <- function(binDLE, DLEdose, DLEweights, data) {
|
|
| 2272 | 259x |
assert_numeric(binDLE) |
| 2273 | 259x |
assert_numeric(DLEdose) |
| 2274 | 259x |
assert_integerish(DLEweights, lower = 0, any.missing = FALSE) |
| 2275 | 259x |
assert_class(data, "Data") |
| 2276 | ||
| 2277 |
# Combine pseudo and observed data. It can also happen that data@nObs == 0. |
|
| 2278 | 259x |
y <- c(binDLE, data@y) |
| 2279 | 259x |
x <- c(DLEdose, data@x) |
| 2280 | 259x |
w <- c(DLEweights, rep(1, data@nObs)) |
| 2281 | ||
| 2282 | 259x |
fit_dle <- suppressWarnings( |
| 2283 | 259x |
glm(y / w ~ log(x), family = binomial(link = "logit"), weights = w) |
| 2284 |
) |
|
| 2285 | 259x |
phi1 <- coef(fit_dle)[["(Intercept)"]] |
| 2286 | 259x |
phi2 <- coef(fit_dle)[["log(x)"]] |
| 2287 | 259x |
Pcov <- vcov(fit_dle) |
| 2288 | ||
| 2289 | 259x |
.LogisticIndepBeta( |
| 2290 | 259x |
binDLE = binDLE, |
| 2291 | 259x |
DLEdose = DLEdose, |
| 2292 | 259x |
DLEweights = as.integer(DLEweights), |
| 2293 | 259x |
phi1 = phi1, |
| 2294 | 259x |
phi2 = phi2, |
| 2295 | 259x |
Pcov = Pcov, |
| 2296 | 259x |
data = data |
| 2297 |
) |
|
| 2298 |
} |
|
| 2299 | ||
| 2300 |
## default constructor ---- |
|
| 2301 | ||
| 2302 |
#' @rdname LogisticIndepBeta-class |
|
| 2303 |
#' @note Typically, end users will not use the `.DefaultLogisticIndepBeta()` function. |
|
| 2304 |
#' @export |
|
| 2305 |
.DefaultLogisticIndepBeta <- function() {
|
|
| 2306 | 5x |
my_model <- LogisticIndepBeta( |
| 2307 | 5x |
binDLE = c(1.05, 1.8), |
| 2308 | 5x |
DLEweights = c(3L, 3L), |
| 2309 | 5x |
DLEdose = c(25, 300), |
| 2310 | 5x |
data = Data(doseGrid = seq(25, 300, 25)) |
| 2311 |
) |
|
| 2312 |
} |
|
| 2313 | ||
| 2314 | ||
| 2315 |
# Effloglog ---- |
|
| 2316 | ||
| 2317 |
## class ---- |
|
| 2318 | ||
| 2319 |
#' `Effloglog` |
|
| 2320 |
#' |
|
| 2321 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2322 |
#' |
|
| 2323 |
#' [`Effloglog`] is the class for the linear log-log efficacy model using pseudo |
|
| 2324 |
#' data prior. It describes the relationship between continuous efficacy |
|
| 2325 |
#' responses and corresponding dose levels in log-log scale. This efficacy |
|
| 2326 |
#' log-log model is given as |
|
| 2327 |
#' \deqn{y_i = theta1 + theta2 * log(log(x_i)) + epsilon_i,}
|
|
| 2328 |
#' where \eqn{y_i} is the efficacy response for subject \eqn{i}, \eqn{x_i} is
|
|
| 2329 |
#' the dose level treated for subject \eqn{i} and \eqn{epsilon_i} is the random
|
|
| 2330 |
#' error term of efficacy model at subject \eqn{i}. The error term
|
|
| 2331 |
#' \eqn{epsilon_i} is a random variable that follows normal distribution with
|
|
| 2332 |
#' mean \eqn{0} and variance \eqn{nu^{-1}}, which is assumed to be the
|
|
| 2333 |
#' same for all subjects. |
|
| 2334 |
#' There are three parameters in this model, the intercept \eqn{theta1}, the
|
|
| 2335 |
#' slope \eqn{theta2} and the precision \eqn{nu} of the efficacy responses, also
|
|
| 2336 |
#' known as the inverse of the variance of the pseudo efficacy responses. It can |
|
| 2337 |
#' be a fixed constant or having a gamma distribution. Therefore, a single scalar |
|
| 2338 |
#' value or a vector with two positive numbers values must be specified for `nu` |
|
| 2339 |
#' slot. If there are some observed efficacy responses available, in the output, |
|
| 2340 |
#' `nu` will display the updated value of the precision or the updated values |
|
| 2341 |
#' for the parameters of the gamma distribution. |
|
| 2342 |
#' The `Effloglog` inherits all slots from [`ModelEff`] class. |
|
| 2343 |
#' |
|
| 2344 |
#' @details The prior of this model is specified in form of pseudo data. First, |
|
| 2345 |
#' at least two dose levels are fixed. Then, using e.g. experts' opinion, the |
|
| 2346 |
#' efficacy values that correspond to these dose levels can be obtained, |
|
| 2347 |
#' The `eff` and `eff_dose` arguments represent the prior in form of the pseudo |
|
| 2348 |
#' data. The `eff` represents the pseudo efficacy values. The `eff_dose` |
|
| 2349 |
#' represents the dose levels at which these pseudo efficacy values are |
|
| 2350 |
#' observed. Hence, the positions of the elements specified in `eff` and |
|
| 2351 |
#' `eff_dose` must correspond to each other between these vectors. |
|
| 2352 |
#' Since at least 2 pseudo efficacy values are needed to obtain modal |
|
| 2353 |
#' estimates of the intercept and slope parameters, both `eff` and `eff_dose` |
|
| 2354 |
#' must be vectors of length at least 2. |
|
| 2355 |
#' |
|
| 2356 |
#' The joint prior distribution of the intercept \eqn{theta1} and the slope
|
|
| 2357 |
#' \eqn{theta2} of this model follows bivariate normal distribution with mean
|
|
| 2358 |
#' \eqn{mu} and covariance matrix \eqn{(nu * Q)^{-1}}.
|
|
| 2359 |
#' The mean \eqn{mu} is a \eqn{2 x 1} column vector that contains the prior
|
|
| 2360 |
#' modal estimates of the intercept and the slope. |
|
| 2361 |
#' Scalar \eqn{nu} is the precision of the pseudo efficacy responses and
|
|
| 2362 |
#' \eqn{Q} is the prior or posterior (given that observed, no DLT data is
|
|
| 2363 |
#' available) precision matrix. |
|
| 2364 |
#' It is specified as \eqn{Q = X0^T * X0 + X^T * X}, where \eqn{X0} is a
|
|
| 2365 |
#' design matrix that is based on pseudo dose levels only, and \eqn{X} is a
|
|
| 2366 |
#' design matrix that is based on dose levels corresponding to the no DLT |
|
| 2367 |
#' efficacy responses observed only (if any). |
|
| 2368 |
#' Hence, the \eqn{X0} (or \eqn{X}) will be of size \eqn{r x 2}, if
|
|
| 2369 |
#' there are \eqn{r >= 2} pseudo efficacy responses specified (or
|
|
| 2370 |
#' if there are \eqn{r} no DLT efficacy responses observed in the `data`).
|
|
| 2371 |
#' |
|
| 2372 |
#' @slot eff (`numeric`)\cr the pseudo efficacy responses. Each element here |
|
| 2373 |
#' must represent responses treated based on one subject. |
|
| 2374 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
| 2375 |
#' correspond to values specified in `eff_dose`. |
|
| 2376 |
#' @slot eff_dose (`numeric`)\cr the pseudo efficacy dose levels at which the |
|
| 2377 |
#' pseudo efficacy responses are observed. |
|
| 2378 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
| 2379 |
#' correspond to values specified in `eff`. |
|
| 2380 |
#' @slot nu (`numeric`)\cr parameter of the prior precision of pseudo efficacy |
|
| 2381 |
#' responses. This is either a fixed value or a named vector with two positive |
|
| 2382 |
#' numbers, the shape (`a`), and the rate (`b`) parameters for the gamma |
|
| 2383 |
#' distribution. |
|
| 2384 |
#' @slot use_fixed (`flag`)\cr indicates whether `nu` specified is a fixed value |
|
| 2385 |
#' or a vector with two parameters for gamma distribution. This slot is for |
|
| 2386 |
#' internal purposes only and must not be used by the user. |
|
| 2387 |
#' @slot theta1 (`number`)\cr the intercept in this efficacy log-log model. This |
|
| 2388 |
#' slot is used in output to display the resulting prior or posterior modal |
|
| 2389 |
#' estimates obtained based on the pseudo and observed (if any) data. |
|
| 2390 |
#' @slot theta2 (`number`)\cr the slope in this efficacy log-log model. This |
|
| 2391 |
#' slot is used in output to display the resulting prior or posterior modal |
|
| 2392 |
#' estimates obtained based on the pseudo and observed (if any) data. |
|
| 2393 |
#' @slot Pcov (`matrix`)\cr refers to the \eqn{2 x 2} covariance matrix of the
|
|
| 2394 |
#' estimators of the intercept \eqn{theta1} and the slope \eqn{theta2}
|
|
| 2395 |
#' parameters in this model. |
|
| 2396 |
#' This is used in output to display the resulting prior and posterior |
|
| 2397 |
#' covariance matrix of \eqn{theta1} and \eqn{theta2} obtained, based on the
|
|
| 2398 |
#' pseudo and observed (if any) data. This slot is needed for internal purposes. |
|
| 2399 |
#' @slot X (`matrix`)\cr is the design matrix that is based on either the pseudo |
|
| 2400 |
#' dose levels or observed dose levels (without DLT). This is used |
|
| 2401 |
#' in the output to display the design matrix for the pseudo or the observed |
|
| 2402 |
#' efficacy responses. |
|
| 2403 |
#' @slot Y (`numeric`)\cr is a vector that either contains the pseudo efficacy |
|
| 2404 |
#' responses or observed efficacy responses (without DLT). |
|
| 2405 |
#' @slot mu (`numeric`)\cr a vector of the prior or the posterior modal estimates |
|
| 2406 |
#' of the intercept (\eqn{theta1}) and the slope (\eqn{theta2}).
|
|
| 2407 |
#' This slot is used in output to display as the mean of the prior or posterior |
|
| 2408 |
#' bivariate normal distribution for \eqn{theta1} and \eqn{theta2}.
|
|
| 2409 |
#' @slot Q (`matrix`)\cr is the prior or posterior (given that observed, no DLT |
|
| 2410 |
#' data is available) precision matrix. It is specified as |
|
| 2411 |
#' \eqn{Q = X0^T * X0 + X^T * X}, where \eqn{X0} is a design matrix that is
|
|
| 2412 |
#' based on pseudo dose levels only, and \eqn{X} is a design matrix that is
|
|
| 2413 |
#' based on dose levels corresponding to the observed, no DLT efficacy values |
|
| 2414 |
#' only (if any). |
|
| 2415 |
#' @slot const (`number`)\cr a non-negative number (default to 0), leading to the |
|
| 2416 |
#' model form described above. In general, the model has the form |
|
| 2417 |
#' \eqn{y_i = theta1 + theta2 * log(log(x_i + const)) + epsilon_i}, such that
|
|
| 2418 |
#' dose levels greater than \eqn{1 - const} can be considered as described in
|
|
| 2419 |
#' Yeung et al. (2015). |
|
| 2420 |
#' |
|
| 2421 |
#' @aliases Effloglog |
|
| 2422 |
#' @export |
|
| 2423 |
#' |
|
| 2424 |
.Effloglog <- setClass( |
|
| 2425 |
Class = "Effloglog", |
|
| 2426 |
slots = c( |
|
| 2427 |
eff = "numeric", |
|
| 2428 |
eff_dose = "numeric", |
|
| 2429 |
nu = "numeric", |
|
| 2430 |
use_fixed = "logical", |
|
| 2431 |
theta1 = "numeric", |
|
| 2432 |
theta2 = "numeric", |
|
| 2433 |
Pcov = "matrix", |
|
| 2434 |
X = "matrix", |
|
| 2435 |
Y = "numeric", |
|
| 2436 |
mu = "numeric", |
|
| 2437 |
Q = "matrix", |
|
| 2438 |
const = "numeric" |
|
| 2439 |
), |
|
| 2440 |
prototype = prototype( |
|
| 2441 |
eff = c(0, 0), |
|
| 2442 |
eff_dose = c(1, 1), |
|
| 2443 |
nu = 1 / 0.025, |
|
| 2444 |
use_fixed = TRUE, |
|
| 2445 |
const = 0 |
|
| 2446 |
), |
|
| 2447 |
contains = "ModelEff", |
|
| 2448 |
validity = v_model_eff_log_log |
|
| 2449 |
) |
|
| 2450 | ||
| 2451 |
## constructor ---- |
|
| 2452 | ||
| 2453 |
#' @rdname Effloglog-class |
|
| 2454 |
#' |
|
| 2455 |
#' @param eff (`numeric`)\cr the pseudo efficacy responses. |
|
| 2456 |
#' Elements of `eff` must correspond to the elements of `eff_dose`. |
|
| 2457 |
#' @param eff_dose (`numeric`)\cr dose levels that correspond to pseudo efficacy |
|
| 2458 |
#' responses in `eff`. |
|
| 2459 |
#' @param nu (`numeric`)\cr the precision (inverse of the variance) of the |
|
| 2460 |
#' efficacy responses. This is either a fixed value or a named vector with two |
|
| 2461 |
#' positive numbers, the shape (`a`), and the rate (`b`) parameters for the |
|
| 2462 |
#' gamma distribution. |
|
| 2463 |
#' @param data (`DataDual`)\cr observed data to update estimates of the model |
|
| 2464 |
#' parameters. |
|
| 2465 |
#' @param const (`number`)\cr the constant value added to the dose level when |
|
| 2466 |
#' the dose level value is less than or equal to 1 and a special form of the |
|
| 2467 |
#' linear log-log has to applied (Yeung et al. (2015).). |
|
| 2468 |
#' |
|
| 2469 |
#' @export |
|
| 2470 |
#' @example examples/Model-class-Effloglog.R |
|
| 2471 |
#' |
|
| 2472 |
Effloglog <- function(eff, eff_dose, nu, data, const = 0) {
|
|
| 2473 | 158x |
assert_numeric(eff) |
| 2474 | 158x |
assert_numeric(eff_dose, len = length(eff)) |
| 2475 | 158x |
assert_numeric(nu, min.len = 1, max.len = 2) |
| 2476 | 158x |
assert_class(data, "Data") |
| 2477 | 158x |
assert_number(const, finite = TRUE) |
| 2478 | ||
| 2479 | 158x |
use_fixed <- length(nu) == 1L |
| 2480 | ||
| 2481 | 158x |
eff_dose <- eff_dose + const |
| 2482 |
# Get observed efficacy data without DLT (if any). |
|
| 2483 | 158x |
eff_obsrv_w_x <- getEff(data, no_dlt = TRUE) |
| 2484 | 158x |
eff_obsrv <- eff_obsrv_w_x$w_no_dlt |
| 2485 | 158x |
eff_obsrv_dose <- eff_obsrv_w_x$x_no_dlt + const |
| 2486 | ||
| 2487 |
# Fit pseudo and observed (if any) efficacy. |
|
| 2488 | 158x |
w <- c(eff, eff_obsrv) |
| 2489 | 158x |
x <- c(eff_dose, eff_obsrv_dose) |
| 2490 | 158x |
fit_eff <- suppressWarnings(lm(w ~ log(log(x)))) |
| 2491 | 158x |
X <- model.matrix(fit_eff) |
| 2492 | 158x |
Y <- w |
| 2493 | 158x |
mu <- coef(fit_eff) # This is [theta1, theta2]^T est. |
| 2494 | 158x |
Q <- crossprod(X) |
| 2495 | 158x |
Pcov <- vcov(fit_eff) |
| 2496 | ||
| 2497 | 158x |
nobs_no_dlt <- length(eff_obsrv) |
| 2498 | 158x |
if (nobs_no_dlt > 0L) {
|
| 2499 |
# Observed data available. |
|
| 2500 |
# Set X, Y to observed data only. |
|
| 2501 | 114x |
X <- model.matrix(fit_eff)[-seq_along(eff), ] |
| 2502 | 114x |
Y <- eff_obsrv |
| 2503 | ||
| 2504 | 114x |
fit_eff0 <- lm(eff ~ log(log(eff_dose))) # Pseudo only. |
| 2505 | 114x |
X0 <- model.matrix(fit_eff0) |
| 2506 | 114x |
mu0 <- coef(fit_eff0) |
| 2507 | 114x |
Q0 <- crossprod(X0) |
| 2508 |
# Note that mu = (Q0 + X^T * X)^{-1} * (Q0 * mu0 + X^T * X * (X^T * X)^{-1} X^T * Y),
|
|
| 2509 |
# given that (X^T * X) is invertible and X, Y, mu0, Q0, are specified in this else block. |
|
| 2510 | 114x |
if (!use_fixed) {
|
| 2511 | 114x |
nu["a"] <- nu["a"] + (nobs_no_dlt) / 2 |
| 2512 | 114x |
nu["b"] <- nu["b"] + |
| 2513 | 114x |
(crossprod(Y) + t(mu0) %*% Q0 %*% mu0 - t(mu) %*% Q %*% mu) / 2 |
| 2514 |
} |
|
| 2515 |
} |
|
| 2516 | ||
| 2517 | 158x |
.Effloglog( |
| 2518 | 158x |
eff = eff, |
| 2519 | 158x |
eff_dose = eff_dose, |
| 2520 | 158x |
nu = nu, |
| 2521 | 158x |
use_fixed = use_fixed, |
| 2522 | 158x |
theta1 = mu[["(Intercept)"]], |
| 2523 | 158x |
theta2 = mu[["log(log(x))"]], |
| 2524 | 158x |
Pcov = Pcov, |
| 2525 | 158x |
X = X, |
| 2526 | 158x |
Y = Y, |
| 2527 | 158x |
mu = as.vector(mu), |
| 2528 | 158x |
Q = Q, |
| 2529 | 158x |
const = const, |
| 2530 | 158x |
data = data |
| 2531 |
) |
|
| 2532 |
} |
|
| 2533 | ||
| 2534 |
## default constructor ---- |
|
| 2535 | ||
| 2536 |
#' @rdname Effloglog-class |
|
| 2537 |
#' @note Typically, end users will not use the `.DefaultEffloglog()` function. |
|
| 2538 |
#' @export |
|
| 2539 |
.DefaultEffloglog <- function() {
|
|
| 2540 | 5x |
emptydata <- DataDual(doseGrid = seq(25, 300, 25), placebo = FALSE) |
| 2541 | ||
| 2542 | 5x |
my_data <- DataDual( |
| 2543 | 5x |
x = c(25, 50, 50, 75, 100, 100, 225, 300), |
| 2544 | 5x |
y = c(0, 0, 0, 0, 1, 1, 1, 1), |
| 2545 | 5x |
w = c(0.31, 0.42, 0.59, 0.45, 0.6, 0.7, 0.6, 0.52), |
| 2546 | 5x |
doseGrid = emptydata@doseGrid, |
| 2547 | 5x |
ID = 1L:8L, |
| 2548 | 5x |
cohort = as.integer(c(1, 2, 2, 3, 4, 4, 5, 6)) |
| 2549 |
) |
|
| 2550 | ||
| 2551 | 5x |
Effloglog( |
| 2552 | 5x |
eff = c(1.223, 2.513), |
| 2553 | 5x |
eff_dose = c(25, 300), |
| 2554 | 5x |
nu = c(a = 1, b = 0.025), |
| 2555 | 5x |
data = my_data |
| 2556 |
) |
|
| 2557 |
} |
|
| 2558 | ||
| 2559 |
# EffFlexi ---- |
|
| 2560 | ||
| 2561 |
## class ---- |
|
| 2562 | ||
| 2563 |
#' `EffFlexi` |
|
| 2564 |
#' |
|
| 2565 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2566 |
#' |
|
| 2567 |
#' [`EffFlexi`] is the class for the efficacy model in flexible form of prior |
|
| 2568 |
#' expressed in form of pseudo data. In this class, a flexible form is used to |
|
| 2569 |
#' describe the relationship between the efficacy responses and the dose levels |
|
| 2570 |
#' and it is specified as |
|
| 2571 |
#' \deqn{(W | betaW, sigma2W) ~ Normal(X * betaW, sigma2W * I),}
|
|
| 2572 |
#' where \eqn{W} is a vector of the efficacy responses, \eqn{betaW} is a column
|
|
| 2573 |
#' vector of the mean efficacy responses for all dose levels, and \eqn{X} is
|
|
| 2574 |
#' the design matrix with entries \eqn{I_i,j} that are equal to 1 if subject
|
|
| 2575 |
#' \eqn{i} is allocated to dose \eqn{j}, and \eqn{0} otherwise. The \eqn{sigma2W}
|
|
| 2576 |
#' is the variance of the efficacy responses which can be either a fixed number |
|
| 2577 |
#' or a number from an inverse gamma distribution. |
|
| 2578 |
#' This flexible form aims to capture different shapes of the dose-efficacy |
|
| 2579 |
#' curve. In addition, the first (RW1) or second order (RW2) random walk model |
|
| 2580 |
#' can be used for smoothing data. That is the random walk model is used to model |
|
| 2581 |
#' the first or the second order differences of the mean efficacy responses to |
|
| 2582 |
#' its neighboring dose levels of their mean efficacy responses. |
|
| 2583 |
#' |
|
| 2584 |
#' The RW1 model is given as |
|
| 2585 |
#' \deqn{betaW_j - betaW_j-1) ~ Normal(0, sigma2betaW),}
|
|
| 2586 |
#' and for RW2 as |
|
| 2587 |
#' \deqn{betaW_j-2 - 2 * betaW_j-1 + beta_j ~ Normal(0, sigma2betaW),}
|
|
| 2588 |
#' where \eqn{betaW_j} is the vector of mean efficacy responses at dose j, and
|
|
| 2589 |
#' the \eqn{sigma2betaW} is the prior variance which can be either a fixed
|
|
| 2590 |
#' number or a number from an inverse gamma distribution. |
|
| 2591 |
#' |
|
| 2592 |
#' The `eff` and `eff_dose` are the pseudo efficacy responses and dose levels at |
|
| 2593 |
#' which these pseudo efficacy responses are observed. Both, `eff` and `eff_dose` |
|
| 2594 |
#' must be vectors of length at least 2. The positions of the elements specified |
|
| 2595 |
#' in `eff` and `eff_dose` must correspond to each other between these vectors. |
|
| 2596 |
#' |
|
| 2597 |
#' @details This model will output the updated value or the updated values of the |
|
| 2598 |
#' parameters of the inverse gamma distributions for \eqn{sigma2W} and
|
|
| 2599 |
#' \eqn{sigma2betaW}. The `EffFlexi` inherits all slots from [`ModelEff`] class.
|
|
| 2600 |
#' |
|
| 2601 |
#' @slot eff (`numeric`)\cr the pseudo efficacy responses. Each element here |
|
| 2602 |
#' must represent responses treated based on one subject. |
|
| 2603 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
| 2604 |
#' correspond to values specified in `eff_dose`. |
|
| 2605 |
#' @slot eff_dose (`numeric`)\cr the pseudo efficacy dose levels at which the |
|
| 2606 |
#' pseudo efficacy responses are observed. |
|
| 2607 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
| 2608 |
#' correspond to values specified in `eff`. |
|
| 2609 |
#' @slot sigma2W (`numeric`)\cr the prior variance of the flexible efficacy form. |
|
| 2610 |
#' This is either a fixed value or a named vector with two positive numbers, |
|
| 2611 |
#' the shape (`a`), and the rate (`b`) parameters for the gamma distribution. |
|
| 2612 |
#' @slot sigma2betaW (`numeric`)\cr the prior variance of the random walk model |
|
| 2613 |
#' for the mean efficacy responses. This is either a fixed value or a named |
|
| 2614 |
#' vector with two positive numbers, the shape (`a`), and the rate (`b`) |
|
| 2615 |
#' parameters for the gamma distribution. |
|
| 2616 |
#' @slot use_fixed (`logical`)\cr indicates whether a fixed value for |
|
| 2617 |
#' `sigma2W` and `sigma2betaW` (for each parameter separately) is used or not. |
|
| 2618 |
#' This slot is needed for internal purposes and must not be touched by the user. |
|
| 2619 |
#' @slot rw1 (`flag`)\cr used for smoothing data for this efficacy model. If it |
|
| 2620 |
#' is `TRUE`, the first-order random walk model is used for the mean efficacy |
|
| 2621 |
#' responses. Otherwise, the random walk of second order is used. |
|
| 2622 |
#' @slot X (`matrix`)\cr the design matrix for the efficacy responses. It is |
|
| 2623 |
#' based on both the pseudo and the observed efficacy responses. |
|
| 2624 |
#' @slot RW (`matrix`)\cr the difference matrix for the random walk model. This |
|
| 2625 |
#' slot is needed for internal purposes and must not be used by the user. |
|
| 2626 |
#' @slot RW_rank (`integer`)\cr is the rank of the difference matrix. This |
|
| 2627 |
#' slot is needed for internal purposes and must not be used by the user. |
|
| 2628 |
#' |
|
| 2629 |
#' @aliases EffFlexi |
|
| 2630 |
#' @export |
|
| 2631 |
#' |
|
| 2632 |
.EffFlexi <- setClass( |
|
| 2633 |
Class = "EffFlexi", |
|
| 2634 |
slots = c( |
|
| 2635 |
eff = "numeric", |
|
| 2636 |
eff_dose = "numeric", |
|
| 2637 |
sigma2W = "numeric", |
|
| 2638 |
sigma2betaW = "numeric", |
|
| 2639 |
use_fixed = "logical", |
|
| 2640 |
rw1 = "logical", |
|
| 2641 |
X = "matrix", |
|
| 2642 |
RW = "matrix", |
|
| 2643 |
RW_rank = "integer" |
|
| 2644 |
), |
|
| 2645 |
prototype = prototype( |
|
| 2646 |
eff = c(0, 0), |
|
| 2647 |
eff_dose = c(1, 1), |
|
| 2648 |
sigma2W = 0.025, |
|
| 2649 |
sigma2betaW = 1, |
|
| 2650 |
rw1 = TRUE, |
|
| 2651 |
use_fixed = c(sigma2W = TRUE, sigma2betaW = TRUE) |
|
| 2652 |
), |
|
| 2653 |
contains = "ModelEff", |
|
| 2654 |
validity = v_model_eff_flexi |
|
| 2655 |
) |
|
| 2656 | ||
| 2657 |
## constructor ---- |
|
| 2658 | ||
| 2659 |
#' @rdname EffFlexi-class |
|
| 2660 |
#' |
|
| 2661 |
#' @param eff (`numeric`)\cr the pseudo efficacy responses. |
|
| 2662 |
#' Elements of `eff` must correspond to the elements of `eff_dose`. |
|
| 2663 |
#' @param eff_dose (`numeric`)\cr dose levels that correspond to pseudo efficacy |
|
| 2664 |
#' responses in `eff`. |
|
| 2665 |
#' @param sigma2W (`numeric`)\cr the prior variance of the efficacy responses. |
|
| 2666 |
#' This is either a fixed value or a named vector with two positive numbers, |
|
| 2667 |
#' the shape (`a`), and the rate (`b`) parameters for the inverse gamma |
|
| 2668 |
#' distribution. |
|
| 2669 |
#' @param sigma2betaW (`numeric`)\cr the prior variance of the random walk model |
|
| 2670 |
#' used for smoothing. This is either a fixed value or a named vector with two |
|
| 2671 |
#' positive numbers, the shape (`a`), and the rate (`b`) parameters for the |
|
| 2672 |
#' inverse gamma distribution. |
|
| 2673 |
#' @param rw1 (`flag`)\cr used for smoothing data for this efficacy model. If it |
|
| 2674 |
#' is `TRUE`, the first-order random walk model is used for the mean efficacy |
|
| 2675 |
#' responses. Otherwise, the random walk of second order is used. |
|
| 2676 |
#' @param data (`DataDual`)\cr observed data to update estimates of the model |
|
| 2677 |
#' parameters. |
|
| 2678 |
#' |
|
| 2679 |
#' @export |
|
| 2680 |
#' @example examples/Model-class-EffFlexi.R |
|
| 2681 |
#' |
|
| 2682 |
EffFlexi <- function(eff, eff_dose, sigma2W, sigma2betaW, rw1 = TRUE, data) {
|
|
| 2683 | 65x |
assert_numeric(eff) |
| 2684 | 65x |
assert_numeric(eff_dose) |
| 2685 | 65x |
assert_numeric(sigma2W, min.len = 1, max.len = 2) |
| 2686 | 65x |
assert_numeric(sigma2betaW, min.len = 1, max.len = 2) |
| 2687 | 65x |
assert_flag(rw1) |
| 2688 | 65x |
assert_class(data, "DataDual") |
| 2689 | ||
| 2690 | 65x |
use_fixed <- c( |
| 2691 | 65x |
sigma2W = test_number(sigma2W), |
| 2692 | 65x |
sigma2betaW = test_number(sigma2betaW) |
| 2693 |
) |
|
| 2694 | ||
| 2695 | 65x |
x <- c(eff_dose, getEff(data, no_dlt = TRUE)$x_no_dlt) |
| 2696 | 65x |
x_level <- match_within_tolerance(x, data@doseGrid) |
| 2697 | 65x |
X <- model.matrix(~ -1L + factor(x_level, levels = seq_len(data@nGrid))) |
| 2698 | 65x |
X <- matrix(as.integer(X), ncol = ncol(X)) # To remove some obsolete attributes. |
| 2699 | ||
| 2700 |
# Set up the random walk penalty matrix and its rank. |
|
| 2701 |
# D1: difference matrix of order 1. |
|
| 2702 | 65x |
D1 <- cbind(0, diag(data@nGrid - 1)) - cbind(diag(data@nGrid - 1), 0) |
| 2703 | 65x |
if (rw1) {
|
| 2704 |
# the rank-deficient prior precision for the RW1 prior. |
|
| 2705 | 44x |
RW <- crossprod(D1) |
| 2706 | 44x |
RW_rank <- data@nGrid - 1L # rank = dimension - 1. # nolintr |
| 2707 |
} else {
|
|
| 2708 |
# Second-order difference. |
|
| 2709 | 21x |
D2 <- D1[-1, -1] %*% D1 |
| 2710 | 21x |
RW <- crossprod(D2) |
| 2711 | 21x |
RW_rank <- data@nGrid - 2L # nolintr |
| 2712 |
} |
|
| 2713 | ||
| 2714 | 65x |
.EffFlexi( |
| 2715 | 65x |
eff = eff, |
| 2716 | 65x |
eff_dose = eff_dose, |
| 2717 | 65x |
sigma2W = sigma2W, |
| 2718 | 65x |
sigma2betaW = sigma2betaW, |
| 2719 | 65x |
use_fixed = use_fixed, |
| 2720 | 65x |
rw1 = rw1, |
| 2721 | 65x |
X = X, |
| 2722 | 65x |
RW = RW, |
| 2723 | 65x |
RW_rank = RW_rank, |
| 2724 | 65x |
data = data |
| 2725 |
) |
|
| 2726 |
} |
|
| 2727 | ||
| 2728 |
## default constructor ---- |
|
| 2729 | ||
| 2730 |
#' @rdname EffFlexi-class |
|
| 2731 |
#' @note Typically, end users will not use the `.DefaultEffFlexi()` function. |
|
| 2732 |
#' @export |
|
| 2733 |
.DefaultEffFlexi <- function() {
|
|
| 2734 | 5x |
empty_data <- DataDual(doseGrid = seq(25, 300, 25)) |
| 2735 | 5x |
EffFlexi( |
| 2736 | 5x |
eff = c(1.223, 2.513), |
| 2737 | 5x |
eff_dose = c(25, 300), |
| 2738 | 5x |
sigma2W = c(a = 0.1, b = 0.1), |
| 2739 | 5x |
sigma2betaW = c(a = 20, b = 50), |
| 2740 | 5x |
rw1 = FALSE, |
| 2741 | 5x |
data = empty_data |
| 2742 |
) |
|
| 2743 | ||
| 2744 | 5x |
data <- DataDual( |
| 2745 | 5x |
x = c(25, 50, 50, 75, 100, 100, 225, 300), |
| 2746 | 5x |
y = c(0, 0, 0, 0, 1, 1, 1, 1), |
| 2747 | 5x |
w = c(0.31, 0.42, 0.59, 0.45, 0.6, 0.7, 0.6, 0.52), |
| 2748 | 5x |
doseGrid = empty_data@doseGrid, |
| 2749 | 5x |
ID = 1L:8L, |
| 2750 | 5x |
cohort = as.integer(c(1, 2, 2, 3, 4, 4, 5, 6)) |
| 2751 |
) |
|
| 2752 | ||
| 2753 | 5x |
EffFlexi( |
| 2754 | 5x |
eff = c(1.223, 2.513), |
| 2755 | 5x |
eff_dose = c(25, 300), |
| 2756 | 5x |
sigma2W = c(a = 0.1, b = 0.1), |
| 2757 | 5x |
sigma2betaW = c(a = 20, b = 50), |
| 2758 | 5x |
rw1 = FALSE, |
| 2759 | 5x |
data = data |
| 2760 |
) |
|
| 2761 |
} |
|
| 2762 | ||
| 2763 |
# DALogisticLogNormal ---- |
|
| 2764 | ||
| 2765 |
## class ---- |
|
| 2766 | ||
| 2767 |
#' `DALogisticLogNormal` |
|
| 2768 |
#' |
|
| 2769 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2770 |
#' |
|
| 2771 |
#' [`DALogisticLogNormal`] is the class for the logistic model with bivariate |
|
| 2772 |
#' (log) normal prior and data augmentation. This class inherits from the |
|
| 2773 |
#' [`LogisticLogNormal`] class. |
|
| 2774 |
#' |
|
| 2775 |
#' @note We still need to include here formula for the lambda prior. |
|
| 2776 |
#' |
|
| 2777 |
#' @slot npiece (`number`)\cr the number of pieces in the `PEM`. |
|
| 2778 |
#' @slot l (`numeric`)\cr a vector used in the lambda prior. |
|
| 2779 |
#' @slot c_par (`numeric`)\cr a parameter used in the lambda prior; according to |
|
| 2780 |
#' Liu's paper, `c_par = 2` is recommended. |
|
| 2781 |
#' @slot cond_pem (`flag`)\cr is a conditional piecewise-exponential model used? |
|
| 2782 |
#' (default). Otherwise an unconditional model is used. |
|
| 2783 |
#' |
|
| 2784 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`]. |
|
| 2785 |
#' |
|
| 2786 |
#' @aliases DALogisticLogNormal |
|
| 2787 |
#' @export |
|
| 2788 |
#' |
|
| 2789 |
.DALogisticLogNormal <- setClass( |
|
| 2790 |
Class = "DALogisticLogNormal", |
|
| 2791 |
slots = c( |
|
| 2792 |
npiece = "integer", |
|
| 2793 |
l = "numeric", |
|
| 2794 |
c_par = "numeric", |
|
| 2795 |
cond_pem = "logical" |
|
| 2796 |
), |
|
| 2797 |
prototype = prototype( |
|
| 2798 |
npiece = 3L, |
|
| 2799 |
l = 0.5, |
|
| 2800 |
c_par = 2, |
|
| 2801 |
cond_pem = TRUE |
|
| 2802 |
), |
|
| 2803 |
contains = "LogisticLogNormal", |
|
| 2804 |
validity = v_model_da_logistic_log_normal |
|
| 2805 |
) |
|
| 2806 | ||
| 2807 |
## constructor ---- |
|
| 2808 | ||
| 2809 |
#' @rdname DALogisticLogNormal-class |
|
| 2810 |
#' |
|
| 2811 |
#' @param npiece (`number`)\cr the number of pieces in the `PEM`. |
|
| 2812 |
#' @param l (`numeric`)\cr a vector used in the lambda prior. |
|
| 2813 |
#' @param c_par (`numeric`)\cr a parameter used in the lambda prior; according to |
|
| 2814 |
#' Liu's paper, `c_par = 2` is recommended. |
|
| 2815 |
#' @param cond_pem (`flag`)\cr is a conditional piecewise-exponential model used? |
|
| 2816 |
#' (default). Otherwise an unconditional model is used. |
|
| 2817 |
#' @inheritDotParams LogisticLogNormal |
|
| 2818 |
#' |
|
| 2819 |
#' @export |
|
| 2820 |
#' @example examples/Model-class-DALogisticLogNormal.R |
|
| 2821 |
#' |
|
| 2822 |
DALogisticLogNormal <- function( |
|
| 2823 |
npiece = 3, |
|
| 2824 |
l, |
|
| 2825 |
c_par = 2, |
|
| 2826 |
cond_pem = TRUE, |
|
| 2827 |
... |
|
| 2828 |
) {
|
|
| 2829 | 31x |
assert_flag(cond_pem) |
| 2830 | ||
| 2831 | 31x |
start <- LogisticLogNormal(...) |
| 2832 | ||
| 2833 | 31x |
datamodel <- function() {
|
| 2834 | ! |
for (i in 1:nObs) {
|
| 2835 |
# Part I: describe the logistic model of DLTs vs dose. |
|
| 2836 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 2837 | ||
| 2838 |
# Part II: describe the piecewise exponential. |
|
| 2839 |
# Notice that: |
|
| 2840 |
# when y=1 -> DLT=1 and u=<T; |
|
| 2841 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
| 2842 |
# when y=0 & T>t (u<T) -> DLT=NA/missing; |
|
| 2843 |
# when indx=0 -> censored, i.e u<T and event=0; |
|
| 2844 |
# when indx=1 -> not censored, i.e. u>=T or event=1; |
|
| 2845 | ! |
indx[i] <- 1 - step(Tmax - u[i] - eps) * (1 - y[i]) |
| 2846 | ||
| 2847 | ! |
for (j in 1:npiece) {
|
| 2848 |
# When not censored, i.e DLT!=NA & t[i]=u[i]; |
|
| 2849 |
# if t[i]<h[j], d[i,j]=0; |
|
| 2850 |
# if h[j]<t[i]=<h[j+1], d[i,j]=1 |
|
| 2851 |
# if h[j+1]<t[i], d[i,j]=0 |
|
| 2852 |
# When censored t[i]>u[i] -> d[i,j]=0 |
|
| 2853 | ! |
d[i, j] <- y[i] * step(u[i] - h[j] - eps) * step(h[j + 1] - u[i]) |
| 2854 | ||
| 2855 |
# DLT free survival(time) for patient i in interval I(j); |
|
| 2856 |
# if t[i]<h[j], s[i,j]=0; |
|
| 2857 |
# if h[j]<t[i]<=h[j+1], s[i,j]=t[i]-h[j] |
|
| 2858 |
# if h[j+1]<=t[i], s[i,j]=h[j+1]-h[j] |
|
| 2859 | ! |
s[i, j] <- min(u[i] - h[j], h[j + 1] - h[j]) * step(u[i] - h[j]) |
| 2860 | ||
| 2861 |
# piecewise exponential hazard rate lambda[j]; |
|
| 2862 | ! |
mu_u[i, j] <- lambda[j] * s[i, j] |
| 2863 | ! |
mu[i, j] <- d[i, j] * log(lambda[j]) - y[i] * mu_u[i, j] |
| 2864 |
} |
|
| 2865 | ||
| 2866 |
# The likelihood function. |
|
| 2867 | ! |
L_obs[i] <- exp(sum(mu[i, ])) * |
| 2868 | ! |
pow(p[i] / A, y[i]) * |
| 2869 | ! |
pow(1 - p[i], 1 - y[i]) # Not censored. # nolintr |
| 2870 | ! |
L_cnsr[i] <- 1 - p[i] * (1 - exp(-sum(mu_u[i, ]))) / A # Censored. # nolintr |
| 2871 | ! |
L[i] <- pow(L_obs[i], indx[i]) * pow(L_cnsr[i], 1 - indx[i]) |
| 2872 | ||
| 2873 |
# Apply zero trick in JAGS. |
|
| 2874 | ! |
phi[i] <- -log(L[i]) + cadj |
| 2875 | ! |
zeros[i] ~ dpois(phi[i]) |
| 2876 |
} |
|
| 2877 |
} |
|
| 2878 | ||
| 2879 | 31x |
priormodel <- h_jags_join_models( |
| 2880 | 31x |
start@priormodel, |
| 2881 | 31x |
function() {
|
| 2882 | ! |
g_beta <- 1 / c_par |
| 2883 | ! |
for (j in 1:npiece) {
|
| 2884 | ! |
g_alpha[j] <- l[j] / c_par |
| 2885 | ! |
lambda[j] ~ dgamma(g_alpha[j], g_beta) |
| 2886 | ! |
mu_T[j] <- lambda[j] * (h[j + 1] - h[j]) # nolintr |
| 2887 |
} |
|
| 2888 |
# If cond = 1, then conditional PEM is used and A is defined as |
|
| 2889 |
# the probability to have DLT, i.e. t<T, otherwise |
|
| 2890 |
# cond = 0 and A is just 1 (so no impact in likelihood). |
|
| 2891 | ! |
A <- cond * (1 - exp(-sum(mu_T))) + (1 - cond) |
| 2892 |
} |
|
| 2893 |
) |
|
| 2894 | ||
| 2895 | 31x |
modelspecs <- function(nObs, Tmax, from_prior) {
|
| 2896 | 42x |
ms <- list( |
| 2897 | 42x |
prec = start@params@prec, |
| 2898 | 42x |
mean = start@params@mean, |
| 2899 | 42x |
npiece = npiece, |
| 2900 | 42x |
l = l, |
| 2901 | 42x |
c_par = c_par, |
| 2902 | 42x |
h = seq(from = 0L, to = Tmax, length = npiece + 1), |
| 2903 | 42x |
cond = as.integer(cond_pem) |
| 2904 |
) |
|
| 2905 | 42x |
if (!from_prior) {
|
| 2906 | 41x |
ms <- c( |
| 2907 | 41x |
list( |
| 2908 | 41x |
ref_dose = start@ref_dose, |
| 2909 | 41x |
zeros = rep(0, nObs), |
| 2910 | 41x |
eps = 1e-10, |
| 2911 | 41x |
cadj = 1e10 |
| 2912 |
), |
|
| 2913 | 41x |
ms |
| 2914 |
) |
|
| 2915 |
} |
|
| 2916 | 42x |
ms |
| 2917 |
} |
|
| 2918 | ||
| 2919 | 31x |
assert_integerish(npiece, lower = 1) |
| 2920 | ||
| 2921 | 31x |
.DALogisticLogNormal( |
| 2922 | 31x |
start, |
| 2923 | 31x |
npiece = as.integer(npiece), |
| 2924 | 31x |
l = l, |
| 2925 | 31x |
c_par = c_par, |
| 2926 | 31x |
cond_pem = cond_pem, |
| 2927 | 31x |
datamodel = datamodel, |
| 2928 | 31x |
priormodel = priormodel, |
| 2929 | 31x |
modelspecs = modelspecs, |
| 2930 | 31x |
datanames = c("nObs", "y", "x", "u", "Tmax"),
|
| 2931 | 31x |
sample = c("alpha0", "alpha1", "lambda")
|
| 2932 |
) |
|
| 2933 |
} |
|
| 2934 | ||
| 2935 |
## default constructor ---- |
|
| 2936 | ||
| 2937 |
#' @rdname DALogisticLogNormal-class |
|
| 2938 |
#' @note Typically, end users will not use the `.DefaultDALogisticLogNormal()` function. |
|
| 2939 |
#' @export |
|
| 2940 |
.DefaultDALogisticLogNormal <- function() {
|
|
| 2941 | 7x |
npiece <- 10 |
| 2942 | 7x |
Tmax <- 60 |
| 2943 | ||
| 2944 | 7x |
lambda_prior <- function(k) {
|
| 2945 | 7x |
npiece / (Tmax * (npiece - k + 0.5)) |
| 2946 |
} |
|
| 2947 | ||
| 2948 | 7x |
DALogisticLogNormal( |
| 2949 | 7x |
mean = c(-0.85, 1), |
| 2950 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 2951 | 7x |
ref_dose = 56, |
| 2952 | 7x |
npiece = npiece, |
| 2953 | 7x |
l = as.numeric(t(apply( |
| 2954 | 7x |
as.matrix(c(1:npiece), 1, npiece), |
| 2955 | 7x |
2, |
| 2956 | 7x |
lambda_prior |
| 2957 |
))), |
|
| 2958 | 7x |
c_par = 2 |
| 2959 |
) |
|
| 2960 |
} |
|
| 2961 | ||
| 2962 |
# TITELogisticLogNormal ---- |
|
| 2963 | ||
| 2964 |
## class ---- |
|
| 2965 | ||
| 2966 |
#' `TITELogisticLogNormal` |
|
| 2967 |
#' |
|
| 2968 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2969 |
#' |
|
| 2970 |
#' [`TITELogisticLogNormal`] is the class for TITE-CRM based on a logistic |
|
| 2971 |
#' regression model using a bivariate normal prior on the intercept and log |
|
| 2972 |
#' slope parameters. |
|
| 2973 |
#' |
|
| 2974 |
#' This class inherits from the [`LogisticLogNormal`]. |
|
| 2975 |
#' |
|
| 2976 |
#' @slot weight_method (`string`)\cr the weight function method: either linear |
|
| 2977 |
#' or adaptive. This was used in Liu, Yin and Yuan's paper. |
|
| 2978 |
#' |
|
| 2979 |
#' @seealso [`DALogisticLogNormal`]. |
|
| 2980 |
#' |
|
| 2981 |
#' @aliases TITELogisticLogNormal |
|
| 2982 |
#' @export |
|
| 2983 |
#' |
|
| 2984 |
.TITELogisticLogNormal <- setClass( |
|
| 2985 |
Class = "TITELogisticLogNormal", |
|
| 2986 |
slots = c(weight_method = "character"), |
|
| 2987 |
prototype = prototype(weight_method = "linear"), |
|
| 2988 |
contains = "LogisticLogNormal", |
|
| 2989 |
validity = v_model_tite_logistic_log_normal |
|
| 2990 |
) |
|
| 2991 | ||
| 2992 |
## constructor ---- |
|
| 2993 | ||
| 2994 |
#' @rdname TITELogisticLogNormal-class |
|
| 2995 |
#' |
|
| 2996 |
#' @param weight_method (`string`)\cr the weight function method: either linear |
|
| 2997 |
#' or adaptive. This was used in Liu, Yin and Yuan's paper. |
|
| 2998 |
#' @inheritDotParams LogisticLogNormal |
|
| 2999 |
#' |
|
| 3000 |
#' @export |
|
| 3001 |
#' @example examples/Model-class-TITELogisticLogNormal.R |
|
| 3002 |
#' |
|
| 3003 |
TITELogisticLogNormal <- function(weight_method = "linear", ...) {
|
|
| 3004 | 18x |
assert_character( |
| 3005 | 18x |
weight_method, |
| 3006 | 18x |
min.len = 1L, |
| 3007 | 18x |
max.len = 2L, |
| 3008 | 18x |
any.missing = FALSE |
| 3009 |
) |
|
| 3010 | ||
| 3011 | 18x |
start <- LogisticLogNormal(...) |
| 3012 | ||
| 3013 | 18x |
datamodel <- function() {
|
| 3014 | ! |
for (i in 1:nObs) {
|
| 3015 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
| 3016 | ||
| 3017 |
# The piecewise exponential likelihood. Notice that: |
|
| 3018 |
# when y=1 -> DLT=1 and u=<T; |
|
| 3019 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
| 3020 |
# when y=0 & T>t (u<T) -> DLT=NA/missing; |
|
| 3021 |
# when indx=0 -> censored, i.e u<T and event=0; |
|
| 3022 |
# when indx=1 -> not censored, i.e. u>=T or event=1; |
|
| 3023 | ! |
L[i] <- pow(p[i], y[i]) * pow((1 - w[i] * p[i]), (1 - y[i])) |
| 3024 | ||
| 3025 |
# Apply zero trick in JAGS. |
|
| 3026 | ! |
phi[i] <- -log(L[i]) + cadj |
| 3027 | ! |
zeros[i] ~ dpois(phi[i]) |
| 3028 |
} |
|
| 3029 |
} |
|
| 3030 | ||
| 3031 | 18x |
modelspecs <- function(nObs, u, Tmax, y, from_prior) {
|
| 3032 | 6x |
ms <- list(prec = start@params@prec, mean = start@params@mean) |
| 3033 |
# Calculate weights `w` based on the input data. |
|
| 3034 | 6x |
if (!from_prior && nObs > 0L) {
|
| 3035 | 4x |
if (weight_method == "linear") {
|
| 3036 | 2x |
w <- u / Tmax |
| 3037 | 2x |
} else if (weight_method == "adaptive") {
|
| 3038 | 2x |
nDLT <- sum(y) |
| 3039 | 2x |
if (nDLT > 0) {
|
| 3040 | 2x |
u_dlt <- sort(u[y == 1]) |
| 3041 | 2x |
w <- sapply(u, function(u_i) {
|
| 3042 | 20x |
m <- sum(u_i >= u_dlt) |
| 3043 | 20x |
w_i <- if (m == 0) {
|
| 3044 | 9x |
u_i / u_dlt[1] |
| 3045 | 20x |
} else if (m < nDLT) {
|
| 3046 | 5x |
m + (u_i - u_dlt[m]) / (u_dlt[m + 1] - u_dlt[m]) |
| 3047 |
} else {
|
|
| 3048 |
# m == nDLT. nolintr |
|
| 3049 | 6x |
m + (u_i - u_dlt[m]) / (Tmax + 0.00000001 - u_dlt[m]) |
| 3050 |
} |
|
| 3051 | 20x |
w_i / (nDLT + 1) |
| 3052 |
}) |
|
| 3053 |
} else {
|
|
| 3054 | ! |
w <- u / Tmax |
| 3055 |
} |
|
| 3056 |
} |
|
| 3057 | 4x |
w[y == 1] <- 1 |
| 3058 | 4x |
w[u == Tmax] <- 1 |
| 3059 | ||
| 3060 | 4x |
ms <- c( |
| 3061 | 4x |
list( |
| 3062 | 4x |
ref_dose = start@ref_dose, |
| 3063 | 4x |
zeros = rep(0, nObs), |
| 3064 | 4x |
cadj = 1e10, |
| 3065 | 4x |
w = w |
| 3066 |
), |
|
| 3067 | 4x |
ms |
| 3068 |
) |
|
| 3069 |
} |
|
| 3070 | 6x |
ms |
| 3071 |
} |
|
| 3072 | ||
| 3073 | 18x |
.TITELogisticLogNormal( |
| 3074 | 18x |
start, |
| 3075 | 18x |
weight_method = weight_method, |
| 3076 | 18x |
datamodel = datamodel, |
| 3077 | 18x |
modelspecs = modelspecs, |
| 3078 | 18x |
datanames = c("nObs", "y", "x")
|
| 3079 |
) |
|
| 3080 |
} |
|
| 3081 | ||
| 3082 |
## default constructor ---- |
|
| 3083 | ||
| 3084 |
#' @rdname TITELogisticLogNormal-class |
|
| 3085 |
#' @note Typically, end users will not use the `.DefaultTITELogisticLogNormal()` function. |
|
| 3086 |
#' @export |
|
| 3087 |
.DefaultTITELogisticLogNormal <- function() {
|
|
| 3088 | 7x |
TITELogisticLogNormal( |
| 3089 | 7x |
mean = c(0, 1), |
| 3090 | 7x |
cov = diag(2), |
| 3091 | 7x |
ref_dose = 1, |
| 3092 | 7x |
weight_method = "linear" |
| 3093 |
) |
|
| 3094 |
} |
|
| 3095 | ||
| 3096 |
# OneParLogNormalPrior ---- |
|
| 3097 | ||
| 3098 |
## class ---- |
|
| 3099 | ||
| 3100 |
#' `OneParLogNormalPrior` |
|
| 3101 |
#' |
|
| 3102 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3103 |
#' |
|
| 3104 |
#' [`OneParLogNormalPrior`] is the class for a standard CRM with a normal prior on |
|
| 3105 |
#' the log power parameter for the skeleton prior probabilities. |
|
| 3106 |
#' |
|
| 3107 |
#' @slot skel_fun (`function`)\cr function to calculate the prior DLT probabilities. |
|
| 3108 |
#' @slot skel_fun_inv (`function`)\cr inverse function of `skel_fun`. |
|
| 3109 |
#' @slot skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
| 3110 |
#' of unique and sorted probability values between 0 and 1. |
|
| 3111 |
#' @slot sigma2 (`number`)\cr prior variance of log power parameter alpha. |
|
| 3112 |
#' |
|
| 3113 |
#' @seealso [`ModelLogNormal`]. |
|
| 3114 |
#' |
|
| 3115 |
#' @aliases OneParLogNormalPrior |
|
| 3116 |
#' @export |
|
| 3117 |
#' |
|
| 3118 |
.OneParLogNormalPrior <- setClass( |
|
| 3119 |
Class = "OneParLogNormalPrior", |
|
| 3120 |
slots = c( |
|
| 3121 |
skel_fun = "function", |
|
| 3122 |
skel_fun_inv = "function", |
|
| 3123 |
skel_probs = "numeric", |
|
| 3124 |
sigma2 = "numeric" |
|
| 3125 |
), |
|
| 3126 |
contains = "GeneralModel", |
|
| 3127 |
validity = v_model_one_par_exp_normal_prior |
|
| 3128 |
) |
|
| 3129 | ||
| 3130 |
## constructor ---- |
|
| 3131 | ||
| 3132 |
#' @rdname OneParLogNormalPrior-class |
|
| 3133 |
#' |
|
| 3134 |
#' @param skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
| 3135 |
#' of unique and sorted probability values between 0 and 1. |
|
| 3136 |
#' @param dose_grid (`numeric`)\cr dose grid. It must be must be a sorted vector |
|
| 3137 |
#' of the same length as `skel_probs`. |
|
| 3138 |
#' @param sigma2 (`number`)\cr prior variance of log power parameter alpha. |
|
| 3139 |
#' |
|
| 3140 |
#' @export |
|
| 3141 |
#' @example examples/Model-class-OneParLogNormalPrior.R |
|
| 3142 |
#' |
|
| 3143 |
OneParLogNormalPrior <- function(skel_probs, dose_grid, sigma2) {
|
|
| 3144 | 44x |
assert_probabilities(skel_probs, unique = TRUE, sorted = TRUE) # So that skel_fun_inv exists. |
| 3145 | 41x |
assert_numeric( |
| 3146 | 41x |
dose_grid, |
| 3147 | 41x |
len = length(skel_probs), |
| 3148 | 41x |
any.missing = FALSE, |
| 3149 | 41x |
unique = TRUE, |
| 3150 | 41x |
sorted = TRUE |
| 3151 |
) |
|
| 3152 | ||
| 3153 | 38x |
skel_fun <- approxfun(x = dose_grid, y = skel_probs, rule = 2) |
| 3154 | 38x |
skel_fun_inv <- approxfun(x = skel_probs, y = dose_grid, rule = 2) |
| 3155 | ||
| 3156 | 38x |
.OneParLogNormalPrior( |
| 3157 | 38x |
skel_fun = skel_fun, |
| 3158 | 38x |
skel_fun_inv = skel_fun_inv, |
| 3159 | 38x |
skel_probs = skel_probs, |
| 3160 | 38x |
sigma2 = sigma2, |
| 3161 | 38x |
datamodel = function() {
|
| 3162 | ! |
for (i in 1:nObs) {
|
| 3163 | ! |
p[i] <- skel_probs[xLevel[i]]^exp(alpha) |
| 3164 | ! |
y[i] ~ dbern(p[i]) |
| 3165 |
} |
|
| 3166 |
}, |
|
| 3167 | 38x |
priormodel = function() {
|
| 3168 | ! |
alpha ~ dnorm(0, 1 / sigma2) |
| 3169 |
}, |
|
| 3170 | 38x |
modelspecs = function(from_prior) {
|
| 3171 | 5x |
ms <- list(sigma2 = sigma2) |
| 3172 | 5x |
if (!from_prior) {
|
| 3173 | 3x |
ms$skel_probs <- skel_probs |
| 3174 |
} |
|
| 3175 | 5x |
ms |
| 3176 |
}, |
|
| 3177 | 38x |
init = function() {
|
| 3178 | 7x |
list(alpha = 1) |
| 3179 |
}, |
|
| 3180 | 38x |
datanames = c("nObs", "y", "xLevel"),
|
| 3181 | 38x |
sample = "alpha" |
| 3182 |
) |
|
| 3183 |
} |
|
| 3184 | ||
| 3185 |
## default constructor ---- |
|
| 3186 | ||
| 3187 |
#' @rdname OneParLogNormalPrior-class |
|
| 3188 |
#' @return an instance of the `OneParLogNormalPrior` class |
|
| 3189 |
#' @export |
|
| 3190 |
.DefaultOneParLogNormalPrior <- function() {
|
|
| 3191 | 6x |
OneParLogNormalPrior( |
| 3192 | 6x |
skel_probs = seq(from = 0.1, to = 0.9, length = 5), |
| 3193 | 6x |
dose_grid = 1:5, |
| 3194 | 6x |
sigma2 = 2 |
| 3195 |
) |
|
| 3196 |
} |
|
| 3197 | ||
| 3198 |
# OneParExpPrior ---- |
|
| 3199 | ||
| 3200 |
## class ---- |
|
| 3201 | ||
| 3202 |
#' `OneParExpPrior` |
|
| 3203 |
#' |
|
| 3204 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3205 |
#' |
|
| 3206 |
#' [`OneParExpPrior`] is the class for a standard CRM with an exponential prior |
|
| 3207 |
#' on the power parameter for the skeleton prior probabilities. It is an |
|
| 3208 |
#' implementation of a version of the one-parameter CRM (O’Quigley et al. 1990). |
|
| 3209 |
#' |
|
| 3210 |
#' @note Typically, end users will not use the `.DefaultOneparExpPrior()` function. |
|
| 3211 |
#' |
|
| 3212 |
#' @slot skel_fun (`function`)\cr function to calculate the prior DLT probabilities. |
|
| 3213 |
#' @slot skel_fun_inv (`function`)\cr inverse function of `skel_fun`. |
|
| 3214 |
#' @slot skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
| 3215 |
#' of unique and sorted probability values between 0 and 1. |
|
| 3216 |
#' @slot lambda (`number`)\cr rate parameter of prior exponential distribution |
|
| 3217 |
#' for theta. |
|
| 3218 |
#' |
|
| 3219 |
#' @aliases OneParExpPrior |
|
| 3220 |
#' @export |
|
| 3221 |
#' |
|
| 3222 |
.OneParExpPrior <- setClass( |
|
| 3223 |
Class = "OneParExpPrior", |
|
| 3224 |
slots = c( |
|
| 3225 |
skel_fun = "function", |
|
| 3226 |
skel_fun_inv = "function", |
|
| 3227 |
skel_probs = "numeric", |
|
| 3228 |
lambda = "numeric" |
|
| 3229 |
), |
|
| 3230 |
contains = "GeneralModel", |
|
| 3231 |
validity = v_model_one_par_exp_prior |
|
| 3232 |
) |
|
| 3233 | ||
| 3234 |
## constructor ---- |
|
| 3235 | ||
| 3236 |
#' @rdname OneParExpPrior-class |
|
| 3237 |
#' |
|
| 3238 |
#' @param skel_probs see slot definition. |
|
| 3239 |
#' @param dose_grid (`numeric`)\cr dose grid. It must be must be a sorted vector |
|
| 3240 |
#' of the same length as `skel_probs`. |
|
| 3241 |
#' @param lambda see slot definition. |
|
| 3242 |
#' |
|
| 3243 |
#' @export |
|
| 3244 |
#' @example examples/Model-class-OneParExpPrior.R |
|
| 3245 |
#' |
|
| 3246 |
OneParExpPrior <- function(skel_probs, dose_grid, lambda) {
|
|
| 3247 | 30x |
assert_probabilities(skel_probs, unique = TRUE, sorted = TRUE) # So that skel_fun_inv exists. |
| 3248 | 27x |
assert_numeric( |
| 3249 | 27x |
dose_grid, |
| 3250 | 27x |
len = length(skel_probs), |
| 3251 | 27x |
any.missing = FALSE, |
| 3252 | 27x |
unique = TRUE, |
| 3253 | 27x |
sorted = TRUE |
| 3254 |
) |
|
| 3255 | ||
| 3256 | 24x |
skel_fun <- approxfun(x = dose_grid, y = skel_probs, rule = 2) |
| 3257 | 24x |
skel_fun_inv <- approxfun(x = skel_probs, y = dose_grid, rule = 2) |
| 3258 | ||
| 3259 | 24x |
.OneParExpPrior( |
| 3260 | 24x |
skel_fun = skel_fun, |
| 3261 | 24x |
skel_fun_inv = skel_fun_inv, |
| 3262 | 24x |
skel_probs = skel_probs, |
| 3263 | 24x |
lambda = lambda, |
| 3264 | 24x |
datamodel = function() {
|
| 3265 | ! |
for (i in 1:nObs) {
|
| 3266 | ! |
p[i] <- skel_probs[xLevel[i]]^theta |
| 3267 | ! |
y[i] ~ dbern(p[i]) |
| 3268 |
} |
|
| 3269 |
}, |
|
| 3270 | 24x |
priormodel = function() {
|
| 3271 | ! |
theta ~ dexp(lambda) |
| 3272 |
}, |
|
| 3273 | 24x |
modelspecs = function(from_prior) {
|
| 3274 | 2x |
ms <- list(lambda = lambda) |
| 3275 | 2x |
if (!from_prior) {
|
| 3276 | 1x |
ms$skel_probs <- skel_probs |
| 3277 |
} |
|
| 3278 | 2x |
ms |
| 3279 |
}, |
|
| 3280 | 24x |
init = function() {
|
| 3281 | 2x |
list(theta = 1) |
| 3282 |
}, |
|
| 3283 | 24x |
datanames = c("nObs", "y", "xLevel"),
|
| 3284 | 24x |
sample = "theta" |
| 3285 |
) |
|
| 3286 |
} |
|
| 3287 | ||
| 3288 |
## default constructor ---- |
|
| 3289 | ||
| 3290 |
#' @rdname OneParExpPrior-class |
|
| 3291 |
#' @note Typically, end users will not use the `.DefaultOneParLogNormalPrior()` function. |
|
| 3292 |
#' @export |
|
| 3293 |
.DefaultOneParExpPrior <- function() {
|
|
| 3294 | 6x |
OneParExpPrior( |
| 3295 | 6x |
skel_probs = c(0.1, 0.3, 0.5, 0.7, 0.9), |
| 3296 | 6x |
dose_grid = 1:5, |
| 3297 | 6x |
lambda = 2 |
| 3298 |
) |
|
| 3299 |
} |
|
| 3300 | ||
| 3301 |
# FractionalCRM ---- |
|
| 3302 | ||
| 3303 |
## class ---- |
|
| 3304 | ||
| 3305 |
#' `FractionalCRM` |
|
| 3306 |
#' |
|
| 3307 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3308 |
#' |
|
| 3309 |
#' [`FractionalCRM`] is the class for a fractional CRM model based on a one |
|
| 3310 |
#' parameter CRM (with normal prior on the log-power parameter) as well as |
|
| 3311 |
#' Kaplan-Meier based estimation of the conditional probability to experience a |
|
| 3312 |
#' DLT for non-complete observations. |
|
| 3313 |
#' |
|
| 3314 |
#' This fractional CRM model follows the paper and code by Guosheng Yin et al. |
|
| 3315 |
#' |
|
| 3316 |
#' @seealso [`TITELogisticLogNormal`]. |
|
| 3317 |
#' |
|
| 3318 |
#' @aliases FractionalCRM |
|
| 3319 |
#' @export |
|
| 3320 |
#' |
|
| 3321 |
.FractionalCRM <- setClass( |
|
| 3322 |
Class = "FractionalCRM", |
|
| 3323 |
contains = "OneParLogNormalPrior" |
|
| 3324 |
) |
|
| 3325 | ||
| 3326 |
## constructor ---- |
|
| 3327 | ||
| 3328 |
#' @rdname FractionalCRM-class |
|
| 3329 |
#' |
|
| 3330 |
#' @inheritDotParams OneParLogNormalPrior |
|
| 3331 |
#' |
|
| 3332 |
#' @export |
|
| 3333 |
#' @example examples/Model-class-FractionalCRM.R |
|
| 3334 |
#' |
|
| 3335 |
FractionalCRM <- function(...) {
|
|
| 3336 | 12x |
start <- OneParLogNormalPrior(...) |
| 3337 | ||
| 3338 |
# This is adapted from the TITELogisticLogNormal class. |
|
| 3339 | 12x |
datamodel <- function() {
|
| 3340 | ! |
for (i in 1:nObs) {
|
| 3341 | ! |
p[i] <- skel_probs[xLevel[i]]^exp(alpha) |
| 3342 | ||
| 3343 |
# The piecewise exponential likelihood. Notice that: |
|
| 3344 |
# when y=1 -> DLT=1 and u=<T; |
|
| 3345 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
| 3346 |
# when y=0 & T>t (u<T) -> DLT=NA/missing. |
|
| 3347 |
# Therefore, `yhat` is used instead of `y` for the likelihood f. (see `modelspecs`). |
|
| 3348 | ! |
L[i] <- pow(p[i], yhat[i]) * pow((1 - p[i]), (1 - yhat[i])) |
| 3349 | ||
| 3350 |
# Apply zero trick in JAGS. |
|
| 3351 | ! |
phi[i] <- -log(L[i]) + cadj |
| 3352 | ! |
zeros[i] ~ dpois(phi[i]) |
| 3353 |
} |
|
| 3354 |
} |
|
| 3355 | ||
| 3356 | 12x |
modelspecs <- function(nObs, u, Tmax, y, from_prior) {
|
| 3357 | 2x |
ms <- list(sigma2 = start@sigma2) |
| 3358 | 2x |
if (!from_prior) {
|
| 3359 |
# Calculate fractional contribution `yhat` |
|
| 3360 |
# based on the input data using the Kaplan-Meier method. |
|
| 3361 | 1x |
yhat <- if (nObs > 0) {
|
| 3362 | 1x |
km <- survival::survfit(survival::Surv(u, y) ~ 1) |
| 3363 | 1x |
s_tau <- tail(km$surv[km$time <= Tmax], 1) # Survival probability = S(Tmax). |
| 3364 | 1x |
ifelse( |
| 3365 | 1x |
u < Tmax & y == 0L, # Within the assessment window and so far no DLT. |
| 3366 | 1x |
yes = 1 - |
| 3367 | 1x |
s_tau / sapply(u, function(u_i) tail(km$surv[km$time <= u_i], 1)), |
| 3368 | 1x |
no = y |
| 3369 |
) |
|
| 3370 |
} else {
|
|
| 3371 | ! |
1L |
| 3372 |
} |
|
| 3373 | 1x |
ms <- c( |
| 3374 | 1x |
list( |
| 3375 | 1x |
skel_probs = start@skel_probs, |
| 3376 | 1x |
zeros = rep(0, nObs), |
| 3377 | 1x |
cadj = 1e10, |
| 3378 | 1x |
yhat = yhat |
| 3379 |
), |
|
| 3380 | 1x |
ms |
| 3381 |
) |
|
| 3382 |
} |
|
| 3383 | 2x |
ms |
| 3384 |
} |
|
| 3385 | ||
| 3386 | 12x |
.FractionalCRM( |
| 3387 | 12x |
start, |
| 3388 | 12x |
datamodel = datamodel, |
| 3389 | 12x |
modelspecs = modelspecs, |
| 3390 | 12x |
datanames = c("nObs", "xLevel")
|
| 3391 |
) |
|
| 3392 |
} |
|
| 3393 | ||
| 3394 |
## default constructor ---- |
|
| 3395 | ||
| 3396 |
#' @rdname FractionalCRM-class |
|
| 3397 |
#' @note Typically, end users will not use the `.DefaultTITELogisticLogNormal()` function. |
|
| 3398 |
#' @export |
|
| 3399 |
.DefaultFractionalCRM <- function() {
|
|
| 3400 | 7x |
FractionalCRM( |
| 3401 | 7x |
skel_probs = c(0.1, 0.2, 0.3, 0.4), |
| 3402 | 7x |
dose_grid = c(10, 30, 50, 100), |
| 3403 | 7x |
sigma2 = 2 |
| 3404 |
) |
|
| 3405 |
} |
|
| 3406 | ||
| 3407 |
## class ---- |
|
| 3408 | ||
| 3409 |
#' `LogisticLogNormalOrdinal` |
|
| 3410 |
#' |
|
| 3411 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3412 |
#' |
|
| 3413 |
#' [`LogisticLogNormalOrdinal`] is the class for a logistic lognormal CRM model |
|
| 3414 |
#' using an ordinal toxicity scale. |
|
| 3415 |
#' |
|
| 3416 |
#' @aliases LogisticLogNormalOrdinal |
|
| 3417 |
#' @export |
|
| 3418 |
.LogisticLogNormalOrdinal <- setClass( |
|
| 3419 |
Class = "LogisticLogNormalOrdinal", |
|
| 3420 |
contains = "ModelLogNormal", |
|
| 3421 |
validity = v_logisticlognormalordinal |
|
| 3422 |
) |
|
| 3423 | ||
| 3424 |
## constructor ---- |
|
| 3425 | ||
| 3426 |
#' @rdname LogisticLogNormalOrdinal-class |
|
| 3427 |
#' @inheritParams ModelLogNormal |
|
| 3428 |
#' @export |
|
| 3429 |
#' @example examples/Model-class-LogisticLogNormalOrdinal.R |
|
| 3430 |
LogisticLogNormalOrdinal <- function(mean, cov, ref_dose) {
|
|
| 3431 | 33x |
params <- ModelParamsNormal(mean, cov) |
| 3432 | 33x |
.LogisticLogNormalOrdinal( |
| 3433 | 33x |
params = params, |
| 3434 | 33x |
ref_dose = positive_number(ref_dose), |
| 3435 | 33x |
priormodel = function() {
|
| 3436 | ! |
alpha[1] ~ dnorm(mean[1], prec[1, 1]) |
| 3437 | ! |
for (i in 2:(k - 1)) {
|
| 3438 | ! |
alpha[i] ~ dnorm(mean[i], prec[i, i]) %_% T(, alpha[i - 1]) |
| 3439 |
} |
|
| 3440 | ! |
gamma ~ dnorm(mean[k], prec[k, k]) |
| 3441 | ! |
beta <- exp(gamma) |
| 3442 |
}, |
|
| 3443 | 33x |
datamodel = function() {
|
| 3444 | ! |
for (i in 1:nObs) {
|
| 3445 | ! |
xhat[i] <- log(x[i] / ref_dose) |
| 3446 | ! |
for (j in 1:(k - 1)) {
|
| 3447 | ! |
z[i, j] <- alpha[j] + beta * xhat[i] |
| 3448 | ! |
p[i, j] <- exp(z[i, j]) / (1 + exp(z[i, j])) |
| 3449 | ! |
tox[i, j] ~ dbern(p[i, j]) |
| 3450 |
} |
|
| 3451 |
} |
|
| 3452 |
}, |
|
| 3453 | 33x |
modelspecs = function(y, from_prior) {
|
| 3454 | 17x |
ms <- list( |
| 3455 | 17x |
mean = params@mean, |
| 3456 | 17x |
prec = params@prec, |
| 3457 | 17x |
k = length(mean), |
| 3458 | 17x |
tox = array(dim = c(length(y), length(mean) - 1)) |
| 3459 |
) |
|
| 3460 | 17x |
if (!from_prior) {
|
| 3461 | 15x |
for (i in seq_along(y)) {
|
| 3462 | 140x |
for (j in 1:(ms$k - 1)) {
|
| 3463 | 280x |
ms$tox[i, j] <- y[i] >= j |
| 3464 |
} |
|
| 3465 |
} |
|
| 3466 | 15x |
ms$ref_dose <- ref_dose |
| 3467 |
} |
|
| 3468 | 17x |
ms |
| 3469 |
}, |
|
| 3470 | 33x |
init = function() {
|
| 3471 | 17x |
list( |
| 3472 | 17x |
alpha = sapply(1:(length(mean) - 1), function(x) -(x + 1)), |
| 3473 | 17x |
gamma = 1 |
| 3474 |
) |
|
| 3475 |
}, |
|
| 3476 | 33x |
datanames = c("nObs", "y", "x"),
|
| 3477 |
# Need to provide JAGS column names here |
|
| 3478 | 33x |
sample = c(paste0("alpha[", 1:(length(mean) - 1), "]"), "beta")
|
| 3479 |
) |
|
| 3480 |
} |
|
| 3481 | ||
| 3482 |
## default constructor ---- |
|
| 3483 | ||
| 3484 |
#' @rdname LogisticLogNormalOrdinal-class |
|
| 3485 |
#' @note Typically, end users will not use the `.DefaultLogisticLogNormalOrdinal()` function. |
|
| 3486 |
#' @export |
|
| 3487 |
.DefaultLogisticLogNormalOrdinal <- function() {
|
|
| 3488 | 23x |
LogisticLogNormalOrdinal( |
| 3489 | 23x |
mean = c(-3, -4, 1), |
| 3490 | 23x |
cov = diag(c(3, 4, 1)), |
| 3491 | 23x |
ref_dose = 50 |
| 3492 |
) |
|
| 3493 |
} |
| 1 |
# assertions ---- |
|
| 2 | ||
| 3 |
#' Additional Assertions for `checkmate` |
|
| 4 |
#' |
|
| 5 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 6 |
#' |
|
| 7 |
#' We provide additional assertion functions that can be used together with |
|
| 8 |
#' the `checkmate` functions. These are described in individual help pages |
|
| 9 |
#' linked below. |
|
| 10 |
#' |
|
| 11 |
#' @return Depending on the function prefix. |
|
| 12 |
#' - `assert_` functions return the object invisibly if successful, and otherwise |
|
| 13 |
#' throw an error message. |
|
| 14 |
#' - `check_` functions return `TRUE` if successful, otherwise a string with the |
|
| 15 |
#' error message. |
|
| 16 |
#' - `test_` functions just return `TRUE` or `FALSE`. |
|
| 17 |
#' |
|
| 18 |
#' @seealso [assert_probabilities()], [assert_probability()], |
|
| 19 |
#' [assert_probability_range()], [assert_length()]. |
|
| 20 |
#' |
|
| 21 |
#' @name assertions |
|
| 22 |
NULL |
|
| 23 | ||
| 24 |
# check equality ---- |
|
| 25 | ||
| 26 |
#' Check if All Arguments Are Equal |
|
| 27 |
#' |
|
| 28 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 29 |
#' Elements of `...` must be numeric vectors or scalars. |
|
| 30 |
#' |
|
| 31 |
#' This function performs an element-by-element comparison of the first object |
|
| 32 |
#' provided in `...` with every other object in `...` and returns `TRUE` if all |
|
| 33 |
#' comparisons are equal within a given tolerance and `FALSE` otherwise. |
|
| 34 |
#' |
|
| 35 |
#' @param ... (`numeric`)\cr vectors to be compared. |
|
| 36 |
#' @param tol (`numeric`)\cr the maximum difference to be tolerated when |
|
| 37 |
#' judging equality. |
|
| 38 |
#' |
|
| 39 |
#' @note If there are any missing or infinite values in `...`, this function |
|
| 40 |
#' returns `FALSE`, regardless of the values of other elements in `...`. |
|
| 41 |
#' |
|
| 42 |
#' @note If elements in `...` are not all of the same length, `FALSE` is returned. |
|
| 43 |
#' |
|
| 44 |
#' @return `TRUE` if all element-by-element differences are less than `tolerance` |
|
| 45 |
#' in magnitude, `FALSE` otherwise. |
|
| 46 |
#' @seealso [`assertions`] for more details. |
|
| 47 |
#' |
|
| 48 |
#' @export |
|
| 49 |
#' @examples |
|
| 50 |
#' check_equal(1:2, 1:2) # TRUE |
|
| 51 |
#' check_equal(1:2, 2:3) # "Not all equal" |
|
| 52 |
#' check_equal(Inf, Inf) # "Not all equal" |
|
| 53 |
#' check_equal(0.01, 0.02) # "Not all equal" |
|
| 54 |
#' check_equal(0.01, 0.02, tol = 0.05) # TRUE |
|
| 55 |
#' check_equal(1, c(1, 1)) # "Not all equal" |
|
| 56 |
check_equal <- function(..., tol = sqrt(.Machine$double.eps)) {
|
|
| 57 | 14x |
dot_args <- list(...) |
| 58 | ||
| 59 | 14x |
sapply(dot_args, assert_numeric) |
| 60 | ||
| 61 | 14x |
tmp <- sapply(dot_args, length) |
| 62 | 14x |
if (min(tmp) != max(tmp)) {
|
| 63 | 2x |
return("Not all of same length")
|
| 64 |
} |
|
| 65 | 12x |
if (any(sapply(dot_args, is.na))) {
|
| 66 | 2x |
return("Some entries NA")
|
| 67 |
} |
|
| 68 | 10x |
if (any(sapply(dot_args, is.infinite))) {
|
| 69 | 2x |
return("Not all entries finite")
|
| 70 |
} |
|
| 71 | 8x |
if (!all(sapply(dot_args, test_numeric))) {
|
| 72 | ! |
return("Not all numeric")
|
| 73 |
} |
|
| 74 | ||
| 75 | 8x |
all_ok <- test_true( |
| 76 | 8x |
all( |
| 77 | 8x |
sapply( |
| 78 | 8x |
2:length(dot_args), |
| 79 | 8x |
function(z) abs(dot_args[[1]] - dot_args[[z]]) < tol |
| 80 |
) |
|
| 81 |
) |
|
| 82 |
) |
|
| 83 | 8x |
if (all_ok) {
|
| 84 | 4x |
return(TRUE) |
| 85 |
} |
|
| 86 | 4x |
"Not all equal" |
| 87 |
} |
|
| 88 | ||
| 89 |
#' Assert That All Arguments Are Equal |
|
| 90 |
#' |
|
| 91 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 92 |
#' Elements of `...` must be numeric vectors or scalars. |
|
| 93 |
#' |
|
| 94 |
#' This function performs an element-by-element comparison of the first object |
|
| 95 |
#' provided in `...` with every other object in `...` and throws an error if they |
|
| 96 |
#' are not. |
|
| 97 |
#' |
|
| 98 |
#' @param ... (`numeric`)\cr vectors to be compared |
|
| 99 |
#' @param tol (`numeric`)\cr the maximum difference to be tolerated when |
|
| 100 |
#' judging equality |
|
| 101 |
#' |
|
| 102 |
#' @note If there are any missing or infinite values in `...`, this function |
|
| 103 |
#' throws an error, regardless of the values of other elements in `...`. |
|
| 104 |
#' |
|
| 105 |
#' @note If elements in `...` are not all of the same length, an error is thrown. |
|
| 106 |
#' |
|
| 107 |
#' @return `list(...)`, invisibly. |
|
| 108 |
#' @seealso [`assertions`] for more details. |
|
| 109 |
#' @inheritParams checkmate::assert_numeric |
|
| 110 |
#' |
|
| 111 |
#' @export |
|
| 112 |
#' @rdname check_equal |
|
| 113 |
#' @examples |
|
| 114 |
#' assert_equal(1:2, 1:2) # no error |
|
| 115 |
#' assert_equal(0.01, 0.02, tol = 0.05) # no error |
|
| 116 |
# nolint start |
|
| 117 |
assert_equal <- function( |
|
| 118 |
..., |
|
| 119 |
tol = sqrt(.Machine$double.eps), |
|
| 120 |
.var.name = vname(x), |
|
| 121 |
add = NULL |
|
| 122 |
) {
|
|
| 123 |
# assert_equal <- makeAssertionFunction(check_equal) fails with error "Error |
|
| 124 |
# in `checkmate::makeAssertion(..., res, .var.name, add)`: unused argument |
|
| 125 |
# (add)", possibly because of the use of ... in check_equal. |
|
| 126 | 7x |
res <- check_equal(..., tol = tol) |
| 127 | 7x |
makeAssertion(list(...), res, .var.name, add) |
| 128 |
} |
|
| 129 |
# nolint end |
|
| 130 | ||
| 131 |
# assert_probabilities ---- |
|
| 132 | ||
| 133 |
#' Check if an argument is a probability vector |
|
| 134 |
#' |
|
| 135 |
#' @description `r lifecycle::badge("stable")`
|
|
| 136 |
#' |
|
| 137 |
#' Check if every element in a given numerical vector or matrix represents a |
|
| 138 |
#' probability, that is a number within (0, 1) interval, that can optionally be |
|
| 139 |
#' closed at any side. |
|
| 140 |
#' |
|
| 141 |
#' @note If there are any missing or non-finite values in `x`, this function |
|
| 142 |
#' returns `FALSE`, regardless of the values of other elements in `x`. |
|
| 143 |
#' |
|
| 144 |
#' @param x (`numeric`)\cr vector or matrix with numerical values to check. |
|
| 145 |
#' @param bounds_closed (`logical`)\cr should bounds be closed? This can be a |
|
| 146 |
#' scalar or vector of length two. If it is a scalar, then its value applies |
|
| 147 |
#' equally to lower bound \eqn{0} and upper bound \eqn{1}. If this is a vector
|
|
| 148 |
#' with two flags, the first flag corresponds to the lower bound \eqn{0}
|
|
| 149 |
#' only, and the second to the upper bound \eqn{1} only.
|
|
| 150 |
#' @inheritParams checkmate::check_numeric |
|
| 151 |
#' |
|
| 152 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
| 153 |
#' |
|
| 154 |
#' @seealso [`assertions`] for more details. |
|
| 155 |
#' |
|
| 156 |
#' @export |
|
| 157 |
#' @examples |
|
| 158 |
#' x <- c(0, 0.2, 0.1, 0.3, 1) |
|
| 159 |
#' check_probabilities(x) |
|
| 160 |
#' check_probabilities(x, bounds_closed = FALSE) |
|
| 161 |
#' check_probabilities(x, bounds_closed = c(FALSE, TRUE)) |
|
| 162 |
check_probabilities <- function( |
|
| 163 |
x, |
|
| 164 |
bounds_closed = TRUE, |
|
| 165 |
len = NULL, |
|
| 166 |
unique = FALSE, |
|
| 167 |
sorted = FALSE |
|
| 168 |
) {
|
|
| 169 | 10858x |
assert_numeric(x) |
| 170 | 10857x |
assert_logical(bounds_closed, min.len = 1, max.len = 2, any.missing = FALSE) |
| 171 | 10857x |
assert_number(len, null.ok = TRUE) |
| 172 | 10857x |
assert_flag(sorted) |
| 173 | ||
| 174 | 10857x |
result <- check_numeric( |
| 175 | 10857x |
x, |
| 176 | 10857x |
finite = TRUE, |
| 177 | 10857x |
any.missing = FALSE, |
| 178 | 10857x |
len = len, |
| 179 | 10857x |
unique = unique, |
| 180 | 10857x |
sorted = sorted |
| 181 |
) |
|
| 182 | ||
| 183 | 10857x |
if (isTRUE(result)) {
|
| 184 | 10778x |
in_bounds <- all(h_in_range( |
| 185 | 10778x |
x, |
| 186 | 10778x |
range = c(0L, 1L), |
| 187 | 10778x |
bounds_closed = bounds_closed |
| 188 |
)) |
|
| 189 | 10778x |
if (!in_bounds) {
|
| 190 | 142x |
result <- paste( |
| 191 | 142x |
"Probability must be within", |
| 192 | 142x |
ifelse(bounds_closed[1], "[0,", "(0,"), |
| 193 | 142x |
ifelse(tail(bounds_closed, 1), "1]", "1)"), |
| 194 | 142x |
"bounds but it is not" |
| 195 |
) |
|
| 196 |
} |
|
| 197 |
} |
|
| 198 | ||
| 199 | 10857x |
result |
| 200 |
} |
|
| 201 | ||
| 202 |
#' @rdname check_probabilities |
|
| 203 |
#' @inheritParams check_probabilities |
|
| 204 |
#' @export |
|
| 205 |
assert_probabilities <- makeAssertionFunction(check_probabilities) |
|
| 206 | ||
| 207 |
#' @rdname check_probabilities |
|
| 208 |
#' @inheritParams check_probabilities |
|
| 209 |
#' @export |
|
| 210 |
test_probabilities <- makeTestFunction(check_probabilities) |
|
| 211 | ||
| 212 |
#' @rdname check_probabilities |
|
| 213 |
#' @inheritParams check_probabilities |
|
| 214 |
#' @export |
|
| 215 |
expect_probabilities <- makeExpectationFunction(check_probabilities) |
|
| 216 | ||
| 217 |
# assert_probability ---- |
|
| 218 | ||
| 219 |
#' Check if an argument is a single probability value |
|
| 220 |
#' |
|
| 221 |
#' @description `r lifecycle::badge("stable")`
|
|
| 222 |
#' |
|
| 223 |
#' Check if a given value represents a probability, that is a number within |
|
| 224 |
#' (0, 1) interval, that can optionally be closed at any side. |
|
| 225 |
#' |
|
| 226 |
#' @param x (`number`)\cr a single value to check. |
|
| 227 |
#' @inheritParams check_probabilities |
|
| 228 |
#' |
|
| 229 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
| 230 |
#' |
|
| 231 |
#' @seealso [`assertions`] for more details. |
|
| 232 |
#' |
|
| 233 |
#' @export |
|
| 234 |
#' @examples |
|
| 235 |
#' check_probability(0.5) |
|
| 236 |
#' check_probability(0, bounds_closed = FALSE) |
|
| 237 |
#' check_probability(0, bounds_closed = c(FALSE, TRUE)) |
|
| 238 |
check_probability <- function(x, bounds_closed = TRUE) {
|
|
| 239 | 5749x |
check_probabilities(x = x, bounds_closed = bounds_closed, len = 1) |
| 240 |
} |
|
| 241 | ||
| 242 |
#' @rdname check_probability |
|
| 243 |
#' @inheritParams check_probability |
|
| 244 |
#' @export |
|
| 245 |
assert_probability <- makeAssertionFunction(check_probability) |
|
| 246 | ||
| 247 |
#' @rdname check_probability |
|
| 248 |
#' @inheritParams check_probability |
|
| 249 |
#' @export |
|
| 250 |
test_probability <- makeTestFunction(check_probability) |
|
| 251 | ||
| 252 |
#' @rdname check_probability |
|
| 253 |
#' @inheritParams check_probability |
|
| 254 |
#' @export |
|
| 255 |
expect_probability <- makeExpectationFunction(check_probability) |
|
| 256 | ||
| 257 |
# assert_probability_range ---- |
|
| 258 | ||
| 259 |
#' Check if an argument is a probability range |
|
| 260 |
#' |
|
| 261 |
#' @description `r lifecycle::badge("stable")`
|
|
| 262 |
#' |
|
| 263 |
#' Check if a given numerical interval represents a probability range, that is |
|
| 264 |
#' a sub-interval of (0, 1) interval, that can optionally be closed at any side. |
|
| 265 |
#' |
|
| 266 |
#' @param x (`number`)\cr an interval to check. |
|
| 267 |
#' @inheritParams check_probabilities |
|
| 268 |
#' |
|
| 269 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
| 270 |
#' |
|
| 271 |
#' @seealso [`assertions`] for more details. |
|
| 272 |
#' |
|
| 273 |
#' @export |
|
| 274 |
#' @examples |
|
| 275 |
#' x <- c(0, 0.2) |
|
| 276 |
#' check_probability_range(x) |
|
| 277 |
#' check_probability_range(rev(x)) |
|
| 278 |
#' check_probability_range(x, bounds_closed = FALSE) |
|
| 279 |
#' check_probability_range(x, bounds_closed = c(FALSE, TRUE)) |
|
| 280 |
check_probability_range <- function(x, bounds_closed = TRUE) {
|
|
| 281 | 835x |
check_probabilities( |
| 282 | 835x |
x = x, |
| 283 | 835x |
bounds_closed = bounds_closed, |
| 284 | 835x |
len = 2, |
| 285 | 835x |
sorted = TRUE |
| 286 |
) |
|
| 287 |
} |
|
| 288 | ||
| 289 |
#' @rdname check_probability_range |
|
| 290 |
#' @inheritParams check_probability_range |
|
| 291 |
#' @export |
|
| 292 |
assert_probability_range <- makeAssertionFunction(check_probability_range) |
|
| 293 | ||
| 294 |
#' @rdname check_probability_range |
|
| 295 |
#' @inheritParams check_probability_range |
|
| 296 |
#' @export |
|
| 297 |
test_probability_range <- makeTestFunction(check_probability_range) |
|
| 298 | ||
| 299 |
#' @rdname check_probability_range |
|
| 300 |
#' @inheritParams check_probability_range |
|
| 301 |
#' @export |
|
| 302 |
expect_probability_range <- makeExpectationFunction(check_probability_range) |
|
| 303 | ||
| 304 |
# assert_length ---- |
|
| 305 | ||
| 306 |
#' Check if vectors are of compatible lengths |
|
| 307 |
#' |
|
| 308 |
#' @description `r lifecycle::badge("stable")`
|
|
| 309 |
#' |
|
| 310 |
#' Two vectors are of compatible size if and only if: \cr |
|
| 311 |
#' 1. At least one vector has size 1 \cr |
|
| 312 |
#' 2. or both vectors are of the same size. \cr |
|
| 313 |
#' |
|
| 314 |
#' @param x (`any`)\cr the first vector, any object for which [length()] |
|
| 315 |
#' function is defined. |
|
| 316 |
#' @param len (`count`)\cr the length of the second vector. |
|
| 317 |
#' @inheritParams checkmate::check_numeric |
|
| 318 |
#' |
|
| 319 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
| 320 |
#' |
|
| 321 |
#' @seealso [`assertions`] for more details. |
|
| 322 |
#' |
|
| 323 |
#' @export |
|
| 324 |
#' @examples |
|
| 325 |
#' check_length(1:5, 1) |
|
| 326 |
#' check_length(1:5, 6) |
|
| 327 |
#' check_length(1:5, 5) |
|
| 328 |
#' check_length(10, 1) |
|
| 329 |
#' check_length(10, 9) |
|
| 330 |
check_length <- function(x, len) {
|
|
| 331 | 46470x |
x_len <- length(x) |
| 332 | 46470x |
assert_true(x_len >= 1L) |
| 333 | 46469x |
assert_count(len) |
| 334 | ||
| 335 | 46465x |
if (x_len == 1L || len == 1L || x_len == len) {
|
| 336 | 46436x |
TRUE |
| 337 |
} else {
|
|
| 338 | 29x |
paste( |
| 339 | 29x |
"x is of length", |
| 340 | 29x |
x_len, |
| 341 | 29x |
"which is not allowed; the allowed lengths are: 1 or", |
| 342 | 29x |
len, |
| 343 | 29x |
collapse = "" |
| 344 |
) |
|
| 345 |
} |
|
| 346 |
} |
|
| 347 | ||
| 348 |
#' @rdname check_length |
|
| 349 |
#' @inheritParams check_length |
|
| 350 |
#' @export |
|
| 351 |
assert_length <- makeAssertionFunction(check_length) |
|
| 352 | ||
| 353 |
#' @rdname check_length |
|
| 354 |
#' @inheritParams check_length |
|
| 355 |
#' @export |
|
| 356 |
test_length <- makeTestFunction(check_length) |
|
| 357 | ||
| 358 |
# assert_range ---- |
|
| 359 | ||
| 360 |
#' Check that an argument is a numerical range |
|
| 361 |
#' |
|
| 362 |
#' @description `r lifecycle::badge("stable")`
|
|
| 363 |
#' |
|
| 364 |
#' An argument `x` is a numerical range if and only if (all conditions must be met): |
|
| 365 |
#' 1. Is an object of type: `integer` or `double`. |
|
| 366 |
#' 2. Is a vector or length two such that the value of the first number is not |
|
| 367 |
#' less than the second number. Equalness is allowed if and only if `unique` flag |
|
| 368 |
#' is set to `TRUE`. |
|
| 369 |
#' 3. Lower bound of the interval is greater than or equal to `lower` and |
|
| 370 |
#' upper bound of the interval is less than or equal to `upper`. |
|
| 371 |
#' 4. It contains only finite (given that `finite` is `TRUE`) and non-missing values. |
|
| 372 |
#' |
|
| 373 |
#' @inheritParams checkmate::check_numeric |
|
| 374 |
#' |
|
| 375 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
| 376 |
#' |
|
| 377 |
#' @seealso [`assertions`] for more details. |
|
| 378 |
#' |
|
| 379 |
#' @export |
|
| 380 |
#' @examples |
|
| 381 |
#' check_range(c(1, 5)) |
|
| 382 |
#' check_range(c(-5, 1)) |
|
| 383 |
#' check_range(c(4, 1)) |
|
| 384 |
#' check_range(c(1, 1)) |
|
| 385 |
#' check_range(c(1, 1), unique = FALSE) |
|
| 386 |
#' check_range(1:3) |
|
| 387 |
check_range <- function( |
|
| 388 |
x, |
|
| 389 |
lower = -Inf, |
|
| 390 |
upper = Inf, |
|
| 391 |
finite = FALSE, |
|
| 392 |
unique = TRUE |
|
| 393 |
) {
|
|
| 394 | 108x |
assert_number(lower) |
| 395 | 107x |
assert_number(upper) |
| 396 | 106x |
assert_flag(finite) |
| 397 | 105x |
assert_flag(unique) |
| 398 | ||
| 399 | 104x |
result <- check_numeric( |
| 400 | 104x |
x, |
| 401 | 104x |
lower = lower, |
| 402 | 104x |
upper = upper, |
| 403 | 104x |
finite = finite, |
| 404 | 104x |
any.missing = FALSE, |
| 405 | 104x |
len = 2, |
| 406 | 104x |
unique = unique, |
| 407 | 104x |
sorted = TRUE |
| 408 |
) |
|
| 409 | ||
| 410 | 104x |
if (!isTRUE(result)) {
|
| 411 | 24x |
result <- paste("x must be a valid numerical range.", result)
|
| 412 |
} |
|
| 413 | 104x |
result |
| 414 |
} |
|
| 415 | ||
| 416 |
#' @rdname check_range |
|
| 417 |
#' @inheritParams check_range |
|
| 418 |
#' @export |
|
| 419 |
assert_range <- makeAssertionFunction(check_range) |
|
| 420 | ||
| 421 |
#' @rdname check_range |
|
| 422 |
#' @inheritParams check_range |
|
| 423 |
#' @export |
|
| 424 |
test_range <- makeTestFunction(check_range) |
|
| 425 | ||
| 426 |
#' @rdname check_range |
|
| 427 |
#' @inheritParams check_range |
|
| 428 |
#' @export |
|
| 429 |
expect_range <- makeExpectationFunction(check_range) |
|
| 430 | ||
| 431 |
# assert_format ---- |
|
| 432 | ||
| 433 |
#' Check that an argument is a valid format specification |
|
| 434 |
#' |
|
| 435 |
#' @description `r lifecycle::badge("stable")`
|
|
| 436 |
#' |
|
| 437 |
#' @inheritParams checkmate::check_numeric |
|
| 438 |
#' |
|
| 439 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
| 440 |
#' |
|
| 441 |
#' @seealso [`assertions`] for more details. |
|
| 442 |
#' |
|
| 443 |
#' @export |
|
| 444 |
#' @examples |
|
| 445 |
#' check_format("%5.2f")
|
|
| 446 |
check_format <- function(x, len = NULL, min.len = NULL, max.len = NULL) {
|
|
| 447 | 262x |
assert_number(len, lower = 1, null.ok = TRUE) |
| 448 | 262x |
assert_number(min.len, lower = 1, null.ok = TRUE) |
| 449 | 262x |
assert_number(max.len, lower = 1, null.ok = TRUE) |
| 450 | ||
| 451 | 262x |
result <- check_character( |
| 452 | 262x |
x, |
| 453 | 262x |
len = len, |
| 454 | 262x |
min.len = min.len, |
| 455 | 262x |
max.len = max.len, |
| 456 | 262x |
any.missing = FALSE, |
| 457 |
# https://stackoverflow.com/questions/446285/validate-sprintf-format-from-input-field-with-regex |
|
| 458 | 262x |
pattern = "%(?:\\d+\\$)?[+-]?(?:[ 0]|'.{1})?-?\\d*(?:\\.\\d+)?[bcdeEufFgGosxX]",
|
| 459 |
) |
|
| 460 | ||
| 461 | 262x |
if (!isTRUE(result)) {
|
| 462 | ! |
result <- paste("x must be a valid format specifier.", result)
|
| 463 |
} |
|
| 464 | 262x |
result |
| 465 |
} |
|
| 466 | ||
| 467 |
#' @rdname check_format |
|
| 468 |
#' @inheritParams check_format |
|
| 469 |
#' @export |
|
| 470 |
assert_format <- makeAssertionFunction(check_format) |
|
| 471 | ||
| 472 |
#' @rdname check_format |
|
| 473 |
#' @inheritParams check_format |
|
| 474 |
#' @export |
|
| 475 |
test_format <- makeTestFunction(check_format) |
|
| 476 | ||
| 477 |
#' @rdname check_format |
|
| 478 |
#' @inheritParams check_format |
|
| 479 |
#' @export |
|
| 480 |
expect_format <- makeExpectationFunction(check_format) |
| 1 |
##' @include helpers.R |
|
| 2 |
##' @include Model-class.R |
|
| 3 |
NULL |
|
| 4 | ||
| 5 |
# Helper functions ---- |
|
| 6 | ||
| 7 |
#' Get Starting Values for Quantiles Optimization |
|
| 8 |
#' |
|
| 9 |
#' @param parstart (`numeric` or `NULL`)\cr starting parameter values. |
|
| 10 |
#' @param median (`numeric`)\cr median values. |
|
| 11 |
#' @param dosegrid (`numeric`)\cr dose grid. |
|
| 12 |
#' @param refDose (`number`)\cr reference dose. |
|
| 13 |
#' @param logNormal (`flag`)\cr use log-normal prior? |
|
| 14 |
#' |
|
| 15 |
#' @return Numeric vector of starting values. |
|
| 16 |
#' @keywords internal |
|
| 17 |
h_get_quantiles_start_values <- function( |
|
| 18 |
parstart, |
|
| 19 |
median, |
|
| 20 |
dosegrid, |
|
| 21 |
refDose, |
|
| 22 |
logNormal |
|
| 23 |
) {
|
|
| 24 | 5x |
if (is.null(parstart)) {
|
| 25 |
# Find approximate means for alpha and slope beta from fitting logistic model to medians. |
|
| 26 | 3x |
startAlphaBeta <- coef(lm(I(logit(median)) ~ I(log(dosegrid / refDose)))) |
| 27 | ||
| 28 | 3x |
c( |
| 29 | 3x |
meanAlpha = unname(startAlphaBeta[1]), |
| 30 | 3x |
meanBeta = unname( |
| 31 | 3x |
if (logNormal) log(startAlphaBeta[2]) else startAlphaBeta[2] |
| 32 |
), |
|
| 33 | 3x |
sdAlpha = 1, |
| 34 | 3x |
sdBeta = 1, |
| 35 | 3x |
correlation = 0 |
| 36 |
) |
|
| 37 |
} else {
|
|
| 38 | 2x |
parstart |
| 39 |
} |
|
| 40 |
} |
|
| 41 | ||
| 42 |
#' Target Function for Quantiles Optimization |
|
| 43 |
#' |
|
| 44 |
#' @param dosegrid (`numeric`)\cr dose grid. |
|
| 45 |
#' @param refDose (`number`)\cr reference dose. |
|
| 46 |
#' @param lower (`numeric`)\cr lower quantiles. |
|
| 47 |
#' @param median (`numeric`)\cr median quantiles. |
|
| 48 |
#' @param upper (`numeric`)\cr upper quantiles. |
|
| 49 |
#' @param level (`number`)\cr credible level. |
|
| 50 |
#' @param logNormal (`flag`)\cr use log-normal prior? |
|
| 51 |
#' @param seed (`count`)\cr random seed. |
|
| 52 |
#' |
|
| 53 |
#' @return Function that computes target value for optimization. |
|
| 54 |
#' @keywords internal |
|
| 55 |
h_quantiles_target_function <- function( |
|
| 56 |
dosegrid, |
|
| 57 |
refDose, |
|
| 58 |
lower, |
|
| 59 |
median, |
|
| 60 |
upper, |
|
| 61 |
level, |
|
| 62 |
logNormal, |
|
| 63 |
seed |
|
| 64 |
) {
|
|
| 65 | 3x |
function(param) {
|
| 66 |
# Form the mean vector and covariance matrix |
|
| 67 | 5x |
mean <- param[1:2] |
| 68 | 5x |
cov <- matrix( |
| 69 | 5x |
c( |
| 70 | 5x |
param[3]^2, |
| 71 | 5x |
prod(param[3:5]), |
| 72 | 5x |
prod(param[3:5]), |
| 73 | 5x |
param[4]^2 |
| 74 |
), |
|
| 75 | 5x |
nrow = 2L, |
| 76 | 5x |
ncol = 2L |
| 77 |
) |
|
| 78 | ||
| 79 |
# Simulate from the corresponding normal distribution |
|
| 80 | 5x |
set.seed(seed) |
| 81 | 5x |
normalSamples <- mvtnorm::rmvnorm( |
| 82 | 5x |
n = 1e4L, |
| 83 | 5x |
mean = mean, |
| 84 | 5x |
sigma = cov |
| 85 |
) |
|
| 86 | ||
| 87 |
# Extract separate coefficients |
|
| 88 | 5x |
alphaSamples <- normalSamples[, 1L] |
| 89 | 5x |
betaSamples <- if (logNormal) {
|
| 90 | 2x |
exp(normalSamples[, 2L]) |
| 91 |
} else {
|
|
| 92 | 3x |
normalSamples[, 2L] |
| 93 |
} |
|
| 94 | ||
| 95 |
# Compute resulting quantiles |
|
| 96 | 5x |
quants <- matrix( |
| 97 | 5x |
nrow = length(dosegrid), |
| 98 | 5x |
ncol = 3L |
| 99 |
) |
|
| 100 | 5x |
colnames(quants) <- c("lower", "median", "upper")
|
| 101 | ||
| 102 |
# Process each dose |
|
| 103 | 5x |
for (i in seq_along(dosegrid)) {
|
| 104 |
# Create samples of the probability |
|
| 105 | 23x |
probSamples <- plogis( |
| 106 | 23x |
alphaSamples + betaSamples * log(dosegrid[i] / refDose) |
| 107 |
) |
|
| 108 | ||
| 109 |
# Compute lower, median and upper quantile |
|
| 110 | 23x |
quants[i, ] <- quantile( |
| 111 | 23x |
probSamples, |
| 112 | 23x |
probs = c((1 - level) / 2, 0.5, (1 + level) / 2) |
| 113 |
) |
|
| 114 |
} |
|
| 115 | ||
| 116 |
# Compute the target value |
|
| 117 | 5x |
ret <- max(abs(quants - c(lower, median, upper))) |
| 118 | 5x |
structure(ret, mean = mean, cov = cov, quantiles = quants) |
| 119 |
} |
|
| 120 |
} |
|
| 121 | ||
| 122 |
# Quantiles2LogisticNormal ---- |
|
| 123 | ||
| 124 |
#' Convert Prior Quantiles to Logistic (Log) Normal Model |
|
| 125 |
#' |
|
| 126 |
#' @description `r lifecycle::badge("stable")`
|
|
| 127 |
#' |
|
| 128 |
#' This function uses generalized simulated annealing to optimize |
|
| 129 |
#' a [`LogisticNormal`] model to be as close as possible |
|
| 130 |
#' to the given prior quantiles. |
|
| 131 |
#' |
|
| 132 |
#' @param dosegrid (`numeric`)\cr the dose grid. |
|
| 133 |
#' @param refDose (`number`)\cr the reference dose. |
|
| 134 |
#' @param lower (`numeric`)\cr the lower quantiles. |
|
| 135 |
#' @param median (`numeric`)\cr the medians. |
|
| 136 |
#' @param upper (`numeric`)\cr the upper quantiles. |
|
| 137 |
#' @param level (`number`)\cr the credible level of the (lower, upper) intervals. |
|
| 138 |
#' Default is 0.95. |
|
| 139 |
#' @param logNormal (`flag`)\cr use the log-normal prior? If `FALSE` (default), |
|
| 140 |
#' the normal prior for the logistic regression coefficients is used. |
|
| 141 |
#' @param parstart (`numeric` or `NULL`)\cr starting values for the parameters. |
|
| 142 |
#' By default, these are determined from the medians supplied. |
|
| 143 |
#' @param parlower (`numeric`)\cr lower bounds on the parameters (intercept alpha |
|
| 144 |
#' and the slope beta, the corresponding standard deviations and the correlation). |
|
| 145 |
#' @param parupper (`numeric`)\cr upper bounds on the parameters. |
|
| 146 |
#' @param seed (`count`)\cr seed for random number generation. |
|
| 147 |
#' @param verbose (`flag`)\cr should the function be verbose? |
|
| 148 |
#' @param control (`list`)\cr additional options for the optimisation routine, |
|
| 149 |
#' see [GenSA::GenSA()] for more details. |
|
| 150 |
#' |
|
| 151 |
#' @return A list with the best approximating `model` |
|
| 152 |
#' ([`LogisticNormal`] or [`LogisticLogNormal`]), the resulting `quantiles`, |
|
| 153 |
#' the `required` quantiles and the `distance` to the required quantiles, |
|
| 154 |
#' as well as the final `parameters` (which could be used for running the |
|
| 155 |
#' algorithm a second time). |
|
| 156 |
#' |
|
| 157 |
#' @importFrom GenSA GenSA |
|
| 158 |
#' @importFrom mvtnorm rmvnorm |
|
| 159 |
#' @export |
|
| 160 |
Quantiles2LogisticNormal <- function( |
|
| 161 |
dosegrid, |
|
| 162 |
refDose, |
|
| 163 |
lower, |
|
| 164 |
median, |
|
| 165 |
upper, |
|
| 166 |
level = 0.95, |
|
| 167 |
logNormal = FALSE, |
|
| 168 |
parstart = NULL, |
|
| 169 |
parlower = c(-10, -10, 0, 0, -0.95), |
|
| 170 |
parupper = c(10, 10, 10, 10, 0.95), |
|
| 171 |
seed = 12345, |
|
| 172 |
verbose = TRUE, |
|
| 173 |
control = list( |
|
| 174 |
threshold.stop = 0.01, |
|
| 175 |
maxit = 50000, |
|
| 176 |
temperature = 50000, |
|
| 177 |
max.time = 600 |
|
| 178 |
) |
|
| 179 |
) {
|
|
| 180 |
# Argument validation |
|
| 181 | 2x |
assert_numeric( |
| 182 | 2x |
dosegrid, |
| 183 | 2x |
min.len = 1, |
| 184 | 2x |
any.missing = FALSE, |
| 185 | 2x |
sorted = TRUE, |
| 186 | 2x |
unique = TRUE |
| 187 |
) |
|
| 188 | 2x |
assert_number(refDose, finite = TRUE) |
| 189 | 2x |
assert_numeric(lower, len = length(dosegrid), any.missing = FALSE) |
| 190 | 2x |
assert_numeric( |
| 191 | 2x |
median, |
| 192 | 2x |
len = length(dosegrid), |
| 193 | 2x |
any.missing = FALSE, |
| 194 | 2x |
sorted = TRUE |
| 195 |
) |
|
| 196 | 2x |
assert_numeric(upper, len = length(dosegrid), any.missing = FALSE) |
| 197 | 2x |
assert_probability(level, bounds_closed = FALSE) |
| 198 | 2x |
assert_flag(logNormal) |
| 199 | 2x |
assert_numeric(parstart, len = 5, null.ok = TRUE) |
| 200 | 2x |
assert_numeric(parlower, len = 5, any.missing = FALSE) |
| 201 | 2x |
assert_numeric(parupper, len = 5, any.missing = FALSE) |
| 202 | 2x |
assert_count(seed, positive = TRUE) |
| 203 | 2x |
assert_flag(verbose) |
| 204 | 2x |
assert_list(control) |
| 205 | ||
| 206 |
# Additional validation |
|
| 207 | 2x |
assert_true(all(lower < median)) |
| 208 | 2x |
assert_true(all(median < upper)) |
| 209 | 2x |
assert_true(all(parlower < parupper)) |
| 210 | 2x |
if (!is.null(parstart)) {
|
| 211 | 1x |
assert_true(all(parlower < parstart)) |
| 212 | 1x |
assert_true(all(parstart < parupper)) |
| 213 |
} |
|
| 214 | ||
| 215 | 2x |
nDoses <- length(dosegrid) |
| 216 | 2x |
control$verbose <- verbose |
| 217 | ||
| 218 | 2x |
startValues <- h_get_quantiles_start_values( |
| 219 | 2x |
parstart = parstart, |
| 220 | 2x |
median = median, |
| 221 | 2x |
dosegrid = dosegrid, |
| 222 | 2x |
refDose = refDose, |
| 223 | 2x |
logNormal = logNormal |
| 224 |
) |
|
| 225 | ||
| 226 | 2x |
target <- h_quantiles_target_function( |
| 227 | 2x |
dosegrid = dosegrid, |
| 228 | 2x |
refDose = refDose, |
| 229 | 2x |
lower = lower, |
| 230 | 2x |
median = median, |
| 231 | 2x |
upper = upper, |
| 232 | 2x |
level = level, |
| 233 | 2x |
logNormal = logNormal, |
| 234 | 2x |
seed = seed |
| 235 |
) |
|
| 236 | ||
| 237 | 2x |
set.seed(seed) |
| 238 |
# Optimize the target function |
|
| 239 | 2x |
genSAres <- GenSA::GenSA( |
| 240 | 2x |
par = startValues, |
| 241 | 2x |
fn = target, |
| 242 | 2x |
lower = parlower, |
| 243 | 2x |
upper = parupper, |
| 244 | 2x |
control = control |
| 245 |
) |
|
| 246 | 2x |
distance <- genSAres$value |
| 247 | 2x |
pars <- genSAres$par |
| 248 | 2x |
targetRes <- target(pars) |
| 249 | ||
| 250 |
# Construct the model |
|
| 251 | 2x |
model <- if (logNormal) {
|
| 252 | 1x |
LogisticLogNormal( |
| 253 | 1x |
mean = attr(targetRes, "mean"), |
| 254 | 1x |
cov = attr(targetRes, "cov"), |
| 255 | 1x |
ref_dose = refDose |
| 256 |
) |
|
| 257 |
} else {
|
|
| 258 | 1x |
LogisticNormal( |
| 259 | 1x |
mean = attr(targetRes, "mean"), |
| 260 | 1x |
cov = attr(targetRes, "cov"), |
| 261 | 1x |
ref_dose = refDose |
| 262 |
) |
|
| 263 |
} |
|
| 264 | ||
| 265 | 2x |
list( |
| 266 | 2x |
model = model, |
| 267 | 2x |
parameters = pars, |
| 268 | 2x |
quantiles = attr(targetRes, "quantiles"), |
| 269 | 2x |
required = cbind(lower, median, upper), |
| 270 | 2x |
distance = distance |
| 271 |
) |
|
| 272 |
} |
|
| 273 | ||
| 274 |
#' Helper for Minimal Informative Unimodal Beta Distribution |
|
| 275 |
#' |
|
| 276 |
#' As defined in Neuenschwander et al (2008), this function computes the |
|
| 277 |
#' parameters of the minimal informative unimodal beta distribution, given the |
|
| 278 |
#' request that the p-quantile should be q, i.e. `X ~ Be(a, b)` with |
|
| 279 |
#' `Pr(X <= q) = p`. |
|
| 280 |
#' |
|
| 281 |
#' @param p (`number`)\cr the probability. |
|
| 282 |
#' @param q (`number`)\cr the quantile. |
|
| 283 |
#' @return A list with the two resulting beta parameters `a` and `b`. |
|
| 284 |
#' |
|
| 285 |
#' @keywords internal |
|
| 286 |
h_get_min_inf_beta <- function(p, q) {
|
|
| 287 | 3x |
assert_probability(p, bounds_closed = FALSE) |
| 288 | 3x |
assert_probability(q, bounds_closed = FALSE) |
| 289 | ||
| 290 | 3x |
if (q > p) {
|
| 291 | 1x |
list( |
| 292 | 1x |
a = log(p) / log(q), |
| 293 | 1x |
b = 1 |
| 294 |
) |
|
| 295 |
} else {
|
|
| 296 | 2x |
list( |
| 297 | 2x |
a = 1, |
| 298 | 2x |
b = log(1 - p) / log(1 - q) |
| 299 |
) |
|
| 300 |
} |
|
| 301 |
} |
|
| 302 | ||
| 303 |
# MinimalInformative ---- |
|
| 304 | ||
| 305 |
#' Construct a Minimally Informative Prior |
|
| 306 |
#' |
|
| 307 |
#' @description `r lifecycle::badge("stable")`
|
|
| 308 |
#' |
|
| 309 |
#' This function constructs a minimally informative prior, which is captured in |
|
| 310 |
#' a [`LogisticNormal`] (or [`LogisticLogNormal`]) object. |
|
| 311 |
#' |
|
| 312 |
#' Based on the proposal by Neuenschwander et al (2008, Statistics in |
|
| 313 |
#' Medicine), a minimally informative prior distribution is constructed. The |
|
| 314 |
#' required key input is the minimum (\eqn{d_{1}} in the notation of the
|
|
| 315 |
#' Appendix A.1 of that paper) and the maximum value (\eqn{d_{J}}) of the dose
|
|
| 316 |
#' grid supplied to this function. Then `threshmin` is the probability |
|
| 317 |
#' threshold \eqn{q_{1}}, such that any probability of DLT larger than
|
|
| 318 |
#' \eqn{q_{1}} has only 5% probability. Therefore \eqn{q_{1}} is the 95%
|
|
| 319 |
#' quantile of the beta distribution and hence \eqn{p_{1} = 0.95}. Likewise,
|
|
| 320 |
#' `threshmax` is the probability threshold \eqn{q_{J}}, such that any
|
|
| 321 |
#' probability of DLT smaller than \eqn{q_{J}} has only 5% probability
|
|
| 322 |
#' (\eqn{p_{J} = 0.05}). The probabilities \eqn{1 - p_{1}} and \eqn{p_{J}} can be
|
|
| 323 |
#' controlled with the arguments `probmin` and `probmax`, respectively. |
|
| 324 |
#' Subsequently, for all doses supplied in the |
|
| 325 |
#' `dosegrid` argument, beta distributions are set up from the assumption |
|
| 326 |
#' that the prior medians are linear in log-dose on the logit scale, and |
|
| 327 |
#' [Quantiles2LogisticNormal()] is used to transform the resulting |
|
| 328 |
#' quantiles into an approximating [`LogisticNormal`] (or |
|
| 329 |
#' [`LogisticLogNormal`]) model. Note that the reference dose |
|
| 330 |
#' is not required for these computations. |
|
| 331 |
#' |
|
| 332 |
#' @param dosegrid (`numeric`)\cr the dose grid. |
|
| 333 |
#' @param refDose (`number`)\cr the reference dose. |
|
| 334 |
#' @param threshmin (`number`)\cr any toxicity probability above this threshold |
|
| 335 |
#' would be very unlikely (see `probmin`) at the minimum dose. |
|
| 336 |
#' @param threshmax (`number`)\cr any toxicity probability below this threshold |
|
| 337 |
#' would be very unlikely (see `probmax`) at the maximum dose. |
|
| 338 |
#' @param probmin (`number`)\cr the prior probability of exceeding `threshmin` |
|
| 339 |
#' at the minimum dose. |
|
| 340 |
#' @param probmax (`number`)\cr the prior probability of being below `threshmax` |
|
| 341 |
#' at the maximum dose. |
|
| 342 |
#' @param ... additional arguments for computations, see |
|
| 343 |
#' [Quantiles2LogisticNormal()], e.g. `refDose` and |
|
| 344 |
#' `logNormal=TRUE` to obtain a minimal informative log normal prior. |
|
| 345 |
#' |
|
| 346 |
#' @return See [Quantiles2LogisticNormal()]. |
|
| 347 |
#' |
|
| 348 |
#' @example examples/MinimalInformative.R |
|
| 349 |
#' @export |
|
| 350 |
MinimalInformative <- function( |
|
| 351 |
dosegrid, |
|
| 352 |
refDose, |
|
| 353 |
threshmin = 0.2, |
|
| 354 |
threshmax = 0.3, |
|
| 355 |
probmin = 0.05, |
|
| 356 |
probmax = 0.05, |
|
| 357 |
... |
|
| 358 |
) {
|
|
| 359 |
# Argument validation |
|
| 360 | ! |
assert_numeric( |
| 361 | ! |
dosegrid, |
| 362 | ! |
min.len = 1, |
| 363 | ! |
any.missing = FALSE, |
| 364 | ! |
sorted = TRUE, |
| 365 | ! |
unique = TRUE |
| 366 |
) |
|
| 367 | ! |
assert_number(refDose, finite = TRUE) |
| 368 | ! |
assert_probability(threshmin, bounds_closed = FALSE) |
| 369 | ! |
assert_probability(threshmax, bounds_closed = FALSE) |
| 370 | ! |
assert_probability(probmin, bounds_closed = FALSE) |
| 371 | ! |
assert_probability(probmax, bounds_closed = FALSE) |
| 372 | ||
| 373 | ! |
nDoses <- length(dosegrid) |
| 374 | ! |
xmin <- dosegrid[1] |
| 375 | ! |
xmax <- dosegrid[nDoses] |
| 376 | ||
| 377 |
# Derive the beta distributions at the lowest and highest dose |
|
| 378 | ! |
betaAtMin <- h_get_min_inf_beta( |
| 379 | ! |
q = threshmin, |
| 380 | ! |
p = 1 - probmin |
| 381 |
) |
|
| 382 | ! |
betaAtMax <- h_get_min_inf_beta( |
| 383 | ! |
q = threshmax, |
| 384 | ! |
p = probmax |
| 385 |
) |
|
| 386 | ||
| 387 |
# Get the medians of those beta distributions |
|
| 388 | ! |
medianMin <- with(betaAtMin, qbeta(p = 0.5, a, b)) |
| 389 | ! |
medianMax <- with(betaAtMax, qbeta(p = 0.5, a, b)) |
| 390 | ||
| 391 |
# Determine the medians of all beta distributions |
|
| 392 | ! |
beta <- (logit(medianMax) - logit(medianMin)) / (log(xmax) - log(xmin)) |
| 393 | ! |
alpha <- logit(medianMax) - beta * log(xmax / refDose) |
| 394 | ! |
medianDosegrid <- plogis(alpha + beta * log(dosegrid / refDose)) |
| 395 | ||
| 396 |
# Calculate 95% credible interval bounds (lower and upper) for all doses |
|
| 397 | ! |
lower <- upper <- dosegrid |
| 398 | ! |
for (i in seq_along(dosegrid)) {
|
| 399 |
# Get minimal informative beta distribution |
|
| 400 | ! |
thisMinBeta <- h_get_min_inf_beta( |
| 401 | ! |
p = 0.5, |
| 402 | ! |
q = medianDosegrid[i] |
| 403 |
) |
|
| 404 | ||
| 405 |
# Derive required quantiles |
|
| 406 | ! |
lower[i] <- with(thisMinBeta, qbeta(p = 0.025, a, b)) |
| 407 | ! |
upper[i] <- with(thisMinBeta, qbeta(p = 0.975, a, b)) |
| 408 |
} |
|
| 409 | ||
| 410 |
# Transform quantiles to LogisticNormal model |
|
| 411 | ! |
Quantiles2LogisticNormal( |
| 412 | ! |
dosegrid = dosegrid, |
| 413 | ! |
refDose = refDose, |
| 414 | ! |
lower = lower, |
| 415 | ! |
median = medianDosegrid, |
| 416 | ! |
upper = upper, |
| 417 | ! |
level = 0.95, |
| 418 |
... |
|
| 419 |
) |
|
| 420 |
} |
|
| 421 | ||
| 422 |
# nolint end |
| 1 |
#' Helper Function to Blind Plot Data |
|
| 2 |
#' |
|
| 3 |
#' @param df (`GeneralData`)\cr The data to be blinded |
|
| 4 |
#' @param blind (`flag`)\cr Should the data be blinded? |
|
| 5 |
#' @param has_placebo (`flag`)\cr Does the data contain a placebo dose? |
|
| 6 |
#' @param pbo_dose (`positive_number`)\cr The dose to be taken as placebo. |
|
| 7 |
#' Ignored if `has_placebo` is `FALSE` |
|
| 8 |
#' @returns The blinded data |
|
| 9 |
h_blind_plot_data <- function(df, blind, has_placebo, pbo_dose) {
|
|
| 10 | 19x |
if (blind) {
|
| 11 |
# This is to blind the data. |
|
| 12 |
# For each cohort, all DLTs are assigned to the first subjects in the cohort. |
|
| 13 |
# In addition, the placebo (if any) is set to the active dose level for that |
|
| 14 |
# cohort. |
|
| 15 |
# Notice: dapply reorders records of df according to the lexicographic order |
|
| 16 |
# of cohort. |
|
| 17 | 9x |
df <- dapply(df, f = ~cohort, FUN = function(coh) {
|
| 18 | 30x |
coh$toxicity <- sort(coh$toxicity, decreasing = TRUE) |
| 19 | 30x |
coh$dose <- max(coh$dose) |
| 20 | 30x |
coh |
| 21 |
}) |
|
| 22 | 10x |
} else if (has_placebo) {
|
| 23 |
# Placebo will be plotted at y = 0 level. |
|
| 24 | 8x |
df$dose[df$dose == pbo_dose] <- 0 |
| 25 |
} |
|
| 26 | 19x |
df |
| 27 |
} |
|
| 28 | ||
| 29 |
# h_plot_data_df ---- |
|
| 30 | ||
| 31 |
## generic ---- |
|
| 32 | ||
| 33 |
#' Helper Function for the Plot Method of subclasses of [`GeneralData`] |
|
| 34 |
#' |
|
| 35 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 36 |
#' |
|
| 37 |
#' A method that transforms [`GeneralData`] objects into a `tibble` suitable for |
|
| 38 |
#' plotting with `ggplot2` methods |
|
| 39 |
#' |
|
| 40 |
#' @param data (`GeneralData`)\cr object from which data is extracted and converted |
|
| 41 |
#' into a data frame. |
|
| 42 |
#' @param ... further arguments passed to class-specific methods. |
|
| 43 |
#' @return `data.frame` containing columns for patient, cohort, dose and toxicity grade |
|
| 44 |
#' @aliases h_plot_data_df |
|
| 45 |
#' |
|
| 46 |
setGeneric( |
|
| 47 |
name = "h_plot_data_df", |
|
| 48 |
def = function(data, ...) standardGeneric("h_plot_data_df"),
|
|
| 49 |
valueClass = "data.frame" |
|
| 50 |
) |
|
| 51 | ||
| 52 |
# Data ---- |
|
| 53 | ||
| 54 |
#' Helper Function for the Plot Method of [`Data`] |
|
| 55 |
#' |
|
| 56 |
#' @param data (`Data`)\cr object from which data is extracted and converted |
|
| 57 |
#' into a data frame. |
|
| 58 |
#' @param blind (`flag`)\cr should data be blinded? |
|
| 59 |
#' If `TRUE`, then for each cohort, all DLTs are assigned to the first |
|
| 60 |
#' subjects in the cohort. In addition, the placebo (if any) is set to the |
|
| 61 |
#' active dose level for that cohort. |
|
| 62 |
#' @param legend (`flag`)\cr Display the legend for the toxicity categories |
|
| 63 |
#' @param ... further arguments passed to `data.frame` constructor. |
|
| 64 |
#' It can be e.g. an extra `column_name = value` pair based on a slot |
|
| 65 |
#' from `x` (which in this case might be a subclass of `Data`) |
|
| 66 |
#' which does not appear in `Data`. |
|
| 67 |
#' @return A `data.frame` object with columns patient, ID, cohort, dose and |
|
| 68 |
#' toxicity. |
|
| 69 |
#' @describeIn h_plot_data_df method for [`Data`]. |
|
| 70 |
setMethod( |
|
| 71 |
f = "h_plot_data_df", |
|
| 72 |
signature = signature(data = "Data"), |
|
| 73 |
definition = function(data, blind = FALSE, legend = TRUE, ...) {
|
|
| 74 | 17x |
df <- data.frame( |
| 75 | 17x |
patient = seq_along(data@x), |
| 76 | 17x |
ID = paste(" ", data@ID),
|
| 77 | 17x |
cohort = data@cohort, |
| 78 | 17x |
dose = data@x, |
| 79 | 17x |
toxicity = ifelse(data@y == 1, "Yes", "No"), |
| 80 |
... |
|
| 81 |
) |
|
| 82 | 17x |
df <- h_blind_plot_data(df, blind, data@placebo, data@doseGrid[1]) |
| 83 | 17x |
df |
| 84 |
} |
|
| 85 |
) |
|
| 86 | ||
| 87 |
# DataOrdinal ---- |
|
| 88 | ||
| 89 |
#' Helper Function for the Plot Method of [`DataOrdinal`] |
|
| 90 |
#' |
|
| 91 |
#' @describeIn h_plot_data_df Class specific method for [`DataOrdinal`] |
|
| 92 |
setMethod( |
|
| 93 |
f = "h_plot_data_df", |
|
| 94 |
signature = signature(data = "DataOrdinal"), |
|
| 95 |
definition = function(data, blind = FALSE, legend = TRUE, ...) {
|
|
| 96 | 2x |
df <- data.frame( |
| 97 | 2x |
patient = seq_along(data@x), |
| 98 | 2x |
ID = paste(" ", data@ID),
|
| 99 | 2x |
cohort = data@cohort, |
| 100 | 2x |
dose = data@x, |
| 101 | 2x |
toxicity = names(data@yCategories)[1 + data@y], |
| 102 |
... |
|
| 103 |
) |
|
| 104 | 2x |
df <- h_blind_plot_data(df, blind, data@placebo, data@doseGrid[1]) |
| 105 | 2x |
df |
| 106 |
} |
|
| 107 |
) |
|
| 108 | ||
| 109 | ||
| 110 |
# h_plot_data_dataordinal |
|
| 111 | ||
| 112 |
## Data ---- |
|
| 113 | ||
| 114 |
#' Helper Function for the Plot Method of the Data and DataOrdinal Classes |
|
| 115 |
#' |
|
| 116 |
#' @description `r lifecycle::badge("stable")`
|
|
| 117 |
#' |
|
| 118 |
#' A method that creates a plot for [`Data`] and [`DataOrdinal`] objects. |
|
| 119 |
#' |
|
| 120 |
#' @note The default values of `tox_shapes` and `tox_labels` result in DLTs |
|
| 121 |
#' being displayed as red triangles and other responses as black circles. |
|
| 122 |
#' @return The [`ggplot2`] object. |
|
| 123 |
#' |
|
| 124 |
#' @rdname plot-Data |
|
| 125 |
h_plot_data_dataordinal <- function( |
|
| 126 |
x, |
|
| 127 |
blind = FALSE, |
|
| 128 |
legend = TRUE, |
|
| 129 |
tox_labels = c(Yes = "red", No = "black"), |
|
| 130 |
tox_shapes = c(Yes = 17L, No = 16L), |
|
| 131 |
... |
|
| 132 |
) {
|
|
| 133 | 10x |
assert_flag(blind) |
| 134 | 10x |
assert_flag(legend) |
| 135 | 10x |
assert_character(tox_labels, any.missing = FALSE, unique = TRUE) |
| 136 | 10x |
assert_integer(tox_shapes, any.missing = FALSE, unique = TRUE) |
| 137 | 10x |
assert_true(length(tox_shapes) == length(tox_labels)) |
| 138 | 10x |
assert_subset(x@y, as.integer(0:(length(tox_shapes) - 1))) |
| 139 | 10x |
if (x@nObs == 0L) {
|
| 140 | ! |
return() |
| 141 |
} |
|
| 142 | 10x |
df <- h_plot_data_df(x, blind, ...) |
| 143 | ||
| 144 | 10x |
p <- ggplot(df, aes(x = patient, y = dose)) + |
| 145 | 10x |
geom_point(aes(shape = toxicity, colour = toxicity), size = 3) + |
| 146 | 10x |
scale_colour_manual( |
| 147 | 10x |
name = "Toxicity", |
| 148 | 10x |
values = tox_labels, |
| 149 | 10x |
breaks = names(tox_labels), |
| 150 | 10x |
guide = guide_legend(reverse = TRUE) |
| 151 |
) + |
|
| 152 | 10x |
scale_shape_manual( |
| 153 | 10x |
name = "Toxicity", |
| 154 | 10x |
values = tox_shapes, |
| 155 | 10x |
breaks = names(tox_shapes), |
| 156 | 10x |
guide = guide_legend(reverse = TRUE) |
| 157 |
) + |
|
| 158 | 10x |
scale_x_continuous(breaks = df$patient, minor_breaks = NULL) + |
| 159 | 10x |
scale_y_continuous( |
| 160 | 10x |
breaks = sort(unique(c(0, df$dose))), |
| 161 | 10x |
minor_breaks = NULL, |
| 162 | 10x |
limits = c(0, max(df$dose) * 1.1) |
| 163 |
) + |
|
| 164 | 10x |
xlab("Patient") +
|
| 165 | 10x |
ylab("Dose Level")
|
| 166 | ||
| 167 | 10x |
p <- p + h_plot_data_cohort_lines(df$cohort, placebo = x@placebo) |
| 168 | ||
| 169 | 10x |
if (!blind) {
|
| 170 | 4x |
p <- p + |
| 171 | 4x |
geom_text( |
| 172 | 4x |
aes(label = ID, size = 2), |
| 173 | 4x |
data = df, |
| 174 | 4x |
hjust = 0, |
| 175 | 4x |
vjust = 0.5, |
| 176 | 4x |
angle = 90, |
| 177 | 4x |
colour = "black", |
| 178 | 4x |
show.legend = FALSE |
| 179 |
) |
|
| 180 |
} |
|
| 181 | ||
| 182 | 10x |
if (!legend) {
|
| 183 | 6x |
p <- p + theme(legend.position = "none") |
| 184 |
} |
|
| 185 | ||
| 186 | 10x |
p |
| 187 |
} |
|
| 188 | ||
| 189 |
#' Helper Function Containing Common Functionality |
|
| 190 |
#' |
|
| 191 |
#' Used by `dose_grid_range-Data` and `dose_grid_range-DataOrdinal` |
|
| 192 |
#' @param object (`Data` or `DataOrdinal`)\cr the object for which the dose grid |
|
| 193 |
#' range is required |
|
| 194 |
#' @param ignore_placebo (`flag`)\cr should placebo dose (if any) not be counted? |
|
| 195 |
#' |
|
| 196 |
h_obtain_dose_grid_range <- function(object, ignore_placebo) {
|
|
| 197 | 267x |
assert_flag(ignore_placebo) |
| 198 | ||
| 199 | 265x |
dose_grid <- if (ignore_placebo && object@placebo && object@nGrid >= 1) {
|
| 200 | 96x |
object@doseGrid[-1] |
| 201 |
} else {
|
|
| 202 | 169x |
object@doseGrid |
| 203 |
} |
|
| 204 | ||
| 205 | 265x |
if (length(dose_grid) == 0L) {
|
| 206 | 10x |
c(-Inf, Inf) |
| 207 |
} else {
|
|
| 208 | 255x |
range(dose_grid) |
| 209 |
} |
|
| 210 |
} |
|
| 211 | ||
| 212 |
#' Convert a Ordinal Data to the Equivalent Binary Data for a Specific |
|
| 213 |
#' Grade |
|
| 214 |
#' |
|
| 215 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 216 |
#' |
|
| 217 |
#' A simple helper function that takes a [`DataOrdinal`] object and an |
|
| 218 |
#' integer grade and converts them to the equivalent `Data` object. |
|
| 219 |
#' |
|
| 220 |
#' @param data_ord (`DataOrdinal`)\cr the `DataOrdinal` object to covert |
|
| 221 |
#' @param grade (`integer`)\cr the toxicity grade for which the equivalent data |
|
| 222 |
#' is required. |
|
| 223 |
#' @return A [`Data`] object. |
|
| 224 |
#' |
|
| 225 |
#' @export |
|
| 226 |
h_convert_ordinal_data <- function(data_ord, grade) {
|
|
| 227 |
# Validate |
|
| 228 | 33x |
assert_integer(grade, len = 1, lower = 1) |
| 229 | 33x |
assert_class(data_ord, "DataOrdinal") |
| 230 |
# Execute |
|
| 231 | 33x |
Data( |
| 232 | 33x |
ID = data_ord@ID, |
| 233 | 33x |
cohort = data_ord@cohort, |
| 234 | 33x |
x = data_ord@x, |
| 235 | 33x |
y = as.integer(data_ord@y >= grade), |
| 236 | 33x |
doseGrid = data_ord@doseGrid, |
| 237 | 33x |
nGrid = data_ord@nGrid, |
| 238 | 33x |
xLevel = data_ord@xLevel, |
| 239 | 33x |
placebo = data_ord@placebo |
| 240 |
) |
|
| 241 |
} |
| 1 |
#' @include Design-validity.R |
|
| 2 |
#' @include Model-class.R |
|
| 3 |
#' @include Rules-class.R |
|
| 4 |
#' @include Data-class.R |
|
| 5 |
#' @include helpers.R |
|
| 6 |
#' @include CrmPackClass-class.R |
|
| 7 |
NULL |
|
| 8 | ||
| 9 |
# RuleDesign ---- |
|
| 10 | ||
| 11 |
## class ---- |
|
| 12 | ||
| 13 |
#' `RuleDesign` |
|
| 14 |
#' |
|
| 15 |
#' @description `r lifecycle::badge("stable")`
|
|
| 16 |
#' |
|
| 17 |
#' [`RuleDesign`] is the class for rule-based designs. The difference between |
|
| 18 |
#' this class and the [`Design`] class is that [`RuleDesign`] does not contain |
|
| 19 |
#' `model`, `stopping` and `increments` slots. |
|
| 20 |
#' |
|
| 21 |
#' @slot nextBest (`NextBest`)\cr how to find the next best dose. |
|
| 22 |
#' @slot cohort_size (`CohortSize`)\cr rules for the cohort sizes. |
|
| 23 |
#' @slot data (`Data`)\cr specifies dose grid, any previous data, etc. |
|
| 24 |
#' @slot startingDose (`number`)\cr the starting dose, it must lie on the dose |
|
| 25 |
#' grid in `data`. |
|
| 26 |
#' |
|
| 27 |
#' @aliases RuleDesign |
|
| 28 |
#' @export |
|
| 29 |
#' |
|
| 30 |
.RuleDesign <- setClass( |
|
| 31 |
Class = "RuleDesign", |
|
| 32 |
slots = c( |
|
| 33 |
nextBest = "NextBest", |
|
| 34 |
cohort_size = "CohortSize", |
|
| 35 |
data = "Data", |
|
| 36 |
startingDose = "numeric" |
|
| 37 |
), |
|
| 38 |
prototype = prototype( |
|
| 39 |
nextBest = .NextBestThreePlusThree(), |
|
| 40 |
cohort_size = CohortSizeConst(3), |
|
| 41 |
data = Data(doseGrid = 1:3), |
|
| 42 |
startingDose = 1 |
|
| 43 |
), |
|
| 44 |
contains = "CrmPackClass", |
|
| 45 |
validity = v_rule_design |
|
| 46 |
) |
|
| 47 | ||
| 48 |
## constructor ---- |
|
| 49 | ||
| 50 |
#' @rdname RuleDesign-class |
|
| 51 |
#' |
|
| 52 |
#' @param nextBest (`NextBest`)\cr see slot definition. |
|
| 53 |
#' @param cohort_size (`CohortSize`)\cr see slot definition. |
|
| 54 |
#' @param data (`Data`)\cr see slot definition. |
|
| 55 |
#' @param startingDose (`number`)\cr see slot definition. |
|
| 56 |
#' |
|
| 57 |
#' @export |
|
| 58 |
#' @example examples/Design-class-RuleDesign.R |
|
| 59 |
#' |
|
| 60 |
RuleDesign <- function(nextBest, cohort_size, data, startingDose) {
|
|
| 61 | 121x |
new( |
| 62 | 121x |
"RuleDesign", |
| 63 | 121x |
nextBest = nextBest, |
| 64 | 121x |
cohort_size = cohort_size, |
| 65 | 121x |
data = data, |
| 66 | 121x |
startingDose = as.numeric(startingDose) |
| 67 |
) |
|
| 68 |
} |
|
| 69 | ||
| 70 |
#' @rdname RuleDesign-class |
|
| 71 |
#' @note Typically, end users will not use the `.DefaultRuleDesign()` function. |
|
| 72 |
#' @export |
|
| 73 | ||
| 74 |
.DefaultRuleDesign <- function() {
|
|
| 75 | 8x |
RuleDesign( |
| 76 | 8x |
nextBest = NextBestThreePlusThree(), |
| 77 | 8x |
cohort_size = CohortSizeConst(size = 3L), |
| 78 | 8x |
data = Data(doseGrid = c(5, 10, 15, 25, 35, 50, 80)), |
| 79 | 8x |
startingDose = 5 |
| 80 |
) |
|
| 81 |
} |
|
| 82 | ||
| 83 |
## ThreePlusThreeDesign ---- |
|
| 84 | ||
| 85 |
#' @describeIn RuleDesign-class creates a new 3+3 design object from a dose grid. |
|
| 86 |
#' |
|
| 87 |
#' @param doseGrid (`numeric`)\cr the dose grid to be used (sorted). |
|
| 88 |
#' |
|
| 89 |
#' @export |
|
| 90 |
#' @example examples/Design-class-ThreePlusThreeDesign.R |
|
| 91 |
#' |
|
| 92 |
ThreePlusThreeDesign <- function(doseGrid) {
|
|
| 93 | 3x |
empty_data <- Data(doseGrid = doseGrid) |
| 94 | ||
| 95 |
# Using a constant cohort size of 3 we obtain exactly the 3+3 design. |
|
| 96 | 3x |
RuleDesign( |
| 97 | 3x |
nextBest = NextBestThreePlusThree(), |
| 98 | 3x |
data = empty_data, |
| 99 | 3x |
cohort_size = CohortSizeConst(size = 3L), |
| 100 | 3x |
startingDose = doseGrid[1] |
| 101 |
) |
|
| 102 |
} |
|
| 103 | ||
| 104 |
# Design ---- |
|
| 105 | ||
| 106 |
## class ---- |
|
| 107 | ||
| 108 |
#' `Design` |
|
| 109 |
#' |
|
| 110 |
#' @description `r lifecycle::badge("stable")`
|
|
| 111 |
#' |
|
| 112 |
#' [`Design`] is the class for rule-based designs. The difference between |
|
| 113 |
#' this class and its parent [`RuleDesign`] class is that [`Design`] class |
|
| 114 |
#' contains additional `model`, `stopping` and `increments` slots. |
|
| 115 |
#' |
|
| 116 |
#' @slot model (`GeneralModel`)\cr the model to be used. |
|
| 117 |
#' @slot stopping (`Stopping`)\cr stopping rule(s) for the trial. |
|
| 118 |
#' @slot increments (`Increments`)\cr how to control increments between dose levels. |
|
| 119 |
#' @slot pl_cohort_size (`CohortSize`)\cr rules for the cohort sizes for placebo, |
|
| 120 |
#' if any planned (defaults to constant 0 placebo patients). |
|
| 121 |
#' |
|
| 122 |
#' @aliases Design |
|
| 123 |
#' @export |
|
| 124 |
#' |
|
| 125 |
.Design <- setClass( |
|
| 126 |
Class = "Design", |
|
| 127 |
slots = c( |
|
| 128 |
model = "GeneralModel", |
|
| 129 |
stopping = "Stopping", |
|
| 130 |
increments = "Increments", |
|
| 131 |
pl_cohort_size = "CohortSize" |
|
| 132 |
), |
|
| 133 |
prototype = prototype( |
|
| 134 |
model = .LogisticNormal(), |
|
| 135 |
nextBest = .NextBestNCRM(), |
|
| 136 |
stopping = .StoppingMinPatients(), |
|
| 137 |
increments = .IncrementsRelative(), |
|
| 138 |
pl_cohort_size = CohortSizeConst(0L) |
|
| 139 |
), |
|
| 140 |
contains = "RuleDesign" |
|
| 141 |
) |
|
| 142 | ||
| 143 |
## constructor ---- |
|
| 144 | ||
| 145 |
#' @rdname Design-class |
|
| 146 |
#' |
|
| 147 |
#' @param model (`GeneralModel`)\cr see slot definition. |
|
| 148 |
#' @param stopping (`Stopping`)\cr see slot definition. |
|
| 149 |
#' @param increments (`Increments`)\cr see slot definition. |
|
| 150 |
#' @param pl_cohort_size (`CohortSize`)\cr see slot definition. |
|
| 151 |
#' @inheritDotParams RuleDesign |
|
| 152 |
#' |
|
| 153 |
#' @export |
|
| 154 |
#' @example examples/Design-class-Design.R |
|
| 155 |
#' |
|
| 156 |
#' |
|
| 157 |
Design <- function( |
|
| 158 |
model, |
|
| 159 |
stopping, |
|
| 160 |
increments, |
|
| 161 |
pl_cohort_size = CohortSizeConst(0L), |
|
| 162 |
... |
|
| 163 |
) {
|
|
| 164 | 64x |
start <- RuleDesign(...) |
| 165 | 64x |
new( |
| 166 | 64x |
"Design", |
| 167 | 64x |
start, |
| 168 | 64x |
model = model, |
| 169 | 64x |
stopping = stopping, |
| 170 | 64x |
increments = increments, |
| 171 | 64x |
pl_cohort_size = pl_cohort_size |
| 172 |
) |
|
| 173 |
} |
|
| 174 | ||
| 175 |
## default constructor ---- |
|
| 176 | ||
| 177 |
#' @rdname Design-class |
|
| 178 |
#' @note Typically, end users will not use the `.DefaultDesign()` function. |
|
| 179 |
#' @export |
|
| 180 |
.DefaultDesign <- function() {
|
|
| 181 | 7x |
my_size1 <- CohortSizeRange( |
| 182 | 7x |
intervals = c(0, 30), |
| 183 | 7x |
cohort_size = c(1, 3) |
| 184 |
) |
|
| 185 | 7x |
my_size2 <- CohortSizeDLT( |
| 186 | 7x |
intervals = c(0, 1), |
| 187 | 7x |
cohort_size = c(1, 3) |
| 188 |
) |
|
| 189 | 7x |
my_size <- maxSize(my_size1, my_size2) |
| 190 | ||
| 191 | 7x |
my_stopping1 <- StoppingMinCohorts(nCohorts = 3) |
| 192 | 7x |
my_stopping2 <- StoppingTargetProb( |
| 193 | 7x |
target = c(0.2, 0.35), |
| 194 | 7x |
prob = 0.5 |
| 195 |
) |
|
| 196 | 7x |
my_stopping3 <- StoppingMinPatients(nPatients = 20) |
| 197 | 7x |
my_stopping <- (my_stopping1 & my_stopping2) | my_stopping3 |
| 198 | ||
| 199 |
# Initialize the design. |
|
| 200 | 7x |
design <- Design( |
| 201 | 7x |
model = LogisticLogNormal( |
| 202 | 7x |
mean = c(-0.85, 1), |
| 203 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 204 | 7x |
ref_dose = 56 |
| 205 |
), |
|
| 206 | 7x |
nextBest = NextBestNCRM( |
| 207 | 7x |
target = c(0.2, 0.35), |
| 208 | 7x |
overdose = c(0.35, 1), |
| 209 | 7x |
max_overdose_prob = 0.25 |
| 210 |
), |
|
| 211 | 7x |
stopping = my_stopping, |
| 212 | 7x |
increments = IncrementsRelative( |
| 213 | 7x |
intervals = c(0, 20), |
| 214 | 7x |
increments = c(1, 0.33) |
| 215 |
), |
|
| 216 | 7x |
cohort_size = my_size, |
| 217 | 7x |
data = Data(doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100)), |
| 218 | 7x |
startingDose = 3 |
| 219 |
) |
|
| 220 |
} |
|
| 221 | ||
| 222 |
# DualDesign ---- |
|
| 223 | ||
| 224 |
## class ---- |
|
| 225 | ||
| 226 |
#' `DualDesign` |
|
| 227 |
#' |
|
| 228 |
#' @description `r lifecycle::badge("stable")`
|
|
| 229 |
#' |
|
| 230 |
#' [`DualDesign`] is the class for the dual-endpoint CRM design. This class has |
|
| 231 |
#' special requirements for the `model` and `data` slots in comparison to the |
|
| 232 |
#' parent class [`Design`]. |
|
| 233 |
#' |
|
| 234 |
#' @note the `nextBest` slot can be of any class, this allows for easy comparison |
|
| 235 |
#' with recommendation methods that don't use the biomarker information. |
|
| 236 |
#' |
|
| 237 |
#' @slot model (`DualEndpoint`)\cr the model to be used. |
|
| 238 |
#' @slot data (`DataDual`)\cr specifies dose grid, any previous data, etc. |
|
| 239 |
#' |
|
| 240 |
#' @aliases DualDesign |
|
| 241 |
#' @export |
|
| 242 |
#' |
|
| 243 |
.DualDesign <- setClass( |
|
| 244 |
Class = "DualDesign", |
|
| 245 |
slots = c( |
|
| 246 |
model = "DualEndpoint", |
|
| 247 |
data = "DataDual" |
|
| 248 |
), |
|
| 249 |
prototype = prototype( |
|
| 250 |
model = .DualEndpoint(), |
|
| 251 |
nextBest = .NextBestDualEndpoint(), |
|
| 252 |
data = DataDual(doseGrid = 1:2), |
|
| 253 |
startingDose = 1 |
|
| 254 |
), |
|
| 255 |
contains = "Design" |
|
| 256 |
) |
|
| 257 | ||
| 258 |
## constructor ---- |
|
| 259 | ||
| 260 |
#' @rdname DualDesign-class |
|
| 261 |
#' |
|
| 262 |
#' @param model (`DualEndpoint`)\cr see slot definition. |
|
| 263 |
#' @param data (`DataDual`)\cr see slot definition. |
|
| 264 |
#' @inheritDotParams Design |
|
| 265 |
#' |
|
| 266 |
#' @export |
|
| 267 |
#' @example examples/Design-class-DualDesign.R |
|
| 268 |
#' |
|
| 269 |
DualDesign <- function(model, data, ...) {
|
|
| 270 | 15x |
start <- Design(model = model, data = data, ...) |
| 271 | 15x |
new( |
| 272 | 15x |
"DualDesign", |
| 273 | 15x |
start, |
| 274 | 15x |
model = model, |
| 275 | 15x |
data = data |
| 276 |
) |
|
| 277 |
} |
|
| 278 | ||
| 279 |
## default constructor ---- |
|
| 280 | ||
| 281 |
#' @rdname DualDesign-class |
|
| 282 |
#' @note Typically, end users will not use the `.DefaultDualDesign()` function. |
|
| 283 |
#' @export |
|
| 284 |
.DefaultDualDesign <- function() {
|
|
| 285 | 8x |
my_model <- DualEndpointRW( |
| 286 | 8x |
mean = c(0, 1), |
| 287 | 8x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 288 | 8x |
sigma2betaW = 0.01, |
| 289 | 8x |
sigma2W = c(a = 0.1, b = 0.1), |
| 290 | 8x |
rho = c(a = 1, b = 1), |
| 291 | 8x |
rw1 = TRUE |
| 292 |
) |
|
| 293 | ||
| 294 |
# Choose the rule for selecting the next dose. |
|
| 295 | 8x |
my_next_best <- NextBestDualEndpoint( |
| 296 | 8x |
target = c(0.9, 1), |
| 297 | 8x |
overdose = c(0.35, 1), |
| 298 | 8x |
max_overdose_prob = 0.25 |
| 299 |
) |
|
| 300 | ||
| 301 |
# Choose the rule for the cohort-size. |
|
| 302 | 8x |
my_size1 <- CohortSizeRange( |
| 303 | 8x |
intervals = c(0, 30), |
| 304 | 8x |
cohort_size = c(1, 3) |
| 305 |
) |
|
| 306 | 8x |
my_size2 <- CohortSizeDLT( |
| 307 | 8x |
intervals = c(0, 1), |
| 308 | 8x |
cohort_size = c(1, 3) |
| 309 |
) |
|
| 310 | 8x |
my_size <- maxSize(my_size1, my_size2) |
| 311 | ||
| 312 |
# Choose the rule for stopping. |
|
| 313 | 8x |
my_stopping1 <- StoppingTargetBiomarker( |
| 314 | 8x |
target = c(0.9, 1), |
| 315 | 8x |
prob = 0.5 |
| 316 |
) |
|
| 317 | 8x |
my_stopping <- my_stopping1 | StoppingMinPatients(40) |
| 318 | ||
| 319 |
# Choose the rule for dose increments. |
|
| 320 | 8x |
my_increments <- IncrementsRelative( |
| 321 | 8x |
intervals = c(0, 20), |
| 322 | 8x |
increments = c(1, 0.33) |
| 323 |
) |
|
| 324 | ||
| 325 |
# Initialize the design. |
|
| 326 | 8x |
DualDesign( |
| 327 | 8x |
model = my_model, |
| 328 | 8x |
data = DataDual(doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100)), |
| 329 | 8x |
nextBest = my_next_best, |
| 330 | 8x |
stopping = my_stopping, |
| 331 | 8x |
increments = my_increments, |
| 332 | 8x |
cohort_size = my_size, |
| 333 | 8x |
startingDose = 3 |
| 334 |
) |
|
| 335 |
} |
|
| 336 | ||
| 337 |
# TDsamplesDesign ---- |
|
| 338 | ||
| 339 |
## class ---- |
|
| 340 | ||
| 341 |
#' `TDsamplesDesign` |
|
| 342 |
#' |
|
| 343 |
#' @description `r lifecycle::badge("stable")`
|
|
| 344 |
#' |
|
| 345 |
#' [`TDsamplesDesign`] is the class of design based only on DLT responses using |
|
| 346 |
#' [`ModelTox`] class model (i.e. [`LogisticIndepBeta`]) as well as MCMC samples |
|
| 347 |
#' obtained for this model. |
|
| 348 |
#' |
|
| 349 |
#' @slot model (`ModelTox`)\cr the pseudo DLT model to be used. |
|
| 350 |
#' @slot stopping (`Stopping`)\cr stopping rule(s) for the trial. |
|
| 351 |
#' @slot increments (`Increments`)\cr how to control increments between dose levels. |
|
| 352 |
#' @slot pl_cohort_size (`CohortSize`)\cr rules for the cohort sizes for placebo, |
|
| 353 |
#' if any planned (defaults to constant 0 placebo patients). |
|
| 354 |
#' |
|
| 355 |
#' @aliases TDsamplesDesign |
|
| 356 |
#' @export |
|
| 357 |
#' |
|
| 358 |
.TDsamplesDesign <- setClass( |
|
| 359 |
Class = "TDsamplesDesign", |
|
| 360 |
slots = c( |
|
| 361 |
model = "ModelTox", |
|
| 362 |
stopping = "Stopping", |
|
| 363 |
increments = "Increments", |
|
| 364 |
pl_cohort_size = "CohortSize" |
|
| 365 |
), |
|
| 366 |
prototype = prototype( |
|
| 367 |
model = .LogisticIndepBeta(), |
|
| 368 |
nextBest = .NextBestTDsamples(), |
|
| 369 |
stopping = .StoppingMinPatients(), |
|
| 370 |
increments = .IncrementsRelative(), |
|
| 371 |
pl_cohort_size = CohortSizeConst(0L) |
|
| 372 |
), |
|
| 373 |
contains = "RuleDesign" |
|
| 374 |
) |
|
| 375 | ||
| 376 |
## constructor ---- |
|
| 377 | ||
| 378 |
#' @rdname TDsamplesDesign-class |
|
| 379 |
#' |
|
| 380 |
#' @param model (`ModelTox`)\cr see slot definition. |
|
| 381 |
#' @param stopping (`Stopping`)\cr see slot definition. |
|
| 382 |
#' @param increments (`Increments`)\cr see slot definition. |
|
| 383 |
#' @param pl_cohort_size (`CohortSize`)\cr see slot definition. |
|
| 384 |
#' @inheritDotParams RuleDesign |
|
| 385 |
#' |
|
| 386 |
#' @export |
|
| 387 |
#' @example examples/Design-class-TDsamplesDesign.R |
|
| 388 |
#' |
|
| 389 |
TDsamplesDesign <- function( |
|
| 390 |
model, |
|
| 391 |
stopping, |
|
| 392 |
increments, |
|
| 393 |
pl_cohort_size = CohortSizeConst(0L), |
|
| 394 |
... |
|
| 395 |
) {
|
|
| 396 | 20x |
start <- RuleDesign(...) |
| 397 | 20x |
new( |
| 398 | 20x |
"TDsamplesDesign", |
| 399 | 20x |
start, |
| 400 | 20x |
model = model, |
| 401 | 20x |
stopping = stopping, |
| 402 | 20x |
increments = increments, |
| 403 | 20x |
pl_cohort_size = pl_cohort_size |
| 404 |
) |
|
| 405 |
} |
|
| 406 | ||
| 407 |
## default constructor ---- |
|
| 408 | ||
| 409 |
#' @rdname TDsamplesDesign-class |
|
| 410 |
#' @note Typically, end users will not use the `.DefaultTDsamplesDesign()` function. |
|
| 411 |
#' @export |
|
| 412 |
.DefaultTDsamplesDesign <- function() {
|
|
| 413 | 5x |
empty_data <- Data(doseGrid = seq(25, 300, 25)) |
| 414 | ||
| 415 | 5x |
my_model <- LogisticIndepBeta( |
| 416 | 5x |
binDLE = c(1.05, 1.8), |
| 417 | 5x |
DLEweights = c(3, 3), |
| 418 | 5x |
DLEdose = c(25, 300), |
| 419 | 5x |
data = empty_data |
| 420 |
) |
|
| 421 | ||
| 422 | 5x |
TDsamplesDesign( |
| 423 | 5x |
model = my_model, |
| 424 | 5x |
stopping = StoppingMinPatients(nPatients = 36), |
| 425 | 5x |
increments = IncrementsRelative( |
| 426 | 5x |
intervals = range(empty_data@doseGrid), |
| 427 | 5x |
increments = c(2, 2) |
| 428 |
), |
|
| 429 | 5x |
nextBest = NextBestTDsamples( |
| 430 | 5x |
prob_target_drt = 0.35, |
| 431 | 5x |
prob_target_eot = 0.3, |
| 432 | 5x |
derive = function(samples) {
|
| 433 | 5x |
as.numeric(quantile(samples, probs = 0.3)) |
| 434 |
} |
|
| 435 |
), |
|
| 436 | 5x |
cohort_size = CohortSizeConst(size = 3), |
| 437 | 5x |
data = empty_data, |
| 438 | 5x |
startingDose = 25 |
| 439 |
) |
|
| 440 |
} |
|
| 441 | ||
| 442 |
# TDDesign ---- |
|
| 443 | ||
| 444 |
## class ---- |
|
| 445 | ||
| 446 |
#' `TDDesign` |
|
| 447 |
#' |
|
| 448 |
#' @description `r lifecycle::badge("stable")`
|
|
| 449 |
#' |
|
| 450 |
#' [`TDDesign`] is the class of design based only on DLT responses using |
|
| 451 |
#' [`ModelTox`] class model (i.e. [`LogisticIndepBeta`]) without MCMC samples. |
|
| 452 |
#' |
|
| 453 |
#' @slot model (`ModelTox`)\cr the pseudo DLT model to be used. |
|
| 454 |
#' @slot stopping (`Stopping`)\cr stopping rule(s) for the trial. |
|
| 455 |
#' @slot increments (`Increments`)\cr how to control increments between dose levels. |
|
| 456 |
#' @slot pl_cohort_size (`CohortSize`)\cr rules for the cohort sizes for placebo, |
|
| 457 |
#' if any planned (defaults to constant 0 placebo patients). |
|
| 458 |
#' |
|
| 459 |
#' @aliases TDDesign |
|
| 460 |
#' @export |
|
| 461 |
#' |
|
| 462 |
.TDDesign <- setClass( |
|
| 463 |
Class = "TDDesign", |
|
| 464 |
slots = c( |
|
| 465 |
model = "ModelTox", |
|
| 466 |
stopping = "Stopping", |
|
| 467 |
increments = "Increments", |
|
| 468 |
pl_cohort_size = "CohortSize" |
|
| 469 |
), |
|
| 470 |
prototype = prototype( |
|
| 471 |
model = .LogisticIndepBeta(), |
|
| 472 |
nextBest = .NextBestTD(), |
|
| 473 |
stopping = .StoppingMinPatients(), |
|
| 474 |
increments = .IncrementsRelative(), |
|
| 475 |
pl_cohort_size = CohortSizeConst(0L) |
|
| 476 |
), |
|
| 477 |
contains = "RuleDesign" |
|
| 478 |
) |
|
| 479 | ||
| 480 |
## constructor ---- |
|
| 481 | ||
| 482 |
#' @rdname TDDesign-class |
|
| 483 |
#' |
|
| 484 |
#' @param model (`ModelTox`)\cr see slot definition. |
|
| 485 |
#' @param stopping (`Stopping`)\cr see slot definition. |
|
| 486 |
#' @param increments (`Increments`)\cr see slot definition. |
|
| 487 |
#' @param pl_cohort_size (`CohortSize`)\cr see slot definition. |
|
| 488 |
#' @inheritDotParams RuleDesign |
|
| 489 |
#' |
|
| 490 |
#' @export |
|
| 491 |
#' @example examples/Design-class-TDDesign.R |
|
| 492 |
#' |
|
| 493 |
TDDesign <- function( |
|
| 494 |
model, |
|
| 495 |
stopping, |
|
| 496 |
increments, |
|
| 497 |
pl_cohort_size = CohortSizeConst(0L), |
|
| 498 |
... |
|
| 499 |
) {
|
|
| 500 | 22x |
start <- RuleDesign(...) |
| 501 | 22x |
new( |
| 502 | 22x |
"TDDesign", |
| 503 | 22x |
start, |
| 504 | 22x |
model = model, |
| 505 | 22x |
stopping = stopping, |
| 506 | 22x |
increments = increments, |
| 507 | 22x |
pl_cohort_size = pl_cohort_size |
| 508 |
) |
|
| 509 |
} |
|
| 510 | ||
| 511 |
## default constructor ---- |
|
| 512 | ||
| 513 |
#' @rdname TDDesign-class |
|
| 514 |
#' @note Typically, end users will not use the `.DefaultTDDesign()` function. |
|
| 515 |
#' @export |
|
| 516 |
.DefaultTDDesign <- function() {
|
|
| 517 | 7x |
empty_data <- Data(doseGrid = seq(25, 300, 25)) |
| 518 | ||
| 519 | 7x |
my_model <- LogisticIndepBeta( |
| 520 | 7x |
binDLE = c(1.05, 1.8), |
| 521 | 7x |
DLEweights = c(3, 3), |
| 522 | 7x |
DLEdose = c(25, 300), |
| 523 | 7x |
data = empty_data |
| 524 |
) |
|
| 525 | ||
| 526 | 7x |
TDDesign( |
| 527 | 7x |
model = my_model, |
| 528 | 7x |
stopping = StoppingMinPatients(nPatients = 36), |
| 529 | 7x |
increments = IncrementsRelative( |
| 530 | 7x |
intervals = range(empty_data@doseGrid), |
| 531 | 7x |
increments = c(2, 2) |
| 532 |
), |
|
| 533 | 7x |
nextBest = NextBestTD( |
| 534 | 7x |
prob_target_drt = 0.35, |
| 535 | 7x |
prob_target_eot = 0.3 |
| 536 |
), |
|
| 537 | 7x |
cohort_size = CohortSizeConst(size = 3), |
| 538 | 7x |
data = empty_data, |
| 539 | 7x |
startingDose = 25 |
| 540 |
) |
|
| 541 |
} |
|
| 542 | ||
| 543 |
# DualResponsesSamplesDesign ---- |
|
| 544 | ||
| 545 |
## class ---- |
|
| 546 | ||
| 547 |
#' `DualResponsesSamplesDesign` |
|
| 548 |
#' |
|
| 549 |
#' @description `r lifecycle::badge("stable")`
|
|
| 550 |
#' |
|
| 551 |
#' This is a class of design based on DLE responses using the [`LogisticIndepBeta`] model |
|
| 552 |
# and efficacy responses using [`ModelEff`] model class |
|
| 553 |
#' with DLE and efficacy samples. It contain all slots in |
|
| 554 |
#' [`RuleDesign`] and [`TDsamplesDesign`] class objects. |
|
| 555 |
# |
|
| 556 |
#' @slot data (`DataDual`)\cr the data set. |
|
| 557 |
#' @slot eff_model (`ModelEff`)\cr the pseudo efficacy model to be used. |
|
| 558 |
#' |
|
| 559 |
#' @aliases DualResponsesSamplesDesign |
|
| 560 |
#' @export |
|
| 561 |
#' |
|
| 562 |
.DualResponsesSamplesDesign <- |
|
| 563 |
setClass( |
|
| 564 |
Class = "DualResponsesSamplesDesign", |
|
| 565 |
slots = c( |
|
| 566 |
eff_model = "ModelEff", |
|
| 567 |
data = "DataDual" |
|
| 568 |
), |
|
| 569 |
prototype = prototype( |
|
| 570 |
nextBest = .NextBestMaxGainSamples(), |
|
| 571 |
data = DataDual(doseGrid = 1:2), |
|
| 572 |
startingDose = 1, |
|
| 573 |
model = .LogisticIndepBeta() |
|
| 574 |
), |
|
| 575 |
contains = "TDsamplesDesign" |
|
| 576 |
) |
|
| 577 | ||
| 578 |
## constructor ---- |
|
| 579 | ||
| 580 |
#' @rdname DualResponsesSamplesDesign-class |
|
| 581 |
#' |
|
| 582 |
#' @param data (`DataDual`)\cr see slot definition. |
|
| 583 |
#' @param eff_model (`ModelEff`)\cr see slot definition. |
|
| 584 |
#' @inheritDotParams TDsamplesDesign |
|
| 585 |
#' |
|
| 586 |
#' @example examples/Design-class-DualResponsesSamplesDesign.R |
|
| 587 |
#' @export |
|
| 588 |
#' |
|
| 589 |
DualResponsesSamplesDesign <- function(eff_model, data, ...) {
|
|
| 590 | 11x |
start <- TDsamplesDesign(data = data, ...) |
| 591 | 11x |
.DualResponsesSamplesDesign( |
| 592 | 11x |
start, |
| 593 | 11x |
eff_model = eff_model, |
| 594 | 11x |
data = data |
| 595 |
) |
|
| 596 |
} |
|
| 597 | ||
| 598 |
## default constructor ---- |
|
| 599 | ||
| 600 |
#' @rdname DualResponsesSamplesDesign-class |
|
| 601 |
#' @note Typically, end users will not use the `.DefaultDualResponsesSamplesDesign()` function. |
|
| 602 |
#' @export |
|
| 603 |
.DefaultDualResponsesSamplesDesign <- function() {
|
|
| 604 | 7x |
empty_data <- DataDual(doseGrid = seq(25, 300, 25)) |
| 605 | ||
| 606 | 7x |
tox_model <- LogisticIndepBeta( |
| 607 | 7x |
binDLE = c(1.05, 1.8), |
| 608 | 7x |
DLEweights = c(3, 3), |
| 609 | 7x |
DLEdose = c(25, 300), |
| 610 | 7x |
data = empty_data |
| 611 |
) |
|
| 612 | 7x |
options <- McmcOptions(burnin = 100, step = 2, samples = 200) |
| 613 | 7x |
tox_samples <- mcmc(empty_data, tox_model, options) |
| 614 | ||
| 615 | 7x |
eff_model <- Effloglog( |
| 616 | 7x |
eff = c(1.223, 2.513), |
| 617 | 7x |
eff_dose = c(25, 300), |
| 618 | 7x |
nu = c(a = 1, b = 0.025), |
| 619 | 7x |
data = empty_data |
| 620 |
) |
|
| 621 | 7x |
eff_samples <- mcmc(empty_data, eff_model, options) |
| 622 | ||
| 623 | 7x |
my_next_best <- NextBestMaxGainSamples( |
| 624 | 7x |
prob_target_drt = 0.35, |
| 625 | 7x |
prob_target_eot = 0.3, |
| 626 | 7x |
derive = function(samples) {
|
| 627 | 7x |
as.numeric(quantile(samples, prob = 0.3)) |
| 628 |
}, |
|
| 629 | 7x |
mg_derive = function(mg_samples) {
|
| 630 | 7x |
as.numeric(quantile(mg_samples, prob = 0.5)) |
| 631 |
} |
|
| 632 |
) |
|
| 633 | ||
| 634 | 7x |
DualResponsesSamplesDesign( |
| 635 | 7x |
nextBest = my_next_best, |
| 636 | 7x |
cohort_size = CohortSizeConst(size = 3), |
| 637 | 7x |
startingDose = 25, |
| 638 | 7x |
model = tox_model, |
| 639 | 7x |
eff_model = eff_model, |
| 640 | 7x |
data = empty_data, |
| 641 | 7x |
stopping = StoppingMinPatients(nPatients = 36), |
| 642 | 7x |
increments = IncrementsRelative( |
| 643 | 7x |
intervals = c(25, 300), |
| 644 | 7x |
increments = c(2, 2) |
| 645 |
) |
|
| 646 |
) |
|
| 647 |
} |
|
| 648 | ||
| 649 |
# DualResponsesDesign.R ---- |
|
| 650 | ||
| 651 |
## class ---- |
|
| 652 | ||
| 653 |
#' `DualResponsesDesign.R` |
|
| 654 |
#' |
|
| 655 |
#' @description `r lifecycle::badge("stable")`
|
|
| 656 |
#' |
|
| 657 |
#' This is a class of design based on DLE responses using the [`LogisticIndepBeta`] model |
|
| 658 |
# and efficacy responses using the [`ModelEff`] model class |
|
| 659 |
#' without DLE and efficacy samples. It contains all slots from the |
|
| 660 |
#' [`RuleDesign`] and [`TDsamplesDesign`] classes. |
|
| 661 |
# |
|
| 662 |
#' @slot data (`DataDual`)\cr the data set. |
|
| 663 |
#' @slot eff_model (`ModelEff`)\cr the pseudo efficacy model to be used. |
|
| 664 |
#' |
|
| 665 |
#' @aliases DualResponsesDesign |
|
| 666 |
#' @export |
|
| 667 |
#' |
|
| 668 |
.DualResponsesDesign <- |
|
| 669 |
setClass( |
|
| 670 |
Class = "DualResponsesDesign", |
|
| 671 |
slots = c( |
|
| 672 |
eff_model = "ModelEff", |
|
| 673 |
data = "DataDual" |
|
| 674 |
), |
|
| 675 |
prototype = prototype( |
|
| 676 |
nextBest = .NextBestMaxGain(), |
|
| 677 |
data = DataDual(doseGrid = 1:2), |
|
| 678 |
startingDose = 1, |
|
| 679 |
model = .LogisticIndepBeta() |
|
| 680 |
), |
|
| 681 |
contains = "TDDesign" |
|
| 682 |
) |
|
| 683 | ||
| 684 |
## constructor ---- |
|
| 685 | ||
| 686 |
#' @rdname DualResponsesDesign-class |
|
| 687 |
#' |
|
| 688 |
#' @param data (`DataDual`)\cr see slot definition. |
|
| 689 |
#' @param eff_model (`ModelEff`)\cr see slot definition. |
|
| 690 |
#' @inheritDotParams TDDesign |
|
| 691 |
#' |
|
| 692 |
#' @example examples/Design-class-DualResponsesDesign.R |
|
| 693 |
#' @export |
|
| 694 |
#' |
|
| 695 |
DualResponsesDesign <- function(eff_model, data, ...) {
|
|
| 696 | 10x |
start <- TDDesign(data = data, ...) |
| 697 | 10x |
.DualResponsesDesign( |
| 698 | 10x |
start, |
| 699 | 10x |
eff_model = eff_model, |
| 700 | 10x |
data = data |
| 701 |
) |
|
| 702 |
} |
|
| 703 | ||
| 704 |
## default constructor ---- |
|
| 705 | ||
| 706 |
#' @rdname DualResponsesDesign-class |
|
| 707 |
#' @note Typically, end users will not use the `.DefaultDualResponsesDesign()` function. |
|
| 708 |
#' @export |
|
| 709 |
.DefaultDualResponsesDesign <- function() {
|
|
| 710 | 7x |
empty_data <- DataDual(doseGrid = seq(25, 300, 25)) |
| 711 | ||
| 712 | 7x |
DualResponsesDesign( |
| 713 | 7x |
nextBest = NextBestMaxGain( |
| 714 | 7x |
prob_target_drt = 0.35, |
| 715 | 7x |
prob_target_eot = 0.3 |
| 716 |
), |
|
| 717 | 7x |
cohort_size = CohortSizeConst(size = 3), |
| 718 | 7x |
startingDose = 25, |
| 719 | 7x |
model = LogisticIndepBeta( |
| 720 | 7x |
binDLE = c(1.05, 1.8), |
| 721 | 7x |
DLEweights = c(3, 3), |
| 722 | 7x |
DLEdose = c(25, 300), |
| 723 | 7x |
data = empty_data |
| 724 |
), |
|
| 725 | 7x |
eff_model = Effloglog( |
| 726 | 7x |
eff = c(1.223, 2.513), |
| 727 | 7x |
eff_dose = c(25, 300), |
| 728 | 7x |
nu = c(a = 1, b = 0.025), |
| 729 | 7x |
data = empty_data |
| 730 |
), |
|
| 731 | 7x |
data = empty_data, |
| 732 | 7x |
stopping = StoppingMinPatients(nPatients = 36), |
| 733 | 7x |
increments = IncrementsRelative( |
| 734 | 7x |
intervals = c(25, 300), |
| 735 | 7x |
increments = c(2, 2) |
| 736 |
) |
|
| 737 |
) |
|
| 738 |
} |
|
| 739 | ||
| 740 | ||
| 741 |
# DADesign ---- |
|
| 742 | ||
| 743 |
## class ---- |
|
| 744 | ||
| 745 |
#' `DADesign` |
|
| 746 |
#' |
|
| 747 |
#' @description `r lifecycle::badge("stable")`
|
|
| 748 |
#' |
|
| 749 |
#' This class has special requirements for the `model` and `data` |
|
| 750 |
#' slots in comparison to the parent class [`Design`]: |
|
| 751 |
#' |
|
| 752 |
#' @slot model (`GeneralModel`)\cr the model to use, see in particular [`DALogisticLogNormal`] and |
|
| 753 |
#' [`TITELogisticLogNormal`] which make use of the time-to-DLT data. |
|
| 754 |
#' @slot data (`DataDA`)\cr what is the dose grid, any previous data, etc. |
|
| 755 |
#' @slot safetyWindow (`SafetyWindow`)\cr the safety window to apply between cohorts. |
|
| 756 |
#' |
|
| 757 |
#' @details |
|
| 758 |
#' The `safetyWindow` slot should be an instance of the `SafetyWindow` class. |
|
| 759 |
#' It can be customized to specify the duration of the safety window for your trial. |
|
| 760 |
#' The safety window represents the time period required to observe toxicity data |
|
| 761 |
#' from the ongoing cohort before opening the next cohort. |
|
| 762 |
#' Note that even after opening the next cohort, |
|
| 763 |
#' further toxicity data will be collected and analyzed to make dose escalation decisions. |
|
| 764 |
#' |
|
| 765 |
#' |
|
| 766 |
#' To specify a constant safety window, use the `SafetyWindowConst` constructor. For example: |
|
| 767 |
#' |
|
| 768 |
#' \code{mysafetywindow <- SafetyWindowConst(c(6, 2), 10, 20)}
|
|
| 769 |
#' |
|
| 770 |
#' @seealso [`SafetyWindowConst`] for creating a constant safety window. |
|
| 771 |
#' |
|
| 772 |
#' @aliases DADesign |
|
| 773 |
#' @export |
|
| 774 |
#' |
|
| 775 |
.DADesign <- |
|
| 776 |
setClass( |
|
| 777 |
Class = "DADesign", |
|
| 778 |
slots = c( |
|
| 779 |
model = "GeneralModel", |
|
| 780 |
data = "DataDA", |
|
| 781 |
safetyWindow = "SafetyWindow" |
|
| 782 |
), |
|
| 783 |
prototype = prototype( |
|
| 784 |
model = .DALogisticLogNormal(), |
|
| 785 |
nextBest = .NextBestNCRM(), |
|
| 786 |
data = DataDA(doseGrid = 1:2), |
|
| 787 |
safetyWindow = .SafetyWindowConst() |
|
| 788 |
), |
|
| 789 |
contains = "Design" |
|
| 790 |
) |
|
| 791 | ||
| 792 | ||
| 793 |
## constructor ---- |
|
| 794 | ||
| 795 |
#' @rdname DADesign-class |
|
| 796 |
#' |
|
| 797 |
#' @param model (`GeneralModel`)\cr see slot definition. |
|
| 798 |
#' @param data (`DataDA`)\cr see slot definition. |
|
| 799 |
#' @param safetyWindow (`SafetyWindow`)\cr see slot definition. |
|
| 800 |
#' @inheritDotParams Design |
|
| 801 |
#' |
|
| 802 |
#' @example examples/Design-class-DADesign.R |
|
| 803 |
#' @export |
|
| 804 |
#' |
|
| 805 |
DADesign <- function(model, data, safetyWindow, ...) {
|
|
| 806 | 11x |
start <- Design( |
| 807 | 11x |
data = data, |
| 808 | 11x |
model = model, |
| 809 |
... |
|
| 810 |
) |
|
| 811 | 11x |
.DADesign(start, safetyWindow = safetyWindow) |
| 812 |
} |
|
| 813 | ||
| 814 |
## default constructor ---- |
|
| 815 | ||
| 816 |
#' @rdname DADesign-class |
|
| 817 |
#' @note Typically, end users will not use the `.DefaultDADesign()` function. |
|
| 818 |
#' @export |
|
| 819 |
.DefaultDADesign <- function() {
|
|
| 820 | 7x |
emptydata <- DataDA( |
| 821 | 7x |
doseGrid = c(0.1, 0.5, 1, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)), |
| 822 | 7x |
Tmax = 60 |
| 823 |
) |
|
| 824 | ||
| 825 | 7x |
npiece_ <- 10 |
| 826 | 7x |
t_max_ <- 60 |
| 827 | ||
| 828 | 7x |
lambda_prior <- function(k) {
|
| 829 | 7x |
npiece_ / (t_max_ * (npiece_ - k + 0.5)) |
| 830 |
} |
|
| 831 | ||
| 832 | 7x |
model <- DALogisticLogNormal( |
| 833 | 7x |
mean = c(-0.85, 1), |
| 834 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
| 835 | 7x |
ref_dose = 56, |
| 836 | 7x |
npiece = npiece_, |
| 837 | 7x |
l = as.numeric(t(apply( |
| 838 | 7x |
as.matrix(c(1:npiece_), 1, npiece_), |
| 839 | 7x |
2, |
| 840 | 7x |
lambda_prior |
| 841 |
))), |
|
| 842 | 7x |
c_par = 2 |
| 843 |
) |
|
| 844 | ||
| 845 | 7x |
mySize1 <- CohortSizeRange( |
| 846 | 7x |
intervals = c(0, 30), |
| 847 | 7x |
cohort_size = c(1, 3) |
| 848 |
) |
|
| 849 | 7x |
mySize2 <- CohortSizeDLT( |
| 850 | 7x |
intervals = c(0, 1), |
| 851 | 7x |
cohort_size = c(1, 3) |
| 852 |
) |
|
| 853 | 7x |
mySize <- maxSize(mySize1, mySize2) |
| 854 | ||
| 855 | 7x |
myStopping1 <- StoppingTargetProb( |
| 856 | 7x |
target = c(0.2, 0.35), |
| 857 | 7x |
prob = 0.5 |
| 858 |
) |
|
| 859 | 7x |
myStopping2 <- StoppingMinPatients(nPatients = 50) |
| 860 | 7x |
myStopping <- (myStopping1 | myStopping2) |
| 861 | ||
| 862 | 7x |
DADesign( |
| 863 | 7x |
model = model, |
| 864 | 7x |
increments = IncrementsRelative( |
| 865 | 7x |
intervals = c(0, 20), |
| 866 | 7x |
increments = c(1, 0.33) |
| 867 |
), |
|
| 868 | 7x |
nextBest = NextBestNCRM( |
| 869 | 7x |
target = c(0.2, 0.35), |
| 870 | 7x |
overdose = c(0.35, 1), |
| 871 | 7x |
max_overdose_prob = 0.25 |
| 872 |
), |
|
| 873 | 7x |
stopping = myStopping, |
| 874 | 7x |
cohort_size = mySize, |
| 875 | 7x |
data = emptydata, |
| 876 | 7x |
safetyWindow = SafetyWindowConst(c(6, 2), 7, 7), |
| 877 | 7x |
startingDose = 3 |
| 878 |
) |
|
| 879 |
} |
|
| 880 |
# DesignGrouped ---- |
|
| 881 | ||
| 882 |
## class ---- |
|
| 883 | ||
| 884 |
#' `DesignGrouped` |
|
| 885 |
#' |
|
| 886 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 887 |
#' |
|
| 888 |
#' [`DesignGrouped`] combines two [`Design`] objects: one for the mono and one |
|
| 889 |
#' for the combo arm of a joint dose escalation design. |
|
| 890 |
#' |
|
| 891 |
#' @slot model (`LogisticLogNormalGrouped`)\cr the model to be used, currently only one |
|
| 892 |
#' class is allowed. |
|
| 893 |
#' @slot mono (`Design`)\cr defines the dose escalation rules for the mono arm, see |
|
| 894 |
#' details. |
|
| 895 |
#' @slot combo (`Design`)\cr defines the dose escalation rules for the combo arm, see |
|
| 896 |
#' details. |
|
| 897 |
#' @slot first_cohort_mono_only (`flag`)\cr whether first test one mono agent cohort, and then |
|
| 898 |
#' once its DLT data has been collected, we proceed from the second cohort onwards with |
|
| 899 |
#' concurrent mono and combo cohorts. |
|
| 900 |
#' @slot same_dose_for_all (`flag`)\cr whether the lower dose of the separately determined mono and combo |
|
| 901 |
#' doses should be used as the next dose for both mono and combo in all cohorts. |
|
| 902 |
#' @slot same_dose_for_start (`flag`)\cr indicates whether, when mono and combo are |
|
| 903 |
#' used in the same cohort for the first time, the same dose should be used for both. |
|
| 904 |
#' Note that this is different from `same_dose_for_all` which will always force |
|
| 905 |
#' them to be the same. If `same_dose_for_all = TRUE`, this is therefore ignored. See Details. |
|
| 906 |
#' |
|
| 907 |
#' @details |
|
| 908 |
#' |
|
| 909 |
#' - Note that the model slots inside the `mono` and `combo` parameters |
|
| 910 |
#' are ignored (because we don't fit separate regression models for the mono and |
|
| 911 |
#' combo arms). Instead, the `model` parameter is used to fit a joint regression |
|
| 912 |
#' model for the mono and combo arms together. |
|
| 913 |
#' - `same_dose_for_start = TRUE` is useful as an option when we want to use `same_dose_for_all = FALSE` |
|
| 914 |
#' combined with `first_cohort_mono_only = TRUE`. |
|
| 915 |
#' This will allow to randomize patients to the mono and combo arms at the same dose |
|
| 916 |
#' as long as the selected dose for the cohorts stay the same. This can therefore |
|
| 917 |
#' further mitigate bias as long as possible between the mono and combo arms. |
|
| 918 |
#' |
|
| 919 |
#' @aliases DesignGrouped |
|
| 920 |
#' @export |
|
| 921 |
#' |
|
| 922 |
.DesignGrouped <- setClass( |
|
| 923 |
Class = "DesignGrouped", |
|
| 924 |
slots = c( |
|
| 925 |
model = "LogisticLogNormalGrouped", |
|
| 926 |
mono = "Design", |
|
| 927 |
combo = "Design", |
|
| 928 |
first_cohort_mono_only = "logical", |
|
| 929 |
same_dose_for_all = "logical", |
|
| 930 |
same_dose_for_start = "logical" |
|
| 931 |
), |
|
| 932 |
prototype = prototype( |
|
| 933 |
model = .DefaultLogisticLogNormalGrouped(), |
|
| 934 |
mono = .Design(), |
|
| 935 |
combo = .Design(), |
|
| 936 |
first_cohort_mono_only = TRUE, |
|
| 937 |
same_dose_for_all = TRUE, |
|
| 938 |
same_dose_for_start = FALSE |
|
| 939 |
), |
|
| 940 |
validity = v_design_grouped, |
|
| 941 |
contains = "CrmPackClass" |
|
| 942 |
) |
|
| 943 | ||
| 944 |
## constructor ---- |
|
| 945 | ||
| 946 |
#' @rdname DesignGrouped-class |
|
| 947 |
#' |
|
| 948 |
#' @param model (`LogisticLogNormalGrouped`)\cr see slot definition. |
|
| 949 |
#' @param mono (`Design`)\cr see slot definition. |
|
| 950 |
#' @param combo (`Design`)\cr see slot definition. |
|
| 951 |
#' @param first_cohort_mono_only (`flag`)\cr see slot definition. |
|
| 952 |
#' @param same_dose_for_all (`flag`)\cr see slot definition. |
|
| 953 |
#' @param same_dose_for_start (`flag`)\cr see slot definition. |
|
| 954 |
#' @param stop_mono_with_combo (`flag`)\cr whether the mono arm should be stopped when the combo |
|
| 955 |
#' arm is stopped (this makes sense when the only real trial objective is the recommended combo dose). |
|
| 956 |
#' @param ... not used. |
|
| 957 |
#' |
|
| 958 |
#' @export |
|
| 959 |
#' @example examples/Design-class-DesignGrouped.R |
|
| 960 |
#' |
|
| 961 |
DesignGrouped <- function( |
|
| 962 |
model, |
|
| 963 |
mono, |
|
| 964 |
combo = mono, |
|
| 965 |
first_cohort_mono_only = TRUE, |
|
| 966 |
same_dose_for_all = !same_dose_for_start, |
|
| 967 |
same_dose_for_start = FALSE, |
|
| 968 |
stop_mono_with_combo = FALSE, |
|
| 969 |
... |
|
| 970 |
) {
|
|
| 971 | 10x |
assert_flag(stop_mono_with_combo) |
| 972 | 10x |
assert_class(mono, "Design") |
| 973 | 10x |
force(combo) |
| 974 | 10x |
if (stop_mono_with_combo) {
|
| 975 | 3x |
mono@stopping <- mono@stopping | |
| 976 | 3x |
StoppingExternal(report_label = "Stop Mono with Combo") |
| 977 |
} |
|
| 978 | ||
| 979 | 10x |
.DesignGrouped( |
| 980 | 10x |
model = model, |
| 981 | 10x |
mono = mono, |
| 982 | 10x |
combo = combo, |
| 983 | 10x |
first_cohort_mono_only = first_cohort_mono_only, |
| 984 | 10x |
same_dose_for_all = same_dose_for_all, |
| 985 | 10x |
same_dose_for_start = same_dose_for_start |
| 986 |
) |
|
| 987 |
} |
|
| 988 | ||
| 989 |
## default constructor ---- |
|
| 990 | ||
| 991 |
#' @rdname DesignGrouped-class |
|
| 992 |
#' @note Typically, end-users will not use the `.DefaultDesignGrouped()` function. |
|
| 993 |
#' @export |
|
| 994 |
.DefaultDesignGrouped <- .DesignGrouped |
|
| 995 | ||
| 996 |
# RuleDesignOrdinal ---- |
|
| 997 | ||
| 998 |
## class ---- |
|
| 999 | ||
| 1000 |
#' `RuleDesignOrdinal` |
|
| 1001 |
#' |
|
| 1002 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1003 |
#' |
|
| 1004 |
#' [`RuleDesignOrdinal`] is the class for rule-based designs. The difference between |
|
| 1005 |
#' this class and the [`DesignOrdinal`] class is that [`RuleDesignOrdinal`] |
|
| 1006 |
#' does not contain `model`, `stopping` and `increments` slots. |
|
| 1007 |
#' |
|
| 1008 |
#' @details Please note that the cohort size rules need to be wrapped into |
|
| 1009 |
#' the corresponding [CohortSizeOrdinal] class, before a successful evaluation of the |
|
| 1010 |
#' corresponding methods can take place. Note also that these wrappers cannot be nested, |
|
| 1011 |
#' i.e., you cannot have a [CohortSizeOrdinal] inside another [CohortSizeOrdinal] |
|
| 1012 |
#' (which also would not make sense) because it would not be clear which event grade to use |
|
| 1013 |
#' for the methods calculation. However, multiple rules can be combined using the operators |
|
| 1014 |
#' defined, e.g., |
|
| 1015 |
#' `CohortSizeMin(list(CohortSizeOrdinal(1L, rule1), CohortSizeOrdinal(2L, rule2)))`. |
|
| 1016 |
#' |
|
| 1017 |
#' @slot next_best (`NextBestOrdinal`)\cr how to find the next best dose. |
|
| 1018 |
#' @slot cohort_size (`CohortSize`)\cr rules for the cohort sizes. |
|
| 1019 |
#' @slot data (`DataOrdinal`)\cr specifies dose grid, any previous data, etc. |
|
| 1020 |
#' @slot starting_dose (`number`)\cr the starting dose, it must lie on the dose |
|
| 1021 |
#' grid in `data`. |
|
| 1022 |
#' |
|
| 1023 |
#' @aliases RuleDesignOrdinal |
|
| 1024 |
#' @export |
|
| 1025 |
#' |
|
| 1026 |
.RuleDesignOrdinal <- setClass( |
|
| 1027 |
Class = "RuleDesignOrdinal", |
|
| 1028 |
slots = c( |
|
| 1029 |
next_best = "NextBestOrdinal", |
|
| 1030 |
cohort_size = "CohortSize", |
|
| 1031 |
data = "DataOrdinal", |
|
| 1032 |
starting_dose = "numeric" |
|
| 1033 |
), |
|
| 1034 |
prototype = prototype( |
|
| 1035 |
next_best = .NextBestOrdinal(), |
|
| 1036 |
cohort_size = CohortSizeOrdinal(1L, CohortSizeConst(3L)), |
|
| 1037 |
data = DataOrdinal(doseGrid = 1:3), |
|
| 1038 |
starting_dose = 1 |
|
| 1039 |
), |
|
| 1040 |
contains = "CrmPackClass", |
|
| 1041 |
validity = v_rule_design_ordinal |
|
| 1042 |
) |
|
| 1043 | ||
| 1044 |
## constructor ---- |
|
| 1045 | ||
| 1046 |
#' @rdname RuleDesignOrdinal-class |
|
| 1047 |
#' |
|
| 1048 |
#' @param next_best (`NextBestOrdinal`)\cr see slot definition. |
|
| 1049 |
#' @param cohort_size (`CohortSize`)\cr see slot definition. |
|
| 1050 |
#' @param data (`DataOrdinal`)\cr see slot definition. |
|
| 1051 |
#' @param starting_dose (`number`)\cr see slot definition. |
|
| 1052 |
#' |
|
| 1053 |
#' @export |
|
| 1054 |
#' @example examples/Design-class-RuleDesignOrdinal.R |
|
| 1055 |
#' |
|
| 1056 |
RuleDesignOrdinal <- function( |
|
| 1057 |
next_best, |
|
| 1058 |
cohort_size, |
|
| 1059 |
data, |
|
| 1060 |
starting_dose |
|
| 1061 |
) {
|
|
| 1062 | 18x |
new( |
| 1063 | 18x |
"RuleDesignOrdinal", |
| 1064 | 18x |
next_best = next_best, |
| 1065 | 18x |
cohort_size = cohort_size, |
| 1066 | 18x |
data = data, |
| 1067 | 18x |
starting_dose = as.numeric(starting_dose) |
| 1068 |
) |
|
| 1069 |
} |
|
| 1070 | ||
| 1071 |
#' @rdname RuleDesignOrdinal-class |
|
| 1072 |
#' @note Typically, end users will not use the `.DefaultRuleDesignOrdinal()` function. |
|
| 1073 |
#' @export |
|
| 1074 | ||
| 1075 |
.DefaultRuleDesignOrdinal <- function() {
|
|
| 1076 | 9x |
RuleDesignOrdinal( |
| 1077 | 9x |
next_best = NextBestOrdinal( |
| 1078 | 9x |
1L, |
| 1079 | 9x |
NextBestMTD(target = 0.25, derive = function(x) mean(x, na.rm = TRUE)) |
| 1080 |
), |
|
| 1081 | 9x |
cohort_size = CohortSizeOrdinal(1L, CohortSizeConst(size = 3L)), |
| 1082 | 9x |
data = DataOrdinal(doseGrid = c(5, 10, 15, 25, 35, 50, 80)), |
| 1083 | 9x |
starting_dose = 5 |
| 1084 |
) |
|
| 1085 |
} |
|
| 1086 | ||
| 1087 |
# DesignOrdinal ---- |
|
| 1088 | ||
| 1089 |
## class ---- |
|
| 1090 | ||
| 1091 |
#' `DesignOrdinal` |
|
| 1092 |
#' |
|
| 1093 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1094 |
#' |
|
| 1095 |
#' [`DesignOrdinal`] is the class for rule-based ordinal designs. The difference |
|
| 1096 |
#' between this class and its parent [`RuleDesignOrdinal`] class is that the |
|
| 1097 |
#' [`DesignOrdinal`] class contains additional `model`, `stopping`, |
|
| 1098 |
#' `increments` and `pl_cohort_size` slots. |
|
| 1099 |
#' |
|
| 1100 |
#' @details Please note that stopping, increments or cohort size rules need to be wrapped into |
|
| 1101 |
#' the corresponding [StoppingOrdinal], [IncrementsOrdinal] or [CohortSizeOrdinal] classes, before |
|
| 1102 |
#' a successful evaluation of the corresponding methods can take place. |
|
| 1103 |
#' Note also that these wrappers cannot be nested, i.e., you cannot have an [IncrementsOrdinal] inside |
|
| 1104 |
#' another [IncrementsOrdinal] (which also would not make sense) because it would not be clear which |
|
| 1105 |
#' event grade to use for the methods calculation. |
|
| 1106 |
#' However, multiple rules can be combined using the operators defined for these classes, e.g., |
|
| 1107 |
#' `StoppingOrdinal(1L, rule1 & rule2) | StoppingOrdinal(2L, rule3)`. |
|
| 1108 |
#' |
|
| 1109 |
#' @slot model (`LogisticLogNormalOrdinal`)\cr the model to be used. |
|
| 1110 |
#' @slot stopping (`Stopping`)\cr stopping rule(s) for the trial. |
|
| 1111 |
#' @slot increments (`Increments`)\cr how to control increments between dose levels. |
|
| 1112 |
#' @slot pl_cohort_size (`CohortSize`)\cr rules for the cohort sizes for placebo, |
|
| 1113 |
#' if any planned (defaults to constant 0 placebo patients). |
|
| 1114 |
#' |
|
| 1115 |
#' @aliases DesignOrdinal |
|
| 1116 |
#' @export |
|
| 1117 |
#' |
|
| 1118 |
.DesignOrdinal <- setClass( |
|
| 1119 |
Class = "DesignOrdinal", |
|
| 1120 |
slots = c( |
|
| 1121 |
model = "LogisticLogNormalOrdinal", |
|
| 1122 |
stopping = "Stopping", |
|
| 1123 |
increments = "Increments", |
|
| 1124 |
pl_cohort_size = "CohortSize" |
|
| 1125 |
), |
|
| 1126 |
prototype = prototype( |
|
| 1127 |
model = .LogisticLogNormalOrdinal(), |
|
| 1128 |
next_best = .NextBestOrdinal(), |
|
| 1129 |
stopping = .StoppingOrdinal(), |
|
| 1130 |
increments = .IncrementsOrdinal(), |
|
| 1131 |
pl_cohort_size = CohortSizeOrdinal(1L, CohortSizeConst(3L)) |
|
| 1132 |
), |
|
| 1133 |
contains = "RuleDesignOrdinal" |
|
| 1134 |
) |
|
| 1135 | ||
| 1136 |
## constructor ---- |
|
| 1137 | ||
| 1138 |
#' @rdname DesignOrdinal-class |
|
| 1139 |
#' |
|
| 1140 |
#' @param model (`LogisticLogNormalOrdinal`)\cr see slot definition. |
|
| 1141 |
#' @param stopping (`Stopping`)\cr see slot definition. |
|
| 1142 |
#' @param increments (`Increments`)\cr see slot definition. |
|
| 1143 |
#' @param pl_cohort_size (`CohortSize`)\cr see slot definition. |
|
| 1144 |
#' @inheritDotParams RuleDesignOrdinal |
|
| 1145 |
#' |
|
| 1146 |
#' @export |
|
| 1147 |
#' @example examples/Design-class-DesignOrdinal.R |
|
| 1148 |
#' |
|
| 1149 |
#' |
|
| 1150 |
DesignOrdinal <- function( |
|
| 1151 |
model, |
|
| 1152 |
stopping, |
|
| 1153 |
increments, |
|
| 1154 |
pl_cohort_size = CohortSizeOrdinal(1L, CohortSizeConst(0L)), |
|
| 1155 |
... |
|
| 1156 |
) {
|
|
| 1157 | 8x |
start <- RuleDesignOrdinal(...) |
| 1158 | 8x |
new( |
| 1159 | 8x |
"DesignOrdinal", |
| 1160 | 8x |
start, |
| 1161 | 8x |
model = model, |
| 1162 | 8x |
stopping = stopping, |
| 1163 | 8x |
increments = increments, |
| 1164 | 8x |
pl_cohort_size = pl_cohort_size |
| 1165 |
) |
|
| 1166 |
} |
|
| 1167 | ||
| 1168 |
## default constructor ---- |
|
| 1169 | ||
| 1170 |
#' @rdname DesignOrdinal-class |
|
| 1171 |
#' @note Typically, end users will not use the `.DefaultDesignOrdinal()` function. |
|
| 1172 |
#' @export |
|
| 1173 |
.DefaultDesignOrdinal <- function() {
|
|
| 1174 | 6x |
my_size1 <- CohortSizeRange( |
| 1175 | 6x |
intervals = c(0, 30), |
| 1176 | 6x |
cohort_size = c(1, 3) |
| 1177 |
) |
|
| 1178 | 6x |
my_size2 <- CohortSizeDLT( |
| 1179 | 6x |
intervals = c(0, 1), |
| 1180 | 6x |
cohort_size = c(1, 3) |
| 1181 |
) |
|
| 1182 | 6x |
my_size <- CohortSizeOrdinal(1L, maxSize(my_size1, my_size2)) |
| 1183 | ||
| 1184 | 6x |
my_stopping1 <- StoppingMinCohorts(nCohorts = 3) |
| 1185 | 6x |
my_stopping2 <- StoppingTargetProb( |
| 1186 | 6x |
target = c(0.2, 0.35), |
| 1187 | 6x |
prob = 0.5 |
| 1188 |
) |
|
| 1189 | 6x |
my_stopping3 <- StoppingMinPatients(nPatients = 20) |
| 1190 | 6x |
my_stopping <- StoppingOrdinal( |
| 1191 | 6x |
1L, |
| 1192 | 6x |
(my_stopping1 & my_stopping2) | my_stopping3 |
| 1193 |
) |
|
| 1194 | ||
| 1195 |
# Initialize the design. |
|
| 1196 | 6x |
design <- DesignOrdinal( |
| 1197 | 6x |
model = LogisticLogNormalOrdinal( |
| 1198 | 6x |
mean = c(-3, -4, 1), |
| 1199 | 6x |
cov = diag(c(3, 4, 1)), |
| 1200 | 6x |
ref_dose = 50 |
| 1201 |
), |
|
| 1202 | 6x |
next_best = NextBestOrdinal( |
| 1203 | 6x |
1L, |
| 1204 | 6x |
NextBestNCRM( |
| 1205 | 6x |
target = c(0.2, 0.35), |
| 1206 | 6x |
overdose = c(0.35, 1), |
| 1207 | 6x |
max_overdose_prob = 0.25 |
| 1208 |
) |
|
| 1209 |
), |
|
| 1210 | 6x |
stopping = my_stopping, |
| 1211 | 6x |
increments = IncrementsOrdinal( |
| 1212 | 6x |
1L, |
| 1213 | 6x |
IncrementsRelative( |
| 1214 | 6x |
intervals = c(0, 20), |
| 1215 | 6x |
increments = c(1, 0.33) |
| 1216 |
) |
|
| 1217 |
), |
|
| 1218 | 6x |
cohort_size = my_size, |
| 1219 | 6x |
data = DataOrdinal( |
| 1220 | 6x |
doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100), |
| 1221 | 6x |
yCategories = c("No tox" = 0L, "Sub-tox AE" = 1L, "DLT" = 2L)
|
| 1222 |
), |
|
| 1223 | 6x |
starting_dose = 3 |
| 1224 |
) |
|
| 1225 |
} |
| 1 |
#' Helper Function to Set and Save the RNG Seed |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("stable")`
|
|
| 4 |
#' |
|
| 5 |
#' This code is basically copied from `stats:::simulate.lm`. |
|
| 6 |
#' |
|
| 7 |
#' @param seed an object specifying if and how the random number generator |
|
| 8 |
#' should be initialized ("seeded"). Either `NULL` (default) or an
|
|
| 9 |
#' integer that will be used in a call to [set.seed()] before |
|
| 10 |
#' simulating the response vectors. If set, the value is saved as the |
|
| 11 |
#' `seed` slot of the returned object. The default, `NULL` will |
|
| 12 |
#' not change the random generator state. |
|
| 13 |
#' @return The integer vector containing the random number generate state will |
|
| 14 |
#' be returned, in order to call this function with this input to reproduce |
|
| 15 |
#' the obtained simulation results. |
|
| 16 |
#' |
|
| 17 |
#' @export |
|
| 18 |
set_seed <- function(seed = NULL) {
|
|
| 19 | 45x |
assert_number(seed, null.ok = TRUE) |
| 20 | ||
| 21 | 45x |
if (!exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
|
| 22 | ! |
runif(1) |
| 23 |
} |
|
| 24 | ||
| 25 | 45x |
if (is.null(seed)) {
|
| 26 | 4x |
get(".Random.seed", envir = .GlobalEnv)
|
| 27 |
} else {
|
|
| 28 | 41x |
seed <- as.integer(seed) |
| 29 | 41x |
r_seed <- get(".Random.seed", envir = .GlobalEnv)
|
| 30 |
# Make sure r_seed exists in parent frame. |
|
| 31 | 41x |
assign(".r_seed", r_seed, envir = parent.frame())
|
| 32 | 41x |
set.seed(seed) |
| 33 |
# Here we need the r_seed in the parent.frame! |
|
| 34 | 41x |
do.call( |
| 35 | 41x |
"on.exit", |
| 36 | 41x |
list(quote(assign(".Random.seed", .r_seed, envir = .GlobalEnv))),
|
| 37 | 41x |
envir = parent.frame() |
| 38 |
) |
|
| 39 | 41x |
structure(seed, kind = as.list(RNGkind())) |
| 40 |
} |
|
| 41 |
} |
|
| 42 | ||
| 43 |
#' Helper Function to Obtain Simulation Results List |
|
| 44 |
#' |
|
| 45 |
#' The function `fun` can use variables that are visible to itself. |
|
| 46 |
#' The names of these variables have to be given in the vector `vars`. |
|
| 47 |
#' |
|
| 48 |
#' @param fun (`function`)\cr the simulation function for a single iteration, which takes as |
|
| 49 |
#' single parameter the iteration index. |
|
| 50 |
#' @param nsim number of simulations to be conducted. |
|
| 51 |
#' @param vars names of the variables. |
|
| 52 |
#' @param parallel should the simulation runs be parallelized across the |
|
| 53 |
#' clusters of the computer? |
|
| 54 |
#' @param n_cores how many cores should be used for parallel computing? |
|
| 55 |
#' @return The list with all simulation results (one iteration corresponds |
|
| 56 |
#' to one list element). |
|
| 57 |
#' |
|
| 58 |
#' @importFrom parallel makeCluster |
|
| 59 |
#' @importFrom parallelly availableCores |
|
| 60 |
#' @keywords internal programming |
|
| 61 |
get_result_list <- function( |
|
| 62 |
fun, |
|
| 63 |
nsim, |
|
| 64 |
vars, |
|
| 65 |
parallel, |
|
| 66 |
n_cores |
|
| 67 |
) {
|
|
| 68 | 45x |
assert_flag(parallel) |
| 69 | 44x |
assert_integerish(n_cores, lower = 1) |
| 70 | ||
| 71 | 43x |
if (!parallel) {
|
| 72 | 43x |
lapply( |
| 73 | 43x |
X = seq_len(nsim), |
| 74 | 43x |
FUN = fun |
| 75 |
) |
|
| 76 |
} else {
|
|
| 77 |
# Process all simulations. |
|
| 78 | ! |
cores <- min( |
| 79 | ! |
as.integer(n_cores), |
| 80 | ! |
parallelly::availableCores() |
| 81 |
) |
|
| 82 | ||
| 83 |
# Start the cluster. |
|
| 84 | ! |
cl <- parallel::makeCluster(cores) |
| 85 | ||
| 86 |
# Load the required R package. |
|
| 87 | ! |
parallel::clusterEvalQ(cl, {
|
| 88 | ! |
library(crmPack) |
| 89 | ! |
NULL |
| 90 |
}) |
|
| 91 | ||
| 92 |
# Export local variables from the caller environment. |
|
| 93 |
# Note: parent.frame() is different from parent.env() which returns |
|
| 94 |
# the environment where this function has been defined! |
|
| 95 | ! |
parallel::clusterExport( |
| 96 | ! |
cl = cl, |
| 97 | ! |
varlist = vars, |
| 98 | ! |
envir = parent.frame() |
| 99 |
) |
|
| 100 | ||
| 101 |
# Export all global variables. |
|
| 102 | ! |
parallel::clusterExport( |
| 103 | ! |
cl = cl, |
| 104 | ! |
varlist = ls(.GlobalEnv) |
| 105 |
) |
|
| 106 | ||
| 107 |
# Load user extensions from global options. |
|
| 108 | ! |
crmpack_extensions <- getOption("crmpack_extensions")
|
| 109 | ! |
if (is.null(crmpack_extensions) != TRUE) {
|
| 110 | ! |
tryCatch( |
| 111 |
{
|
|
| 112 | ! |
parallel::clusterCall(cl, crmpack_extensions) |
| 113 |
}, |
|
| 114 | ! |
error = function(e) {
|
| 115 | ! |
stop("Failed to export crmpack_extensions: ", e$message)
|
| 116 |
} |
|
| 117 |
) |
|
| 118 |
} |
|
| 119 | ||
| 120 |
# Do the computations in parallel. |
|
| 121 | ! |
res <- parallel::parLapply( |
| 122 | ! |
cl = cl, |
| 123 | ! |
X = seq_len(nsim), |
| 124 | ! |
fun = fun |
| 125 |
) |
|
| 126 | ||
| 127 |
# Stop the cluster. |
|
| 128 | ! |
parallel::stopCluster(cl) |
| 129 | ||
| 130 | ! |
res |
| 131 |
} |
|
| 132 |
} |
|
| 133 | ||
| 134 | ||
| 135 |
#' Helper Function to call truth calculation |
|
| 136 |
#' |
|
| 137 |
#' @param dose (`number`)\cr current dose. |
|
| 138 |
#' @param truth (`function`)\cr defines the true probability for a DLT at a dose. |
|
| 139 |
#' @param this_args (`data.frame`)\cr list of arguments for the truth. |
|
| 140 |
#' @return The updated `this_truth`. |
|
| 141 |
#' |
|
| 142 |
#' @keywords internal |
|
| 143 |
h_this_truth <- function(dose, this_args, truth) {
|
|
| 144 | 118x |
do.call( |
| 145 | 118x |
truth, |
| 146 |
## First argument: the dose |
|
| 147 | 118x |
c( |
| 148 | 118x |
dose, |
| 149 |
## Following arguments |
|
| 150 | 118x |
this_args |
| 151 |
) |
|
| 152 |
) |
|
| 153 |
} |
|
| 154 | ||
| 155 | ||
| 156 |
#' Helper Function to create return list for Simulations output |
|
| 157 |
#' |
|
| 158 |
#' @param resultList (`list`)\cr raw iteration output. |
|
| 159 |
#' |
|
| 160 |
#' @return aggregated output for simulation object `list`. |
|
| 161 |
#' |
|
| 162 |
#' @keywords internal |
|
| 163 |
h_simulations_output_format <- function(resultList) {
|
|
| 164 |
## put everything in the Simulations format: |
|
| 165 | ||
| 166 |
## setup the list for the simulated data objects |
|
| 167 | 14x |
dataList <- lapply(resultList, "[[", "data") |
| 168 | ||
| 169 |
## the vector of the final dose recommendations |
|
| 170 | 14x |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "dose")) |
| 171 | ||
| 172 |
## setup the list for the final fits |
|
| 173 | 14x |
fitList <- lapply(resultList, "[[", "fit") |
| 174 | ||
| 175 |
## the reasons for stopping |
|
| 176 | 14x |
stopReasons <- lapply(resultList, "[[", "stop") |
| 177 | ||
| 178 |
# individual stopping rule results as matrix, labels as column names |
|
| 179 | 14x |
stopResults <- lapply(resultList, "[[", "report_results") |
| 180 | 14x |
stop_matrix <- as.matrix(do.call(rbind, stopResults)) |
| 181 | ||
| 182 |
# Result list of additional statistical summary. |
|
| 183 | 14x |
additional_stats <- lapply(resultList, "[[", "additional_stats") |
| 184 | ||
| 185 | 14x |
return(list( |
| 186 | 14x |
dataList = dataList, |
| 187 | 14x |
recommendedDoses = recommendedDoses, |
| 188 | 14x |
fitList = fitList, |
| 189 | 14x |
stopReasons = stopReasons, |
| 190 | 14x |
stopResults = stopResults, |
| 191 | 14x |
additional_stats = additional_stats, |
| 192 | 14x |
stop_matrix = stop_matrix |
| 193 |
)) |
|
| 194 |
} |
|
| 195 | ||
| 196 | ||
| 197 |
#' Helper function to recursively unpack stopping rules and return lists with |
|
| 198 |
#' logical value and label given |
|
| 199 |
#' |
|
| 200 |
#' @param stopit_tree object from simulate method |
|
| 201 |
#' @return named list |
|
| 202 | ||
| 203 |
h_unpack_stopit <- function(stopit_tree) {
|
|
| 204 | 614x |
label <- attr(stopit_tree, "report_label") |
| 205 | 614x |
value <- stopit_tree[1] |
| 206 | 614x |
names(value) <- label |
| 207 | 614x |
value |
| 208 | 614x |
if (is.null(attr(stopit_tree, "individual"))) {
|
| 209 | 507x |
return(value) |
| 210 |
} else {
|
|
| 211 | 107x |
return(unlist(c( |
| 212 | 107x |
value, |
| 213 | 107x |
lapply(attr(stopit_tree, "individual"), h_unpack_stopit) |
| 214 |
))) |
|
| 215 |
} |
|
| 216 |
} |
|
| 217 | ||
| 218 | ||
| 219 |
#' Helper function to determine the dlts including first separate and placebo |
|
| 220 |
#' condition |
|
| 221 |
#' |
|
| 222 |
#' @param data (`Data`)\cr what data to start from. |
|
| 223 |
#' @param dose (`number`)\cr current dose. |
|
| 224 |
#' @param prob (`function`)\cr defines the true probability for a DLT at a dose. |
|
| 225 |
#' @param prob_placebo (`function`)\cr defines the true probability for a DLT at a placebo condition. |
|
| 226 |
#' @param cohort_size (`number`)\cr the cohort size to use. |
|
| 227 |
#' @param cohort_size_placebo (`number`)\cr the cohort size to use for placebo condition. |
|
| 228 |
#' @param dose_grid (`numeric`)\cr the dose_grid as specified by the user. |
|
| 229 |
#' @param first_separate (`flag`)\cr whether the first patient is enrolled separately. |
|
| 230 |
#' @return updated data object |
|
| 231 |
#' @keywords internal |
|
| 232 | ||
| 233 |
h_determine_dlts <- function( |
|
| 234 |
data, |
|
| 235 |
dose, |
|
| 236 |
prob, |
|
| 237 |
prob_placebo, |
|
| 238 |
cohort_size, |
|
| 239 |
cohort_size_placebo, |
|
| 240 |
dose_grid, |
|
| 241 |
first_separate |
|
| 242 |
) {
|
|
| 243 | 236x |
assert_class(data, "Data") |
| 244 | 236x |
assert_number(dose) |
| 245 | 236x |
assert_number(prob) |
| 246 | 236x |
assert_number(cohort_size) |
| 247 | 236x |
assert_flag(first_separate) |
| 248 | ||
| 249 | 236x |
if (first_separate && cohort_size > 1) {
|
| 250 | 33x |
dlts <- rbinom(n = 1, size = 1, prob = prob) |
| 251 | 33x |
if ((data@placebo) && cohort_size_placebo > 0) {
|
| 252 | 4x |
dlts_placebo <- rbinom(n = 1, size = 1, prob = prob_placebo) |
| 253 |
} |
|
| 254 | 33x |
if (dlts == 0) {
|
| 255 | 20x |
dlts <- c(dlts, rbinom(n = cohort_size - 1L, size = 1, prob = prob)) |
| 256 | 20x |
if ((data@placebo) && cohort_size_placebo > 0) {
|
| 257 | 4x |
dlts_placebo <- c( |
| 258 | 4x |
dlts_placebo, |
| 259 | 4x |
rbinom( |
| 260 | 4x |
n = cohort_size_placebo, # cohort_size_placebo - 1? |
| 261 | 4x |
size = 1, |
| 262 | 4x |
prob = prob_placebo |
| 263 |
) |
|
| 264 |
) |
|
| 265 |
} |
|
| 266 |
} |
|
| 267 |
} else {
|
|
| 268 | 203x |
dlts <- rbinom(n = cohort_size, size = 1, prob = prob) |
| 269 | 203x |
if ((data@placebo) && cohort_size_placebo > 0) {
|
| 270 | 11x |
dlts_placebo <- rbinom( |
| 271 | 11x |
n = cohort_size_placebo, |
| 272 | 11x |
size = 1, |
| 273 | 11x |
prob = prob_placebo |
| 274 |
) |
|
| 275 |
} |
|
| 276 |
} |
|
| 277 | ||
| 278 | 236x |
if ((data@placebo) && cohort_size_placebo > 0) {
|
| 279 | 15x |
this_data <- update( |
| 280 | 15x |
object = data, |
| 281 | 15x |
x = dose_grid, |
| 282 | 15x |
y = dlts_placebo, |
| 283 | 15x |
check = FALSE |
| 284 |
) |
|
| 285 | ||
| 286 |
## update the data with active dose |
|
| 287 | 15x |
this_data <- update( |
| 288 | 15x |
object = this_data, |
| 289 | 15x |
x = dose, |
| 290 | 15x |
y = dlts, |
| 291 | 15x |
new_cohort = FALSE |
| 292 |
) |
|
| 293 |
} else {
|
|
| 294 |
## update the data with this cohort |
|
| 295 | 221x |
this_data <- update( |
| 296 | 221x |
object = data, |
| 297 | 221x |
x = dose, |
| 298 | 221x |
y = dlts |
| 299 |
) |
|
| 300 |
} |
|
| 301 | 236x |
return(this_data) |
| 302 |
} |
| 1 |
#' Internal Helper Functions for Validation of [`GeneralModel`] and [`ModelPseudo`] Objects |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("stable")`
|
|
| 4 |
#' |
|
| 5 |
#' These functions are only used internally to validate the format of an input |
|
| 6 |
#' [`GeneralModel`] and [`ModelPseudo`] or inherited classes and therefore are |
|
| 7 |
#' not exported. |
|
| 8 |
#' |
|
| 9 |
#' @name v_model_objects |
|
| 10 |
#' @param object (`GeneralModel`) or (`ModelPseudo`) \cr object to validate. |
|
| 11 |
#' @return A `character` vector with the validation failure messages, |
|
| 12 |
#' or `TRUE` in case validation passes. |
|
| 13 |
NULL |
|
| 14 | ||
| 15 |
#' @describeIn v_model_objects validates that the names of the |
|
| 16 |
#' arguments in `init` function are included in `datanames` or `datanames_prior` |
|
| 17 |
#' slots. |
|
| 18 |
v_general_model <- function(object) {
|
|
| 19 | 2x |
v <- Validate() |
| 20 | 2x |
v$check( |
| 21 | 2x |
h_check_fun_formals( |
| 22 | 2x |
object@init, |
| 23 | 2x |
allowed = union(object@datanames, object@datanames_prior) |
| 24 |
), |
|
| 25 | 2x |
"Arguments of the init function must be data names" |
| 26 |
) |
|
| 27 | 2x |
v$result() |
| 28 |
} |
|
| 29 | ||
| 30 |
#' @describeIn v_model_objects validates that the logistic Kadane model |
|
| 31 |
#' parameters are valid. |
|
| 32 |
v_model_logistic_kadane <- function(object) {
|
|
| 33 | 13x |
v <- Validate() |
| 34 | 13x |
v$check( |
| 35 | 13x |
test_probability(object@theta, bounds_closed = FALSE), |
| 36 | 13x |
"theta must be a probability scalar > 0 and < 1" |
| 37 |
) |
|
| 38 | 13x |
is_xmin_number <- test_number(object@xmin) |
| 39 | 13x |
v$check(is_xmin_number, "xmin must be scalar") |
| 40 | ||
| 41 | 13x |
is_xmax_number <- test_number(object@xmax) |
| 42 | 13x |
v$check(is_xmax_number, "xmax must be scalar") |
| 43 | ||
| 44 | 13x |
if (is_xmin_number && is_xmax_number) {
|
| 45 | 11x |
v$check( |
| 46 | 11x |
object@xmin < object@xmax, |
| 47 | 11x |
"xmin must be strictly smaller than xmax" |
| 48 |
) |
|
| 49 |
} |
|
| 50 | 13x |
v$result() |
| 51 |
} |
|
| 52 | ||
| 53 |
#' @describeIn v_model_objects validates that the logistic Kadane model |
|
| 54 |
#' parameters with a beta and gamma prior are valid. |
|
| 55 |
v_model_logistic_kadane_beta_gamma <- function(object) {
|
|
| 56 |
# nolintr |
|
| 57 | 10x |
v <- Validate() |
| 58 | 10x |
v$check( |
| 59 | 10x |
test_number(object@alpha, lower = .Machine$double.xmin, finite = TRUE), |
| 60 | 10x |
"Beta distribution shape parameter alpha must be a positive scalar" |
| 61 |
) |
|
| 62 | 10x |
v$check( |
| 63 | 10x |
test_number(object@beta, lower = .Machine$double.xmin, finite = TRUE), |
| 64 | 10x |
"Beta distribution shape parameter beta must be a positive scalar" |
| 65 |
) |
|
| 66 | 10x |
v$check( |
| 67 | 10x |
test_number(object@shape, lower = .Machine$double.xmin, finite = TRUE), |
| 68 | 10x |
"Gamma distribution shape parameter must be a positive scalar" |
| 69 |
) |
|
| 70 | 10x |
v$check( |
| 71 | 10x |
test_number(object@rate, lower = .Machine$double.xmin, finite = TRUE), |
| 72 | 10x |
"Gamma distribution rate parameter must be a positive scalar" |
| 73 |
) |
|
| 74 | 10x |
v$result() |
| 75 |
} |
|
| 76 | ||
| 77 |
#' @describeIn v_model_objects validates that `weightpar` is valid. |
|
| 78 |
v_model_logistic_normal_mix <- function(object) {
|
|
| 79 | 8x |
v <- Validate() |
| 80 | 8x |
v$check( |
| 81 | 8x |
h_test_named_numeric(object@weightpar, permutation.of = c("a", "b")),
|
| 82 | 8x |
"weightpar must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
| 83 |
) |
|
| 84 | 8x |
v$result() |
| 85 |
} |
|
| 86 | ||
| 87 |
#' @describeIn v_model_objects validates that `component` is a list with |
|
| 88 |
#' valid `ModelParamsNormal` objects as well as `weights` are correct. |
|
| 89 |
v_model_logistic_normal_fixed_mix <- function(object) {
|
|
| 90 |
# nolintr |
|
| 91 | 7x |
v <- Validate() |
| 92 | 7x |
v$check( |
| 93 | 7x |
all(sapply(object@components, test_class, "ModelParamsNormal")), |
| 94 | 7x |
"components must be a list with ModelParamsNormal S4 class objects" |
| 95 |
) |
|
| 96 | 7x |
comp_valid_result <- sapply(object@components, validObject, test = TRUE) |
| 97 | 7x |
comp_valid <- sapply(comp_valid_result, isTRUE) |
| 98 | 7x |
v$check( |
| 99 | 7x |
all(comp_valid), |
| 100 | 7x |
paste( |
| 101 | 7x |
"components must be a list with valid ModelParamsNormal S4 class objects", |
| 102 | 7x |
paste(unlist(comp_valid_result[!comp_valid]), collapse = ", "), |
| 103 | 7x |
collapse = ", ", |
| 104 | 7x |
sep = ", " |
| 105 |
) |
|
| 106 |
) |
|
| 107 | 7x |
v$check( |
| 108 | 7x |
length(object@components) == length(object@weights), |
| 109 | 7x |
"components must have same length as weights" |
| 110 |
) |
|
| 111 | 7x |
v$check( |
| 112 | 7x |
test_numeric( |
| 113 | 7x |
object@weights, |
| 114 | 7x |
lower = .Machine$double.xmin, |
| 115 | 7x |
finite = TRUE, |
| 116 | 7x |
any.missing = FALSE |
| 117 |
), |
|
| 118 | 7x |
"weights must be positive" |
| 119 |
) |
|
| 120 | 7x |
v$check( |
| 121 | 7x |
sum(object@weights) == 1, |
| 122 | 7x |
"weights must sum to 1" |
| 123 |
) |
|
| 124 | 7x |
v$check( |
| 125 | 7x |
test_flag(object@log_normal), |
| 126 | 7x |
"log_normal must be TRUE or FALSE" |
| 127 |
) |
|
| 128 | 7x |
v$result() |
| 129 |
} |
|
| 130 | ||
| 131 |
#' @describeIn v_model_objects validates that `share_weight` represents probability. |
|
| 132 |
v_model_logistic_log_normal_mix <- function(object) {
|
|
| 133 |
# nolintr |
|
| 134 | 3x |
v <- Validate() |
| 135 | 3x |
v$check( |
| 136 | 3x |
test_probability(object@share_weight), |
| 137 | 3x |
"share_weight does not specify a probability" |
| 138 |
) |
|
| 139 | 3x |
v$result() |
| 140 |
} |
|
| 141 | ||
| 142 |
#' @describeIn v_model_objects validates that [`DualEndpoint`] class slots are valid. |
|
| 143 |
v_model_dual_endpoint <- function(object) {
|
|
| 144 | 8x |
rmin <- .Machine$double.xmin |
| 145 | 8x |
v <- Validate() |
| 146 | ||
| 147 | 8x |
v$check( |
| 148 | 8x |
test_flag(object@use_log_dose), |
| 149 | 8x |
"use_log_dose must be TRUE or FALSE" |
| 150 |
) |
|
| 151 | 8x |
uf_sigma2W <- object@use_fixed["sigma2W"] # nolintr |
| 152 | 8x |
v$check( |
| 153 | 8x |
test_flag(uf_sigma2W), |
| 154 | 8x |
"use_fixed must be a named logical vector that contains name 'sigma2W'" |
| 155 |
) |
|
| 156 | 8x |
uf_rho <- object@use_fixed["rho"] |
| 157 | 8x |
v$check( |
| 158 | 8x |
test_flag(uf_rho), |
| 159 | 8x |
"use_fixed must be a named logical vector that contains name 'rho'" |
| 160 |
) |
|
| 161 | ||
| 162 | 8x |
if (isTRUE(uf_sigma2W)) {
|
| 163 | 5x |
v$check( |
| 164 | 5x |
test_number(object@sigma2W, lower = rmin, finite = TRUE), |
| 165 | 5x |
"sigma2W must be a positive and finite numerical scalar" |
| 166 |
) |
|
| 167 |
} else {
|
|
| 168 |
# object@sigma2W is a vector with parameters for InverseGamma(a, b). |
|
| 169 | 3x |
v$check( |
| 170 | 3x |
h_test_named_numeric(object@sigma2W, permutation.of = c("a", "b")),
|
| 171 | 3x |
"sigma2W must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
| 172 |
) |
|
| 173 |
} |
|
| 174 | ||
| 175 | 8x |
if (isTRUE(uf_rho)) {
|
| 176 | 5x |
v$check( |
| 177 | 5x |
test_number(object@rho, lower = -1 + rmin, upper = 1 - rmin), # rmin is ignored here! |
| 178 | 5x |
"rho must be a number in (-1, 1)" |
| 179 |
) |
|
| 180 |
} else {
|
|
| 181 |
# object@rho is a vector with parameters for Beta(a, b). |
|
| 182 | 3x |
v$check( |
| 183 | 3x |
h_test_named_numeric(object@rho, permutation.of = c("a", "b")),
|
| 184 | 3x |
"rho must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
| 185 |
) |
|
| 186 |
} |
|
| 187 | ||
| 188 | 8x |
v$result() |
| 189 |
} |
|
| 190 | ||
| 191 |
#' @describeIn v_model_objects validates that [`DualEndpointRW`] class slots are valid. |
|
| 192 |
v_model_dual_endpoint_rw <- function(object) {
|
|
| 193 | 5x |
v <- Validate() |
| 194 | 5x |
uf_sigma2W <- object@use_fixed["sigma2betaW"] # nolintr |
| 195 | 5x |
v$check( |
| 196 | 5x |
test_flag(uf_sigma2W), |
| 197 | 5x |
"use_fixed must be a named logical vector that contains name 'sigma2betaW'" |
| 198 |
) |
|
| 199 | 5x |
if (isTRUE(uf_sigma2W)) {
|
| 200 | 2x |
v$check( |
| 201 | 2x |
test_number( |
| 202 | 2x |
object@sigma2betaW, |
| 203 | 2x |
lower = .Machine$double.xmin, |
| 204 | 2x |
finite = TRUE |
| 205 |
), |
|
| 206 | 2x |
"sigma2betaW must be a positive and finite numerical scalar" |
| 207 |
) |
|
| 208 |
} else {
|
|
| 209 |
# object@sigma2betaW is a vector with parameters for InverseGamma(a, b). |
|
| 210 | 3x |
v$check( |
| 211 | 3x |
h_test_named_numeric(object@sigma2betaW, permutation.of = c("a", "b")),
|
| 212 | 3x |
"sigma2betaW must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
| 213 |
) |
|
| 214 |
} |
|
| 215 | 5x |
v$result() |
| 216 |
} |
|
| 217 | ||
| 218 |
#' @describeIn v_model_objects validates that [`DualEndpointBeta`] class slots are valid. |
|
| 219 |
v_model_dual_endpoint_beta <- function(object) {
|
|
| 220 | 5x |
v <- Validate() |
| 221 | ||
| 222 | 5x |
for (s in c("E0", "Emax", "delta1", "mode")) {
|
| 223 | 20x |
rmin <- .Machine$double.xmin |
| 224 | 20x |
uf <- object@use_fixed[s] |
| 225 | ||
| 226 | 20x |
v$check( |
| 227 | 20x |
test_flag(uf), |
| 228 | 20x |
paste0( |
| 229 | 20x |
"use_fixed must be a named logical vector that contains name '", |
| 230 | 20x |
s, |
| 231 |
"'" |
|
| 232 |
) |
|
| 233 |
) |
|
| 234 | 20x |
if (isTRUE(uf)) {
|
| 235 | 12x |
if (s %in% c("delta1", "mode")) {
|
| 236 | 8x |
v$check( |
| 237 | 8x |
test_number(slot(object, s), lower = rmin, finite = TRUE), |
| 238 | 8x |
paste(s, "must be a positive and finite numerical scalar") |
| 239 |
) |
|
| 240 |
} |
|
| 241 |
} else {
|
|
| 242 |
# s is a vector with parameters for Uniform(s[1], s[2]) prior. |
|
| 243 | 8x |
v$check( |
| 244 | 8x |
test_numeric( |
| 245 | 8x |
slot(object, s), |
| 246 | 8x |
lower = 0, |
| 247 | 8x |
finite = TRUE, |
| 248 | 8x |
any.missing = FALSE, |
| 249 | 8x |
len = 2, |
| 250 | 8x |
unique = TRUE, |
| 251 | 8x |
sorted = TRUE |
| 252 |
), |
|
| 253 | 8x |
paste( |
| 254 | 8x |
s, |
| 255 | 8x |
"must be a numerical vector of length two with non-negative, finite, unique and sorted (asc.) values" |
| 256 |
) |
|
| 257 |
) |
|
| 258 |
} |
|
| 259 |
} |
|
| 260 | ||
| 261 | 5x |
v$result() |
| 262 |
} |
|
| 263 | ||
| 264 |
#' @describeIn v_model_objects validates that [`DualEndpointEmax`] class slots are valid. |
|
| 265 |
v_model_dual_endpoint_emax <- function(object) {
|
|
| 266 | 4x |
v <- Validate() |
| 267 | ||
| 268 | 4x |
for (s in c("E0", "Emax", "ED50")) {
|
| 269 | 12x |
rmin <- .Machine$double.xmin |
| 270 | 12x |
uf <- object@use_fixed[s] |
| 271 | ||
| 272 | 12x |
v$check( |
| 273 | 12x |
test_flag(uf), |
| 274 | 12x |
paste0( |
| 275 | 12x |
"use_fixed must be a named logical vector that contains name '", |
| 276 | 12x |
s, |
| 277 |
"'" |
|
| 278 |
) |
|
| 279 |
) |
|
| 280 | 12x |
if (isTRUE(uf)) {
|
| 281 | 6x |
v$check( |
| 282 | 6x |
test_number(slot(object, s), lower = rmin, finite = TRUE), |
| 283 | 6x |
paste(s, "must be a positive and finite numerical scalar") |
| 284 |
) |
|
| 285 |
} else {
|
|
| 286 |
# s is a vector with parameters for Uniform(s[1], s[2]) prior. |
|
| 287 | 6x |
v$check( |
| 288 | 6x |
test_numeric( |
| 289 | 6x |
slot(object, s), |
| 290 | 6x |
lower = 0, |
| 291 | 6x |
finite = TRUE, |
| 292 | 6x |
any.missing = FALSE, |
| 293 | 6x |
len = 2, |
| 294 | 6x |
unique = TRUE, |
| 295 | 6x |
sorted = TRUE |
| 296 |
), |
|
| 297 | 6x |
paste( |
| 298 | 6x |
s, |
| 299 | 6x |
"must be a numerical vector of length two with non-negative, finite, unique and sorted (asc.) values" |
| 300 |
) |
|
| 301 |
) |
|
| 302 |
} |
|
| 303 |
} |
|
| 304 | ||
| 305 | 4x |
v$result() |
| 306 |
} |
|
| 307 | ||
| 308 |
#' @describeIn v_model_objects validates that [`LogisticIndepBeta`] class slots are valid. |
|
| 309 |
v_model_logistic_indep_beta <- function(object) {
|
|
| 310 | 8x |
v <- Validate() |
| 311 | ||
| 312 | 8x |
dle_len <- length(object@binDLE) |
| 313 | 8x |
v$check( |
| 314 | 8x |
test_numeric( |
| 315 | 8x |
object@binDLE, |
| 316 | 8x |
finite = TRUE, |
| 317 | 8x |
any.missing = FALSE, |
| 318 | 8x |
min.len = 2 |
| 319 |
), |
|
| 320 | 8x |
"binDLE must be a finite numerical vector of minimum length 2, without missing values" |
| 321 |
) |
|
| 322 | 8x |
v$check( |
| 323 | 8x |
test_numeric( |
| 324 | 8x |
object@DLEdose, |
| 325 | 8x |
finite = TRUE, |
| 326 | 8x |
any.missing = FALSE, |
| 327 | 8x |
len = dle_len |
| 328 |
), |
|
| 329 | 8x |
"DLEdose must be a finite numerical vector of the same length as 'binDLE', without missing values" |
| 330 |
) |
|
| 331 | 8x |
v$check( |
| 332 | 8x |
test_integer(object@DLEweights, any.missing = FALSE, len = dle_len), |
| 333 | 8x |
"DLEweights must be an integer vector of the same length as 'binDLE', without missing values" |
| 334 |
) |
|
| 335 | 8x |
v$check( |
| 336 | 8x |
test_number(object@phi1), |
| 337 | 8x |
"phi1 must be a numerical scalar" |
| 338 |
) |
|
| 339 | 8x |
v$check( |
| 340 | 8x |
test_number(object@phi2), |
| 341 | 8x |
"phi2 must be a numerical scalar" |
| 342 |
) |
|
| 343 | 8x |
v$check( |
| 344 | 8x |
h_is_positive_definite(object@Pcov), |
| 345 | 8x |
"Pcov must be 2x2 positive-definite matrix without any missing values" |
| 346 |
) |
|
| 347 | 8x |
v$result() |
| 348 |
} |
|
| 349 | ||
| 350 |
#' @describeIn v_model_objects validates that [`Effloglog`] class slots are valid. |
|
| 351 |
v_model_eff_log_log <- function(object) {
|
|
| 352 | 27x |
rmin <- .Machine$double.xmin |
| 353 | ||
| 354 | 27x |
v <- Validate() |
| 355 | 27x |
v$check( |
| 356 | 27x |
test_numeric(object@eff, finite = TRUE, any.missing = FALSE, min.len = 2), |
| 357 | 27x |
"eff must be a finite numerical vector of minimum length 2, without missing values" |
| 358 |
) |
|
| 359 | 27x |
eff_dose_ok <- test_numeric( |
| 360 | 27x |
object@eff_dose, |
| 361 | 27x |
lower = rmin, |
| 362 | 27x |
finite = TRUE, |
| 363 | 27x |
any.missing = FALSE, |
| 364 | 27x |
len = length(object@eff) |
| 365 |
) |
|
| 366 | 27x |
v$check( |
| 367 | 27x |
eff_dose_ok, |
| 368 | 27x |
"eff_dose must be a finite numerical vector of the same length as 'eff', without missing values" |
| 369 |
) |
|
| 370 | 27x |
v$check( |
| 371 | 27x |
test_flag(object@use_fixed), |
| 372 | 27x |
"use_fixed must be a flag" |
| 373 |
) |
|
| 374 | 27x |
if (isTRUE(object@use_fixed)) {
|
| 375 | 1x |
v$check( |
| 376 | 1x |
test_number(object@nu, lower = rmin, finite = TRUE), |
| 377 | 1x |
"nu must be a positive and finite numerical scalar" |
| 378 |
) |
|
| 379 |
} else {
|
|
| 380 |
# object@nu is a vector with parameters for Gamma(a, b). |
|
| 381 | 26x |
v$check( |
| 382 | 26x |
h_test_named_numeric(object@nu, permutation.of = c("a", "b")),
|
| 383 | 26x |
"nu must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
| 384 |
) |
|
| 385 |
} |
|
| 386 | 27x |
const_ok <- test_number(object@const, lower = 0) |
| 387 | 27x |
v$check(const_ok, "const must be a non-negative number") |
| 388 | 27x |
if (eff_dose_ok && const_ok) {
|
| 389 | 23x |
v$check( |
| 390 | 23x |
min(object@data@doseGrid, object@eff_dose) > 1 - object@const, |
| 391 | 23x |
"For log-log model, doses and const must be such that dose + const > 1" |
| 392 |
) |
|
| 393 |
} |
|
| 394 | 27x |
v$check( |
| 395 | 27x |
test_number(object@theta1), |
| 396 | 27x |
"theta1 must be a numerical scalar" |
| 397 |
) |
|
| 398 | 27x |
v$check( |
| 399 | 27x |
test_number(object@theta2), |
| 400 | 27x |
"theta2 must be a numerical scalar" |
| 401 |
) |
|
| 402 | 27x |
nobs_no_dlt <- sum(!object@data@y) |
| 403 | 27x |
if (nobs_no_dlt + length(object@eff) > 2) {
|
| 404 | 21x |
v$check( |
| 405 | 21x |
h_is_positive_definite(object@Pcov), |
| 406 | 21x |
"Pcov must be 2x2 positive-definite matrix without any missing values" |
| 407 |
) |
|
| 408 |
} else {
|
|
| 409 | 6x |
v$check( |
| 410 | 6x |
test_matrix(object@Pcov, mode = "numeric", nrows = 2, ncols = 2) && |
| 411 | 6x |
all(is.na(object@Pcov)), |
| 412 | 6x |
"Pcov must be 2x2 numeric matrix with all values missing if the length of combined data is 2" |
| 413 |
) |
|
| 414 |
} |
|
| 415 | 27x |
v$check( |
| 416 | 27x |
test_numeric(object@mu, finite = TRUE, len = 2), |
| 417 | 27x |
"mu must be a finite numerical vector of length 2" |
| 418 |
) |
|
| 419 | 27x |
Xnrow <- ifelse(nobs_no_dlt > 0, nobs_no_dlt, length(object@eff_dose)) |
| 420 | 27x |
v$check( |
| 421 | 27x |
test_matrix( |
| 422 | 27x |
object@X, |
| 423 | 27x |
mode = "numeric", |
| 424 | 27x |
nrows = Xnrow, |
| 425 | 27x |
ncols = 2, |
| 426 | 27x |
any.missing = FALSE |
| 427 |
), |
|
| 428 | 27x |
paste( |
| 429 | 27x |
"X must be a finite numerical matrix of size", |
| 430 | 27x |
Xnrow, |
| 431 | 27x |
"x 2, without any missing values" |
| 432 |
) |
|
| 433 |
) |
|
| 434 | 27x |
v$check( |
| 435 | 27x |
all(object@X[, 1] == 1), |
| 436 | 27x |
"X must be a design matrix, i.e. first column must be of 1s" |
| 437 |
) |
|
| 438 | 27x |
v$check( |
| 439 | 27x |
h_is_positive_definite(object@Q), |
| 440 | 27x |
"Q must be 2x2 positive-definite matrix without any missing values" |
| 441 |
) |
|
| 442 | 27x |
v$check( |
| 443 | 27x |
test_numeric(object@Y, finite = TRUE, any.missing = FALSE, len = Xnrow), |
| 444 | 27x |
paste( |
| 445 | 27x |
"Y must be a finite numerical vector of length", |
| 446 | 27x |
Xnrow, |
| 447 | 27x |
"and without any missing values" |
| 448 |
) |
|
| 449 |
) |
|
| 450 | 27x |
v$result() |
| 451 |
} |
|
| 452 | ||
| 453 |
#' @describeIn v_model_objects validates that [`EffFlexi`] class slots are valid. |
|
| 454 |
v_model_eff_flexi <- function(object) {
|
|
| 455 | 19x |
rmin <- .Machine$double.xmin |
| 456 | ||
| 457 | 19x |
v <- Validate() |
| 458 | 19x |
v$check( |
| 459 | 19x |
test_numeric(object@eff, finite = TRUE, any.missing = FALSE, min.len = 2), |
| 460 | 19x |
"eff must be a finite numerical vector of minimum length 2, without missing values" |
| 461 |
) |
|
| 462 | 19x |
v$check( |
| 463 | 19x |
test_numeric( |
| 464 | 19x |
object@eff_dose, |
| 465 | 19x |
lower = rmin, |
| 466 | 19x |
finite = TRUE, |
| 467 | 19x |
any.missing = FALSE, |
| 468 | 19x |
len = length(object@eff) |
| 469 |
), |
|
| 470 | 19x |
"eff_dose must be a finite numerical vector of the same length as 'eff', without missing values" |
| 471 |
) |
|
| 472 | ||
| 473 | 19x |
uf_sigma2W <- object@use_fixed["sigma2W"] # nolintr |
| 474 | 19x |
v$check( |
| 475 | 19x |
test_flag(uf_sigma2W), |
| 476 | 19x |
"use_fixed must be a named logical vector that contains name 'sigma2W'" |
| 477 |
) |
|
| 478 | 19x |
uf_sigma2betaW <- object@use_fixed["sigma2betaW"] # nolintr |
| 479 | 19x |
v$check( |
| 480 | 19x |
test_flag(uf_sigma2betaW), |
| 481 | 19x |
"use_fixed must be a named logical vector that contains name 'sigma2betaW'" |
| 482 |
) |
|
| 483 | ||
| 484 | 19x |
if (isTRUE(uf_sigma2W)) {
|
| 485 | ! |
v$check( |
| 486 | ! |
test_number(object@sigma2W, lower = rmin, finite = TRUE), |
| 487 | ! |
"sigma2W must be a positive and finite numerical scalar" |
| 488 |
) |
|
| 489 |
} else {
|
|
| 490 |
# object@sigma2W is a vector with parameters for InverseGamma(a, b). |
|
| 491 | 19x |
v$check( |
| 492 | 19x |
h_test_named_numeric(object@sigma2W, permutation.of = c("a", "b")),
|
| 493 | 19x |
"sigma2W must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
| 494 |
) |
|
| 495 |
} |
|
| 496 | 19x |
if (isTRUE(uf_sigma2betaW)) {
|
| 497 | ! |
v$check( |
| 498 | ! |
test_number(object@sigma2betaW, lower = rmin, finite = TRUE), |
| 499 | ! |
"sigma2betaW must be a positive and finite numerical scalar" |
| 500 |
) |
|
| 501 |
} else {
|
|
| 502 |
# object@sigma2betaW is a vector with parameters for InverseGamma(a, b). |
|
| 503 | 19x |
v$check( |
| 504 | 19x |
h_test_named_numeric(object@sigma2betaW, permutation.of = c("a", "b")),
|
| 505 | 19x |
"sigma2betaW must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
| 506 |
) |
|
| 507 |
} |
|
| 508 | ||
| 509 | 19x |
v$check( |
| 510 | 19x |
test_flag(object@rw1), |
| 511 | 19x |
"rw1 must be a flag" |
| 512 |
) |
|
| 513 | 19x |
v$check( |
| 514 | 19x |
test_matrix( |
| 515 | 19x |
object@X, |
| 516 | 19x |
mode = "integer", |
| 517 | 19x |
ncols = object@data@nGrid, |
| 518 | 19x |
any.missing = FALSE |
| 519 |
), |
|
| 520 | 19x |
paste( |
| 521 | 19x |
"X must be an integer matrix with", |
| 522 | 19x |
object@data@nGrid, |
| 523 | 19x |
"columns and without any missing values" |
| 524 |
) |
|
| 525 |
) |
|
| 526 | 19x |
v$check( |
| 527 | 19x |
all(object@X == 0L | object@X == 1L), |
| 528 | 19x |
"X must be a matrix with 0-1 values only" |
| 529 |
) |
|
| 530 | 19x |
v$check( |
| 531 | 19x |
test_matrix( |
| 532 | 19x |
object@RW, |
| 533 | 19x |
nrows = object@data@nGrid, |
| 534 | 19x |
ncols = object@data@nGrid, |
| 535 | 19x |
any.missing = FALSE |
| 536 |
), |
|
| 537 | 19x |
paste0( |
| 538 | 19x |
"RW must be ", |
| 539 | 19x |
object@data@nGrid, |
| 540 | 19x |
"x", |
| 541 | 19x |
object@data@nGrid, |
| 542 | 19x |
" matrix without any missing values" |
| 543 |
) |
|
| 544 |
) |
|
| 545 | 19x |
v$check( |
| 546 | 19x |
test_int(object@RW_rank) && |
| 547 | 19x |
(object@RW_rank == |
| 548 | 19x |
(object@data@nGrid - ifelse(isTRUE(object@rw1), 1L, 2L))), |
| 549 | 19x |
"RW_rank must be an integer equal to data@nGrid - 2L" |
| 550 |
) |
|
| 551 | 19x |
v$result() |
| 552 |
} |
|
| 553 | ||
| 554 |
#' @describeIn v_model_objects validates that [`DALogisticLogNormal`] class slots are valid. |
|
| 555 |
v_model_da_logistic_log_normal <- function(object) {
|
|
| 556 | 6x |
v <- Validate() |
| 557 | ||
| 558 | 6x |
npiece_ok <- test_int(object@npiece) |
| 559 | 6x |
v$check(npiece_ok, "npiece must be a is a single integerish value") |
| 560 | 6x |
if (npiece_ok) {
|
| 561 | 5x |
v$check( |
| 562 | 5x |
test_numeric( |
| 563 | 5x |
object@l, |
| 564 | 5x |
lower = 0, |
| 565 | 5x |
finite = TRUE, |
| 566 | 5x |
any.missing = FALSE, |
| 567 | 5x |
len = object@npiece |
| 568 |
), |
|
| 569 | 5x |
"prior parameter vector l of lambda must be a non-negative vector of length equal to npiece" |
| 570 |
) |
|
| 571 |
} |
|
| 572 | 6x |
v$check( |
| 573 | 6x |
test_number(object@c_par, finite = TRUE), |
| 574 | 6x |
"c_par must be a finite numerical scalar" |
| 575 |
) |
|
| 576 | 6x |
v$check( |
| 577 | 6x |
test_flag(object@cond_pem), |
| 578 | 6x |
"cond_pem must be a flag" |
| 579 |
) |
|
| 580 | 6x |
v$result() |
| 581 |
} |
|
| 582 | ||
| 583 |
#' @describeIn v_model_objects validates that [`TITELogisticLogNormal`] class slots are valid. |
|
| 584 |
v_model_tite_logistic_log_normal <- function(object) {
|
|
| 585 |
# nolintr |
|
| 586 | 3x |
v <- Validate() |
| 587 | 3x |
v$check( |
| 588 | 3x |
test_string(object@weight_method, pattern = "^linear$|^adaptive$"), |
| 589 | 3x |
"weight_method must be a string equal either to linear or adaptive" |
| 590 |
) |
|
| 591 | 3x |
v$result() |
| 592 |
} |
|
| 593 | ||
| 594 |
#' @describeIn v_model_objects validates that [`OneParLogNormalPrior`] class slots are valid. |
|
| 595 |
v_model_one_par_exp_normal_prior <- function(object) {
|
|
| 596 |
# nolintr |
|
| 597 | 8x |
v <- Validate() |
| 598 | ||
| 599 | 8x |
is_skel_prob_ok <- test_probabilities( |
| 600 | 8x |
object@skel_probs, |
| 601 | 8x |
unique = TRUE, |
| 602 | 8x |
sorted = TRUE |
| 603 |
) |
|
| 604 | 8x |
v$check( |
| 605 | 8x |
is_skel_prob_ok, |
| 606 | 8x |
"skel_probs must be a unique sorted probability values between 0 and 1" |
| 607 |
) |
|
| 608 | ||
| 609 | 8x |
if (is_skel_prob_ok) {
|
| 610 |
# Validating skel_fun/skel_fun_inv on within the range of skeleton probs. |
|
| 611 | 5x |
skel_probs_range <- range(object@skel_probs) |
| 612 |
# Probabilities within the range of skel_probs. |
|
| 613 | 5x |
probs_in_range <- seq( |
| 614 | 5x |
from = skel_probs_range[1], |
| 615 | 5x |
to = skel_probs_range[2], |
| 616 | 5x |
by = 0.01 |
| 617 |
) |
|
| 618 |
# Interpolated dose grid. |
|
| 619 | 5x |
doses_in_range <- object@skel_fun_inv(probs_in_range) |
| 620 | 5x |
v$check( |
| 621 | 5x |
isTRUE(all.equal(object@skel_fun(doses_in_range), probs_in_range)), |
| 622 | 5x |
"skel_fun_inv must be an inverse funtion of skel_fun function on within the range of sekeleton probs" |
| 623 |
) |
|
| 624 | ||
| 625 |
# Validating skel_fun/skel_fun_inv on outside the range of skeleton probs. |
|
| 626 | 5x |
probs_out_range <- c( |
| 627 | 5x |
seq(from = 0, to = skel_probs_range[1], length.out = 3), |
| 628 | 5x |
seq(from = skel_probs_range[2], to = 1, length.out = 3) |
| 629 |
) |
|
| 630 | 5x |
doses_out_range <- object@skel_fun_inv(probs_out_range) |
| 631 | 5x |
v$check( |
| 632 | 5x |
isTRUE(all.equal( |
| 633 | 5x |
object@skel_fun(doses_out_range), |
| 634 | 5x |
rep(skel_probs_range, each = 3) |
| 635 |
)), |
|
| 636 | 5x |
"skel_fun_inv must be an inverse funtion of skel_fun function on outside the range of sekeleton probs" |
| 637 |
) |
|
| 638 |
} |
|
| 639 | ||
| 640 | 8x |
v$check( |
| 641 | 8x |
test_number(object@sigma2, lower = .Machine$double.xmin, finite = TRUE), |
| 642 | 8x |
"sigma2 must be a positive finite number" |
| 643 |
) |
|
| 644 | ||
| 645 | 8x |
v$result() |
| 646 |
} |
|
| 647 | ||
| 648 |
#' @describeIn v_model_objects validates that [`OneParExpPrior`] class slots are valid. |
|
| 649 |
v_model_one_par_exp_prior <- function(object) {
|
|
| 650 | 8x |
v <- Validate() |
| 651 | ||
| 652 | 8x |
is_skel_prob_ok <- test_probabilities( |
| 653 | 8x |
object@skel_probs, |
| 654 | 8x |
unique = TRUE, |
| 655 | 8x |
sorted = TRUE |
| 656 |
) |
|
| 657 | 8x |
v$check( |
| 658 | 8x |
is_skel_prob_ok, |
| 659 | 8x |
"skel_probs must be a unique sorted probability values between 0 and 1" |
| 660 |
) |
|
| 661 | ||
| 662 | 8x |
if (is_skel_prob_ok) {
|
| 663 |
# Validating skel_fun/skel_fun_inv on within the range of skeleton probs. |
|
| 664 | 5x |
skel_probs_range <- range(object@skel_probs) |
| 665 |
# Probabilities within the range of skel_probs. |
|
| 666 | 5x |
probs_in_range <- seq( |
| 667 | 5x |
from = skel_probs_range[1], |
| 668 | 5x |
to = skel_probs_range[2], |
| 669 | 5x |
by = 0.01 |
| 670 |
) |
|
| 671 |
# Interpolated dose grid. |
|
| 672 | 5x |
doses_in_range <- object@skel_fun_inv(probs_in_range) |
| 673 | 5x |
v$check( |
| 674 | 5x |
isTRUE(all.equal(object@skel_fun(doses_in_range), probs_in_range)), |
| 675 | 5x |
"skel_fun_inv must be an inverse funtion of skel_fun function on within the range of sekeleton probs" |
| 676 |
) |
|
| 677 | ||
| 678 |
# Validating skel_fun/skel_fun_inv on outside the range of skeleton probs. |
|
| 679 | 5x |
probs_out_range <- c( |
| 680 | 5x |
seq(from = 0, to = skel_probs_range[1], length.out = 3), |
| 681 | 5x |
seq(from = skel_probs_range[2], to = 1, length.out = 3) |
| 682 |
) |
|
| 683 | 5x |
doses_out_range <- object@skel_fun_inv(probs_out_range) |
| 684 | 5x |
v$check( |
| 685 | 5x |
isTRUE(all.equal( |
| 686 | 5x |
object@skel_fun(doses_out_range), |
| 687 | 5x |
rep(skel_probs_range, each = 3) |
| 688 |
)), |
|
| 689 | 5x |
"skel_fun_inv must be an inverse funtion of skel_fun function on outside the range of sekeleton probs" |
| 690 |
) |
|
| 691 |
} |
|
| 692 | ||
| 693 | 8x |
v$check( |
| 694 | 8x |
test_number(object@lambda, lower = .Machine$double.xmin, finite = TRUE), |
| 695 | 8x |
"lambda must be a positive finite number" |
| 696 |
) |
|
| 697 | ||
| 698 | 8x |
v$result() |
| 699 |
} |
|
| 700 | ||
| 701 |
#' @describeIn v_model_objects confirms that cov is diagonal |
|
| 702 |
v_logisticlognormalordinal <- function(object) {
|
|
| 703 | ! |
v <- Validate() |
| 704 |
# diag(x) returns a vector, not a matrix, so cannot use identical(x, diag(x) |
|
| 705 | ! |
x <- object@params@cov |
| 706 | ! |
diag(x) <- rep(0, ncol(x)) |
| 707 | ! |
v$check( |
| 708 | ! |
all(x == 0), |
| 709 | ! |
"covariance matrix must be diagonal" |
| 710 |
) |
|
| 711 | ! |
v$result() |
| 712 |
} |
| 1 |
# Integration with knitr ---- |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' We provide additional utility functions to allow human-friendly rendition of |
|
| 6 |
#' crmPack objects in Markdown and Quarto files |
|
| 7 |
#' |
|
| 8 |
#' @return a character string that represents the object in markdown. |
|
| 9 |
#' @name knit_print |
|
| 10 |
NULL |
|
| 11 | ||
| 12 |
# Helpers --- |
|
| 13 | ||
| 14 |
#' Helper Function to Convert a `gap` Slot to Words |
|
| 15 |
#' |
|
| 16 |
#' @inheritParams knit_print.SafetyWindowConst |
|
| 17 |
#' @param gap (`numeric`)\cr a vector of gaps |
|
| 18 |
#' @return a Markdown representation of the `gap` parameter as a bullet list. |
|
| 19 |
#' @noRd |
|
| 20 |
#' @keywords internal |
|
| 21 |
h_describe_safety_gap <- function(gap, ordinals, label, time_unit) {
|
|
| 22 | 26x |
assert_character( |
| 23 | 26x |
ordinals, |
| 24 | 26x |
min.len = length(gap) - 1, |
| 25 | 26x |
any.missing = FALSE, |
| 26 | 26x |
unique = TRUE |
| 27 |
) |
|
| 28 | ||
| 29 | 24x |
if (length(gap) == 1) {
|
| 30 | 5x |
paste0( |
| 31 | 5x |
"- The gap between consecutive enrolments should always be at least ", |
| 32 | 5x |
gap[1], |
| 33 |
" ", |
|
| 34 | 5x |
ifelse(gap[1] == 1, time_unit[1], time_unit[2]), |
| 35 | 5x |
".\n\n" |
| 36 |
) |
|
| 37 |
} else {
|
|
| 38 | 19x |
paste0( |
| 39 | 19x |
paste0( |
| 40 | 19x |
lapply( |
| 41 | 19x |
seq_along(1:(length(gap) - 1)), |
| 42 | 19x |
function(n) {
|
| 43 | 21x |
paste0( |
| 44 | 21x |
"- The gap between the enrolment of the ", |
| 45 | 21x |
ordinals[n], |
| 46 | 21x |
" and the ", |
| 47 | 21x |
ordinals[n + 1], |
| 48 |
" ", |
|
| 49 | 21x |
label[2], |
| 50 | 21x |
" in the cohort should be at least ", |
| 51 | 21x |
gap[n], |
| 52 |
" ", |
|
| 53 | 21x |
ifelse(gap[n] == 1, time_unit[1], time_unit[2]) |
| 54 |
) |
|
| 55 |
} |
|
| 56 |
), |
|
| 57 | 19x |
collapse = "\n\n" |
| 58 |
), |
|
| 59 | 19x |
"\n", |
| 60 | 19x |
paste0( |
| 61 | 19x |
"- The gap between all subsequent ", |
| 62 | 19x |
label[2], |
| 63 | 19x |
" should be at least ", |
| 64 | 19x |
gap[length(gap)], |
| 65 |
" ", |
|
| 66 | 19x |
ifelse(gap[length(gap)] == 1, time_unit[1], time_unit[2]), |
| 67 | 19x |
"\n" |
| 68 |
), |
|
| 69 | 19x |
sep = "\n" |
| 70 |
) |
|
| 71 |
} |
|
| 72 |
} |
|
| 73 |
# Methods ---- |
|
| 74 | ||
| 75 |
# SafetyWindow ---- |
|
| 76 | ||
| 77 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 78 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 79 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 80 |
#' @section Usage Notes: |
|
| 81 |
#' `label` should be a character vector of length 1 or 2. If of length 2, the first |
|
| 82 |
#' element describes a count of 1 and the second describes all other counts. |
|
| 83 |
#' If of length 1, the character `s` is appended to the value when the count is not 1. |
|
| 84 |
#' @rdname knit_print |
|
| 85 |
#' @export |
|
| 86 |
#' @method knit_print SafetyWindow |
|
| 87 |
knit_print.SafetyWindow <- function( |
|
| 88 |
x, |
|
| 89 |
..., |
|
| 90 |
asis = TRUE, |
|
| 91 |
time_unit = "day", |
|
| 92 |
label = "participant" |
|
| 93 |
) {
|
|
| 94 | 20x |
assert_character(time_unit, min.len = 1, max.len = 2, any.missing = FALSE) |
| 95 | 20x |
assert_flag(asis) |
| 96 | ||
| 97 | 20x |
label <- h_prepare_labels(label) |
| 98 | 20x |
if (length(time_unit) == 1) {
|
| 99 | 20x |
time_unit[2] <- paste0(time_unit[1], "s") |
| 100 |
} |
|
| 101 | ||
| 102 | 20x |
rv <- paste0( |
| 103 | 20x |
"To protect the welfare of individual ", |
| 104 | 20x |
label[2], |
| 105 | 20x |
", the rate of enrolment within each cohort will be restricted.\n\n" |
| 106 |
) |
|
| 107 | ||
| 108 | 20x |
if (asis) {
|
| 109 | ! |
rv <- knitr::asis_output(rv) |
| 110 |
} |
|
| 111 | 20x |
rv |
| 112 |
} |
|
| 113 | ||
| 114 |
# SafetyWindowConst ---- |
|
| 115 | ||
| 116 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 117 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 118 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 119 |
#' @param time_unit (`character`)\cr the word used to describe units of time. |
|
| 120 |
#' See Usage Notes below. |
|
| 121 |
#' @param ordinals (`character`)\cr a character vector whose nth defines the |
|
| 122 |
#' word used as the written representation of the nth ordinal number. |
|
| 123 |
#' @section Usage Notes: |
|
| 124 |
#' `label` and `time_unit` are, collectively, labels. |
|
| 125 |
#' |
|
| 126 |
#' A label should be a character vector of length 1 or 2. If of length 2, the first |
|
| 127 |
#' element describes a count of 1 and the second describes all other counts. |
|
| 128 |
#' If of length 1, the character `s` is appended to the value when the count is not 1. |
|
| 129 |
#' @rdname knit_print |
|
| 130 |
#' @export |
|
| 131 |
#' @method knit_print SafetyWindowConst |
|
| 132 |
knit_print.SafetyWindowConst <- function( |
|
| 133 |
x, |
|
| 134 |
..., |
|
| 135 |
asis = TRUE, |
|
| 136 |
label = "participant", |
|
| 137 |
ordinals = c( |
|
| 138 |
"first", |
|
| 139 |
"second", |
|
| 140 |
"third", |
|
| 141 |
"fourth", |
|
| 142 |
"fifth", |
|
| 143 |
"sixth", |
|
| 144 |
"seventh", |
|
| 145 |
"eighth", |
|
| 146 |
"ninth", |
|
| 147 |
"tenth" |
|
| 148 |
), |
|
| 149 |
time_unit = "day" |
|
| 150 |
) {
|
|
| 151 | 19x |
assert_character(time_unit, min.len = 1, max.len = 2, any.missing = FALSE) |
| 152 | 18x |
assert_character( |
| 153 | 18x |
ordinals, |
| 154 | 18x |
min.len = length(x@gap) - 1, |
| 155 | 18x |
any.missing = FALSE, |
| 156 | 18x |
unique = TRUE |
| 157 |
) |
|
| 158 | 16x |
assert_flag(asis) |
| 159 | ||
| 160 | 14x |
label <- h_prepare_labels(label) |
| 161 | 13x |
if (length(time_unit) == 1) {
|
| 162 | 13x |
time_unit[2] <- paste0(time_unit[1], "s") |
| 163 |
} |
|
| 164 | ||
| 165 | 13x |
rv <- paste0( |
| 166 | 13x |
knit_print.SafetyWindow(x, asis = FALSE, label = label, ...), |
| 167 | 13x |
"For all cohorts:\n\n", |
| 168 | 13x |
h_describe_safety_gap(x@gap, ordinals, label, time_unit), |
| 169 | 13x |
"Before the next cohort can open, all ", |
| 170 | 13x |
label[2], |
| 171 | 13x |
" in the current cohort must have been followed up for at least ", |
| 172 | 13x |
x@follow, |
| 173 |
" ", |
|
| 174 | 13x |
ifelse(x@follow == 1, time_unit[1], time_unit[2]), |
| 175 | 13x |
" and at least one ", |
| 176 | 13x |
label[1], |
| 177 | 13x |
" must have been followed up for at least ", |
| 178 | 13x |
x@follow_min, |
| 179 |
" ", |
|
| 180 | 13x |
ifelse(x@follow_min == 1, time_unit[1], time_unit[2]), |
| 181 | 13x |
".\n\n" |
| 182 |
) |
|
| 183 | ||
| 184 | 13x |
if (asis) {
|
| 185 | 2x |
rv <- knitr::asis_output(rv) |
| 186 |
} |
|
| 187 | 13x |
rv |
| 188 |
} |
|
| 189 | ||
| 190 |
# SafetyWindowSize ---- |
|
| 191 | ||
| 192 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 193 |
#' @inheritParams knit_print.SafetyWindowConst |
|
| 194 |
#' @inherit SafetyWindowConst sections |
|
| 195 |
#' @param level (`count`)\cr the markdown level at which the headings for cohort size |
|
| 196 |
#' will be printed. An integer between 1 and 6 |
|
| 197 |
#' @rdname knit_print |
|
| 198 |
#' @export |
|
| 199 |
#' @method knit_print SafetyWindowSize |
|
| 200 |
knit_print.SafetyWindowSize <- function( |
|
| 201 |
x, |
|
| 202 |
..., |
|
| 203 |
asis = TRUE, |
|
| 204 |
# We could use package english here and avoid the need for `ordinals`, but |
|
| 205 |
# is an extra dependency for very limited benefit |
|
| 206 |
ordinals = c( |
|
| 207 |
"first", |
|
| 208 |
"second", |
|
| 209 |
"third", |
|
| 210 |
"fourth", |
|
| 211 |
"fifth", |
|
| 212 |
"sixth", |
|
| 213 |
"seventh", |
|
| 214 |
"eighth", |
|
| 215 |
"ninth", |
|
| 216 |
"tenth" |
|
| 217 |
), |
|
| 218 |
label = "participant", |
|
| 219 |
time_unit = "day", |
|
| 220 |
level = 2L |
|
| 221 |
) {
|
|
| 222 | 13x |
assert_character(time_unit, min.len = 1, max.len = 2, any.missing = FALSE) |
| 223 | 12x |
assert_flag(asis) |
| 224 | 10x |
assert_integer(level, lower = 1, upper = 6, any.missing = FALSE) |
| 225 | ||
| 226 | 8x |
label <- h_prepare_labels(label) |
| 227 | 7x |
if (length(time_unit) == 1) {
|
| 228 | 7x |
time_unit[2] <- paste0(time_unit[1], "s") |
| 229 |
} |
|
| 230 | ||
| 231 | 7x |
rv <- paste0( |
| 232 | 7x |
knit_print.SafetyWindow(x, asis = FALSE, label = label, ...), |
| 233 | 7x |
paste0( |
| 234 | 7x |
lapply( |
| 235 | 7x |
seq_along(x@size), |
| 236 | 7x |
function(i) {
|
| 237 | 13x |
paste0( |
| 238 | 13x |
dplyr::case_when( |
| 239 | 13x |
i == 1 ~ |
| 240 | 13x |
paste0( |
| 241 | 13x |
stringr::str_dup("#", level),
|
| 242 | 13x |
" For cohort sizes of less than ", |
| 243 | 13x |
x@size[2] |
| 244 |
), |
|
| 245 | 13x |
i == length(x@size) ~ |
| 246 | 13x |
paste0( |
| 247 | 13x |
stringr::str_dup("#", level),
|
| 248 | 13x |
" For cohort sizes of ", |
| 249 | 13x |
x@size[i], |
| 250 | 13x |
" or more" |
| 251 |
), |
|
| 252 | 13x |
TRUE ~ |
| 253 | 13x |
paste0( |
| 254 | 13x |
stringr::str_dup("#", level),
|
| 255 | 13x |
" For cohort sizes greater than or equal to ", |
| 256 | 13x |
x@size[i], |
| 257 | 13x |
" and strictly less than ", |
| 258 | 13x |
x@size[i + 1] |
| 259 |
) |
|
| 260 |
), |
|
| 261 | 13x |
"\n\n", |
| 262 | 13x |
h_describe_safety_gap(x@gap[[i]], ordinals, label, time_unit) |
| 263 |
) |
|
| 264 |
} |
|
| 265 |
), |
|
| 266 | 7x |
collapse = "\n" |
| 267 |
) |
|
| 268 |
) |
|
| 269 | ||
| 270 | 5x |
rv <- paste0( |
| 271 | 5x |
rv, |
| 272 | 5x |
"For all cohorts, before the next cohort can open, all ", |
| 273 | 5x |
label[2], |
| 274 | 5x |
" in the current cohort must have been followed up for at least ", |
| 275 | 5x |
x@follow, |
| 276 |
" ", |
|
| 277 | 5x |
ifelse(x@follow == 1, time_unit[1], time_unit[2]), |
| 278 | 5x |
" and at least one ", |
| 279 | 5x |
label[1], |
| 280 | 5x |
" must have been followed up for at least ", |
| 281 | 5x |
x@follow_min, |
| 282 |
" ", |
|
| 283 | 5x |
ifelse(x@follow_min == 1, time_unit[1], time_unit[2]), |
| 284 | 5x |
".\n\n" |
| 285 |
) |
|
| 286 | ||
| 287 | 5x |
if (asis) {
|
| 288 | 2x |
rv <- knitr::asis_output(rv) |
| 289 |
} |
|
| 290 | 5x |
rv |
| 291 |
} |
| 1 |
#' @include helpers.R |
|
| 2 |
#' @include Rules-validity.R |
|
| 3 |
#' @include CrmPackClass-class.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 |
# NextBest ---- |
|
| 7 | ||
| 8 |
## class ---- |
|
| 9 | ||
| 10 |
#' `NextBest` |
|
| 11 |
#' |
|
| 12 |
#' @description `r lifecycle::badge("stable")`
|
|
| 13 |
#' |
|
| 14 |
#' [`NextBest`] is a virtual class for finding next best dose, from which all |
|
| 15 |
#' other specific next best dose classes inherit. |
|
| 16 |
#' |
|
| 17 |
#' @seealso [`NextBestMTD`], [`NextBestNCRM`], [`NextBestDualEndpoint`], |
|
| 18 |
#' [`NextBestThreePlusThree`], [`NextBestDualEndpoint`], [`NextBestMinDist`], |
|
| 19 |
#' [`NextBestInfTheory`], [`NextBestTD`], [`NextBestTDsamples`], |
|
| 20 |
#' [`NextBestMaxGain`], [`NextBestMaxGainSamples`]. |
|
| 21 |
#' |
|
| 22 |
#' @aliases NextBest |
|
| 23 |
#' @export |
|
| 24 |
#' |
|
| 25 |
setClass( |
|
| 26 |
Class = "NextBest", |
|
| 27 |
contains = "CrmPackClass" |
|
| 28 |
) |
|
| 29 | ||
| 30 |
## default constructor ---- |
|
| 31 | ||
| 32 |
#' @rdname NextBest-class |
|
| 33 |
#' @note Typically, end users will not use the `DefaultNextBest()` function. |
|
| 34 |
#' @export |
|
| 35 |
.DefaultNextBest <- function() {
|
|
| 36 | 2x |
stop(paste0( |
| 37 | 2x |
"Class NextBest should not be instantiated directly. Please use one of its subclasses instead." |
| 38 |
)) |
|
| 39 |
} |
|
| 40 | ||
| 41 | ||
| 42 |
# NextBestMTD ---- |
|
| 43 | ||
| 44 |
## class ---- |
|
| 45 | ||
| 46 |
#' `NextBestMTD` |
|
| 47 |
#' |
|
| 48 |
#' @description `r lifecycle::badge("stable")`
|
|
| 49 |
#' |
|
| 50 |
#' [`NextBestMTD`] is the class for next best dose based on MTD estimate. |
|
| 51 |
#' |
|
| 52 |
#' @slot target (`proportion`)\cr target toxicity probability, except 0 or 1. |
|
| 53 |
#' @slot derive (`function`)\cr a function which derives the final next best MTD |
|
| 54 |
#' estimate, based on vector of posterior MTD samples. It must therefore accept |
|
| 55 |
#' one and only one argument, which is a numeric vector, and return a number. |
|
| 56 |
#' |
|
| 57 |
#' @aliases NextBestMTD |
|
| 58 |
#' @export |
|
| 59 |
#' |
|
| 60 |
.NextBestMTD <- setClass( |
|
| 61 |
Class = "NextBestMTD", |
|
| 62 |
slots = c( |
|
| 63 |
target = "numeric", |
|
| 64 |
derive = "function" |
|
| 65 |
), |
|
| 66 |
prototype = prototype( |
|
| 67 |
target = 0.3, |
|
| 68 |
derive = function(mtd_samples) {
|
|
| 69 |
quantile(mtd_samples, probs = 0.3) |
|
| 70 |
} |
|
| 71 |
), |
|
| 72 |
contains = "NextBest", |
|
| 73 |
validity = v_next_best_mtd |
|
| 74 |
) |
|
| 75 | ||
| 76 |
## constructor ---- |
|
| 77 | ||
| 78 |
#' @rdname NextBestMTD-class |
|
| 79 |
#' |
|
| 80 |
#' @param target (`proportion`)\cr see slot definition. |
|
| 81 |
#' @param derive (`function`)\cr see slot definition. |
|
| 82 |
#' |
|
| 83 |
#' @export |
|
| 84 |
#' @example examples/Rules-class-NextBestMTD.R |
|
| 85 |
#' |
|
| 86 |
NextBestMTD <- function(target, derive) {
|
|
| 87 | 42x |
.NextBestMTD( |
| 88 | 42x |
target = target, |
| 89 | 42x |
derive = derive |
| 90 |
) |
|
| 91 |
} |
|
| 92 | ||
| 93 |
## default constructor ---- |
|
| 94 | ||
| 95 |
#' @rdname NextBestMTD-class |
|
| 96 |
#' @note Typically, end users will not use the `.DefaultNextBestMTD()` function. |
|
| 97 |
#' @export |
|
| 98 |
.DefaultNextBestMTD <- function() {
|
|
| 99 | 8x |
NextBestMTD( |
| 100 | 8x |
target = 0.33, |
| 101 | 8x |
derive = function(mtd_samples) {
|
| 102 | 10x |
quantile(mtd_samples, probs = 0.25) |
| 103 |
} |
|
| 104 |
) |
|
| 105 |
} |
|
| 106 | ||
| 107 |
# NextBestNCRM ---- |
|
| 108 | ||
| 109 |
## class ---- |
|
| 110 | ||
| 111 |
#' `NextBestNCRM` |
|
| 112 |
#' |
|
| 113 |
#' @description `r lifecycle::badge("stable")`
|
|
| 114 |
#' |
|
| 115 |
#' [`NextBestNCRM`] is the class for next best dose that finds the next dose |
|
| 116 |
#' with high posterior probability to be in the target toxicity interval. |
|
| 117 |
#' |
|
| 118 |
#' @details To avoid numerical problems, the dose selection algorithm has been |
|
| 119 |
#' implemented as follows: First admissible doses are found, which are those |
|
| 120 |
#' with probability to fall in `overdose` category being below `max_overdose_prob`. |
|
| 121 |
#' Next, within the admissible doses, the maximum probability to fall in the |
|
| 122 |
#' `target` category is calculated. If that is above 5% (i.e. it is not just |
|
| 123 |
#' numerical error), then the corresponding dose is the next recommended dose. |
|
| 124 |
#' Otherwise, the highest admissible dose is the next recommended dose. |
|
| 125 |
#' |
|
| 126 |
#' @slot target (`numeric`)\cr the target toxicity interval (limits included). |
|
| 127 |
#' @slot overdose (`numeric`)\cr the overdose toxicity interval (lower limit |
|
| 128 |
#' excluded, upper limit included). It is used to filter probability samples. |
|
| 129 |
#' @slot max_overdose_prob (`proportion`)\cr maximum overdose posterior |
|
| 130 |
#' probability that is allowed, except 0 or 1. |
|
| 131 |
#' |
|
| 132 |
#' @aliases NextBestNCRM |
|
| 133 |
#' @export |
|
| 134 |
#' |
|
| 135 |
.NextBestNCRM <- setClass( |
|
| 136 |
Class = "NextBestNCRM", |
|
| 137 |
slots = c( |
|
| 138 |
target = "numeric", |
|
| 139 |
overdose = "numeric", |
|
| 140 |
max_overdose_prob = "numeric" |
|
| 141 |
), |
|
| 142 |
prototype = prototype( |
|
| 143 |
target = c(0.2, 0.35), |
|
| 144 |
overdose = c(0.35, 1), |
|
| 145 |
max_overdose_prob = 0.25 |
|
| 146 |
), |
|
| 147 |
contains = "NextBest", |
|
| 148 |
validity = v_next_best_ncrm |
|
| 149 |
) |
|
| 150 | ||
| 151 |
## constructor ---- |
|
| 152 | ||
| 153 |
#' @rdname NextBestNCRM-class |
|
| 154 |
#' |
|
| 155 |
#' @param target (`numeric`)\cr see slot definition. |
|
| 156 |
#' @param overdose (`numeric`)\cr see slot definition. |
|
| 157 |
#' @param max_overdose_prob (`proportion`)\cr see slot definition. |
|
| 158 |
#' @export |
|
| 159 |
#' @example examples/Rules-class-NextBestNCRM.R |
|
| 160 |
#' |
|
| 161 |
NextBestNCRM <- function(target, overdose, max_overdose_prob) {
|
|
| 162 | 75x |
.NextBestNCRM( |
| 163 | 75x |
target = target, |
| 164 | 75x |
overdose = overdose, |
| 165 | 75x |
max_overdose_prob = max_overdose_prob |
| 166 |
) |
|
| 167 |
} |
|
| 168 | ||
| 169 |
## default constructor ---- |
|
| 170 | ||
| 171 |
#' @rdname NextBestNCRM-class |
|
| 172 |
#' @note Typically, end users will not use the `.DefaultNextBestNCRM()` function. |
|
| 173 |
#' @export |
|
| 174 |
.DefaultNextBestNCRM <- function() {
|
|
| 175 | 9x |
NextBestNCRM( |
| 176 | 9x |
target = c(0.2, 0.35), |
| 177 | 9x |
overdose = c(0.35, 1), |
| 178 | 9x |
max_overdose_prob = 0.25 |
| 179 |
) |
|
| 180 |
} |
|
| 181 | ||
| 182 |
# NextBestNCRMLoss ---- |
|
| 183 | ||
| 184 |
## class ---- |
|
| 185 | ||
| 186 |
#' `NextBestNCRMLoss` |
|
| 187 |
#' |
|
| 188 |
#' @description `r lifecycle::badge("stable")`
|
|
| 189 |
#' |
|
| 190 |
#' [`NextBestNCRMLoss`] is the class based on NCRM rule and loss function. |
|
| 191 |
#' This class is similar to [`NextBestNCRM`] class, but differences are the |
|
| 192 |
#' addition of loss function and re-defined toxicity intervals, see each |
|
| 193 |
#' toxicity interval documentation and the note for details. As in NCRM rule, first admissible doses are found, |
|
| 194 |
#' which are those with probability to fall in overdose category being below |
|
| 195 |
#' `max_overdose_prob`. Next, within the admissible doses, the loss function is |
|
| 196 |
#' calculated, i.e. `losses` %*% `target`. Finally, the corresponding |
|
| 197 |
#' dose with lowest loss function (Bayes risk) is recommended for the next dose. |
|
| 198 |
#' |
|
| 199 |
#' @slot target (`numeric`)\cr the target toxicity interval (limits included). |
|
| 200 |
#' It has to be a probability range excluding 0 and 1. |
|
| 201 |
#' @slot overdose (`numeric`)\cr the overdose toxicity interval (lower limit |
|
| 202 |
#' excluded, upper limit included) or the excessive toxicity interval (lower |
|
| 203 |
#' limit excluded, upper limit included) if unacceptable is not provided. |
|
| 204 |
#' It has to be a probability range. It is used to filter probability samples. |
|
| 205 |
#' @slot unacceptable (`numeric`)\cr an unacceptable toxicity |
|
| 206 |
#' interval (lower limit excluded, upper limit included). This must be |
|
| 207 |
#' specified if the `overdose` does not include 1. Otherwise, it is `c(1, 1)` |
|
| 208 |
#' (default), which is essentially a scalar equals 1. It has to be a |
|
| 209 |
#' probability range. |
|
| 210 |
#' @slot losses (`numeric`)\cr a vector specifying the loss function. If the |
|
| 211 |
#' `unacceptable` is provided, the vector length must be 4, otherwise 3. |
|
| 212 |
#' |
|
| 213 |
#' @note The loss function should be a vector of either 3 or 4 values. |
|
| 214 |
#' This is because the loss function values must be specified for each |
|
| 215 |
#' interval, that is under-dosing, target toxicity, and overdosing toxicity or |
|
| 216 |
#' under-dosing, target toxicity, overdosing (excessive) toxicity, and |
|
| 217 |
#' unacceptable toxicity intervals. |
|
| 218 |
#' |
|
| 219 |
#' @aliases NextBestNCRMLoss |
|
| 220 |
#' @export |
|
| 221 |
#' |
|
| 222 |
.NextBestNCRMLoss <- setClass( |
|
| 223 |
Class = "NextBestNCRMLoss", |
|
| 224 |
slots = c( |
|
| 225 |
unacceptable = "numeric", |
|
| 226 |
losses = "numeric" |
|
| 227 |
), |
|
| 228 |
prototype = prototype( |
|
| 229 |
unacceptable = c(1, 1), |
|
| 230 |
losses = c(1, 0, 2) |
|
| 231 |
), |
|
| 232 |
contains = "NextBestNCRM", |
|
| 233 |
validity = v_next_best_ncrm_loss |
|
| 234 |
) |
|
| 235 | ||
| 236 |
## constructor ---- |
|
| 237 | ||
| 238 |
#' @rdname NextBestNCRMLoss-class |
|
| 239 |
#' |
|
| 240 |
#' @param target (`numeric`)\cr see slot definition. |
|
| 241 |
#' @param overdose (`numeric`)\cr see slot definition. |
|
| 242 |
#' @param unacceptable (`numeric`)\cr see slot definition. |
|
| 243 |
#' @param max_overdose_prob (`proportion`)\cr see slot definition in [`NextBestNCRM`]. |
|
| 244 |
#' @param losses (`numeric`)\cr see slot definition. |
|
| 245 |
#' |
|
| 246 |
#' @export |
|
| 247 |
#' @example examples/Rules-class-NextBestNCRMLoss.R |
|
| 248 |
#' |
|
| 249 |
NextBestNCRMLoss <- function( |
|
| 250 |
target, |
|
| 251 |
overdose, |
|
| 252 |
unacceptable = c(1, 1), |
|
| 253 |
max_overdose_prob, |
|
| 254 |
losses |
|
| 255 |
) {
|
|
| 256 | 23x |
.NextBestNCRMLoss( |
| 257 | 23x |
target = target, |
| 258 | 23x |
overdose = overdose, |
| 259 | 23x |
unacceptable = unacceptable, |
| 260 | 23x |
max_overdose_prob = max_overdose_prob, |
| 261 | 23x |
losses = losses |
| 262 |
) |
|
| 263 |
} |
|
| 264 | ||
| 265 |
## default constructor ---- |
|
| 266 | ||
| 267 |
#' @rdname NextBestNCRMLoss-class |
|
| 268 |
#' @note Typically, end users will not use the `.DefaultNextBestnCRMLoss()` function. |
|
| 269 |
#' @export |
|
| 270 |
.DefaultNextBestNCRMLoss <- function() {
|
|
| 271 | 10x |
NextBestNCRMLoss( |
| 272 | 10x |
target = c(0.2, 0.35), |
| 273 | 10x |
overdose = c(0.35, 0.6), |
| 274 | 10x |
unacceptable = c(0.6, 1), |
| 275 | 10x |
max_overdose_prob = 0.25, |
| 276 | 10x |
losses = c(1, 0, 1, 2) |
| 277 |
) |
|
| 278 |
} |
|
| 279 | ||
| 280 | ||
| 281 |
# NextBestThreePlusThree ---- |
|
| 282 | ||
| 283 |
## class ---- |
|
| 284 | ||
| 285 |
#' `NextBestThreePlusThree` |
|
| 286 |
#' |
|
| 287 |
#' @description `r lifecycle::badge("stable")`
|
|
| 288 |
#' |
|
| 289 |
#' [`NextBestThreePlusThree`] is the class for next best dose that |
|
| 290 |
#' implements the classical 3+3 dose recommendation. No input is required, |
|
| 291 |
#' hence this class has no slots. |
|
| 292 |
#' |
|
| 293 |
#' @aliases NextBestThreePlusThree |
|
| 294 |
#' @export |
|
| 295 |
#' |
|
| 296 |
.NextBestThreePlusThree <- setClass( |
|
| 297 |
Class = "NextBestThreePlusThree", |
|
| 298 |
contains = "NextBest" |
|
| 299 |
) |
|
| 300 | ||
| 301 |
## constructor ---- |
|
| 302 | ||
| 303 |
#' @rdname NextBestThreePlusThree-class |
|
| 304 |
#' |
|
| 305 |
#' @export |
|
| 306 |
#' @examples |
|
| 307 |
#' # Next best dose class object using the classical 3+3 design. |
|
| 308 |
#' my_next_best <- NextBestThreePlusThree() |
|
| 309 |
NextBestThreePlusThree <- function() {
|
|
| 310 | 32x |
.NextBestThreePlusThree() |
| 311 |
} |
|
| 312 | ||
| 313 |
## default constructor ---- |
|
| 314 | ||
| 315 |
#' @rdname NextBestThreePlusThree-class |
|
| 316 |
#' @note Typically, end users will not use the `.DefaultNextBestThreePlusThree()` function. |
|
| 317 |
#' @export |
|
| 318 |
.DefaultNextBestThreePlusThree <- function() {
|
|
| 319 | 8x |
NextBestThreePlusThree() |
| 320 |
} |
|
| 321 | ||
| 322 |
# NextBestDualEndpoint ---- |
|
| 323 | ||
| 324 |
## class ---- |
|
| 325 | ||
| 326 |
#' `NextBestDualEndpoint` |
|
| 327 |
#' |
|
| 328 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 329 |
#' |
|
| 330 |
#' [`NextBestDualEndpoint`] is the class for next best dose that is based on the |
|
| 331 |
#' dual endpoint model. |
|
| 332 |
#' |
|
| 333 |
#' @details Under this rule, at first admissible doses are found, which are those |
|
| 334 |
#' with toxicity probability to fall in `overdose` category and being below |
|
| 335 |
#' `max_overdose_prob`. Next, it picks (from the remaining admissible doses) the |
|
| 336 |
#' one that maximizes the probability to be in the `target` biomarker range. By |
|
| 337 |
#' default (`target_relative = TRUE`) the target is specified as relative to the |
|
| 338 |
#' maximum biomarker level across the dose grid or relative to the `Emax` |
|
| 339 |
#' parameter in case a parametric model was selected (i.e. [`DualEndpointBeta`], |
|
| 340 |
#' [`DualEndpointEmax`]). However, if `target_relative = FALSE`, then the |
|
| 341 |
#' absolute biomarker range can be used as a target. |
|
| 342 |
#' |
|
| 343 |
#' @slot target (`numeric`)\cr the biomarker target range that needs to be |
|
| 344 |
#' reached. For example, the target range \eqn{(0.8, 1.0)} and
|
|
| 345 |
#' `target_relative = TRUE` means that we target a dose with at least |
|
| 346 |
#' \eqn{80\%} of maximum biomarker level. As an other example,
|
|
| 347 |
#' \eqn{(0.5, 0.8)} would mean that we target a dose between \eqn{50\%} and
|
|
| 348 |
#' \eqn{80\%} of the maximum biomarker level.
|
|
| 349 |
#' @slot overdose (`numeric`)\cr the overdose toxicity interval (lower limit |
|
| 350 |
#' excluded, upper limit included). |
|
| 351 |
#' @slot max_overdose_prob (`proportion`)\cr maximum overdose probability that |
|
| 352 |
#' is allowed. |
|
| 353 |
#' @slot target_relative (`flag`)\cr is `target` specified as relative? If |
|
| 354 |
#' `TRUE`, then the `target` is interpreted relative to the maximum, so it |
|
| 355 |
#' must be a probability range. Otherwise, the `target` is interpreted as |
|
| 356 |
#' absolute biomarker range. |
|
| 357 |
#' @slot target_thresh (`proportion`)\cr a target probability threshold that |
|
| 358 |
#' needs to be fulfilled before the target probability will be used for |
|
| 359 |
#' deriving the next best dose (default to 0.01). |
|
| 360 |
#' |
|
| 361 |
#' @aliases NextBestDualEndpoint |
|
| 362 |
#' @export |
|
| 363 |
#' |
|
| 364 |
.NextBestDualEndpoint <- setClass( |
|
| 365 |
Class = "NextBestDualEndpoint", |
|
| 366 |
slots = c( |
|
| 367 |
target = "numeric", |
|
| 368 |
overdose = "numeric", |
|
| 369 |
max_overdose_prob = "numeric", |
|
| 370 |
target_relative = "logical", |
|
| 371 |
target_thresh = "numeric" |
|
| 372 |
), |
|
| 373 |
prototype = prototype( |
|
| 374 |
target = c(0.9, 1), |
|
| 375 |
overdose = c(0.35, 1), |
|
| 376 |
max_overdose_prob = 0.25, |
|
| 377 |
target_relative = TRUE, |
|
| 378 |
target_thresh = 0.01 |
|
| 379 |
), |
|
| 380 |
contains = "NextBest", |
|
| 381 |
validity = v_next_best_dual_endpoint |
|
| 382 |
) |
|
| 383 | ||
| 384 |
## constructor ---- |
|
| 385 | ||
| 386 |
#' @rdname NextBestDualEndpoint-class |
|
| 387 |
#' |
|
| 388 |
#' @param target (`numeric`)\cr see slot definition. |
|
| 389 |
#' @param overdose (`numeric`)\cr see slot definition. |
|
| 390 |
#' @param max_overdose_prob (`proportion`)\cr see slot definition. |
|
| 391 |
#' @param target_relative (`flag`)\cr see slot definition. |
|
| 392 |
#' @param target_thresh (`proportion`)\cr see slot definition. |
|
| 393 |
#' |
|
| 394 |
#' @export |
|
| 395 |
#' @example examples/Rules-class-NextBestDualEndpoint.R |
|
| 396 |
#' |
|
| 397 |
NextBestDualEndpoint <- function( |
|
| 398 |
target, |
|
| 399 |
overdose, |
|
| 400 |
max_overdose_prob, |
|
| 401 |
target_relative = TRUE, |
|
| 402 |
target_thresh = 0.01 |
|
| 403 |
) {
|
|
| 404 | 40x |
.NextBestDualEndpoint( |
| 405 | 40x |
target = target, |
| 406 | 40x |
overdose = overdose, |
| 407 | 40x |
max_overdose_prob = max_overdose_prob, |
| 408 | 40x |
target_relative = target_relative, |
| 409 | 40x |
target_thresh = target_thresh |
| 410 |
) |
|
| 411 |
} |
|
| 412 | ||
| 413 |
## default constructor ---- |
|
| 414 | ||
| 415 |
#' @rdname NextBestDualEndpoint-class |
|
| 416 |
#' @note Typically, end users will not use the `.DefaultNextBestDualEndpoint()` function. |
|
| 417 |
#' @export |
|
| 418 |
.DefaultNextBestDualEndpoint <- function() {
|
|
| 419 | 8x |
NextBestDualEndpoint( |
| 420 | 8x |
target = c(200, 300), |
| 421 | 8x |
overdose = c(0.35, 1), |
| 422 | 8x |
max_overdose_prob = 0.25, |
| 423 | 8x |
target_relative = FALSE |
| 424 |
) |
|
| 425 |
} |
|
| 426 | ||
| 427 |
# NextBestMinDist ---- |
|
| 428 | ||
| 429 |
## class ---- |
|
| 430 | ||
| 431 |
#' `NextBestMinDist` |
|
| 432 |
#' |
|
| 433 |
#' @description `r lifecycle::badge("stable")`
|
|
| 434 |
#' |
|
| 435 |
#' [`NextBestMinDist`] is the class for next best dose that is based on minimum |
|
| 436 |
#' distance to target probability. |
|
| 437 |
#' |
|
| 438 |
#' @slot target (`proportion`)\cr single target toxicity probability, except |
|
| 439 |
#' 0 or 1. |
|
| 440 |
#' |
|
| 441 |
#' @aliases NextBestMinDist |
|
| 442 |
#' @export |
|
| 443 |
#' |
|
| 444 |
.NextBestMinDist <- setClass( |
|
| 445 |
Class = "NextBestMinDist", |
|
| 446 |
slots = c( |
|
| 447 |
target = "numeric" |
|
| 448 |
), |
|
| 449 |
prototype = prototype( |
|
| 450 |
target = 0.3 |
|
| 451 |
), |
|
| 452 |
contains = "NextBest", |
|
| 453 |
validity = v_next_best_min_dist |
|
| 454 |
) |
|
| 455 | ||
| 456 |
## constructor ---- |
|
| 457 | ||
| 458 |
#' @rdname NextBestMinDist-class |
|
| 459 |
#' |
|
| 460 |
#' @param target (`proportion`)\cr see slot definition. |
|
| 461 |
#' |
|
| 462 |
#' @export |
|
| 463 |
#' @example examples/Rules-class-NextBestMinDist.R |
|
| 464 |
#' |
|
| 465 |
NextBestMinDist <- function(target) {
|
|
| 466 | 16x |
.NextBestMinDist(target = target) |
| 467 |
} |
|
| 468 | ||
| 469 |
## default constructor ---- |
|
| 470 | ||
| 471 |
#' @rdname NextBestMinDist-class |
|
| 472 |
#' @note Typically, end users will not use the `.DefaultNextBestMinDist()` function. |
|
| 473 |
#' @export |
|
| 474 |
.DefaultNextBestMinDist <- function() {
|
|
| 475 | 8x |
NextBestMinDist(target = 0.3) |
| 476 |
} |
|
| 477 | ||
| 478 |
# NextBestInfTheory ---- |
|
| 479 | ||
| 480 |
## class ---- |
|
| 481 | ||
| 482 |
#' `NextBestInfTheory` |
|
| 483 |
#' |
|
| 484 |
#' @description `r lifecycle::badge("stable")`
|
|
| 485 |
#' |
|
| 486 |
#' [`NextBestInfTheory`] is the class for next best dose that is based on |
|
| 487 |
#' information theory as proposed in https://doi.org/10.1002/sim.8450. |
|
| 488 |
#' |
|
| 489 |
#' @slot target (`proportion`)\cr target toxicity probability, except 0 or 1. |
|
| 490 |
#' @slot asymmetry (`number`)\cr value of the asymmetry exponent in the |
|
| 491 |
#' divergence function that describes the rate of penalization for overly |
|
| 492 |
#' toxic does. It must be a value from \eqn{(0, 2)} interval.
|
|
| 493 |
#' |
|
| 494 |
#' @aliases NextBestInfTheory |
|
| 495 |
#' @export |
|
| 496 |
#' |
|
| 497 |
.NextBestInfTheory <- setClass( |
|
| 498 |
Class = "NextBestInfTheory", |
|
| 499 |
slots = c( |
|
| 500 |
target = "numeric", |
|
| 501 |
asymmetry = "numeric" |
|
| 502 |
), |
|
| 503 |
prototype = prototype( |
|
| 504 |
target = 0.3, |
|
| 505 |
asymmetry = 1 |
|
| 506 |
), |
|
| 507 |
contains = "NextBest", |
|
| 508 |
validity = v_next_best_inf_theory |
|
| 509 |
) |
|
| 510 | ||
| 511 |
## constructor ---- |
|
| 512 | ||
| 513 |
#' @rdname NextBestInfTheory-class |
|
| 514 |
#' |
|
| 515 |
#' @param target (`proportion`)\cr see slot definition. |
|
| 516 |
#' @param asymmetry (`number`)\cr see slot definition. |
|
| 517 |
#' |
|
| 518 |
#' @export |
|
| 519 |
#' |
|
| 520 |
NextBestInfTheory <- function(target, asymmetry) {
|
|
| 521 | 16x |
.NextBestInfTheory(target = target, asymmetry = asymmetry) |
| 522 |
} |
|
| 523 | ||
| 524 |
## default constructor ---- |
|
| 525 | ||
| 526 |
#' @rdname NextBestInfTheory-class |
|
| 527 |
#' @note Typically, end users will not use the `.DefaultNextBestInfTheory()` function. |
|
| 528 |
#' @export |
|
| 529 |
.DefaultNextBestInfTheory <- function() {
|
|
| 530 | 8x |
NextBestInfTheory(0.33, 1.2) |
| 531 |
} |
|
| 532 | ||
| 533 |
# NextBestTD ---- |
|
| 534 | ||
| 535 |
## class ---- |
|
| 536 | ||
| 537 |
#' `NextBestTD` |
|
| 538 |
#' |
|
| 539 |
#' @description `r lifecycle::badge("stable")`
|
|
| 540 |
#' |
|
| 541 |
#' [`NextBestTD`] is the class to find a next best dose based on pseudo |
|
| 542 |
#' DLT model without samples. Namely, it is to find two next best doses, one |
|
| 543 |
#' for allocation during the trial and the second for final recommendation at |
|
| 544 |
#' the end of a trial without involving any samples, i.e. only DLT responses |
|
| 545 |
#' will be incorporated for the dose-allocation. This is based solely on the |
|
| 546 |
#' probabilities of the occurrence of a DLT obtained by using the modal estimates |
|
| 547 |
#' of the model parameters. There are two target probabilities of the |
|
| 548 |
#' occurrence of a DLT that must be specified: target probability to be used |
|
| 549 |
#' during the trial and target probability to be used at the end of the trial. |
|
| 550 |
#' It is suitable to use it only with the [`ModelTox`] model class. |
|
| 551 |
#' |
|
| 552 |
#' @slot prob_target_drt (`proportion`)\cr the target probability (except 0 or 1) |
|
| 553 |
#' of the occurrence of a DLT to be used during the trial. |
|
| 554 |
#' @slot prob_target_eot (`proportion`)\cr the target probability (except 0 or 1) |
|
| 555 |
#' of the occurrence of a DLT to be used at the end of the trial. |
|
| 556 |
#' |
|
| 557 |
#' @aliases NextBestTD |
|
| 558 |
#' @export |
|
| 559 |
#' |
|
| 560 |
.NextBestTD <- setClass( |
|
| 561 |
Class = "NextBestTD", |
|
| 562 |
slots = c( |
|
| 563 |
prob_target_drt = "numeric", |
|
| 564 |
prob_target_eot = "numeric" |
|
| 565 |
), |
|
| 566 |
prototype = prototype( |
|
| 567 |
prob_target_drt = 0.35, |
|
| 568 |
prob_target_eot = 0.3 |
|
| 569 |
), |
|
| 570 |
contains = "NextBest", |
|
| 571 |
validity = v_next_best_td |
|
| 572 |
) |
|
| 573 | ||
| 574 |
## default constructor ---- |
|
| 575 | ||
| 576 |
#' @rdname NextBestTD-class |
|
| 577 |
#' @note Typically, end users will not use the `.DefaultNextBestTD()` function. |
|
| 578 |
#' @export |
|
| 579 |
.DefaultNextBestTD <- function() {
|
|
| 580 | 8x |
NextBestTD(0.35, 0.3) |
| 581 |
} |
|
| 582 | ||
| 583 |
## constructor ---- |
|
| 584 | ||
| 585 |
#' @rdname NextBestTD-class |
|
| 586 |
#' |
|
| 587 |
#' @param prob_target_drt (`proportion`)\cr see slot definition. |
|
| 588 |
#' @param prob_target_eot (`proportion`)\cr see slot definition. |
|
| 589 |
#' |
|
| 590 |
#' @export |
|
| 591 |
#' @examples |
|
| 592 |
#' my_next_best <- NextBestTD(0.35, 0.3) |
|
| 593 |
NextBestTD <- function(prob_target_drt, prob_target_eot) {
|
|
| 594 | 26x |
.NextBestTD( |
| 595 | 26x |
prob_target_drt = prob_target_drt, |
| 596 | 26x |
prob_target_eot = prob_target_eot |
| 597 |
) |
|
| 598 |
} |
|
| 599 | ||
| 600 |
# NextBestTDsamples ---- |
|
| 601 | ||
| 602 |
## class ---- |
|
| 603 | ||
| 604 |
#' `NextBestTDsamples` |
|
| 605 |
#' |
|
| 606 |
#' @description `r lifecycle::badge("stable")`
|
|
| 607 |
#' |
|
| 608 |
#' [`NextBestTDsamples`] is the class to find a next best dose based on Pseudo |
|
| 609 |
#' DLT model with samples. Namely, it is to find two next best doses, one |
|
| 610 |
#' for allocation during the trial and the second for final recommendation at |
|
| 611 |
#' the end of a trial. Hence, there are two target probabilities of the |
|
| 612 |
#' occurrence of a DLT that must be specified: target probability to be used |
|
| 613 |
#' during the trial and target probability to be used at the end of the trial. |
|
| 614 |
#' |
|
| 615 |
#' @slot derive (`function`)\cr derives, based on a vector of posterior dose |
|
| 616 |
#' samples, the target dose that has the probability of the occurrence of |
|
| 617 |
#' DLT equals to either the `prob_target_drt` or `prob_target_eot`. It must |
|
| 618 |
#' therefore accept one and only one argument, which is a numeric vector, and |
|
| 619 |
#' return a number. |
|
| 620 |
#' |
|
| 621 |
#' @aliases NextBestTDsamples |
|
| 622 |
#' @export |
|
| 623 |
#' |
|
| 624 |
.NextBestTDsamples <- setClass( |
|
| 625 |
Class = "NextBestTDsamples", |
|
| 626 |
slots = c( |
|
| 627 |
derive = "function" |
|
| 628 |
), |
|
| 629 |
prototype = prototype( |
|
| 630 |
derive = function(dose_samples) {
|
|
| 631 |
quantile(dose_samples, prob = 0.3) |
|
| 632 |
} |
|
| 633 |
), |
|
| 634 |
contains = "NextBestTD", |
|
| 635 |
validity = v_next_best_td_samples |
|
| 636 |
) |
|
| 637 | ||
| 638 |
## constructor ---- |
|
| 639 | ||
| 640 |
#' @rdname NextBestTDsamples-class |
|
| 641 |
#' |
|
| 642 |
#' @param prob_target_drt (`proportion`)\cr see slot definition in [`NextBestTD`]. |
|
| 643 |
#' @param prob_target_eot (`proportion`)\cr see slot definition in [`NextBestTD`]. |
|
| 644 |
#' @param derive (`function`)\cr see slot definition. |
|
| 645 |
#' |
|
| 646 |
#' @export |
|
| 647 |
#' @example examples/Rules-class-NextBestTDsamples.R |
|
| 648 |
#' |
|
| 649 |
NextBestTDsamples <- function(prob_target_drt, prob_target_eot, derive) {
|
|
| 650 | 26x |
.NextBestTDsamples( |
| 651 | 26x |
prob_target_drt = prob_target_drt, |
| 652 | 26x |
prob_target_eot = prob_target_eot, |
| 653 | 26x |
derive = derive |
| 654 |
) |
|
| 655 |
} |
|
| 656 | ||
| 657 |
## default constructor ---- |
|
| 658 | ||
| 659 |
#' @rdname NextBestTDsamples-class |
|
| 660 |
#' @note Typically, end users will not use the `.DefaultNextBestTDsamples()` function. |
|
| 661 |
#' @export |
|
| 662 |
.DefaultNextBestTDsamples <- function() {
|
|
| 663 | 8x |
NextBestTDsamples( |
| 664 | 8x |
prob_target_drt = 0.35, |
| 665 | 8x |
prob_target_eot = 0.3, |
| 666 | 8x |
derive = function(samples) {
|
| 667 | 9x |
as.numeric(quantile(samples, probs = 0.3)) |
| 668 |
} |
|
| 669 |
) |
|
| 670 |
} |
|
| 671 | ||
| 672 | ||
| 673 |
# NextBestMaxGain ---- |
|
| 674 | ||
| 675 |
## class ---- |
|
| 676 | ||
| 677 |
#' `NextBestMaxGain` |
|
| 678 |
#' |
|
| 679 |
#' @description `r lifecycle::badge("stable")`
|
|
| 680 |
#' |
|
| 681 |
#' [`NextBestMaxGain`] is the class to find a next best dose with maximum gain |
|
| 682 |
#' value based on a pseudo DLT and efficacy models without samples. It is based |
|
| 683 |
#' solely on the probabilities of the occurrence of a DLT and the values |
|
| 684 |
#' of the mean efficacy responses obtained by using the modal estimates of the |
|
| 685 |
#' DLT and efficacy model parameters. There are two target probabilities of the |
|
| 686 |
#' occurrence of a DLT that must be specified: target probability to be used |
|
| 687 |
#' during the trial and target probability to be used at the end of the trial. |
|
| 688 |
#' It is suitable to use it only with the [`ModelTox`] model and [`ModelEff`] |
|
| 689 |
#' classes (except [`EffFlexi`]). |
|
| 690 |
#' |
|
| 691 |
#' @slot prob_target_drt (`proportion`)\cr the target probability of the |
|
| 692 |
#' occurrence of a DLT to be used during the trial. |
|
| 693 |
#' @slot prob_target_eot (`proportion`)\cr the target probability of the |
|
| 694 |
#' occurrence of a DLT to be used at the end of the trial. |
|
| 695 |
#' |
|
| 696 |
#' @aliases NextBestMaxGain |
|
| 697 |
#' @export |
|
| 698 |
#' |
|
| 699 |
.NextBestMaxGain <- setClass( |
|
| 700 |
Class = "NextBestMaxGain", |
|
| 701 |
slots = c( |
|
| 702 |
prob_target_drt = "numeric", |
|
| 703 |
prob_target_eot = "numeric" |
|
| 704 |
), |
|
| 705 |
prototype = prototype( |
|
| 706 |
prob_target_drt = 0.35, |
|
| 707 |
prob_target_eot = 0.3 |
|
| 708 |
), |
|
| 709 |
contains = "NextBest", |
|
| 710 |
validity = v_next_best_td |
|
| 711 |
) |
|
| 712 | ||
| 713 |
## constructor ---- |
|
| 714 | ||
| 715 |
#' @rdname NextBestMaxGain-class |
|
| 716 |
#' |
|
| 717 |
#' @param prob_target_drt (`proportion`)\cr see slot definition. |
|
| 718 |
#' @param prob_target_eot (`proportion`)\cr see slot definition. |
|
| 719 |
#' |
|
| 720 |
#' @export |
|
| 721 |
#' @examples |
|
| 722 |
#' my_next_best <- NextBestMaxGain(0.35, 0.3) |
|
| 723 |
NextBestMaxGain <- function(prob_target_drt, prob_target_eot) {
|
|
| 724 | 21x |
.NextBestMaxGain( |
| 725 | 21x |
prob_target_drt = prob_target_drt, |
| 726 | 21x |
prob_target_eot = prob_target_eot |
| 727 |
) |
|
| 728 |
} |
|
| 729 | ||
| 730 |
## default constructor ---- |
|
| 731 | ||
| 732 |
#' @rdname NextBestMaxGain-class |
|
| 733 |
#' @note Typically, end users will not use the `.DefaultNextBestMaxGain()` function. |
|
| 734 |
#' @export |
|
| 735 |
.DefaultNextBestMaxGain <- function() {
|
|
| 736 | 8x |
NextBestMaxGain(0.35, 0.3) |
| 737 |
} |
|
| 738 | ||
| 739 |
# NextBestMaxGainSamples ---- |
|
| 740 | ||
| 741 |
## class ---- |
|
| 742 | ||
| 743 |
#' `NextBestMaxGainSamples` |
|
| 744 |
#' |
|
| 745 |
#' @description `r lifecycle::badge("stable")`
|
|
| 746 |
#' |
|
| 747 |
#' [`NextBestMaxGainSamples`] is the class to find a next best dose with maximum |
|
| 748 |
#' gain value based on a pseudo DLT and efficacy models and DLT and efficacy |
|
| 749 |
#' samples. There are two target probabilities of the occurrence of a DLT that |
|
| 750 |
#' must be specified: target probability to be used during the trial and target |
|
| 751 |
#' probability to be used at the end of the trial. |
|
| 752 |
#' It is suitable to use it only with the [`ModelTox`] model and [`ModelEff`] |
|
| 753 |
#' classes. |
|
| 754 |
#' |
|
| 755 |
#' @slot derive (`function`)\cr derives, based on a vector of posterior dose |
|
| 756 |
#' samples, the target dose that has the probability of the occurrence of |
|
| 757 |
#' DLT equals to either the `prob_target_drt` or `prob_target_eot`. It must |
|
| 758 |
#' therefore accept one and only one argument, which is a numeric vector, and |
|
| 759 |
#' return a number. |
|
| 760 |
#' @slot mg_derive (`function`)\cr derives, based on a vector of posterior dose |
|
| 761 |
#' samples that give the maximum gain value, the final next best estimate of |
|
| 762 |
#' the dose that gives the maximum gain value. It must therefore accept one |
|
| 763 |
#' and only one argument, which is a numeric vector, and return a number. |
|
| 764 |
#' |
|
| 765 |
#' @aliases NextBestMaxGainSamples |
|
| 766 |
#' @export |
|
| 767 |
#' |
|
| 768 |
.NextBestMaxGainSamples <- setClass( |
|
| 769 |
Class = "NextBestMaxGainSamples", |
|
| 770 |
slots = c( |
|
| 771 |
derive = "function", |
|
| 772 |
mg_derive = "function" |
|
| 773 |
), |
|
| 774 |
prototype = prototype( |
|
| 775 |
prob_target_drt = 0.35, |
|
| 776 |
prob_target_eot = 0.3, |
|
| 777 |
derive = function(dose_samples) {
|
|
| 778 |
as.numeric(quantile(dose_samples, prob = 0.3)) |
|
| 779 |
}, |
|
| 780 |
mg_derive = function(dose_samples) {
|
|
| 781 |
as.numeric(quantile(dose_samples, prob = 0.5)) |
|
| 782 |
} |
|
| 783 |
), |
|
| 784 |
contains = "NextBestMaxGain", |
|
| 785 |
validity = v_next_best_max_gain_samples |
|
| 786 |
) |
|
| 787 | ||
| 788 |
## constructor ---- |
|
| 789 | ||
| 790 |
#' @rdname NextBestMaxGainSamples-class |
|
| 791 |
#' |
|
| 792 |
#' @param prob_target_drt (`proportion`)\cr see slot definition in [`NextBestMaxGain`]. |
|
| 793 |
#' @param prob_target_eot (`proportion`)\cr see slot definition in [`NextBestMaxGain`]. |
|
| 794 |
#' @param derive (`function`)\cr see slot definition. |
|
| 795 |
#' @param mg_derive (`function`)\cr see slot definition. |
|
| 796 |
#' |
|
| 797 |
#' @export |
|
| 798 |
#' @example examples/Rules-class-NextBestMaxGainSamples.R |
|
| 799 |
#' |
|
| 800 |
NextBestMaxGainSamples <- function( |
|
| 801 |
prob_target_drt, |
|
| 802 |
prob_target_eot, |
|
| 803 |
derive, |
|
| 804 |
mg_derive |
|
| 805 |
) {
|
|
| 806 | 25x |
.NextBestMaxGainSamples( |
| 807 | 25x |
prob_target_drt = prob_target_drt, |
| 808 | 25x |
prob_target_eot = prob_target_eot, |
| 809 | 25x |
derive = derive, |
| 810 | 25x |
mg_derive = mg_derive |
| 811 |
) |
|
| 812 |
} |
|
| 813 | ||
| 814 |
## default constructor ---- |
|
| 815 | ||
| 816 |
#' @rdname NextBestMaxGainSamples-class |
|
| 817 |
#' @note Typically, end users will not use the `.DefaultNextBestMaxGainSamples()` function. |
|
| 818 |
#' @export |
|
| 819 |
.DefaultNextBestMaxGainSamples <- function() {
|
|
| 820 | 8x |
NextBestMaxGainSamples( |
| 821 | 8x |
prob_target_drt = 0.35, |
| 822 | 8x |
prob_target_eot = 0.3, |
| 823 | 8x |
derive = function(samples) {
|
| 824 | 10x |
as.numeric(quantile(samples, prob = 0.3)) |
| 825 |
}, |
|
| 826 | 8x |
mg_derive = function(mg_samples) {
|
| 827 | 10x |
as.numeric(quantile(mg_samples, prob = 0.5)) |
| 828 |
} |
|
| 829 |
) |
|
| 830 |
} |
|
| 831 | ||
| 832 |
# NextBestProbMTDLTE ---- |
|
| 833 | ||
| 834 |
## class ---- |
|
| 835 | ||
| 836 |
#' `NextBestProbMTDLTE` |
|
| 837 |
#' |
|
| 838 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 839 |
#' |
|
| 840 |
#' [`NextBestProbMTDLTE`] is the class of finding a next best dose that selects |
|
| 841 |
#' the dose with the highest probability of having a toxicity rate less or equal |
|
| 842 |
#' to the toxicity target. |
|
| 843 |
#' The dose is determined by calculating the posterior toxicity probability |
|
| 844 |
#' for each dose per iteration and select the maximum dose that has a toxicity |
|
| 845 |
#' probability below or equal to the target. The dose with the highest frequency |
|
| 846 |
#' of being selected as MTD across iterations is the next best dose. Placebo |
|
| 847 |
#' is not considered in the calculation and removed from the dose grid for |
|
| 848 |
#' any calculations. |
|
| 849 |
#' |
|
| 850 |
#' @slot target (`numeric`)\cr the target toxicity probability. |
|
| 851 |
#' |
|
| 852 |
#' @aliases NextBestProbMTDLTE |
|
| 853 |
#' @export |
|
| 854 |
#' |
|
| 855 |
.NextBestProbMTDLTE <- setClass( |
|
| 856 |
Class = "NextBestProbMTDLTE", |
|
| 857 |
slots = c(target = "numeric"), |
|
| 858 |
prototype = prototype(target = 0.3), |
|
| 859 |
contains = "NextBest", |
|
| 860 |
validity = v_next_best_prob_mtd_lte |
|
| 861 |
) |
|
| 862 | ||
| 863 |
## constructor ---- |
|
| 864 | ||
| 865 |
#' @rdname NextBestProbMTDLTE-class |
|
| 866 |
#' |
|
| 867 |
#' @param target (`numeric`)\cr see slot definition. |
|
| 868 |
#' @export |
|
| 869 |
#' @example examples/Rules-class-NextBestProbMTDLTE.R |
|
| 870 |
#' |
|
| 871 |
NextBestProbMTDLTE <- function(target) {
|
|
| 872 | 14x |
.NextBestProbMTDLTE(target = target) |
| 873 |
} |
|
| 874 | ||
| 875 |
## default constructor ---- |
|
| 876 | ||
| 877 |
#' @rdname NextBestProbMTDLTE-class |
|
| 878 |
#' @note Typically, end users will not use the `.DefaultNextBestProbMTDLTE()` function. |
|
| 879 |
#' @export |
|
| 880 |
.DefaultNextBestProbMTDLTE <- function() {
|
|
| 881 | 8x |
NextBestProbMTDLTE(target = 0.3) |
| 882 |
} |
|
| 883 | ||
| 884 |
# NextBestProbMTDMinDist ---- |
|
| 885 | ||
| 886 |
## class ---- |
|
| 887 | ||
| 888 |
#' `NextBestProbMTDMinDist` |
|
| 889 |
#' |
|
| 890 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 891 |
#' |
|
| 892 |
#' [`NextBestProbMTDMinDist`] is the class of finding a next best dose that selects |
|
| 893 |
#' the dose with the highest probability of having a toxicity rate with the |
|
| 894 |
#' smallest distance to the toxicity target. |
|
| 895 |
#' The dose is determined by calculating the posterior toxicity probability |
|
| 896 |
#' for each dose per iteration and select the dose that has the smallest toxicity |
|
| 897 |
#' probability distance to the target. The dose with the highest frequency |
|
| 898 |
#' of being selected as MTD across iterations is the next best dose. Placebo |
|
| 899 |
#' is not considered as the next dose and for that reason not used in |
|
| 900 |
#' calculations. I.e. for placebo the toxicity probability distance to target |
|
| 901 |
#' is not calculated and taken into account for determination of the next dose. |
|
| 902 |
#' |
|
| 903 |
#' @slot target (`numeric`)\cr the target toxicity probability. |
|
| 904 |
#' |
|
| 905 |
#' @aliases NextBestProbMTDMinDist |
|
| 906 |
#' @export |
|
| 907 |
#' |
|
| 908 |
.NextBestProbMTDMinDist <- setClass( |
|
| 909 |
Class = "NextBestProbMTDMinDist", |
|
| 910 |
slots = c(target = "numeric"), |
|
| 911 |
prototype = prototype(target = 0.3), |
|
| 912 |
contains = "NextBest", |
|
| 913 |
validity = v_next_best_prob_mtd_min_dist |
|
| 914 |
) |
|
| 915 | ||
| 916 |
## constructor ---- |
|
| 917 | ||
| 918 |
#' @rdname NextBestProbMTDMinDist-class |
|
| 919 |
#' |
|
| 920 |
#' @param target (`numeric`)\cr see slot definition. |
|
| 921 |
#' @export |
|
| 922 |
#' @example examples/Rules-class-NextBestProbMTDMinDist.R |
|
| 923 |
#' |
|
| 924 |
NextBestProbMTDMinDist <- function(target) {
|
|
| 925 | 14x |
.NextBestProbMTDMinDist(target = target) |
| 926 |
} |
|
| 927 | ||
| 928 |
## default constructor ---- |
|
| 929 | ||
| 930 |
#' @rdname NextBestProbMTDMinDist-class |
|
| 931 |
#' @note Typically, end users will not use the `.DefaultNextBestProbMTDMinDist()` function. |
|
| 932 |
#' @export |
|
| 933 |
.DefaultNextBestProbMTDMinDist <- function() {
|
|
| 934 | 8x |
NextBestProbMTDMinDist(target = 0.3) |
| 935 |
} |
|
| 936 | ||
| 937 |
# NextBestOrdinal ---- |
|
| 938 | ||
| 939 |
## class ---- |
|
| 940 | ||
| 941 |
#' `NextBestOrdinal` |
|
| 942 |
#' |
|
| 943 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 944 |
#' |
|
| 945 |
#' [`NextBestOrdinal`] is the class for applying a standard `NextBest` rule to |
|
| 946 |
#' the results of an ordinal CRM trial. |
|
| 947 |
#' |
|
| 948 |
#' @slot grade (`integer`)\cr the toxicity grade to which the `rule` should be |
|
| 949 |
#' applied. |
|
| 950 |
#' @slot rule (`NextBest`)\cr the standard `NextBest` rule to be applied |
|
| 951 |
#' |
|
| 952 |
#' @aliases NextBestOrdinal |
|
| 953 |
#' @export |
|
| 954 |
#' |
|
| 955 |
.NextBestOrdinal <- setClass( |
|
| 956 |
Class = "NextBestOrdinal", |
|
| 957 |
slots = c(grade = "numeric", rule = "NextBest"), |
|
| 958 |
contains = "NextBest", |
|
| 959 |
validity = v_next_best_ordinal |
|
| 960 |
) |
|
| 961 | ||
| 962 |
## constructor ---- |
|
| 963 | ||
| 964 |
#' @rdname NextBestOrdinal-class |
|
| 965 |
#' |
|
| 966 |
#' @param grade (`numeric`)\cr see slot definition. |
|
| 967 |
#' @param rule (`NextBest`)\cr see slot definition. |
|
| 968 |
#' @export |
|
| 969 |
#' @example examples/Rules-class-NextBestOrdinal.R |
|
| 970 |
#' |
|
| 971 |
NextBestOrdinal <- function(grade, rule) {
|
|
| 972 | 33x |
.NextBestOrdinal(grade = grade, rule = rule) |
| 973 |
} |
|
| 974 | ||
| 975 |
## default constructor ---- |
|
| 976 | ||
| 977 |
#' @rdname NextBestOrdinal-class |
|
| 978 |
#' @note Typically, end users will not use the `.DefaultNextBestOrdinal()` function. |
|
| 979 |
#' @export |
|
| 980 |
.DefaultNextBestOrdinal <- function() {
|
|
| 981 | 12x |
NextBestOrdinal( |
| 982 | 12x |
grade = 1L, |
| 983 | 12x |
rule = NextBestMTD( |
| 984 | 12x |
0.25, |
| 985 | 12x |
function(mtd_samples) {
|
| 986 | 14x |
quantile(mtd_samples, probs = 0.25) |
| 987 |
} |
|
| 988 |
) |
|
| 989 |
) |
|
| 990 |
} |
|
| 991 | ||
| 992 |
# Increments ---- |
|
| 993 | ||
| 994 |
## class ---- |
|
| 995 | ||
| 996 |
#' `Increments` |
|
| 997 |
#' |
|
| 998 |
#' @description `r lifecycle::badge("stable")`
|
|
| 999 |
#' |
|
| 1000 |
#' [`Increments`] is a virtual class for controlling increments, from which all |
|
| 1001 |
#' other specific increments classes inherit. |
|
| 1002 |
#' |
|
| 1003 |
#' @seealso [`IncrementsRelative`], [`IncrementsRelativeDLT`], |
|
| 1004 |
#' [`IncrementsDoseLevels`], [`IncrementsHSRBeta`], [`IncrementsMin`]. |
|
| 1005 |
#' |
|
| 1006 |
#' @aliases Increments |
|
| 1007 |
#' @export |
|
| 1008 |
#' |
|
| 1009 |
setClass( |
|
| 1010 |
Class = "Increments", |
|
| 1011 |
contains = "CrmPackClass" |
|
| 1012 |
) |
|
| 1013 | ||
| 1014 |
## default constructor ---- |
|
| 1015 | ||
| 1016 |
#' @rdname Increments-class |
|
| 1017 |
#' @note Typically, end users will not use the `.DefaultIncrements()` function. |
|
| 1018 |
#' @export |
|
| 1019 |
.DefaultIncrements <- function() {
|
|
| 1020 | 2x |
stop(paste0( |
| 1021 | 2x |
"Class Increments cannot be instantiated directly. Please use one of its subclasses instead." |
| 1022 |
)) |
|
| 1023 |
} |
|
| 1024 | ||
| 1025 | ||
| 1026 |
# IncrementsRelative ---- |
|
| 1027 | ||
| 1028 |
## class ---- |
|
| 1029 | ||
| 1030 |
#' `IncrementsRelative` |
|
| 1031 |
#' |
|
| 1032 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1033 |
#' |
|
| 1034 |
#' [`IncrementsRelative`] is the class for increments control based on relative |
|
| 1035 |
#' differences in intervals. |
|
| 1036 |
#' |
|
| 1037 |
#' @slot intervals (`numeric`)\cr a vector with the left bounds of the relevant |
|
| 1038 |
#' intervals. For example, `intervals = c(0, 50, 100)` specifies three intervals: |
|
| 1039 |
#' \eqn{(0, 50)}, \eqn{[50, 100)} and \eqn{[100, +Inf)}. That means, the right
|
|
| 1040 |
#' bound of the intervals are exclusive to the interval and the last interval |
|
| 1041 |
#' goes from the last value to infinity. |
|
| 1042 |
#' @slot increments (`numeric`)\cr a vector of the same length with the maximum |
|
| 1043 |
#' allowable relative increments in the `intervals`. |
|
| 1044 |
#' |
|
| 1045 |
#' @aliases IncrementsRelative |
|
| 1046 |
#' @export |
|
| 1047 |
#' |
|
| 1048 |
.IncrementsRelative <- setClass( |
|
| 1049 |
Class = "IncrementsRelative", |
|
| 1050 |
slots = c( |
|
| 1051 |
intervals = "numeric", |
|
| 1052 |
increments = "numeric" |
|
| 1053 |
), |
|
| 1054 |
prototype = prototype( |
|
| 1055 |
intervals = c(0, 2), |
|
| 1056 |
increments = c(2, 1) |
|
| 1057 |
), |
|
| 1058 |
contains = "Increments", |
|
| 1059 |
validity = v_increments_relative |
|
| 1060 |
) |
|
| 1061 | ||
| 1062 |
## constructor ---- |
|
| 1063 | ||
| 1064 |
#' @rdname IncrementsRelative-class |
|
| 1065 |
#' |
|
| 1066 |
#' @param intervals (`numeric`)\cr see slot definition. |
|
| 1067 |
#' @param increments (`numeric`)\cr see slot definition. |
|
| 1068 |
#' |
|
| 1069 |
#' @export |
|
| 1070 |
#' @example examples/Rules-class-IncrementsRelative.R |
|
| 1071 |
#' |
|
| 1072 |
IncrementsRelative <- function(intervals, increments) {
|
|
| 1073 | 155x |
.IncrementsRelative( |
| 1074 | 155x |
intervals = intervals, |
| 1075 | 155x |
increments = increments |
| 1076 |
) |
|
| 1077 |
} |
|
| 1078 | ||
| 1079 |
## default constructor ---- |
|
| 1080 | ||
| 1081 |
#' @rdname IncrementsRelative-class |
|
| 1082 |
#' @note Typically, end users will not use the `.DefaultIncrementsRelative()` function. |
|
| 1083 |
#' @export |
|
| 1084 |
.DefaultIncrementsRelative <- function() {
|
|
| 1085 | 9x |
IncrementsRelative(intervals = c(0, 20), increments = c(1, 0.33)) |
| 1086 |
} |
|
| 1087 | ||
| 1088 | ||
| 1089 |
# IncrementsRelativeDLT ---- |
|
| 1090 | ||
| 1091 |
## class ---- |
|
| 1092 | ||
| 1093 |
#' `IncrementsRelativeDLT` |
|
| 1094 |
#' |
|
| 1095 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1096 |
#' |
|
| 1097 |
#' [`IncrementsRelativeDLT`] is the class for increments control based on |
|
| 1098 |
#' relative differences in terms of DLTs. |
|
| 1099 |
#' |
|
| 1100 |
#' @slot intervals (`integer`)\cr a vector with the left bounds of the |
|
| 1101 |
#' relevant DLT intervals. For example, `intervals = c(0, 1, 3)` specifies |
|
| 1102 |
#' three intervals (sets of DLTs: first, 0 DLT; second 1 or 2 DLTs; and the third |
|
| 1103 |
#' one, at least 3 DLTs. That means, the right bound of the intervals are |
|
| 1104 |
#' exclusive to the interval and the last interval goes from the last value to |
|
| 1105 |
#' infinity. |
|
| 1106 |
#' @slot increments (`numeric`)\cr a vector of maximum allowable relative |
|
| 1107 |
#' increments corresponding to `intervals`. IT must be of the same length |
|
| 1108 |
#' as the length of `intervals`. |
|
| 1109 |
#' |
|
| 1110 |
#' @note This considers all DLTs across all cohorts observed so far. |
|
| 1111 |
#' |
|
| 1112 |
#' @seealso [IncrementsRelativeDLTCurrent] which only considers the DLTs |
|
| 1113 |
#' in the current cohort. |
|
| 1114 |
#' |
|
| 1115 |
#' @aliases IncrementsRelativeDLT |
|
| 1116 |
#' @export |
|
| 1117 |
#' |
|
| 1118 |
.IncrementsRelativeDLT <- setClass( |
|
| 1119 |
Class = "IncrementsRelativeDLT", |
|
| 1120 |
slots = representation( |
|
| 1121 |
intervals = "integer", |
|
| 1122 |
increments = "numeric" |
|
| 1123 |
), |
|
| 1124 |
prototype = prototype( |
|
| 1125 |
intervals = c(0L, 1L), |
|
| 1126 |
increments = c(2, 1) |
|
| 1127 |
), |
|
| 1128 |
contains = "Increments", |
|
| 1129 |
validity = v_increments_relative_dlt |
|
| 1130 |
) |
|
| 1131 | ||
| 1132 |
## constructor ---- |
|
| 1133 | ||
| 1134 |
#' @rdname IncrementsRelativeDLT-class |
|
| 1135 |
#' |
|
| 1136 |
#' @param intervals (`numeric`)\cr see slot definition. |
|
| 1137 |
#' @param increments (`numeric`)\cr see slot definition. |
|
| 1138 |
#' |
|
| 1139 |
#' @export |
|
| 1140 |
#' @example examples/Rules-class-IncrementsRelativeDLT.R |
|
| 1141 |
#' |
|
| 1142 |
IncrementsRelativeDLT <- function(intervals, increments) {
|
|
| 1143 | 35x |
assert_integerish(intervals, lower = 0, any.missing = FALSE) |
| 1144 | 35x |
assert_numeric(increments, any.missing = FALSE, lower = 0) |
| 1145 | ||
| 1146 | 35x |
.IncrementsRelativeDLT( |
| 1147 | 35x |
intervals = as.integer(intervals), |
| 1148 | 35x |
increments = increments |
| 1149 |
) |
|
| 1150 |
} |
|
| 1151 | ||
| 1152 |
## default constructor ---- |
|
| 1153 | ||
| 1154 |
#' @rdname IncrementsRelativeDLT-class |
|
| 1155 |
#' @note Typically, end users will not use the `.DefaultIncrementsRelativeDLT()` function. |
|
| 1156 |
#' @export |
|
| 1157 |
.DefaultIncrementsRelativeDLT <- function() {
|
|
| 1158 | 8x |
IncrementsRelativeDLT(intervals = c(0L, 1L, 3L), increments = c(1, 0.33, 0.2)) |
| 1159 |
} |
|
| 1160 | ||
| 1161 |
# IncrementsRelativeDLTCurrent ---- |
|
| 1162 | ||
| 1163 |
## class ---- |
|
| 1164 | ||
| 1165 |
#' `IncrementsRelativeDLTCurrent` |
|
| 1166 |
#' |
|
| 1167 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1168 |
#' |
|
| 1169 |
#' [`IncrementsRelativeDLTCurrent`] is the class for increments control based on |
|
| 1170 |
#' relative differences and current DLTs. The class is based on the number of |
|
| 1171 |
#' DLTs observed in the current cohort, but not cumulatively over all cohorts |
|
| 1172 |
#' so far. |
|
| 1173 |
#' |
|
| 1174 |
#' @seealso [IncrementsRelativeDLT]. |
|
| 1175 |
#' |
|
| 1176 |
#' @aliases IncrementsRelativeDLTCurrent |
|
| 1177 |
#' @export |
|
| 1178 |
#' |
|
| 1179 |
.IncrementsRelativeDLTCurrent <- setClass( |
|
| 1180 |
Class = "IncrementsRelativeDLTCurrent", |
|
| 1181 |
contains = "IncrementsRelativeDLT" |
|
| 1182 |
) |
|
| 1183 | ||
| 1184 |
## constructor ---- |
|
| 1185 | ||
| 1186 |
#' @rdname IncrementsRelativeDLTCurrent-class |
|
| 1187 |
#' |
|
| 1188 |
#' @inheritParams IncrementsRelativeDLT |
|
| 1189 |
#' |
|
| 1190 |
#' @export |
|
| 1191 |
#' @example examples/Rules-class-IncrementsRelativeDLTCurrent.R |
|
| 1192 |
#' |
|
| 1193 |
IncrementsRelativeDLTCurrent <- function( |
|
| 1194 |
intervals = c(0L, 1L), |
|
| 1195 |
increments = c(2L, 1L) |
|
| 1196 |
) {
|
|
| 1197 | 17x |
assert_integerish(intervals, lower = 0, any.missing = FALSE) |
| 1198 | 17x |
assert_numeric(increments, any.missing = FALSE, lower = 0) |
| 1199 | ||
| 1200 | 17x |
.IncrementsRelativeDLTCurrent( |
| 1201 | 17x |
intervals = as.integer(intervals), |
| 1202 | 17x |
increments = increments |
| 1203 |
) |
|
| 1204 |
} |
|
| 1205 | ||
| 1206 |
## default constructor ---- |
|
| 1207 | ||
| 1208 |
#' @rdname IncrementsRelativeDLTCurrent-class |
|
| 1209 |
#' @note Typically, end users will not use the `.DefaultIncrementsRelativeDLTCurrent()` function. |
|
| 1210 |
#' @export |
|
| 1211 |
.DefaultIncrementsRelativeDLTCurrent <- function() {
|
|
| 1212 |
# nolint |
|
| 1213 | 7x |
IncrementsRelativeDLTCurrent( |
| 1214 | 7x |
intervals = c(0L, 1L, 3L), |
| 1215 | 7x |
increments = c(1, 0.33, 0.2) |
| 1216 |
) |
|
| 1217 |
} |
|
| 1218 | ||
| 1219 |
# IncrementsRelativeParts ---- |
|
| 1220 | ||
| 1221 |
## class ---- |
|
| 1222 | ||
| 1223 |
#' `IncrementsRelativeParts` |
|
| 1224 |
#' |
|
| 1225 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1226 |
#' |
|
| 1227 |
#' [`IncrementsRelativeParts`] is the class for increments control based on |
|
| 1228 |
#' relative differences in intervals, with special rules for part 1 and |
|
| 1229 |
#' beginning of part 2. |
|
| 1230 |
#' |
|
| 1231 |
#' @details This class works only in conjunction with [`DataParts`] objects. If |
|
| 1232 |
#' part 2 will just be started in the next cohort, then the next maximum dose |
|
| 1233 |
#' will be either `dlt_start` (e.g. -1) shift of the last part 1 dose in case |
|
| 1234 |
#' of a DLT in part 1, or `clean_start` shift (e.g. -1) in case of no DLTs in |
|
| 1235 |
#' part 1, given that `clean_start <= 0` (see description of `clean_start` |
|
| 1236 |
#' slot for more details). If part 1 will still be on in the next cohort, |
|
| 1237 |
#' then the next dose level will be the next higher dose level in the |
|
| 1238 |
#' `part1Ladder` slot of the data object. If part 2 has been started before, |
|
| 1239 |
#' the usual relative increment rules apply, see [`IncrementsRelative`]. |
|
| 1240 |
#' |
|
| 1241 |
#' @slot dlt_start (`integer`)\cr a scalar, the dose level increment for starting |
|
| 1242 |
#' part 2 in case of at least one DLT event in part 1. |
|
| 1243 |
#' @slot clean_start (`integer`)\cr a scalar, the dose level increment for |
|
| 1244 |
#' starting part 2 in case of no DLTs in part 1. If `clean_start <= 0`, |
|
| 1245 |
#' then the part 1 ladder will be used to find the maximum next dose. |
|
| 1246 |
#' Otherwise, the relative increment rules will be applied to find the next |
|
| 1247 |
#' maximum dose level. |
|
| 1248 |
#' |
|
| 1249 |
#' @note We require that `clean_start >= dlt_start`. However, this precondition |
|
| 1250 |
#' is not a prerequisite for any function (except of the class' validation |
|
| 1251 |
#' function) that works with objects of this class. It is rather motivated by |
|
| 1252 |
#' the semantics. That is, if we observe a DLT in part 1, we cannot be more |
|
| 1253 |
#' aggressive than in case of a clean part 1 without DLT. |
|
| 1254 |
#' |
|
| 1255 |
#' @aliases IncrementsRelativeParts |
|
| 1256 |
#' @export |
|
| 1257 |
#' |
|
| 1258 |
.IncrementsRelativeParts <- setClass( |
|
| 1259 |
Class = "IncrementsRelativeParts", |
|
| 1260 |
slots = representation( |
|
| 1261 |
dlt_start = "integer", |
|
| 1262 |
clean_start = "integer" |
|
| 1263 |
), |
|
| 1264 |
prototype = prototype( |
|
| 1265 |
dlt_start = -1L, |
|
| 1266 |
clean_start = 1L |
|
| 1267 |
), |
|
| 1268 |
contains = "IncrementsRelative", |
|
| 1269 |
validity = v_increments_relative_parts |
|
| 1270 |
) |
|
| 1271 | ||
| 1272 |
## constructor ---- |
|
| 1273 | ||
| 1274 |
#' @rdname IncrementsRelativeParts-class |
|
| 1275 |
#' |
|
| 1276 |
#' @param dlt_start (`count`)\cr see slot definition. |
|
| 1277 |
#' @param clean_start (`count`)\cr see slot definition. |
|
| 1278 |
#' @inheritDotParams IncrementsRelative |
|
| 1279 |
#' |
|
| 1280 |
#' @export |
|
| 1281 |
#' @example examples/Rules-class-IncrementsRelative-DataParts.R |
|
| 1282 |
#' |
|
| 1283 |
IncrementsRelativeParts <- function(dlt_start, clean_start, ...) {
|
|
| 1284 | 29x |
assert_integerish(dlt_start) |
| 1285 | 29x |
assert_integerish(clean_start) |
| 1286 | ||
| 1287 | 29x |
.IncrementsRelativeParts( |
| 1288 | 29x |
dlt_start = as.integer(dlt_start), |
| 1289 | 29x |
clean_start = as.integer(clean_start), |
| 1290 |
... |
|
| 1291 |
) |
|
| 1292 |
} |
|
| 1293 | ||
| 1294 |
## default constructor ---- |
|
| 1295 | ||
| 1296 |
#' @rdname IncrementsRelativeParts-class |
|
| 1297 |
#' @note Typically, end users will not use the `.DefaultIncrementsRelativeParts()` function. |
|
| 1298 |
#' @export |
|
| 1299 |
.DefaultIncrementsRelativeParts <- function() {
|
|
| 1300 | 9x |
IncrementsRelativeParts(dlt_start = 0L, clean_start = 1L) |
| 1301 |
} |
|
| 1302 | ||
| 1303 |
# IncrementsDoseLevels ---- |
|
| 1304 | ||
| 1305 |
## class ---- |
|
| 1306 | ||
| 1307 |
#' `IncrementsDoseLevels` |
|
| 1308 |
#' |
|
| 1309 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1310 |
#' |
|
| 1311 |
#' [`IncrementsDoseLevels`] is the class for increments control based on the |
|
| 1312 |
#' number of dose levels. |
|
| 1313 |
#' |
|
| 1314 |
#' @slot levels (`count`)\cr maximum number of dose levels to increment for |
|
| 1315 |
#' the next dose. It defaults to 1, which means that no dose skipping is |
|
| 1316 |
#' allowed, i.e. the next dose can be maximum one level higher than the current |
|
| 1317 |
#' base dose. The current base dose level is the dose level used to increment |
|
| 1318 |
#' from (see `basis_level` parameter). |
|
| 1319 |
#' @slot basis_level (`string`)\cr defines the current base dose level. It can |
|
| 1320 |
#' take one out of two possible values: `last` or `max`. |
|
| 1321 |
#' If `last` is specified (default), the current base dose level is set to the |
|
| 1322 |
#' last dose given. If `max` is specified, then the current base dose level is |
|
| 1323 |
#' set to the maximum dose level given. |
|
| 1324 |
#' |
|
| 1325 |
#' @aliases IncrementsDoseLevels |
|
| 1326 |
#' @export |
|
| 1327 |
#' |
|
| 1328 |
.IncrementsDoseLevels <- setClass( |
|
| 1329 |
Class = "IncrementsDoseLevels", |
|
| 1330 |
slots = representation( |
|
| 1331 |
levels = "integer", |
|
| 1332 |
basis_level = "character" |
|
| 1333 |
), |
|
| 1334 |
prototype = prototype( |
|
| 1335 |
levels = 1L, |
|
| 1336 |
basis_level = "last" |
|
| 1337 |
), |
|
| 1338 |
contains = "Increments", |
|
| 1339 |
validity = v_increments_dose_levels |
|
| 1340 |
) |
|
| 1341 | ||
| 1342 |
## constructor ---- |
|
| 1343 | ||
| 1344 |
#' @rdname IncrementsDoseLevels-class |
|
| 1345 |
#' |
|
| 1346 |
#' @param levels (`count`)\cr see slot definition. |
|
| 1347 |
#' @param basis_level (`string`)\cr see slot definition. |
|
| 1348 |
#' |
|
| 1349 |
#' @export |
|
| 1350 |
#' @example examples/Rules-class-IncrementsDoseLevels.R |
|
| 1351 |
#' |
|
| 1352 |
IncrementsDoseLevels <- function(levels = 1L, basis_level = "last") {
|
|
| 1353 | 31x |
assert_count(levels, positive = TRUE) |
| 1354 | 31x |
assert_string(basis_level) |
| 1355 | 31x |
assert_subset(basis_level, c("last", "max"))
|
| 1356 | ||
| 1357 | 31x |
.IncrementsDoseLevels( |
| 1358 | 31x |
levels = as.integer(levels), |
| 1359 | 31x |
basis_level = basis_level |
| 1360 |
) |
|
| 1361 |
} |
|
| 1362 | ||
| 1363 |
## default constructor ---- |
|
| 1364 | ||
| 1365 |
#' @rdname IncrementsDoseLevels-class |
|
| 1366 |
#' @note Typically, end users will not use the `.DefaultIncrementsDoseLevels()` function. |
|
| 1367 |
#' @export |
|
| 1368 |
.DefaultIncrementsDoseLevels <- function() {
|
|
| 1369 | 7x |
IncrementsDoseLevels(levels = 2L, basis_level = "last") |
| 1370 |
} |
|
| 1371 | ||
| 1372 |
# IncrementsHSRBeta ---- |
|
| 1373 | ||
| 1374 |
## class ---- |
|
| 1375 | ||
| 1376 |
#' `IncrementsHSRBeta` |
|
| 1377 |
#' |
|
| 1378 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1379 |
#' |
|
| 1380 |
#' [`IncrementsHSRBeta`] is a class for limiting further increments using |
|
| 1381 |
#' a Hard Safety Rule based on the Bin-Beta model. |
|
| 1382 |
#' Increment control is based on the number of observed DLTs and number of |
|
| 1383 |
#' subjects at each dose level. The probability of toxicity is calculated |
|
| 1384 |
#' using a Bin-Beta model with prior (a,b). If the probability exceeds |
|
| 1385 |
#' the threshold for a given dose, that dose and all doses above are excluded |
|
| 1386 |
#' from further escalation. |
|
| 1387 |
#' This is a hard safety rule that limits further escalation based on the |
|
| 1388 |
#' observed data per dose level, independent from the underlying model. |
|
| 1389 |
#' |
|
| 1390 |
#' @slot target (`proportion`)\cr the target toxicity, except |
|
| 1391 |
#' 0 or 1. |
|
| 1392 |
#' @slot prob (`proportion`)\cr the threshold probability (except 0 or 1) for |
|
| 1393 |
#' a dose being toxic. |
|
| 1394 |
#' @slot a (`number`)\cr shape parameter \eqn{a > 0} of probability distribution
|
|
| 1395 |
#' Beta (a,b). |
|
| 1396 |
#' @slot b (`number`)\cr shape parameter \eqn{b > 0} of probability distribution
|
|
| 1397 |
#' Beta (a,b). |
|
| 1398 |
#' |
|
| 1399 |
#' @aliases IncrementsHSRBeta |
|
| 1400 |
#' @export |
|
| 1401 |
#' |
|
| 1402 |
.IncrementsHSRBeta <- setClass( |
|
| 1403 |
Class = "IncrementsHSRBeta", |
|
| 1404 |
slots = c( |
|
| 1405 |
target = "numeric", |
|
| 1406 |
prob = "numeric", |
|
| 1407 |
a = "numeric", |
|
| 1408 |
b = "numeric" |
|
| 1409 |
), |
|
| 1410 |
prototype = prototype( |
|
| 1411 |
target = 0.3, |
|
| 1412 |
prob = 0.95, |
|
| 1413 |
a = 1, |
|
| 1414 |
b = 1 |
|
| 1415 |
), |
|
| 1416 |
contains = "Increments", |
|
| 1417 |
validity = v_increments_hsr_beta |
|
| 1418 |
) |
|
| 1419 | ||
| 1420 |
## constructor ---- |
|
| 1421 | ||
| 1422 |
#' @rdname IncrementsHSRBeta-class |
|
| 1423 |
#' |
|
| 1424 |
#' @param target (`proportion`)\cr see slot definition. |
|
| 1425 |
#' @param prob (`proportion`)\cr see slot definition. |
|
| 1426 |
#' @param a (`number`)\cr see slot definition. |
|
| 1427 |
#' @param b (`number`)\cr see slot definition. |
|
| 1428 |
#' |
|
| 1429 |
#' @example examples/Rules-class-IncrementsHSRBeta.R |
|
| 1430 |
#' @export |
|
| 1431 |
#' |
|
| 1432 |
IncrementsHSRBeta <- function(target = 0.3, prob = 0.95, a = 1, b = 1) {
|
|
| 1433 | 22x |
.IncrementsHSRBeta( |
| 1434 | 22x |
target = target, |
| 1435 | 22x |
prob = prob, |
| 1436 | 22x |
a = a, |
| 1437 | 22x |
b = b |
| 1438 |
) |
|
| 1439 |
} |
|
| 1440 | ||
| 1441 |
## default constructor ---- |
|
| 1442 | ||
| 1443 |
#' @rdname IncrementsHSRBeta-class |
|
| 1444 |
#' @note Typically, end users will not use the `.DefaultIncrementsHSRBeta()` function. |
|
| 1445 |
#' @export |
|
| 1446 |
.DefaultIncrementsHSRBeta <- function() {
|
|
| 1447 | 7x |
IncrementsHSRBeta(target = 0.3, prob = 0.95) |
| 1448 |
} |
|
| 1449 | ||
| 1450 |
# IncrementsMin ---- |
|
| 1451 | ||
| 1452 |
## class ---- |
|
| 1453 | ||
| 1454 |
#' `IncrementsMin` |
|
| 1455 |
#' |
|
| 1456 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1457 |
#' |
|
| 1458 |
#' [`IncrementsMin`] is the class that combines multiple increment rules with |
|
| 1459 |
#' the `minimum` operation. Slot `increments_list` contains all increment rules, |
|
| 1460 |
#' which are itself the objects of class [`Increments`]. The minimum of these |
|
| 1461 |
#' individual increments is taken to give the final maximum increment. |
|
| 1462 |
#' |
|
| 1463 |
#' @slot increments_list (`list`)\cr list with increment rules. |
|
| 1464 |
#' |
|
| 1465 |
#' @aliases IncrementsMin |
|
| 1466 |
#' @export |
|
| 1467 |
#' |
|
| 1468 |
.IncrementsMin <- setClass( |
|
| 1469 |
Class = "IncrementsMin", |
|
| 1470 |
slots = c(increments_list = "list"), |
|
| 1471 |
prototype = prototype( |
|
| 1472 |
increments_list = list( |
|
| 1473 |
IncrementsRelativeDLT(intervals = c(0L, 1L), increments = c(2, 1)), |
|
| 1474 |
IncrementsRelative(intervals = c(0, 2), increments = c(2, 1)) |
|
| 1475 |
) |
|
| 1476 |
), |
|
| 1477 |
contains = "Increments", |
|
| 1478 |
validity = v_increments_min |
|
| 1479 |
) |
|
| 1480 | ||
| 1481 |
## constructor ---- |
|
| 1482 | ||
| 1483 |
#' @rdname IncrementsMin-class |
|
| 1484 |
#' |
|
| 1485 |
#' @param increments_list (`list`)\cr see slot definition. |
|
| 1486 |
#' |
|
| 1487 |
#' @example examples/Rules-class-IncrementsMin.R |
|
| 1488 |
#' @export |
|
| 1489 |
#' |
|
| 1490 |
IncrementsMin <- function(increments_list) {
|
|
| 1491 | 17x |
.IncrementsMin(increments_list = increments_list) |
| 1492 |
} |
|
| 1493 |
## default constructor ---- |
|
| 1494 | ||
| 1495 |
#' @rdname IncrementsMin-class |
|
| 1496 |
#' @note Typically, end users will not use the `.DefaultIncrementsMin()` function. |
|
| 1497 |
#' @export |
|
| 1498 |
.DefaultIncrementsMin <- function() {
|
|
| 1499 | 8x |
IncrementsMin( |
| 1500 | 8x |
increments_list = list( |
| 1501 | 8x |
IncrementsRelativeDLT( |
| 1502 | 8x |
intervals = c(0, 1, 3), |
| 1503 | 8x |
increments = c(1, 0.33, 0.2) |
| 1504 |
), |
|
| 1505 | 8x |
IncrementsRelative(intervals = c(0, 20), increments = c(1, 0.33)) |
| 1506 |
) |
|
| 1507 |
) |
|
| 1508 |
} |
|
| 1509 | ||
| 1510 |
# IncrementsOrdinal ---- |
|
| 1511 | ||
| 1512 |
## class ---- |
|
| 1513 | ||
| 1514 |
#' `IncrementsOrdinal` |
|
| 1515 |
#' |
|
| 1516 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1517 |
#' |
|
| 1518 |
#' [`IncrementsOrdinal`] is the class for applying a standard `Increments` rule to |
|
| 1519 |
#' the results of an ordinal CRM trial. |
|
| 1520 |
#' |
|
| 1521 |
#' @slot grade (`integer`)\cr the toxicity grade to which the `rule` should be |
|
| 1522 |
#' applied. |
|
| 1523 |
#' @slot rule (`Increments`)\cr the standard `Increments` rule to be applied |
|
| 1524 |
#' |
|
| 1525 |
#' @aliases IncrementsOrdinal |
|
| 1526 |
#' @export |
|
| 1527 |
#' |
|
| 1528 |
.IncrementsOrdinal <- setClass( |
|
| 1529 |
Class = "IncrementsOrdinal", |
|
| 1530 |
slots = c(grade = "numeric", rule = "Increments"), |
|
| 1531 |
contains = "Increments", |
|
| 1532 |
validity = v_increments_ordinal |
|
| 1533 |
) |
|
| 1534 | ||
| 1535 |
## constructor ---- |
|
| 1536 | ||
| 1537 |
#' @rdname IncrementsOrdinal-class |
|
| 1538 |
#' |
|
| 1539 |
#' @param grade (`numeric`)\cr see slot definition. |
|
| 1540 |
#' @param rule (`Increments`)\cr see slot definition. |
|
| 1541 |
#' @export |
|
| 1542 |
#' @example examples/Rules-class-IncrementsOrdinal.R |
|
| 1543 |
#' |
|
| 1544 |
IncrementsOrdinal <- function(grade, rule) {
|
|
| 1545 | 24x |
.IncrementsOrdinal(grade = grade, rule = rule) |
| 1546 |
} |
|
| 1547 | ||
| 1548 |
## default constructor ---- |
|
| 1549 | ||
| 1550 |
#' @rdname IncrementsOrdinal-class |
|
| 1551 |
#' @note Typically, end users will not use the `.DefaultIncrementsOrdinal()` function. |
|
| 1552 |
#' @export |
|
| 1553 |
.DefaultIncrementsOrdinal <- function() {
|
|
| 1554 | 10x |
IncrementsOrdinal( |
| 1555 | 10x |
grade = 1L, |
| 1556 | 10x |
rule = IncrementsRelative(intervals = c(0, 20), increments = c(1, 0.33)) |
| 1557 |
) |
|
| 1558 |
} |
|
| 1559 | ||
| 1560 |
# IncrementsMaxToxProb ---- |
|
| 1561 | ||
| 1562 |
## class ---- |
|
| 1563 | ||
| 1564 |
#' `IncrementsMaxToxProb` |
|
| 1565 |
#' |
|
| 1566 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1567 |
#' |
|
| 1568 |
#' [`IncrementsMaxToxProb`] is the class for increments control based on |
|
| 1569 |
#' probability of toxicity |
|
| 1570 |
#' |
|
| 1571 |
#' @slot prob (`numeric`)\cr See Usage Notes below. |
|
| 1572 |
#' |
|
| 1573 |
#' @section Usage Notes: |
|
| 1574 |
#' For binary models, `prob` should be a scalar probability. |
|
| 1575 |
#' |
|
| 1576 |
#' For ordinal models, `prob` should be a named vector containing the maximum |
|
| 1577 |
#' permissible probability of toxicity by grade. The names should match the |
|
| 1578 |
#' names of the `yCategories` slot of the associated `DataOrdinal` object. |
|
| 1579 |
#' |
|
| 1580 |
#' @aliases IncrementsMaxToxProb |
|
| 1581 |
#' @export |
|
| 1582 |
#' |
|
| 1583 |
.IncrementsMaxToxProb <- setClass( |
|
| 1584 |
Class = "IncrementsMaxToxProb", |
|
| 1585 |
slots = c( |
|
| 1586 |
prob = "numeric" |
|
| 1587 |
), |
|
| 1588 |
prototype = prototype( |
|
| 1589 |
prob = c("DLAE" = 0.2, "DLT" = 0.05)
|
|
| 1590 |
), |
|
| 1591 |
contains = "Increments", |
|
| 1592 |
validity = v_increments_maxtoxprob |
|
| 1593 |
) |
|
| 1594 | ||
| 1595 |
## constructor ---- |
|
| 1596 | ||
| 1597 |
#' @rdname IncrementsMaxToxProb-class |
|
| 1598 |
#' |
|
| 1599 |
#' @param prob (`numeric`)\cr see slot definition. |
|
| 1600 |
#' |
|
| 1601 |
#' @export |
|
| 1602 |
#' @example examples/Rules-class-IncrementsMaxToxProb.R |
|
| 1603 |
#' |
|
| 1604 |
IncrementsMaxToxProb <- function(prob) {
|
|
| 1605 | 15x |
.IncrementsMaxToxProb( |
| 1606 | 15x |
prob = prob |
| 1607 |
) |
|
| 1608 |
} |
|
| 1609 | ||
| 1610 |
## default constructor ---- |
|
| 1611 | ||
| 1612 |
#' @rdname IncrementsMaxToxProb-class |
|
| 1613 |
#' @note Typically, end users will not use the `.DefaultIncrementsMaxToxProb()` function. |
|
| 1614 |
#' @export |
|
| 1615 |
.DefaultIncrementsMaxToxProb <- function() {
|
|
| 1616 | 6x |
IncrementsMaxToxProb(prob = c("DLAE" = 0.2, "DLT" = 0.05))
|
| 1617 |
} |
|
| 1618 | ||
| 1619 |
# Stopping ---- |
|
| 1620 | ||
| 1621 |
## class ---- |
|
| 1622 | ||
| 1623 |
#' `Stopping` |
|
| 1624 |
#' |
|
| 1625 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1626 |
#' |
|
| 1627 |
#' [`Stopping`] is a class for stopping rules. |
|
| 1628 |
#' |
|
| 1629 |
#' @slot report_label (`string`)\cr a label for the stopping report. The meaning |
|
| 1630 |
#' of this parameter is twofold. If it is equal to `NA_character_` (default), |
|
| 1631 |
#' the `report_label` will not be used in the report at all. Otherwise, if it |
|
| 1632 |
#' is specified as an empty character (i.e. `character(0)`) in a user constructor, |
|
| 1633 |
#' then a default, class-specific label will be created for this slot. |
|
| 1634 |
#' Finally, for the remaining cases, a user can provide a custom label. |
|
| 1635 |
#' |
|
| 1636 |
#' @seealso [`StoppingList`], [`StoppingCohortsNearDose`], [`StoppingPatientsNearDose`], |
|
| 1637 |
#' [`StoppingMinCohorts`], [`StoppingMinPatients`], [`StoppingTargetProb`], |
|
| 1638 |
#' [`StoppingMTDdistribution`], [`StoppingTargetBiomarker`], [`StoppingHighestDose`] |
|
| 1639 |
#' [`StoppingMTDCV`], [`StoppingLowestDoseHSRBeta`], [`StoppingSpecificDose`]. |
|
| 1640 |
#' |
|
| 1641 |
#' @aliases Stopping |
|
| 1642 |
#' @export |
|
| 1643 |
#' |
|
| 1644 |
setClass( |
|
| 1645 |
Class = "Stopping", |
|
| 1646 |
contains = "CrmPackClass", |
|
| 1647 |
slots = c(report_label = "character"), |
|
| 1648 |
prototype = prototype(report_label = character(0)) |
|
| 1649 |
) |
|
| 1650 | ||
| 1651 | ||
| 1652 |
## default constructor ---- |
|
| 1653 | ||
| 1654 |
#' @rdname CohortSize-class |
|
| 1655 |
#' @note Typically, end users will not use the `DefaultCohortSize()` function. |
|
| 1656 |
#' @export |
|
| 1657 |
.DefaultCohortSize <- function() {
|
|
| 1658 |
stop(paste0( |
|
| 1659 |
"Class CohortSize should not be instantiated directly. Please use one of its subclasses instead." |
|
| 1660 |
)) |
|
| 1661 |
} |
|
| 1662 | ||
| 1663 |
# StoppingMissingDose ---- |
|
| 1664 | ||
| 1665 |
## class ---- |
|
| 1666 | ||
| 1667 |
#' `StoppingMissingDose` |
|
| 1668 |
#' |
|
| 1669 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 1670 |
#' |
|
| 1671 |
#' [`StoppingMissingDose`] is the class for stopping based on NA returned by |
|
| 1672 |
#' next best dose. |
|
| 1673 |
#' |
|
| 1674 |
#' @aliases StoppingMissingDose |
|
| 1675 |
#' @export |
|
| 1676 |
#' |
|
| 1677 |
.StoppingMissingDose <- setClass( |
|
| 1678 |
Class = "StoppingMissingDose", |
|
| 1679 |
contains = "Stopping" |
|
| 1680 |
) |
|
| 1681 | ||
| 1682 |
## constructor ---- |
|
| 1683 | ||
| 1684 |
#' @rdname StoppingMissingDose-class |
|
| 1685 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 1686 |
#' @example examples/Rules-class-StoppingMissingDose.R |
|
| 1687 |
#' @export |
|
| 1688 |
#' |
|
| 1689 |
StoppingMissingDose <- function(report_label = NA_character_) {
|
|
| 1690 | 28x |
report_label <- h_default_if_empty( |
| 1691 | 28x |
as.character(report_label), |
| 1692 | 28x |
paste("Stopped because of missing dose")
|
| 1693 |
) |
|
| 1694 | ||
| 1695 | 28x |
.StoppingMissingDose(report_label = report_label) |
| 1696 |
} |
|
| 1697 | ||
| 1698 |
## default constructor ---- |
|
| 1699 | ||
| 1700 |
#' @rdname StoppingMissingDose-class |
|
| 1701 |
#' @note Typically, end users will not use the `.DefaultStoppingMissingDose()` function. |
|
| 1702 |
#' @export |
|
| 1703 |
#' |
|
| 1704 |
.DefaultStoppingMissingDose <- function() {
|
|
| 1705 | 7x |
StoppingMissingDose() |
| 1706 |
} |
|
| 1707 | ||
| 1708 |
# StoppingCohortsNearDose ---- |
|
| 1709 | ||
| 1710 |
## class ---- |
|
| 1711 | ||
| 1712 |
#' `StoppingCohortsNearDose` |
|
| 1713 |
#' |
|
| 1714 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1715 |
#' |
|
| 1716 |
#' [`StoppingCohortsNearDose`] is the class for stopping based on number of |
|
| 1717 |
#' cohorts near to next best dose. |
|
| 1718 |
#' |
|
| 1719 |
#' |
|
| 1720 |
#' @slot nCohorts (`number`)\cr number of required cohorts. |
|
| 1721 |
#' @slot percentage (`number`)\cr percentage (between and including 0 and 100) |
|
| 1722 |
#' within the next best dose the cohorts must lie. |
|
| 1723 |
#' |
|
| 1724 |
#' @aliases StoppingCohortsNearDose |
|
| 1725 |
#' @export |
|
| 1726 |
#' |
|
| 1727 |
.StoppingCohortsNearDose <- setClass( |
|
| 1728 |
Class = "StoppingCohortsNearDose", |
|
| 1729 |
slots = c( |
|
| 1730 |
nCohorts = "integer", |
|
| 1731 |
percentage = "numeric" |
|
| 1732 |
), |
|
| 1733 |
prototype = prototype( |
|
| 1734 |
nCohorts = 2L, |
|
| 1735 |
percentage = 50 |
|
| 1736 |
), |
|
| 1737 |
contains = "Stopping", |
|
| 1738 |
validity = v_stopping_cohorts_near_dose |
|
| 1739 |
) |
|
| 1740 | ||
| 1741 |
## constructor ---- |
|
| 1742 | ||
| 1743 |
#' @rdname StoppingCohortsNearDose-class |
|
| 1744 |
#' |
|
| 1745 |
#' @param nCohorts (`number`)\cr see slot definition. |
|
| 1746 |
#' @param percentage (`number`)\cr see slot definition. |
|
| 1747 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 1748 |
#' |
|
| 1749 |
#' @example examples/Rules-class-StoppingCohortsNearDose.R |
|
| 1750 |
#' @export |
|
| 1751 |
#' |
|
| 1752 |
StoppingCohortsNearDose <- function( |
|
| 1753 |
nCohorts = 2L, |
|
| 1754 |
percentage = 50, |
|
| 1755 |
report_label = NA_character_ |
|
| 1756 |
) {
|
|
| 1757 | 22x |
assert_count(nCohorts, positive = TRUE) |
| 1758 | 22x |
assert_numeric(percentage, lower = 0) |
| 1759 | ||
| 1760 | 22x |
report_label <- h_default_if_empty( |
| 1761 | 22x |
as.character(report_label), |
| 1762 | 22x |
paste( |
| 1763 | 22x |
"\u2265", |
| 1764 | 22x |
nCohorts, |
| 1765 | 22x |
"cohorts dosed in", |
| 1766 | 22x |
percentage, |
| 1767 | 22x |
"% dose range around NBD" |
| 1768 |
) |
|
| 1769 |
) |
|
| 1770 | ||
| 1771 | 22x |
.StoppingCohortsNearDose( |
| 1772 | 22x |
nCohorts = as.integer(nCohorts), |
| 1773 | 22x |
percentage = percentage, |
| 1774 | 22x |
report_label = report_label |
| 1775 |
) |
|
| 1776 |
} |
|
| 1777 | ||
| 1778 |
## default constructor ---- |
|
| 1779 | ||
| 1780 |
#' @rdname StoppingCohortsNearDose-class |
|
| 1781 |
#' @note Typically, end users will not use the `.DefaultStoppingCohortsNearDose()` function. |
|
| 1782 |
#' @export |
|
| 1783 |
.DefaultStoppingCohortsNearDose <- function() {
|
|
| 1784 |
# nolint |
|
| 1785 | 7x |
StoppingCohortsNearDose( |
| 1786 | 7x |
nCohorts = 3L, |
| 1787 | 7x |
percentage = 0.2 |
| 1788 |
) |
|
| 1789 |
} |
|
| 1790 | ||
| 1791 | ||
| 1792 |
# StoppingPatientsNearDose ---- |
|
| 1793 | ||
| 1794 |
## class ---- |
|
| 1795 | ||
| 1796 |
#' `StoppingPatientsNearDose` |
|
| 1797 |
#' |
|
| 1798 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1799 |
#' |
|
| 1800 |
#' [`StoppingPatientsNearDose`] is the class for stopping based on number of |
|
| 1801 |
#' patients near to next best dose. |
|
| 1802 |
#' |
|
| 1803 |
#' @slot nPatients (`number`)\cr number of required patients. |
|
| 1804 |
#' @slot percentage (`number`)\cr percentage (between and including 0 and 100) |
|
| 1805 |
#' within the next best dose the patients must lie. |
|
| 1806 |
#' |
|
| 1807 |
#' @aliases StoppingPatientsNearDose |
|
| 1808 |
#' @export |
|
| 1809 |
#' |
|
| 1810 |
.StoppingPatientsNearDose <- setClass( |
|
| 1811 |
Class = "StoppingPatientsNearDose", |
|
| 1812 |
slots = c( |
|
| 1813 |
nPatients = "integer", |
|
| 1814 |
percentage = "numeric" |
|
| 1815 |
), |
|
| 1816 |
prototype = prototype( |
|
| 1817 |
nPatients = 10L, |
|
| 1818 |
percentage = 50 |
|
| 1819 |
), |
|
| 1820 |
contains = "Stopping", |
|
| 1821 |
validity = v_stopping_patients_near_dose |
|
| 1822 |
) |
|
| 1823 | ||
| 1824 |
## constructor ---- |
|
| 1825 | ||
| 1826 |
#' @rdname StoppingPatientsNearDose-class |
|
| 1827 |
#' |
|
| 1828 |
#' @param nPatients (`number`)\cr see slot definition. |
|
| 1829 |
#' @param percentage (`number`)\cr see slot definition. |
|
| 1830 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 1831 |
#' |
|
| 1832 |
#' @example examples/Rules-class-StoppingPatientsNearDose.R |
|
| 1833 |
#' @export |
|
| 1834 |
#' |
|
| 1835 |
StoppingPatientsNearDose <- function( |
|
| 1836 |
nPatients = 10L, |
|
| 1837 |
percentage = 50, |
|
| 1838 |
report_label = NA_character_ |
|
| 1839 |
) {
|
|
| 1840 | 23x |
assert_count(nPatients, positive = TRUE) |
| 1841 | 23x |
assert_number(percentage, lower = 0, upper = 100) |
| 1842 | ||
| 1843 | 23x |
report_label <- h_default_if_empty( |
| 1844 | 23x |
as.character(report_label), |
| 1845 | 23x |
paste( |
| 1846 | 23x |
"\u2265", |
| 1847 | 23x |
nPatients, |
| 1848 | 23x |
"patients dosed in", |
| 1849 | 23x |
percentage, |
| 1850 | 23x |
"% dose range around NBD" |
| 1851 |
) |
|
| 1852 |
) |
|
| 1853 | ||
| 1854 | 23x |
.StoppingPatientsNearDose( |
| 1855 | 23x |
nPatients = as.integer(nPatients), |
| 1856 | 23x |
percentage = percentage, |
| 1857 | 23x |
report_label = report_label |
| 1858 |
) |
|
| 1859 |
} |
|
| 1860 | ||
| 1861 |
## default constructor ---- |
|
| 1862 | ||
| 1863 |
#' @rdname StoppingPatientsNearDose-class |
|
| 1864 |
#' @note Typically, end users will not use the `.DefaultStoppingPatientsNearDose()` function. |
|
| 1865 |
#' @export |
|
| 1866 |
.DefaultStoppingPatientsNearDose <- function() {
|
|
| 1867 |
# nolint |
|
| 1868 | 7x |
StoppingPatientsNearDose( |
| 1869 | 7x |
nPatients = 9L, |
| 1870 | 7x |
percentage = 20, |
| 1871 | 7x |
report_label = NA_character_ |
| 1872 |
) |
|
| 1873 |
} |
|
| 1874 | ||
| 1875 |
# StoppingMinCohorts ---- |
|
| 1876 | ||
| 1877 |
## class ---- |
|
| 1878 | ||
| 1879 |
#' `StoppingMinCohorts` |
|
| 1880 |
#' |
|
| 1881 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1882 |
#' |
|
| 1883 |
#' [`StoppingMinCohorts`] is the class for stopping based on minimum number of |
|
| 1884 |
#' cohorts. |
|
| 1885 |
#' |
|
| 1886 |
#' @slot nCohorts (`number`)\cr minimum required number of cohorts. |
|
| 1887 |
#' |
|
| 1888 |
#' @aliases StoppingMinCohorts |
|
| 1889 |
#' @export |
|
| 1890 |
#' |
|
| 1891 |
.StoppingMinCohorts <- setClass( |
|
| 1892 |
Class = "StoppingMinCohorts", |
|
| 1893 |
slots = c(nCohorts = "integer"), |
|
| 1894 |
prototype = prototype(nCohorts = 2L), |
|
| 1895 |
contains = "Stopping", |
|
| 1896 |
validity = v_stopping_min_cohorts |
|
| 1897 |
) |
|
| 1898 | ||
| 1899 |
## constructor ---- |
|
| 1900 | ||
| 1901 |
#' @rdname StoppingMinCohorts-class |
|
| 1902 |
#' |
|
| 1903 |
#' @param nCohorts (`number`)\cr see slot definition. |
|
| 1904 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 1905 |
#' |
|
| 1906 |
#' @example examples/Rules-class-StoppingMinCohorts.R |
|
| 1907 |
#' @export |
|
| 1908 |
#' |
|
| 1909 |
StoppingMinCohorts <- function(nCohorts = 2L, report_label = NA_character_) {
|
|
| 1910 | 86x |
assert_count(nCohorts, positive = TRUE) |
| 1911 | ||
| 1912 | 86x |
report_label <- h_default_if_empty( |
| 1913 | 86x |
as.character(report_label), |
| 1914 | 86x |
paste("\u2265", nCohorts, "cohorts dosed")
|
| 1915 |
) |
|
| 1916 | ||
| 1917 | 86x |
.StoppingMinCohorts( |
| 1918 | 86x |
nCohorts = as.integer(nCohorts), |
| 1919 | 86x |
report_label = report_label |
| 1920 |
) |
|
| 1921 |
} |
|
| 1922 | ||
| 1923 |
## default constructor ---- |
|
| 1924 | ||
| 1925 |
#' @rdname StoppingMinCohorts-class |
|
| 1926 |
#' @note Typically, end users will not use the `.DefaultStoppingMinCohorts()` function. |
|
| 1927 |
#' @export |
|
| 1928 |
.DefaultStoppingMinCohorts <- function() {
|
|
| 1929 | 7x |
StoppingMinCohorts( |
| 1930 | 7x |
nCohorts = 6L |
| 1931 |
) |
|
| 1932 |
} |
|
| 1933 | ||
| 1934 |
# StoppingMinPatients ---- |
|
| 1935 | ||
| 1936 |
## class ---- |
|
| 1937 | ||
| 1938 |
#' `StoppingMinPatients` |
|
| 1939 |
#' |
|
| 1940 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1941 |
#' |
|
| 1942 |
#' [`StoppingMinPatients`] is the class for stopping based on minimum number of |
|
| 1943 |
#' patients |
|
| 1944 |
#' |
|
| 1945 |
#' @slot nPatients (`number`)\cr minimum allowed number of patients. |
|
| 1946 |
#' |
|
| 1947 |
#' @aliases StoppingMinPatients |
|
| 1948 |
#' @export |
|
| 1949 |
#' |
|
| 1950 |
.StoppingMinPatients <- setClass( |
|
| 1951 |
Class = "StoppingMinPatients", |
|
| 1952 |
slots = c(nPatients = "integer"), |
|
| 1953 |
prototype = prototype(nPatients = 20L), |
|
| 1954 |
contains = "Stopping", |
|
| 1955 |
validity = v_stopping_min_patients |
|
| 1956 |
) |
|
| 1957 | ||
| 1958 |
## constructor ---- |
|
| 1959 | ||
| 1960 |
#' @rdname StoppingMinPatients-class |
|
| 1961 |
#' |
|
| 1962 |
#' @param nPatients (`number`)\cr see slot definition. |
|
| 1963 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 1964 |
#' |
|
| 1965 |
#' @example examples/Rules-class-StoppingMinPatients.R |
|
| 1966 |
#' @export |
|
| 1967 |
#' |
|
| 1968 |
StoppingMinPatients <- function(nPatients = 20L, report_label = NA_character_) {
|
|
| 1969 | 157x |
assert_count(nPatients, positive = TRUE) |
| 1970 | ||
| 1971 | 157x |
report_label <- h_default_if_empty( |
| 1972 | 157x |
as.character(report_label), |
| 1973 | 157x |
paste("\u2265", nPatients, "patients dosed")
|
| 1974 |
) |
|
| 1975 | ||
| 1976 | 157x |
.StoppingMinPatients( |
| 1977 | 157x |
nPatients = as.integer(nPatients), |
| 1978 | 157x |
report_label = report_label |
| 1979 |
) |
|
| 1980 |
} |
|
| 1981 | ||
| 1982 |
## default constructor ---- |
|
| 1983 | ||
| 1984 |
#' @rdname StoppingMinPatients-class |
|
| 1985 |
#' @note Typically, end users will not use the `.DefaultStoppingMinPatients()` function. |
|
| 1986 |
#' @export |
|
| 1987 |
.DefaultStoppingMinPatients <- function() {
|
|
| 1988 | 7x |
StoppingMinPatients( |
| 1989 | 7x |
nPatients = 20L |
| 1990 |
) |
|
| 1991 |
} |
|
| 1992 | ||
| 1993 |
# StoppingTargetProb ---- |
|
| 1994 | ||
| 1995 |
## class ---- |
|
| 1996 | ||
| 1997 |
#' `StoppingTargetProb` |
|
| 1998 |
#' |
|
| 1999 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2000 |
#' |
|
| 2001 |
#' [`StoppingTargetProb`] is the class for stopping based on the probability of |
|
| 2002 |
#' the DLT rate being in the target toxicity interval. |
|
| 2003 |
#' |
|
| 2004 |
#' @slot target (`number`)\cr the target toxicity interval, e.g. `c(0.2, 0.35)`. |
|
| 2005 |
#' @slot prob (`proportion`)\cr required target toxicity probability (except 0 or 1) |
|
| 2006 |
#' for reaching sufficient precision. |
|
| 2007 |
#' |
|
| 2008 |
#' @aliases StoppingTargetProb |
|
| 2009 |
#' @export |
|
| 2010 |
#' |
|
| 2011 |
.StoppingTargetProb <- setClass( |
|
| 2012 |
Class = "StoppingTargetProb", |
|
| 2013 |
slots = c( |
|
| 2014 |
target = "numeric", |
|
| 2015 |
prob = "numeric" |
|
| 2016 |
), |
|
| 2017 |
prototype = prototype( |
|
| 2018 |
target = c(0.2, 0.35), |
|
| 2019 |
prob = 0.4 |
|
| 2020 |
), |
|
| 2021 |
contains = "Stopping", |
|
| 2022 |
validity = v_stopping_target_prob |
|
| 2023 |
) |
|
| 2024 | ||
| 2025 |
## constructor ---- |
|
| 2026 | ||
| 2027 |
#' @rdname StoppingTargetProb-class |
|
| 2028 |
#' |
|
| 2029 |
#' @param target (`number`)\cr see slot definition. |
|
| 2030 |
#' @param prob (`proportion`)\cr see slot definition. |
|
| 2031 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2032 |
#' |
|
| 2033 |
#' @example examples/Rules-class-StoppingTargetProb.R |
|
| 2034 |
#' @export |
|
| 2035 |
#' |
|
| 2036 |
StoppingTargetProb <- function( |
|
| 2037 |
target = c(0.2, 0.35), |
|
| 2038 |
prob = 0.4, |
|
| 2039 |
report_label = NA_character_ |
|
| 2040 |
) {
|
|
| 2041 | 122x |
assert_numeric(target, len = 2) |
| 2042 | 122x |
report_label <- h_default_if_empty( |
| 2043 | 122x |
as.character(report_label), |
| 2044 | 122x |
paste0( |
| 2045 | 122x |
"P(",
|
| 2046 | 122x |
target[1], |
| 2047 | 122x |
" \u2264 prob(DLE | NBD) \u2264 ", |
| 2048 | 122x |
target[2], |
| 2049 | 122x |
") \u2265 ", |
| 2050 | 122x |
prob |
| 2051 |
) |
|
| 2052 |
) |
|
| 2053 | ||
| 2054 | 122x |
.StoppingTargetProb( |
| 2055 | 122x |
target = target, |
| 2056 | 122x |
prob = prob, |
| 2057 | 122x |
report_label = report_label |
| 2058 |
) |
|
| 2059 |
} |
|
| 2060 | ||
| 2061 |
## default constructor ---- |
|
| 2062 | ||
| 2063 |
#' @rdname StoppingTargetProb-class |
|
| 2064 |
#' @note Typically, end users will not use the `.DefaultStoppingTargetProb()` function. |
|
| 2065 |
#' @export |
|
| 2066 |
.DefaultStoppingTargetProb <- function() {
|
|
| 2067 | 7x |
StoppingTargetProb( |
| 2068 | 7x |
target = c(0.2, 0.35), |
| 2069 | 7x |
prob = 0.5 |
| 2070 |
) |
|
| 2071 |
} |
|
| 2072 | ||
| 2073 |
# StoppingMTDdistribution ---- |
|
| 2074 | ||
| 2075 |
## class ---- |
|
| 2076 | ||
| 2077 |
#' `StoppingMTDdistribution` |
|
| 2078 |
#' |
|
| 2079 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2080 |
#' |
|
| 2081 |
#' [`StoppingMTDdistribution`] is the class for stopping based on the posterior |
|
| 2082 |
#' distribution of the MTD. It is used for the cases where the stopping occurs |
|
| 2083 |
#' when the probability of `MTD > thresh * next_dose` is greater than or equal |
|
| 2084 |
#' to `prob`, where the `next_dose` is the recommended next best dose. |
|
| 2085 |
#' Here, the MTD is defined as the dose that reaches a specific `target` |
|
| 2086 |
#' probability of the occurrence of a DLT. |
|
| 2087 |
#' |
|
| 2088 |
#' @slot target (`proportion`)\cr the target toxicity probability (except 0 or 1) |
|
| 2089 |
#' defining the MTD. |
|
| 2090 |
#' @slot thresh (`proportion`)\cr the threshold (except 0 or 1) relative to the |
|
| 2091 |
#' recommended next best dose. |
|
| 2092 |
#' @slot prob (`proportion`)\cr required minimum probability, except 0 or 1. |
|
| 2093 |
#' |
|
| 2094 |
#' @aliases StoppingMTDdistribution |
|
| 2095 |
#' @export |
|
| 2096 |
#' |
|
| 2097 |
.StoppingMTDdistribution <- setClass( |
|
| 2098 |
Class = "StoppingMTDdistribution", |
|
| 2099 |
slots = c( |
|
| 2100 |
target = "numeric", |
|
| 2101 |
thresh = "numeric", |
|
| 2102 |
prob = "numeric" |
|
| 2103 |
), |
|
| 2104 |
prototype = prototype( |
|
| 2105 |
target = 0.33, |
|
| 2106 |
thresh = 0.5, |
|
| 2107 |
prob = 0.9 |
|
| 2108 |
), |
|
| 2109 |
contains = "Stopping", |
|
| 2110 |
validity = v_stopping_mtd_distribution |
|
| 2111 |
) |
|
| 2112 | ||
| 2113 |
## constructor ---- |
|
| 2114 | ||
| 2115 |
#' @rdname StoppingMTDdistribution-class |
|
| 2116 |
#' |
|
| 2117 |
#' @param target (`proportion`)\cr see slot definition. |
|
| 2118 |
#' @param thresh (`proportion`)\cr see slot definition. |
|
| 2119 |
#' @param prob (`proportion`)\cr see slot definition. |
|
| 2120 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2121 |
#' |
|
| 2122 |
#' @example examples/Rules-class-StoppingMTDdistribution.R |
|
| 2123 |
#' @export |
|
| 2124 |
#' |
|
| 2125 |
StoppingMTDdistribution <- function( |
|
| 2126 |
target = 0.33, |
|
| 2127 |
thresh = 0.5, |
|
| 2128 |
prob = 0.9, |
|
| 2129 |
report_label = NA_character_ |
|
| 2130 |
) {
|
|
| 2131 | 768x |
report_label <- h_default_if_empty( |
| 2132 | 768x |
as.character(report_label), |
| 2133 | 768x |
paste0("P(MTD > ", thresh, " * NBD | P(DLE) = ", target, ") \u2265 ", prob)
|
| 2134 |
) |
|
| 2135 | ||
| 2136 | 768x |
.StoppingMTDdistribution( |
| 2137 | 768x |
target = target, |
| 2138 | 768x |
thresh = thresh, |
| 2139 | 768x |
prob = prob, |
| 2140 | 768x |
report_label = report_label |
| 2141 |
) |
|
| 2142 |
} |
|
| 2143 | ||
| 2144 |
## default constructor ---- |
|
| 2145 | ||
| 2146 |
#' @rdname StoppingMTDdistribution-class |
|
| 2147 |
#' @note Typically, end users will not use the `.DefaultStoppingMTDDistribution()` function. |
|
| 2148 |
#' @export |
|
| 2149 |
.DefaultStoppingMTDdistribution <- function() {
|
|
| 2150 | 7x |
StoppingMTDdistribution( |
| 2151 | 7x |
target = 0.33, |
| 2152 | 7x |
thresh = 0.5, |
| 2153 | 7x |
prob = 0.9 |
| 2154 |
) |
|
| 2155 |
} |
|
| 2156 | ||
| 2157 |
# StoppingMTDCV ---- |
|
| 2158 | ||
| 2159 |
## class ---- |
|
| 2160 | ||
| 2161 |
#' `StoppingMTDCV` |
|
| 2162 |
#' |
|
| 2163 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 2164 |
#' |
|
| 2165 |
#' [`StoppingMTDCV`] is a class for stopping rule based on precision of MTD |
|
| 2166 |
#' which is calculated as the coefficient of variation (CV) of the MTD. |
|
| 2167 |
#' Here, the MTD is defined as the dose that reaches a specific `target` |
|
| 2168 |
#' probability of the occurrence of a DLT. |
|
| 2169 |
#' |
|
| 2170 |
#' @slot target (`proportion`)\cr toxicity target of MTD (except 0 or 1). |
|
| 2171 |
#' @slot thresh_cv (`number`)\cr threshold (percentage > 0) for CV to be |
|
| 2172 |
#' considered accurate enough to stop the trial. The stopping occurs when the |
|
| 2173 |
#' CV is less than or equal to `tresh_cv`. |
|
| 2174 |
#' |
|
| 2175 |
#' @aliases StoppingMTDCV |
|
| 2176 |
#' @export |
|
| 2177 |
#' |
|
| 2178 |
.StoppingMTDCV <- setClass( |
|
| 2179 |
Class = "StoppingMTDCV", |
|
| 2180 |
slots = c( |
|
| 2181 |
target = "numeric", |
|
| 2182 |
thresh_cv = "numeric" |
|
| 2183 |
), |
|
| 2184 |
prototype = prototype( |
|
| 2185 |
target = 0.3, |
|
| 2186 |
thresh_cv = 40 |
|
| 2187 |
), |
|
| 2188 |
contains = "Stopping", |
|
| 2189 |
validity = v_stopping_mtd_cv |
|
| 2190 |
) |
|
| 2191 | ||
| 2192 |
## constructor ---- |
|
| 2193 | ||
| 2194 |
#' @rdname StoppingMTDCV-class |
|
| 2195 |
#' |
|
| 2196 |
#' @param target (`proportion`)\cr see slot definition. |
|
| 2197 |
#' @param thresh_cv (`number`)\cr see slot definition. |
|
| 2198 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2199 |
#' |
|
| 2200 |
#' @export |
|
| 2201 |
#' @example examples/Rules-class-StoppingMTDCV.R |
|
| 2202 |
#' |
|
| 2203 |
StoppingMTDCV <- function( |
|
| 2204 |
target = 0.3, |
|
| 2205 |
thresh_cv = 40, |
|
| 2206 |
report_label = NA_character_ |
|
| 2207 |
) {
|
|
| 2208 | 20x |
report_label <- h_default_if_empty( |
| 2209 | 20x |
as.character(report_label), |
| 2210 | 20x |
paste("CV(MTD) >", target)
|
| 2211 |
) |
|
| 2212 | ||
| 2213 | 20x |
.StoppingMTDCV( |
| 2214 | 20x |
target = target, |
| 2215 | 20x |
thresh_cv = thresh_cv, |
| 2216 | 20x |
report_label = report_label |
| 2217 |
) |
|
| 2218 |
} |
|
| 2219 | ||
| 2220 |
## default constructor ---- |
|
| 2221 | ||
| 2222 |
#' @rdname StoppingMTDCV-class |
|
| 2223 |
#' @note Typically, end users will not use the `.DefaultStoppingMTDCV()` function. |
|
| 2224 |
#' |
|
| 2225 |
#' @export |
|
| 2226 |
.DefaultStoppingMTDCV <- function() {
|
|
| 2227 | 7x |
StoppingMTDCV( |
| 2228 | 7x |
target = 0.3, |
| 2229 | 7x |
thresh_cv = 40 |
| 2230 |
) |
|
| 2231 |
} |
|
| 2232 | ||
| 2233 |
# StoppingLowestDoseHSRBeta ---- |
|
| 2234 | ||
| 2235 |
## class ---- |
|
| 2236 | ||
| 2237 |
#' `StoppingLowestDoseHSRBeta` |
|
| 2238 |
#' |
|
| 2239 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 2240 |
#' |
|
| 2241 |
#' [`StoppingLowestDoseHSRBeta`] is a class for stopping based on a Hard Safety |
|
| 2242 |
#' Rule using the Beta posterior distribution with Beta(a,b) prior and a |
|
| 2243 |
#' Bin-Beta model based on the observed data at the lowest dose level. |
|
| 2244 |
#' The rule is triggered when the first dose is considered to be toxic |
|
| 2245 |
#' (i.e. above threshold probability) based on the observed data at the |
|
| 2246 |
#' lowest dose level and a Beta(a,b) prior distribution. |
|
| 2247 |
#' The default prior is Beta(1,1). |
|
| 2248 |
#' In case that placebo is used, the rule is evaluated at the second dose of the |
|
| 2249 |
#' dose grid, i.e. at the lowest non-placebo dose. |
|
| 2250 |
#' |
|
| 2251 |
#' @note This stopping rule is independent from the underlying model. |
|
| 2252 |
#' |
|
| 2253 |
#' @slot target (`proportion`)\cr the target toxicity. |
|
| 2254 |
#' @slot prob (`proportion`)\cr the threshold probability for the lowest dose |
|
| 2255 |
#' being toxic. |
|
| 2256 |
#' @slot a (`number`)\cr shape parameter \eqn{a > 0} of probability distribution
|
|
| 2257 |
#' Beta (a,b). |
|
| 2258 |
#' @slot b (`number`)\cr shape parameter \eqn{b > 0} of probability distribution
|
|
| 2259 |
#' Beta (a,b). |
|
| 2260 |
#' |
|
| 2261 |
#' @aliases StoppingLowestDoseHSRBeta |
|
| 2262 |
#' @export |
|
| 2263 |
#' |
|
| 2264 |
.StoppingLowestDoseHSRBeta <- setClass( |
|
| 2265 |
Class = "StoppingLowestDoseHSRBeta", |
|
| 2266 |
slots = c( |
|
| 2267 |
target = "numeric", |
|
| 2268 |
prob = "numeric", |
|
| 2269 |
a = "numeric", |
|
| 2270 |
b = "numeric" |
|
| 2271 |
), |
|
| 2272 |
prototype = prototype( |
|
| 2273 |
target = 0.3, |
|
| 2274 |
prob = 0.95, |
|
| 2275 |
a = 1, |
|
| 2276 |
b = 1 |
|
| 2277 |
), |
|
| 2278 |
contains = "Stopping", |
|
| 2279 |
validity = v_increments_hsr_beta |
|
| 2280 |
) |
|
| 2281 | ||
| 2282 |
## constructor ---- |
|
| 2283 | ||
| 2284 |
#' @rdname StoppingLowestDoseHSRBeta-class |
|
| 2285 |
#' |
|
| 2286 |
#' @param target (`proportion`)\cr see slot definition. |
|
| 2287 |
#' @param prob (`proportion`)\cr see slot definition. |
|
| 2288 |
#' @param a (`number`)\cr see slot definition. |
|
| 2289 |
#' @param b (`number`)\cr see slot definition. |
|
| 2290 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2291 |
#' |
|
| 2292 |
#' @export |
|
| 2293 |
#' @example examples/Rules-class-StoppingLowestDoseHSRBeta.R |
|
| 2294 |
#' |
|
| 2295 |
StoppingLowestDoseHSRBeta <- function( |
|
| 2296 |
target = 0.3, |
|
| 2297 |
prob = 0.95, |
|
| 2298 |
a = 1, |
|
| 2299 |
b = 1, |
|
| 2300 |
report_label = NA_character_ |
|
| 2301 |
) {
|
|
| 2302 | 20x |
report_label <- h_default_if_empty( |
| 2303 | 20x |
as.character(report_label), |
| 2304 | 20x |
paste0("P\u03B2(lowest dose > P(DLE) = ", target, ") > ", prob)
|
| 2305 |
) |
|
| 2306 | ||
| 2307 | 20x |
.StoppingLowestDoseHSRBeta( |
| 2308 | 20x |
target = target, |
| 2309 | 20x |
prob = prob, |
| 2310 | 20x |
a = a, |
| 2311 | 20x |
b = b, |
| 2312 | 20x |
report_label = report_label |
| 2313 |
) |
|
| 2314 |
} |
|
| 2315 | ||
| 2316 |
## default constructor ---- |
|
| 2317 | ||
| 2318 |
#' @rdname StoppingLowestDoseHSRBeta-class |
|
| 2319 |
#' @note Typically, end users will not use the `.DefaultStoppingLowestDoseHSRBeta()` function. |
|
| 2320 |
#' @export |
|
| 2321 |
.DefaultStoppingLowestDoseHSRBeta <- function() {
|
|
| 2322 |
# nolint |
|
| 2323 | 7x |
StoppingLowestDoseHSRBeta( |
| 2324 | 7x |
target = 0.3, |
| 2325 | 7x |
prob = 0.95, |
| 2326 | 7x |
a = 1, |
| 2327 | 7x |
b = 1 |
| 2328 |
) |
|
| 2329 |
} |
|
| 2330 | ||
| 2331 |
# StoppingTargetBiomarker ---- |
|
| 2332 | ||
| 2333 |
## class ---- |
|
| 2334 | ||
| 2335 |
#' `StoppingTargetBiomarker` |
|
| 2336 |
#' |
|
| 2337 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2338 |
#' |
|
| 2339 |
#' [`StoppingTargetBiomarker`] is a class for stopping based on probability of |
|
| 2340 |
#' target biomarker. |
|
| 2341 |
#' |
|
| 2342 |
#' @slot target (`numeric`)\cr the biomarker target range that needs to be |
|
| 2343 |
#' reached. For example, `target = c(0.8, 1.0)` with `is_relative = TRUE` |
|
| 2344 |
#' means that we target a dose with at least 80% of maximum biomarker level. |
|
| 2345 |
#' @slot is_relative (`flag`)\cr is target relative? If it so (default), then |
|
| 2346 |
#' the `target` is interpreted relative to the maximum, so it must be a |
|
| 2347 |
#' probability range. Otherwise, the `target` is interpreted as absolute |
|
| 2348 |
#' biomarker range. |
|
| 2349 |
#' @slot prob (`proportion`)\cr required target probability (except 0 or 1) for |
|
| 2350 |
#' reaching sufficient precision. |
|
| 2351 |
#' |
|
| 2352 |
#' @aliases StoppingTargetBiomarker |
|
| 2353 |
#' @export |
|
| 2354 |
#' |
|
| 2355 |
.StoppingTargetBiomarker <- setClass( |
|
| 2356 |
Class = "StoppingTargetBiomarker", |
|
| 2357 |
slots = c( |
|
| 2358 |
target = "numeric", |
|
| 2359 |
is_relative = "logical", |
|
| 2360 |
prob = "numeric" |
|
| 2361 |
), |
|
| 2362 |
prototype = prototype( |
|
| 2363 |
target = c(0.9, 1), |
|
| 2364 |
is_relative = TRUE, |
|
| 2365 |
prob = 0.3 |
|
| 2366 |
), |
|
| 2367 |
contains = "Stopping", |
|
| 2368 |
validity = v_stopping_target_biomarker |
|
| 2369 |
) |
|
| 2370 | ||
| 2371 |
## constructor ---- |
|
| 2372 | ||
| 2373 |
#' @rdname StoppingTargetBiomarker-class |
|
| 2374 |
#' |
|
| 2375 |
#' @param target (`numeric`)\cr see slot definition. |
|
| 2376 |
#' @param prob (`proportion`)\cr see slot definition. |
|
| 2377 |
#' @param is_relative (`flag`)\cr see slot definition. |
|
| 2378 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2379 |
#' |
|
| 2380 |
#' @export |
|
| 2381 |
#' @example examples/Rules-class-StoppingTargetBiomarker.R |
|
| 2382 |
#' |
|
| 2383 |
StoppingTargetBiomarker <- function( |
|
| 2384 |
target = c(0.9, 1), |
|
| 2385 |
prob = 0.3, |
|
| 2386 |
is_relative = TRUE, |
|
| 2387 |
report_label = NA_character_ |
|
| 2388 |
) {
|
|
| 2389 | 36x |
assert_numeric(target, len = 2) |
| 2390 | 36x |
assert_flag(is_relative) |
| 2391 | ||
| 2392 | 36x |
report_label <- h_default_if_empty( |
| 2393 | 36x |
as.character(report_label), |
| 2394 | 36x |
paste0( |
| 2395 | 36x |
"P(",
|
| 2396 | 36x |
target[1], |
| 2397 | 36x |
" \u2264 ", |
| 2398 | 36x |
"Biomarker \u2264 ", |
| 2399 | 36x |
target[2], |
| 2400 | 36x |
") \u2265 ", |
| 2401 | 36x |
prob, |
| 2402 | 36x |
ifelse(is_relative, " (relative)", " (absolute)") |
| 2403 |
) |
|
| 2404 |
) |
|
| 2405 | ||
| 2406 | 36x |
.StoppingTargetBiomarker( |
| 2407 | 36x |
target = target, |
| 2408 | 36x |
is_relative = is_relative, |
| 2409 | 36x |
prob = prob, |
| 2410 | 36x |
report_label = report_label |
| 2411 |
) |
|
| 2412 |
} |
|
| 2413 | ||
| 2414 |
## default constructor ---- |
|
| 2415 | ||
| 2416 |
#' @rdname StoppingTargetBiomarker-class |
|
| 2417 |
#' @note Typically, end users will not use the `.DefaultStoppingTargetBiomarker()` function. |
|
| 2418 |
#' @export |
|
| 2419 |
.DefaultStoppingTargetBiomarker <- function() {
|
|
| 2420 | 7x |
StoppingTargetBiomarker( |
| 2421 | 7x |
target = c(0.9, 1), |
| 2422 | 7x |
prob = 0.5, |
| 2423 | 7x |
is_relative = TRUE |
| 2424 |
) |
|
| 2425 |
} |
|
| 2426 | ||
| 2427 |
# StoppingSpecificDose ---- |
|
| 2428 | ||
| 2429 |
## class ---- |
|
| 2430 | ||
| 2431 |
#' `StoppingSpecificDose` |
|
| 2432 |
#' |
|
| 2433 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 2434 |
#' |
|
| 2435 |
#' [`StoppingSpecificDose`] is the class for testing a stopping rule at specific |
|
| 2436 |
#' dose of the dose grid and not at the next best dose. |
|
| 2437 |
#' |
|
| 2438 |
#' @slot rule (`Stopping`)\cr a stopping rule available in this package. |
|
| 2439 |
#' @slot dose (`positive_number`)\cr a dose that is defined as part of the dose |
|
| 2440 |
#' grid of the data. |
|
| 2441 |
#' |
|
| 2442 |
#' @aliases StoppingSpecificDose |
|
| 2443 |
#' @export |
|
| 2444 |
#' |
|
| 2445 |
.StoppingSpecificDose <- setClass( |
|
| 2446 |
Class = "StoppingSpecificDose", |
|
| 2447 |
slots = c( |
|
| 2448 |
rule = "Stopping", |
|
| 2449 |
dose = "positive_number" |
|
| 2450 |
), |
|
| 2451 |
contains = "Stopping" |
|
| 2452 |
) |
|
| 2453 | ||
| 2454 |
## constructor ---- |
|
| 2455 | ||
| 2456 |
#' @rdname StoppingSpecificDose-class |
|
| 2457 |
#' |
|
| 2458 |
#' @param rule (`Stopping`)\cr see slot definition. |
|
| 2459 |
#' @param dose (`number`)\cr see slot definition. |
|
| 2460 |
#' @param report_label (`string` or `NA`) \cr see slot definition. |
|
| 2461 |
#' |
|
| 2462 |
#' @export |
|
| 2463 |
#' @example examples/Rules-class-StoppingSpecificDose.R |
|
| 2464 |
#' |
|
| 2465 |
StoppingSpecificDose <- function( |
|
| 2466 |
rule = StoppingTargetProb(target = c(0, 0.3), prob = 0.8), |
|
| 2467 |
dose = 80, |
|
| 2468 |
report_label = NA_character_ |
|
| 2469 |
) {
|
|
| 2470 | 20x |
report_label <- h_default_if_empty( |
| 2471 | 20x |
as.character(report_label), |
| 2472 | 20x |
paste0("Dose ", dose, " used for testing a stopping rule")
|
| 2473 |
) |
|
| 2474 | ||
| 2475 | 20x |
.StoppingSpecificDose( |
| 2476 | 20x |
rule = rule, |
| 2477 | 20x |
dose = positive_number(dose), |
| 2478 | 20x |
report_label = report_label |
| 2479 |
) |
|
| 2480 |
} |
|
| 2481 | ||
| 2482 |
## default constructor ---- |
|
| 2483 | ||
| 2484 |
#' @rdname StoppingSpecificDose-class |
|
| 2485 |
#' @note Typically, end users will not use the `.DefaultStoppingSpecificDose()` function. |
|
| 2486 |
#' @export |
|
| 2487 |
.DefaultStoppingSpecificDose <- function() {
|
|
| 2488 | 7x |
StoppingSpecificDose( |
| 2489 | 7x |
rule = StoppingTargetProb(target = c(0, 0.3), prob = 0.8), |
| 2490 | 7x |
dose = positive_number(80) |
| 2491 |
) |
|
| 2492 |
} |
|
| 2493 | ||
| 2494 |
# StoppingHighestDose ---- |
|
| 2495 | ||
| 2496 |
## class ---- |
|
| 2497 | ||
| 2498 |
#' `StoppingHighestDose` |
|
| 2499 |
#' |
|
| 2500 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 2501 |
#' |
|
| 2502 |
#' [`StoppingHighestDose`] is the class for stopping based on the highest dose. |
|
| 2503 |
#' That is, the stopping occurs when the highest dose is reached. |
|
| 2504 |
#' |
|
| 2505 |
#' @aliases StoppingHighestDose |
|
| 2506 |
#' @export |
|
| 2507 |
#' |
|
| 2508 |
.StoppingHighestDose <- setClass( |
|
| 2509 |
Class = "StoppingHighestDose", |
|
| 2510 |
contains = "Stopping" |
|
| 2511 |
) |
|
| 2512 | ||
| 2513 |
## constructor ---- |
|
| 2514 | ||
| 2515 |
#' @rdname StoppingHighestDose-class |
|
| 2516 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2517 |
#' |
|
| 2518 |
#' @export |
|
| 2519 |
#' @example examples/Rules-class-StoppingHighestDose.R |
|
| 2520 |
#' |
|
| 2521 |
StoppingHighestDose <- function(report_label = NA_character_) {
|
|
| 2522 | 23x |
report_label <- h_default_if_empty( |
| 2523 | 23x |
as.character(report_label), |
| 2524 | 23x |
"NBD is the highest dose" |
| 2525 |
) |
|
| 2526 | ||
| 2527 | 23x |
.StoppingHighestDose(report_label = report_label) |
| 2528 |
} |
|
| 2529 | ||
| 2530 |
## default constructor ---- |
|
| 2531 | ||
| 2532 |
#' @rdname StoppingHighestDose-class |
|
| 2533 |
#' @note Typically, end users will not use the `.DefaultStoppingHighestDose()` function. |
|
| 2534 |
#' @export |
|
| 2535 |
.DefaultStoppingHighestDose <- function() {
|
|
| 2536 | 7x |
StoppingHighestDose() |
| 2537 |
} |
|
| 2538 | ||
| 2539 |
# StoppingTDCIRatio ---- |
|
| 2540 | ||
| 2541 |
## class ---- |
|
| 2542 | ||
| 2543 |
#' `StoppingTDCIRatio` |
|
| 2544 |
#' |
|
| 2545 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2546 |
#' |
|
| 2547 |
#' [`StoppingTDCIRatio`] is the class for testing a stopping rule that is based |
|
| 2548 |
#' on a target ratio of the 95% credibility interval. Specifically, this is the |
|
| 2549 |
#' ratio of the upper to the lower bound of the 95% credibility interval's |
|
| 2550 |
#' estimate of the target dose (i.e. a dose that corresponds to a given target |
|
| 2551 |
#' probability of the occurrence of a DLT `prob_target`). |
|
| 2552 |
#' |
|
| 2553 |
#' @slot target_ratio (`numeric`)\cr target for the ratio of the 95% credibility |
|
| 2554 |
#' interval's estimate, that is required to stop a trial. |
|
| 2555 |
#' @slot prob_target (`proportion`)\cr the target probability of the occurrence |
|
| 2556 |
#' of a DLT. |
|
| 2557 |
#' |
|
| 2558 |
#' @aliases StoppingTDCIRatio |
|
| 2559 |
#' @export |
|
| 2560 |
#' |
|
| 2561 |
.StoppingTDCIRatio <- setClass( |
|
| 2562 |
Class = "StoppingTDCIRatio", |
|
| 2563 |
slots = c( |
|
| 2564 |
target_ratio = "numeric", |
|
| 2565 |
prob_target = "numeric" |
|
| 2566 |
), |
|
| 2567 |
prototype = prototype( |
|
| 2568 |
target_ratio = 5, |
|
| 2569 |
prob_target = 0.3 |
|
| 2570 |
), |
|
| 2571 |
contains = "Stopping", |
|
| 2572 |
validity = v_stopping_tdci_ratio |
|
| 2573 |
) |
|
| 2574 | ||
| 2575 |
## constructor ---- |
|
| 2576 | ||
| 2577 |
#' @rdname StoppingTDCIRatio-class |
|
| 2578 |
#' |
|
| 2579 |
#' @param target_ratio (`numeric`)\cr see slot definition. |
|
| 2580 |
#' @param prob_target (`proportion`)\cr see slot definition. |
|
| 2581 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2582 |
#' |
|
| 2583 |
#' @export |
|
| 2584 |
#' @example examples/Rules-class-StoppingTDCIRatio.R |
|
| 2585 |
#' |
|
| 2586 |
StoppingTDCIRatio <- function( |
|
| 2587 |
target_ratio = 5, |
|
| 2588 |
prob_target = 0.3, |
|
| 2589 |
report_label = NA_character_ |
|
| 2590 |
) {
|
|
| 2591 | 979x |
report_label <- h_default_if_empty( |
| 2592 | 979x |
as.character(report_label), |
| 2593 | 979x |
paste("TD", target_ratio, "for", prob_target, "target prob")
|
| 2594 |
) |
|
| 2595 | ||
| 2596 | 979x |
.StoppingTDCIRatio( |
| 2597 | 979x |
target_ratio = target_ratio, |
| 2598 | 979x |
prob_target = prob_target, |
| 2599 | 979x |
report_label = report_label |
| 2600 |
) |
|
| 2601 |
} |
|
| 2602 | ||
| 2603 |
## default constructor ---- |
|
| 2604 | ||
| 2605 |
#' @rdname StoppingTDCIRatio-class |
|
| 2606 |
#' @note Typically, end users will not use the `.DefaultStoppingTDCIRatio()` function. |
|
| 2607 |
#' @export |
|
| 2608 |
.DefaultStoppingTDCIRatio <- function() {
|
|
| 2609 | 7x |
StoppingTDCIRatio( |
| 2610 | 7x |
target_ratio = 5, |
| 2611 | 7x |
prob_target = 0.3 |
| 2612 |
) |
|
| 2613 |
} |
|
| 2614 | ||
| 2615 |
# StoppingMaxGainCIRatio ---- |
|
| 2616 | ||
| 2617 |
## class ---- |
|
| 2618 | ||
| 2619 |
#' `StoppingMaxGainCIRatio` |
|
| 2620 |
#' |
|
| 2621 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2622 |
#' |
|
| 2623 |
#' [`StoppingMaxGainCIRatio`] is the class for testing a stopping rule that is based |
|
| 2624 |
#' on a target ratio of the 95% credibility interval. Specifically, this is the |
|
| 2625 |
#' ratio of the upper to the lower bound of the 95% credibility interval's |
|
| 2626 |
#' estimate of the: |
|
| 2627 |
#' (1) target dose (i.e. a dose that corresponds to a given target |
|
| 2628 |
#' probability of the occurrence of a DLT `prob_target`), or |
|
| 2629 |
#' (2) max gain dose (i.e. a dose which gives the maximum gain), |
|
| 2630 |
#' depending on which one out of these two is smaller. |
|
| 2631 |
#' |
|
| 2632 |
#' @slot target_ratio (`numeric`)\cr target for the ratio of the 95% credibility |
|
| 2633 |
#' interval's estimate, that is required to stop a trial. |
|
| 2634 |
#' @slot prob_target (`proportion`)\cr the target probability of the occurrence |
|
| 2635 |
#' of a DLT. |
|
| 2636 |
#' |
|
| 2637 |
#' @aliases StoppingMaxGainCIRatio |
|
| 2638 |
#' @export |
|
| 2639 |
#' |
|
| 2640 |
.StoppingMaxGainCIRatio <- setClass( |
|
| 2641 |
Class = "StoppingMaxGainCIRatio", |
|
| 2642 |
slots = c( |
|
| 2643 |
target_ratio = "numeric", |
|
| 2644 |
prob_target = "numeric" |
|
| 2645 |
), |
|
| 2646 |
prototype = prototype( |
|
| 2647 |
target_ratio = 5, |
|
| 2648 |
prob_target = 0.3 |
|
| 2649 |
), |
|
| 2650 |
contains = "Stopping", |
|
| 2651 |
validity = v_stopping_tdci_ratio |
|
| 2652 |
) |
|
| 2653 | ||
| 2654 |
## constructor ---- |
|
| 2655 | ||
| 2656 |
#' @rdname StoppingMaxGainCIRatio-class |
|
| 2657 |
#' |
|
| 2658 |
#' @param target_ratio (`numeric`)\cr see slot definition. |
|
| 2659 |
#' @param prob_target (`proportion`)\cr see slot definition. |
|
| 2660 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2661 |
#' |
|
| 2662 |
#' @export |
|
| 2663 |
#' @example examples/Rules-class-StoppingMaxGainCIRatio.R |
|
| 2664 |
#' |
|
| 2665 |
StoppingMaxGainCIRatio <- function( |
|
| 2666 |
target_ratio = 5, |
|
| 2667 |
prob_target = 0.3, |
|
| 2668 |
report_label = NA_character_ |
|
| 2669 |
) {
|
|
| 2670 | 13x |
report_label <- h_default_if_empty( |
| 2671 | 13x |
as.character(report_label), |
| 2672 | 13x |
paste("GStar", target_ratio, "for", prob_target, "target prob")
|
| 2673 |
) |
|
| 2674 | ||
| 2675 | 13x |
.StoppingMaxGainCIRatio( |
| 2676 | 13x |
target_ratio = target_ratio, |
| 2677 | 13x |
prob_target = prob_target, |
| 2678 | 13x |
report_label = report_label |
| 2679 |
) |
|
| 2680 |
} |
|
| 2681 | ||
| 2682 | ||
| 2683 |
## default constructor ---- |
|
| 2684 | ||
| 2685 |
#' @rdname StoppingMaxGainCIRatio-class |
|
| 2686 |
#' @examples |
|
| 2687 |
#' .DefaultStoppingMaxGainCIRatio() |
|
| 2688 |
#' @export |
|
| 2689 |
.DefaultStoppingMaxGainCIRatio <- function() {
|
|
| 2690 | 7x |
StoppingMaxGainCIRatio( |
| 2691 | 7x |
target_ratio = 5, |
| 2692 | 7x |
prob_target = 0.3 |
| 2693 |
) |
|
| 2694 |
} |
|
| 2695 | ||
| 2696 |
# StoppingList ---- |
|
| 2697 | ||
| 2698 |
## class ---- |
|
| 2699 | ||
| 2700 |
#' `StoppingList` |
|
| 2701 |
#' |
|
| 2702 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2703 |
#' |
|
| 2704 |
#' [`StoppingList`] is the class for testing a stopping rule that consists of |
|
| 2705 |
#' many single stopping rules that are in turn the objects of class `Stopping`. |
|
| 2706 |
#' The `summary` slot stores a function that takes a logical vector of the size |
|
| 2707 |
#' of `stop_list` and returns a single logical value. For example, if the function |
|
| 2708 |
#' `all` is specified as a `summary` function, then that all stopping rules |
|
| 2709 |
#' defined in `stop_list` must be satisfied in order the result of this rule to |
|
| 2710 |
#' be `TRUE`. |
|
| 2711 |
#' |
|
| 2712 |
#' @slot stop_list (`list`)\cr list of stopping rules. |
|
| 2713 |
#' @slot summary (`function`)\cr a summary function to combine the results of |
|
| 2714 |
#' the stopping rules into a single result. |
|
| 2715 |
#' |
|
| 2716 |
#' @aliases StoppingList |
|
| 2717 |
#' @export |
|
| 2718 |
#' |
|
| 2719 |
.StoppingList <- setClass( |
|
| 2720 |
Class = "StoppingList", |
|
| 2721 |
slots = c( |
|
| 2722 |
stop_list = "list", |
|
| 2723 |
summary = "function" |
|
| 2724 |
), |
|
| 2725 |
prototype = prototype( |
|
| 2726 |
stop_list = list(StoppingMinPatients(50), StoppingMinCohorts(5)), |
|
| 2727 |
summary = all |
|
| 2728 |
), |
|
| 2729 |
contains = "Stopping", |
|
| 2730 |
validity = v_stopping_list |
|
| 2731 |
) |
|
| 2732 | ||
| 2733 |
## constructor ---- |
|
| 2734 | ||
| 2735 |
#' @rdname StoppingList-class |
|
| 2736 |
#' |
|
| 2737 |
#' @param stop_list (`list`)\cr see slot definition. |
|
| 2738 |
#' @param summary (`function`)\cr see slot definition. |
|
| 2739 |
#' |
|
| 2740 |
#' @export |
|
| 2741 |
#' @example examples/Rules-class-StoppingList.R |
|
| 2742 |
#' |
|
| 2743 |
StoppingList <- function(stop_list, summary) {
|
|
| 2744 | 18x |
.StoppingList( |
| 2745 | 18x |
stop_list = stop_list, |
| 2746 | 18x |
summary = summary |
| 2747 |
) |
|
| 2748 |
} |
|
| 2749 | ||
| 2750 |
## default constructor ---- |
|
| 2751 | ||
| 2752 |
#' @rdname StoppingList-class |
|
| 2753 |
#' @note Typically, end users will not use the `.DefaultStoppingList()` function. |
|
| 2754 |
#' @export |
|
| 2755 |
.DefaultStoppingList <- function() {
|
|
| 2756 | 8x |
StoppingList( |
| 2757 | 8x |
stop_list = c( |
| 2758 | 8x |
StoppingMinCohorts(nCohorts = 3L), |
| 2759 | 8x |
StoppingTargetProb(target = c(0.2, 0.35), prob = 0.5), |
| 2760 | 8x |
StoppingMinPatients(nPatients = 20L) |
| 2761 |
), |
|
| 2762 | 8x |
summary = any |
| 2763 |
) |
|
| 2764 |
} |
|
| 2765 | ||
| 2766 |
# StoppingAll ---- |
|
| 2767 | ||
| 2768 |
## class ---- |
|
| 2769 | ||
| 2770 |
#' `StoppingAll` |
|
| 2771 |
#' |
|
| 2772 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2773 |
#' |
|
| 2774 |
#' [`StoppingAll`] is the class for testing a stopping rule that consists of |
|
| 2775 |
#' many single stopping rules that are in turn the objects of class `Stopping`. |
|
| 2776 |
#' All single stopping rules must be satisfied in order the result of this rule |
|
| 2777 |
#' to be `TRUE`. |
|
| 2778 |
#' |
|
| 2779 |
#' @slot stop_list (`list`)\cr list of stopping rules. |
|
| 2780 |
#' @slot report_label label for reporting |
|
| 2781 |
#' @aliases StoppingAll |
|
| 2782 |
#' @export |
|
| 2783 |
#' |
|
| 2784 |
.StoppingAll <- setClass( |
|
| 2785 |
Class = "StoppingAll", |
|
| 2786 |
slots = c( |
|
| 2787 |
stop_list = "list" |
|
| 2788 |
), |
|
| 2789 |
prototype = prototype( |
|
| 2790 |
stop_list = list( |
|
| 2791 |
StoppingMinPatients(50), |
|
| 2792 |
StoppingMinCohorts(5) |
|
| 2793 |
) |
|
| 2794 |
), |
|
| 2795 |
contains = "Stopping", |
|
| 2796 |
validity = v_stopping_all |
|
| 2797 |
) |
|
| 2798 | ||
| 2799 |
## constructor ---- |
|
| 2800 | ||
| 2801 |
#' @rdname StoppingAll-class |
|
| 2802 |
#' |
|
| 2803 |
#' @param stop_list (`list`)\cr see slot definition. |
|
| 2804 |
#' @param report_label (`string`) \cr see slot definition. |
|
| 2805 |
#' @export |
|
| 2806 |
#' @example examples/Rules-class-StoppingAll.R |
|
| 2807 |
#' |
|
| 2808 |
StoppingAll <- function(stop_list, report_label = NA_character_) {
|
|
| 2809 | 40x |
.StoppingAll( |
| 2810 | 40x |
stop_list = stop_list, |
| 2811 | 40x |
report_label = report_label |
| 2812 |
) |
|
| 2813 |
} |
|
| 2814 |
## default constructor ---- |
|
| 2815 | ||
| 2816 |
#' @rdname StoppingAll-class |
|
| 2817 |
#' @note Typically, end users will not use the `.DefaultStoppingAll()` function. |
|
| 2818 |
#' @export |
|
| 2819 |
.DefaultStoppingAll <- function() {
|
|
| 2820 | 8x |
StoppingAll( |
| 2821 | 8x |
stop_list = c( |
| 2822 | 8x |
StoppingMinCohorts(nCohorts = 3L), |
| 2823 | 8x |
StoppingTargetProb(target = c(0.2, 0.35), prob = 0.5), |
| 2824 | 8x |
StoppingMinPatients(nPatients = 20L) |
| 2825 |
) |
|
| 2826 |
) |
|
| 2827 |
} |
|
| 2828 | ||
| 2829 |
# StoppingAny ---- |
|
| 2830 | ||
| 2831 |
## class ---- |
|
| 2832 | ||
| 2833 |
#' `StoppingAny` |
|
| 2834 |
#' |
|
| 2835 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2836 |
#' |
|
| 2837 |
#' [`StoppingAny`] is the class for testing a stopping rule that consists of |
|
| 2838 |
#' many single stopping rules that are in turn the objects of class `Stopping`. |
|
| 2839 |
#' At least one single stopping rule must be satisfied in order the result of |
|
| 2840 |
#' this rule to be `TRUE`. |
|
| 2841 |
#' |
|
| 2842 |
#' @slot stop_list (`list`)\cr list of stopping rules. |
|
| 2843 |
#' @slot report_label label for reporting |
|
| 2844 |
#' |
|
| 2845 |
#' @aliases StoppingAny |
|
| 2846 |
#' @export |
|
| 2847 |
#' |
|
| 2848 |
.StoppingAny <- setClass( |
|
| 2849 |
Class = "StoppingAny", |
|
| 2850 |
slots = c( |
|
| 2851 |
stop_list = "list" |
|
| 2852 |
), |
|
| 2853 |
prototype = prototype( |
|
| 2854 |
stop_list = list(StoppingMinPatients(50), StoppingMinCohorts(5)) |
|
| 2855 |
), |
|
| 2856 |
contains = "Stopping", |
|
| 2857 |
validity = v_stopping_all |
|
| 2858 |
) |
|
| 2859 | ||
| 2860 |
## constructor ---- |
|
| 2861 | ||
| 2862 |
#' @rdname StoppingAny-class |
|
| 2863 |
#' |
|
| 2864 |
#' @param stop_list (`list`)\cr see slot definition. |
|
| 2865 |
#' @param report_label (`string`)\cr see slot definition. |
|
| 2866 |
#' |
|
| 2867 |
#' @export |
|
| 2868 |
#' @example examples/Rules-class-StoppingAny.R |
|
| 2869 |
#' |
|
| 2870 |
StoppingAny <- function(stop_list, report_label = NA_character_) {
|
|
| 2871 | 67x |
.StoppingAny( |
| 2872 | 67x |
stop_list = stop_list, |
| 2873 | 67x |
report_label = report_label |
| 2874 |
) |
|
| 2875 |
} |
|
| 2876 | ||
| 2877 |
## default constructor ---- |
|
| 2878 | ||
| 2879 |
#' @rdname StoppingAny-class |
|
| 2880 |
#' @note Typically, end users will not use the `.DefaultStoppingAny()` function. |
|
| 2881 |
#' @export |
|
| 2882 |
.DefaultStoppingAny <- function() {
|
|
| 2883 | 8x |
StoppingAny( |
| 2884 | 8x |
stop_list = c( |
| 2885 | 8x |
StoppingMinCohorts(nCohorts = 3L), |
| 2886 | 8x |
StoppingTargetProb(target = c(0.2, 0.35), prob = 0.5), |
| 2887 | 8x |
StoppingMinPatients(nPatients = 20L) |
| 2888 |
) |
|
| 2889 |
) |
|
| 2890 |
} |
|
| 2891 | ||
| 2892 |
# StoppingOrdinal ---- |
|
| 2893 | ||
| 2894 |
## class ---- |
|
| 2895 | ||
| 2896 |
#' `StoppingOrdinal` |
|
| 2897 |
#' |
|
| 2898 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 2899 |
#' |
|
| 2900 |
#' [`StoppingOrdinal`] is the class for stopping based on a Stopping rule applied |
|
| 2901 |
#' to a specific toxicity grade in an ordinal CRM trial |
|
| 2902 |
#' |
|
| 2903 |
#' @slot grade (`integer`)\cr the grade to which the rule should be applied |
|
| 2904 |
#' @slot rule (`Stopping`)\cr the rule to apply |
|
| 2905 |
#' |
|
| 2906 |
#' @aliases StoppingOrdinal |
|
| 2907 |
#' @export |
|
| 2908 |
#' |
|
| 2909 |
.StoppingOrdinal <- setClass( |
|
| 2910 |
Class = "StoppingOrdinal", |
|
| 2911 |
slots = c(grade = "integer", rule = "Stopping"), |
|
| 2912 |
contains = "Stopping" |
|
| 2913 |
) |
|
| 2914 | ||
| 2915 |
## constructor ---- |
|
| 2916 | ||
| 2917 |
#' @rdname StoppingOrdinal-class |
|
| 2918 |
#' @param grade (`integer`)\cr see slot definition. |
|
| 2919 |
#' @param rule (`Stopping`)\cr see slot definition. |
|
| 2920 |
#' @example examples/Rules-class-StoppingOrdinal.R |
|
| 2921 |
#' @export |
|
| 2922 |
#' |
|
| 2923 |
StoppingOrdinal <- function(grade, rule) {
|
|
| 2924 | 19x |
.StoppingOrdinal(grade = grade, rule = rule) |
| 2925 |
} |
|
| 2926 | ||
| 2927 |
## default constructor ---- |
|
| 2928 | ||
| 2929 |
#' @rdname StoppingOrdinal-class |
|
| 2930 |
#' @note Typically, end users will not use the `.DefaultStoppingOrdinal()` function. |
|
| 2931 |
#' @export |
|
| 2932 |
#' |
|
| 2933 |
.DefaultStoppingOrdinal <- function() {
|
|
| 2934 | 11x |
StoppingOrdinal( |
| 2935 | 11x |
1L, |
| 2936 | 11x |
StoppingTargetProb(target = c(0.2, 0.35), prob = 0.6) |
| 2937 |
) |
|
| 2938 |
} |
|
| 2939 | ||
| 2940 |
# StoppingExternal ---- |
|
| 2941 | ||
| 2942 |
## class ---- |
|
| 2943 | ||
| 2944 |
#' `StoppingExternal` |
|
| 2945 |
#' |
|
| 2946 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 2947 |
#' |
|
| 2948 |
#' [`StoppingExternal`] is the class for stopping based on an external flag. |
|
| 2949 |
#' |
|
| 2950 |
#' @aliases StoppingExternal |
|
| 2951 |
#' @export |
|
| 2952 |
#' |
|
| 2953 |
.StoppingExternal <- setClass( |
|
| 2954 |
Class = "StoppingExternal", |
|
| 2955 |
contains = "Stopping" |
|
| 2956 |
) |
|
| 2957 | ||
| 2958 |
## constructor ---- |
|
| 2959 | ||
| 2960 |
#' @rdname StoppingExternal-class |
|
| 2961 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
| 2962 |
#' @example examples/Rules-class-StoppingExternal.R |
|
| 2963 |
#' @export |
|
| 2964 |
#' |
|
| 2965 |
StoppingExternal <- function(report_label = NA_character_) {
|
|
| 2966 | 13x |
report_label <- h_default_if_empty( |
| 2967 | 13x |
as.character(report_label), |
| 2968 | 13x |
paste("Stopped because of external flag")
|
| 2969 |
) |
|
| 2970 | 13x |
.StoppingExternal(report_label = report_label) |
| 2971 |
} |
|
| 2972 | ||
| 2973 |
## default constructor ---- |
|
| 2974 | ||
| 2975 |
#' @rdname StoppingExternal-class |
|
| 2976 |
#' @note Typically, end users will not use the `.DefaultStoppingExternal()` function. |
|
| 2977 |
#' @export |
|
| 2978 |
#' |
|
| 2979 |
.DefaultStoppingExternal <- StoppingExternal |
|
| 2980 | ||
| 2981 |
# CohortSize ---- |
|
| 2982 | ||
| 2983 |
## class ---- |
|
| 2984 | ||
| 2985 |
#' `CohortSize` |
|
| 2986 |
#' |
|
| 2987 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2988 |
#' |
|
| 2989 |
#' [`CohortSize`] is a class for cohort sizes. |
|
| 2990 |
#' |
|
| 2991 |
#' @seealso [`CohortSizeRange`], [`CohortSizeDLT`], [`CohortSizeConst`], |
|
| 2992 |
#' [`CohortSizeParts`], [`CohortSizeMin`], [`CohortSizeMin`]. |
|
| 2993 |
#' |
|
| 2994 |
#' @aliases CohortSize |
|
| 2995 |
#' @export |
|
| 2996 |
#' |
|
| 2997 |
setClass( |
|
| 2998 |
Class = "CohortSize", |
|
| 2999 |
contains = "CrmPackClass" |
|
| 3000 |
) |
|
| 3001 | ||
| 3002 |
## default constructor |
|
| 3003 | ||
| 3004 |
#' @rdname CohortSize-class |
|
| 3005 |
#' @note Typically, end users will not use the `DefaultCohortSize()` function. |
|
| 3006 |
#' @export |
|
| 3007 |
.DefaultCohortSize <- function() {
|
|
| 3008 | 2x |
stop(paste0( |
| 3009 | 2x |
"Class CohortSize should not be instantiated directly. Please use one of its subclasses instead." |
| 3010 |
)) |
|
| 3011 |
} |
|
| 3012 | ||
| 3013 |
# CohortSizeRange ---- |
|
| 3014 | ||
| 3015 |
## class ---- |
|
| 3016 | ||
| 3017 |
#' `CohortSizeRange` |
|
| 3018 |
#' |
|
| 3019 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3020 |
#' |
|
| 3021 |
#' [`CohortSizeRange`] is the class for cohort size based on dose range. |
|
| 3022 |
#' |
|
| 3023 |
#' @slot intervals (`numeric`)\cr a vector with the left bounds of the relevant |
|
| 3024 |
#' dose intervals. |
|
| 3025 |
#' @slot cohort_size (`integer`)\cr an integer vector with the cohort sizes |
|
| 3026 |
#' corresponding to the elements of `intervals`. |
|
| 3027 |
#' |
|
| 3028 |
#' @aliases CohortSizeRange |
|
| 3029 |
#' @export |
|
| 3030 |
#' |
|
| 3031 |
.CohortSizeRange <- setClass( |
|
| 3032 |
Class = "CohortSizeRange", |
|
| 3033 |
slots = c( |
|
| 3034 |
intervals = "numeric", |
|
| 3035 |
cohort_size = "integer" |
|
| 3036 |
), |
|
| 3037 |
prototype = prototype( |
|
| 3038 |
intervals = c(0, 20), |
|
| 3039 |
cohort_size = c(1L, 3L) |
|
| 3040 |
), |
|
| 3041 |
contains = "CohortSize", |
|
| 3042 |
validity = v_cohort_size_range |
|
| 3043 |
) |
|
| 3044 | ||
| 3045 |
## constructor ---- |
|
| 3046 | ||
| 3047 |
#' @rdname CohortSizeRange-class |
|
| 3048 |
#' |
|
| 3049 |
#' @param intervals (`numeric`)\cr see slot definition. |
|
| 3050 |
#' @param cohort_size (`numeric`)\cr see slot definition. |
|
| 3051 |
#' |
|
| 3052 |
#' @export |
|
| 3053 |
#' @example examples/Rules-class-CohortSizeRange.R |
|
| 3054 |
#' |
|
| 3055 |
CohortSizeRange <- function(intervals, cohort_size) {
|
|
| 3056 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
| 3057 | 89x |
assert_integerish(cohort_size, lower = 0, any.missing = FALSE) |
| 3058 | ||
| 3059 | 89x |
.CohortSizeRange( |
| 3060 | 89x |
intervals = intervals, |
| 3061 | 89x |
cohort_size = as.integer(cohort_size) |
| 3062 |
) |
|
| 3063 |
} |
|
| 3064 | ||
| 3065 |
## default constructor ---- |
|
| 3066 | ||
| 3067 |
#' @rdname CohortSizeRange-class |
|
| 3068 |
#' @note Typically, end users will not use the `.DefaultCohortSizeRange()` function. |
|
| 3069 |
#' @export |
|
| 3070 |
.DefaultCohortSizeRange <- function() {
|
|
| 3071 | 9x |
CohortSizeRange(intervals = c(0L, 30L), cohort_size = c(1L, 3L)) |
| 3072 |
} |
|
| 3073 | ||
| 3074 |
# CohortSizeDLT ---- |
|
| 3075 | ||
| 3076 |
## class ---- |
|
| 3077 | ||
| 3078 |
#' `CohortSizeDLT` |
|
| 3079 |
#' |
|
| 3080 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3081 |
#' |
|
| 3082 |
#' [`CohortSizeDLT`] is the class for cohort size based on number of DLTs. |
|
| 3083 | ||
| 3084 |
#' @slot intervals (`integer`)\cr a vector with the left bounds of the |
|
| 3085 |
#' relevant DLT intervals. |
|
| 3086 |
#' @slot cohort_size (`integer`)\cr a vector with the cohort sizes corresponding |
|
| 3087 |
#' to the elements of `intervals`. |
|
| 3088 |
#' |
|
| 3089 |
#' @aliases CohortSizeDLT |
|
| 3090 |
#' @export |
|
| 3091 |
#' |
|
| 3092 |
.CohortSizeDLT <- setClass( |
|
| 3093 |
Class = "CohortSizeDLT", |
|
| 3094 |
slots = c( |
|
| 3095 |
intervals = "integer", |
|
| 3096 |
cohort_size = "integer" |
|
| 3097 |
), |
|
| 3098 |
prototype = prototype( |
|
| 3099 |
intervals = c(0L, 1L), |
|
| 3100 |
cohort_size = c(1L, 3L) |
|
| 3101 |
), |
|
| 3102 |
contains = "CohortSize", |
|
| 3103 |
validity = v_cohort_size_dlt |
|
| 3104 |
) |
|
| 3105 | ||
| 3106 |
## constructor ---- |
|
| 3107 | ||
| 3108 |
#' @rdname CohortSizeDLT-class |
|
| 3109 |
#' |
|
| 3110 |
#' @param intervals (`numeric`)\cr see slot definition. |
|
| 3111 |
#' @param cohort_size (`numeric`)\cr see slot definition. |
|
| 3112 |
#' |
|
| 3113 |
#' @export |
|
| 3114 |
#' @example examples/Rules-class-CohortSizeDLT.R |
|
| 3115 |
#' |
|
| 3116 |
CohortSizeDLT <- function(intervals, cohort_size) {
|
|
| 3117 | 77x |
assert_integerish(intervals, lower = 0, any.missing = FALSE) |
| 3118 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
| 3119 | 77x |
assert_integerish(cohort_size, lower = 0, any.missing = FALSE) |
| 3120 | ||
| 3121 | 77x |
.CohortSizeDLT( |
| 3122 | 77x |
intervals = as.integer(intervals), |
| 3123 | 77x |
cohort_size = as.integer(cohort_size) |
| 3124 |
) |
|
| 3125 |
} |
|
| 3126 | ||
| 3127 |
## default constructor ---- |
|
| 3128 | ||
| 3129 |
#' @rdname CohortSizeDLT-class |
|
| 3130 |
#' @note Typically, end users will not use the `.DefaultCohortSizeDLT()` function. |
|
| 3131 |
#' @export |
|
| 3132 |
.DefaultCohortSizeDLT <- function() {
|
|
| 3133 | 7x |
CohortSizeDLT(intervals = c(0L, 1L), cohort_size = c(1L, 3L)) |
| 3134 |
} |
|
| 3135 | ||
| 3136 | ||
| 3137 |
# CohortSizeConst ---- |
|
| 3138 | ||
| 3139 |
## class ---- |
|
| 3140 | ||
| 3141 |
#' `CohortSizeConst` |
|
| 3142 |
#' |
|
| 3143 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3144 |
#' |
|
| 3145 |
#' [`CohortSizeConst`] is the class for fixed and constant size of cohort. |
|
| 3146 |
#' |
|
| 3147 |
#' @slot size (`integer`)\cr cohort size. |
|
| 3148 |
#' |
|
| 3149 |
#' @aliases CohortSizeConst |
|
| 3150 |
#' @export |
|
| 3151 |
#' |
|
| 3152 |
.CohortSizeConst <- setClass( |
|
| 3153 |
Class = "CohortSizeConst", |
|
| 3154 |
slots = c(size = "integer"), |
|
| 3155 |
prototype = prototype(size = 3L), |
|
| 3156 |
contains = "CohortSize", |
|
| 3157 |
validity = v_cohort_size_const |
|
| 3158 |
) |
|
| 3159 | ||
| 3160 |
## constructor ---- |
|
| 3161 | ||
| 3162 |
#' @rdname CohortSizeConst-class |
|
| 3163 |
#' |
|
| 3164 |
#' @param size (`number`)\cr see slot definition. |
|
| 3165 |
#' |
|
| 3166 |
#' @export |
|
| 3167 |
#' @example examples/Rules-class-CohortSizeConst.R |
|
| 3168 |
#' |
|
| 3169 |
CohortSizeConst <- function(size) {
|
|
| 3170 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
| 3171 | 338x |
assert_integerish(size, lower = 0) |
| 3172 | 338x |
.CohortSizeConst(size = as.integer(size)) |
| 3173 |
} |
|
| 3174 | ||
| 3175 |
## default constructor ---- |
|
| 3176 | ||
| 3177 |
#' @rdname CohortSizeConst-class |
|
| 3178 |
#' @note Typically, end users will not use the `.DefaultCohortSizeConst()` function. |
|
| 3179 |
#' @export |
|
| 3180 |
.DefaultCohortSizeConst <- function() {
|
|
| 3181 | 6x |
CohortSizeConst(size = 3L) |
| 3182 |
} |
|
| 3183 | ||
| 3184 |
# CohortSizeParts ---- |
|
| 3185 | ||
| 3186 |
## class ---- |
|
| 3187 | ||
| 3188 |
#' `CohortSizeParts` |
|
| 3189 |
#' |
|
| 3190 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3191 |
#' |
|
| 3192 |
#' [`CohortSizeParts`] is the class for cohort size that changes for the second |
|
| 3193 |
#' part of the dose escalation. It works only in conjunction with [`DataParts`] |
|
| 3194 |
#' objects. |
|
| 3195 |
#' |
|
| 3196 |
#' @slot cohort_sizes (`integer`)\cr a vector of length two with two sizes, one for |
|
| 3197 |
#' part 1, and one for part 2 respectively. |
|
| 3198 |
#' |
|
| 3199 |
#' @aliases CohortSizeParts |
|
| 3200 |
#' @export |
|
| 3201 |
#' |
|
| 3202 |
.CohortSizeParts <- setClass( |
|
| 3203 |
Class = "CohortSizeParts", |
|
| 3204 |
slots = c(cohort_sizes = "integer"), |
|
| 3205 |
prototype = prototype(cohort_sizes = c(1L, 3L)), |
|
| 3206 |
contains = "CohortSize", |
|
| 3207 |
validity = v_cohort_size_parts |
|
| 3208 |
) |
|
| 3209 | ||
| 3210 |
## constructor ---- |
|
| 3211 | ||
| 3212 |
#' @rdname CohortSizeParts-class |
|
| 3213 |
#' |
|
| 3214 |
#' @param cohort_sizes (`numeric`)\cr see slot definition. |
|
| 3215 |
#' |
|
| 3216 |
#' @export |
|
| 3217 |
#' @example examples/Rules-class-CohortSizeParts.R |
|
| 3218 |
#' |
|
| 3219 |
CohortSizeParts <- function(cohort_sizes) {
|
|
| 3220 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
| 3221 | 16x |
assert_integerish(cohort_sizes, lower = 0, any.missing = FALSE) |
| 3222 | 16x |
.CohortSizeParts(cohort_sizes = as.integer(cohort_sizes)) |
| 3223 |
} |
|
| 3224 | ||
| 3225 |
## default constructor ---- |
|
| 3226 | ||
| 3227 |
#' @rdname CohortSizeParts-class |
|
| 3228 |
#' @note Typically, end users will not use the `.DefaultCohortSizeParts()` function. |
|
| 3229 |
#' @export |
|
| 3230 |
.DefaultCohortSizeParts <- function() {
|
|
| 3231 | 7x |
CohortSizeParts(cohort_sizes = c(1L, 3L)) |
| 3232 |
} |
|
| 3233 | ||
| 3234 |
# CohortSizeMax ---- |
|
| 3235 | ||
| 3236 |
## class ---- |
|
| 3237 | ||
| 3238 |
#' `CohortSizeMax` |
|
| 3239 |
#' |
|
| 3240 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3241 |
#' |
|
| 3242 |
#' [`CohortSizeMax`] is the class for cohort size that is based on maximum of |
|
| 3243 |
#' multiple cohort size rules. The `cohort_sizes` slot stores a set of cohort |
|
| 3244 |
#' size rules, which are again the objects of class [`CohortSize`]. The maximum |
|
| 3245 |
#' of these individual cohort sizes is taken to give the final cohort size. |
|
| 3246 |
#' |
|
| 3247 |
#' @slot cohort_sizes (`list`)\cr a list of cohort size rules, i.e. objects |
|
| 3248 |
#' of class [`CohortSize`]. |
|
| 3249 |
#' |
|
| 3250 |
#' @aliases CohortSizeMax |
|
| 3251 |
#' @export |
|
| 3252 |
#' |
|
| 3253 |
.CohortSizeMax <- setClass( |
|
| 3254 |
Class = "CohortSizeMax", |
|
| 3255 |
slots = c(cohort_sizes = "list"), |
|
| 3256 |
prototype = prototype( |
|
| 3257 |
cohort_sizes = list( |
|
| 3258 |
CohortSizeRange(intervals = c(0, 30), cohort_size = c(1, 3)), |
|
| 3259 |
CohortSizeDLT(intervals = c(0, 1), cohort_size = c(1, 3)) |
|
| 3260 |
) |
|
| 3261 |
), |
|
| 3262 |
contains = "CohortSize", |
|
| 3263 |
validity = v_cohort_size_max |
|
| 3264 |
) |
|
| 3265 | ||
| 3266 |
## default constructor ---- |
|
| 3267 | ||
| 3268 |
#' @rdname CohortSizeMax-class |
|
| 3269 |
#' @note Typically, end users will not use the `.DefaultCohortSizeMax()` function. |
|
| 3270 |
#' |
|
| 3271 |
#' @export |
|
| 3272 |
.DefaultCohortSizeMax <- function() {
|
|
| 3273 | 7x |
CohortSizeMax( |
| 3274 | 7x |
cohort_sizes = list( |
| 3275 | 7x |
CohortSizeRange(intervals = c(0, 10), cohort_size = c(1L, 3L)), |
| 3276 | 7x |
CohortSizeDLT(intervals = c(0L, 1L), cohort_size = c(1L, 3L)) |
| 3277 |
) |
|
| 3278 |
) |
|
| 3279 |
} |
|
| 3280 | ||
| 3281 |
## constructor ---- |
|
| 3282 | ||
| 3283 |
#' @rdname CohortSizeMax-class |
|
| 3284 |
#' |
|
| 3285 |
#' @param cohort_sizes (`list`)\cr see slot definition. |
|
| 3286 |
#' |
|
| 3287 |
#' @export |
|
| 3288 |
#' @example examples/Rules-class-CohortSizeMax.R |
|
| 3289 |
#' |
|
| 3290 |
CohortSizeMax <- function(cohort_sizes) {
|
|
| 3291 | 50x |
.CohortSizeMax(cohort_sizes = cohort_sizes) |
| 3292 |
} |
|
| 3293 | ||
| 3294 |
# CohortSizeMin ---- |
|
| 3295 | ||
| 3296 |
## class ---- |
|
| 3297 | ||
| 3298 |
#' `CohortSizeMin` |
|
| 3299 |
#' |
|
| 3300 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3301 |
#' |
|
| 3302 |
#' [`CohortSizeMin`] is the class for cohort size that is based on minimum of |
|
| 3303 |
#' multiple cohort size rules. The `cohort_sizes` slot stores a set of cohort |
|
| 3304 |
#' size rules, which are again the objects of class [`CohortSize`]. The minimum |
|
| 3305 |
#' of these individual cohort sizes is taken to give the final cohort size. |
|
| 3306 |
#' |
|
| 3307 |
#' @slot cohort_sizes (`list`)\cr a list of cohort size rules, i.e. objects |
|
| 3308 |
#' of class [`CohortSize`]. |
|
| 3309 |
#' |
|
| 3310 |
#' @aliases CohortSizeMin |
|
| 3311 |
#' @export |
|
| 3312 |
#' |
|
| 3313 |
.CohortSizeMin <- setClass( |
|
| 3314 |
Class = "CohortSizeMin", |
|
| 3315 |
slots = c(cohort_sizes = "list"), |
|
| 3316 |
prototype = prototype( |
|
| 3317 |
cohort_sizes = list( |
|
| 3318 |
CohortSizeRange(intervals = c(0, 30), cohort_size = c(1, 3)), |
|
| 3319 |
CohortSizeDLT(intervals = c(0, 1), cohort_size = c(1, 3)) |
|
| 3320 |
) |
|
| 3321 |
), |
|
| 3322 |
contains = "CohortSize", |
|
| 3323 |
validity = v_cohort_size_max |
|
| 3324 |
) |
|
| 3325 | ||
| 3326 |
## constructor ---- |
|
| 3327 | ||
| 3328 |
#' @rdname CohortSizeMin-class |
|
| 3329 |
#' |
|
| 3330 |
#' @param cohort_sizes (`list`)\cr see slot definition. |
|
| 3331 |
#' |
|
| 3332 |
#' @export |
|
| 3333 |
#' @example examples/Rules-class-CohortSizeMin.R |
|
| 3334 |
#' |
|
| 3335 |
CohortSizeMin <- function(cohort_sizes) {
|
|
| 3336 | 15x |
.CohortSizeMin(cohort_sizes = cohort_sizes) |
| 3337 |
} |
|
| 3338 | ||
| 3339 |
## default constructor ---- |
|
| 3340 | ||
| 3341 |
#' @rdname CohortSizeMin-class |
|
| 3342 |
#' @note Typically, end users will not use the `.DefaultCohortSizeMin()` function. |
|
| 3343 |
#' @export |
|
| 3344 |
.DefaultCohortSizeMin <- function() {
|
|
| 3345 | 7x |
CohortSizeMin( |
| 3346 | 7x |
cohort_sizes = list( |
| 3347 | 7x |
CohortSizeRange(intervals = c(0, 10), cohort_size = c(1L, 3L)), |
| 3348 | 7x |
CohortSizeDLT(intervals = c(0L, 1L), cohort_size = c(1L, 3L)) |
| 3349 |
) |
|
| 3350 |
) |
|
| 3351 |
} |
|
| 3352 | ||
| 3353 |
# CohortSizeOrdinal ---- |
|
| 3354 | ||
| 3355 |
## class ---- |
|
| 3356 | ||
| 3357 |
#' `CohortSizeOrdinal` |
|
| 3358 |
#' |
|
| 3359 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3360 |
#' |
|
| 3361 |
#' [`CohortSizeOrdinal`] is the class for cohort size for an ordinal CRM trial. |
|
| 3362 |
#' |
|
| 3363 |
#' @slot grade (`integer`)\cr the grade at which the rule should be applied |
|
| 3364 |
#' @slot rule (`CohortSize`)\cr the `CohortSize` rule to apply. |
|
| 3365 |
#' |
|
| 3366 |
#' @aliases CohortSizeOrdinal |
|
| 3367 |
#' @export |
|
| 3368 |
#' |
|
| 3369 |
.CohortSizeOrdinal <- setClass( |
|
| 3370 |
Class = "CohortSizeOrdinal", |
|
| 3371 |
slots = c( |
|
| 3372 |
grade = "integer", |
|
| 3373 |
rule = "CohortSize" |
|
| 3374 |
), |
|
| 3375 |
prototype = prototype( |
|
| 3376 |
grade = 1L, |
|
| 3377 |
rule = CohortSizeRange(intervals = c(0, 30), cohort_size = c(1L, 3L)) |
|
| 3378 |
), |
|
| 3379 |
contains = "CohortSize", |
|
| 3380 |
validity = v_cohort_size_ordinal |
|
| 3381 |
) |
|
| 3382 | ||
| 3383 |
## constructor ---- |
|
| 3384 | ||
| 3385 |
#' @rdname CohortSizeOrdinal-class |
|
| 3386 |
#' |
|
| 3387 |
#' @param grade (`integer`)\cr see slot definition. |
|
| 3388 |
#' @param rule (`CohortSize`)\cr see slot definition. |
|
| 3389 |
#' |
|
| 3390 |
#' @export |
|
| 3391 |
#' @example examples/Rules-class-CohortSizeOrdinal.R |
|
| 3392 |
#' |
|
| 3393 |
CohortSizeOrdinal <- function(grade, rule) {
|
|
| 3394 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
| 3395 | 37x |
assert_integer(grade, lower = 1, len = 1) |
| 3396 | 37x |
assert_class(rule, "CohortSize") |
| 3397 | ||
| 3398 | 37x |
.CohortSizeOrdinal(grade = grade, rule = rule) |
| 3399 |
} |
|
| 3400 | ||
| 3401 |
## default constructor ---- |
|
| 3402 | ||
| 3403 |
#' @rdname CohortSizeOrdinal-class |
|
| 3404 |
#' @note Typically, end users will not use the `.DefaultCohortSizeOrdinal()` function. |
|
| 3405 |
#' @export |
|
| 3406 |
.DefaultCohortSizeOrdinal <- function() {
|
|
| 3407 | 6x |
CohortSizeOrdinal( |
| 3408 | 6x |
grade = 1L, |
| 3409 | 6x |
rule = CohortSizeRange(intervals = c(0L, 30L), cohort_size = c(1L, 3L)) |
| 3410 |
) |
|
| 3411 |
} |
|
| 3412 | ||
| 3413 | ||
| 3414 |
# SafetyWindow ---- |
|
| 3415 | ||
| 3416 |
## class ---- |
|
| 3417 | ||
| 3418 |
#' `SafetyWindow` |
|
| 3419 |
#' |
|
| 3420 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3421 |
#' |
|
| 3422 |
#' [`SafetyWindow`] is a class for safety window. |
|
| 3423 |
#' |
|
| 3424 |
#' @seealso [`SafetyWindowSize`], [`SafetyWindowConst`]. |
|
| 3425 |
#' |
|
| 3426 |
#' @aliases SafetyWindow |
|
| 3427 |
#' @export |
|
| 3428 |
#' |
|
| 3429 |
setClass( |
|
| 3430 |
Class = "SafetyWindow", |
|
| 3431 |
contains = "CrmPackClass" |
|
| 3432 |
) |
|
| 3433 | ||
| 3434 |
## default constructor ---- |
|
| 3435 | ||
| 3436 |
#' @rdname SafetyWindow-class |
|
| 3437 |
#' @note Typically, end users will not use the `.DefaultSafetyWindow()` function. |
|
| 3438 |
#' @export |
|
| 3439 |
.DefaultSafetyWindow <- function() {
|
|
| 3440 | 2x |
stop(paste0( |
| 3441 | 2x |
"Class SafetyWindow cannot be instantiated directly. Please use one of its subclasses instead." |
| 3442 |
)) |
|
| 3443 |
} |
|
| 3444 | ||
| 3445 | ||
| 3446 |
# SafetyWindowSize ---- |
|
| 3447 | ||
| 3448 |
## class ---- |
|
| 3449 | ||
| 3450 |
#' `SafetyWindowSize` |
|
| 3451 |
#' |
|
| 3452 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3453 |
#' |
|
| 3454 |
#' [`SafetyWindowSize`] is the class for safety window length based on cohort |
|
| 3455 |
#' size. This class is used to decide the rolling rule from the clinical |
|
| 3456 |
#' perspective. |
|
| 3457 |
#' |
|
| 3458 |
#' @slot gap (`list`)\cr observed period of the previous patient before |
|
| 3459 |
#' the next patient can be dosed. This is used as follows. If for instance, |
|
| 3460 |
#' the cohort size is 4 and we want to specify three time intervals between |
|
| 3461 |
#' these four consecutive patients, i.e. 7 units of time between the 1st and |
|
| 3462 |
#' the 2nd patient, 5 units between the 2nd and the 3rd one, and finally 3 |
|
| 3463 |
#' units between the 3rd and the 4th one, then, |
|
| 3464 |
#' `gap` = `list(c(7L, 5L, 3L))`. Sometimes, we want that the interval |
|
| 3465 |
#' only between the 1st and 2nd patient should be increased for the |
|
| 3466 |
#' safety consideration and the rest time intervals should remain constant, |
|
| 3467 |
#' regardless of what the cohort size is. Then, `gap` = `list(c(7L, 3L))` |
|
| 3468 |
#' and the the package will automatically repeat the last element of the vector |
|
| 3469 |
#' for the remaining time intervals. |
|
| 3470 |
#' @slot size (`integer`)\cr a vector with the left bounds of the |
|
| 3471 |
#' relevant cohort size intervals. This is used as follows. For instance, when |
|
| 3472 |
#' we want to change the `gap` based on the cohort size, i.e. the time |
|
| 3473 |
#' interval between the 1st and 2nd patient = 9 units of time and the rest |
|
| 3474 |
#' time intervals are of 5 units of time when the cohort size is equal to or |
|
| 3475 |
#' larger than 4. And the time interval between the 1st and 2nd patient = 7 units |
|
| 3476 |
#' of time and the rest time intervals are 3 units of time when the cohort size |
|
| 3477 |
#' is smaller than 4, then we specify both `gap = list(c(7, 3), c(9, 5))` and |
|
| 3478 |
#' `size = c(0L, 4L)`. This means, the right bounds of the intervals are |
|
| 3479 |
#' excluded from the interval, and the last interval goes from the last value |
|
| 3480 |
#' to infinity. |
|
| 3481 |
#' @slot follow (`count`)\cr the period of time that each patient in the |
|
| 3482 |
#' cohort needs to be followed before the next cohort opens. |
|
| 3483 |
#' @slot follow_min (`count`)\cr at least one patient in the cohort needs |
|
| 3484 |
#' to be followed at the minimal follow up time. |
|
| 3485 |
#' |
|
| 3486 |
#' @aliases SafetyWindowSize |
|
| 3487 |
#' @export |
|
| 3488 |
#' |
|
| 3489 |
.SafetyWindowSize <- setClass( |
|
| 3490 |
Class = "SafetyWindowSize", |
|
| 3491 |
slots = c( |
|
| 3492 |
gap = "list", |
|
| 3493 |
size = "integer", |
|
| 3494 |
follow = "integer", |
|
| 3495 |
follow_min = "integer" |
|
| 3496 |
), |
|
| 3497 |
prototype = prototype( |
|
| 3498 |
gap = list(1:2, 1:2), |
|
| 3499 |
size = c(1L, 3L), |
|
| 3500 |
follow = 1L, |
|
| 3501 |
follow_min = 1L |
|
| 3502 |
), |
|
| 3503 |
contains = "SafetyWindow", |
|
| 3504 |
validity = v_safety_window_size |
|
| 3505 |
) |
|
| 3506 | ||
| 3507 |
## constructor ---- |
|
| 3508 | ||
| 3509 |
#' @rdname SafetyWindowSize-class |
|
| 3510 |
#' |
|
| 3511 |
#' @param gap see slot definition. |
|
| 3512 |
#' @param size see slot definition. |
|
| 3513 |
#' @param follow see slot definition. |
|
| 3514 |
#' @param follow_min see slot definition. |
|
| 3515 |
#' |
|
| 3516 |
#' @export |
|
| 3517 |
#' @example examples/Rules-class-SafetyWindowSize.R |
|
| 3518 |
#' |
|
| 3519 |
SafetyWindowSize <- function(gap, size, follow, follow_min) {
|
|
| 3520 | 17x |
assert_integerish(follow, lower = 0) |
| 3521 | 17x |
assert_integerish(follow_min, lower = 0) |
| 3522 | 17x |
for (g in gap) {
|
| 3523 | 36x |
assert_integerish(g, lower = 0) |
| 3524 |
} |
|
| 3525 | 17x |
assert_integerish(size, lower = 0) |
| 3526 | 17x |
if (follow > follow_min) {
|
| 3527 | 1x |
warning( |
| 3528 | 1x |
"The value of follow_min is typically larger than the value of follow" |
| 3529 |
) |
|
| 3530 |
} |
|
| 3531 | 17x |
gap <- lapply(gap, as.integer) |
| 3532 | 17x |
.SafetyWindowSize( |
| 3533 | 17x |
gap = gap, |
| 3534 | 17x |
size = as.integer(size), |
| 3535 | 17x |
follow = as.integer(follow), |
| 3536 | 17x |
follow_min = as.integer(follow_min) |
| 3537 |
) |
|
| 3538 |
} |
|
| 3539 | ||
| 3540 |
## default constructor ---- |
|
| 3541 | ||
| 3542 |
#' @rdname SafetyWindowSize-class |
|
| 3543 |
#' @note Typically, end users will not use the `.DefaultSafetyWindowSize()` function. |
|
| 3544 |
#' @export |
|
| 3545 |
.DefaultSafetyWindowSize <- function() {
|
|
| 3546 | 7x |
SafetyWindowSize( |
| 3547 | 7x |
gap = list(c(7, 3), c(9, 5)), |
| 3548 | 7x |
size = c(1, 4), |
| 3549 | 7x |
follow = 7, |
| 3550 | 7x |
follow_min = 14 |
| 3551 |
) |
|
| 3552 |
} |
|
| 3553 | ||
| 3554 |
# SafetyWindowConst ---- |
|
| 3555 | ||
| 3556 |
## class ---- |
|
| 3557 | ||
| 3558 |
#' `SafetyWindowConst` |
|
| 3559 |
#' |
|
| 3560 |
#' @description `r lifecycle::badge("stable")`
|
|
| 3561 |
#' |
|
| 3562 |
#' [`SafetyWindowConst`] is the class for safety window length and it is used |
|
| 3563 |
#' when the `gap` should be kept constant across cohorts (though it may vary |
|
| 3564 |
#' within a cohort). |
|
| 3565 |
#' |
|
| 3566 |
#' @slot gap (`integer`)\cr a vector, the constant gap between patients. |
|
| 3567 |
#' @slot follow (`count`)\cr how long to follow each patient. The period of time |
|
| 3568 |
#' that each patient in the cohort needs to be followed before the next cohort |
|
| 3569 |
#' opens. |
|
| 3570 |
#' @slot follow_min (`count`)\cr minimum follow up. At least one patient in the |
|
| 3571 |
#' cohort needs to be followed at the minimal follow up time. |
|
| 3572 |
#' |
|
| 3573 |
#' @aliases SafetyWindowConst |
|
| 3574 |
#' @export |
|
| 3575 |
#' |
|
| 3576 |
.SafetyWindowConst <- setClass( |
|
| 3577 |
Class = "SafetyWindowConst", |
|
| 3578 |
slots = c( |
|
| 3579 |
gap = "integer", |
|
| 3580 |
follow = "integer", |
|
| 3581 |
follow_min = "integer" |
|
| 3582 |
), |
|
| 3583 |
prototype = prototype( |
|
| 3584 |
gap = 0L, |
|
| 3585 |
follow = 1L, |
|
| 3586 |
follow_min = 1L |
|
| 3587 |
), |
|
| 3588 |
contains = "SafetyWindow", |
|
| 3589 |
validity = v_safety_window_const |
|
| 3590 |
) |
|
| 3591 | ||
| 3592 |
## constructor ---- |
|
| 3593 | ||
| 3594 |
#' @rdname SafetyWindowConst-class |
|
| 3595 |
#' |
|
| 3596 |
#' @param gap see slot definition. |
|
| 3597 |
#' @param follow see slot definition. |
|
| 3598 |
#' @param follow_min see slot definition. |
|
| 3599 |
#' |
|
| 3600 |
#' @export |
|
| 3601 |
#' @example examples/Rules-class-SafetyWindowConst.R |
|
| 3602 |
#' |
|
| 3603 |
SafetyWindowConst <- function(gap, follow, follow_min) {
|
|
| 3604 | 28x |
assert_integerish(follow, lower = 0) |
| 3605 | 28x |
assert_integerish(follow_min, lower = 0) |
| 3606 | 28x |
assert_integerish(gap, lower = 0) |
| 3607 | ||
| 3608 | 28x |
if (follow > follow_min) {
|
| 3609 | 1x |
warning( |
| 3610 | 1x |
"The value of follow_min is typically larger than the value of follow" |
| 3611 |
) |
|
| 3612 |
} |
|
| 3613 | 28x |
.SafetyWindowConst( |
| 3614 | 28x |
gap = as.integer(gap), |
| 3615 | 28x |
follow = as.integer(follow), |
| 3616 | 28x |
follow_min = as.integer(follow_min) |
| 3617 |
) |
|
| 3618 |
} |
|
| 3619 | ||
| 3620 |
## default constructor ---- |
|
| 3621 | ||
| 3622 |
#' @rdname SafetyWindowConst-class |
|
| 3623 |
#' @note Typically, end users will not use the `.DefaultSafetyWindowConst()` function. |
|
| 3624 |
#' @export |
|
| 3625 |
.DefaultSafetyWindowConst <- function() {
|
|
| 3626 | 7x |
SafetyWindowConst( |
| 3627 | 7x |
gap = 7, |
| 3628 | 7x |
follow = 7, |
| 3629 | 7x |
follow_min = 14 |
| 3630 |
) |
|
| 3631 |
} |
| 1 |
#' @include Data-methods.R |
|
| 2 |
#' @include Design-class.R |
|
| 3 |
#' @include McmcOptions-class.R |
|
| 4 |
#' @include Rules-methods.R |
|
| 5 |
#' @include Simulations-class.R |
|
| 6 |
#' @include helpers.R |
|
| 7 |
#' @include mcmc.R |
|
| 8 |
NULL |
|
| 9 | ||
| 10 |
# simulate ---- |
|
| 11 | ||
| 12 |
## Design ---- |
|
| 13 | ||
| 14 |
#' Simulate outcomes from a CRM design |
|
| 15 |
#' |
|
| 16 |
#' @description `r lifecycle::badge("stable")`
|
|
| 17 |
#' |
|
| 18 |
#' @param object the [`Design`] object we want to simulate data from |
|
| 19 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 20 |
#' @param seed see [set_seed()] |
|
| 21 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 22 |
#' true probability (vector) for toxicity. Additional arguments can be supplied |
|
| 23 |
#' in `args`. |
|
| 24 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The |
|
| 25 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 26 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 27 |
#' simulations. In order to produce outcomes from the posterior predictive |
|
| 28 |
#' distribution, e.g, pass an `object` that contains the data observed so |
|
| 29 |
#' far, `truth` contains the `prob` function from the model in |
|
| 30 |
#' `object`, and `args` contains posterior samples from the model. |
|
| 31 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 32 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 33 |
#' in this patient. |
|
| 34 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 35 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 36 |
#' the standard options are used |
|
| 37 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 38 |
#' clusters of the computer? (not default) |
|
| 39 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 40 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 41 |
#' @param ... not used |
|
| 42 |
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the |
|
| 43 |
#' vector of posterior MTD samples. Each list element must therefore accept |
|
| 44 |
#' one and only one argument, which is a numeric vector, and return a number. |
|
| 45 |
#' |
|
| 46 |
#' @return an object of class [`Simulations`] |
|
| 47 |
#' |
|
| 48 |
#' @example examples/design-method-simulate-Design.R |
|
| 49 |
#' @export |
|
| 50 |
#' @importFrom parallel detectCores |
|
| 51 |
setMethod( |
|
| 52 |
f = "simulate", |
|
| 53 |
signature = signature( |
|
| 54 |
object = "Design", |
|
| 55 |
nsim = "ANY", |
|
| 56 |
seed = "ANY" |
|
| 57 |
), |
|
| 58 |
definition = function( |
|
| 59 |
object, |
|
| 60 |
nsim = 1L, |
|
| 61 |
seed = NULL, |
|
| 62 |
truth, |
|
| 63 |
args = NULL, |
|
| 64 |
firstSeparate = FALSE, |
|
| 65 |
mcmcOptions = McmcOptions(), |
|
| 66 |
parallel = FALSE, |
|
| 67 |
nCores = min(parallel::detectCores(), 5), |
|
| 68 |
derive = list(), |
|
| 69 |
... |
|
| 70 |
) {
|
|
| 71 | 13x |
nsim <- as.integer(nsim) |
| 72 | 13x |
assert_function(truth) |
| 73 | 13x |
assert_flag(firstSeparate) |
| 74 | 13x |
assert_count(nsim, positive = TRUE) |
| 75 | 13x |
assert_flag(parallel) |
| 76 | 13x |
assert_count(nCores, positive = TRUE) |
| 77 | ||
| 78 | 13x |
args <- as.data.frame(args) |
| 79 | 13x |
n_args <- max(nrow(args), 1L) |
| 80 | 13x |
rng_state <- set_seed(seed) |
| 81 | 13x |
sim_seeds <- sample.int(n = 2147483647, size = as.integer(nsim)) |
| 82 | ||
| 83 | 13x |
run_sim <- function(iter_sim) {
|
| 84 | 31x |
set.seed(sim_seeds[iter_sim]) |
| 85 | ||
| 86 | 31x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 87 | 31x |
data <- object@data |
| 88 | 31x |
prob_placebo <- NULL |
| 89 | 31x |
cohort_size_placebo <- NULL |
| 90 | ||
| 91 | 31x |
if (data@placebo) {
|
| 92 | 3x |
prob_placebo <- h_this_truth( |
| 93 | 3x |
object@data@doseGrid[1], |
| 94 | 3x |
current_args, |
| 95 | 3x |
truth |
| 96 |
) |
|
| 97 |
} |
|
| 98 | ||
| 99 | 31x |
should_stop <- FALSE |
| 100 | 31x |
dose <- object@startingDose |
| 101 | ||
| 102 | 31x |
while (!should_stop) {
|
| 103 | 114x |
prob <- h_this_truth(dose, current_args, truth) |
| 104 | 114x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 105 | ||
| 106 | 114x |
if (data@placebo) {
|
| 107 | 14x |
cohort_size_placebo <- size( |
| 108 | 14x |
object@pl_cohort_size, |
| 109 | 14x |
dose = dose, |
| 110 | 14x |
data = data |
| 111 |
) |
|
| 112 |
} else {
|
|
| 113 | 100x |
cohort_size_placebo <- NULL |
| 114 |
} |
|
| 115 | ||
| 116 | 114x |
data <- h_determine_dlts( |
| 117 | 114x |
data = data, |
| 118 | 114x |
dose = dose, |
| 119 | 114x |
prob = prob, |
| 120 | 114x |
prob_placebo = prob_placebo, |
| 121 | 114x |
cohort_size = cohort_size, |
| 122 | 114x |
cohort_size_placebo = cohort_size_placebo, |
| 123 | 114x |
dose_grid = object@data@doseGrid[1], |
| 124 | 114x |
first_separate = firstSeparate |
| 125 |
) |
|
| 126 | ||
| 127 | 114x |
dose_limit <- maxDose(object@increments, data = data) |
| 128 | 114x |
samples <- mcmc( |
| 129 | 114x |
data = data, |
| 130 | 114x |
model = object@model, |
| 131 | 114x |
options = mcmcOptions |
| 132 |
) |
|
| 133 | ||
| 134 | 114x |
dose <- nextBest( |
| 135 | 114x |
object@nextBest, |
| 136 | 114x |
doselimit = dose_limit, |
| 137 | 114x |
samples = samples, |
| 138 | 114x |
model = object@model, |
| 139 | 114x |
data = data |
| 140 | 114x |
)$value |
| 141 | ||
| 142 | 114x |
should_stop <- stopTrial( |
| 143 | 114x |
object@stopping, |
| 144 | 114x |
dose = dose, |
| 145 | 114x |
samples = samples, |
| 146 | 114x |
model = object@model, |
| 147 | 114x |
data = data |
| 148 |
) |
|
| 149 | 114x |
stopit_results <- h_unpack_stopit(should_stop) |
| 150 |
} |
|
| 151 | ||
| 152 | 31x |
fit_model <- fit(object = samples, model = object@model, data = data) |
| 153 | 31x |
target_dose_samples <- dose( |
| 154 | 31x |
mean(object@nextBest@target), |
| 155 | 31x |
model = object@model, |
| 156 | 31x |
samples = samples |
| 157 |
) |
|
| 158 | 31x |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
| 159 | ||
| 160 | 31x |
list( |
| 161 | 31x |
data = data, |
| 162 | 31x |
dose = dose, |
| 163 | 31x |
fit = subset(fit_model, select = c(middle, lower, upper)), |
| 164 | 31x |
stop = attr(should_stop, "message"), |
| 165 | 31x |
report_results = stopit_results, |
| 166 | 31x |
additional_stats = additional_stats |
| 167 |
) |
|
| 168 |
} |
|
| 169 | ||
| 170 | 13x |
result_list <- get_result_list( |
| 171 | 13x |
fun = run_sim, |
| 172 | 13x |
nsim = nsim, |
| 173 | 13x |
vars = c( |
| 174 | 13x |
"sim_seeds", |
| 175 | 13x |
"args", |
| 176 | 13x |
"n_args", |
| 177 | 13x |
"firstSeparate", |
| 178 | 13x |
"truth", |
| 179 | 13x |
"object", |
| 180 | 13x |
"mcmcOptions" |
| 181 |
), |
|
| 182 | 13x |
parallel = parallel, |
| 183 | 13x |
n_cores = nCores |
| 184 |
) |
|
| 185 | ||
| 186 | 13x |
simulations_output <- h_simulations_output_format(result_list) |
| 187 | ||
| 188 | 13x |
Simulations( |
| 189 | 13x |
data = simulations_output$dataList, |
| 190 | 13x |
doses = simulations_output$recommendedDoses, |
| 191 | 13x |
fit = simulations_output$fitList, |
| 192 | 13x |
stop_report = simulations_output$stop_matrix, |
| 193 | 13x |
stop_reasons = simulations_output$stopReasons, |
| 194 | 13x |
additional_stats = simulations_output$additional_stats, |
| 195 | 13x |
seed = rng_state |
| 196 |
) |
|
| 197 |
} |
|
| 198 |
) |
|
| 199 | ||
| 200 |
## RuleDesign ---- |
|
| 201 | ||
| 202 |
#' Simulate outcomes from a rule-based design |
|
| 203 |
#' |
|
| 204 |
#' @description `r lifecycle::badge("stable")`
|
|
| 205 |
#' |
|
| 206 |
#' @param object the [`RuleDesign`] object we want to simulate data from |
|
| 207 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 208 |
#' @param seed see [set_seed()] |
|
| 209 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 210 |
#' true probability (vector) for toxicity. Additional arguments can be supplied |
|
| 211 |
#' in `args`. |
|
| 212 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The |
|
| 213 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 214 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 215 |
#' simulations. |
|
| 216 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 217 |
#' clusters of the computer? (not default) |
|
| 218 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 219 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 220 |
#' @param ... not used |
|
| 221 |
#' |
|
| 222 |
#' @return an object of class [`GeneralSimulations`] |
|
| 223 |
#' |
|
| 224 |
#' @example examples/design-method-simulate-RuleDesign.R |
|
| 225 |
#' @export |
|
| 226 |
setMethod( |
|
| 227 |
f = "simulate", |
|
| 228 |
signature = signature( |
|
| 229 |
object = "RuleDesign", |
|
| 230 |
nsim = "ANY", |
|
| 231 |
seed = "ANY" |
|
| 232 |
), |
|
| 233 |
definition = function( |
|
| 234 |
object, |
|
| 235 |
nsim = 1L, |
|
| 236 |
seed = NULL, |
|
| 237 |
truth, |
|
| 238 |
args = NULL, |
|
| 239 |
parallel = FALSE, |
|
| 240 |
nCores = min(parallel::detectCores(), 5L), |
|
| 241 |
... |
|
| 242 |
) {
|
|
| 243 | 1x |
nsim <- as.integer(nsim) |
| 244 | 1x |
assert_function(truth) |
| 245 | 1x |
assert_count(nsim, positive = TRUE) |
| 246 | 1x |
assert_flag(parallel) |
| 247 | 1x |
assert_count(nCores, positive = TRUE) |
| 248 | 1x |
assert_class(object, "RuleDesign") |
| 249 | ||
| 250 | 1x |
args <- as.data.frame(args) |
| 251 | 1x |
n_args <- max(nrow(args), 1L) |
| 252 | 1x |
rng_state <- set_seed(seed) |
| 253 | 1x |
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
| 254 | ||
| 255 | 1x |
run_sim <- function(iter_sim) {
|
| 256 | 1x |
set.seed(sim_seeds[iter_sim]) |
| 257 | ||
| 258 | 1x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 259 | ||
| 260 | 1x |
truth_with_args <- function(dose) {
|
| 261 | 12x |
do.call(truth, c(dose, current_args)) |
| 262 |
} |
|
| 263 | ||
| 264 | 1x |
data <- object@data |
| 265 | 1x |
should_stop <- FALSE |
| 266 | 1x |
dose <- object@startingDose |
| 267 | ||
| 268 | 1x |
while (!should_stop) {
|
| 269 | 12x |
prob <- truth_with_args(dose) |
| 270 | 12x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 271 | ||
| 272 | 12x |
dlts <- rbinom(n = cohort_size, size = 1L, prob = prob) |
| 273 | 12x |
data <- update(object = data, x = dose, y = dlts) |
| 274 | ||
| 275 | 12x |
outcome <- nextBest(object@nextBest, data = data) |
| 276 | 12x |
dose <- outcome$value |
| 277 | 12x |
should_stop <- outcome$stopHere |
| 278 |
} |
|
| 279 | ||
| 280 | 1x |
list(data = data, dose = dose) |
| 281 |
} |
|
| 282 | ||
| 283 | 1x |
result_list <- get_result_list( |
| 284 | 1x |
fun = run_sim, |
| 285 | 1x |
nsim = nsim, |
| 286 | 1x |
vars = c( |
| 287 | 1x |
"sim_seeds", |
| 288 | 1x |
"args", |
| 289 | 1x |
"n_args", |
| 290 | 1x |
"truth", |
| 291 | 1x |
"object" |
| 292 |
), |
|
| 293 | 1x |
parallel = parallel, |
| 294 | 1x |
n_cores = nCores |
| 295 |
) |
|
| 296 | ||
| 297 | 1x |
data_list <- lapply(result_list, "[[", "data") |
| 298 | 1x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose")) |
| 299 | ||
| 300 | 1x |
GeneralSimulations( |
| 301 | 1x |
data = data_list, |
| 302 | 1x |
doses = recommended_doses, |
| 303 | 1x |
seed = rng_state |
| 304 |
) |
|
| 305 |
} |
|
| 306 |
) |
|
| 307 | ||
| 308 |
## DualDesign ---- |
|
| 309 | ||
| 310 |
#' Simulate outcomes from a dual-endpoint design |
|
| 311 |
#' |
|
| 312 |
#' @description `r lifecycle::badge("stable")`
|
|
| 313 |
#' |
|
| 314 |
#' @param object the [`DualDesign`] object we want to simulate data from |
|
| 315 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 316 |
#' @param seed see [set_seed()] |
|
| 317 |
#' @param trueTox (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 318 |
#' true probability (vector) for toxicity. Additional arguments can be supplied |
|
| 319 |
#' in `args`. |
|
| 320 |
#' @param trueBiomarker (`function`)\cr a function which takes as input a dose (vector) and |
|
| 321 |
#' returns the true biomarker level (vector). Additional arguments can be |
|
| 322 |
#' supplied in `args`. |
|
| 323 |
#' @param args (`data.frame`)\cr data frame with arguments for the `trueTox` and |
|
| 324 |
#' `trueBiomarker` function. The column names correspond to the argument |
|
| 325 |
#' names, the rows to the values of the arguments. The rows are appropriately |
|
| 326 |
#' recycled in the `nsim` simulations. |
|
| 327 |
#' @param sigma2W (`number`)\cr variance for the biomarker measurements |
|
| 328 |
#' @param rho (`number`)\cr correlation between toxicity and biomarker measurements (default: 0) |
|
| 329 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 330 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 331 |
#' in this patient. |
|
| 332 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 333 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 334 |
#' the standard options are used |
|
| 335 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 336 |
#' clusters of the computer? (not default) |
|
| 337 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 338 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 339 |
#' @param ... not used |
|
| 340 |
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the |
|
| 341 |
#' vector of posterior MTD samples. Each list element must therefore accept |
|
| 342 |
#' one and only one argument, which is a numeric vector, and return a number. |
|
| 343 |
#' |
|
| 344 |
#' @return an object of class [`DualSimulations`] |
|
| 345 |
#' |
|
| 346 |
#' @example examples/design-method-simulate-DualDesign.R |
|
| 347 |
#' @importFrom mvtnorm rmvnorm |
|
| 348 |
#' @export |
|
| 349 |
setMethod( |
|
| 350 |
f = "simulate", |
|
| 351 |
signature = signature(object = "DualDesign"), |
|
| 352 |
definition = function( |
|
| 353 |
object, |
|
| 354 |
nsim = 1L, |
|
| 355 |
seed = NULL, |
|
| 356 |
trueTox, |
|
| 357 |
trueBiomarker, |
|
| 358 |
args = NULL, |
|
| 359 |
sigma2W, |
|
| 360 |
rho = 0, |
|
| 361 |
firstSeparate = FALSE, |
|
| 362 |
mcmcOptions = McmcOptions(), |
|
| 363 |
parallel = FALSE, |
|
| 364 |
nCores = min(parallel::detectCores(), 5), |
|
| 365 |
derive = list(), |
|
| 366 |
... |
|
| 367 |
) {
|
|
| 368 | 6x |
nsim <- as.integer(nsim) |
| 369 | 6x |
assert_function(trueTox) |
| 370 | 6x |
assert_function(trueBiomarker) |
| 371 | 6x |
assert_number(sigma2W, lower = 0) |
| 372 | 6x |
assert_number(rho, lower = -1, upper = 1) |
| 373 | 6x |
assert_flag(firstSeparate) |
| 374 | 6x |
assert_count(nsim, positive = TRUE) |
| 375 | 6x |
assert_flag(parallel) |
| 376 | 6x |
assert_count(nCores, positive = TRUE) |
| 377 | 6x |
assert_class(object, "DualDesign") |
| 378 | 6x |
assert_list(derive) |
| 379 | ||
| 380 | 6x |
args <- as.data.frame(args) |
| 381 | 6x |
n_args <- max(nrow(args), 1L) |
| 382 | ||
| 383 | 6x |
tox_arg_names <- names(formals(trueTox))[-1] |
| 384 | 6x |
biomarker_arg_names <- names(formals(trueBiomarker))[-1] |
| 385 | ||
| 386 | 6x |
covariance_matrix <- matrix( |
| 387 | 6x |
c( |
| 388 | 6x |
sigma2W, |
| 389 | 6x |
sqrt(sigma2W) * rho, |
| 390 | 6x |
sqrt(sigma2W) * rho, |
| 391 | 6x |
1 |
| 392 |
), |
|
| 393 | 6x |
nrow = 2, |
| 394 | 6x |
byrow = TRUE |
| 395 |
) |
|
| 396 | ||
| 397 | 6x |
rng_state <- set_seed(seed) |
| 398 | 6x |
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
| 399 | ||
| 400 | 6x |
run_sim <- function(iter_sim) {
|
| 401 | 6x |
set.seed(sim_seeds[iter_sim]) |
| 402 | ||
| 403 | 6x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 404 | ||
| 405 | 6x |
tox_with_args <- function(dose) {
|
| 406 | 20x |
do.call( |
| 407 | 20x |
trueTox, |
| 408 | 20x |
c(dose, as.list(current_args)[tox_arg_names]) |
| 409 |
) |
|
| 410 |
} |
|
| 411 | ||
| 412 | 6x |
biomarker_with_args <- function(dose) {
|
| 413 | 20x |
do.call( |
| 414 | 20x |
trueBiomarker, |
| 415 | 20x |
c(dose, as.list(current_args)[biomarker_arg_names]) |
| 416 |
) |
|
| 417 |
} |
|
| 418 | ||
| 419 | 6x |
data <- object@data |
| 420 | 6x |
should_stop <- FALSE |
| 421 | 6x |
dose <- object@startingDose |
| 422 | ||
| 423 | 6x |
prob_placebo <- NULL |
| 424 | 6x |
mean_z_placebo <- NULL |
| 425 | 6x |
mean_biomarker_placebo <- NULL |
| 426 | ||
| 427 | 6x |
if (data@placebo) {
|
| 428 | 2x |
prob_placebo <- tox_with_args(object@data@doseGrid[1]) |
| 429 | 2x |
mean_z_placebo <- qlogis(prob_placebo) |
| 430 | 2x |
mean_biomarker_placebo <- biomarker_with_args(object@data@doseGrid[1]) |
| 431 |
} |
|
| 432 | ||
| 433 | 6x |
while (!should_stop) {
|
| 434 | 18x |
prob <- tox_with_args(dose) |
| 435 | 18x |
mean_z <- qlogis(prob) |
| 436 | 18x |
mean_biomarker <- biomarker_with_args(dose) |
| 437 | 18x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 438 | ||
| 439 | 18x |
cohort_size_placebo <- NULL |
| 440 | 18x |
if (data@placebo) {
|
| 441 | 2x |
cohort_size_placebo <- size( |
| 442 | 2x |
object@pl_cohort_size, |
| 443 | 2x |
dose = dose, |
| 444 | 2x |
data = data |
| 445 |
) |
|
| 446 |
} |
|
| 447 | ||
| 448 | 18x |
response_data <- if (firstSeparate && (cohort_size > 1L)) {
|
| 449 | 8x |
first_patient <- mvtnorm::rmvnorm( |
| 450 | 8x |
n = 1, |
| 451 | 8x |
mean = c(mean_biomarker, mean_z), |
| 452 | 8x |
sigma = covariance_matrix |
| 453 |
) |
|
| 454 | ||
| 455 | 8x |
first_patient_placebo <- NULL |
| 456 | 8x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 457 | ! |
first_patient_placebo <- mvtnorm::rmvnorm( |
| 458 | ! |
n = 1, |
| 459 | ! |
mean = c(mean_biomarker_placebo, mean_z_placebo), |
| 460 | ! |
sigma = covariance_matrix |
| 461 |
) |
|
| 462 |
} |
|
| 463 | ||
| 464 | 8x |
if (first_patient[, 2] < 0) {
|
| 465 | 8x |
remaining_patients <- mvtnorm::rmvnorm( |
| 466 | 8x |
n = cohort_size - 1, |
| 467 | 8x |
mean = c(mean_biomarker, mean_z), |
| 468 | 8x |
sigma = covariance_matrix |
| 469 |
) |
|
| 470 | 8x |
first_patient <- rbind(first_patient, remaining_patients) |
| 471 | ||
| 472 | 8x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 473 | ! |
remaining_patients_placebo <- mvtnorm::rmvnorm( |
| 474 | ! |
n = cohort_size_placebo, |
| 475 | ! |
mean = c(mean_biomarker_placebo, mean_z_placebo), |
| 476 | ! |
sigma = covariance_matrix |
| 477 |
) |
|
| 478 | ! |
first_patient_placebo <- rbind( |
| 479 | ! |
first_patient_placebo, |
| 480 | ! |
remaining_patients_placebo |
| 481 |
) |
|
| 482 |
} |
|
| 483 |
} |
|
| 484 | ||
| 485 | 8x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 486 | ! |
list(active = first_patient, placebo = first_patient_placebo) |
| 487 |
} else {
|
|
| 488 | 8x |
list(active = first_patient) |
| 489 |
} |
|
| 490 |
} else {
|
|
| 491 | 10x |
active_responses <- mvtnorm::rmvnorm( |
| 492 | 10x |
n = cohort_size, |
| 493 | 10x |
mean = c(mean_biomarker, mean_z), |
| 494 | 10x |
sigma = covariance_matrix |
| 495 |
) |
|
| 496 | ||
| 497 | 10x |
placebo_responses <- NULL |
| 498 | 10x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 499 | 2x |
placebo_responses <- mvtnorm::rmvnorm( |
| 500 | 2x |
n = cohort_size_placebo, |
| 501 | 2x |
mean = c(mean_biomarker_placebo, mean_z_placebo), |
| 502 | 2x |
sigma = covariance_matrix |
| 503 |
) |
|
| 504 |
} |
|
| 505 | ||
| 506 | 10x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 507 | 2x |
list(active = active_responses, placebo = placebo_responses) |
| 508 |
} else {
|
|
| 509 | 8x |
list(active = active_responses) |
| 510 |
} |
|
| 511 |
} |
|
| 512 | ||
| 513 | 18x |
biomarkers <- response_data$active[, 1] |
| 514 | 18x |
dlts <- as.integer(response_data$active[, 2] > 0) |
| 515 | ||
| 516 | 18x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 517 | 2x |
biomarkers_placebo <- response_data$placebo[, 1] |
| 518 | 2x |
dlts_placebo <- as.integer(response_data$placebo[, 2] > 0) |
| 519 | ||
| 520 | 2x |
data <- update( |
| 521 | 2x |
object = data, |
| 522 | 2x |
x = object@data@doseGrid[1], |
| 523 | 2x |
y = dlts_placebo, |
| 524 | 2x |
w = biomarkers_placebo, |
| 525 | 2x |
check = FALSE |
| 526 |
) |
|
| 527 | ||
| 528 | 2x |
data <- update( |
| 529 | 2x |
object = data, |
| 530 | 2x |
x = dose, |
| 531 | 2x |
y = dlts, |
| 532 | 2x |
w = biomarkers, |
| 533 | 2x |
new_cohort = FALSE |
| 534 |
) |
|
| 535 |
} else {
|
|
| 536 | 16x |
data <- update( |
| 537 | 16x |
object = data, |
| 538 | 16x |
x = dose, |
| 539 | 16x |
y = dlts, |
| 540 | 16x |
w = biomarkers |
| 541 |
) |
|
| 542 |
} |
|
| 543 | ||
| 544 | 18x |
dose_limit <- maxDose(object@increments, data = data) |
| 545 | 18x |
samples <- mcmc( |
| 546 | 18x |
data = data, |
| 547 | 18x |
model = object@model, |
| 548 | 18x |
options = mcmcOptions |
| 549 |
) |
|
| 550 | ||
| 551 | 18x |
dose <- nextBest( |
| 552 | 18x |
object@nextBest, |
| 553 | 18x |
doselimit = dose_limit, |
| 554 | 18x |
samples = samples, |
| 555 | 18x |
model = object@model, |
| 556 | 18x |
data = data |
| 557 | 18x |
)$value |
| 558 | ||
| 559 | 18x |
should_stop <- stopTrial( |
| 560 | 18x |
object@stopping, |
| 561 | 18x |
dose = dose, |
| 562 | 18x |
samples = samples, |
| 563 | 18x |
model = object@model, |
| 564 | 18x |
data = data |
| 565 |
) |
|
| 566 | 18x |
stopit_results <- h_unpack_stopit(should_stop) |
| 567 |
} |
|
| 568 | ||
| 569 | 6x |
fit_model <- fit(object = samples, model = object@model, data = data) |
| 570 | ||
| 571 | 6x |
target_dose_samples <- dose( |
| 572 | 6x |
mean(object@nextBest@target), |
| 573 | 6x |
model = object@model, |
| 574 | 6x |
samples = samples |
| 575 |
) |
|
| 576 | ||
| 577 | 6x |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
| 578 | ||
| 579 | 6x |
list( |
| 580 | 6x |
data = data, |
| 581 | 6x |
dose = dose, |
| 582 | 6x |
fitTox = subset(fit_model, select = c(middle, lower, upper)), |
| 583 | 6x |
fit_biomarker = subset( |
| 584 | 6x |
fit_model, |
| 585 | 6x |
select = c(middleBiomarker, lowerBiomarker, upperBiomarker) |
| 586 |
), |
|
| 587 | 6x |
rho_est = median(samples@data$rho), |
| 588 | 6x |
sigma2w_est = median(1 / samples@data$precW), |
| 589 | 6x |
stop = attr(should_stop, "message"), |
| 590 | 6x |
additional_stats = additional_stats, |
| 591 | 6x |
report_results = stopit_results |
| 592 |
) |
|
| 593 |
} |
|
| 594 | ||
| 595 | 6x |
result_list <- get_result_list( |
| 596 | 6x |
fun = run_sim, |
| 597 | 6x |
nsim = nsim, |
| 598 | 6x |
vars = c( |
| 599 | 6x |
"sim_seeds", |
| 600 | 6x |
"args", |
| 601 | 6x |
"n_args", |
| 602 | 6x |
"firstSeparate", |
| 603 | 6x |
"trueTox", |
| 604 | 6x |
"trueBiomarker", |
| 605 | 6x |
"covariance_matrix", |
| 606 | 6x |
"object", |
| 607 | 6x |
"mcmcOptions" |
| 608 |
), |
|
| 609 | 6x |
parallel = parallel, |
| 610 | 6x |
n_cores = nCores |
| 611 |
) |
|
| 612 | ||
| 613 | 6x |
data_list <- lapply(result_list, "[[", "data") |
| 614 | 6x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose")) |
| 615 | 6x |
rho_estimates <- as.numeric(sapply(result_list, "[[", "rho_est")) |
| 616 | 6x |
sigma2w_estimates <- as.numeric(sapply(result_list, "[[", "sigma2w_est")) |
| 617 | 6x |
fit_tox_list <- lapply(result_list, "[[", "fitTox") |
| 618 | 6x |
fit_biomarker_list <- lapply(result_list, "[[", "fit_biomarker") |
| 619 | 6x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 620 | 6x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 621 | 6x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 622 | 6x |
additional_stats <- lapply(result_list, "[[", "additional_stats") |
| 623 | ||
| 624 | 6x |
DualSimulations( |
| 625 | 6x |
data = data_list, |
| 626 | 6x |
doses = recommended_doses, |
| 627 | 6x |
rho_est = rho_estimates, |
| 628 | 6x |
sigma2w_est = sigma2w_estimates, |
| 629 | 6x |
fit = fit_tox_list, |
| 630 | 6x |
fit_biomarker = fit_biomarker_list, |
| 631 | 6x |
stop_report = stop_report, |
| 632 | 6x |
stop_reasons = stop_reasons, |
| 633 | 6x |
additional_stats = additional_stats, |
| 634 | 6x |
seed = rng_state |
| 635 |
) |
|
| 636 |
} |
|
| 637 |
) |
|
| 638 | ||
| 639 |
## TDsamplesDesign ---- |
|
| 640 | ||
| 641 |
#' Simulate dose escalation procedure using DLE responses only with DLE samples |
|
| 642 |
#' |
|
| 643 |
#' @description `r lifecycle::badge("stable")`
|
|
| 644 |
#' |
|
| 645 |
#' This is a method to simulate dose escalation procedure only using the DLE responses. |
|
| 646 |
#' This is a method based on the [`TDsamplesDesign`] where model used are of |
|
| 647 |
#' [`ModelTox`] class object DLE samples are also used. |
|
| 648 |
#' |
|
| 649 |
#' @param object the [`TDsamplesDesign`] object we want to simulate the data from |
|
| 650 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 651 |
#' @param seed see [set_seed()] |
|
| 652 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the true probability |
|
| 653 |
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`. |
|
| 654 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The |
|
| 655 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 656 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 657 |
#' simulations. In order to produce outcomes from the posterior predictive |
|
| 658 |
#' distribution, e.g, pass an `object` that contains the data observed so |
|
| 659 |
#' far, `truth` contains the `prob` function from the model in |
|
| 660 |
#' `object`, and `args` contains posterior samples from the model. |
|
| 661 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 662 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 663 |
#' in this patient. |
|
| 664 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 665 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 666 |
#' the standard options are used |
|
| 667 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 668 |
#' clusters of the computer? (not default) |
|
| 669 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 670 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 671 |
#' @param ... not used |
|
| 672 |
#' |
|
| 673 |
#' @return an object of class [`PseudoSimulations`] |
|
| 674 |
#' |
|
| 675 |
#' @example examples/design-method-simulateTDsamplesDesign.R |
|
| 676 |
#' @export |
|
| 677 |
setMethod( |
|
| 678 |
f = "simulate", |
|
| 679 |
signature = signature( |
|
| 680 |
object = "TDsamplesDesign", |
|
| 681 |
nsim = "ANY", |
|
| 682 |
seed = "ANY" |
|
| 683 |
), |
|
| 684 |
definition = function( |
|
| 685 |
object, |
|
| 686 |
nsim = 1L, |
|
| 687 |
seed = NULL, |
|
| 688 |
truth, |
|
| 689 |
args = NULL, |
|
| 690 |
firstSeparate = FALSE, |
|
| 691 |
mcmcOptions = McmcOptions(), |
|
| 692 |
parallel = FALSE, |
|
| 693 |
nCores = min(parallel::detectCores(), 5L), |
|
| 694 |
... |
|
| 695 |
) {
|
|
| 696 | 2x |
nsim <- as.integer(nsim) |
| 697 | 2x |
assert_function(truth) |
| 698 | 2x |
assert_flag(firstSeparate) |
| 699 | 2x |
assert_count(nsim, positive = TRUE) |
| 700 | 2x |
assert_flag(parallel) |
| 701 | 2x |
assert_count(nCores, positive = TRUE) |
| 702 | 2x |
assert_class(object, "TDsamplesDesign") |
| 703 | ||
| 704 | 2x |
args <- as.data.frame(args) |
| 705 | 2x |
n_args <- max(nrow(args), 1L) |
| 706 | 2x |
rng_state <- set_seed(seed) |
| 707 | ||
| 708 |
# Keep original seed generation for snapshot test compatibility |
|
| 709 | 2x |
sim_seeds <- sample(x = seq_len(1e5), size = nsim) |
| 710 | ||
| 711 | 2x |
run_sim <- function(iter_sim) {
|
| 712 | 2x |
set.seed(sim_seeds[iter_sim]) |
| 713 | ||
| 714 | 2x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 715 | ||
| 716 | 2x |
truth_with_args <- function(dose) {
|
| 717 | 22x |
do.call(truth, c(dose, current_args)) |
| 718 |
} |
|
| 719 | ||
| 720 | 2x |
data <- object@data |
| 721 | 2x |
prob_placebo <- NULL |
| 722 | ||
| 723 | 2x |
if (data@placebo) {
|
| 724 | 1x |
prob_placebo <- truth_with_args(object@data@doseGrid[1]) |
| 725 |
} |
|
| 726 | ||
| 727 | 2x |
should_stop <- FALSE |
| 728 | 2x |
dose <- object@startingDose |
| 729 | ||
| 730 | 2x |
while (!should_stop) {
|
| 731 | 21x |
prob <- truth_with_args(dose) |
| 732 | 21x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 733 | ||
| 734 | 21x |
cohort_size_placebo <- NULL |
| 735 | 21x |
if (data@placebo) {
|
| 736 | 9x |
cohort_size_placebo <- size( |
| 737 | 9x |
object@pl_cohort_size, |
| 738 | 9x |
dose = dose, |
| 739 | 9x |
data = data |
| 740 |
) |
|
| 741 |
} |
|
| 742 | ||
| 743 | 21x |
dlts <- if (firstSeparate && (cohort_size > 1L)) {
|
| 744 | ! |
first_dlt <- rbinom(n = 1L, size = 1L, prob = prob) |
| 745 | ||
| 746 | ! |
dlts_placebo_first <- NULL |
| 747 | ! |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 748 | ! |
dlts_placebo_first <- rbinom(n = 1L, size = 1L, prob = prob_placebo) |
| 749 |
} |
|
| 750 | ||
| 751 | ! |
if (first_dlt == 0) {
|
| 752 | ! |
remaining_dlts <- rbinom( |
| 753 | ! |
n = cohort_size - 1L, |
| 754 | ! |
size = 1L, |
| 755 | ! |
prob = prob |
| 756 |
) |
|
| 757 | ! |
c(first_dlt, remaining_dlts) |
| 758 |
} else {
|
|
| 759 | ! |
first_dlt |
| 760 |
} |
|
| 761 |
} else {
|
|
| 762 | 21x |
rbinom(n = cohort_size, size = 1L, prob = prob) |
| 763 |
} |
|
| 764 | ||
| 765 | 21x |
dlts_placebo <- NULL |
| 766 | 21x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 767 | 9x |
if (firstSeparate && (cohort_size > 1L) && length(dlts) == 1) {
|
| 768 | ! |
dlts_placebo <- dlts_placebo_first |
| 769 |
} else {
|
|
| 770 | 9x |
dlts_placebo <- if (firstSeparate && (cohort_size > 1L)) {
|
| 771 | ! |
c( |
| 772 | ! |
dlts_placebo_first, |
| 773 | ! |
rbinom( |
| 774 | ! |
n = cohort_size_placebo, |
| 775 | ! |
size = 1L, |
| 776 | ! |
prob = prob_placebo |
| 777 |
) |
|
| 778 |
) |
|
| 779 |
} else {
|
|
| 780 | 9x |
rbinom(n = cohort_size_placebo, size = 1L, prob = prob_placebo) |
| 781 |
} |
|
| 782 |
} |
|
| 783 |
} |
|
| 784 | ||
| 785 | 21x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 786 | 9x |
data <- update(object = data, x = dose, y = dlts) |
| 787 | 9x |
data <- update( |
| 788 | 9x |
object = data, |
| 789 | 9x |
x = object@data@doseGrid[1], |
| 790 | 9x |
y = dlts_placebo, |
| 791 | 9x |
new_cohort = FALSE |
| 792 |
) |
|
| 793 |
} else {
|
|
| 794 | 12x |
data <- update(object = data, x = dose, y = dlts) |
| 795 |
} |
|
| 796 | ||
| 797 | 21x |
model <- update(object@model, data = data) |
| 798 | 21x |
dose_limit <- maxDose(object@increments, data = data) |
| 799 | 21x |
samples <- mcmc(data = data, model = model, options = mcmcOptions) |
| 800 | ||
| 801 | 21x |
next_best_result <- nextBest( |
| 802 | 21x |
object@nextBest, |
| 803 | 21x |
doselimit = dose_limit, |
| 804 | 21x |
samples = samples, |
| 805 | 21x |
model = model, |
| 806 | 21x |
data = data, |
| 807 | 21x |
in_sim = TRUE |
| 808 |
) |
|
| 809 | ||
| 810 | 21x |
dose <- next_best_result$next_dose_drt |
| 811 | 21x |
td_target_during_trial <- next_best_result$dose_target_drt |
| 812 | 21x |
td_target_end_of_trial <- next_best_result$dose_target_eot |
| 813 | 21x |
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot |
| 814 | 21x |
ci_tdeot <- list( |
| 815 | 21x |
lower = next_best_result$ci_dose_target_eot[1], |
| 816 | 21x |
upper = next_best_result$ci_dose_target_eot[2] |
| 817 |
) |
|
| 818 | 21x |
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot |
| 819 | ||
| 820 | 21x |
should_stop <- stopTrial( |
| 821 | 21x |
object@stopping, |
| 822 | 21x |
dose = dose, |
| 823 | 21x |
samples = samples, |
| 824 | 21x |
model = model, |
| 825 | 21x |
data = data |
| 826 |
) |
|
| 827 | 21x |
stopit_results <- h_unpack_stopit(should_stop) |
| 828 |
} |
|
| 829 | ||
| 830 | 2x |
fit_model <- fit(object = samples, model = model, data = data) |
| 831 | ||
| 832 | 2x |
list( |
| 833 | 2x |
data = data, |
| 834 | 2x |
dose = dose, |
| 835 | 2x |
TDtargetDuringTrial = td_target_during_trial, |
| 836 | 2x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 837 | 2x |
TDtargetEndOfTrialatdoseGrid = td_target_end_of_trial_at_dose_grid, |
| 838 | 2x |
TDtargetDuringTrialatdoseGrid = dose, |
| 839 | 2x |
CITDEOT = ci_tdeot, |
| 840 | 2x |
ratioTDEOT = ratio_tdeot, |
| 841 | 2x |
fit = subset(fit_model, select = c(middle, lower, upper)), |
| 842 | 2x |
stop = attr(should_stop, "message"), |
| 843 | 2x |
report_results = stopit_results |
| 844 |
) |
|
| 845 |
} |
|
| 846 | ||
| 847 | 2x |
result_list <- get_result_list( |
| 848 | 2x |
fun = run_sim, |
| 849 | 2x |
nsim = nsim, |
| 850 | 2x |
vars = c( |
| 851 | 2x |
"sim_seeds", |
| 852 | 2x |
"args", |
| 853 | 2x |
"n_args", |
| 854 | 2x |
"firstSeparate", |
| 855 | 2x |
"truth", |
| 856 | 2x |
"object", |
| 857 | 2x |
"mcmcOptions" |
| 858 |
), |
|
| 859 | 2x |
parallel = parallel, |
| 860 | 2x |
n_cores = nCores |
| 861 |
) |
|
| 862 | ||
| 863 | 2x |
data_list <- lapply(result_list, "[[", "data") |
| 864 | ||
| 865 | 2x |
td_target_during_trial_list <- as.numeric(sapply( |
| 866 | 2x |
result_list, |
| 867 |
"[[", |
|
| 868 | 2x |
"TDtargetDuringTrial" |
| 869 |
)) |
|
| 870 | ||
| 871 | 2x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 872 | 2x |
result_list, |
| 873 |
"[[", |
|
| 874 | 2x |
"TDtargetEndOfTrial" |
| 875 |
)) |
|
| 876 | ||
| 877 | 2x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 878 | 2x |
result_list, |
| 879 |
"[[", |
|
| 880 | 2x |
"TDtargetDuringTrialatdoseGrid" |
| 881 |
)) |
|
| 882 | ||
| 883 | 2x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 884 | 2x |
result_list, |
| 885 |
"[[", |
|
| 886 | 2x |
"TDtargetEndOfTrialatdoseGrid" |
| 887 |
)) |
|
| 888 | ||
| 889 | 2x |
recommended_doses <- as.numeric(sapply( |
| 890 | 2x |
result_list, |
| 891 |
"[[", |
|
| 892 | 2x |
"TDtargetEndOfTrialatdoseGrid" |
| 893 |
)) |
|
| 894 | ||
| 895 | 2x |
ci_list <- lapply(result_list, "[[", "CITDEOT") |
| 896 | 2x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 897 | 2x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 898 | 2x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 899 | 2x |
fit_list <- lapply(result_list, "[[", "fit") |
| 900 | 2x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 901 | ||
| 902 | 2x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 903 | 2x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 904 | ||
| 905 | 2x |
PseudoSimulations( |
| 906 | 2x |
data = data_list, |
| 907 | 2x |
doses = recommended_doses, |
| 908 | 2x |
fit = fit_list, |
| 909 | 2x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 910 | 2x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 911 | 2x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 912 | 2x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 913 | 2x |
final_cis = ci_list, |
| 914 | 2x |
final_ratios = ratio_list, |
| 915 | 2x |
final_tdeot_cis = ci_tdeot_list, |
| 916 | 2x |
final_tdeot_ratios = ratio_tdeot_list, |
| 917 | 2x |
stop_reasons = stop_reasons, |
| 918 | 2x |
stop_report = stop_report, |
| 919 | 2x |
seed = rng_state |
| 920 |
) |
|
| 921 |
} |
|
| 922 |
) |
|
| 923 | ||
| 924 |
## TDDesign ---- |
|
| 925 | ||
| 926 |
#' Simulate dose escalation procedure using DLE responses only without samples |
|
| 927 |
#' |
|
| 928 |
#' @description `r lifecycle::badge("stable")`
|
|
| 929 |
#' |
|
| 930 |
#' This is a method to simulate dose escalation procedure only using the DLE responses. |
|
| 931 |
#' This is a method based on the [`TDDesign`] where model used are of |
|
| 932 |
#' [`ModelTox`] class object and no samples are involved. |
|
| 933 |
#' |
|
| 934 |
#' @param object the [`TDDesign`] object we want to simulate the data from |
|
| 935 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 936 |
#' @param seed see [set_seed()] |
|
| 937 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and returns the true probability |
|
| 938 |
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`. |
|
| 939 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truth` function. The |
|
| 940 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 941 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 942 |
#' simulations. In order to produce outcomes from the posterior predictive |
|
| 943 |
#' distribution, e.g, pass an `object` that contains the data observed so |
|
| 944 |
#' far, `truth` contains the `prob` function from the model in |
|
| 945 |
#' `object`, and `args` contains posterior samples from the model. |
|
| 946 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 947 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 948 |
#' in this patient. |
|
| 949 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 950 |
#' clusters of the computer? (not default) |
|
| 951 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 952 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 953 |
#' @param ... not used |
|
| 954 |
#' |
|
| 955 |
#' @return an object of class [`PseudoSimulations`] |
|
| 956 |
#' |
|
| 957 |
#' @example examples/design-method-simulateTDDesign.R |
|
| 958 |
#' @export |
|
| 959 |
setMethod( |
|
| 960 |
f = "simulate", |
|
| 961 |
signature = signature( |
|
| 962 |
object = "TDDesign", |
|
| 963 |
nsim = "ANY", |
|
| 964 |
seed = "ANY" |
|
| 965 |
), |
|
| 966 |
definition = function( |
|
| 967 |
object, |
|
| 968 |
nsim = 1L, |
|
| 969 |
seed = NULL, |
|
| 970 |
truth, |
|
| 971 |
args = NULL, |
|
| 972 |
firstSeparate = FALSE, |
|
| 973 |
parallel = FALSE, |
|
| 974 |
nCores = min(parallel::detectCores(), 5L), |
|
| 975 |
... |
|
| 976 |
) {
|
|
| 977 | 3x |
nsim <- as.integer(nsim) |
| 978 | 3x |
assert_function(truth) |
| 979 | 3x |
assert_flag(firstSeparate) |
| 980 | 3x |
assert_count(nsim, positive = TRUE) |
| 981 | 3x |
assert_flag(parallel) |
| 982 | 3x |
assert_count(nCores, positive = TRUE) |
| 983 | 3x |
assert_class(object, "TDDesign") |
| 984 | ||
| 985 | 3x |
args <- as.data.frame(args) |
| 986 | 3x |
n_args <- max(nrow(args), 1L) |
| 987 | 3x |
rng_state <- set_seed(seed) |
| 988 | ||
| 989 |
# Keep original seed generation for snapshot test compatibility |
|
| 990 | 3x |
sim_seeds <- sample(x = seq_len(1e5), size = nsim) |
| 991 | ||
| 992 | 3x |
run_sim <- function(iter_sim) {
|
| 993 | 4x |
set.seed(sim_seeds[iter_sim]) |
| 994 | ||
| 995 | 4x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 996 | ||
| 997 | 4x |
truth_with_args <- function(dose) {
|
| 998 | 28x |
do.call(truth, c(dose, current_args)) |
| 999 |
} |
|
| 1000 | ||
| 1001 | 4x |
data <- object@data |
| 1002 | 4x |
prob_placebo <- NULL |
| 1003 | ||
| 1004 | 4x |
if (data@placebo) {
|
| 1005 | 1x |
prob_placebo <- truth_with_args(object@data@doseGrid[1]) |
| 1006 |
} |
|
| 1007 | ||
| 1008 | 4x |
should_stop <- FALSE |
| 1009 | 4x |
dose <- object@startingDose |
| 1010 | ||
| 1011 | 4x |
while (!should_stop) {
|
| 1012 | 27x |
prob <- truth_with_args(dose) |
| 1013 | 27x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 1014 | ||
| 1015 | 27x |
cohort_size_placebo <- NULL |
| 1016 | 27x |
if (data@placebo) {
|
| 1017 | 11x |
cohort_size_placebo <- size( |
| 1018 | 11x |
object@pl_cohort_size, |
| 1019 | 11x |
dose = dose, |
| 1020 | 11x |
data = data |
| 1021 |
) |
|
| 1022 |
} |
|
| 1023 | ||
| 1024 | 27x |
dlts <- if (firstSeparate && (cohort_size > 1L)) {
|
| 1025 | 11x |
first_dlt <- rbinom(n = 1L, size = 1L, prob = prob) |
| 1026 | ||
| 1027 | 11x |
dlts_placebo_first <- NULL |
| 1028 | 11x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1029 | 11x |
dlts_placebo_first <- rbinom(n = 1L, size = 1L, prob = prob_placebo) |
| 1030 |
} |
|
| 1031 | ||
| 1032 | 11x |
if (first_dlt == 0) {
|
| 1033 | 7x |
remaining_dlts <- rbinom( |
| 1034 | 7x |
n = cohort_size - 1L, |
| 1035 | 7x |
size = 1L, |
| 1036 | 7x |
prob = prob |
| 1037 |
) |
|
| 1038 | 7x |
c(first_dlt, remaining_dlts) |
| 1039 |
} else {
|
|
| 1040 | 4x |
first_dlt |
| 1041 |
} |
|
| 1042 |
} else {
|
|
| 1043 | 16x |
rbinom(n = cohort_size, size = 1L, prob = prob) |
| 1044 |
} |
|
| 1045 | ||
| 1046 | 27x |
dlts_placebo <- NULL |
| 1047 | 27x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1048 | 11x |
if (firstSeparate && (cohort_size > 1L) && length(dlts) == 1) {
|
| 1049 | 4x |
dlts_placebo <- dlts_placebo_first |
| 1050 |
} else {
|
|
| 1051 | 7x |
dlts_placebo <- rbinom( |
| 1052 | 7x |
n = cohort_size_placebo, |
| 1053 | 7x |
size = 1L, |
| 1054 | 7x |
prob = prob_placebo |
| 1055 |
) |
|
| 1056 |
} |
|
| 1057 |
} |
|
| 1058 | ||
| 1059 | 27x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1060 | 11x |
data <- update( |
| 1061 | 11x |
object = data, |
| 1062 | 11x |
x = object@data@doseGrid[1], |
| 1063 | 11x |
y = dlts_placebo |
| 1064 |
) |
|
| 1065 | 11x |
data <- update( |
| 1066 | 11x |
object = data, |
| 1067 | 11x |
x = dose, |
| 1068 | 11x |
y = dlts, |
| 1069 | 11x |
new_cohort = FALSE |
| 1070 |
) |
|
| 1071 |
} else {
|
|
| 1072 | 16x |
data <- update(object = data, x = dose, y = dlts) |
| 1073 |
} |
|
| 1074 | ||
| 1075 | 27x |
model <- update(object@model, data = data) |
| 1076 | 27x |
dose_limit <- maxDose(object@increments, data = data) |
| 1077 | ||
| 1078 | 27x |
next_best_result <- nextBest( |
| 1079 | 27x |
object@nextBest, |
| 1080 | 27x |
doselimit = dose_limit, |
| 1081 | 27x |
model = model, |
| 1082 | 27x |
data = data, |
| 1083 | 27x |
in_sim = TRUE |
| 1084 |
) |
|
| 1085 | ||
| 1086 | 27x |
dose <- next_best_result$next_dose_drt |
| 1087 | 27x |
td_target_during_trial <- next_best_result$dose_target_drt |
| 1088 | 27x |
td_target_end_of_trial <- next_best_result$dose_target_eot |
| 1089 | 27x |
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot |
| 1090 | 27x |
ci_tdeot <- list( |
| 1091 | 27x |
lower = next_best_result$ci_dose_target_eot[1], |
| 1092 | 27x |
upper = next_best_result$ci_dose_target_eot[2] |
| 1093 |
) |
|
| 1094 | 27x |
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot |
| 1095 | ||
| 1096 | 27x |
should_stop <- stopTrial( |
| 1097 | 27x |
object@stopping, |
| 1098 | 27x |
dose = dose, |
| 1099 | 27x |
model = model, |
| 1100 | 27x |
data = data |
| 1101 |
) |
|
| 1102 | 27x |
stopit_results <- h_unpack_stopit(should_stop) |
| 1103 |
} |
|
| 1104 | ||
| 1105 | 4x |
prob_fun <- probFunction( |
| 1106 | 4x |
model, |
| 1107 | 4x |
phi1 = model@phi1, |
| 1108 | 4x |
phi2 = model@phi2 |
| 1109 |
) |
|
| 1110 | 4x |
fit_model <- list( |
| 1111 | 4x |
phi1 = model@phi1, |
| 1112 | 4x |
phi2 = model@phi2, |
| 1113 | 4x |
probDLE = prob_fun(object@data@doseGrid) |
| 1114 |
) |
|
| 1115 | ||
| 1116 | 4x |
list( |
| 1117 | 4x |
data = data, |
| 1118 | 4x |
dose = dose, |
| 1119 | 4x |
TDtargetDuringTrial = td_target_during_trial, |
| 1120 | 4x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 1121 | 4x |
TDtargetEndOfTrialatdoseGrid = td_target_end_of_trial_at_dose_grid, |
| 1122 | 4x |
TDtargetDuringTrialatdoseGrid = dose, |
| 1123 | 4x |
CITDEOT = ci_tdeot, |
| 1124 | 4x |
ratioTDEOT = ratio_tdeot, |
| 1125 | 4x |
fit = fit_model, |
| 1126 | 4x |
stop = attr(should_stop, "message"), |
| 1127 | 4x |
report_results = stopit_results |
| 1128 |
) |
|
| 1129 |
} |
|
| 1130 | ||
| 1131 | 3x |
result_list <- get_result_list( |
| 1132 | 3x |
fun = run_sim, |
| 1133 | 3x |
nsim = nsim, |
| 1134 | 3x |
vars = c( |
| 1135 | 3x |
"sim_seeds", |
| 1136 | 3x |
"args", |
| 1137 | 3x |
"n_args", |
| 1138 | 3x |
"firstSeparate", |
| 1139 | 3x |
"truth", |
| 1140 | 3x |
"object" |
| 1141 |
), |
|
| 1142 | 3x |
parallel = parallel, |
| 1143 | 3x |
n_cores = nCores |
| 1144 |
) |
|
| 1145 | ||
| 1146 | 3x |
data_list <- lapply(result_list, "[[", "data") |
| 1147 | ||
| 1148 | 3x |
td_target_during_trial_list <- as.numeric(sapply( |
| 1149 | 3x |
result_list, |
| 1150 |
"[[", |
|
| 1151 | 3x |
"TDtargetDuringTrial" |
| 1152 |
)) |
|
| 1153 | ||
| 1154 | 3x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 1155 | 3x |
result_list, |
| 1156 |
"[[", |
|
| 1157 | 3x |
"TDtargetEndOfTrial" |
| 1158 |
)) |
|
| 1159 | ||
| 1160 | 3x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 1161 | 3x |
result_list, |
| 1162 |
"[[", |
|
| 1163 | 3x |
"TDtargetDuringTrialatdoseGrid" |
| 1164 |
)) |
|
| 1165 | ||
| 1166 | 3x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 1167 | 3x |
result_list, |
| 1168 |
"[[", |
|
| 1169 | 3x |
"TDtargetEndOfTrialatdoseGrid" |
| 1170 |
)) |
|
| 1171 | ||
| 1172 | 3x |
recommended_doses <- as.numeric(sapply( |
| 1173 | 3x |
result_list, |
| 1174 |
"[[", |
|
| 1175 | 3x |
"TDtargetEndOfTrialatdoseGrid" |
| 1176 |
)) |
|
| 1177 | ||
| 1178 | 3x |
ci_list <- lapply(result_list, "[[", "CITDEOT") |
| 1179 | 3x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 1180 | 3x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 1181 | 3x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 1182 | 3x |
fit_list <- lapply(result_list, "[[", "fit") |
| 1183 | 3x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 1184 | ||
| 1185 | 3x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 1186 | 3x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 1187 | ||
| 1188 | 3x |
PseudoSimulations( |
| 1189 | 3x |
data = data_list, |
| 1190 | 3x |
doses = recommended_doses, |
| 1191 | 3x |
fit = fit_list, |
| 1192 | 3x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 1193 | 3x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 1194 | 3x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 1195 | 3x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 1196 | 3x |
final_cis = ci_list, |
| 1197 | 3x |
final_ratios = ratio_list, |
| 1198 | 3x |
final_tdeot_cis = ci_tdeot_list, |
| 1199 | 3x |
final_tdeot_ratios = ratio_tdeot_list, |
| 1200 | 3x |
stop_reasons = stop_reasons, |
| 1201 | 3x |
stop_report = stop_report, |
| 1202 | 3x |
seed = rng_state |
| 1203 |
) |
|
| 1204 |
} |
|
| 1205 |
) |
|
| 1206 | ||
| 1207 |
## DualResponsesDesign ---- |
|
| 1208 | ||
| 1209 |
#' Simulate dose escalation procedure using both DLE and efficacy responses without samples |
|
| 1210 |
#' |
|
| 1211 |
#' @description `r lifecycle::badge("stable")`
|
|
| 1212 |
#' |
|
| 1213 |
#' This is a method to simulate dose escalation procedure using both DLE and efficacy responses. |
|
| 1214 |
#' This is a method based on the [`DualResponsesDesign`] where DLE model used are of |
|
| 1215 |
#' [`ModelTox`] class object and efficacy model used are of [`ModelEff`] |
|
| 1216 |
#' class object. In addition, no DLE and efficacy samples are involved or generated in the simulation |
|
| 1217 |
#' process. |
|
| 1218 |
#' |
|
| 1219 |
#' @param object the [`DualResponsesDesign`] object we want to simulate the data from |
|
| 1220 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 1221 |
#' @param seed see [set_seed()] |
|
| 1222 |
#' @param trueDLE (`function`)\cr a function which takes as input a dose (vector) and returns the true probability |
|
| 1223 |
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`. |
|
| 1224 |
#' @param trueEff (`function`)\cr a function which takes as input a dose (vector) and returns the expected efficacy |
|
| 1225 |
#' responses (vector). Additional arguments can be supplied in `args`. |
|
| 1226 |
#' @param trueNu (`number`)\cr the precision, the inverse of the variance of the efficacy responses |
|
| 1227 |
#' @param args (`data.frame`)\cr data frame with arguments for the `trueDLE` and |
|
| 1228 |
#' `trueEff` function. The column names correspond to the argument |
|
| 1229 |
#' names, the rows to the values of the arguments. The rows are appropriately |
|
| 1230 |
#' recycled in the `nsim` simulations. |
|
| 1231 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 1232 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 1233 |
#' in this patient. |
|
| 1234 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 1235 |
#' clusters of the computer? (not default) |
|
| 1236 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 1237 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 1238 |
#' @param ... not used |
|
| 1239 |
#' |
|
| 1240 |
#' @return an object of class [`PseudoDualSimulations`] |
|
| 1241 |
#' |
|
| 1242 |
#' @example examples/design-method-simulateDualResponsesDesign.R |
|
| 1243 |
#' @export |
|
| 1244 |
setMethod( |
|
| 1245 |
f = "simulate", |
|
| 1246 |
signature = signature( |
|
| 1247 |
object = "DualResponsesDesign", |
|
| 1248 |
nsim = "ANY", |
|
| 1249 |
seed = "ANY" |
|
| 1250 |
), |
|
| 1251 |
definition = function( |
|
| 1252 |
object, |
|
| 1253 |
nsim = 1L, |
|
| 1254 |
seed = NULL, |
|
| 1255 |
trueDLE, |
|
| 1256 |
trueEff, |
|
| 1257 |
trueNu, |
|
| 1258 |
args = NULL, |
|
| 1259 |
firstSeparate = FALSE, |
|
| 1260 |
parallel = FALSE, |
|
| 1261 |
nCores = min(parallel::detectCores(), 5L), |
|
| 1262 |
... |
|
| 1263 |
) {
|
|
| 1264 | 2x |
nsim <- as.integer(nsim) |
| 1265 | 2x |
assert_function(trueDLE) |
| 1266 | 2x |
assert_function(trueEff) |
| 1267 | 2x |
assert_true(trueNu > 0) |
| 1268 | 2x |
assert_flag(firstSeparate) |
| 1269 | 2x |
assert_count(nsim, positive = TRUE) |
| 1270 | 2x |
assert_flag(parallel) |
| 1271 | 2x |
assert_count(nCores, positive = TRUE) |
| 1272 | 2x |
assert_class(object, "DualResponsesDesign") |
| 1273 | ||
| 1274 | 2x |
args <- as.data.frame(args) |
| 1275 | 2x |
n_args <- max(nrow(args), 1L) |
| 1276 | ||
| 1277 | 2x |
dle_arg_names <- names(formals(trueDLE))[-1] |
| 1278 | 2x |
eff_arg_names <- names(formals(trueEff))[-1] |
| 1279 | ||
| 1280 | 2x |
rng_state <- set_seed(seed) |
| 1281 | ||
| 1282 |
# Keep original seed generation for snapshot test compatibility |
|
| 1283 | 2x |
sim_seeds <- sample(x = seq_len(1e5), size = nsim) |
| 1284 | ||
| 1285 | 2x |
run_sim <- function(iter_sim) {
|
| 1286 | 2x |
set.seed(sim_seeds[iter_sim]) |
| 1287 | ||
| 1288 | 2x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 1289 | ||
| 1290 | 2x |
dle_with_args <- function(dose) {
|
| 1291 | 22x |
do.call( |
| 1292 | 22x |
trueDLE, |
| 1293 | 22x |
c(dose, as.list(current_args)[dle_arg_names]) |
| 1294 |
) |
|
| 1295 |
} |
|
| 1296 | ||
| 1297 | 2x |
eff_with_args <- function(dose) {
|
| 1298 | 22x |
do.call( |
| 1299 | 22x |
trueEff, |
| 1300 | 22x |
c(dose, as.list(current_args)[eff_arg_names]) |
| 1301 |
) |
|
| 1302 |
} |
|
| 1303 | ||
| 1304 | 2x |
data <- object@data |
| 1305 | 2x |
sigma2 <- 1 / trueNu |
| 1306 | 2x |
prob_placebo <- NULL |
| 1307 | 2x |
mean_eff_placebo <- NULL |
| 1308 | ||
| 1309 | 2x |
if (data@placebo) {
|
| 1310 | 1x |
prob_placebo <- dle_with_args(object@data@doseGrid[1]) |
| 1311 | 1x |
mean_eff_placebo <- eff_with_args(object@data@doseGrid[1]) |
| 1312 |
} |
|
| 1313 | ||
| 1314 | 2x |
should_stop <- FALSE |
| 1315 | 2x |
dose <- object@startingDose |
| 1316 | ||
| 1317 | 2x |
while (!should_stop) {
|
| 1318 | 21x |
prob <- dle_with_args(dose) |
| 1319 | 21x |
mean_eff <- eff_with_args(dose) |
| 1320 | 21x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 1321 | ||
| 1322 | 21x |
cohort_size_placebo <- NULL |
| 1323 | 21x |
if (data@placebo) {
|
| 1324 | 9x |
cohort_size_placebo <- size( |
| 1325 | 9x |
object@pl_cohort_size, |
| 1326 | 9x |
dose = dose, |
| 1327 | 9x |
data = data |
| 1328 |
) |
|
| 1329 |
} |
|
| 1330 | ||
| 1331 | 21x |
if (firstSeparate && (cohort_size > 1L)) {
|
| 1332 | 9x |
dlts <- rbinom(n = 1L, size = 1L, prob = prob) |
| 1333 | 9x |
eff_responses <- rnorm(n = 1L, mean = mean_eff, sd = sqrt(sigma2)) |
| 1334 | ||
| 1335 | 9x |
dlts_placebo <- NULL |
| 1336 | 9x |
eff_responses_placebo <- NULL |
| 1337 | 9x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1338 | 9x |
dlts_placebo <- rbinom(n = 1L, size = 1L, prob = prob_placebo) |
| 1339 | 9x |
eff_responses_placebo <- rnorm( |
| 1340 | 9x |
n = 1L, |
| 1341 | 9x |
mean = mean_eff_placebo, |
| 1342 | 9x |
sd = sqrt(sigma2) |
| 1343 |
) |
|
| 1344 |
} |
|
| 1345 | ||
| 1346 | 9x |
if (dlts == 0) {
|
| 1347 | 7x |
remaining_dlts <- rbinom( |
| 1348 | 7x |
n = cohort_size - 1L, |
| 1349 | 7x |
size = 1L, |
| 1350 | 7x |
prob = prob |
| 1351 |
) |
|
| 1352 | 7x |
remaining_eff <- rnorm( |
| 1353 | 7x |
n = cohort_size - 1L, |
| 1354 | 7x |
mean = mean_eff, |
| 1355 | 7x |
sd = sqrt(sigma2) |
| 1356 |
) |
|
| 1357 | 7x |
dlts <- c(dlts, remaining_dlts) |
| 1358 | 7x |
eff_responses <- c(eff_responses, remaining_eff) |
| 1359 | ||
| 1360 | 7x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1361 | 7x |
remaining_dlts_placebo <- rbinom( |
| 1362 | 7x |
n = cohort_size_placebo, |
| 1363 | 7x |
size = 1L, |
| 1364 | 7x |
prob = prob_placebo |
| 1365 |
) |
|
| 1366 | 7x |
remaining_eff_placebo <- rnorm( |
| 1367 | 7x |
n = cohort_size_placebo, |
| 1368 | 7x |
mean = mean_eff_placebo, |
| 1369 | 7x |
sd = sqrt(sigma2) |
| 1370 |
) |
|
| 1371 | 7x |
dlts_placebo <- c(dlts_placebo, remaining_dlts_placebo) |
| 1372 | 7x |
eff_responses_placebo <- c( |
| 1373 | 7x |
eff_responses_placebo, |
| 1374 | 7x |
remaining_eff_placebo |
| 1375 |
) |
|
| 1376 |
} |
|
| 1377 |
} |
|
| 1378 |
} else {
|
|
| 1379 | 12x |
dlts <- rbinom(n = cohort_size, size = 1L, prob = prob) |
| 1380 | 12x |
eff_responses <- rnorm( |
| 1381 | 12x |
n = cohort_size, |
| 1382 | 12x |
mean = mean_eff, |
| 1383 | 12x |
sd = sqrt(sigma2) |
| 1384 |
) |
|
| 1385 | ||
| 1386 | 12x |
dlts_placebo <- NULL |
| 1387 | 12x |
eff_responses_placebo <- NULL |
| 1388 | 12x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1389 | ! |
dlts_placebo <- rbinom( |
| 1390 | ! |
n = cohort_size_placebo, |
| 1391 | ! |
size = 1L, |
| 1392 | ! |
prob = prob_placebo |
| 1393 |
) |
|
| 1394 | ! |
eff_responses_placebo <- rnorm( |
| 1395 | ! |
n = cohort_size_placebo, |
| 1396 | ! |
mean = mean_eff_placebo, |
| 1397 | ! |
sd = sqrt(sigma2) |
| 1398 |
) |
|
| 1399 |
} |
|
| 1400 |
} |
|
| 1401 | ||
| 1402 | 21x |
if (data@placebo && (cohort_size_placebo > 0L)) {
|
| 1403 | 9x |
data <- update( |
| 1404 | 9x |
object = data, |
| 1405 | 9x |
x = object@data@doseGrid[1], |
| 1406 | 9x |
y = dlts_placebo, |
| 1407 | 9x |
w = eff_responses_placebo, |
| 1408 | 9x |
check = FALSE |
| 1409 |
) |
|
| 1410 | 9x |
data <- update( |
| 1411 | 9x |
object = data, |
| 1412 | 9x |
x = dose, |
| 1413 | 9x |
y = dlts, |
| 1414 | 9x |
w = eff_responses, |
| 1415 | 9x |
new_cohort = FALSE |
| 1416 |
) |
|
| 1417 |
} else {
|
|
| 1418 | 12x |
data <- update(object = data, x = dose, y = dlts, w = eff_responses) |
| 1419 |
} |
|
| 1420 | ||
| 1421 | 21x |
dle_model <- update(object = object@model, data = data) |
| 1422 | 21x |
eff_model <- update(object = object@eff_model, data = data) |
| 1423 | ||
| 1424 | 21x |
eff_nu <- eff_model@nu |
| 1425 | 21x |
eff_sigma2 <- if (eff_model@use_fixed) {
|
| 1426 | ! |
1 / eff_nu |
| 1427 |
} else {
|
|
| 1428 | 21x |
1 / (as.numeric(eff_nu["a"] / eff_nu["b"])) |
| 1429 |
} |
|
| 1430 | ||
| 1431 | 21x |
dose_limit <- maxDose(object@increments, data = data) |
| 1432 | ||
| 1433 | 21x |
next_best_result <- nextBest( |
| 1434 | 21x |
object@nextBest, |
| 1435 | 21x |
doselimit = dose_limit, |
| 1436 | 21x |
model = dle_model, |
| 1437 | 21x |
data = data, |
| 1438 | 21x |
model_eff = eff_model, |
| 1439 | 21x |
in_sim = TRUE |
| 1440 |
) |
|
| 1441 | ||
| 1442 | 21x |
dose <- next_best_result$next_dose |
| 1443 | 21x |
td_target_during_trial <- next_best_result$dose_target_drt |
| 1444 | 21x |
td_target_during_trial_at_dose_grid <- next_best_result$next_dose_drt |
| 1445 | 21x |
td_target_end_of_trial <- next_best_result$dose_target_eot |
| 1446 | 21x |
td_target_end_of_trial_at_dose_grid <- next_best_result$next_dose_eot |
| 1447 | 21x |
gstar <- next_best_result$dose_max_gain |
| 1448 | 21x |
gstar_at_dose_grid <- next_best_result$next_dose_max_gain |
| 1449 | ||
| 1450 | 21x |
recommend <- min( |
| 1451 | 21x |
td_target_end_of_trial_at_dose_grid, |
| 1452 | 21x |
gstar_at_dose_grid |
| 1453 |
) |
|
| 1454 | ||
| 1455 | 21x |
ci_tdeot <- list( |
| 1456 | 21x |
lower = next_best_result$ci_dose_target_eot[1], |
| 1457 | 21x |
upper = next_best_result$ci_dose_target_eot[2] |
| 1458 |
) |
|
| 1459 | 21x |
ratio_tdeot <- next_best_result$ci_ratio_dose_target_eot |
| 1460 | ||
| 1461 | 21x |
ci_gstar <- list( |
| 1462 | 21x |
lower = next_best_result$ci_dose_max_gain[1], |
| 1463 | 21x |
upper = next_best_result$ci_dose_max_gain[2] |
| 1464 |
) |
|
| 1465 | 21x |
ratio_gstar <- next_best_result$ci_ratio_dose_max_gain |
| 1466 | ||
| 1467 | 21x |
optimal_dose <- min(gstar, td_target_end_of_trial) |
| 1468 | ||
| 1469 | 21x |
if (optimal_dose == gstar) {
|
| 1470 | ! |
ratio <- ratio_gstar |
| 1471 | ! |
ci <- ci_gstar |
| 1472 |
} else {
|
|
| 1473 | 21x |
ratio <- ratio_tdeot |
| 1474 | 21x |
ci <- ci_tdeot |
| 1475 |
} |
|
| 1476 | ||
| 1477 | 21x |
should_stop <- stopTrial( |
| 1478 | 21x |
object@stopping, |
| 1479 | 21x |
dose = dose, |
| 1480 | 21x |
model = dle_model, |
| 1481 | 21x |
data = data, |
| 1482 | 21x |
Effmodel = eff_model |
| 1483 |
) |
|
| 1484 | 21x |
stopit_results <- h_unpack_stopit(should_stop) |
| 1485 |
} |
|
| 1486 | ||
| 1487 | 2x |
prob_fun <- probFunction( |
| 1488 | 2x |
dle_model, |
| 1489 | 2x |
phi1 = dle_model@phi1, |
| 1490 | 2x |
phi2 = dle_model@phi2 |
| 1491 |
) |
|
| 1492 | 2x |
dle_fit <- list( |
| 1493 | 2x |
phi1 = dle_model@phi1, |
| 1494 | 2x |
phi2 = dle_model@phi2, |
| 1495 | 2x |
probDLE = prob_fun(object@data@doseGrid) |
| 1496 |
) |
|
| 1497 | ||
| 1498 | 2x |
eff_fun <- efficacyFunction( |
| 1499 | 2x |
eff_model, |
| 1500 | 2x |
theta1 = eff_model@theta1, |
| 1501 | 2x |
theta2 = eff_model@theta2 |
| 1502 |
) |
|
| 1503 | 2x |
eff_fit <- list( |
| 1504 | 2x |
theta1 = eff_model@theta1, |
| 1505 | 2x |
theta2 = eff_model@theta2, |
| 1506 | 2x |
ExpEff = eff_fun(object@data@doseGrid) |
| 1507 |
) |
|
| 1508 | ||
| 1509 | 2x |
list( |
| 1510 | 2x |
data = data, |
| 1511 | 2x |
dose = dose, |
| 1512 | 2x |
TDtargetDuringTrial = td_target_during_trial, |
| 1513 | 2x |
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid, |
| 1514 | 2x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 1515 | 2x |
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid, |
| 1516 | 2x |
Gstar = gstar, |
| 1517 | 2x |
GstarAtDoseGrid = gstar_at_dose_grid, |
| 1518 | 2x |
Recommend = recommend, |
| 1519 | 2x |
OptimalDose = optimal_dose, |
| 1520 | 2x |
OptimalDoseAtDoseGrid = recommend, |
| 1521 | 2x |
ratio = ratio, |
| 1522 | 2x |
CI = ci, |
| 1523 | 2x |
ratioGstar = ratio_gstar, |
| 1524 | 2x |
CIGstar = ci_gstar, |
| 1525 | 2x |
ratioTDEOT = ratio_tdeot, |
| 1526 | 2x |
CITDEOT = ci_tdeot, |
| 1527 | 2x |
fitDLE = dle_fit, |
| 1528 | 2x |
fitEff = eff_fit, |
| 1529 | 2x |
sigma2est = eff_sigma2, |
| 1530 | 2x |
stop = attr(should_stop, "message"), |
| 1531 | 2x |
report_results = stopit_results |
| 1532 |
) |
|
| 1533 |
} |
|
| 1534 | ||
| 1535 | 2x |
result_list <- get_result_list( |
| 1536 | 2x |
fun = run_sim, |
| 1537 | 2x |
nsim = nsim, |
| 1538 | 2x |
vars = c( |
| 1539 | 2x |
"sim_seeds", |
| 1540 | 2x |
"args", |
| 1541 | 2x |
"n_args", |
| 1542 | 2x |
"firstSeparate", |
| 1543 | 2x |
"trueDLE", |
| 1544 | 2x |
"trueEff", |
| 1545 | 2x |
"trueNu", |
| 1546 | 2x |
"object" |
| 1547 |
), |
|
| 1548 | 2x |
parallel = parallel, |
| 1549 | 2x |
n_cores = nCores |
| 1550 |
) |
|
| 1551 | ||
| 1552 | 2x |
data_list <- lapply(result_list, "[[", "data") |
| 1553 | 2x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend")) |
| 1554 | ||
| 1555 | 2x |
td_target_during_trial_list <- as.numeric(sapply( |
| 1556 | 2x |
result_list, |
| 1557 |
"[[", |
|
| 1558 | 2x |
"TDtargetDuringTrial" |
| 1559 |
)) |
|
| 1560 | ||
| 1561 | 2x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 1562 | 2x |
result_list, |
| 1563 |
"[[", |
|
| 1564 | 2x |
"TDtargetEndOfTrial" |
| 1565 |
)) |
|
| 1566 | ||
| 1567 | 2x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 1568 | 2x |
result_list, |
| 1569 |
"[[", |
|
| 1570 | 2x |
"TDtargetDuringTrialAtDoseGrid" |
| 1571 |
)) |
|
| 1572 | ||
| 1573 | 2x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 1574 | 2x |
result_list, |
| 1575 |
"[[", |
|
| 1576 | 2x |
"TDtargetEndOfTrialAtDoseGrid" |
| 1577 |
)) |
|
| 1578 | ||
| 1579 | 2x |
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar")) |
| 1580 | 2x |
gstar_at_dose_grid_list <- as.numeric(sapply( |
| 1581 | 2x |
result_list, |
| 1582 |
"[[", |
|
| 1583 | 2x |
"GstarAtDoseGrid" |
| 1584 |
)) |
|
| 1585 | ||
| 1586 | 2x |
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose")) |
| 1587 | 2x |
optimal_dose_at_dose_grid_list <- as.numeric(sapply( |
| 1588 | 2x |
result_list, |
| 1589 |
"[[", |
|
| 1590 | 2x |
"Recommend" |
| 1591 |
)) |
|
| 1592 | ||
| 1593 | 2x |
ci_list <- lapply(result_list, "[[", "CI") |
| 1594 | 2x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio")) |
| 1595 | 2x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 1596 | 2x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 1597 | 2x |
ci_gstar_list <- lapply(result_list, "[[", "CIGstar") |
| 1598 | 2x |
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar")) |
| 1599 | ||
| 1600 | 2x |
fit_dle_list <- lapply(result_list, "[[", "fitDLE") |
| 1601 | 2x |
fit_eff_list <- lapply(result_list, "[[", "fitEff") |
| 1602 | 2x |
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est")) |
| 1603 | 2x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 1604 | ||
| 1605 | 2x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 1606 | 2x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 1607 | ||
| 1608 | 2x |
PseudoDualSimulations( |
| 1609 | 2x |
data = data_list, |
| 1610 | 2x |
doses = recommended_doses, |
| 1611 | 2x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 1612 | 2x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 1613 | 2x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 1614 | 2x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 1615 | 2x |
final_cis = ci_list, |
| 1616 | 2x |
final_ratios = ratio_list, |
| 1617 | 2x |
final_gstar_estimates = gstar_list, |
| 1618 | 2x |
final_gstar_at_dose_grid = gstar_at_dose_grid_list, |
| 1619 | 2x |
final_gstar_cis = ci_gstar_list, |
| 1620 | 2x |
final_gstar_ratios = ratio_gstar_list, |
| 1621 | 2x |
final_tdeot_cis = ci_tdeot_list, |
| 1622 | 2x |
final_tdeot_ratios = ratio_tdeot_list, |
| 1623 | 2x |
final_optimal_dose = optimal_dose_list, |
| 1624 | 2x |
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list, |
| 1625 | 2x |
fit = fit_dle_list, |
| 1626 | 2x |
fit_eff = fit_eff_list, |
| 1627 | 2x |
sigma2_est = sigma2_estimates, |
| 1628 | 2x |
stop_reasons = stop_reasons, |
| 1629 | 2x |
stop_report = stop_report, |
| 1630 | 2x |
seed = rng_state |
| 1631 |
) |
|
| 1632 |
} |
|
| 1633 |
) |
|
| 1634 | ||
| 1635 |
## DualResponsesSamplesDesign ---- |
|
| 1636 | ||
| 1637 |
### h_simulate_flexi ---- |
|
| 1638 | ||
| 1639 |
h_simulate_flexi <- function( |
|
| 1640 |
object, |
|
| 1641 |
nsim = 1L, |
|
| 1642 |
seed = NULL, |
|
| 1643 |
trueDLE, |
|
| 1644 |
trueEff, |
|
| 1645 |
trueNu = NULL, |
|
| 1646 |
trueSigma2 = NULL, |
|
| 1647 |
trueSigma2betaW = NULL, |
|
| 1648 |
args = NULL, |
|
| 1649 |
firstSeparate = FALSE, |
|
| 1650 |
mcmcOptions = McmcOptions(), |
|
| 1651 |
parallel = FALSE, |
|
| 1652 |
nCores = min(parallel::detectCores(), 5L), |
|
| 1653 |
... |
|
| 1654 |
) {
|
|
| 1655 | 1x |
stopifnot( |
| 1656 | 1x |
trueSigma2 > 0, |
| 1657 | 1x |
trueSigma2betaW > 0, |
| 1658 | 1x |
is.numeric(trueEff), |
| 1659 | 1x |
length(trueEff) == length(object@data@doseGrid) |
| 1660 |
) |
|
| 1661 | ||
| 1662 | 1x |
args <- as.data.frame(args) |
| 1663 | 1x |
n_args <- max(nrow(args), 1L) |
| 1664 | ||
| 1665 |
# Get argument names (excluding the first one which is the dose) |
|
| 1666 | 1x |
dle_arg_names <- names(formals(trueDLE))[-1] |
| 1667 | ||
| 1668 |
# Seed handling |
|
| 1669 | 1x |
rng_state <- set_seed(seed) |
| 1670 | ||
| 1671 |
# Generate individual seeds for simulation runs |
|
| 1672 | 1x |
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
| 1673 | ||
| 1674 |
# Function to run a single simulation with index "iter_sim" |
|
| 1675 | 1x |
run_sim <- function(iter_sim) {
|
| 1676 |
# Set the seed for this run |
|
| 1677 | 1x |
set.seed(sim_seeds[iter_sim]) |
| 1678 | ||
| 1679 |
# Get current arguments (appropriately recycled) |
|
| 1680 | 1x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 1681 | ||
| 1682 |
# DLE truth function with current arguments |
|
| 1683 | 1x |
dle_with_args <- function(dose) {
|
| 1684 | 6x |
do.call( |
| 1685 | 6x |
trueDLE, |
| 1686 |
# First argument: the dose |
|
| 1687 | 6x |
c( |
| 1688 | 6x |
dose, |
| 1689 |
# Following arguments |
|
| 1690 | 6x |
current_args |
| 1691 |
) |
|
| 1692 |
) |
|
| 1693 |
} |
|
| 1694 | ||
| 1695 |
# Efficacy truth function (fixed for EffFlexi) |
|
| 1696 | 1x |
eff_truth <- trueEff |
| 1697 | ||
| 1698 |
# Start with the provided data |
|
| 1699 | 1x |
data <- object@data |
| 1700 | ||
| 1701 |
# Trial control variables |
|
| 1702 | 1x |
should_stop <- FALSE |
| 1703 | 1x |
dose <- object@startingDose |
| 1704 | 1x |
dose_pl <- object@data@doseGrid[1] |
| 1705 | ||
| 1706 |
# Start with specified variance parameters |
|
| 1707 | 1x |
sigma2 <- trueSigma2 |
| 1708 | 1x |
sigma2_beta_w <- trueSigma2betaW |
| 1709 | ||
| 1710 |
# Main simulation loop |
|
| 1711 | 1x |
while (!should_stop) {
|
| 1712 |
# Calculate probabilities and outcomes at current dose |
|
| 1713 | 3x |
dle_prob <- dle_with_args(dose) |
| 1714 | 3x |
dle_prob_pl <- dle_with_args(dose_pl) |
| 1715 | ||
| 1716 | 3x |
dose_index <- which(dose == data@doseGrid) |
| 1717 | 3x |
mean_eff <- eff_truth[dose_index] |
| 1718 | 3x |
mean_eff_pl <- eff_truth[1] |
| 1719 | ||
| 1720 |
# Determine cohort size |
|
| 1721 | 3x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 1722 | ||
| 1723 | 3x |
if (data@placebo) {
|
| 1724 | ! |
placebo_size <- size( |
| 1725 | ! |
object@pl_cohort_size, |
| 1726 | ! |
dose = dose, |
| 1727 | ! |
data = data |
| 1728 |
) |
|
| 1729 |
} |
|
| 1730 | ||
| 1731 |
## simulate DLTs: depends on whether we |
|
| 1732 |
## separate the first patient or not. |
|
| 1733 | 3x |
if (firstSeparate && (cohort_size > 1L)) {
|
| 1734 |
## dose the first patient |
|
| 1735 | ! |
dlts <- rbinom( |
| 1736 | ! |
n = 1L, |
| 1737 | ! |
size = 1L, |
| 1738 | ! |
prob = dle_prob |
| 1739 |
) |
|
| 1740 | ||
| 1741 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 1742 | ! |
dlts_pl <- rbinom( |
| 1743 | ! |
n = 1L, |
| 1744 | ! |
size = 1L, |
| 1745 | ! |
prob = dle_prob_pl |
| 1746 |
) |
|
| 1747 |
} |
|
| 1748 | ||
| 1749 | ! |
eff_responses <- rnorm( |
| 1750 | ! |
n = 1L, |
| 1751 | ! |
mean = mean_eff, |
| 1752 | ! |
sd = sqrt(trueSigma2) |
| 1753 |
) |
|
| 1754 | ||
| 1755 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 1756 | ! |
eff_responses_pl <- rnorm( |
| 1757 | ! |
n = 1L, |
| 1758 | ! |
mean = mean_eff_pl, |
| 1759 | ! |
sd = sqrt(trueSigma2) |
| 1760 |
) |
|
| 1761 |
} |
|
| 1762 | ||
| 1763 |
# If no DLT in first patient, enroll remaining patients |
|
| 1764 | ! |
if (dlts == 0) {
|
| 1765 | ! |
dlts <- c( |
| 1766 | ! |
dlts, |
| 1767 | ! |
rbinom( |
| 1768 | ! |
n = cohort_size - 1L, |
| 1769 | ! |
size = 1L, |
| 1770 | ! |
prob = dle_prob |
| 1771 |
) |
|
| 1772 |
) |
|
| 1773 | ! |
eff_responses <- c( |
| 1774 | ! |
eff_responses, |
| 1775 | ! |
rnorm( |
| 1776 | ! |
n = cohort_size - 1L, |
| 1777 | ! |
mean = mean_eff, |
| 1778 | ! |
sd = sqrt(trueSigma2) |
| 1779 |
) |
|
| 1780 |
) |
|
| 1781 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 1782 | ! |
dlts_pl <- c( |
| 1783 | ! |
dlts_pl, |
| 1784 | ! |
rbinom( |
| 1785 | ! |
n = placebo_size, |
| 1786 | ! |
size = 1L, |
| 1787 | ! |
prob = dle_prob_pl |
| 1788 |
) |
|
| 1789 |
) |
|
| 1790 | ! |
eff_responses_pl <- c( |
| 1791 | ! |
eff_responses_pl, |
| 1792 | ! |
rnorm( |
| 1793 | ! |
n = placebo_size, |
| 1794 | ! |
mean = mean_eff_pl, |
| 1795 | ! |
sd = sqrt(trueSigma2) |
| 1796 |
) |
|
| 1797 |
) |
|
| 1798 |
} |
|
| 1799 |
} |
|
| 1800 |
} else {
|
|
| 1801 |
# Dose all patients directly |
|
| 1802 | 3x |
dlts <- rbinom( |
| 1803 | 3x |
n = cohort_size, |
| 1804 | 3x |
size = 1L, |
| 1805 | 3x |
prob = dle_prob |
| 1806 |
) |
|
| 1807 | ||
| 1808 | 3x |
eff_responses <- rnorm( |
| 1809 | 3x |
n = cohort_size, |
| 1810 | 3x |
mean = mean_eff, |
| 1811 | 3x |
sd = sqrt(trueSigma2) |
| 1812 |
) |
|
| 1813 | 3x |
if (data@placebo && (placebo_size > 0L)) {
|
| 1814 | ! |
dlts_pl <- rbinom( |
| 1815 | ! |
n = placebo_size, |
| 1816 | ! |
size = 1L, |
| 1817 | ! |
prob = dle_prob_pl |
| 1818 |
) |
|
| 1819 | ! |
eff_responses_pl <- rnorm( |
| 1820 | ! |
n = placebo_size, |
| 1821 | ! |
mean = mean_eff_pl, |
| 1822 | ! |
sd = sqrt(trueSigma2) |
| 1823 |
) |
|
| 1824 |
} |
|
| 1825 |
} |
|
| 1826 | ||
| 1827 |
## update the data with this placebo (if any) cohort and then with active dose |
|
| 1828 | 3x |
if (data@placebo && (placebo_size > 0L)) {
|
| 1829 | ! |
data <- update( |
| 1830 | ! |
object = data, |
| 1831 | ! |
x = object@data@doseGrid[1], |
| 1832 | ! |
y = dlts_pl, |
| 1833 | ! |
w = eff_responses_pl, |
| 1834 | ! |
check = FALSE |
| 1835 |
) |
|
| 1836 | ||
| 1837 |
## update the data with active dose |
|
| 1838 | ! |
data <- update( |
| 1839 | ! |
object = data, |
| 1840 | ! |
x = dose, |
| 1841 | ! |
y = dlts, |
| 1842 | ! |
w = eff_responses, |
| 1843 | ! |
new_cohort = FALSE |
| 1844 |
) |
|
| 1845 |
} else {
|
|
| 1846 |
## update the data with this cohort |
|
| 1847 | 3x |
data <- update( |
| 1848 | 3x |
object = data, |
| 1849 | 3x |
x = dose, |
| 1850 | 3x |
y = dlts, |
| 1851 | 3x |
w = eff_responses |
| 1852 |
) |
|
| 1853 |
} |
|
| 1854 | ||
| 1855 |
# Update models with new data |
|
| 1856 | 3x |
dle_model <- update( |
| 1857 | 3x |
object = object@model, |
| 1858 | 3x |
data = data |
| 1859 |
) |
|
| 1860 | ||
| 1861 | 3x |
eff_model <- update( |
| 1862 | 3x |
object = object@eff_model, |
| 1863 | 3x |
data = data |
| 1864 |
) |
|
| 1865 | ||
| 1866 |
# Calculate dose limit |
|
| 1867 | 3x |
dose_limit <- maxDose(object@increments, data = data) |
| 1868 | ||
| 1869 |
# Generate MCMC samples from both models |
|
| 1870 | 3x |
dle_samples <- mcmc( |
| 1871 | 3x |
data = data, |
| 1872 | 3x |
model = dle_model, |
| 1873 | 3x |
options = mcmcOptions |
| 1874 |
) |
|
| 1875 | ||
| 1876 | 3x |
eff_samples <- mcmc( |
| 1877 | 3x |
data = data, |
| 1878 | 3x |
model = eff_model, |
| 1879 | 3x |
options = mcmcOptions |
| 1880 |
) |
|
| 1881 | ||
| 1882 |
# Update variance estimates from MCMC samples |
|
| 1883 | 3x |
sigma2 <- mean(eff_samples@data$sigma2W) |
| 1884 | 3x |
sigma2_beta_w <- mean(eff_samples@data$sigma2betaW) |
| 1885 | ||
| 1886 |
# Calculate next best dose |
|
| 1887 | 3x |
next_bd <- nextBest( |
| 1888 | 3x |
object@nextBest, |
| 1889 | 3x |
doselimit = dose_limit, |
| 1890 | 3x |
samples = dle_samples, |
| 1891 | 3x |
model = dle_model, |
| 1892 | 3x |
model_eff = eff_model, |
| 1893 | 3x |
samples_eff = eff_samples, |
| 1894 | 3x |
data = data, |
| 1895 | 3x |
in_sim = TRUE |
| 1896 |
) |
|
| 1897 | ||
| 1898 |
# Extract dose recommendations |
|
| 1899 | 3x |
dose <- next_bd$next_dose |
| 1900 | 3x |
td_target_during_trial <- next_bd$dose_target_drt |
| 1901 | 3x |
td_target_during_trial_at_dose_grid <- next_bd$next_dose_drt |
| 1902 | 3x |
td_target_end_of_trial <- next_bd$dose_target_eot |
| 1903 | 3x |
td_target_end_of_trial_at_dose_grid <- next_bd$next_dose_eot |
| 1904 | 3x |
gstar <- next_bd$dose_max_gain |
| 1905 | 3x |
gstar_at_dose_grid <- next_bd$next_dose_max_gain |
| 1906 | ||
| 1907 | 3x |
recommend <- min( |
| 1908 | 3x |
td_target_end_of_trial_at_dose_grid, |
| 1909 | 3x |
gstar_at_dose_grid |
| 1910 |
) |
|
| 1911 | ||
| 1912 |
# Calculate 95% confidence intervals and ratios |
|
| 1913 | 3x |
ci_tdeot <- list( |
| 1914 | 3x |
lower = next_bd$ci_dose_target_eot[1], |
| 1915 | 3x |
upper = next_bd$ci_dose_target_eot[2] |
| 1916 |
) |
|
| 1917 | 3x |
ratio_tdeot <- next_bd$ci_ratio_dose_target_eot |
| 1918 | ||
| 1919 | 3x |
ci_gstar <- list( |
| 1920 | 3x |
lower = next_bd$ci_dose_max_gain[1], |
| 1921 | 3x |
upper = next_bd$ci_dose_max_gain[2] |
| 1922 |
) |
|
| 1923 | 3x |
ratio_gstar <- next_bd$ci_ratio_dose_max_gain |
| 1924 | ||
| 1925 |
# Find the optimal dose |
|
| 1926 | 3x |
optimal_dose <- min(gstar, td_target_end_of_trial) |
| 1927 | ||
| 1928 | 3x |
if (optimal_dose == gstar) {
|
| 1929 | 2x |
ratio <- ratio_gstar |
| 1930 | 2x |
ci <- ci_gstar |
| 1931 |
} else {
|
|
| 1932 | 1x |
ratio <- ratio_tdeot |
| 1933 | 1x |
ci <- ci_tdeot |
| 1934 |
} |
|
| 1935 | ||
| 1936 |
# Evaluate stopping rules |
|
| 1937 | 3x |
should_stop <- stopTrial( |
| 1938 | 3x |
object@stopping, |
| 1939 | 3x |
dose = dose, |
| 1940 | 3x |
samples = dle_samples, |
| 1941 | 3x |
model = dle_model, |
| 1942 | 3x |
data = data, |
| 1943 | 3x |
TDderive = object@nextBest@derive, |
| 1944 | 3x |
Effmodel = eff_model, |
| 1945 | 3x |
Effsamples = eff_samples, |
| 1946 | 3x |
Gstarderive = object@nextBest@mg_derive |
| 1947 |
) |
|
| 1948 | 3x |
stop_results <- h_unpack_stopit(should_stop) |
| 1949 |
} |
|
| 1950 | ||
| 1951 |
# Calculate final model fits |
|
| 1952 | 1x |
dle_fit <- fit( |
| 1953 | 1x |
object = dle_samples, |
| 1954 | 1x |
model = dle_model, |
| 1955 | 1x |
data = data |
| 1956 |
) |
|
| 1957 | ||
| 1958 | 1x |
eff_fit <- fit( |
| 1959 | 1x |
object = eff_samples, |
| 1960 | 1x |
model = eff_model, |
| 1961 | 1x |
data = data |
| 1962 |
) |
|
| 1963 | ||
| 1964 |
# Return simulation results |
|
| 1965 | 1x |
list( |
| 1966 | 1x |
data = data, |
| 1967 | 1x |
dose = dose, |
| 1968 | 1x |
TDtargetDuringTrial = td_target_during_trial, |
| 1969 | 1x |
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid, |
| 1970 | 1x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 1971 | 1x |
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid, |
| 1972 | 1x |
Gstar = gstar, |
| 1973 | 1x |
GstarAtDoseGrid = gstar_at_dose_grid, |
| 1974 | 1x |
Recommend = recommend, |
| 1975 | 1x |
OptimalDose = optimal_dose, |
| 1976 | 1x |
OptimalDoseAtDoseGrid = recommend, |
| 1977 | 1x |
ratio = ratio, |
| 1978 | 1x |
CI = ci, |
| 1979 | 1x |
ratioGstar = ratio_gstar, |
| 1980 | 1x |
CIGstar = ci_gstar, |
| 1981 | 1x |
ratioTDEOT = ratio_tdeot, |
| 1982 | 1x |
CITDEOT = ci_tdeot, |
| 1983 | 1x |
fitDLE = subset(dle_fit, select = c(middle, lower, upper)), |
| 1984 | 1x |
fitEff = subset(eff_fit, select = c(middle, lower, upper)), |
| 1985 | 1x |
sigma2est = sigma2, |
| 1986 | 1x |
sigma2betaWest = sigma2_beta_w, |
| 1987 | 1x |
stop = attr(should_stop, "message"), |
| 1988 | 1x |
report_results = stop_results |
| 1989 |
) |
|
| 1990 |
} |
|
| 1991 | ||
| 1992 | 1x |
result_list <- get_result_list( |
| 1993 | 1x |
fun = run_sim, |
| 1994 | 1x |
nsim = nsim, |
| 1995 | 1x |
vars = c( |
| 1996 | 1x |
"sim_seeds", |
| 1997 | 1x |
"args", |
| 1998 | 1x |
"n_args", |
| 1999 | 1x |
"firstSeparate", |
| 2000 | 1x |
"trueDLE", |
| 2001 | 1x |
"trueEff", |
| 2002 | 1x |
"trueSigma2", |
| 2003 | 1x |
"trueSigma2betaW", |
| 2004 | 1x |
"object", |
| 2005 | 1x |
"mcmcOptions" |
| 2006 |
), |
|
| 2007 | 1x |
parallel = parallel, |
| 2008 | 1x |
n_cores = nCores |
| 2009 |
) |
|
| 2010 | ||
| 2011 |
# Process simulation results |
|
| 2012 | 1x |
data_list <- lapply(result_list, "[[", "data") |
| 2013 | 1x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend")) |
| 2014 | ||
| 2015 |
# Extract model fits and variance estimates |
|
| 2016 | 1x |
fit_dle_list <- lapply(result_list, "[[", "fitDLE") |
| 2017 | 1x |
fit_eff_list <- lapply(result_list, "[[", "fitEff") |
| 2018 | 1x |
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est")) |
| 2019 | 1x |
sigma2_beta_w_estimates <- as.numeric(sapply( |
| 2020 | 1x |
result_list, |
| 2021 |
"[[", |
|
| 2022 | 1x |
"sigma2betaWest" |
| 2023 |
)) |
|
| 2024 | ||
| 2025 |
# Extract TD target estimates |
|
| 2026 | 1x |
td_target_during_trial_list <- as.numeric(sapply( |
| 2027 | 1x |
result_list, |
| 2028 |
"[[", |
|
| 2029 | 1x |
"TDtargetDuringTrial" |
| 2030 |
)) |
|
| 2031 | ||
| 2032 | 1x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 2033 | 1x |
result_list, |
| 2034 |
"[[", |
|
| 2035 | 1x |
"TDtargetEndOfTrial" |
| 2036 |
)) |
|
| 2037 | ||
| 2038 | 1x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 2039 | 1x |
result_list, |
| 2040 |
"[[", |
|
| 2041 | 1x |
"TDtargetDuringTrialAtDoseGrid" |
| 2042 |
)) |
|
| 2043 | ||
| 2044 | 1x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 2045 | 1x |
result_list, |
| 2046 |
"[[", |
|
| 2047 | 1x |
"TDtargetEndOfTrialAtDoseGrid" |
| 2048 |
)) |
|
| 2049 | ||
| 2050 |
# Extract Gstar and optimal dose estimates |
|
| 2051 | 1x |
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar")) |
| 2052 | ||
| 2053 | 1x |
gstar_at_dose_grid_list <- as.numeric(sapply( |
| 2054 | 1x |
result_list, |
| 2055 |
"[[", |
|
| 2056 | 1x |
"GstarAtDoseGrid" |
| 2057 |
)) |
|
| 2058 | ||
| 2059 | 1x |
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose")) |
| 2060 | ||
| 2061 | 1x |
optimal_dose_at_dose_grid_list <- as.numeric(sapply( |
| 2062 | 1x |
result_list, |
| 2063 |
"[[", |
|
| 2064 | 1x |
"Recommend" |
| 2065 |
)) |
|
| 2066 | ||
| 2067 |
# Extract confidence intervals and ratios |
|
| 2068 | 1x |
ci_list <- lapply(result_list, "[[", "CI") |
| 2069 | ||
| 2070 | 1x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio")) |
| 2071 | ||
| 2072 | 1x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 2073 | ||
| 2074 | 1x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 2075 | ||
| 2076 | 1x |
ci_gstar_list <- lapply(result_list, "[[", "CIGstar") |
| 2077 | ||
| 2078 | 1x |
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar")) |
| 2079 | ||
| 2080 |
# Extract stopping information |
|
| 2081 | 1x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 2082 | ||
| 2083 | 1x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 2084 | 1x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 2085 | ||
| 2086 |
# Return simulation results |
|
| 2087 | 1x |
PseudoDualFlexiSimulations( |
| 2088 | 1x |
data = data_list, |
| 2089 | 1x |
doses = recommended_doses, |
| 2090 | 1x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 2091 | 1x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 2092 | 1x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 2093 | 1x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 2094 | 1x |
final_cis = ci_list, |
| 2095 | 1x |
final_ratios = ratio_list, |
| 2096 | 1x |
final_gstar_estimates = gstar_list, |
| 2097 | 1x |
final_gstar_at_dose_grid = gstar_at_dose_grid_list, |
| 2098 | 1x |
final_gstar_cis = ci_gstar_list, |
| 2099 | 1x |
final_gstar_ratios = ratio_gstar_list, |
| 2100 | 1x |
final_tdeot_cis = ci_tdeot_list, |
| 2101 | 1x |
final_tdeot_ratios = ratio_tdeot_list, |
| 2102 | 1x |
final_optimal_dose = optimal_dose_list, |
| 2103 | 1x |
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list, |
| 2104 | 1x |
fit = fit_dle_list, |
| 2105 | 1x |
fit_eff = fit_eff_list, |
| 2106 | 1x |
sigma2_est = sigma2_estimates, |
| 2107 | 1x |
sigma2_beta_w_est = sigma2_beta_w_estimates, |
| 2108 | 1x |
stop_reasons = stop_reasons, |
| 2109 | 1x |
stop_report = stop_report, |
| 2110 | 1x |
seed = rng_state |
| 2111 |
) |
|
| 2112 |
} |
|
| 2113 | ||
| 2114 |
### h_simulate_nonflexi ---- |
|
| 2115 | ||
| 2116 |
h_simulate_nonflexi <- function( |
|
| 2117 |
object, |
|
| 2118 |
nsim = 1L, |
|
| 2119 |
seed = NULL, |
|
| 2120 |
trueDLE, |
|
| 2121 |
trueEff, |
|
| 2122 |
trueNu = NULL, |
|
| 2123 |
trueSigma2 = NULL, |
|
| 2124 |
trueSigma2betaW = NULL, |
|
| 2125 |
args = NULL, |
|
| 2126 |
firstSeparate = FALSE, |
|
| 2127 |
mcmcOptions = McmcOptions(), |
|
| 2128 |
parallel = FALSE, |
|
| 2129 |
nCores = min(parallel::detectCores(), 5L), |
|
| 2130 |
... |
|
| 2131 |
) {
|
|
| 2132 | 2x |
stopifnot( |
| 2133 | 2x |
trueNu > 0, |
| 2134 | 2x |
is.function(trueEff) |
| 2135 |
) |
|
| 2136 | ||
| 2137 | 2x |
args <- as.data.frame(args) |
| 2138 | 2x |
n_args <- max(nrow(args), 1L) |
| 2139 | ||
| 2140 |
# Get argument names (excluding the first one which is the dose) |
|
| 2141 | 2x |
dle_arg_names <- names(formals(trueDLE))[-1] |
| 2142 | 2x |
eff_arg_names <- names(formals(trueEff))[-1] |
| 2143 | ||
| 2144 |
# Seed handling |
|
| 2145 | 2x |
rng_state <- set_seed(seed) |
| 2146 | ||
| 2147 |
# Generate individual seeds for simulation runs |
|
| 2148 | 2x |
sim_seeds <- sample(x = seq_len(1e5), size = nsim) |
| 2149 | ||
| 2150 |
# Function to run a single simulation with index "iter_sim" |
|
| 2151 | 2x |
run_sim <- function(iter_sim) {
|
| 2152 |
# Set the seed for this run |
|
| 2153 | 2x |
set.seed(sim_seeds[iter_sim]) |
| 2154 | ||
| 2155 |
# Get current arguments (appropriately recycled) |
|
| 2156 | 2x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 2157 | ||
| 2158 |
# DLE truth function with current arguments |
|
| 2159 | 2x |
dle_with_args <- function(dose) {
|
| 2160 | 10x |
do.call( |
| 2161 | 10x |
trueDLE, |
| 2162 |
# First argument: the dose |
|
| 2163 | 10x |
c( |
| 2164 | 10x |
dose, |
| 2165 |
# Following arguments: take only those that |
|
| 2166 |
# are required by the DLE function |
|
| 2167 | 10x |
as.list(current_args)[dle_arg_names] |
| 2168 |
) |
|
| 2169 |
) |
|
| 2170 |
} |
|
| 2171 | ||
| 2172 |
# Efficacy truth function with current arguments |
|
| 2173 | 2x |
eff_with_args <- function(dose) {
|
| 2174 | 10x |
do.call( |
| 2175 | 10x |
trueEff, |
| 2176 |
# First argument: the dose |
|
| 2177 | 10x |
c( |
| 2178 | 10x |
dose, |
| 2179 |
# Following arguments: take only those that |
|
| 2180 |
# are required by the Eff function |
|
| 2181 | 10x |
as.list(current_args)[eff_arg_names] |
| 2182 |
) |
|
| 2183 |
) |
|
| 2184 |
} |
|
| 2185 | ||
| 2186 |
# Find true sigma2 to generate responses |
|
| 2187 | 2x |
true_sigma2 <- 1 / trueNu |
| 2188 | ||
| 2189 |
# Start with the provided data |
|
| 2190 | 2x |
data <- object@data |
| 2191 | ||
| 2192 |
# Trial control variables |
|
| 2193 | 2x |
should_stop <- FALSE |
| 2194 | 2x |
dose <- object@startingDose |
| 2195 | ||
| 2196 |
# Main simulation loop |
|
| 2197 | 2x |
while (!should_stop) {
|
| 2198 |
# Calculate probabilities and outcomes at current dose |
|
| 2199 | 7x |
dle_prob <- dle_with_args(dose) |
| 2200 | 7x |
mean_eff <- eff_with_args(dose) |
| 2201 | ||
| 2202 |
# Determine cohort size |
|
| 2203 | 7x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 2204 | ||
| 2205 | 7x |
if (data@placebo) {
|
| 2206 | 3x |
placebo_size <- size( |
| 2207 | 3x |
object@pl_cohort_size, |
| 2208 | 3x |
dose = dose, |
| 2209 | 3x |
data = data |
| 2210 |
) |
|
| 2211 | 3x |
dose_pl <- data@doseGrid[1] |
| 2212 | 3x |
dle_prob_pl <- dle_with_args(dose_pl) |
| 2213 | 3x |
mean_eff_pl <- eff_with_args(dose_pl) |
| 2214 |
} |
|
| 2215 | ||
| 2216 |
## simulate DLTs: depends on whether we |
|
| 2217 |
## separate the first patient or not. |
|
| 2218 | 7x |
if (firstSeparate && (cohort_size > 1L)) {
|
| 2219 |
# Dose the first patient |
|
| 2220 | ! |
dlts <- rbinom( |
| 2221 | ! |
n = 1L, |
| 2222 | ! |
size = 1L, |
| 2223 | ! |
prob = dle_prob |
| 2224 |
) |
|
| 2225 | ||
| 2226 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 2227 | ! |
dlts_pl <- rbinom( |
| 2228 | ! |
n = 1L, |
| 2229 | ! |
size = 1L, |
| 2230 | ! |
prob = dle_prob_pl |
| 2231 |
) |
|
| 2232 |
} |
|
| 2233 | ||
| 2234 | ! |
eff_responses <- rnorm( |
| 2235 | ! |
n = 1L, |
| 2236 | ! |
mean = mean_eff, |
| 2237 | ! |
sd = sqrt(true_sigma2) |
| 2238 |
) |
|
| 2239 | ||
| 2240 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 2241 | ! |
eff_responses_pl <- rnorm( |
| 2242 | ! |
n = 1L, |
| 2243 | ! |
mean = mean_eff_pl, |
| 2244 | ! |
sd = sqrt(true_sigma2) |
| 2245 |
) |
|
| 2246 |
} |
|
| 2247 | ||
| 2248 |
# If there is no DLT, enroll the remaining patients |
|
| 2249 | ! |
if (dlts == 0) {
|
| 2250 | ! |
dlts <- c( |
| 2251 | ! |
dlts, |
| 2252 | ! |
rbinom( |
| 2253 | ! |
n = cohort_size - 1L, |
| 2254 | ! |
size = 1L, |
| 2255 | ! |
prob = dle_prob |
| 2256 |
) |
|
| 2257 |
) |
|
| 2258 | ! |
eff_responses <- c( |
| 2259 | ! |
eff_responses, |
| 2260 | ! |
rnorm( |
| 2261 | ! |
n = cohort_size - 1L, |
| 2262 | ! |
mean = mean_eff, |
| 2263 | ! |
sd = sqrt(true_sigma2) |
| 2264 |
) |
|
| 2265 |
) |
|
| 2266 | ||
| 2267 | ! |
if (data@placebo && (placebo_size > 0L)) {
|
| 2268 | ! |
dlts_pl <- c( |
| 2269 | ! |
dlts_pl, |
| 2270 | ! |
rbinom( |
| 2271 | ! |
n = placebo_size, |
| 2272 | ! |
size = 1L, |
| 2273 | ! |
prob = dle_prob_pl |
| 2274 |
) |
|
| 2275 |
) |
|
| 2276 | ! |
eff_responses_pl <- c( |
| 2277 | ! |
mean_eff_pl, |
| 2278 | ! |
rnorm( |
| 2279 | ! |
n = placebo_size, |
| 2280 | ! |
mean = mean_eff_pl, |
| 2281 | ! |
sd = sqrt(true_sigma2) |
| 2282 |
) |
|
| 2283 |
) |
|
| 2284 |
} |
|
| 2285 |
} |
|
| 2286 |
} else {
|
|
| 2287 |
# Directly dose all patients |
|
| 2288 | 7x |
dlts <- rbinom( |
| 2289 | 7x |
n = cohort_size, |
| 2290 | 7x |
size = 1L, |
| 2291 | 7x |
prob = dle_prob |
| 2292 |
) |
|
| 2293 | 7x |
eff_responses <- rnorm( |
| 2294 | 7x |
n = cohort_size, |
| 2295 | 7x |
mean = mean_eff, |
| 2296 | 7x |
sd = sqrt(true_sigma2) |
| 2297 |
) |
|
| 2298 | ||
| 2299 | 7x |
if (data@placebo && (placebo_size > 0L)) {
|
| 2300 | 3x |
dlts_pl <- rbinom( |
| 2301 | 3x |
n = placebo_size, |
| 2302 | 3x |
size = 1L, |
| 2303 | 3x |
prob = dle_prob_pl |
| 2304 |
) |
|
| 2305 | 3x |
eff_responses_pl <- rnorm( |
| 2306 | 3x |
n = placebo_size, |
| 2307 | 3x |
mean = mean_eff_pl, |
| 2308 | 3x |
sd = sqrt(true_sigma2) |
| 2309 |
) |
|
| 2310 |
} |
|
| 2311 |
} |
|
| 2312 | ||
| 2313 |
## update the data with this placebo (if any) cohort and then with active dose |
|
| 2314 | 7x |
if (data@placebo && (placebo_size > 0L)) {
|
| 2315 | 3x |
data <- update( |
| 2316 | 3x |
object = data, |
| 2317 | 3x |
x = object@data@doseGrid[1], |
| 2318 | 3x |
y = dlts_pl, |
| 2319 | 3x |
w = eff_responses_pl, |
| 2320 | 3x |
check = FALSE |
| 2321 |
) |
|
| 2322 | ||
| 2323 |
# Update the data with active dose |
|
| 2324 | 3x |
data <- update( |
| 2325 | 3x |
object = data, |
| 2326 | 3x |
x = dose, |
| 2327 | 3x |
y = dlts, |
| 2328 | 3x |
w = eff_responses, |
| 2329 | 3x |
new_cohort = FALSE |
| 2330 |
) |
|
| 2331 |
} else {
|
|
| 2332 |
# Update the data with this cohort |
|
| 2333 | 4x |
data <- update( |
| 2334 | 4x |
object = data, |
| 2335 | 4x |
x = dose, |
| 2336 | 4x |
y = dlts, |
| 2337 | 4x |
w = eff_responses |
| 2338 |
) |
|
| 2339 |
} |
|
| 2340 | ||
| 2341 |
# Update models with new data |
|
| 2342 | 7x |
dle_model <- update( |
| 2343 | 7x |
object = object@model, |
| 2344 | 7x |
data = data |
| 2345 |
) |
|
| 2346 | ||
| 2347 | 7x |
eff_model <- update( |
| 2348 | 7x |
object = object@eff_model, |
| 2349 | 7x |
data = data |
| 2350 |
) |
|
| 2351 | ||
| 2352 | 7x |
nu <- eff_model@nu |
| 2353 | ||
| 2354 | 7x |
dle_samples <- mcmc( |
| 2355 | 7x |
data = data, |
| 2356 | 7x |
model = dle_model, |
| 2357 | 7x |
options = mcmcOptions |
| 2358 |
) |
|
| 2359 | ||
| 2360 | 7x |
eff_samples <- mcmc( |
| 2361 | 7x |
data = data, |
| 2362 | 7x |
model = eff_model, |
| 2363 | 7x |
options = mcmcOptions |
| 2364 |
) |
|
| 2365 | ||
| 2366 | 7x |
sigma2 <- if (eff_model@use_fixed) {
|
| 2367 | ! |
1 / nu |
| 2368 |
} else {
|
|
| 2369 | 7x |
1 / (as.numeric(nu["a"] / nu["b"])) |
| 2370 |
} |
|
| 2371 | ||
| 2372 |
# Calculate dose limit |
|
| 2373 | 7x |
dose_limit <- maxDose(object@increments, data = data) |
| 2374 | ||
| 2375 |
# Calculate next best dose |
|
| 2376 | 7x |
next_bd <- nextBest( |
| 2377 | 7x |
object@nextBest, |
| 2378 | 7x |
doselimit = dose_limit, |
| 2379 | 7x |
samples = dle_samples, |
| 2380 | 7x |
model = dle_model, |
| 2381 | 7x |
data = data, |
| 2382 | 7x |
model_eff = eff_model, |
| 2383 | 7x |
samples_eff = eff_samples, |
| 2384 | 7x |
in_sim = TRUE |
| 2385 |
) |
|
| 2386 | ||
| 2387 |
# Extract dose recommendations |
|
| 2388 | 7x |
dose <- next_bd$next_dose |
| 2389 | 7x |
td_target_during_trial <- next_bd$dose_target_drt |
| 2390 | 7x |
td_target_during_trial_at_dose_grid <- next_bd$next_dose_drt |
| 2391 | 7x |
td_target_end_of_trial <- next_bd$dose_target_eot |
| 2392 | 7x |
td_target_end_of_trial_at_dose_grid <- next_bd$next_dose_eot |
| 2393 | 7x |
gstar <- next_bd$dose_max_gain |
| 2394 | 7x |
gstar_at_dose_grid <- next_bd$next_dose_max_gain |
| 2395 | ||
| 2396 | 7x |
recommend <- min( |
| 2397 | 7x |
td_target_end_of_trial_at_dose_grid, |
| 2398 | 7x |
gstar_at_dose_grid |
| 2399 |
) |
|
| 2400 | ||
| 2401 |
# Calculate 95% confidence intervals and ratios |
|
| 2402 | 7x |
ci_tdeot <- list( |
| 2403 | 7x |
lower = next_bd$ci_dose_target_eot[1], |
| 2404 | 7x |
upper = next_bd$ci_dose_target_eot[2] |
| 2405 |
) |
|
| 2406 | 7x |
ratio_tdeot <- next_bd$ci_ratio_dose_target_eot |
| 2407 | ||
| 2408 | 7x |
ci_gstar <- list( |
| 2409 | 7x |
lower = next_bd$ci_dose_max_gain[1], |
| 2410 | 7x |
upper = next_bd$ci_dose_max_gain[2] |
| 2411 |
) |
|
| 2412 | 7x |
ratio_gstar <- next_bd$ci_ratio_dose_max_gain |
| 2413 | ||
| 2414 |
# Find the optimal dose |
|
| 2415 | 7x |
optimal_dose <- min(gstar, td_target_end_of_trial) |
| 2416 | ||
| 2417 | 7x |
if (optimal_dose == gstar) {
|
| 2418 | ! |
ratio <- ratio_gstar |
| 2419 | ! |
ci <- ci_gstar |
| 2420 |
} else {
|
|
| 2421 | 7x |
ratio <- ratio_tdeot |
| 2422 | 7x |
ci <- ci_tdeot |
| 2423 |
} |
|
| 2424 | ||
| 2425 |
# Evaluate stopping rules |
|
| 2426 | 7x |
should_stop <- stopTrial( |
| 2427 | 7x |
object@stopping, |
| 2428 | 7x |
dose = dose, |
| 2429 | 7x |
samples = dle_samples, |
| 2430 | 7x |
model = dle_model, |
| 2431 | 7x |
data = data, |
| 2432 | 7x |
TDderive = object@nextBest@derive, |
| 2433 | 7x |
Effmodel = eff_model, |
| 2434 | 7x |
Effsamples = eff_samples, |
| 2435 | 7x |
Gstarderive = object@nextBest@mg_derive |
| 2436 |
) |
|
| 2437 | 7x |
stop_results <- h_unpack_stopit(should_stop) |
| 2438 |
} |
|
| 2439 |
# Calculate final model fits |
|
| 2440 | 2x |
dle_fit <- fit( |
| 2441 | 2x |
object = dle_samples, |
| 2442 | 2x |
model = dle_model, |
| 2443 | 2x |
data = data |
| 2444 |
) |
|
| 2445 | ||
| 2446 | 2x |
eff_fit <- fit( |
| 2447 | 2x |
object = eff_samples, |
| 2448 | 2x |
model = eff_model, |
| 2449 | 2x |
data = data |
| 2450 |
) |
|
| 2451 | ||
| 2452 |
# Return simulation results |
|
| 2453 | 2x |
list( |
| 2454 | 2x |
data = data, |
| 2455 | 2x |
dose = dose, |
| 2456 | 2x |
TDtargetDuringTrial = td_target_during_trial, |
| 2457 | 2x |
TDtargetDuringTrialAtDoseGrid = td_target_during_trial_at_dose_grid, |
| 2458 | 2x |
TDtargetEndOfTrial = td_target_end_of_trial, |
| 2459 | 2x |
TDtargetEndOfTrialAtDoseGrid = td_target_end_of_trial_at_dose_grid, |
| 2460 | 2x |
Gstar = gstar, |
| 2461 | 2x |
GstarAtDoseGrid = gstar_at_dose_grid, |
| 2462 | 2x |
Recommend = recommend, |
| 2463 | 2x |
OptimalDose = optimal_dose, |
| 2464 | 2x |
OptimalDoseAtDoseGrid = recommend, |
| 2465 | 2x |
ratio = ratio, |
| 2466 | 2x |
CI = ci, |
| 2467 | 2x |
ratioGstar = ratio_gstar, |
| 2468 | 2x |
CIGstar = ci_gstar, |
| 2469 | 2x |
ratioTDEOT = ratio_tdeot, |
| 2470 | 2x |
CITDEOT = ci_tdeot, |
| 2471 | 2x |
fitDLE = subset(dle_fit, select = c(middle, lower, upper)), |
| 2472 | 2x |
fitEff = subset(eff_fit, select = c(middle, lower, upper)), |
| 2473 | 2x |
sigma2est = sigma2, |
| 2474 | 2x |
stop = attr( |
| 2475 | 2x |
should_stop, |
| 2476 | 2x |
"message" |
| 2477 |
), |
|
| 2478 | 2x |
report_results = stop_results |
| 2479 |
) |
|
| 2480 |
} |
|
| 2481 | ||
| 2482 | 2x |
result_list <- get_result_list( |
| 2483 | 2x |
fun = run_sim, |
| 2484 | 2x |
nsim = nsim, |
| 2485 | 2x |
vars = c( |
| 2486 | 2x |
"sim_seeds", |
| 2487 | 2x |
"args", |
| 2488 | 2x |
"n_args", |
| 2489 | 2x |
"firstSeparate", |
| 2490 | 2x |
"trueDLE", |
| 2491 | 2x |
"trueEff", |
| 2492 | 2x |
"trueNu", |
| 2493 | 2x |
"object" |
| 2494 |
), |
|
| 2495 | 2x |
parallel = parallel, |
| 2496 | 2x |
n_cores = nCores |
| 2497 |
) |
|
| 2498 | ||
| 2499 |
# Process simulation results |
|
| 2500 | 2x |
data_list <- lapply(result_list, "[[", "data") |
| 2501 | 2x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "Recommend")) |
| 2502 | ||
| 2503 |
# Extract TD target estimates |
|
| 2504 | 2x |
td_target_during_trial_list <- as.numeric(sapply( |
| 2505 | 2x |
result_list, |
| 2506 |
"[[", |
|
| 2507 | 2x |
"TDtargetDuringTrial" |
| 2508 |
)) |
|
| 2509 | ||
| 2510 | 2x |
td_target_end_of_trial_list <- as.numeric(sapply( |
| 2511 | 2x |
result_list, |
| 2512 |
"[[", |
|
| 2513 | 2x |
"TDtargetEndOfTrial" |
| 2514 |
)) |
|
| 2515 | ||
| 2516 | 2x |
td_target_during_trial_dose_grid_list <- as.numeric(sapply( |
| 2517 | 2x |
result_list, |
| 2518 |
"[[", |
|
| 2519 | 2x |
"TDtargetDuringTrialAtDoseGrid" |
| 2520 |
)) |
|
| 2521 | ||
| 2522 | 2x |
td_target_end_of_trial_dose_grid_list <- as.numeric(sapply( |
| 2523 | 2x |
result_list, |
| 2524 |
"[[", |
|
| 2525 | 2x |
"TDtargetEndOfTrialAtDoseGrid" |
| 2526 |
)) |
|
| 2527 | ||
| 2528 |
# Extract Gstar and optimal dose estimates |
|
| 2529 | 2x |
gstar_list <- as.numeric(sapply(result_list, "[[", "Gstar")) |
| 2530 | ||
| 2531 | 2x |
gstar_at_dose_grid_list <- as.numeric(sapply( |
| 2532 | 2x |
result_list, |
| 2533 |
"[[", |
|
| 2534 | 2x |
"GstarAtDoseGrid" |
| 2535 |
)) |
|
| 2536 | ||
| 2537 | 2x |
optimal_dose_list <- as.numeric(sapply(result_list, "[[", "OptimalDose")) |
| 2538 | ||
| 2539 | 2x |
optimal_dose_at_dose_grid_list <- as.numeric(sapply( |
| 2540 | 2x |
result_list, |
| 2541 |
"[[", |
|
| 2542 | 2x |
"Recommend" |
| 2543 |
)) |
|
| 2544 | ||
| 2545 |
# Extract confidence intervals and ratios |
|
| 2546 | 2x |
ci_list <- lapply(result_list, "[[", "CI") |
| 2547 | 2x |
ratio_list <- as.numeric(sapply(result_list, "[[", "ratio")) |
| 2548 | ||
| 2549 | 2x |
ci_tdeot_list <- lapply(result_list, "[[", "CITDEOT") |
| 2550 | 2x |
ratio_tdeot_list <- as.numeric(sapply(result_list, "[[", "ratioTDEOT")) |
| 2551 | ||
| 2552 | 2x |
ci_gstar_list <- lapply(result_list, "[[", "CIGstar") |
| 2553 | 2x |
ratio_gstar_list <- as.numeric(sapply(result_list, "[[", "ratioGstar")) |
| 2554 | ||
| 2555 |
# Extract model fits and variance estimates |
|
| 2556 | 2x |
fit_dle_list <- lapply(result_list, "[[", "fitDLE") |
| 2557 | 2x |
fit_eff_list <- lapply(result_list, "[[", "fitEff") |
| 2558 | 2x |
sigma2_estimates <- as.numeric(sapply(result_list, "[[", "sigma2est")) |
| 2559 | ||
| 2560 |
# Extract stopping information |
|
| 2561 | 2x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 2562 | ||
| 2563 | 2x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 2564 | 2x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 2565 | ||
| 2566 |
# Return simulation results |
|
| 2567 | 2x |
PseudoDualSimulations( |
| 2568 | 2x |
data = data_list, |
| 2569 | 2x |
doses = recommended_doses, |
| 2570 | 2x |
final_td_target_during_trial_estimates = td_target_during_trial_list, |
| 2571 | 2x |
final_td_target_end_of_trial_estimates = td_target_end_of_trial_list, |
| 2572 | 2x |
final_td_target_during_trial_at_dose_grid = td_target_during_trial_dose_grid_list, |
| 2573 | 2x |
final_td_target_end_of_trial_at_dose_grid = td_target_end_of_trial_dose_grid_list, |
| 2574 | 2x |
final_cis = ci_list, |
| 2575 | 2x |
final_ratios = ratio_list, |
| 2576 | 2x |
final_gstar_estimates = gstar_list, |
| 2577 | 2x |
final_gstar_at_dose_grid = gstar_at_dose_grid_list, |
| 2578 | 2x |
final_gstar_cis = ci_gstar_list, |
| 2579 | 2x |
final_gstar_ratios = ratio_gstar_list, |
| 2580 | 2x |
final_tdeot_cis = ci_tdeot_list, |
| 2581 | 2x |
final_tdeot_ratios = ratio_tdeot_list, |
| 2582 | 2x |
final_optimal_dose = optimal_dose_list, |
| 2583 | 2x |
final_optimal_dose_at_dose_grid = optimal_dose_at_dose_grid_list, |
| 2584 | 2x |
fit = fit_dle_list, |
| 2585 | 2x |
fit_eff = fit_eff_list, |
| 2586 | 2x |
sigma2_est = sigma2_estimates, |
| 2587 | 2x |
stop_reasons = stop_reasons, |
| 2588 | 2x |
stop_report = stop_report, |
| 2589 | 2x |
seed = rng_state |
| 2590 |
) |
|
| 2591 |
} |
|
| 2592 | ||
| 2593 |
### method definition ---- |
|
| 2594 | ||
| 2595 |
#' Simulate dose escalation procedure using DLE and efficacy responses with samples |
|
| 2596 |
#' |
|
| 2597 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2598 |
#' |
|
| 2599 |
#' This is a method to simulate dose escalation procedure using both DLE and efficacy responses. |
|
| 2600 |
#' This is a method based on the [`DualResponsesSamplesDesign`] where DLE model used are of |
|
| 2601 |
#' [`ModelTox`] class object and efficacy model used are of [`ModelEff`] |
|
| 2602 |
#' class object (special case is [`EffFlexi`] class model object). |
|
| 2603 |
#' In addition, DLE and efficacy samples are involved or generated in the simulation |
|
| 2604 |
#' process. |
|
| 2605 |
#' |
|
| 2606 |
#' @param object the [`DualResponsesSamplesDesign`] object we want to |
|
| 2607 |
#' simulate the data from |
|
| 2608 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 2609 |
#' @param seed see [set_seed()] |
|
| 2610 |
#' @param trueDLE (`function`)\cr a function which takes as input a dose (vector) and returns the true probability |
|
| 2611 |
#' (vector) of the occurrence of a DLE. Additional arguments can be supplied in `args`. |
|
| 2612 |
#' @param trueEff (`function`)\cr a function which takes as input a dose (vector) and returns the expected |
|
| 2613 |
#' efficacy responses (vector). Additional arguments can be supplied in `args`. |
|
| 2614 |
#' @param trueNu (`number`)\cr (not with [`EffFlexi`]) the precision, the inverse of the |
|
| 2615 |
#' variance of the efficacy responses |
|
| 2616 |
#' @param trueSigma2 (`number`)\cr (only with [`EffFlexi`]) the true variance of the efficacy |
|
| 2617 |
#' responses which must be a single positive scalar. |
|
| 2618 |
#' @param trueSigma2betaW (`number`)\cr (only with [`EffFlexi`]) the true variance for the |
|
| 2619 |
#' random walk model used for smoothing. This must be a single positive scalar. |
|
| 2620 |
#' @param args (`data.frame`)\cr data frame with arguments for the `trueDLE` and |
|
| 2621 |
#' `trueEff` function. The column names correspond to the argument |
|
| 2622 |
#' names, the rows to the values of the arguments. The rows are appropriately |
|
| 2623 |
#' recycled in the `nsim` simulations. |
|
| 2624 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 2625 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 2626 |
#' in this patient. |
|
| 2627 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 2628 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 2629 |
#' the standard options are used |
|
| 2630 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 2631 |
#' clusters of the computer? (not default) |
|
| 2632 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 2633 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 2634 |
#' @param ... not used |
|
| 2635 |
#' |
|
| 2636 |
#' @return an object of class [`PseudoDualSimulations`] or |
|
| 2637 |
#' [`PseudoDualFlexiSimulations`] |
|
| 2638 |
#' |
|
| 2639 |
#' @example examples/design-method-simulateDualResponsesSamplesDesign.R |
|
| 2640 |
#' @export |
|
| 2641 |
setMethod( |
|
| 2642 |
f = "simulate", |
|
| 2643 |
signature = signature( |
|
| 2644 |
object = "DualResponsesSamplesDesign", |
|
| 2645 |
nsim = "ANY", |
|
| 2646 |
seed = "ANY" |
|
| 2647 |
), |
|
| 2648 |
definition = function( |
|
| 2649 |
object, |
|
| 2650 |
nsim = 1L, |
|
| 2651 |
seed = NULL, |
|
| 2652 |
trueDLE, |
|
| 2653 |
trueEff, |
|
| 2654 |
trueNu = NULL, |
|
| 2655 |
trueSigma2 = NULL, |
|
| 2656 |
trueSigma2betaW = NULL, |
|
| 2657 |
args = NULL, |
|
| 2658 |
firstSeparate = FALSE, |
|
| 2659 |
mcmcOptions = McmcOptions(), |
|
| 2660 |
parallel = FALSE, |
|
| 2661 |
nCores = min(parallel::detectCores(), 5L), |
|
| 2662 |
... |
|
| 2663 |
) {
|
|
| 2664 |
# Common checks and validations |
|
| 2665 | 3x |
assert_function(trueDLE) |
| 2666 | 3x |
assert_flag(firstSeparate) |
| 2667 | 3x |
assert_count(nsim, positive = TRUE) |
| 2668 | 3x |
assert_flag(parallel) |
| 2669 | 3x |
assert_count(nCores, positive = TRUE) |
| 2670 | ||
| 2671 |
# Check if special case applies |
|
| 2672 | 3x |
is_flexi <- is(object@eff_model, "EffFlexi") |
| 2673 | ||
| 2674 |
# Dispatch to appropriate helper based on model type |
|
| 2675 | 3x |
if (is_flexi) {
|
| 2676 | 1x |
h_simulate_flexi( |
| 2677 | 1x |
object = object, |
| 2678 | 1x |
nsim = nsim, |
| 2679 | 1x |
seed = seed, |
| 2680 | 1x |
trueDLE = trueDLE, |
| 2681 | 1x |
trueEff = trueEff, |
| 2682 | 1x |
trueSigma2 = trueSigma2, |
| 2683 | 1x |
trueSigma2betaW = trueSigma2betaW, |
| 2684 | 1x |
args = args, |
| 2685 | 1x |
firstSeparate = firstSeparate, |
| 2686 | 1x |
mcmcOptions = mcmcOptions, |
| 2687 | 1x |
parallel = parallel, |
| 2688 | 1x |
nCores = nCores |
| 2689 |
) |
|
| 2690 |
} else {
|
|
| 2691 | 2x |
h_simulate_nonflexi( |
| 2692 | 2x |
object = object, |
| 2693 | 2x |
nsim = nsim, |
| 2694 | 2x |
seed = seed, |
| 2695 | 2x |
trueDLE = trueDLE, |
| 2696 | 2x |
trueEff = trueEff, |
| 2697 | 2x |
trueNu = trueNu, |
| 2698 | 2x |
args = args, |
| 2699 | 2x |
firstSeparate = firstSeparate, |
| 2700 | 2x |
mcmcOptions = mcmcOptions, |
| 2701 | 2x |
parallel = parallel, |
| 2702 | 2x |
nCores = nCores |
| 2703 |
) |
|
| 2704 |
} |
|
| 2705 |
} |
|
| 2706 |
) |
|
| 2707 | ||
| 2708 |
## DADesign ---- |
|
| 2709 | ||
| 2710 |
#' Simulate outcomes from a time-to-DLT augmented CRM design |
|
| 2711 |
#' |
|
| 2712 |
#' @description `r lifecycle::badge("stable")`
|
|
| 2713 |
#' |
|
| 2714 |
#' This method simulates dose escalation trials using time-to-DLT data, |
|
| 2715 |
#' where the timing of dose-limiting toxicities is explicitly modeled. |
|
| 2716 |
#' |
|
| 2717 |
#' @param object the [`DADesign`] object we want to simulate data from |
|
| 2718 |
#' @param nsim (`count`)\cr the number of simulations (default: 1) |
|
| 2719 |
#' @param seed see [set_seed()] |
|
| 2720 |
#' @param truthTox (`function`)\cr a function which takes as input a dose (vector) and returns the |
|
| 2721 |
#' true probability (vector) for toxicity and the time DLT occurs. Additional |
|
| 2722 |
#' arguments can be supplied in `args`. |
|
| 2723 |
#' @param truthSurv (`function`)\cr a CDF which takes as input a time (vector) and returns |
|
| 2724 |
#' the true cumulative probability (vector) that the DLT would occur conditioning on the patient |
|
| 2725 |
#' has DLTs. |
|
| 2726 |
#' @param trueTmax (`number` or `NULL`)\cr the true maximum time at which DLTs can occur. |
|
| 2727 |
#' Note that this must be larger than `Tmax` from the `object`'s base data, which is |
|
| 2728 |
#' the length of the DLT window, i.e. until which time DLTs are officially declared |
|
| 2729 |
#' as such and used in the trial. |
|
| 2730 |
#' @param args (`data.frame`)\cr data frame with arguments for the `truthTox` function. The |
|
| 2731 |
#' column names correspond to the argument names, the rows to the values of the |
|
| 2732 |
#' arguments. The rows are appropriately recycled in the `nsim` |
|
| 2733 |
#' simulations. In order to produce outcomes from the posterior predictive |
|
| 2734 |
#' distribution, e.g, pass an `object` that contains the data observed so |
|
| 2735 |
#' far, `truthTox` contains the `prob` function from the model in |
|
| 2736 |
#' `object`, and `args` contains posterior samples from the model. |
|
| 2737 |
#' @param firstSeparate (`flag`)\cr enroll the first patient separately from the rest of |
|
| 2738 |
#' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
| 2739 |
#' in this patient. |
|
| 2740 |
#' @param deescalate (`flag`)\cr allow deescalation when a DLT occurs in cohorts with lower dose |
|
| 2741 |
#' level? (default: TRUE) |
|
| 2742 |
#' @param mcmcOptions ([McmcOptions])\cr object of class [`McmcOptions`], |
|
| 2743 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 2744 |
#' the standard options are used. |
|
| 2745 |
#' @param DA (`flag`)\cr use dose-adaptation rules? (default: TRUE) |
|
| 2746 |
#' @param parallel (`flag`)\cr should the simulation runs be parallelized across the |
|
| 2747 |
#' clusters of the computer? (not default) |
|
| 2748 |
#' @param nCores (`count`)\cr how many cores should be used for parallel computing? |
|
| 2749 |
#' Defaults to the number of cores on the machine, maximum 5. |
|
| 2750 |
#' @param derive (`list`)\cr a named list of functions which derives statistics, based on the |
|
| 2751 |
#' vector of posterior MTD samples. Each list element must therefore accept |
|
| 2752 |
#' one and only one argument, which is a numeric vector, and return a number. |
|
| 2753 |
#' @param ... not used |
|
| 2754 |
#' |
|
| 2755 |
#' @return an object of class [`Simulations`] |
|
| 2756 |
#' |
|
| 2757 |
#' @example examples/design-method-simulate-DADesign.R |
|
| 2758 |
#' @export |
|
| 2759 |
setMethod( |
|
| 2760 |
f = "simulate", |
|
| 2761 |
signature = signature( |
|
| 2762 |
object = "DADesign", |
|
| 2763 |
nsim = "ANY", |
|
| 2764 |
seed = "ANY" |
|
| 2765 |
), |
|
| 2766 |
definition = function( |
|
| 2767 |
object, |
|
| 2768 |
nsim = 1L, |
|
| 2769 |
seed = NULL, |
|
| 2770 |
truthTox, |
|
| 2771 |
truthSurv, |
|
| 2772 |
trueTmax = NULL, |
|
| 2773 |
args = NULL, |
|
| 2774 |
firstSeparate = FALSE, |
|
| 2775 |
deescalate = TRUE, |
|
| 2776 |
mcmcOptions = McmcOptions(), |
|
| 2777 |
DA = TRUE, |
|
| 2778 |
parallel = FALSE, |
|
| 2779 |
nCores = min(parallel::detectCores(), 5), |
|
| 2780 |
derive = list(), |
|
| 2781 |
... |
|
| 2782 |
) {
|
|
| 2783 |
# Validate inputs |
|
| 2784 | 2x |
assert_function(truthTox) |
| 2785 | 2x |
assert_function(truthSurv) |
| 2786 | 2x |
assert_flag(firstSeparate) |
| 2787 | 2x |
assert_count(nsim, positive = TRUE) |
| 2788 | 2x |
assert_flag(parallel) |
| 2789 | 2x |
assert_count(nCores, positive = TRUE) |
| 2790 | ||
| 2791 | 2x |
args <- as.data.frame(args) |
| 2792 | 2x |
n_args <- max(nrow(args), 1L) |
| 2793 | ||
| 2794 |
# Seed handling |
|
| 2795 | 2x |
rng_state <- set_seed(seed) |
| 2796 | ||
| 2797 |
# Generate individual seeds for simulation runs |
|
| 2798 | 2x |
sim_seeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
| 2799 | ||
| 2800 |
# Define inverse function for DLT survival generation. |
|
| 2801 | 2x |
inverse <- function(f, lower = -100, upper = 100) {
|
| 2802 | 2x |
function(y) {
|
| 2803 | 5x |
uniroot((function(x) f(x) - y), lower = lower, upper = upper)[1]$root |
| 2804 |
} |
|
| 2805 |
} |
|
| 2806 | ||
| 2807 |
# Get DLT window length. |
|
| 2808 | 2x |
data <- object@data |
| 2809 | 2x |
t_max <- data@Tmax |
| 2810 | ||
| 2811 | 2x |
if (is.null(trueTmax)) {
|
| 2812 | ! |
trueTmax <- t_max |
| 2813 | 2x |
} else if (trueTmax < t_max) {
|
| 2814 | ! |
warning("trueTmax < Tmax! trueTmax is set to Tmax")
|
| 2815 | ! |
trueTmax <- t_max |
| 2816 |
} |
|
| 2817 | ||
| 2818 |
# Calculate the inverse function of survival to DLT CDF. |
|
| 2819 | 2x |
inverse_truth_surv <- inverse(truthSurv, 0, trueTmax) |
| 2820 | ||
| 2821 |
# Generate random survival times for DLT data. |
|
| 2822 |
# Returns t_max when no DLT occurs. |
|
| 2823 | 2x |
generate_surv_times <- function( |
| 2824 | 2x |
dlt, |
| 2825 | 2x |
t_max, |
| 2826 | 2x |
inverse_surv = inverse_truth_surv |
| 2827 |
) {
|
|
| 2828 | 33x |
surv_times <- rep(-100, length(dlt)) |
| 2829 | ||
| 2830 | 33x |
if (sum(dlt == 0) > 0) {
|
| 2831 | 29x |
surv_times[dlt == 0] <- t_max |
| 2832 |
} |
|
| 2833 | ||
| 2834 | 33x |
if (sum(dlt == 1) > 0) {
|
| 2835 | 5x |
surv_times[dlt == 1] <- unlist(lapply( |
| 2836 | 5x |
runif(sum(dlt == 1), 0, 1), |
| 2837 | 5x |
inverse_surv |
| 2838 |
)) |
|
| 2839 |
} |
|
| 2840 | ||
| 2841 |
# Ensure results are always positive. |
|
| 2842 | 33x |
surv_times[surv_times == 0] <- 0.5 |
| 2843 | 33x |
surv_times |
| 2844 |
} |
|
| 2845 | ||
| 2846 |
# Check if follow-up requirements are fulfilled for opening next cohort. |
|
| 2847 | 2x |
ready_to_open <- function(day, window, surv_times) {
|
| 2848 | 1221x |
cohort_size <- length(surv_times) |
| 2849 |
# Calculate when patients start. |
|
| 2850 | 1221x |
start_time <- apply( |
| 2851 | 1221x |
rbind(surv_times[-cohort_size], window$patientGap[-1]), |
| 2852 | 1221x |
2, |
| 2853 | 1221x |
min |
| 2854 |
) |
|
| 2855 |
# Calculate relative time for each patient on the specified day. |
|
| 2856 | 1221x |
individual_check <- day - cumsum(c(0, start_time)) |
| 2857 |
# Ensure minimum is 0. |
|
| 2858 | 1221x |
individual_check[individual_check < 0] <- 0 |
| 2859 | 1221x |
follow_up <- apply(rbind(surv_times, individual_check), 2, min) |
| 2860 | ||
| 2861 | 1221x |
all( |
| 2862 | 1221x |
(follow_up - |
| 2863 | 1221x |
apply(rbind(window$patientFollow, surv_times), 2, min)) >= |
| 2864 | 1221x |
0 |
| 2865 |
) & |
|
| 2866 | 1221x |
(max(follow_up) >= min(window$patientFollowMin, max(surv_times))) |
| 2867 |
} |
|
| 2868 | ||
| 2869 |
# Determine when to open the next cohort. |
|
| 2870 |
# Assumes sufficient patients are available for immediate enrollment. |
|
| 2871 | 2x |
next_open <- function(window, surv_times) {
|
| 2872 | 22x |
cohort_size <- length(surv_times) |
| 2873 | ||
| 2874 | 22x |
window$patientGap <- window$patientGap[1:cohort_size] |
| 2875 |
# If DLT happens before end of DLT window, next cohort opens earlier. |
|
| 2876 | 22x |
start_time <- apply( |
| 2877 | 22x |
rbind(surv_times[-cohort_size], window$patientGap[-1]), |
| 2878 | 22x |
2, |
| 2879 | 22x |
min |
| 2880 |
) |
|
| 2881 |
# Duration until all DLT windows finished. |
|
| 2882 | 22x |
max_time <- max(surv_times + cumsum(c(0, start_time))) |
| 2883 | ||
| 2884 | 22x |
requirements_met <- sapply(1:max_time, function(i) {
|
| 2885 | 1221x |
ready_to_open(i, window, surv_times) |
| 2886 |
}) |
|
| 2887 | 22x |
if (sum(requirements_met) > 0) {
|
| 2888 |
# Earliest time that requirements are met. |
|
| 2889 | 22x |
time <- min(c(1:max_time)[requirements_met]) |
| 2890 |
} else {
|
|
| 2891 | ! |
time <- max_time |
| 2892 |
} |
|
| 2893 | 22x |
time |
| 2894 |
} |
|
| 2895 | ||
| 2896 |
# Function to run a single simulation. |
|
| 2897 | 2x |
run_sim <- function(iter_sim) {
|
| 2898 |
# Set the seed for this run. |
|
| 2899 | 4x |
set.seed(sim_seeds[iter_sim]) |
| 2900 | ||
| 2901 |
# Get current arguments (appropriately recycled). |
|
| 2902 | 4x |
current_args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 2903 | ||
| 2904 |
# Truth function with current arguments. |
|
| 2905 | 4x |
truth_with_args <- function(dose) {
|
| 2906 | 24x |
do.call( |
| 2907 | 24x |
truthTox, |
| 2908 | 24x |
c( |
| 2909 | 24x |
dose, |
| 2910 | 24x |
current_args |
| 2911 |
) |
|
| 2912 |
) |
|
| 2913 |
} |
|
| 2914 | ||
| 2915 |
# Start with the provided data. |
|
| 2916 | 4x |
data <- object@data |
| 2917 | ||
| 2918 |
# Handle placebo if present. |
|
| 2919 | 4x |
if (data@placebo) {
|
| 2920 | 2x |
prob_pl <- truth_with_args(object@data@doseGrid[1]) |
| 2921 |
} |
|
| 2922 | ||
| 2923 |
# Trial control variables. |
|
| 2924 | 4x |
should_stop <- FALSE |
| 2925 | 4x |
trial_time <- 0 |
| 2926 | ||
| 2927 |
# Initialize observed DLT data. |
|
| 2928 | 4x |
observed_dlts <- data@y |
| 2929 | 4x |
observed_surv <- data@u |
| 2930 | 4x |
observed_t0 <- data@t0 |
| 2931 | ||
| 2932 |
# Initialize with starting dose. |
|
| 2933 | 4x |
dose <- object@startingDose |
| 2934 | ||
| 2935 |
# Main simulation loop. |
|
| 2936 | 4x |
while (!should_stop) {
|
| 2937 |
# Calculate toxicity probability at current dose. |
|
| 2938 | 22x |
prob <- truth_with_args(dose) |
| 2939 | ||
| 2940 |
# Determine cohort size. |
|
| 2941 | 22x |
cohort_size <- size(object@cohort_size, dose = dose, data = data) |
| 2942 | ||
| 2943 | 22x |
if (data@placebo) {
|
| 2944 | 9x |
placebo_size <- size( |
| 2945 | 9x |
object@pl_cohort_size, |
| 2946 | 9x |
dose = dose, |
| 2947 | 9x |
data = data |
| 2948 |
) |
|
| 2949 |
} |
|
| 2950 | ||
| 2951 | 22x |
total_size <- if (data@placebo) {
|
| 2952 | 9x |
cohort_size + placebo_size |
| 2953 |
} else {
|
|
| 2954 | 13x |
cohort_size |
| 2955 |
} |
|
| 2956 | ||
| 2957 | 22x |
safety_window <- windowLength(object@safetyWindow, total_size) |
| 2958 | ||
| 2959 |
# Simulate DLTs for cohort. |
|
| 2960 |
# If any patient has DLT before first patient finishes staggered window, |
|
| 2961 |
# further enrollment will be stopped. |
|
| 2962 | 22x |
h_generate_dlt_and_surv <- function(n, prob, start = NULL) {
|
| 2963 | 33x |
dlts <- rbinom( |
| 2964 | 33x |
n = n, |
| 2965 | 33x |
size = 1L, |
| 2966 | 33x |
prob = prob |
| 2967 |
) |
|
| 2968 | 33x |
surv_times <- ceiling(generate_surv_times( |
| 2969 | 33x |
dlts, |
| 2970 | 33x |
trueTmax, |
| 2971 | 33x |
inverse_surv = inverse_truth_surv |
| 2972 |
)) |
|
| 2973 | ||
| 2974 | 33x |
if (!is.null(start)) {
|
| 2975 | 2x |
dlts <- c(start$dlts, dlts) |
| 2976 | 2x |
surv_times <- c(start$surv, surv_times) |
| 2977 |
} |
|
| 2978 | ||
| 2979 | 33x |
if (t_max < trueTmax) {
|
| 2980 | 33x |
dlts[dlts == 1 & surv_times > t_max] <- 0 |
| 2981 | ||
| 2982 | 33x |
surv_times <- apply( |
| 2983 | 33x |
rbind(surv_times, rep(t_max, length(surv_times))), |
| 2984 | 33x |
2, |
| 2985 | 33x |
min |
| 2986 |
) |
|
| 2987 |
} |
|
| 2988 | ||
| 2989 | 33x |
list(dlts = dlts, surv = surv_times) |
| 2990 |
} |
|
| 2991 | ||
| 2992 |
# Update data with active and placebo cohorts. |
|
| 2993 | 22x |
h_update_data_da <- function(active, placebo, time) {
|
| 2994 | 5x |
result <- update( |
| 2995 | 5x |
object = data, |
| 2996 | 5x |
y = c(observed_dlts, active$dlts), |
| 2997 | 5x |
u = c(observed_surv, active$surv), |
| 2998 | 5x |
t0 = c(observed_t0, cohort_t0), |
| 2999 | 5x |
x = dose, |
| 3000 | 5x |
trialtime = time |
| 3001 |
) |
|
| 3002 | ||
| 3003 | 5x |
if (data@placebo) {
|
| 3004 | ! |
result <- update( |
| 3005 | ! |
object = result, |
| 3006 | ! |
y = c(observed_dlts, active$dlts, placebo$dlts), |
| 3007 | ! |
u = c(observed_surv, active$surv, placebo$surv), |
| 3008 | ! |
t0 = c( |
| 3009 | ! |
observed_t0, |
| 3010 | ! |
cohort_t0, |
| 3011 | ! |
rep(cohort_t0[1], length(placebo$dlts)) |
| 3012 |
), |
|
| 3013 | ! |
x = object@data@doseGrid[1], |
| 3014 | ! |
trialtime = time |
| 3015 |
) |
|
| 3016 |
} |
|
| 3017 | ||
| 3018 | 5x |
result |
| 3019 |
} |
|
| 3020 | ||
| 3021 | 22x |
if (firstSeparate && (cohort_size > 1L)) {
|
| 3022 |
# Dose the first patient. |
|
| 3023 | 5x |
active_dlt_surv <- h_generate_dlt_and_surv(1L, prob) |
| 3024 | 5x |
placebo_dlt_surv <- if (data@placebo && (placebo_size > 0L)) {
|
| 3025 |
# If placebo, also dose one placebo patient. |
|
| 3026 | ! |
h_generate_dlt_and_surv(1L, prob_pl) |
| 3027 |
} else {
|
|
| 3028 | 5x |
list() |
| 3029 |
} |
|
| 3030 | ||
| 3031 | 5x |
cohort_t0 <- trial_time |
| 3032 | ||
| 3033 |
# Check if there are DLTs during safety window. |
|
| 3034 | 5x |
temp_data <- h_update_data_da( |
| 3035 | 5x |
active_dlt_surv, |
| 3036 | 5x |
placebo_dlt_surv, |
| 3037 | 5x |
trial_time + safety_window$patientGap[2] |
| 3038 |
) |
|
| 3039 | 5x |
temp_time <- (temp_data@u + temp_data@t0)[ |
| 3040 | 5x |
temp_data@y == 1 & temp_data@x <= dose |
| 3041 |
] |
|
| 3042 | ||
| 3043 |
# If no DLTs occur during safety window, enroll remaining patients. |
|
| 3044 | 5x |
if (sum(temp_time > trial_time) == 0) {
|
| 3045 |
# Enroll the remaining patients. |
|
| 3046 | 2x |
active_dlt_surv <- h_generate_dlt_and_surv( |
| 3047 | 2x |
cohort_size - 1L, |
| 3048 | 2x |
prob, |
| 3049 | 2x |
start = active_dlt_surv |
| 3050 |
) |
|
| 3051 | 2x |
placebo_dlt_surv <- if (data@placebo && (placebo_size > 1L)) {
|
| 3052 | ! |
h_generate_dlt_and_surv( |
| 3053 | ! |
placebo_size - 1L, |
| 3054 | ! |
prob_pl, |
| 3055 | ! |
start = placebo_dlt_surv |
| 3056 |
) |
|
| 3057 |
} else {
|
|
| 3058 | 2x |
list() |
| 3059 |
} |
|
| 3060 | ||
| 3061 |
# Adjust for DLTs happening before end of safety window. |
|
| 3062 | 2x |
real_window <- apply( |
| 3063 | 2x |
rbind( |
| 3064 | 2x |
c(active_dlt_surv$surv, placebo_dlt_surv$surv)[-cohort_size], |
| 3065 | 2x |
safety_window$patientGap[-1] |
| 3066 |
), |
|
| 3067 | 2x |
2, |
| 3068 | 2x |
min |
| 3069 |
) |
|
| 3070 | ||
| 3071 | 2x |
cohort_t0 <- trial_time + c(0, cumsum(real_window)) |
| 3072 |
} |
|
| 3073 | ||
| 3074 | 5x |
rm(temp_data) |
| 3075 | 5x |
rm(temp_time) |
| 3076 |
} else {
|
|
| 3077 |
# Directly dose all patients. |
|
| 3078 | 17x |
active_dlt_surv <- h_generate_dlt_and_surv( |
| 3079 | 17x |
cohort_size, |
| 3080 | 17x |
prob |
| 3081 |
) |
|
| 3082 | 17x |
placebo_dlt_surv <- if (data@placebo) {
|
| 3083 | 9x |
h_generate_dlt_and_surv( |
| 3084 | 9x |
placebo_size, |
| 3085 | 9x |
prob_pl |
| 3086 |
) |
|
| 3087 |
} else {
|
|
| 3088 | 8x |
list() |
| 3089 |
} |
|
| 3090 | ||
| 3091 |
# Adjust for DLTs happening before end of safety window. |
|
| 3092 | 17x |
real_window <- apply( |
| 3093 | 17x |
rbind( |
| 3094 | 17x |
c(active_dlt_surv$surv, placebo_dlt_surv$surv)[-cohort_size], |
| 3095 | 17x |
safety_window$patientGap[-1] |
| 3096 |
), |
|
| 3097 | 17x |
2, |
| 3098 | 17x |
min |
| 3099 |
) |
|
| 3100 | ||
| 3101 | 17x |
cohort_t0 <- trial_time + c(0, cumsum(real_window)) |
| 3102 |
} |
|
| 3103 | ||
| 3104 |
# Update observed data with new cohort. |
|
| 3105 | 22x |
old_dlts <- observed_dlts |
| 3106 | ||
| 3107 | 22x |
observed_dlts <- c( |
| 3108 | 22x |
observed_dlts, |
| 3109 | 22x |
placebo_dlt_surv$dlts, |
| 3110 | 22x |
active_dlt_surv$dlts |
| 3111 |
) |
|
| 3112 | ||
| 3113 | 22x |
observed_surv <- c( |
| 3114 | 22x |
observed_surv, |
| 3115 | 22x |
placebo_dlt_surv$surv, |
| 3116 | 22x |
active_dlt_surv$surv |
| 3117 |
) |
|
| 3118 | ||
| 3119 | 22x |
observed_t0 <- c( |
| 3120 | 22x |
observed_t0, |
| 3121 | 22x |
rep(cohort_t0[1], length(placebo_dlt_surv$dlts)), |
| 3122 | 22x |
rep(cohort_t0, length.out = length(active_dlt_surv$dlts)) |
| 3123 |
) |
|
| 3124 | ||
| 3125 | 22x |
time_to_next <- next_open( |
| 3126 | 22x |
window = safety_window, |
| 3127 | 22x |
surv_times = c(placebo_dlt_surv$surv, active_dlt_surv$surv) |
| 3128 |
) |
|
| 3129 | ||
| 3130 |
# Handle deescalation if DLTs occur in previous cohorts. |
|
| 3131 | 22x |
if (deescalate == TRUE) {
|
| 3132 | 9x |
are_dlts_after_trial_start <- (observed_surv + observed_t0) > |
| 3133 | 9x |
trial_time |
| 3134 | 9x |
are_dlts_before_open_next_cohort <- (observed_surv + |
| 3135 | 9x |
observed_t0 - |
| 3136 | 9x |
trial_time) <= |
| 3137 | 9x |
time_to_next |
| 3138 | 9x |
are_dlts_happening <- observed_dlts == 1 |
| 3139 | 9x |
is_new_dlt <- (are_dlts_after_trial_start & |
| 3140 | 9x |
are_dlts_before_open_next_cohort & |
| 3141 | 9x |
are_dlts_happening) |
| 3142 | ||
| 3143 | 9x |
new_dlt_ids <- seq_along(observed_dlts)[is_new_dlt] |
| 3144 | 9x |
last_id_previous_cohort <- length(old_dlts) |
| 3145 | 9x |
is_new_dlt_in_previous_cohort <- new_dlt_ids <= |
| 3146 | 9x |
last_id_previous_cohort |
| 3147 | ||
| 3148 | 9x |
new_dlt_ids <- new_dlt_ids[is_new_dlt_in_previous_cohort] |
| 3149 | ||
| 3150 | 9x |
if (length(new_dlt_ids) > 0) {
|
| 3151 | ! |
for (this_new_dlt_id in new_dlt_ids) {
|
| 3152 | ! |
this_new_dlt_time <- (observed_surv + observed_t0)[ |
| 3153 | ! |
this_new_dlt_id |
| 3154 |
] |
|
| 3155 | ||
| 3156 |
# Identify patients at higher doses who are impacted. |
|
| 3157 | ! |
later_ids <- c(this_new_dlt_id:length(observed_dlts)) |
| 3158 | ! |
all_doses <- c(data@x, rep(dose, length(active_dlt_surv$dlts))) |
| 3159 | ! |
this_new_dlt_dose <- all_doses[this_new_dlt_id] |
| 3160 | ! |
is_dose_higher_than_this_new_dlt_dose <- all_doses[later_ids] > |
| 3161 | ! |
this_new_dlt_dose |
| 3162 | ! |
ids_to_deescalate <- later_ids[ |
| 3163 | ! |
is_dose_higher_than_this_new_dlt_dose |
| 3164 |
] |
|
| 3165 | ||
| 3166 | ! |
if (length(ids_to_deescalate) > 0) {
|
| 3167 |
# DLT will be observed once follow-up time >= time to DLT. |
|
| 3168 | ! |
this_new_dlt_time_after_followup <- this_new_dlt_time >= |
| 3169 | ! |
(observed_t0[ids_to_deescalate] + |
| 3170 | ! |
observed_surv[ids_to_deescalate]) |
| 3171 | ! |
observed_dlts[ids_to_deescalate] <- as.integer( |
| 3172 | ! |
observed_dlts[ids_to_deescalate] * |
| 3173 | ! |
this_new_dlt_time_after_followup |
| 3174 |
) |
|
| 3175 | ||
| 3176 |
# Some patients in later cohorts may not be enrolled yet when new DLT occurs. |
|
| 3177 |
# Remove those patients from the cohort. |
|
| 3178 | ! |
ids_not_enrolled <- ids_to_deescalate[ |
| 3179 | ! |
(observed_t0[ids_to_deescalate] >= this_new_dlt_time) |
| 3180 |
] |
|
| 3181 | ||
| 3182 | ! |
ids_enrolled <- setdiff( |
| 3183 | ! |
ids_to_deescalate, |
| 3184 | ! |
ids_not_enrolled |
| 3185 |
) |
|
| 3186 | ||
| 3187 |
# Update DLT-free survival time for already enrolled patients. |
|
| 3188 | ! |
if (length(ids_enrolled) > 0) {
|
| 3189 | ! |
surv_time <- pmin( |
| 3190 | ! |
observed_surv[ids_enrolled], |
| 3191 | ! |
this_new_dlt_time - observed_t0[ids_enrolled] |
| 3192 |
) |
|
| 3193 | ||
| 3194 | ! |
assert_true(surv_time >= 0) |
| 3195 | ||
| 3196 | ! |
observed_surv[ids_enrolled] <- surv_time |
| 3197 |
} |
|
| 3198 | ||
| 3199 |
# Remove patients not yet enrolled. |
|
| 3200 | ! |
if (length(ids_not_enrolled) > 0) {
|
| 3201 | ! |
observed_surv <- observed_surv[-ids_not_enrolled] |
| 3202 | ! |
observed_t0 <- observed_t0[-ids_not_enrolled] |
| 3203 | ! |
observed_dlts <- observed_dlts[-ids_not_enrolled] |
| 3204 |
} |
|
| 3205 |
} |
|
| 3206 |
} |
|
| 3207 | ||
| 3208 | ! |
time_to_next <- min( |
| 3209 | ! |
time_to_next, |
| 3210 | ! |
max((observed_surv + observed_t0)[ |
| 3211 | ! |
(length(old_dlts) + 1):length(observed_dlts) |
| 3212 |
]) - |
|
| 3213 | ! |
trial_time |
| 3214 |
) |
|
| 3215 |
} |
|
| 3216 |
} |
|
| 3217 | ||
| 3218 |
# Update trial time. |
|
| 3219 | 22x |
trial_time <- trial_time + time_to_next |
| 3220 | ||
| 3221 |
# Update data object with observations available when next cohort opens. |
|
| 3222 | 22x |
if (data@placebo) {
|
| 3223 |
# First patients are from placebo. |
|
| 3224 | 9x |
data <- update( |
| 3225 | 9x |
object = data, |
| 3226 | 9x |
y = head(observed_dlts, -length(active_dlt_surv$dlts)), |
| 3227 | 9x |
u = head(observed_surv, -length(active_dlt_surv$surv)), |
| 3228 | 9x |
t0 = head(observed_t0, -length(active_dlt_surv$surv)), |
| 3229 | 9x |
x = object@data@doseGrid[1], |
| 3230 | 9x |
trialtime = trial_time |
| 3231 |
) |
|
| 3232 |
} |
|
| 3233 | 22x |
data <- update( |
| 3234 | 22x |
object = data, |
| 3235 | 22x |
y = observed_dlts, |
| 3236 | 22x |
u = observed_surv, |
| 3237 | 22x |
t0 = observed_t0, |
| 3238 | 22x |
x = dose, |
| 3239 | 22x |
trialtime = trial_time |
| 3240 |
) |
|
| 3241 | ||
| 3242 | 22x |
try( |
| 3243 | 22x |
if ( |
| 3244 | 22x |
length(data@x) != length(data@u) || |
| 3245 | 22x |
length(data@u) != length(data@y) |
| 3246 |
) {
|
|
| 3247 | ! |
stop("x,y,u dimension error")
|
| 3248 |
} |
|
| 3249 |
) |
|
| 3250 | ||
| 3251 |
# Calculate dose limit. |
|
| 3252 | 22x |
dose_limit <- maxDose(object@increments, data = data) |
| 3253 | ||
| 3254 |
# Generate MCMC samples from model. |
|
| 3255 | 22x |
if (DA == TRUE) {
|
| 3256 | 22x |
samples <- mcmc( |
| 3257 | 22x |
data = data, |
| 3258 | 22x |
model = object@model, |
| 3259 | 22x |
options = mcmcOptions |
| 3260 |
) |
|
| 3261 | ! |
} else if (DA == FALSE) {
|
| 3262 | ! |
temp_model <- LogisticLogNormal( |
| 3263 | ! |
mean = object@model@params@mean, |
| 3264 | ! |
cov = object@model@params@cov, |
| 3265 | ! |
ref_dose = object@model@refDose |
| 3266 |
) |
|
| 3267 | ||
| 3268 | ! |
truncated_data <- Data( |
| 3269 | ! |
x = data@x, |
| 3270 | ! |
y = data@y, |
| 3271 | ! |
doseGrid = data@doseGrid, |
| 3272 | ! |
cohort = data@cohort, |
| 3273 | ! |
ID = data@ID |
| 3274 |
) |
|
| 3275 | ||
| 3276 | ! |
samples <- mcmc( |
| 3277 | ! |
data = truncated_data, |
| 3278 | ! |
model = temp_model, |
| 3279 | ! |
options = mcmcOptions |
| 3280 |
) |
|
| 3281 |
} |
|
| 3282 | ||
| 3283 |
# Calculate next best dose. |
|
| 3284 | 22x |
dose <- nextBest( |
| 3285 | 22x |
object@nextBest, |
| 3286 | 22x |
doselimit = dose_limit, |
| 3287 | 22x |
samples = samples, |
| 3288 | 22x |
model = object@model, |
| 3289 | 22x |
data = data |
| 3290 | 22x |
)$value |
| 3291 | ||
| 3292 |
# Evaluate stopping rules. |
|
| 3293 | 22x |
should_stop <- stopTrial( |
| 3294 | 22x |
object@stopping, |
| 3295 | 22x |
dose = dose, |
| 3296 | 22x |
samples = samples, |
| 3297 | 22x |
model = object@model, |
| 3298 | 22x |
data = data |
| 3299 |
) |
|
| 3300 | 22x |
stop_results <- h_unpack_stopit(should_stop) |
| 3301 |
} |
|
| 3302 | ||
| 3303 |
# Calculate final model fit. |
|
| 3304 | 4x |
fit_result <- fit( |
| 3305 | 4x |
object = samples, |
| 3306 | 4x |
model = object@model, |
| 3307 | 4x |
data = data |
| 3308 |
) |
|
| 3309 | ||
| 3310 |
# Get MTD estimate from samples. |
|
| 3311 | 4x |
target_dose_samples <- dose( |
| 3312 | 4x |
mean(object@nextBest@target), |
| 3313 | 4x |
model = object@model, |
| 3314 | 4x |
samples = samples |
| 3315 |
) |
|
| 3316 | ||
| 3317 |
# Calculate additional statistics. |
|
| 3318 | 4x |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
| 3319 | ||
| 3320 |
# Return simulation results. |
|
| 3321 | 4x |
list( |
| 3322 | 4x |
data = data, |
| 3323 | 4x |
dose = dose, |
| 3324 | 4x |
duration = trial_time, |
| 3325 | 4x |
fit = subset(fit_result, select = c(middle, lower, upper)), |
| 3326 | 4x |
stop = attr( |
| 3327 | 4x |
should_stop, |
| 3328 | 4x |
"message" |
| 3329 |
), |
|
| 3330 | 4x |
report_results = stop_results, |
| 3331 | 4x |
additional_stats = additional_stats |
| 3332 |
) |
|
| 3333 |
} |
|
| 3334 | ||
| 3335 | 2x |
result_list <- get_result_list( |
| 3336 | 2x |
fun = run_sim, |
| 3337 | 2x |
nsim = nsim, |
| 3338 | 2x |
vars = c( |
| 3339 | 2x |
"sim_seeds", |
| 3340 | 2x |
"args", |
| 3341 | 2x |
"n_args", |
| 3342 | 2x |
"firstSeparate", |
| 3343 | 2x |
"truthTox", |
| 3344 | 2x |
"truthSurv", |
| 3345 | 2x |
"object", |
| 3346 | 2x |
"mcmcOptions", |
| 3347 | 2x |
"next_open", |
| 3348 | 2x |
"ready_to_open" |
| 3349 |
), |
|
| 3350 | 2x |
parallel = parallel, |
| 3351 | 2x |
n_cores = nCores |
| 3352 |
) |
|
| 3353 | ||
| 3354 |
# Process simulation results. |
|
| 3355 | 2x |
data_list <- lapply(result_list, "[[", "data") |
| 3356 | 2x |
recommended_doses <- as.numeric(sapply(result_list, "[[", "dose")) |
| 3357 | 2x |
trial_duration <- as.numeric(sapply(result_list, "[[", "duration")) |
| 3358 | 2x |
fit_list <- lapply(result_list, "[[", "fit") |
| 3359 | ||
| 3360 | 2x |
stop_reasons <- lapply(result_list, "[[", "stop") |
| 3361 | ||
| 3362 | 2x |
stop_results <- lapply(result_list, "[[", "report_results") |
| 3363 | 2x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
| 3364 | ||
| 3365 | 2x |
additional_stats <- lapply(result_list, "[[", "additional_stats") |
| 3366 | ||
| 3367 |
# Return simulation results. |
|
| 3368 | 2x |
DASimulations( |
| 3369 | 2x |
data = data_list, |
| 3370 | 2x |
doses = recommended_doses, |
| 3371 | 2x |
fit = fit_list, |
| 3372 | 2x |
trial_duration = trial_duration, |
| 3373 | 2x |
stop_report = stop_report, |
| 3374 | 2x |
stop_reasons = stop_reasons, |
| 3375 | 2x |
additional_stats = additional_stats, |
| 3376 | 2x |
seed = rng_state |
| 3377 |
) |
|
| 3378 |
} |
|
| 3379 |
) |
|
| 3380 | ||
| 3381 |
## DesignGrouped ---- |
|
| 3382 | ||
| 3383 |
#' Simulate Method for the [`DesignGrouped`] Class |
|
| 3384 |
#' |
|
| 3385 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 3386 |
#' |
|
| 3387 |
#' A simulate method for [`DesignGrouped`] designs. |
|
| 3388 |
#' |
|
| 3389 |
#' @param object (`DesignGrouped`)\cr the design we want to simulate trials from. |
|
| 3390 |
#' @param nsim (`number`)\cr how many trials should be simulated. |
|
| 3391 |
#' @param seed (`RNGstate`)\cr generated with [set_seed()]. |
|
| 3392 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and |
|
| 3393 |
#' returns the true probability (vector) for toxicity for the mono arm. |
|
| 3394 |
#' Additional arguments can be supplied in `args`. |
|
| 3395 |
#' @param combo_truth (`function`)\cr same as `truth` but for the combo arm. |
|
| 3396 |
#' @param args (`data.frame`)\cr optional `data.frame` with arguments that work |
|
| 3397 |
#' for both the `truth` and `combo_truth` functions. The column names correspond to |
|
| 3398 |
#' the argument names, the rows to the values of the arguments. The rows are |
|
| 3399 |
#' appropriately recycled in the `nsim` simulations. |
|
| 3400 |
#' @param firstSeparate (`flag`)\cr whether to enroll the first patient separately |
|
| 3401 |
#' from the rest of the cohort and close the cohort in case a DLT occurs in this |
|
| 3402 |
#' first patient. |
|
| 3403 |
#' @param mcmcOptions (`McmcOptions`)\cr MCMC options for each evaluation in the trial. |
|
| 3404 |
#' @param parallel (`flag`)\cr whether the simulation runs are parallelized across the |
|
| 3405 |
#' cores of the computer. |
|
| 3406 |
#' @param nCores (`number`)\cr how many cores should be used for parallel computing. |
|
| 3407 |
#' @param ... not used. |
|
| 3408 |
#' |
|
| 3409 |
#' @return A list of `mono` and `combo` simulation results as [`Simulations`] objects. |
|
| 3410 |
#' |
|
| 3411 |
#' @aliases simulate-DesignGrouped |
|
| 3412 |
#' @export |
|
| 3413 |
#' @example examples/Design-method-simulate-DesignGrouped.R |
|
| 3414 |
#' |
|
| 3415 |
setMethod( |
|
| 3416 |
"simulate", |
|
| 3417 |
signature = signature( |
|
| 3418 |
object = "DesignGrouped", |
|
| 3419 |
nsim = "ANY", |
|
| 3420 |
seed = "ANY" |
|
| 3421 |
), |
|
| 3422 |
def = function( |
|
| 3423 |
object, |
|
| 3424 |
nsim = 1L, |
|
| 3425 |
seed = NULL, |
|
| 3426 |
truth, |
|
| 3427 |
combo_truth, |
|
| 3428 |
args = data.frame(), |
|
| 3429 |
firstSeparate = FALSE, |
|
| 3430 |
mcmcOptions = McmcOptions(), |
|
| 3431 |
parallel = FALSE, |
|
| 3432 |
nCores = min(parallelly::availableCores(), 5), |
|
| 3433 |
... |
|
| 3434 |
) {
|
|
| 3435 | 9x |
nsim <- as.integer(nsim) |
| 3436 | 9x |
assert_function(truth) |
| 3437 | 9x |
assert_function(combo_truth) |
| 3438 | 9x |
assert_data_frame(args) |
| 3439 | 9x |
assert_count(nsim, positive = TRUE) |
| 3440 | 9x |
assert_flag(firstSeparate) |
| 3441 | 9x |
assert_flag(parallel) |
| 3442 | 9x |
assert_count(nCores, positive = TRUE) |
| 3443 | ||
| 3444 | 9x |
n_args <- max(nrow(args), 1L) |
| 3445 | 9x |
rng_state <- set_seed(seed) |
| 3446 | 9x |
sim_seeds <- sample.int(n = 2147483647, size = nsim) |
| 3447 | ||
| 3448 | 9x |
run_sim <- function(iter_sim) {
|
| 3449 | 16x |
set.seed(sim_seeds[iter_sim]) |
| 3450 | 16x |
current <- list(mono = list(), combo = list()) |
| 3451 |
# Define true toxicity functions. |
|
| 3452 | 16x |
current$args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
| 3453 | 16x |
current$mono$truth <- function(dose) do.call(truth, c(dose, current$args)) |
| 3454 | 16x |
current$combo$truth <- function(dose) {
|
| 3455 | 63x |
do.call(combo_truth, c(dose, current$args)) |
| 3456 |
} |
|
| 3457 |
# Start the simulated data with the provided one. |
|
| 3458 | 16x |
current$mono$data <- object@mono@data |
| 3459 | 16x |
current$combo$data <- object@combo@data |
| 3460 |
# We are in the first cohort and continue for mono and combo. |
|
| 3461 | 16x |
current$first <- TRUE |
| 3462 | 16x |
current$mono$stop <- current$combo$stop <- FALSE |
| 3463 | ||
| 3464 |
# What are the next doses to be used? Initialize with starting doses. |
|
| 3465 | 9x |
if ( |
| 3466 | 16x |
object@same_dose_for_all || |
| 3467 | 16x |
(!object@first_cohort_mono_only && object@same_dose_for_start) |
| 3468 |
) {
|
|
| 3469 | 6x |
current$mono$dose <- current$combo$dose <- min( |
| 3470 | 6x |
object@mono@startingDose, |
| 3471 | 6x |
object@combo@startingDose |
| 3472 |
) |
|
| 3473 |
} else {
|
|
| 3474 | 10x |
current$mono$dose <- object@mono@startingDose |
| 3475 | 10x |
current$combo$dose <- object@combo@startingDose |
| 3476 |
} |
|
| 3477 | ||
| 3478 |
# Inside this loop we simulate the whole trial, until stopping. |
|
| 3479 | 16x |
while (!(current$mono$stop && current$combo$stop)) {
|
| 3480 | 71x |
if (!current$mono$stop) {
|
| 3481 | 55x |
cohort_size_mono <- size( |
| 3482 | 55x |
object@mono@cohort_size, |
| 3483 | 55x |
dose = current$mono$dose, |
| 3484 | 55x |
data = current$mono$data |
| 3485 |
) |
|
| 3486 | 55x |
this_prob_mono <- current$mono$truth(current$mono$dose) |
| 3487 | 55x |
current$mono$data <- current$mono$data %>% |
| 3488 | 55x |
h_determine_dlts( |
| 3489 | 55x |
dose = current$mono$dose, |
| 3490 | 55x |
prob = this_prob_mono, |
| 3491 | 55x |
cohort_size = cohort_size_mono, |
| 3492 | 55x |
first_separate = firstSeparate |
| 3493 |
) |
|
| 3494 |
} |
|
| 3495 | 16x |
if ( |
| 3496 | 71x |
!current$combo$stop && |
| 3497 | 71x |
(!current$first || !object@first_cohort_mono_only) |
| 3498 |
) {
|
|
| 3499 | 63x |
cohort_size_combo <- size( |
| 3500 | 63x |
object@combo@cohort_size, |
| 3501 | 63x |
dose = current$combo$dose, |
| 3502 | 63x |
data = current$combo$data |
| 3503 |
) |
|
| 3504 | 63x |
this_prob_combo <- current$combo$truth(current$combo$dose) |
| 3505 | 63x |
current$combo$data <- current$combo$data %>% |
| 3506 | 63x |
h_determine_dlts( |
| 3507 | 63x |
dose = current$combo$dose, |
| 3508 | 63x |
prob = this_prob_combo, |
| 3509 | 63x |
cohort_size = cohort_size_combo, |
| 3510 | 63x |
first_separate = firstSeparate |
| 3511 |
) |
|
| 3512 |
} |
|
| 3513 | ||
| 3514 | 71x |
current$grouped <- h_group_data(current$mono$data, current$combo$data) |
| 3515 | 71x |
current$samples <- mcmc(current$grouped, object@model, mcmcOptions) |
| 3516 | 71x |
if (!current$mono$stop) {
|
| 3517 | 55x |
current$mono$limit <- maxDose( |
| 3518 | 55x |
object@mono@increments, |
| 3519 | 55x |
data = current$mono$data |
| 3520 |
) |
|
| 3521 | 55x |
current$mono$dose <- object@mono@nextBest %>% |
| 3522 | 55x |
nextBest( |
| 3523 | 55x |
current$mono$limit, |
| 3524 | 55x |
current$samples, |
| 3525 | 55x |
object@model, |
| 3526 | 55x |
current$grouped, |
| 3527 | 55x |
group = "mono" |
| 3528 |
) |
|
| 3529 | 55x |
current$mono$dose <- current$mono$dose$value |
| 3530 |
} |
|
| 3531 | 16x |
if ( |
| 3532 | 71x |
!current$combo$stop && |
| 3533 | 71x |
(!current$first || !object@first_cohort_mono_only) |
| 3534 |
) {
|
|
| 3535 | 63x |
current$combo$limit <- if (is.na(current$mono$dose)) {
|
| 3536 | ! |
0 |
| 3537 |
} else {
|
|
| 3538 | 63x |
maxDose(object@combo@increments, current$combo$data) %>% |
| 3539 | 63x |
min(current$mono$dose, na.rm = TRUE) |
| 3540 |
} |
|
| 3541 | 63x |
current$combo$dose <- object@combo@nextBest %>% |
| 3542 | 63x |
nextBest( |
| 3543 | 63x |
current$combo$limit, |
| 3544 | 63x |
current$samples, |
| 3545 | 63x |
object@model, |
| 3546 | 63x |
current$grouped, |
| 3547 | 63x |
group = "combo" |
| 3548 |
) |
|
| 3549 | 63x |
current$combo$dose <- current$combo$dose$value |
| 3550 | 63x |
current$combo$stop <- object@combo@stopping %>% |
| 3551 | 63x |
stopTrial( |
| 3552 | 63x |
current$combo$dose, |
| 3553 | 63x |
current$samples, |
| 3554 | 63x |
object@model, |
| 3555 | 63x |
current$combo$data, |
| 3556 | 63x |
group = "combo" |
| 3557 |
) |
|
| 3558 | 63x |
current$combo$results <- h_unpack_stopit(current$combo$stop) |
| 3559 |
} |
|
| 3560 | 71x |
if (!current$mono$stop) {
|
| 3561 | 55x |
current$mono$stop <- object@mono@stopping %>% |
| 3562 | 55x |
stopTrial( |
| 3563 | 55x |
current$mono$dose, |
| 3564 | 55x |
current$samples, |
| 3565 | 55x |
object@model, |
| 3566 | 55x |
current$mono$data, |
| 3567 | 55x |
group = "mono", |
| 3568 | 55x |
external = current$combo$stop |
| 3569 |
) |
|
| 3570 | 55x |
current$mono$results <- h_unpack_stopit(current$mono$stop) |
| 3571 |
} |
|
| 3572 | 16x |
if ( |
| 3573 | 71x |
object@same_dose_for_all && !current$mono$stop && !current$combo$stop |
| 3574 |
) {
|
|
| 3575 | 16x |
current$mono$dose <- current$combo$dose <- min( |
| 3576 | 16x |
current$mono$dose, |
| 3577 | 16x |
current$combo$dose |
| 3578 |
) |
|
| 3579 |
} |
|
| 3580 | 71x |
if (current$first) {
|
| 3581 | 16x |
current$first <- FALSE |
| 3582 | 16x |
if (object@first_cohort_mono_only && object@same_dose_for_start) {
|
| 3583 | 2x |
current$mono$dose <- current$combo$dose <- min( |
| 3584 | 2x |
current$mono$dose, |
| 3585 | 2x |
current$combo$dose |
| 3586 |
) |
|
| 3587 |
} |
|
| 3588 |
} |
|
| 3589 |
} |
|
| 3590 | 16x |
current$mono$fit <- fit( |
| 3591 | 16x |
current$samples, |
| 3592 | 16x |
object@model, |
| 3593 | 16x |
current$grouped, |
| 3594 | 16x |
group = "mono" |
| 3595 |
) |
|
| 3596 | 16x |
current$combo$fit <- fit( |
| 3597 | 16x |
current$samples, |
| 3598 | 16x |
object@model, |
| 3599 | 16x |
current$grouped, |
| 3600 | 16x |
group = "combo" |
| 3601 |
) |
|
| 3602 | 16x |
lapply( |
| 3603 | 16x |
X = current[c("mono", "combo")],
|
| 3604 | 16x |
FUN = with, |
| 3605 | 16x |
list( |
| 3606 | 16x |
data = data, |
| 3607 | 16x |
dose = dose, |
| 3608 | 16x |
fit = subset(fit, select = -dose), |
| 3609 | 16x |
stop = attr(stop, "message"), |
| 3610 | 16x |
results = results |
| 3611 |
) |
|
| 3612 |
) |
|
| 3613 |
} |
|
| 3614 | 9x |
vars_needed <- c( |
| 3615 | 9x |
"simSeeds", |
| 3616 | 9x |
"args", |
| 3617 | 9x |
"nArgs", |
| 3618 | 9x |
"truth", |
| 3619 | 9x |
"combo_truth", |
| 3620 | 9x |
"firstSeparate", |
| 3621 | 9x |
"object", |
| 3622 | 9x |
"mcmcOptions" |
| 3623 |
) |
|
| 3624 | ||
| 3625 | 9x |
result_list <- get_result_list(run_sim, nsim, vars_needed, parallel, nCores) |
| 3626 |
# Now we have a list with each element containing mono and combo. Reorder this a bit: |
|
| 3627 | 9x |
result_list <- list( |
| 3628 | 9x |
mono = lapply(result_list, "[[", "mono"), |
| 3629 | 9x |
combo = lapply(result_list, "[[", "combo") |
| 3630 |
) |
|
| 3631 |
# Put everything in a list with both mono and combo Simulations: |
|
| 3632 | 9x |
lapply(result_list, function(this_list) {
|
| 3633 | 18x |
data_list <- lapply(this_list, "[[", "data") |
| 3634 | 18x |
recommended_doses <- as.numeric(sapply(this_list, "[[", "dose")) |
| 3635 | 18x |
fit_list <- lapply(this_list, "[[", "fit") |
| 3636 | 18x |
stop_reasons <- lapply(this_list, "[[", "stop") |
| 3637 | 18x |
report_results <- lapply(this_list, "[[", "results") |
| 3638 | 18x |
stop_report <- as.matrix(do.call(rbind, report_results)) |
| 3639 | 18x |
additional_stats <- lapply(this_list, "[[", "additional_stats") |
| 3640 | ||
| 3641 | 18x |
Simulations( |
| 3642 | 18x |
data = data_list, |
| 3643 | 18x |
doses = recommended_doses, |
| 3644 | 18x |
fit = fit_list, |
| 3645 | 18x |
stop_reasons = stop_reasons, |
| 3646 | 18x |
stop_report = stop_report, |
| 3647 | 18x |
additional_stats = additional_stats, |
| 3648 | 18x |
seed = rng_state |
| 3649 |
) |
|
| 3650 |
}) |
|
| 3651 |
} |
|
| 3652 |
) |
|
| 3653 | ||
| 3654 |
# examine ---- |
|
| 3655 | ||
| 3656 |
#' Obtain Hypothetical Trial Course Table for a Design |
|
| 3657 |
#' |
|
| 3658 |
#' This generic function takes a design and generates a `data.frame` |
|
| 3659 |
#' showing the beginning of several hypothetical trial courses under |
|
| 3660 |
#' the design. This means, from the generated `data.frame` one can read off: |
|
| 3661 |
#' |
|
| 3662 |
#' - how many cohorts are required in the optimal case (no DLTs observed) in |
|
| 3663 |
#' order to reach the highest dose of the specified dose grid (or until |
|
| 3664 |
#' the stopping rule is fulfilled) |
|
| 3665 |
#' - assuming no DLTs are observed until a certain dose level, what the next |
|
| 3666 |
#' recommended dose is for all possible number of DLTs observed |
|
| 3667 |
#' - the actual relative increments that will be used in these cases |
|
| 3668 |
#' - whether the trial would stop at a certain cohort |
|
| 3669 |
#' |
|
| 3670 |
#' Examining the "single trial" behavior of a dose escalation design is |
|
| 3671 |
#' the first important step in evaluating a design, and cannot be replaced by |
|
| 3672 |
#' studying solely the operating characteristics in "many trials". The cohort |
|
| 3673 |
#' sizes are also taken from the design, assuming no DLTs occur until the dose |
|
| 3674 |
#' listed. |
|
| 3675 |
#' |
|
| 3676 |
#' @param object ([`Design`] or [`RuleDesign`])\cr the design we want to examine |
|
| 3677 |
#' @param ... additional arguments (see methods) |
|
| 3678 |
#' @param maxNoIncrement maximum number of contiguous next doses at 0 |
|
| 3679 |
#' DLTs that are the same as before, i.e. no increment (default to 100) |
|
| 3680 |
#' |
|
| 3681 |
#' @return The data frame |
|
| 3682 |
#' |
|
| 3683 |
#' @export |
|
| 3684 |
#' @keywords methods regression |
|
| 3685 |
setGeneric( |
|
| 3686 |
"examine", |
|
| 3687 |
def = function(object, ..., maxNoIncrement = 100L) {
|
|
| 3688 | 4x |
assert_count(maxNoIncrement, positive = TRUE) |
| 3689 | 4x |
standardGeneric("examine")
|
| 3690 |
}, |
|
| 3691 |
valueClass = "data.frame" |
|
| 3692 |
) |
|
| 3693 | ||
| 3694 |
## Design ---- |
|
| 3695 | ||
| 3696 |
#' @describeIn examine Examine a model-based CRM. |
|
| 3697 |
#' |
|
| 3698 |
#' @param mcmcOptions ([`McmcOptions`])\cr giving the MCMC options |
|
| 3699 |
#' for each evaluation in the trial. By default, the standard options are used. |
|
| 3700 |
#' |
|
| 3701 |
#' @example examples/design-method-examine-Design.R |
|
| 3702 |
setMethod( |
|
| 3703 |
"examine", |
|
| 3704 |
signature = signature(object = "Design"), |
|
| 3705 |
def = function(object, mcmcOptions = McmcOptions(), ..., maxNoIncrement) {
|
|
| 3706 | 2x |
ret <- data.frame( |
| 3707 | 2x |
dose = numeric(), |
| 3708 | 2x |
DLTs = integer(), |
| 3709 | 2x |
nextDose = numeric(), |
| 3710 | 2x |
stop = logical(), |
| 3711 | 2x |
increment = integer() |
| 3712 |
) |
|
| 3713 | 2x |
base_data <- object@data |
| 3714 | ||
| 3715 | 2x |
should_stop <- FALSE |
| 3716 | ||
| 3717 |
# Counter how many contiguous doses at 0 DLTs with no increment. |
|
| 3718 | 2x |
no_increment_counter <- 0L |
| 3719 | ||
| 3720 |
# Initialize with starting dose. |
|
| 3721 | 2x |
dose <- object@startingDose |
| 3722 | ||
| 3723 | 2x |
while (!should_stop) {
|
| 3724 |
# What is the cohort size at this dose? |
|
| 3725 | 12x |
cohort_size <- size(object@cohort_size, dose = dose, data = base_data) |
| 3726 | ||
| 3727 | 12x |
if (base_data@placebo) {
|
| 3728 | 5x |
cohort_size_pl <- size( |
| 3729 | 5x |
object@pl_cohort_size, |
| 3730 | 5x |
dose = dose, |
| 3731 | 5x |
data = base_data |
| 3732 |
) |
|
| 3733 |
} |
|
| 3734 | ||
| 3735 |
# For all possible number of DLTs: |
|
| 3736 | 12x |
for (num_dlts in 0:cohort_size) {
|
| 3737 |
# Update data with corresponding DLT vector. |
|
| 3738 | 48x |
if (base_data@placebo && (cohort_size_pl > 0L)) {
|
| 3739 | 20x |
data_updated <- update( |
| 3740 | 20x |
object = base_data, |
| 3741 | 20x |
x = base_data@doseGrid[1], |
| 3742 | 20x |
y = rep(0, cohort_size_pl), |
| 3743 | 20x |
check = FALSE |
| 3744 |
) |
|
| 3745 | ||
| 3746 | 20x |
data_updated <- update( |
| 3747 | 20x |
object = data_updated, |
| 3748 | 20x |
x = dose, |
| 3749 | 20x |
y = rep( |
| 3750 | 20x |
x = c(0, 1), |
| 3751 | 20x |
times = c( |
| 3752 | 20x |
cohort_size - num_dlts, |
| 3753 | 20x |
num_dlts |
| 3754 |
) |
|
| 3755 |
), |
|
| 3756 | 20x |
new_cohort = FALSE |
| 3757 |
) |
|
| 3758 |
} else {
|
|
| 3759 | 28x |
data_updated <- update( |
| 3760 | 28x |
object = base_data, |
| 3761 | 28x |
x = dose, |
| 3762 | 28x |
y = rep( |
| 3763 | 28x |
x = c(0, 1), |
| 3764 | 28x |
times = c( |
| 3765 | 28x |
cohort_size - num_dlts, |
| 3766 | 28x |
num_dlts |
| 3767 |
) |
|
| 3768 |
) |
|
| 3769 |
) |
|
| 3770 |
} |
|
| 3771 | ||
| 3772 |
# Calculate dose limit. |
|
| 3773 | 48x |
dose_limit <- maxDose(object@increments, data = data_updated) |
| 3774 | ||
| 3775 |
# Generate samples from the model. |
|
| 3776 | 48x |
samples <- mcmc( |
| 3777 | 48x |
data = data_updated, |
| 3778 | 48x |
model = object@model, |
| 3779 | 48x |
options = mcmcOptions |
| 3780 |
) |
|
| 3781 | ||
| 3782 |
# Calculate next best dose. |
|
| 3783 | 48x |
next_dose <- nextBest( |
| 3784 | 48x |
object@nextBest, |
| 3785 | 48x |
doselimit = dose_limit, |
| 3786 | 48x |
samples = samples, |
| 3787 | 48x |
model = object@model, |
| 3788 | 48x |
data = data_updated |
| 3789 | 48x |
)$value |
| 3790 | ||
| 3791 |
# Compute relative increment in percent. |
|
| 3792 | 48x |
increment <- round((next_dose - dose) / dose * 100) |
| 3793 | ||
| 3794 |
# Evaluate stopping rules. |
|
| 3795 | 48x |
stop_this_trial <- stopTrial( |
| 3796 | 48x |
object@stopping, |
| 3797 | 48x |
dose = next_dose, |
| 3798 | 48x |
samples = samples, |
| 3799 | 48x |
model = object@model, |
| 3800 | 48x |
data = data_updated |
| 3801 |
) |
|
| 3802 | ||
| 3803 |
# Append information to the data frame. |
|
| 3804 | 48x |
ret <- rbind( |
| 3805 | 48x |
ret, |
| 3806 | 48x |
list( |
| 3807 | 48x |
dose = dose, |
| 3808 | 48x |
DLTs = num_dlts, |
| 3809 | 48x |
nextDose = next_dose, |
| 3810 | 48x |
stop = stop_this_trial, |
| 3811 | 48x |
increment = as.integer(increment) |
| 3812 |
) |
|
| 3813 |
) |
|
| 3814 |
} |
|
| 3815 | ||
| 3816 |
# Update base data. |
|
| 3817 | 12x |
if (base_data@placebo && (cohort_size_pl > 0L)) {
|
| 3818 | 5x |
base_data <- update( |
| 3819 | 5x |
object = base_data, |
| 3820 | 5x |
x = base_data@doseGrid[1], |
| 3821 | 5x |
y = rep(0, cohort_size_pl), |
| 3822 | 5x |
check = FALSE |
| 3823 |
) |
|
| 3824 | ||
| 3825 | 5x |
base_data <- update( |
| 3826 | 5x |
object = base_data, |
| 3827 | 5x |
x = dose, |
| 3828 | 5x |
y = rep(0, cohort_size), |
| 3829 | 5x |
new_cohort = FALSE |
| 3830 |
) |
|
| 3831 |
} else {
|
|
| 3832 | 7x |
base_data <- update( |
| 3833 | 7x |
object = base_data, |
| 3834 | 7x |
x = dose, |
| 3835 | 7x |
y = rep(0, cohort_size) |
| 3836 |
) |
|
| 3837 |
} |
|
| 3838 | ||
| 3839 |
# Extract results if 0 DLTs. |
|
| 3840 | 12x |
results_no_dlts <- subset( |
| 3841 | 12x |
tail(ret, cohort_size + 1), |
| 3842 | 12x |
dose == dose & DLTs == 0 |
| 3843 |
) |
|
| 3844 | ||
| 3845 |
# Determine new dose. |
|
| 3846 | 12x |
new_dose <- as.numeric(results_no_dlts$nextDose) |
| 3847 | ||
| 3848 |
# Calculate difference to previous dose. |
|
| 3849 | 12x |
dose_diff <- new_dose - dose |
| 3850 | ||
| 3851 |
# Update the counter for no increments of the dose. |
|
| 3852 | 12x |
if (dose_diff == 0) {
|
| 3853 | 10x |
no_increment_counter <- no_increment_counter + 1L |
| 3854 |
} else {
|
|
| 3855 | 2x |
no_increment_counter <- 0L |
| 3856 |
} |
|
| 3857 | ||
| 3858 |
# Check if stopping rule would be fulfilled. |
|
| 3859 | 12x |
stop_already <- results_no_dlts$stop |
| 3860 | ||
| 3861 |
# Update dose. |
|
| 3862 | 12x |
dose <- new_dose |
| 3863 | ||
| 3864 |
# Check if too many times no increment. |
|
| 3865 | 12x |
stop_no_increment <- (no_increment_counter >= maxNoIncrement) |
| 3866 | 12x |
if (stop_no_increment) {
|
| 3867 | ! |
warning(paste( |
| 3868 | ! |
"Stopping because", |
| 3869 | ! |
no_increment_counter, |
| 3870 | ! |
"times no increment vs. previous dose" |
| 3871 |
)) |
|
| 3872 |
} |
|
| 3873 | ||
| 3874 |
# Check if we can stop: |
|
| 3875 |
# Either when we have reached the highest dose in the next cohort, |
|
| 3876 |
# or when the stopping rule is already fulfilled, |
|
| 3877 |
# or when too many times no increment. |
|
| 3878 | 12x |
should_stop <- (dose >= max(object@data@doseGrid)) || |
| 3879 | 12x |
stop_already || |
| 3880 | 12x |
stop_no_increment |
| 3881 |
} |
|
| 3882 | 2x |
ret |
| 3883 |
} |
|
| 3884 |
) |
|
| 3885 | ||
| 3886 |
## RuleDesign ---- |
|
| 3887 | ||
| 3888 |
#' @describeIn examine Examine a rule-based design. |
|
| 3889 |
#' @example examples/design-method-examine-RuleDesign.R |
|
| 3890 |
setMethod( |
|
| 3891 |
"examine", |
|
| 3892 |
signature = signature(object = "RuleDesign"), |
|
| 3893 |
def = function(object, ..., maxNoIncrement) {
|
|
| 3894 |
# Start with the empty table. |
|
| 3895 | 1x |
ret <- data.frame( |
| 3896 | 1x |
dose = numeric(), |
| 3897 | 1x |
DLTs = integer(), |
| 3898 | 1x |
nextDose = numeric(), |
| 3899 | 1x |
stop = logical(), |
| 3900 | 1x |
increment = integer() |
| 3901 |
) |
|
| 3902 | ||
| 3903 |
# Start the base data with the provided one. |
|
| 3904 | 1x |
base_data <- object@data |
| 3905 | ||
| 3906 |
# Are we finished and can stop? |
|
| 3907 | 1x |
should_stop <- FALSE |
| 3908 | ||
| 3909 |
# Counter: contiguous doses at 0 DLTs with no increment. |
|
| 3910 | 1x |
no_increment_counter <- 0L |
| 3911 | ||
| 3912 |
# Initialize with starting dose. |
|
| 3913 | 1x |
dose <- object@startingDose |
| 3914 | ||
| 3915 |
# Continue filling up the table until stopping. |
|
| 3916 | 1x |
while (!should_stop) {
|
| 3917 |
# Cohort size at this dose. |
|
| 3918 | 10x |
cohort_size <- size(object@cohort_size, dose = dose, data = base_data) |
| 3919 | ||
| 3920 |
# For all possible number of DLTs. |
|
| 3921 | 10x |
for (num_dlts in 0:cohort_size) {
|
| 3922 |
# Update data with corresponding DLT vector. |
|
| 3923 | 40x |
data_updated <- update( |
| 3924 | 40x |
object = base_data, |
| 3925 | 40x |
x = dose, |
| 3926 | 40x |
y = rep( |
| 3927 | 40x |
x = c(0, 1), |
| 3928 | 40x |
times = c( |
| 3929 | 40x |
cohort_size - num_dlts, |
| 3930 | 40x |
num_dlts |
| 3931 |
) |
|
| 3932 |
) |
|
| 3933 |
) |
|
| 3934 | ||
| 3935 |
# Evaluate the rule. |
|
| 3936 | 40x |
outcome <- nextBest(object@nextBest, data = data_updated) |
| 3937 | ||
| 3938 |
# Next dose and whether to stop here. |
|
| 3939 | 40x |
next_dose <- outcome$value |
| 3940 | 40x |
stop_this_trial <- outcome$stopHere |
| 3941 | ||
| 3942 |
# Compute relative increment in percent. |
|
| 3943 | 40x |
increment <- round((next_dose - dose) / dose * 100) |
| 3944 | ||
| 3945 |
# Append information to the data frame. |
|
| 3946 | 40x |
ret <- rbind( |
| 3947 | 40x |
ret, |
| 3948 | 40x |
list( |
| 3949 | 40x |
dose = dose, |
| 3950 | 40x |
DLTs = num_dlts, |
| 3951 | 40x |
nextDose = next_dose, |
| 3952 | 40x |
stop = stop_this_trial, |
| 3953 | 40x |
increment = as.integer(increment) |
| 3954 |
) |
|
| 3955 |
) |
|
| 3956 |
} |
|
| 3957 | ||
| 3958 |
# Change base data. |
|
| 3959 | 10x |
base_data <- update( |
| 3960 | 10x |
object = base_data, |
| 3961 | 10x |
x = dose, |
| 3962 | 10x |
y = rep(0, cohort_size) |
| 3963 |
) |
|
| 3964 | ||
| 3965 |
# Results if 0 DLTs. |
|
| 3966 | 10x |
results_no_dlts <- subset( |
| 3967 | 10x |
tail(ret, cohort_size + 1), |
| 3968 | 10x |
dose == dose & DLTs == 0 |
| 3969 |
) |
|
| 3970 | ||
| 3971 |
# New dose and difference to previous dose. |
|
| 3972 | 10x |
new_dose <- as.numeric(results_no_dlts$nextDose) |
| 3973 | 10x |
dose_diff <- new_dose - dose |
| 3974 | ||
| 3975 |
# Update the counter for no increments of the dose. |
|
| 3976 | 10x |
if (dose_diff == 0) {
|
| 3977 | ! |
no_increment_counter <- no_increment_counter + 1L |
| 3978 |
} else {
|
|
| 3979 | 10x |
no_increment_counter <- 0L |
| 3980 |
} |
|
| 3981 | ||
| 3982 |
# Would stopping rule be fulfilled already? |
|
| 3983 | 10x |
stop_already <- results_no_dlts$stop |
| 3984 | ||
| 3985 |
# Update dose. |
|
| 3986 | 10x |
dose <- new_dose |
| 3987 | ||
| 3988 |
# Too many times no increment? |
|
| 3989 | 10x |
stop_no_increment <- (no_increment_counter >= maxNoIncrement) |
| 3990 | 10x |
if (stop_no_increment) {
|
| 3991 | ! |
warning(paste( |
| 3992 | ! |
"Stopping because", |
| 3993 | ! |
no_increment_counter, |
| 3994 | ! |
"times no increment vs. previous dose" |
| 3995 |
)) |
|
| 3996 |
} |
|
| 3997 | ||
| 3998 |
# Check if we can stop: |
|
| 3999 |
# highest dose reached next cohort, stopping rule fulfilled, or too many no-increment. |
|
| 4000 | 10x |
should_stop <- (dose >= max(object@data@doseGrid)) || |
| 4001 | 10x |
stop_already || |
| 4002 | 10x |
stop_no_increment |
| 4003 |
} |
|
| 4004 | ||
| 4005 | 1x |
ret |
| 4006 |
} |
|
| 4007 |
) |
|
| 4008 | ||
| 4009 |
## DADesign ---- |
|
| 4010 | ||
| 4011 |
#' @describeIn examine Examine a model-based CRM. |
|
| 4012 |
#' |
|
| 4013 |
#' @param mcmcOptions ([`McmcOptions`])\cr |
|
| 4014 |
#' giving the MCMC options for each evaluation in the trial. By default, |
|
| 4015 |
#' the standard options are used |
|
| 4016 |
#' |
|
| 4017 |
#' @example examples/design-method-examine-DADesign.R |
|
| 4018 |
setMethod( |
|
| 4019 |
"examine", |
|
| 4020 |
signature = signature(object = "DADesign"), |
|
| 4021 |
def = function(object, mcmcOptions = McmcOptions(), ..., maxNoIncrement) {
|
|
| 4022 |
# Check follow-up sufficiency (TRUE/FALSE); |
|
| 4023 | 1x |
ready_to_open <- function(day, window, this_surv) {
|
| 4024 | 180x |
size <- length(this_surv) |
| 4025 | 180x |
start_time <- apply( |
| 4026 | 180x |
rbind(this_surv[-size], window$patientGap[-1]), |
| 4027 | 180x |
2, |
| 4028 | 180x |
min |
| 4029 |
) |
|
| 4030 | 180x |
individual_check <- day - cumsum(c(0, start_time)) |
| 4031 | 180x |
individual_check[individual_check < 0] <- 0 |
| 4032 | 180x |
follow_up <- apply(rbind(this_surv, individual_check), 2, min) |
| 4033 | 180x |
all( |
| 4034 | 180x |
(follow_up - apply(rbind(window$patientFollow, this_surv), 2, min)) >= 0 |
| 4035 |
) && |
|
| 4036 | 180x |
(max(follow_up) >= min(window$patientFollowMin, max(this_surv))) |
| 4037 |
} |
|
| 4038 | ||
| 4039 |
# Determine when to open the next cohort; applies to all trials. |
|
| 4040 | 1x |
next_open <- function(window, this_surv) {
|
| 4041 | 3x |
size <- length(this_surv) |
| 4042 | 3x |
window$patientGap <- window$patientGap[1:size] |
| 4043 | 3x |
start_time <- apply( |
| 4044 | 3x |
rbind(this_surv[-size], window$patientGap[-1]), |
| 4045 | 3x |
2, |
| 4046 | 3x |
min |
| 4047 |
) |
|
| 4048 | 3x |
max_t <- max(this_surv + cumsum(c(0, start_time))) |
| 4049 | ||
| 4050 | 3x |
met <- sapply(1:max_t, function(i) ready_to_open(i, window, this_surv)) |
| 4051 | ! |
if (sum(met) > 0) min(c(1:max_t)[met]) else max_t |
| 4052 |
} |
|
| 4053 | ||
| 4054 |
# Initialize result table. |
|
| 4055 | 1x |
ret <- data.frame( |
| 4056 | 1x |
DLTsearly_1 = integer(), |
| 4057 | 1x |
dose = numeric(), |
| 4058 | 1x |
DLTs = integer(), |
| 4059 | 1x |
nextDose = numeric(), |
| 4060 | 1x |
stop = logical(), |
| 4061 | 1x |
increment = integer() |
| 4062 |
) |
|
| 4063 | ||
| 4064 |
# Base data and trial state. |
|
| 4065 | 1x |
base_data <- object@data |
| 4066 | 1x |
should_stop <- FALSE |
| 4067 | 1x |
dose <- object@startingDose |
| 4068 | ||
| 4069 |
# Observed facts trackers (cumulative across cohorts). |
|
| 4070 | 1x |
observed_dlts <- base_data@y |
| 4071 | 1x |
observed_surv <- base_data@u |
| 4072 | 1x |
observed_t0 <- base_data@t0 |
| 4073 | ||
| 4074 |
# Global trial clock and previous cohort timing. |
|
| 4075 | 1x |
trial_time <- 0 |
| 4076 | 1x |
prev_time <- 0 |
| 4077 | ||
| 4078 |
# DLT window length. |
|
| 4079 | 1x |
t_max <- base_data@Tmax |
| 4080 | ||
| 4081 |
# Number of patients with unfinished DLT window (initially none). |
|
| 4082 | 1x |
prev_size <- 0 |
| 4083 | ||
| 4084 |
# Iterate cohorts until stopping. |
|
| 4085 | 1x |
while (!should_stop) {
|
| 4086 | 3x |
cohort_size <- size(object@cohort_size, dose = dose, data = base_data) |
| 4087 | 3x |
safety_window <- windowLength(object@safetyWindow, cohort_size) |
| 4088 | ||
| 4089 |
# When cohort patients start relative to trial clock. |
|
| 4090 | ||
| 4091 | 3x |
cohort_t0 <- trial_time + cumsum(safety_window$patientGap) |
| 4092 | ||
| 4093 |
# Append placeholders for the incoming cohort (no DLTs yet, censored at t_max). |
|
| 4094 | 3x |
observed_dlts <- c(observed_dlts, rep(0, cohort_size)) |
| 4095 | 3x |
observed_surv <- c(observed_surv, rep(t_max, cohort_size)) |
| 4096 | 3x |
observed_t0 <- c(observed_t0, cohort_t0) |
| 4097 | ||
| 4098 |
# Advance time until next cohort may open (all follow-up constraints satisfied). |
|
| 4099 | 3x |
trial_time <- trial_time + |
| 4100 | 3x |
next_open(window = safety_window, this_surv = rep(t_max, cohort_size)) |
| 4101 | ||
| 4102 |
# Count patients still within DLT window (for nFollow loop). |
|
| 4103 | 3x |
n_follow <- cohort_size + prev_size |
| 4104 | ||
| 4105 |
# Identify censored patients indices. |
|
| 4106 | 3x |
npt <- length(base_data@x) |
| 4107 | 3x |
censored_indices <- c( |
| 4108 | 3x |
which((trial_time - base_data@t0) < base_data@Tmax & base_data@y == 0), |
| 4109 | 3x |
(npt + 1):(npt + cohort_size) |
| 4110 |
) |
|
| 4111 | ||
| 4112 |
# For all possible number of DLTs (0..n_follow): |
|
| 4113 | 3x |
for (num_dlts in 0:n_follow) {
|
| 4114 | 9x |
if (num_dlts == 0) {
|
| 4115 |
# Update base_data for zero DLTs scenario. |
|
| 4116 | 3x |
base_data <- update( |
| 4117 | 3x |
object = base_data, |
| 4118 | 3x |
y = observed_dlts, |
| 4119 | 3x |
u = observed_surv, |
| 4120 | 3x |
t0 = observed_t0, |
| 4121 | 3x |
x = dose, |
| 4122 | 3x |
trialtime = trial_time |
| 4123 |
) |
|
| 4124 | ||
| 4125 | 3x |
dose_limit <- maxDose(object@increments, data = base_data) |
| 4126 | 3x |
samples <- mcmc( |
| 4127 | 3x |
data = base_data, |
| 4128 | 3x |
model = object@model, |
| 4129 | 3x |
options = mcmcOptions |
| 4130 |
) |
|
| 4131 | 3x |
next_dose <- nextBest( |
| 4132 | 3x |
object@nextBest, |
| 4133 | 3x |
doselimit = dose_limit, |
| 4134 | 3x |
samples = samples, |
| 4135 | 3x |
model = object@model, |
| 4136 | 3x |
data = base_data |
| 4137 | 3x |
)$value |
| 4138 | ||
| 4139 | 3x |
increment <- round((next_dose - dose) / dose * 100) |
| 4140 | 3x |
stop_this_trial <- stopTrial( |
| 4141 | 3x |
object@stopping, |
| 4142 | 3x |
dose = next_dose, |
| 4143 | 3x |
samples = samples, |
| 4144 | 3x |
model = object@model, |
| 4145 | 3x |
data = base_data |
| 4146 |
) |
|
| 4147 | ||
| 4148 | 3x |
ret <- rbind( |
| 4149 | 3x |
ret, |
| 4150 | 3x |
list( |
| 4151 | 3x |
DLTsearly_1 = 0, |
| 4152 | 3x |
dose = dose, |
| 4153 | 3x |
DLTs = num_dlts, |
| 4154 | 3x |
nextDose = next_dose, |
| 4155 | 3x |
stop = stop_this_trial, |
| 4156 | 3x |
increment = as.integer(increment) |
| 4157 |
) |
|
| 4158 |
) |
|
| 4159 |
} else {
|
|
| 4160 |
# Consider two extremes: DLTs at longest vs shortest follow-ups. |
|
| 4161 | 6x |
for (dlt_early in 1:num_dlts) {
|
| 4162 | 10x |
curr_dlts <- observed_dlts |
| 4163 | 10x |
curr_surv <- observed_surv |
| 4164 | ||
| 4165 | 10x |
if (dlt_early == 1) {
|
| 4166 |
# Longest follow-up patients have DLTs. |
|
| 4167 | 6x |
curr_dlts[censored_indices][1:num_dlts] <- 1 |
| 4168 | 6x |
curr_surv[censored_indices][1:num_dlts] <- apply( |
| 4169 | 6x |
rbind( |
| 4170 | 6x |
rep(t_max, num_dlts), |
| 4171 | 6x |
trial_time - observed_t0[censored_indices][1:num_dlts] |
| 4172 |
), |
|
| 4173 | 6x |
2, |
| 4174 | 6x |
min |
| 4175 |
) |
|
| 4176 | ||
| 4177 | 6x |
data_current <- update( |
| 4178 | 6x |
object = base_data, |
| 4179 | 6x |
y = curr_dlts, |
| 4180 | 6x |
u = curr_surv, |
| 4181 | 6x |
t0 = observed_t0, |
| 4182 | 6x |
x = dose, |
| 4183 | 6x |
trialtime = trial_time |
| 4184 |
) |
|
| 4185 |
} else {
|
|
| 4186 |
# Shortest follow-up patients have DLTs. |
|
| 4187 | 4x |
curr_dlts[rev(censored_indices)][1:num_dlts] <- 1 |
| 4188 | 4x |
curr_surv[rev(censored_indices)][1:num_dlts] <- apply( |
| 4189 | 4x |
rbind( |
| 4190 | 4x |
rep(1, num_dlts), |
| 4191 | 4x |
prev_time + 1 - observed_t0[rev(censored_indices)][1:num_dlts] |
| 4192 |
), |
|
| 4193 | 4x |
2, |
| 4194 | 4x |
max |
| 4195 |
) |
|
| 4196 | ||
| 4197 | 4x |
temp_time <- if (num_dlts >= cohort_size) {
|
| 4198 | 4x |
1 + max(cohort_t0) |
| 4199 |
} else {
|
|
| 4200 | ! |
trial_time |
| 4201 |
} |
|
| 4202 | ||
| 4203 | 4x |
data_current <- update( |
| 4204 | 4x |
object = base_data, |
| 4205 | 4x |
y = curr_dlts, |
| 4206 | 4x |
u = curr_surv, |
| 4207 | 4x |
t0 = observed_t0, |
| 4208 | 4x |
x = dose, |
| 4209 | 4x |
trialtime = temp_time |
| 4210 |
) |
|
| 4211 |
} |
|
| 4212 | ||
| 4213 | 10x |
dose_limit <- maxDose(object@increments, data = data_current) |
| 4214 | 10x |
samples <- mcmc( |
| 4215 | 10x |
data = data_current, |
| 4216 | 10x |
model = object@model, |
| 4217 | 10x |
options = mcmcOptions |
| 4218 |
) |
|
| 4219 | 10x |
next_dose <- nextBest( |
| 4220 | 10x |
object@nextBest, |
| 4221 | 10x |
doselimit = dose_limit, |
| 4222 | 10x |
samples = samples, |
| 4223 | 10x |
model = object@model, |
| 4224 | 10x |
data = data_current |
| 4225 | 10x |
)$value |
| 4226 | ||
| 4227 | 10x |
increment <- round((next_dose - dose) / dose * 100) |
| 4228 | 10x |
stop_this_trial <- stopTrial( |
| 4229 | 10x |
object@stopping, |
| 4230 | 10x |
dose = next_dose, |
| 4231 | 10x |
samples = samples, |
| 4232 | 10x |
model = object@model, |
| 4233 | 10x |
data = data_current |
| 4234 |
) |
|
| 4235 | ||
| 4236 | 10x |
ret <- rbind( |
| 4237 | 10x |
ret, |
| 4238 | 10x |
list( |
| 4239 | 10x |
DLTsearly_1 = dlt_early, |
| 4240 | 10x |
dose = dose, |
| 4241 | 10x |
DLTs = num_dlts, |
| 4242 | 10x |
nextDose = next_dose, |
| 4243 | 10x |
stop = stop_this_trial, |
| 4244 | 10x |
increment = as.integer(increment) |
| 4245 |
) |
|
| 4246 |
) |
|
| 4247 |
} |
|
| 4248 |
} |
|
| 4249 |
} |
|
| 4250 | ||
| 4251 |
# Update previous time and compute next state. |
|
| 4252 | 3x |
prev_time <- trial_time |
| 4253 | ||
| 4254 |
# Filter results at this dose with 0 DLTs and derive new dose. |
|
| 4255 | 3x |
results_no_dlts <- subset(ret, dose == dose & DLTs == 0) |
| 4256 | 3x |
new_dose <- as.numeric(results_no_dlts$nextDose) |
| 4257 | 3x |
dose_diff <- new_dose - dose |
| 4258 | 3x |
stop_already <- any(results_no_dlts$stop) |
| 4259 | ||
| 4260 |
# Update dose to the maximum recommended among ties. |
|
| 4261 | 3x |
dose <- max(new_dose) |
| 4262 | ||
| 4263 |
# Patients still within DLT window. |
|
| 4264 | 3x |
prev_size <- sum(base_data@u[base_data@y == 0] < base_data@Tmax) |
| 4265 | ||
| 4266 |
# No-increment counter and stopping due to no increment. |
|
| 4267 | 3x |
no_increment_counter <- if (all(dose_diff == 0)) {
|
| 4268 | 2x |
no_increment_counter + 1L |
| 4269 |
} else {
|
|
| 4270 | 1x |
0L |
| 4271 |
} |
|
| 4272 | 3x |
stop_no_increment <- (no_increment_counter >= maxNoIncrement) |
| 4273 | 3x |
if (stop_no_increment) {
|
| 4274 | 1x |
warning(paste( |
| 4275 | 1x |
"Stopping because", |
| 4276 | 1x |
no_increment_counter, |
| 4277 | 1x |
"times no increment vs. previous dose" |
| 4278 |
)) |
|
| 4279 |
} |
|
| 4280 | ||
| 4281 |
# Overall stop condition. |
|
| 4282 | 3x |
should_stop <- (dose >= max(object@data@doseGrid)) || |
| 4283 | 3x |
stop_already || |
| 4284 | 3x |
stop_no_increment |
| 4285 |
} |
|
| 4286 | ||
| 4287 | 1x |
ret |
| 4288 |
} |
|
| 4289 |
) |
|
| 4290 | ||
| 4291 |
# tidy ---- |
|
| 4292 | ||
| 4293 |
## tidy-DualDesign ---- |
|
| 4294 | ||
| 4295 |
#' @rdname tidy |
|
| 4296 |
#' @aliases tidy-DualDesign |
|
| 4297 |
#' @example examples/Design-method-tidyDualDesign.R |
|
| 4298 |
#' |
|
| 4299 |
#' @export |
|
| 4300 |
setMethod( |
|
| 4301 |
f = "tidy", |
|
| 4302 |
signature = signature(x = "DualDesign"), |
|
| 4303 |
definition = function(x, ...) {
|
|
| 4304 |
# Some Design objects have complex attributes whose structure is not supported. |
|
| 4305 | 3x |
rv <- h_tidy_all_slots(x, attributes = FALSE) %>% h_tidy_class(x) |
| 4306 | 3x |
if (length(rv) == 1) {
|
| 4307 | ! |
rv[[names(rv)[1]]] %>% h_tidy_class(x) |
| 4308 |
} else {
|
|
| 4309 | 3x |
rv |
| 4310 |
} |
|
| 4311 |
} |
|
| 4312 |
) |
| 1 |
#' @include helpers_data.R |
|
| 2 | ||
| 3 |
# plot ---- |
|
| 4 | ||
| 5 |
## Data ---- |
|
| 6 | ||
| 7 |
#' Plot Method for the [`Data`] Class |
|
| 8 |
#' |
|
| 9 |
#' @description `r lifecycle::badge("stable")`
|
|
| 10 |
#' |
|
| 11 |
#' A method that creates a plot for [`Data`] object. |
|
| 12 |
#' |
|
| 13 |
#' @return The [`ggplot2`] object. |
|
| 14 |
#' |
|
| 15 |
#' @aliases plot-Data |
|
| 16 |
#' @rdname plot-Data |
|
| 17 |
#' @export |
|
| 18 |
#' @example examples/Data-method-plot.R |
|
| 19 |
#' |
|
| 20 |
setMethod( |
|
| 21 |
f = "plot", |
|
| 22 |
signature = signature(x = "Data", y = "missing"), |
|
| 23 |
definition = function(x, y, blind = FALSE, legend = TRUE, ...) {
|
|
| 24 | 8x |
assert_flag(blind) |
| 25 | 8x |
assert_flag(legend) |
| 26 | 8x |
h_plot_data_dataordinal(x, blind, legend, ...) |
| 27 |
} |
|
| 28 |
) |
|
| 29 | ||
| 30 |
#' Plot Method for the [`DataOrdinal`] Class |
|
| 31 |
#' |
|
| 32 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 33 |
#' |
|
| 34 |
#' A method that creates a plot for [`DataOrdinal`] object. |
|
| 35 |
#' |
|
| 36 |
#' @param x (`DataOrdinal`)\cr object we want to plot. |
|
| 37 |
#' @param y (`missing`)\cr missing object, for compatibility with the generic |
|
| 38 |
#' function. |
|
| 39 |
#' @param blind (`flag`)\cr indicates whether to blind the data. |
|
| 40 |
#' If `TRUE`, then placebo subjects are reported at the same level |
|
| 41 |
#' as the active dose level in the corresponding cohort, |
|
| 42 |
#' and DLTs are always assigned to the first subjects in a cohort. |
|
| 43 |
#' @param legend (`flag`)\cr whether the legend should be added. |
|
| 44 |
#' @param tox_labels (`named list of character`)\cr the labels of the toxicity |
|
| 45 |
#' categories. |
|
| 46 |
#' @param tox_shapes (`names list of integers`)\cr the symbols used to identify |
|
| 47 |
#' the toxicity categories. |
|
| 48 |
#' @param ... not used. |
|
| 49 |
#' |
|
| 50 |
#' @note With more than 9 toxicity categories, toxicity symbols must be |
|
| 51 |
#' specified manually.\cr With more than 5 toxicity categories, toxicity labels |
|
| 52 |
#' must be specified manually. |
|
| 53 |
#' |
|
| 54 |
#' @return The [`ggplot2`] object. |
|
| 55 |
#' |
|
| 56 |
#' @rdname plot-Data |
|
| 57 |
#' @export |
|
| 58 |
#' @example examples/DataOrdinal-method-plot.R |
|
| 59 |
setMethod( |
|
| 60 |
f = "plot", |
|
| 61 |
signature = signature(x = "DataOrdinal", y = "missing"), |
|
| 62 |
definition = function( |
|
| 63 |
x, |
|
| 64 |
y, |
|
| 65 |
blind = FALSE, |
|
| 66 |
legend = TRUE, |
|
| 67 |
tox_labels = NULL, |
|
| 68 |
tox_shapes = NULL, |
|
| 69 |
... |
|
| 70 |
) {
|
|
| 71 | 2x |
if (is.null(tox_shapes)) {
|
| 72 | 2x |
assert_true(length(x@yCategories) <= 9) |
| 73 | 2x |
tox_shapes <- c(17L, 16L, 15L, 18L, 0L:2L, 5L, 6L)[seq_along( |
| 74 | 2x |
x@yCategories |
| 75 |
)] |
|
| 76 | 2x |
names(tox_shapes) <- names(x@yCategories) |
| 77 |
} |
|
| 78 | 2x |
if (is.null(tox_labels)) {
|
| 79 | 2x |
assert_true(length(x@yCategories) <= 5) |
| 80 | 2x |
tox_labels <- switch( |
| 81 | 2x |
length(x@yCategories), |
| 82 | 2x |
c("black"),
|
| 83 | 2x |
c("black", "red"),
|
| 84 | 2x |
c("black", "orange", "red"),
|
| 85 | 2x |
c("black", "green", "orange", "red"),
|
| 86 | 2x |
c("black", "green", "yellow", "orange", "red")
|
| 87 |
) |
|
| 88 | 2x |
names(tox_labels) <- names(x@yCategories) |
| 89 |
} |
|
| 90 | 2x |
h_plot_data_dataordinal( |
| 91 | 2x |
x, |
| 92 | 2x |
blind, |
| 93 | 2x |
legend, |
| 94 | 2x |
tox_labels = tox_labels, |
| 95 | 2x |
tox_shapes = tox_shapes, |
| 96 |
... |
|
| 97 |
) |
|
| 98 |
} |
|
| 99 |
) |
|
| 100 | ||
| 101 |
## DataDual ---- |
|
| 102 | ||
| 103 |
#' Plot Method for the [`DataDual`] Class |
|
| 104 |
#' |
|
| 105 |
#' @description `r lifecycle::badge("stable")`
|
|
| 106 |
#' |
|
| 107 |
#' A method that creates a plot for [`DataDual`] object. |
|
| 108 |
#' |
|
| 109 |
#' @param x (`DataDual`)\cr object we want to plot. |
|
| 110 |
#' @param y (`missing`)\cr missing object, for compatibility with the generic |
|
| 111 |
#' function. |
|
| 112 |
#' @param blind (`flag`)\cr indicates whether to blind the data. |
|
| 113 |
#' If `TRUE`, then placebo subjects are reported at the same level |
|
| 114 |
#' as the active dose level in the corresponding cohort, |
|
| 115 |
#' and DLTs are always assigned to the first subjects in a cohort. |
|
| 116 |
#' @param ... passed to the first inherited method `plot` after this current |
|
| 117 |
#' method. |
|
| 118 |
#' |
|
| 119 |
#' @return The [`ggplot2`] object. |
|
| 120 |
#' |
|
| 121 |
#' @aliases plot-DataDual |
|
| 122 |
#' @export |
|
| 123 |
#' @example examples/Data-method-plot-DataDual.R |
|
| 124 |
#' |
|
| 125 |
setMethod( |
|
| 126 |
f = "plot", |
|
| 127 |
signature = signature(x = "DataDual", y = "missing"), |
|
| 128 |
definition = function(x, y, blind = FALSE, ...) {
|
|
| 129 | 2x |
assert_flag(blind) |
| 130 | ||
| 131 |
# Call the superclass method, to get the first plot. |
|
| 132 | 2x |
plot1 <- callNextMethod(x, blind = blind, legend = FALSE, ...) |
| 133 | ||
| 134 |
# Create the second, biomarker plot. |
|
| 135 | 2x |
df <- h_plot_data_df(x, blind, biomarker = x@w) |
| 136 | ||
| 137 | 2x |
plot2 <- ggplot(df, aes(x = dose, y = biomarker)) + |
| 138 | 2x |
geom_point(aes(shape = toxicity, colour = toxicity), size = 3) + |
| 139 | 2x |
scale_colour_manual( |
| 140 | 2x |
name = "Toxicity", |
| 141 | 2x |
values = c(Yes = "red", No = "black") |
| 142 |
) + |
|
| 143 | 2x |
scale_shape_manual(name = "Toxicity", values = c(Yes = 17, No = 16)) + |
| 144 | 2x |
xlab("Dose Level") +
|
| 145 | 2x |
ylab("Biomarker")
|
| 146 | ||
| 147 | 2x |
if (!blind) {
|
| 148 | 1x |
plot2 <- plot2 + |
| 149 | 1x |
geom_text( |
| 150 | 1x |
aes( |
| 151 | 1x |
y = biomarker + 0.02 * diff(range(biomarker)), |
| 152 | 1x |
label = patient, |
| 153 | 1x |
size = 2 |
| 154 |
), |
|
| 155 | 1x |
data = df, |
| 156 | 1x |
hjust = 0, |
| 157 | 1x |
vjust = 0.5, |
| 158 | 1x |
angle = 90, |
| 159 | 1x |
colour = "black", |
| 160 | 1x |
show.legend = FALSE |
| 161 |
) |
|
| 162 |
} |
|
| 163 | ||
| 164 |
# Arrange both plots side by side. |
|
| 165 | 2x |
gridExtra::arrangeGrob(plot1, plot2, ncol = 2) |
| 166 |
} |
|
| 167 |
) |
|
| 168 | ||
| 169 |
## DataDA ---- |
|
| 170 | ||
| 171 |
#' Plot Method for the [`DataDA`] Class |
|
| 172 |
#' |
|
| 173 |
#' @description `r lifecycle::badge("stable")`
|
|
| 174 |
#' |
|
| 175 |
#' A method that creates a plot for [`DataDA`] object. |
|
| 176 |
#' |
|
| 177 |
#' @param x (`DataDA`)\cr object we want to plot. |
|
| 178 |
#' @param y (`missing`)\cr missing object, for compatibility with the generic |
|
| 179 |
#' function. |
|
| 180 |
#' @param blind (`flag`)\cr indicates whether to blind the data. |
|
| 181 |
#' If `TRUE`, then placebo subjects are reported at the same level |
|
| 182 |
#' as the active dose level in the corresponding cohort, |
|
| 183 |
#' and DLTs are always assigned to the first subjects in a cohort. |
|
| 184 |
#' @param ... passed to the first inherited method `plot` after this current |
|
| 185 |
#' method. |
|
| 186 |
#' |
|
| 187 |
#' @return The [`ggplot2`] object. |
|
| 188 |
#' |
|
| 189 |
#' @aliases plot-DataDA |
|
| 190 |
#' @export |
|
| 191 |
#' @example examples/Data-method-plot-DataDA.R |
|
| 192 |
#' |
|
| 193 |
setMethod( |
|
| 194 |
f = "plot", |
|
| 195 |
signature = signature(x = "DataDA", y = "missing"), |
|
| 196 |
definition = function(x, y, blind = FALSE, ...) {
|
|
| 197 | 2x |
assert_flag(blind) |
| 198 | ||
| 199 |
# Call the superclass method, to get the first plot. |
|
| 200 | 2x |
plot1 <- callNextMethod(x, blind = blind, legend = FALSE, ...) |
| 201 | ||
| 202 |
# Prepare data set for the second, time plot. |
|
| 203 | 2x |
df <- h_plot_data_df(x, blind, u = x@u, t0 = x@t0) |
| 204 | 2x |
df$censored <- ifelse(df$u < x@Tmax & df$toxicity == 0, 1, 0) |
| 205 | 2x |
df$tend <- df$t0 + df$u # `tend` stands for `time end` |
| 206 | 2x |
df$t0_case <- "Start" |
| 207 | 2x |
df$tend_case <- ifelse( |
| 208 | 2x |
df$toxicity == "Yes", |
| 209 | 2x |
"Yes", |
| 210 | 2x |
ifelse(df$censored, "Censored", "No") |
| 211 |
) |
|
| 212 | ||
| 213 |
# Build plot object. |
|
| 214 | 2x |
plot2 <- ggplot(df, aes(x = t0, y = patient)) + |
| 215 | 2x |
geom_segment(aes(xend = tend, yend = patient)) + |
| 216 | 2x |
geom_point(aes(shape = t0_case, colour = t0_case), size = 3) + |
| 217 | 2x |
geom_point( |
| 218 | 2x |
aes(x = tend, shape = tend_case, colour = tend_case), |
| 219 | 2x |
size = 3 |
| 220 |
) + |
|
| 221 | 2x |
scale_colour_manual( |
| 222 | 2x |
name = "Toxicity", |
| 223 | 2x |
values = c( |
| 224 | 2x |
Yes = "red", |
| 225 | 2x |
No = "black", |
| 226 | 2x |
Start = "black", |
| 227 | 2x |
Censored = "black" |
| 228 |
) |
|
| 229 |
) + |
|
| 230 | 2x |
scale_shape_manual( |
| 231 | 2x |
name = "Toxicity", |
| 232 | 2x |
values = c(Yes = 17, No = 16, Start = 1, Censored = 4) |
| 233 |
) + |
|
| 234 | 2x |
scale_y_continuous(breaks = df$patient, minor_breaks = NULL) + |
| 235 | 2x |
xlab("Time") +
|
| 236 | 2x |
ylab("Patient")
|
| 237 | ||
| 238 | 2x |
plot2 <- plot2 + |
| 239 | 2x |
h_plot_data_cohort_lines(df$cohort, placebo = x@placebo, vertical = FALSE) |
| 240 | ||
| 241 | 2x |
if (!blind) {
|
| 242 | 1x |
plot2 <- plot2 + |
| 243 | 1x |
geom_text( |
| 244 | 1x |
aes(label = ID, size = 2), |
| 245 | 1x |
size = 3, |
| 246 | 1x |
hjust = 1.5, |
| 247 | 1x |
vjust = 0, |
| 248 | 1x |
angle = 0, |
| 249 | 1x |
colour = "black", |
| 250 | 1x |
show.legend = FALSE |
| 251 |
) |
|
| 252 |
} |
|
| 253 | ||
| 254 |
# Arrange both plots side by side. |
|
| 255 | 2x |
gridExtra::arrangeGrob(plot1, plot2, ncol = 1) |
| 256 |
} |
|
| 257 |
) |
|
| 258 | ||
| 259 |
# update ---- |
|
| 260 | ||
| 261 |
## Data ---- |
|
| 262 | ||
| 263 |
#' Updating `Data` Objects |
|
| 264 |
#' |
|
| 265 |
#' @description `r lifecycle::badge("stable")`
|
|
| 266 |
#' |
|
| 267 |
#' A method that updates existing [`Data`] object with new data. |
|
| 268 |
#' |
|
| 269 |
#' @param object (`Data`)\cr object you want to update. |
|
| 270 |
#' @param x (`number`)\cr the dose level (one level only!). |
|
| 271 |
#' @param y (`integer`)\cr the DLT vector (0/1 vector) for all patients in this |
|
| 272 |
#' cohort. You can also supply `numeric` vectors, but these will then be |
|
| 273 |
#' converted to `integer` internally. |
|
| 274 |
#' @param ID (`integer`)\cr the patient IDs. |
|
| 275 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
| 276 |
#' `integer` internally. |
|
| 277 |
#' @param new_cohort (`flag`)\cr if `TRUE` (default) the new data are assigned |
|
| 278 |
#' to a new cohort. |
|
| 279 |
#' @param check (`flag`)\cr whether the validation of the updated object should |
|
| 280 |
#' be conducted. See details below. |
|
| 281 |
#' @param ... not used. |
|
| 282 |
#' |
|
| 283 |
#' @return The new, updated [`Data`] object. |
|
| 284 |
#' |
|
| 285 |
#' @details The current implementation of this `update` method allows for |
|
| 286 |
#' updating the `Data` class object by adding a single dose level `x` only. |
|
| 287 |
#' However, there might be some use cases where the new cohort to be added |
|
| 288 |
#' contains a placebo and active dose. Hence, such update would need to be |
|
| 289 |
#' performed iteratively by calling the `update` method twice. For example, |
|
| 290 |
#' in the first call a user can add a placebo, and then in the second call, |
|
| 291 |
#' an active dose. Since having a cohort with placebo only is not allowed, |
|
| 292 |
#' the `update` method would normally throw the error when attempting to add |
|
| 293 |
#' a placebo in the first call. To allow for such updates, the `check` |
|
| 294 |
#' parameter should be then set to `FALSE` for that first call. |
|
| 295 |
#' |
|
| 296 |
#' @aliases update-Data |
|
| 297 |
#' @export |
|
| 298 |
#' @example examples/Data-method-update.R |
|
| 299 |
#' |
|
| 300 |
setMethod( |
|
| 301 |
f = "update", |
|
| 302 |
signature = signature(object = "Data"), |
|
| 303 |
definition = function( |
|
| 304 |
object, |
|
| 305 |
x, |
|
| 306 |
y, |
|
| 307 |
ID = length(object@ID) + seq_along(y), |
|
| 308 |
new_cohort = TRUE, |
|
| 309 |
check = TRUE, |
|
| 310 |
... |
|
| 311 |
) {
|
|
| 312 | 598x |
assert_numeric(x, min.len = 0, max.len = 1) |
| 313 | 598x |
assert_integerish(y, lower = 0, upper = 1, any.missing = FALSE) |
| 314 | 598x |
assert_integerish(ID, len = length(y), any.missing = FALSE) |
| 315 | 598x |
assert_disjunct(object@ID, ID) |
| 316 | 598x |
assert_flag(new_cohort) |
| 317 | 598x |
assert_flag(check) |
| 318 | ||
| 319 |
# How many additional patients, ie. the length of the update. |
|
| 320 | 598x |
n <- length(y) |
| 321 | ||
| 322 |
# Which grid level is the dose? |
|
| 323 | 598x |
gridLevel <- match_within_tolerance(x, object@doseGrid) |
| 324 | 598x |
object@xLevel <- c(object@xLevel, rep(gridLevel, n)) |
| 325 | ||
| 326 |
# Add dose. |
|
| 327 | 598x |
object@x <- c(object@x, rep(as.numeric(x), n)) |
| 328 | ||
| 329 |
# Add DLT data. |
|
| 330 | 598x |
object@y <- c(object@y, as.integer(y)) |
| 331 | ||
| 332 |
# Add ID. |
|
| 333 | 598x |
object@ID <- c(object@ID, as.integer(ID)) |
| 334 | ||
| 335 |
# Add cohort number. |
|
| 336 | 598x |
new_cohort_id <- if (object@nObs == 0) {
|
| 337 | 96x |
1L |
| 338 |
} else {
|
|
| 339 | 502x |
tail(object@cohort, 1L) + ifelse(new_cohort, 1L, 0L) |
| 340 |
} |
|
| 341 | 598x |
object@cohort <- c(object@cohort, rep(new_cohort_id, n)) |
| 342 | ||
| 343 |
# Increment sample size. |
|
| 344 | 598x |
object@nObs <- object@nObs + n |
| 345 | ||
| 346 | 598x |
if (check) {
|
| 347 | 438x |
validObject(object) |
| 348 |
} |
|
| 349 | ||
| 350 | 597x |
object |
| 351 |
} |
|
| 352 |
) |
|
| 353 | ||
| 354 |
## DataOrdinal ---- |
|
| 355 | ||
| 356 |
#' Updating `DataOrdinal` Objects |
|
| 357 |
#' |
|
| 358 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 359 |
#' |
|
| 360 |
#' A method that updates existing [`DataOrdinal`] object with new data. |
|
| 361 |
#' |
|
| 362 |
#' @param object (`DataOrdinal`)\cr object you want to update. |
|
| 363 |
#' @param x (`number`)\cr the dose level (one level only!). |
|
| 364 |
#' @param y (`integer`)\cr the vector of toxicity grades (0, 1, 2, ...) for all |
|
| 365 |
#' patients in this cohort. You can also supply `numeric` vectors, but these |
|
| 366 |
#' will then be converted to `integer` internally. |
|
| 367 |
#' @param ID (`integer`)\cr the patient IDs. |
|
| 368 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
| 369 |
#' `integer` internally. |
|
| 370 |
#' @param new_cohort (`flag`)\cr if `TRUE` (default) the new data are assigned |
|
| 371 |
#' to a new cohort. |
|
| 372 |
#' @param check (`flag`)\cr whether the validation of the updated object should |
|
| 373 |
#' be conducted. See Details below. |
|
| 374 |
#' @param ... not used. |
|
| 375 |
#' |
|
| 376 |
#' @return The new, updated [`DataOrdinal`] object. |
|
| 377 |
#' |
|
| 378 |
#' @details The current implementation of this `update` method allows for |
|
| 379 |
#' updating the `DataOrdinal` class object by adding a single dose level `x` only. |
|
| 380 |
#' However, there might be some use cases where the new cohort to be added |
|
| 381 |
#' contains a placebo and active dose. Hence, such update would need to be |
|
| 382 |
#' performed iteratively by calling the `update` method twice. For example, |
|
| 383 |
#' in the first call a user can add a placebo, and then in the second call, |
|
| 384 |
#' an active dose. Since having a cohort with placebo only is not allowed, |
|
| 385 |
#' the `update` method would normally throw the error when attempting to add |
|
| 386 |
#' a placebo in the first call. To allow for such updates, the `check` |
|
| 387 |
#' parameter should be then set to `FALSE` for that first call. |
|
| 388 |
#' |
|
| 389 |
#' @aliases update-DataOrdinal |
|
| 390 |
#' @export |
|
| 391 |
#' @example examples/DataOrdinal-method-update.R |
|
| 392 |
#' |
|
| 393 |
setMethod( |
|
| 394 |
f = "update", |
|
| 395 |
signature = signature(object = "DataOrdinal"), |
|
| 396 |
definition = function( |
|
| 397 |
object, |
|
| 398 |
x, |
|
| 399 |
y, |
|
| 400 |
ID = length(object@ID) + seq_along(y), |
|
| 401 |
new_cohort = TRUE, |
|
| 402 |
check = TRUE, |
|
| 403 |
... |
|
| 404 |
) {
|
|
| 405 | 5x |
assert_numeric(x, min.len = 0, max.len = 1) |
| 406 | 5x |
assert_integerish( |
| 407 | 5x |
y, |
| 408 | 5x |
lower = 0, |
| 409 | 5x |
upper = length(object@yCategories) - 1, |
| 410 | 5x |
any.missing = FALSE |
| 411 |
) |
|
| 412 | 5x |
assert_integerish(ID, unique = TRUE, any.missing = FALSE, len = length(y)) |
| 413 | 5x |
assert_disjunct(object@ID, ID) |
| 414 | 5x |
assert_flag(new_cohort) |
| 415 | 5x |
assert_flag(check) |
| 416 | ||
| 417 |
# How many additional patients, ie. the length of the update. |
|
| 418 | 5x |
n <- length(y) |
| 419 | ||
| 420 |
# Which grid level is the dose? |
|
| 421 | 5x |
gridLevel <- match_within_tolerance(x, object@doseGrid) |
| 422 | 5x |
object@xLevel <- c(object@xLevel, rep(gridLevel, n)) |
| 423 | ||
| 424 |
# Add dose. |
|
| 425 | 5x |
object@x <- c(object@x, rep(as.numeric(x), n)) |
| 426 | ||
| 427 |
# Add DLT data. |
|
| 428 | 5x |
object@y <- c(object@y, as.integer(y)) |
| 429 | ||
| 430 |
# Add ID. |
|
| 431 | 5x |
object@ID <- c(object@ID, as.integer(ID)) |
| 432 | ||
| 433 |
# Add cohort number. |
|
| 434 | 5x |
new_cohort_id <- if (object@nObs == 0) {
|
| 435 | 1x |
1L |
| 436 |
} else {
|
|
| 437 | 4x |
tail(object@cohort, 1L) + ifelse(new_cohort, 1L, 0L) |
| 438 |
} |
|
| 439 | 5x |
object@cohort <- c(object@cohort, rep(new_cohort_id, n)) |
| 440 | ||
| 441 |
# Increment sample size. |
|
| 442 | 5x |
object@nObs <- object@nObs + n |
| 443 | ||
| 444 | 5x |
if (check) {
|
| 445 | 4x |
validObject(object) |
| 446 |
} |
|
| 447 | ||
| 448 | 4x |
object |
| 449 |
} |
|
| 450 |
) |
|
| 451 | ||
| 452 |
## DataParts ---- |
|
| 453 | ||
| 454 |
#' Updating `DataParts` Objects |
|
| 455 |
#' |
|
| 456 |
#' @description `r lifecycle::badge("stable")`
|
|
| 457 |
#' |
|
| 458 |
#' A method that updates existing [`DataParts`] object with new data. |
|
| 459 |
#' |
|
| 460 |
#' @param object (`DataParts`)\cr object you want to update. |
|
| 461 |
#' @inheritParams update,Data-method |
|
| 462 |
#' @param ... further arguments passed to `Data` update method [`update-Data`]. |
|
| 463 |
#' @param check (`flag`)\cr whether the validation of the updated object |
|
| 464 |
#' should be conducted. See help for [`update-Data`] for more details |
|
| 465 |
#' on the use case of this parameter. |
|
| 466 |
#' |
|
| 467 |
#' @return The new, updated [`DataParts`] object. |
|
| 468 |
#' |
|
| 469 |
#' @aliases update-DataParts |
|
| 470 |
#' @export |
|
| 471 |
#' @example examples/Data-method-update-DataParts.R |
|
| 472 |
#' |
|
| 473 |
setMethod( |
|
| 474 |
f = "update", |
|
| 475 |
signature = signature(object = "DataParts"), |
|
| 476 |
definition = function(object, x, y, ..., check = TRUE) {
|
|
| 477 | 3x |
assert_numeric(y) |
| 478 | 3x |
assert_flag(check) |
| 479 | ||
| 480 |
# Update slots corresponding to `Data` class. |
|
| 481 | 3x |
object <- callNextMethod(object = object, x = x, y = y, ..., check = FALSE) |
| 482 | ||
| 483 |
# Update the part information. |
|
| 484 | ||
| 485 | 3x |
object@part <- c(object@part, rep(object@nextPart, length(y))) |
| 486 | ||
| 487 |
# Decide which part the next cohort will belong to: |
|
| 488 |
# only if the `nextPart` was 1, it can potentially be required |
|
| 489 |
# to change it to 2 (once it is 2, it stays). |
|
| 490 | 3x |
if (object@nextPart == 1L) {
|
| 491 |
# If there was a DLT in one of the cohorts, |
|
| 492 |
# or if the current dose was the highest from part 1. |
|
| 493 | 3x |
if (any(object@y == 1L) || x == max(object@part1Ladder)) {
|
| 494 |
# Then this closes part 1 and the next cohort will be from part 2. |
|
| 495 | 3x |
object@nextPart <- 2L |
| 496 |
} |
|
| 497 |
} |
|
| 498 | ||
| 499 | 3x |
if (check) {
|
| 500 | 3x |
validObject(object) |
| 501 |
} |
|
| 502 | ||
| 503 | 3x |
object |
| 504 |
} |
|
| 505 |
) |
|
| 506 | ||
| 507 |
## DataDual ---- |
|
| 508 | ||
| 509 |
#' Updating `DataDual` Objects |
|
| 510 |
#' |
|
| 511 |
#' @description `r lifecycle::badge("stable")`
|
|
| 512 |
#' |
|
| 513 |
#' A method that updates existing [`DataDual`] object with new data. |
|
| 514 |
#' |
|
| 515 |
#' @param object (`DataDual`)\cr object you want to update. |
|
| 516 |
#' @param w (`numeric`)\cr the continuous vector of biomarker values |
|
| 517 |
#' for all the patients in this update. |
|
| 518 |
#' @param ... further arguments passed to `Data` update method [`update-Data`]. |
|
| 519 |
#' @param check (`flag`)\cr whether the validation of the updated object |
|
| 520 |
#' should be conducted. See help for [`update-Data`] for more details |
|
| 521 |
#' on the use case of this parameter. |
|
| 522 |
#' |
|
| 523 |
#' @return The new, updated [`DataDual`] object. |
|
| 524 |
#' |
|
| 525 |
#' @aliases update-DataDual |
|
| 526 |
#' @export |
|
| 527 |
#' @example examples/Data-method-update-DataDual.R |
|
| 528 |
#' |
|
| 529 |
setMethod( |
|
| 530 |
f = "update", |
|
| 531 |
signature = signature(object = "DataDual"), |
|
| 532 |
definition = function(object, w, ..., check = TRUE) {
|
|
| 533 | 64x |
assert_numeric(w) |
| 534 | 64x |
assert_flag(check) |
| 535 | ||
| 536 |
# Update slots corresponding to `Data` class. |
|
| 537 | 64x |
object <- callNextMethod(object = object, ..., check = FALSE) |
| 538 | ||
| 539 |
# Update the biomarker information. |
|
| 540 | 64x |
object@w <- c(object@w, w) |
| 541 | ||
| 542 | 64x |
if (check) {
|
| 543 | 50x |
validObject(object) |
| 544 |
} |
|
| 545 | ||
| 546 | 64x |
object |
| 547 |
} |
|
| 548 |
) |
|
| 549 | ||
| 550 |
## DataDA ---- |
|
| 551 | ||
| 552 |
#' Updating `DataDA` Objects |
|
| 553 |
#' |
|
| 554 |
#' @description `r lifecycle::badge("stable")`
|
|
| 555 |
#' |
|
| 556 |
#' A method that updates existing [`DataDA`] object with new data. |
|
| 557 |
#' |
|
| 558 |
#' @note This function is capable of not only adding new patients but also |
|
| 559 |
#' updates existing ones with respect to `y`, `t0`, `u` slots. |
|
| 560 |
#' |
|
| 561 |
#' @param object (`DataDA`)\cr object you want to update. |
|
| 562 |
#' @param u (`numeric`)\cr the new DLT free survival times for all patients, |
|
| 563 |
#' i.e. for existing patients in the `object` as well as for new patients. |
|
| 564 |
#' @param t0 (`numeric`)\cr the time that each patient starts DLT observation |
|
| 565 |
#' window. This parameter covers all patients, i.e. existing patients in the |
|
| 566 |
#' `object` as well as for new patients. |
|
| 567 |
#' @param trialtime (`number`)\cr current time in the trial, i.e. a followup |
|
| 568 |
#' time. |
|
| 569 |
#' @param y (`numeric`)\cr the new DLTs for all patients, i.e. for existing |
|
| 570 |
#' patients in the `object` as well as for new patients. |
|
| 571 |
#' @param ... further arguments passed to `Data` update method [`update-Data`]. |
|
| 572 |
#' These are used when there are new patients to be added to the cohort. |
|
| 573 |
#' @param check (`flag`)\cr whether the validation of the updated object |
|
| 574 |
#' should be conducted. See help for [`update-Data`] for more details |
|
| 575 |
#' on the use case of this parameter. |
|
| 576 |
#' |
|
| 577 |
#' @return The new, updated [`DataDA`] object. |
|
| 578 |
#' |
|
| 579 |
#' @aliases update-DataDA |
|
| 580 |
#' @export |
|
| 581 |
#' @example examples/Data-method-update-DataDA.R |
|
| 582 |
#' |
|
| 583 |
setMethod( |
|
| 584 |
f = "update", |
|
| 585 |
signature = signature(object = "DataDA"), |
|
| 586 |
definition = function(object, u, t0, trialtime, y, ..., check = TRUE) {
|
|
| 587 | 53x |
assert_flag(check) |
| 588 | 53x |
assert_numeric(y, lower = 0, upper = 1) |
| 589 | 53x |
assert_true(length(y) == 0 || length(y) >= object@nObs) |
| 590 | 53x |
assert_numeric(u, lower = 0, len = length(y)) |
| 591 | 53x |
assert_numeric(t0, lower = 0, len = length(y)) |
| 592 | 53x |
assert_integerish(y * (trialtime >= t0 + u)) |
| 593 | 53x |
if (length(y) > 0) {
|
| 594 | 52x |
assert_number(trialtime, lower = max(c(object@t0, t0))) |
| 595 |
} |
|
| 596 | ||
| 597 |
# How many additional patients. |
|
| 598 | 52x |
n <- max(length(y) - object@nObs, 0L) |
| 599 | ||
| 600 |
# Update slots corresponding to `Data` class. |
|
| 601 | 52x |
object <- callNextMethod( |
| 602 | 52x |
object = object, |
| 603 | 52x |
y = y[object@nObs + seq_len(n)], # Empty vector when n = 0. |
| 604 |
..., |
|
| 605 | 52x |
check = FALSE |
| 606 |
) |
|
| 607 | ||
| 608 |
# DLT will be observed once the followup time >= the time to DLT |
|
| 609 |
# and y = 1 at the same time. |
|
| 610 | 52x |
object@y <- as.integer(y * (trialtime >= t0 + u)) |
| 611 | ||
| 612 |
# Update DLT free survival time. |
|
| 613 | 52x |
object@u <- apply(rbind(u, trialtime - t0), 2, min) |
| 614 | ||
| 615 |
# Update t0. |
|
| 616 | 52x |
object@t0 <- t0 |
| 617 | ||
| 618 | 52x |
if (check) {
|
| 619 | 52x |
validObject(object) |
| 620 |
} |
|
| 621 | ||
| 622 | 52x |
object |
| 623 |
} |
|
| 624 |
) |
|
| 625 | ||
| 626 |
# getEff ---- |
|
| 627 | ||
| 628 |
## generic ---- |
|
| 629 | ||
| 630 |
#' Extracting Efficacy Responses for Subjects Categorized by the DLT |
|
| 631 |
#' |
|
| 632 |
#' @description `r lifecycle::badge("stable")`
|
|
| 633 |
#' |
|
| 634 |
#' A method that extracts efficacy responses for subjects and categorizes it |
|
| 635 |
#' with respect to DLT, i.e. DLT or no DLT. The efficacy responses |
|
| 636 |
#' are reported together with their corresponding dose levels. |
|
| 637 |
#' |
|
| 638 |
#' @param object (`DataDual`)\cr object from which the responses and dose levels |
|
| 639 |
#' are extracted. |
|
| 640 |
#' @param ... further arguments passed to class-specific methods. |
|
| 641 |
#' @return `list` with efficacy responses categorized by the DLT value. |
|
| 642 |
#' @export |
|
| 643 |
#' |
|
| 644 |
setGeneric( |
|
| 645 |
name = "getEff", |
|
| 646 |
def = function(object, ...) {
|
|
| 647 | 236x |
standardGeneric("getEff")
|
| 648 |
}, |
|
| 649 |
valueClass = "list" |
|
| 650 |
) |
|
| 651 | ||
| 652 |
## DataDual ---- |
|
| 653 | ||
| 654 |
#' @rdname getEff |
|
| 655 |
#' |
|
| 656 |
#' @param no_dlt (`flag`)\cr should only no DLT responses be returned? Otherwise, |
|
| 657 |
#' all responses are returned. |
|
| 658 |
#' |
|
| 659 |
#' @aliases getEff-DataDual |
|
| 660 |
#' @example examples/Data-method-getEff.R |
|
| 661 |
#' |
|
| 662 |
setMethod( |
|
| 663 |
f = "getEff", |
|
| 664 |
signature = signature(object = "DataDual"), |
|
| 665 |
definition = function(object, no_dlt = FALSE) {
|
|
| 666 | 236x |
assert_flag(no_dlt) |
| 667 | ||
| 668 | 236x |
is_dlt <- object@y == 1L |
| 669 | 236x |
is_no_dlt <- !is_dlt |
| 670 | ||
| 671 | 236x |
eff <- if (any(is_no_dlt)) {
|
| 672 | 174x |
list(x_no_dlt = object@x[is_no_dlt], w_no_dlt = object@w[is_no_dlt]) |
| 673 |
} else {
|
|
| 674 | 62x |
list(x_no_dlt = NULL, w_no_dlt = NULL) |
| 675 |
} |
|
| 676 | ||
| 677 | 236x |
if (!no_dlt) {
|
| 678 | 3x |
eff_dlt <- if (any(is_dlt)) {
|
| 679 | 2x |
list(x_dlt = object@x[is_dlt], w_dlt = object@w[is_dlt]) |
| 680 |
} else {
|
|
| 681 | 1x |
list(x_dlt = NULL, w_dlt = NULL) |
| 682 |
} |
|
| 683 | 3x |
eff <- c(eff, eff_dlt) |
| 684 |
} |
|
| 685 | 236x |
eff |
| 686 |
} |
|
| 687 |
) |
|
| 688 | ||
| 689 |
# ngrid ---- |
|
| 690 | ||
| 691 |
## generic ---- |
|
| 692 | ||
| 693 |
#' Number of Doses in Grid |
|
| 694 |
#' |
|
| 695 |
#' @description `r lifecycle::badge("stable")`
|
|
| 696 |
#' |
|
| 697 |
#' A function that gets the number of doses in grid. User can choose whether |
|
| 698 |
#' the placebo dose (if any) should be counted or not. |
|
| 699 |
#' |
|
| 700 |
#' @param object (`Data`)\cr object with dose grid. |
|
| 701 |
#' @param ignore_placebo (`flag`)\cr should placebo dose (if any) not be counted? |
|
| 702 |
#' @param ... further arguments passed to class-specific methods. |
|
| 703 |
#' @return `integer` the number of doses in grid. |
|
| 704 |
#' @export |
|
| 705 |
#' |
|
| 706 |
setGeneric( |
|
| 707 |
name = "ngrid", |
|
| 708 |
def = function(object, ignore_placebo = TRUE, ...) {
|
|
| 709 | 18x |
assert_flag(ignore_placebo) |
| 710 | 16x |
standardGeneric("ngrid")
|
| 711 |
}, |
|
| 712 |
valueClass = "integer" |
|
| 713 |
) |
|
| 714 | ||
| 715 |
## Data ---- |
|
| 716 | ||
| 717 |
#' @rdname ngrid |
|
| 718 |
#' |
|
| 719 |
#' @aliases ngrid-Data |
|
| 720 |
#' @example examples/Data-method-ngrid.R |
|
| 721 |
#' |
|
| 722 |
setMethod( |
|
| 723 |
f = "ngrid", |
|
| 724 |
signature = signature(object = "Data"), |
|
| 725 |
definition = function(object, ignore_placebo, ...) {
|
|
| 726 | 16x |
if (ignore_placebo && object@placebo) {
|
| 727 | 4x |
max(object@nGrid - 1L, 0L) |
| 728 |
} else {
|
|
| 729 | 12x |
object@nGrid |
| 730 |
} |
|
| 731 |
} |
|
| 732 |
) |
|
| 733 | ||
| 734 |
# dose_grid_range ---- |
|
| 735 | ||
| 736 |
## generic ---- |
|
| 737 | ||
| 738 |
#' Getting the Dose Grid Range |
|
| 739 |
#' |
|
| 740 |
#' @description `r lifecycle::badge("stable")`
|
|
| 741 |
#' |
|
| 742 |
#' A function that returns a vector of length two with the minimum and maximum |
|
| 743 |
#' dose in a grid. It returns `c(-Inf, Inf)` if the range cannot be determined, |
|
| 744 |
#' which happens when the dose grid is empty. User can choose whether the |
|
| 745 |
#' placebo dose (if any) should be counted or not. |
|
| 746 |
#' |
|
| 747 |
#' @param object (`Data`)\cr object with dose grid. |
|
| 748 |
#' @param ... further arguments passed to class-specific methods. |
|
| 749 |
#' @return A `numeric` vector containing the minimum and maximum of all the |
|
| 750 |
#' doses in a grid or `c(-Inf, Inf)`. |
|
| 751 |
#' |
|
| 752 |
#' @export |
|
| 753 |
#' |
|
| 754 |
setGeneric( |
|
| 755 |
name = "dose_grid_range", |
|
| 756 |
def = function(object, ...) {
|
|
| 757 | 267x |
standardGeneric("dose_grid_range")
|
| 758 |
}, |
|
| 759 |
valueClass = "numeric" |
|
| 760 |
) |
|
| 761 | ||
| 762 |
## Data ---- |
|
| 763 | ||
| 764 |
#' @rdname dose_grid_range |
|
| 765 |
#' |
|
| 766 |
#' @param ignore_placebo (`flag`)\cr should placebo dose (if any) not be counted? |
|
| 767 |
#' |
|
| 768 |
#' @aliases dose_grid_range-Data |
|
| 769 |
#' @example examples/Data-method-dose_grid_range.R |
|
| 770 |
#' |
|
| 771 |
setMethod( |
|
| 772 |
f = "dose_grid_range", |
|
| 773 |
signature = signature(object = "Data"), |
|
| 774 |
definition = function(object, ignore_placebo = TRUE) {
|
|
| 775 | 251x |
h_obtain_dose_grid_range(object, ignore_placebo) |
| 776 |
} |
|
| 777 |
) |
|
| 778 | ||
| 779 | ||
| 780 |
## DataOrdinal ---- |
|
| 781 | ||
| 782 |
#' @include Data-methods.R |
|
| 783 |
#' @rdname dose_grid_range |
|
| 784 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 785 |
#' |
|
| 786 |
#' @param ignore_placebo (`flag`)\cr should placebo dose (if any) not be counted? |
|
| 787 |
#' |
|
| 788 |
#' @aliases dose_grid_range-Data |
|
| 789 |
#' @example examples/DataOrdinal-method-dose_grid_range.R |
|
| 790 |
#' |
|
| 791 |
setMethod( |
|
| 792 |
f = "dose_grid_range", |
|
| 793 |
signature = signature(object = "DataOrdinal"), |
|
| 794 |
definition = function(object, ignore_placebo = TRUE) {
|
|
| 795 | 16x |
h_obtain_dose_grid_range(object, ignore_placebo) |
| 796 |
} |
|
| 797 |
) |
|
| 798 | ||
| 799 |
# tidy ---- |
|
| 800 | ||
| 801 |
## GeneralData ---- |
|
| 802 | ||
| 803 |
#' Tidy Method for the [`GeneralData`] Class |
|
| 804 |
#' |
|
| 805 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 806 |
#' |
|
| 807 |
#' A method that tidies a [`GeneralData`] object. |
|
| 808 |
#' |
|
| 809 |
#' @return The [`tibble`] object. |
|
| 810 |
#' |
|
| 811 |
#' @aliases tidy-GeneralData |
|
| 812 |
#' @rdname tidy |
|
| 813 |
#' @export |
|
| 814 |
#' @example examples/GeneralData-method-tidy.R |
|
| 815 |
#' |
|
| 816 |
setMethod( |
|
| 817 |
f = "tidy", |
|
| 818 |
signature = signature(x = "GeneralData"), |
|
| 819 |
definition = function(x, ...) {
|
|
| 820 | 197x |
d <- tibble::tibble( |
| 821 | 197x |
ID = x@ID, |
| 822 | 197x |
Cohort = x@cohort, |
| 823 | 197x |
Dose = x@x, |
| 824 | 197x |
XLevel = x@xLevel, |
| 825 | 197x |
Tox = as.logical(x@y), |
| 826 | 197x |
Placebo = x@placebo, |
| 827 | 197x |
NObs = x@nObs, |
| 828 | 197x |
NGrid = x@nGrid, |
| 829 | 197x |
DoseGrid = list(x@doseGrid) |
| 830 |
) %>% |
|
| 831 | 197x |
h_tidy_class(x) |
| 832 |
} |
|
| 833 |
) |
|
| 834 | ||
| 835 |
## DataGrouped ---- |
|
| 836 | ||
| 837 |
#' Tidy Method for the [`DataGrouped`] Class |
|
| 838 |
#' |
|
| 839 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 840 |
#' |
|
| 841 |
#' A method that tidies a [`DataGrouped`] object. |
|
| 842 |
#' |
|
| 843 |
#' @return The [`tibble`] object. |
|
| 844 |
#' |
|
| 845 |
#' @aliases tidy-DataGrouped |
|
| 846 |
#' @rdname tidy |
|
| 847 |
#' @export |
|
| 848 |
#' @example examples/GeneralData-method-tidy.R |
|
| 849 |
#' |
|
| 850 |
setMethod( |
|
| 851 |
f = "tidy", |
|
| 852 |
signature = signature(x = "DataGrouped"), |
|
| 853 |
definition = function(x, ...) {
|
|
| 854 | 6x |
d <- callNextMethod() |
| 855 | 6x |
d %>% |
| 856 | 6x |
tibble::add_column(Group = x@group) %>% |
| 857 | 6x |
h_tidy_class(x) |
| 858 |
} |
|
| 859 |
) |
|
| 860 | ||
| 861 |
## DataDA ---- |
|
| 862 | ||
| 863 |
#' Tidy Method for the [`DataDA`] Class |
|
| 864 |
#' |
|
| 865 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 866 |
#' |
|
| 867 |
#' A method that tidies a [`DataDA`] object. |
|
| 868 |
#' |
|
| 869 |
#' @return The [`tibble`] object. |
|
| 870 |
#' |
|
| 871 |
#' @aliases tidy-DataDA |
|
| 872 |
#' @rdname tidy |
|
| 873 |
#' @export |
|
| 874 |
#' @example examples/GeneralData-method-tidy.R |
|
| 875 |
#' |
|
| 876 |
setMethod( |
|
| 877 |
f = "tidy", |
|
| 878 |
signature = signature(x = "DataDA"), |
|
| 879 |
definition = function(x, ...) {
|
|
| 880 | 18x |
d <- callNextMethod() |
| 881 | 18x |
d %>% |
| 882 | 18x |
tibble::add_column(U = x@u) %>% |
| 883 | 18x |
tibble::add_column(T0 = x@t0) %>% |
| 884 | 18x |
tibble::add_column(TMax = x@Tmax) %>% |
| 885 | 18x |
h_tidy_class(x) |
| 886 |
} |
|
| 887 |
) |
|
| 888 | ||
| 889 |
## DataDA ---- |
|
| 890 | ||
| 891 |
#' Tidy Method for the [`DataDual`] Class |
|
| 892 |
#' |
|
| 893 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 894 |
#' |
|
| 895 |
#' A method that tidies a [`DataDual`] object. |
|
| 896 |
#' |
|
| 897 |
#' @return The [`tibble`] object. |
|
| 898 |
#' |
|
| 899 |
#' @aliases tidy-DataDual |
|
| 900 |
#' @rdname tidy |
|
| 901 |
#' @export |
|
| 902 |
#' @example examples/GeneralData-method-tidy.R |
|
| 903 |
#' |
|
| 904 |
setMethod( |
|
| 905 |
f = "tidy", |
|
| 906 |
signature = signature(x = "DataDual"), |
|
| 907 |
definition = function(x, ...) {
|
|
| 908 | 73x |
d <- callNextMethod() |
| 909 | 73x |
d %>% |
| 910 | 73x |
tibble::add_column(W = x@w) %>% |
| 911 | 73x |
h_tidy_class(x) |
| 912 |
} |
|
| 913 |
) |
|
| 914 | ||
| 915 |
## DataParts ---- |
|
| 916 | ||
| 917 |
#' Tidy Method for the [`DataParts`] Class |
|
| 918 |
#' |
|
| 919 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 920 |
#' |
|
| 921 |
#' A method that tidies a [`DataParts`] object. |
|
| 922 |
#' |
|
| 923 |
#' @return The [`tibble`] object. |
|
| 924 |
#' |
|
| 925 |
#' @aliases tidy-DataParts |
|
| 926 |
#' @rdname tidy |
|
| 927 |
#' @export |
|
| 928 |
#' @example examples/GeneralData-method-tidy.R |
|
| 929 |
#' |
|
| 930 |
setMethod( |
|
| 931 |
f = "tidy", |
|
| 932 |
signature = signature(x = "DataParts"), |
|
| 933 |
definition = function(x, ...) {
|
|
| 934 | 6x |
d <- callNextMethod() |
| 935 | 6x |
d %>% |
| 936 | 6x |
tibble::add_column(Part = x@part) %>% |
| 937 | 6x |
tibble::add_column(NextPart = x@nextPart) %>% |
| 938 | 6x |
tibble::add_column(Part1Ladder = list(x@part1Ladder)) %>% |
| 939 | 6x |
h_tidy_class(x) |
| 940 |
} |
|
| 941 |
) |
|
| 942 | ||
| 943 |
## DataMixture ---- |
|
| 944 | ||
| 945 |
#' Tidy Method for the [`DataMixture`] Class |
|
| 946 |
#' |
|
| 947 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 948 |
#' |
|
| 949 |
#' A method that tidies a [`DataMixture`] object. |
|
| 950 |
#' @section Usage Notes: |
|
| 951 |
#' The prior observations are indicated by a `Cohort` value of `0` in the returned |
|
| 952 |
#' `tibble`. |
|
| 953 |
#' @return The [`tibble`] object. |
|
| 954 |
#' |
|
| 955 |
#' @aliases tidy-DataMixture |
|
| 956 |
#' @rdname tidy |
|
| 957 |
#' @export |
|
| 958 |
#' @example examples/GeneralData-method-tidy.R |
|
| 959 |
#' |
|
| 960 |
setMethod( |
|
| 961 |
f = "tidy", |
|
| 962 |
signature = signature(x = "DataMixture"), |
|
| 963 |
definition = function(x, ...) {
|
|
| 964 | 6x |
observed <- callNextMethod() |
| 965 | 6x |
tibble::tibble( |
| 966 | 6x |
Cohort = 0, |
| 967 | 6x |
Dose = x@xshare, |
| 968 | 6x |
Tox = as.logical(x@yshare), |
| 969 | 6x |
ID = sort(seq_along(x@xshare)), |
| 970 | 6x |
Placebo = x@placebo, |
| 971 | 6x |
NObs = x@nObs, |
| 972 | 6x |
NGrid = x@nGrid, |
| 973 | 6x |
DoseGrid = list(x@doseGrid), |
| 974 | 6x |
XLevel = which(x@doseGrid %in% x@xshare) |
| 975 |
) %>% |
|
| 976 | 6x |
dplyr::bind_rows(observed) %>% |
| 977 | 6x |
h_tidy_class(x) |
| 978 |
} |
|
| 979 |
) |
|
| 980 | ||
| 981 | ||
| 982 |
## DataOrdinal ---- |
|
| 983 | ||
| 984 |
#' Tidy Method for the [`DataMixture`] Class |
|
| 985 |
#' |
|
| 986 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 987 |
#' |
|
| 988 |
#' A method that tidies a [`DataOrdinal`] object. |
|
| 989 |
#' @section Usage Notes: |
|
| 990 |
#' @return The [`tibble`] object. |
|
| 991 |
#' |
|
| 992 |
#' @aliases tidy-DataOrdinal |
|
| 993 |
#' @rdname tidy |
|
| 994 |
#' @export |
|
| 995 |
#' @example examples/GeneralData-method-tidy.R |
|
| 996 |
#' |
|
| 997 |
setMethod( |
|
| 998 |
f = "tidy", |
|
| 999 |
signature = signature(x = "DataOrdinal"), |
|
| 1000 |
definition = function(x, ...) {
|
|
| 1001 | 23x |
y <- tibble::tibble( |
| 1002 | 23x |
ID = x@ID, |
| 1003 | 23x |
Cohort = x@cohort, |
| 1004 | 23x |
Dose = x@x, |
| 1005 | 23x |
Tox = x@y, |
| 1006 | 23x |
Placebo = x@placebo, |
| 1007 | 23x |
NObs = x@nObs, |
| 1008 | 23x |
NGrid = x@nGrid, |
| 1009 | 23x |
DoseGrid = list(x@doseGrid), |
| 1010 | 23x |
XLevel = x@xLevel |
| 1011 |
) %>% |
|
| 1012 | 23x |
tidyr::pivot_wider( |
| 1013 | 23x |
names_from = "Tox", |
| 1014 | 23x |
values_from = "Tox", |
| 1015 | 23x |
names_prefix = "Cat", |
| 1016 | 23x |
values_fill = 0 |
| 1017 |
) |
|
| 1018 | 23x |
if (nrow(y) > 0) {
|
| 1019 | 8x |
y <- y %>% |
| 1020 | 8x |
dplyr::mutate( |
| 1021 | 8x |
dplyr::across(tidyselect::matches("Cat\\d+"), \(x) x > 0)
|
| 1022 |
) %>% |
|
| 1023 | 8x |
dplyr::rowwise() %>% |
| 1024 | 8x |
dplyr::mutate( |
| 1025 | 8x |
AnyTox = any(dplyr::across( |
| 1026 | 8x |
c(tidyselect::starts_with("Cat"), -tidyselect::all_of("Cat0")),
|
| 1027 | 8x |
any |
| 1028 |
)), |
|
| 1029 |
# Direct assignment fails on GitHub |
|
| 1030 | 8x |
Cat0 = !.data$AnyTox |
| 1031 |
) %>% |
|
| 1032 | 8x |
dplyr::select(-tidyselect::all_of("AnyTox")) %>%
|
| 1033 | 8x |
dplyr::ungroup() |
| 1034 |
} |
|
| 1035 | 23x |
y <- y %>% h_tidy_class(x) |
| 1036 | 23x |
y |
| 1037 |
} |
|
| 1038 |
) |
| 1 |
#' @include helpers.R |
|
| 2 |
#' @include Data-validity.R |
|
| 3 |
#' @include CrmPackClass-class.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 |
# GeneralData-class ---- |
|
| 7 | ||
| 8 |
#' `GeneralData` |
|
| 9 |
#' |
|
| 10 |
#' @description `r lifecycle::badge("stable")`
|
|
| 11 |
#' |
|
| 12 |
#' [`GeneralData`] is a class for general data input. |
|
| 13 |
#' |
|
| 14 |
#' @slot ID (`integer`)\cr unique patient IDs. |
|
| 15 |
#' @slot cohort (`integer`)\cr the cohort (non-negative sorted) indices. |
|
| 16 |
#' @slot nObs (`integer`)\cr number of observations, a single value. |
|
| 17 |
#' |
|
| 18 |
#' @aliases GeneralData |
|
| 19 |
#' @export |
|
| 20 |
#' |
|
| 21 |
.GeneralData <- setClass( |
|
| 22 |
Class = "GeneralData", |
|
| 23 |
slots = c( |
|
| 24 |
ID = "integer", |
|
| 25 |
cohort = "integer", |
|
| 26 |
nObs = "integer" |
|
| 27 |
), |
|
| 28 |
prototype = prototype( |
|
| 29 |
ID = integer(), |
|
| 30 |
cohort = integer(), |
|
| 31 |
nObs = 0L |
|
| 32 |
), |
|
| 33 |
contains = "CrmPackClass", |
|
| 34 |
validity = v_general_data |
|
| 35 |
) |
|
| 36 | ||
| 37 |
## default constructor ---- |
|
| 38 | ||
| 39 |
#' @rdname GeneralData-class |
|
| 40 |
#' @note Typically, end users will not use the `.DefaultDataGeneral()` function. |
|
| 41 |
#' @export |
|
| 42 |
.DefaultDataGeneral <- function() {
|
|
| 43 | ! |
stop(paste0( |
| 44 | ! |
"Class DataGeneral cannot be instantiated directly. Please use one of its subclasses instead." |
| 45 |
)) |
|
| 46 |
} |
|
| 47 | ||
| 48 |
# Data ---- |
|
| 49 | ||
| 50 |
## class ---- |
|
| 51 | ||
| 52 |
#' `Data` |
|
| 53 |
#' |
|
| 54 |
#' @description `r lifecycle::badge("stable")`
|
|
| 55 |
#' |
|
| 56 |
#' [`Data`] is a class for the data input. |
|
| 57 |
#' It inherits from [`GeneralData`]. |
|
| 58 |
#' |
|
| 59 |
#' @slot x (`numeric`)\cr the doses for the patients. |
|
| 60 |
#' @slot y (`integer`)\cr the vector of toxicity events (0 or 1 integers). |
|
| 61 |
#' @slot doseGrid (`numeric`)\cr the vector of all possible doses (sorted), |
|
| 62 |
#' i.e. the dose grid. |
|
| 63 |
#' @slot nGrid (`integer`)\cr number of gridpoints. |
|
| 64 |
#' @slot xLevel (`integer`)\cr the levels for the doses the patients have been given, |
|
| 65 |
#' w.r.t `doseGrid`. |
|
| 66 |
#' @slot placebo (`logical`)\cr if `TRUE` the first dose level |
|
| 67 |
#' in the `doseGrid`is considered as PLACEBO. |
|
| 68 |
#' |
|
| 69 |
#' @aliases Data |
|
| 70 |
#' @export |
|
| 71 |
#' |
|
| 72 |
.Data <- setClass( |
|
| 73 |
Class = "Data", |
|
| 74 |
contains = "GeneralData", |
|
| 75 |
slots = c( |
|
| 76 |
x = "numeric", |
|
| 77 |
y = "integer", |
|
| 78 |
doseGrid = "numeric", |
|
| 79 |
nGrid = "integer", |
|
| 80 |
xLevel = "integer", |
|
| 81 |
placebo = "logical" |
|
| 82 |
), |
|
| 83 |
prototype = prototype( |
|
| 84 |
x = numeric(), |
|
| 85 |
y = integer(), |
|
| 86 |
doseGrid = numeric(), |
|
| 87 |
nGrid = 0L, |
|
| 88 |
xLevel = integer(), |
|
| 89 |
placebo = FALSE |
|
| 90 |
), |
|
| 91 |
validity = v_data |
|
| 92 |
) |
|
| 93 | ||
| 94 |
## constructor ---- |
|
| 95 | ||
| 96 |
#' @rdname Data-class |
|
| 97 |
#' |
|
| 98 |
#' @details The `cohort` can be missing if and only if `placebo` is equal to |
|
| 99 |
#' `FALSE`. |
|
| 100 |
#' |
|
| 101 |
#' @note `ID` and `cohort` can be missing. Then a message will be issued |
|
| 102 |
#' and the variables will be filled with default IDs and best guesses cohort, |
|
| 103 |
#' i.e. a sorted (in ascending order) sequence of values from `{1, 2, ...}`.
|
|
| 104 |
#' |
|
| 105 |
#' @param x (`numeric`)\cr the doses for the patients. |
|
| 106 |
#' @param y (`integer`)\cr the vector of toxicity events (0 or 1). |
|
| 107 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
| 108 |
#' `integer` internally. |
|
| 109 |
#' @param ID (`integer`)\cr unique patient IDs. |
|
| 110 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
| 111 |
#' `integer` internally. |
|
| 112 |
#' @param cohort (`integer`)\cr the cohort (non-negative sorted) indices. |
|
| 113 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
| 114 |
#' `integer` internally. |
|
| 115 |
#' @param doseGrid (`numeric`)\cr all possible doses. |
|
| 116 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level |
|
| 117 |
#' in the `doseGrid` is considered as placebo. |
|
| 118 |
#' @param ... not used. |
|
| 119 |
#' |
|
| 120 |
#' @export |
|
| 121 |
#' @example examples/Data-class.R |
|
| 122 |
#' |
|
| 123 |
Data <- function( |
|
| 124 |
x = numeric(), |
|
| 125 |
y = integer(), |
|
| 126 |
ID = integer(), |
|
| 127 |
cohort = integer(), |
|
| 128 |
doseGrid = numeric(), |
|
| 129 |
placebo = FALSE, |
|
| 130 |
... |
|
| 131 |
) {
|
|
| 132 | 915x |
assert_numeric(x) |
| 133 | 915x |
assert_integerish(y, lower = 0, upper = 1, any.missing = FALSE) |
| 134 | 915x |
assert_integerish(ID, unique = TRUE, any.missing = FALSE) |
| 135 | 915x |
assert_integerish(cohort) |
| 136 | 915x |
if (length(x) > 0) {
|
| 137 | 652x |
assert_numeric(doseGrid, any.missing = FALSE, unique = TRUE, min.len = 1) |
| 138 |
} else {
|
|
| 139 | 263x |
assert_numeric(doseGrid, any.missing = FALSE, unique = TRUE) |
| 140 |
} |
|
| 141 | 914x |
assert_flag(placebo) |
| 142 | ||
| 143 | 914x |
doseGrid <- sort(doseGrid) |
| 144 | 914x |
assert_subset(x, doseGrid) |
| 145 | ||
| 146 | 914x |
if (length(ID) == 0 && length(x) > 0) {
|
| 147 | 1x |
message("Used default patient IDs!")
|
| 148 | 1x |
ID <- seq_along(x) |
| 149 |
} else {
|
|
| 150 | 913x |
assert_integerish(ID, unique = TRUE) |
| 151 |
} |
|
| 152 | ||
| 153 | 914x |
if (!placebo && length(cohort) == 0 && length(x) > 0) {
|
| 154 | 1x |
message("Used best guess cohort indices!")
|
| 155 |
# This is just assuming that consecutive patients |
|
| 156 |
# in the data set are in the same cohort if they |
|
| 157 |
# have the same dose. Note that this could be wrong, |
|
| 158 |
# if two subsequent cohorts are at the same dose. |
|
| 159 | 1x |
cohort <- as.integer(c(1, 1 + cumsum(diff(x) != 0))) |
| 160 |
} else {
|
|
| 161 | 913x |
assert_integerish(cohort) |
| 162 |
} |
|
| 163 | ||
| 164 | 914x |
.Data( |
| 165 | 914x |
x = as.numeric(x), |
| 166 | 914x |
y = as.integer(y), |
| 167 | 914x |
ID = as.integer(ID), |
| 168 | 914x |
cohort = as.integer(cohort), |
| 169 | 914x |
doseGrid = as.numeric(doseGrid), |
| 170 | 914x |
nObs = length(x), |
| 171 | 914x |
nGrid = length(doseGrid), |
| 172 | 914x |
xLevel = match_within_tolerance(x, doseGrid), |
| 173 | 914x |
placebo = placebo |
| 174 |
) |
|
| 175 |
} |
|
| 176 | ||
| 177 |
## default constructor ---- |
|
| 178 | ||
| 179 |
#' @rdname Data-class |
|
| 180 |
#' @note Typically, end users will not use the `.DefaultData()` function. |
|
| 181 |
#' @export |
|
| 182 |
.DefaultData <- function() {
|
|
| 183 | 15x |
Data( |
| 184 | 15x |
doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100), |
| 185 | 15x |
ID = 1L:3L, |
| 186 | 15x |
cohort = 1L:3L, |
| 187 | 15x |
x = c(1, 3, 5), |
| 188 | 15x |
y = rep(0L, 3) |
| 189 |
) |
|
| 190 |
} |
|
| 191 | ||
| 192 |
# DataDual ---- |
|
| 193 | ||
| 194 |
## class ---- |
|
| 195 | ||
| 196 |
#' `DataDual` |
|
| 197 |
#' |
|
| 198 |
#' @description `r lifecycle::badge("stable")`
|
|
| 199 |
#' |
|
| 200 |
#' [`DataDual`] is a class for the dual endpoint data. |
|
| 201 |
#' It inherits from [`Data`] and it contains additional biomarker information. |
|
| 202 |
#' |
|
| 203 |
#' @slot w (`numeric`)\cr the continuous vector of biomarker values. |
|
| 204 |
#' |
|
| 205 |
#' @aliases DataDual |
|
| 206 |
#' @export |
|
| 207 |
#' |
|
| 208 |
.DataDual <- setClass( |
|
| 209 |
Class = "DataDual", |
|
| 210 |
slots = c(w = "numeric"), |
|
| 211 |
prototype = prototype(w = numeric()), |
|
| 212 |
contains = "Data", |
|
| 213 |
validity = v_data_dual |
|
| 214 |
) |
|
| 215 | ||
| 216 |
## constructor ---- |
|
| 217 | ||
| 218 |
#' @rdname DataDual-class |
|
| 219 |
#' |
|
| 220 |
#' @param w (`numeric`)\cr the continuous vector of biomarker values. |
|
| 221 |
#' @param ... parameters passed to [Data()]. |
|
| 222 |
#' |
|
| 223 |
#' @export |
|
| 224 |
#' @example examples/Data-class-DataDual.R |
|
| 225 |
#' |
|
| 226 |
DataDual <- function(w = numeric(), ...) {
|
|
| 227 | 191x |
d <- Data(...) |
| 228 | 191x |
.DataDual(d, w = w) |
| 229 |
} |
|
| 230 | ||
| 231 | ||
| 232 |
## default constructor ---- |
|
| 233 | ||
| 234 |
#' @rdname DataDual-class |
|
| 235 |
#' @note Typically, end users will not use the `.DefaultDataDual()` function. |
|
| 236 |
#' @export |
|
| 237 |
.DefaultDataDual <- function() {
|
|
| 238 | 6x |
set.seed(1230) |
| 239 | 6x |
DataDual( |
| 240 | 6x |
x = c(0.1, 0.5, 1.5, 3, 6, 10, 10, 10), |
| 241 | 6x |
y = c(0, 0, 0, 0, 0, 0, 1, 0), |
| 242 | 6x |
w = rnorm(8), |
| 243 | 6x |
doseGrid = c(0.1, 0.5, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)), |
| 244 | 6x |
ID = 1L:8L, |
| 245 | 6x |
cohort = as.integer(c(1, 2, 3, 4, 5, 6, 6, 6)) |
| 246 |
) |
|
| 247 |
} |
|
| 248 | ||
| 249 |
# DataParts ---- |
|
| 250 | ||
| 251 |
## class ---- |
|
| 252 | ||
| 253 |
#' `DataParts` |
|
| 254 |
#' |
|
| 255 |
#' @description `r lifecycle::badge("stable")`
|
|
| 256 |
#' |
|
| 257 |
#' [`DataParts`] is a class for the data with two study parts. |
|
| 258 |
#' It inherits from [`Data`] and it contains additional information |
|
| 259 |
#' on the two study parts. |
|
| 260 |
#' |
|
| 261 |
#' @slot part (`integer`)\cr which part does each of the patients belong to? |
|
| 262 |
#' @slot nextPart (`count`)\cr what is the part for the next cohort (1 or 2)? |
|
| 263 |
#' @slot part1Ladder (`numeric`)\cr what is the escalation ladder for |
|
| 264 |
#' part 1? This shall be an ordered subset of the `doseGrid`. |
|
| 265 |
#' |
|
| 266 |
#' @aliases DataParts |
|
| 267 |
#' @export |
|
| 268 |
#' |
|
| 269 |
.DataParts <- setClass( |
|
| 270 |
Class = "DataParts", |
|
| 271 |
slots = c( |
|
| 272 |
part = "integer", |
|
| 273 |
nextPart = "integer", |
|
| 274 |
part1Ladder = "numeric" |
|
| 275 |
), |
|
| 276 |
prototype = prototype( |
|
| 277 |
part = integer(), |
|
| 278 |
nextPart = 1L, |
|
| 279 |
part1Ladder = numeric() |
|
| 280 |
), |
|
| 281 |
contains = "Data", |
|
| 282 |
validity = v_data_parts |
|
| 283 |
) |
|
| 284 | ||
| 285 |
## constructor ---- |
|
| 286 | ||
| 287 |
#' @rdname DataParts-class |
|
| 288 |
#' |
|
| 289 |
#' @param part (`integer`)\cr which part does each of the patients belong to? |
|
| 290 |
#' @param nextPart (`count`)\cr what is the part for the next cohort (1 or 2)? |
|
| 291 |
#' @param part1Ladder (`numeric`)\cr what is the escalation ladder for part 1? |
|
| 292 |
#' This shall be an ordered subset of the `doseGrid`. |
|
| 293 |
#' @param ... parameters passed to [Data()]. |
|
| 294 |
#' |
|
| 295 |
#' @export |
|
| 296 |
#' @example examples/Data-class-DataParts.R |
|
| 297 |
#' |
|
| 298 |
DataParts <- function( |
|
| 299 |
part = integer(), |
|
| 300 |
nextPart = 1L, |
|
| 301 |
part1Ladder = numeric(), |
|
| 302 |
... |
|
| 303 |
) {
|
|
| 304 | 26x |
d <- Data(...) |
| 305 | 26x |
.DataParts( |
| 306 | 26x |
d, |
| 307 | 26x |
part = part, |
| 308 | 26x |
nextPart = nextPart, |
| 309 | 26x |
part1Ladder = part1Ladder |
| 310 |
) |
|
| 311 |
} |
|
| 312 | ||
| 313 |
## default constructor ---- |
|
| 314 | ||
| 315 |
#' @rdname DataParts-class |
|
| 316 |
#' @note Typically, end users will not use the `.DefaultDataParts()` function. |
|
| 317 |
#' @export |
|
| 318 |
.DefaultDataParts <- function() {
|
|
| 319 | 5x |
DataParts( |
| 320 | 5x |
x = c(0.1, 0.5, 1.5), |
| 321 | 5x |
y = c(0, 0, 0), |
| 322 | 5x |
ID = 1:3, |
| 323 | 5x |
cohort = 1:3, |
| 324 | 5x |
doseGrid = c(0.1, 0.5, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)), |
| 325 | 5x |
part = c(1L, 1L, 1L), |
| 326 | 5x |
nextPart = 1L, |
| 327 | 5x |
part1Ladder = c(0.1, 0.5, 1.5, 3, 6, 10) |
| 328 |
) |
|
| 329 |
} |
|
| 330 | ||
| 331 | ||
| 332 |
# DataMixture ---- |
|
| 333 | ||
| 334 |
## class ---- |
|
| 335 | ||
| 336 |
#' `DataMixture` |
|
| 337 |
#' |
|
| 338 |
#' @description `r lifecycle::badge("stable")`
|
|
| 339 |
#' |
|
| 340 |
#' [`DataMixture`] is a class for the data with mixture sharing. |
|
| 341 |
#' It inherits from [`Data`] and it contains additional information |
|
| 342 |
#' on the mixture sharing. |
|
| 343 |
#' |
|
| 344 |
#' @slot xshare (`numeric`)\cr the doses for the share patients. |
|
| 345 |
#' @slot yshare (`integer`)\cr the vector of toxicity events (0 or 1) |
|
| 346 |
#' for the share patients. |
|
| 347 |
#' @slot nObsshare (`count`)\cr number of share patients. |
|
| 348 |
#' |
|
| 349 |
#' @aliases DataMixture |
|
| 350 |
#' @export |
|
| 351 |
#' |
|
| 352 |
.DataMixture <- setClass( |
|
| 353 |
Class = "DataMixture", |
|
| 354 |
slots = c( |
|
| 355 |
xshare = "numeric", |
|
| 356 |
yshare = "integer", |
|
| 357 |
nObsshare = "integer" |
|
| 358 |
), |
|
| 359 |
prototype = prototype( |
|
| 360 |
xshare = numeric(), |
|
| 361 |
yshare = integer(), |
|
| 362 |
nObsshare = 0L |
|
| 363 |
), |
|
| 364 |
contains = "Data", |
|
| 365 |
validity = v_data_mixture |
|
| 366 |
) |
|
| 367 | ||
| 368 |
## constructor ---- |
|
| 369 | ||
| 370 |
#' @rdname DataMixture-class |
|
| 371 |
#' |
|
| 372 |
#' @param xshare (`numeric`)\cr the doses for the share patients. |
|
| 373 |
#' @param yshare (`integer`)\cr the vector of toxicity events (0 or 1) |
|
| 374 |
#' for the share patients. You can also supply `numeric` vectors, |
|
| 375 |
#' but these will then be converted to `integer` internally. |
|
| 376 |
#' @param ... parameters passed to [Data()]. |
|
| 377 |
#' |
|
| 378 |
#' @export |
|
| 379 |
#' @example examples/Data-class-DataMixture.R |
|
| 380 |
#' |
|
| 381 |
DataMixture <- function(xshare = numeric(), yshare = integer(), ...) {
|
|
| 382 | 8x |
d <- Data(...) |
| 383 | 8x |
assert_integerish(yshare) |
| 384 | 8x |
assert_numeric(xshare) |
| 385 | 8x |
.DataMixture( |
| 386 | 8x |
d, |
| 387 | 8x |
xshare = as.numeric(xshare), |
| 388 | 8x |
yshare = as.integer(yshare), |
| 389 | 8x |
nObsshare = length(xshare) |
| 390 |
) |
|
| 391 |
} |
|
| 392 | ||
| 393 |
## default constructor ---- |
|
| 394 | ||
| 395 |
#' @rdname DataMixture-class |
|
| 396 |
#' @note Typically, end users will not use the `.DefaultDataMixture()` function. |
|
| 397 |
#' @export |
|
| 398 |
.DefaultDataMixture <- function() {
|
|
| 399 | 6x |
DataMixture( |
| 400 | 6x |
xshare = c(12, 14, 16, 18.0), |
| 401 | 6x |
yshare = c(0L, 1L, 1L, 1L), |
| 402 | 6x |
nObsshare = 4L, |
| 403 | 6x |
x = c(0.1, 0.5, 1.5), |
| 404 | 6x |
y = c(0, 0, 0), |
| 405 | 6x |
ID = 1L:3L, |
| 406 | 6x |
cohort = 1L:3L, |
| 407 | 6x |
doseGrid = c(0.1, 0.5, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)) |
| 408 |
) |
|
| 409 |
} |
|
| 410 | ||
| 411 | ||
| 412 |
# DataDA ---- |
|
| 413 | ||
| 414 |
## class ---- |
|
| 415 | ||
| 416 |
#' `DataDA` |
|
| 417 |
#' |
|
| 418 |
#' @description `r lifecycle::badge("stable")`
|
|
| 419 |
#' |
|
| 420 |
#' [`DataDA`] is a class for the time-to-DLT augmented data. |
|
| 421 |
#' It inherits from [`Data`] and it contains additional DLT free survival times. |
|
| 422 |
#' |
|
| 423 |
#' @note `survival time` here refers to the time period for which the subject |
|
| 424 |
#' did not experience any DLT, and is not referring to deaths. |
|
| 425 |
#' |
|
| 426 |
#' @slot u (`numeric`)\cr the continuous vector of DLT free survival times. |
|
| 427 |
#' @slot t0 (`numeric`)\cr time of initial dosing for each patient. |
|
| 428 |
#' Non-negative values sorted in ascending order. |
|
| 429 |
#' @slot Tmax (`number`)\cr the DLT observation period. |
|
| 430 |
#' |
|
| 431 |
#' @aliases DataDA |
|
| 432 |
#' @export |
|
| 433 |
#' |
|
| 434 |
.DataDA <- setClass( |
|
| 435 |
Class = "DataDA", |
|
| 436 |
slots = c( |
|
| 437 |
u = "numeric", |
|
| 438 |
t0 = "numeric", |
|
| 439 |
Tmax = "numeric" |
|
| 440 |
), |
|
| 441 |
prototype = prototype( |
|
| 442 |
u = numeric(), |
|
| 443 |
t0 = numeric(), |
|
| 444 |
Tmax = 0 + .Machine$double.xmin |
|
| 445 |
), |
|
| 446 |
contains = "Data", |
|
| 447 |
validity = v_data_da |
|
| 448 |
) |
|
| 449 | ||
| 450 |
## constructor ---- |
|
| 451 | ||
| 452 |
#' @rdname DataDA-class |
|
| 453 |
#' |
|
| 454 |
#' @param u (`numeric`)\cr the continuous vector of DLT free survival times. |
|
| 455 |
#' @param t0 (`numeric`)\cr time of initial dosing for each patient. |
|
| 456 |
#' Non-negative values sorted in ascending order. |
|
| 457 |
#' Default to vector of 0s of length equal to length of `u`. |
|
| 458 |
#' @param Tmax (`number`)\cr the DLT observation period. |
|
| 459 |
#' @param ... parameters passed to [Data()]. |
|
| 460 |
#' |
|
| 461 |
#' @export |
|
| 462 |
#' @example examples/Data-class-DataDA.R |
|
| 463 |
#' |
|
| 464 |
DataDA <- function( |
|
| 465 |
u = numeric(), |
|
| 466 |
t0 = numeric(length(u)), |
|
| 467 |
Tmax = 0 + .Machine$double.xmin, |
|
| 468 |
... |
|
| 469 |
) {
|
|
| 470 | 28x |
d <- Data(...) |
| 471 | 28x |
.DataDA( |
| 472 | 28x |
d, |
| 473 | 28x |
u = as.numeric(u), |
| 474 | 28x |
t0 = as.numeric(t0), |
| 475 | 28x |
Tmax = as.numeric(Tmax) |
| 476 |
) |
|
| 477 |
} |
|
| 478 | ||
| 479 |
## default constructor ---- |
|
| 480 | ||
| 481 |
#' @rdname DataDA-class |
|
| 482 |
#' @note Typically, end users will not use the `.DefaultDataDA()` function. |
|
| 483 |
#' @export |
|
| 484 |
.DefaultDataDA <- function() {
|
|
| 485 | 6x |
DataDA( |
| 486 | 6x |
u = c(42, 30, 15, 5, 20, 25, 30, 60), |
| 487 | 6x |
t0 = c(0, 15, 30, 40, 55, 70, 75, 85), |
| 488 | 6x |
Tmax = 60, |
| 489 | 6x |
x = c(0.1, 0.5, 1.5, 3, 6, 10, 10, 10), |
| 490 | 6x |
y = c(0, 0, 1, 1, 0, 0, 1, 0), |
| 491 | 6x |
doseGrid = c(0.1, 0.5, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)), |
| 492 | 6x |
ID = 1L:8L, |
| 493 | 6x |
cohort = as.integer(c(1, 2, 3, 4, 5, 6, 6, 6)) |
| 494 |
) |
|
| 495 |
} |
|
| 496 | ||
| 497 |
# DataOrdinal ---- |
|
| 498 | ||
| 499 |
## class ---- |
|
| 500 | ||
| 501 |
#' `DataOrdinal` |
|
| 502 |
#' |
|
| 503 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 504 |
#' |
|
| 505 |
#' [`DataOrdinal`] is a class for ordinal toxicity data. |
|
| 506 |
#' It inherits from [`GeneralData`] and it describes toxicity responses on an |
|
| 507 |
#' ordinal rather than binary scale. |
|
| 508 |
#' |
|
| 509 |
#' @note This class has been implemented as a sibling of the existing `Data` class |
|
| 510 |
#' (rather than as a parent or child) to minimise the risk of unintended side |
|
| 511 |
#' effects on existing classes and methods. |
|
| 512 |
#' |
|
| 513 |
#' The default setting for the `yCategories` slot replicates the behaviour |
|
| 514 |
#' of the existing `Data` class. |
|
| 515 |
#' |
|
| 516 |
#' @aliases DataOrdinal |
|
| 517 |
#' @export |
|
| 518 |
.DataOrdinal <- setClass( |
|
| 519 |
Class = "DataOrdinal", |
|
| 520 |
contains = "GeneralData", |
|
| 521 |
slots = c( |
|
| 522 |
x = "numeric", |
|
| 523 |
y = "integer", |
|
| 524 |
doseGrid = "numeric", |
|
| 525 |
nGrid = "integer", |
|
| 526 |
xLevel = "integer", |
|
| 527 |
yCategories = "integer", |
|
| 528 |
placebo = "logical" |
|
| 529 |
), |
|
| 530 |
prototype = prototype( |
|
| 531 |
x = numeric(), |
|
| 532 |
y = integer(), |
|
| 533 |
doseGrid = numeric(), |
|
| 534 |
nGrid = 0L, |
|
| 535 |
xLevel = integer(), |
|
| 536 |
yCategories = c("No DLT" = 0L, "DLT" = 1L),
|
|
| 537 |
placebo = FALSE |
|
| 538 |
), |
|
| 539 |
validity = v_data_ordinal |
|
| 540 |
) |
|
| 541 | ||
| 542 |
## constructor ---- |
|
| 543 | ||
| 544 |
#' @rdname DataOrdinal-class |
|
| 545 |
#' @param yCategories (named `integer`)\cr the names and codes for the |
|
| 546 |
#' toxicity categories used in the data. Category labels are taken from the |
|
| 547 |
#' names of the vector. The names of the vector must be unique and its values |
|
| 548 |
#' must be sorted and take the values 0, 1, 2, ... |
|
| 549 |
#' @inheritParams Data |
|
| 550 |
#' @inherit Data details note params |
|
| 551 |
#' @example examples/Data-class-DataOrdinal.R |
|
| 552 |
#' @export |
|
| 553 |
DataOrdinal <- function( |
|
| 554 |
x = numeric(), |
|
| 555 |
y = integer(), |
|
| 556 |
ID = integer(), |
|
| 557 |
cohort = integer(), |
|
| 558 |
doseGrid = numeric(), |
|
| 559 |
placebo = FALSE, |
|
| 560 |
yCategories = c("No DLT" = 0L, "DLT" = 1L),
|
|
| 561 |
... |
|
| 562 |
) {
|
|
| 563 | 72x |
assert_numeric(doseGrid, any.missing = FALSE, unique = TRUE) |
| 564 | 72x |
assert_integerish( |
| 565 | 72x |
yCategories, |
| 566 | 72x |
any.missing = FALSE, |
| 567 | 72x |
unique = TRUE, |
| 568 | 72x |
names = "unique", |
| 569 | 72x |
min.len = 2 |
| 570 |
) |
|
| 571 | 72x |
assert_flag(placebo) |
| 572 | ||
| 573 | 72x |
doseGrid <- as.numeric(sort(doseGrid)) |
| 574 | ||
| 575 | 72x |
if (length(ID) == 0 && length(x) > 0) {
|
| 576 | ! |
message("Used default patient IDs!")
|
| 577 | ! |
ID <- seq_along(x) |
| 578 |
} else {
|
|
| 579 | 72x |
assert_integerish(ID, unique = TRUE) |
| 580 |
} |
|
| 581 | ||
| 582 | 72x |
if (!placebo && length(cohort) == 0 && length(x) > 0) {
|
| 583 | ! |
message("Used best guess cohort indices!")
|
| 584 |
# This is just assuming that consecutive patients |
|
| 585 |
# in the data set are in the same cohort if they |
|
| 586 |
# have the same dose. Note that this could be wrong, |
|
| 587 |
# if two subsequent cohorts are at the same dose. |
|
| 588 | ! |
cohort <- as.integer(c(1, 1 + cumsum(diff(x) != 0))) |
| 589 |
} else {
|
|
| 590 | 72x |
assert_integerish(cohort) |
| 591 |
} |
|
| 592 | ||
| 593 | 72x |
.DataOrdinal( |
| 594 | 72x |
x = as.numeric(x), |
| 595 | 72x |
y = as.integer(y), |
| 596 | 72x |
ID = as.integer(ID), |
| 597 | 72x |
cohort = as.integer(cohort), |
| 598 | 72x |
doseGrid = doseGrid, |
| 599 | 72x |
nObs = length(x), |
| 600 | 72x |
nGrid = length(doseGrid), |
| 601 | 72x |
xLevel = match_within_tolerance(x = x, table = doseGrid), |
| 602 | 72x |
placebo = placebo, |
| 603 | 72x |
yCategories = yCategories |
| 604 |
) |
|
| 605 |
} |
|
| 606 | ||
| 607 | ||
| 608 |
## default constructor ---- |
|
| 609 | ||
| 610 |
#' @rdname DataOrdinal-class |
|
| 611 |
#' @note Typically, end users will not use the `.DefaultDataOrdinal()` function. |
|
| 612 |
#' @export |
|
| 613 |
.DefaultDataOrdinal <- function() {
|
|
| 614 | 22x |
DataOrdinal( |
| 615 | 22x |
x = c(10, 20, 30, 40, 50, 50, 50, 60, 60, 60), |
| 616 | 22x |
y = as.integer(c(0, 0, 0, 0, 0, 1, 0, 0, 1, 2)), |
| 617 | 22x |
ID = 1L:10L, |
| 618 | 22x |
cohort = as.integer(c(1:4, 5, 5, 5, 6, 6, 6)), |
| 619 | 22x |
doseGrid = c(seq(from = 10, to = 100, by = 10)), |
| 620 | 22x |
yCategories = c("No tox" = 0L, "Sub-tox AE" = 1L, "DLT" = 2L),
|
| 621 | 22x |
placebo = FALSE |
| 622 |
) |
|
| 623 |
} |
|
| 624 | ||
| 625 |
# DataGrouped ---- |
|
| 626 | ||
| 627 |
## class ---- |
|
| 628 | ||
| 629 |
#' `DataGrouped` |
|
| 630 |
#' |
|
| 631 |
#' @description `r lifecycle::badge("stable")`
|
|
| 632 |
#' |
|
| 633 |
#' [`DataGrouped`] is a class for a two groups dose escalation data set, |
|
| 634 |
#' comprised of a monotherapy (`mono`) and a combination therapy (`combo`) |
|
| 635 |
#' arm. It inherits from [`Data`] and it contains the additional group information. |
|
| 636 |
#' |
|
| 637 |
#' @slot group (`factor`)\cr whether `mono` or `combo` was used. |
|
| 638 |
#' |
|
| 639 |
#' @aliases DataGrouped |
|
| 640 |
#' @export |
|
| 641 |
.DataGrouped <- setClass( |
|
| 642 |
Class = "DataGrouped", |
|
| 643 |
slots = c( |
|
| 644 |
group = "factor" |
|
| 645 |
), |
|
| 646 |
prototype = prototype( |
|
| 647 |
group = factor(levels = c("mono", "combo"))
|
|
| 648 |
), |
|
| 649 |
contains = "Data", |
|
| 650 |
validity = v_data_grouped |
|
| 651 |
) |
|
| 652 | ||
| 653 |
#' @rdname DataGrouped-class |
|
| 654 |
#' |
|
| 655 |
#' @param group (`factor` or `character`)\cr whether `mono` or `combo` was used. |
|
| 656 |
#' If `character` then will be coerced to `factor` with the correct levels |
|
| 657 |
#' internally. |
|
| 658 |
#' @param ... parameters passed to [Data()]. |
|
| 659 |
#' |
|
| 660 |
#' @export |
|
| 661 |
#' @example examples/Data-class-DataGrouped.R |
|
| 662 |
#' |
|
| 663 |
DataGrouped <- function(group = character(), ...) {
|
|
| 664 | 81x |
d <- Data(...) |
| 665 | 81x |
if (!is.factor(group)) {
|
| 666 | 81x |
assert_character(group) |
| 667 | 81x |
assert_subset(group, choices = c("mono", "combo"))
|
| 668 | 81x |
group <- factor(group, levels = c("mono", "combo"))
|
| 669 |
} |
|
| 670 | 81x |
.DataGrouped( |
| 671 | 81x |
d, |
| 672 | 81x |
group = group |
| 673 |
) |
|
| 674 |
} |
|
| 675 | ||
| 676 |
## default constructor ---- |
|
| 677 | ||
| 678 |
#' @rdname DataGrouped-class |
|
| 679 |
#' @note Typically, end users will not use the `.DefaultDataGrouped()` function. |
|
| 680 |
#' @export |
|
| 681 |
.DefaultDataGrouped <- function() {
|
|
| 682 | 7x |
DataGrouped( |
| 683 | 7x |
group = c("mono", "mono", "combo"),
|
| 684 | 7x |
x = c(1, 3, 5), |
| 685 | 7x |
y = c(0, 0, 0), |
| 686 | 7x |
ID = 1L:3L, |
| 687 | 7x |
cohort = 1L:3L, |
| 688 | 7x |
doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100), |
| 689 | 7x |
placebo = FALSE |
| 690 |
) |
|
| 691 |
} |
| 1 |
#' Internal Helper Functions for Validation of [`GeneralData`] Objects |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("stable")`
|
|
| 4 |
#' |
|
| 5 |
#' These functions are only used internally to validate the format of an input |
|
| 6 |
#' [`GeneralData`] or inherited classes and therefore not exported. |
|
| 7 |
#' |
|
| 8 |
#' @name v_data_objects |
|
| 9 |
#' @param object (`GeneralData`)\cr object to validate. |
|
| 10 |
#' @return A `character` vector with the validation failure messages, |
|
| 11 |
#' or `TRUE` in case validation passes. |
|
| 12 |
NULL |
|
| 13 | ||
| 14 |
#' @describeIn v_data_objects validates that the [`GeneralData`] |
|
| 15 |
#' object contains unique `ID`, non-negative `cohort` indices and |
|
| 16 |
#' `ID` and `cohort` vectors are of the same length `nObs`. |
|
| 17 |
v_general_data <- function(object) {
|
|
| 18 | 2x |
v <- Validate() |
| 19 |
# In if clause so that below test_* won't fail. |
|
| 20 | 2x |
if (!test_int(object@nObs)) {
|
| 21 | ! |
return("nObs must be of type integer of length 1")
|
| 22 |
} |
|
| 23 | 2x |
v$check( |
| 24 | 2x |
test_integer( |
| 25 | 2x |
object@ID, |
| 26 | 2x |
len = object@nObs, |
| 27 | 2x |
any.missing = FALSE, |
| 28 | 2x |
unique = TRUE, |
| 29 | 2x |
null.ok = TRUE |
| 30 |
), |
|
| 31 | 2x |
"ID must be of type integer and length nObs and unique" |
| 32 |
) |
|
| 33 | 2x |
v$check( |
| 34 | 2x |
test_integer( |
| 35 | 2x |
object@cohort, |
| 36 | 2x |
lower = 0L, |
| 37 | 2x |
len = object@nObs, |
| 38 | 2x |
any.missing = FALSE, |
| 39 | 2x |
sorted = TRUE |
| 40 |
), |
|
| 41 | 2x |
"cohort must be of type integer and length nObs and contain non-negative, sorted values" |
| 42 |
) |
|
| 43 | 2x |
v$result() |
| 44 |
} |
|
| 45 | ||
| 46 |
#' @describeIn v_data_objects helper function which verifies whether |
|
| 47 |
#' the `dose` values are unique in each and every different `cohort`. |
|
| 48 |
#' @param dose (`numeric`)\cr dose values. |
|
| 49 |
#' @param cohort (`integer`)\cr cohort indices parallel to `doses`. |
|
| 50 |
#' @return `TRUE` if `dose` is unique per `cohort`, otherwise `FALSE`. |
|
| 51 |
h_doses_unique_per_cohort <- function(dose, cohort) {
|
|
| 52 | 1600x |
assert_numeric(dose) |
| 53 | 1600x |
assert_integer(cohort) |
| 54 | ||
| 55 | 1600x |
num_doses_per_cohort <- tapply( |
| 56 | 1600x |
X = dose, |
| 57 | 1600x |
INDEX = cohort, |
| 58 | 1600x |
FUN = function(d) length(unique(d)) |
| 59 |
) |
|
| 60 | 1600x |
all(num_doses_per_cohort == 1L) |
| 61 |
} |
|
| 62 | ||
| 63 |
#' Helper Function performing validation Common to Data and DataOrdinal |
|
| 64 |
#' |
|
| 65 |
#' @rdname h_validate_common_data_slots |
|
| 66 |
#' @param object (`Data` or `DataOrdinal`)\cr the object to be validated |
|
| 67 |
#' @returns a `Validate` object containing the result of the validation |
|
| 68 |
h_validate_common_data_slots <- function(object) {
|
|
| 69 | 2015x |
v <- Validate() |
| 70 | 2015x |
v$check( |
| 71 | 2015x |
test_double(object@x, len = object@nObs, any.missing = FALSE), |
| 72 | 2015x |
"Doses vector x must be of type double and length nObs" |
| 73 |
) |
|
| 74 | 2015x |
v$check( |
| 75 | 2015x |
test_double( |
| 76 | 2015x |
object@doseGrid, |
| 77 | 2015x |
len = object@nGrid, |
| 78 | 2015x |
any.missing = FALSE, |
| 79 | 2015x |
unique = TRUE, |
| 80 | 2015x |
sorted = TRUE |
| 81 |
), |
|
| 82 | 2015x |
"doseGrid must be of type double and length nGrid and contain unique, sorted values" |
| 83 |
) |
|
| 84 | 2015x |
v$check( |
| 85 | 2015x |
test_int(object@nGrid), |
| 86 | 2015x |
"Number of dose grid values nGrid must be scalar integer" |
| 87 |
) |
|
| 88 | 2015x |
v$check( |
| 89 | 2015x |
test_integer(object@xLevel, len = object@nObs, any.missing = FALSE), |
| 90 | 2015x |
"Levels xLevel for the doses the patients have been given must be of type integer and length nObs" |
| 91 |
) |
|
| 92 | 2015x |
v$check( |
| 93 | 2015x |
test_flag(object@placebo), |
| 94 | 2015x |
"The placebo flag must be scalar logical" |
| 95 |
) |
|
| 96 | 2015x |
v$check( |
| 97 | 2015x |
test_subset(object@x, object@doseGrid), |
| 98 | 2015x |
"Dose values in x must be from doseGrid" |
| 99 |
) |
|
| 100 | 2015x |
v$check( |
| 101 | 2015x |
h_all_equivalent(object@x, object@doseGrid[object@xLevel]), |
| 102 | 2015x |
"x must be equivalent to doseGrid[xLevel] (up to numerical tolerance)" |
| 103 |
) |
|
| 104 | 2015x |
if (!object@placebo) {
|
| 105 | 1598x |
v$check( |
| 106 | 1598x |
h_doses_unique_per_cohort(dose = object@x, cohort = object@cohort), |
| 107 | 1598x |
"There must be only one dose level per cohort" |
| 108 |
) |
|
| 109 |
} |
|
| 110 | 2015x |
v |
| 111 |
} |
|
| 112 | ||
| 113 |
#' @describeIn v_data_objects validates that the [`Data`] object contains |
|
| 114 |
#' valid elements with respect to their types, dependency and length. |
|
| 115 |
v_data <- function(object) {
|
|
| 116 | 3x |
v <- h_validate_common_data_slots(object) |
| 117 | 3x |
v$check( |
| 118 | 3x |
test_integer( |
| 119 | 3x |
object@y, |
| 120 | 3x |
lower = 0, |
| 121 | 3x |
upper = 1, |
| 122 | 3x |
len = object@nObs, |
| 123 | 3x |
any.missing = FALSE |
| 124 |
), |
|
| 125 | 3x |
"DLT vector y must be nObs long and contain 0 or 1 integers only" |
| 126 |
) |
|
| 127 | ||
| 128 | 3x |
v$result() |
| 129 |
} |
|
| 130 | ||
| 131 |
#' @describeIn v_data_objects validates that the [`DataDual`] object |
|
| 132 |
#' contains valid biomarker vector with respect to its type and the length. |
|
| 133 |
v_data_dual <- function(object) {
|
|
| 134 | 2x |
v <- Validate() |
| 135 | 2x |
v$check( |
| 136 | 2x |
test_double(object@w, len = object@nObs, any.missing = FALSE), |
| 137 | 2x |
"Biomarker vector w must be of type double and length nObs" |
| 138 |
) |
|
| 139 | 2x |
v$result() |
| 140 |
} |
|
| 141 | ||
| 142 |
#' @describeIn v_data_objects validates that the [`DataParts`] object |
|
| 143 |
#' contains valid elements with respect to their types, dependency and length. |
|
| 144 |
v_data_parts <- function(object) {
|
|
| 145 | 5x |
v <- Validate() |
| 146 | 5x |
v$check( |
| 147 | 5x |
test_integer( |
| 148 | 5x |
object@part, |
| 149 | 5x |
lower = 1, |
| 150 | 5x |
upper = 2, |
| 151 | 5x |
len = object@nObs, |
| 152 | 5x |
any.missing = FALSE |
| 153 |
), |
|
| 154 | 5x |
"vector part must be nObs long and contain 1 or 2 integers only" |
| 155 |
) |
|
| 156 | 5x |
v$check( |
| 157 | 5x |
test_int(object@nextPart, lower = 1, upper = 2), |
| 158 | 5x |
"nextPart must be integer scalar 1 or 2" |
| 159 |
) |
|
| 160 | 5x |
v$check( |
| 161 | 5x |
test_numeric( |
| 162 | 5x |
object@part1Ladder, |
| 163 | 5x |
any.missing = FALSE, |
| 164 | 5x |
sorted = TRUE, |
| 165 | 5x |
unique = TRUE |
| 166 |
), |
|
| 167 | 5x |
"part1Ladder must be of type double and contain unique, sorted values" |
| 168 |
) |
|
| 169 | 5x |
v$check( |
| 170 | 5x |
test_subset(object@part1Ladder, object@doseGrid), |
| 171 | 5x |
"part1Ladder must have all entries from doseGrid" |
| 172 |
) |
|
| 173 | 5x |
v$result() |
| 174 |
} |
|
| 175 | ||
| 176 |
#' @describeIn v_data_objects validates that the [`DataMixture`] object |
|
| 177 |
#' contains valid elements with respect to their types, dependency and length. |
|
| 178 |
v_data_mixture <- function(object) {
|
|
| 179 | 5x |
v <- Validate() |
| 180 | ||
| 181 |
# In if clause so that below test_* won't fail. |
|
| 182 | 5x |
if (!test_int(object@nObsshare)) {
|
| 183 | 1x |
return("nObsshare must be of type integer of length 1")
|
| 184 |
} |
|
| 185 | 4x |
v$check( |
| 186 | 4x |
test_numeric(object@xshare, len = object@nObsshare, any.missing = FALSE), |
| 187 | 4x |
"Dose vector xshare must be of type double and length nObsshare" |
| 188 |
) |
|
| 189 | 4x |
v$check( |
| 190 | 4x |
test_integer( |
| 191 | 4x |
object@yshare, |
| 192 | 4x |
lower = 0, |
| 193 | 4x |
upper = 1, |
| 194 | 4x |
len = object@nObsshare, |
| 195 | 4x |
any.missing = FALSE |
| 196 |
), |
|
| 197 | 4x |
"DLT vector yshare must be nObsshare long and contain 0 or 1 integers only" |
| 198 |
) |
|
| 199 | 4x |
v$check( |
| 200 | 4x |
test_subset(object@xshare, object@doseGrid), |
| 201 | 4x |
"Dose values in xshare must be from doseGrid" |
| 202 |
) |
|
| 203 | 4x |
v$result() |
| 204 |
} |
|
| 205 | ||
| 206 |
#' @describeIn v_data_objects validates that the [`DataDA`] object |
|
| 207 |
#' contains valid elements with respect to their types, dependency and length. |
|
| 208 |
v_data_da <- function(object) {
|
|
| 209 | 4x |
v <- Validate() |
| 210 |
# In if clause so that below test_* won't fail. |
|
| 211 | 4x |
if (!(test_number(object@Tmax) && object@Tmax > 0)) {
|
| 212 | 1x |
return( |
| 213 | 1x |
"DLT window Tmax must be of type double of length 1 and greater than 0" |
| 214 |
) |
|
| 215 |
} |
|
| 216 | 3x |
v$check( |
| 217 | 3x |
test_numeric( |
| 218 | 3x |
object@u, |
| 219 | 3x |
upper = object@Tmax, |
| 220 | 3x |
len = object@nObs, |
| 221 | 3x |
any.missing = FALSE |
| 222 |
) && |
|
| 223 | 3x |
all(object@u >= 0), |
| 224 | 3x |
"u must be of type double, nObs length, non-negative, not missing and not greater than Tmax" |
| 225 |
) |
|
| 226 | 3x |
v$check( |
| 227 | 3x |
test_numeric( |
| 228 | 3x |
object@t0, |
| 229 | 3x |
lower = 0, |
| 230 | 3x |
len = object@nObs, |
| 231 | 3x |
any.missing = FALSE, |
| 232 | 3x |
sorted = TRUE |
| 233 |
), |
|
| 234 | 3x |
"t0 must be of type double, nObs length, sorted non-negative" |
| 235 |
) |
|
| 236 | 3x |
v$result() |
| 237 |
} |
|
| 238 | ||
| 239 |
#' @describeIn v_data_objects validates that the [`DataOrdinal`] object |
|
| 240 |
#' contains valid elements with respect to their types, dependency and length. |
|
| 241 |
v_data_ordinal <- function(object) {
|
|
| 242 | 8x |
v <- h_validate_common_data_slots(object) |
| 243 | 8x |
v$check( |
| 244 | 8x |
test_integer( |
| 245 | 8x |
object@y, |
| 246 | 8x |
lower = 0, |
| 247 | 8x |
upper = length(object@yCategories) - 1, |
| 248 | 8x |
len = object@nObs, |
| 249 | 8x |
any.missing = FALSE |
| 250 |
), |
|
| 251 | 8x |
"DLT vector y must be nObs long and contain integers between 0 and k-1 only, where k is the length of the vector in the yCategories slot" # nolint |
| 252 |
) |
|
| 253 | 8x |
v$check( |
| 254 | 8x |
length(unique(names(object@yCategories))) == |
| 255 | 8x |
length(names(object@yCategories)), |
| 256 | 8x |
"yCategory labels must be unique" |
| 257 |
) |
|
| 258 | 8x |
v$result() |
| 259 |
} |
|
| 260 | ||
| 261 |
#' @describeIn v_data_objects validates that the [`DataGrouped`] object |
|
| 262 |
#' contains valid group information. |
|
| 263 |
v_data_grouped <- function(object) {
|
|
| 264 | 3x |
v <- Validate() |
| 265 | 3x |
v$check( |
| 266 | 3x |
test_factor( |
| 267 | 3x |
object@group, |
| 268 | 3x |
levels = c("mono", "combo"),
|
| 269 | 3x |
len = object@nObs, |
| 270 | 3x |
any.missing = FALSE |
| 271 |
), |
|
| 272 | 3x |
"group must be factor with levels mono and combo of length nObs without missings" |
| 273 |
) |
|
| 274 | 3x |
v$result() |
| 275 |
} |
| 1 |
# StoppingOrdinal ---- |
|
| 2 | ||
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 5 |
#' @rdname knit_print |
|
| 6 |
#' @export |
|
| 7 |
#' @method knit_print StoppingOrdinal |
|
| 8 |
knit_print.StoppingOrdinal <- function( |
|
| 9 |
x, |
|
| 10 |
..., |
|
| 11 |
asis = TRUE |
|
| 12 |
) {
|
|
| 13 | 12x |
assert_flag(asis) |
| 14 | ||
| 15 | 10x |
rv <- paste0( |
| 16 | 10x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 17 | 10x |
"Based on a toxicity grade of ", |
| 18 | 10x |
x@grade, |
| 19 |
": ", |
|
| 20 | 10x |
paste0(knit_print(x@rule, asis = asis, ...), collapse = "\n") |
| 21 |
) |
|
| 22 | ||
| 23 | 10x |
if (asis) {
|
| 24 | 2x |
rv <- knitr::asis_output(rv) |
| 25 |
} |
|
| 26 | 10x |
rv |
| 27 |
} |
|
| 28 | ||
| 29 |
# StoppingMaxGainCIRatio ---- |
|
| 30 | ||
| 31 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 32 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 33 |
#' @rdname knit_print |
|
| 34 |
#' @export |
|
| 35 |
#' @method knit_print StoppingMaxGainCIRatio |
|
| 36 |
knit_print.StoppingMaxGainCIRatio <- function( |
|
| 37 |
x, |
|
| 38 |
..., |
|
| 39 |
asis = TRUE |
|
| 40 |
) {
|
|
| 41 | 8x |
assert_flag(asis) |
| 42 | ||
| 43 | 6x |
rv <- paste0( |
| 44 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 45 | 6x |
"If the ratio of the upper to the lower limit of the posterior 95% credible ", |
| 46 | 6x |
"interval for the probability of toxicity at the target dose (the smaller ", |
| 47 | 6x |
"of the MTD for ", |
| 48 | 6x |
100 * x@prob_target, |
| 49 | 6x |
"% target and GStar) is less than or equal to ", |
| 50 | 6x |
x@target_ratio, |
| 51 | 6x |
".\n\n" |
| 52 |
) |
|
| 53 | ||
| 54 | 6x |
if (asis) {
|
| 55 | 2x |
rv <- knitr::asis_output(rv) |
| 56 |
} |
|
| 57 | 6x |
rv |
| 58 |
} |
|
| 59 | ||
| 60 |
# StoppingList ---- |
|
| 61 | ||
| 62 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 63 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 64 |
#' @param preamble (`character`)\cr the text that introduces the list of rules |
|
| 65 |
#' @param indent (`integer`)\cr the indent level of the current stopping rule |
|
| 66 |
#' list. Spaces with length `indent * 4` will be prepended to the beginning of |
|
| 67 |
#' the rendered stopping rule list. |
|
| 68 |
#' @rdname knit_print |
|
| 69 |
#' @export |
|
| 70 |
#' @method knit_print StoppingList |
|
| 71 |
knit_print.StoppingList <- function( |
|
| 72 |
x, |
|
| 73 |
..., |
|
| 74 |
preamble, |
|
| 75 |
indent = 0L, |
|
| 76 |
asis = TRUE |
|
| 77 |
) {
|
|
| 78 | 63x |
assert_flag(asis) |
| 79 | 57x |
assert_integer(indent, lower = 0) |
| 80 | ||
| 81 | 57x |
if (missing(preamble)) {
|
| 82 | 9x |
case_string <- switch( |
| 83 | 9x |
as.character(length(x@stop_list)), |
| 84 | 9x |
`1` = "rule ", |
| 85 | 9x |
"rules " |
| 86 |
) |
|
| 87 | 9x |
preamble <- paste0( |
| 88 | 9x |
"If the result of applying the summary function to the following ", |
| 89 | 9x |
case_string, |
| 90 | 9x |
"is `TRUE`:\n" |
| 91 |
) |
|
| 92 |
} else {
|
|
| 93 | 48x |
assert_character(preamble, len = 1, any.missing = FALSE) |
| 94 |
} |
|
| 95 | ||
| 96 | 57x |
rules_list <- paste0( |
| 97 | 57x |
lapply( |
| 98 | 57x |
x@stop_list, |
| 99 | 57x |
function(z, indent) {
|
| 100 | 131x |
paste0( |
| 101 | 131x |
strrep(" ", indent * 4),
|
| 102 |
"- ", |
|
| 103 | 131x |
knit_print(z, asis = FALSE, indent = indent + 1L, ...) |
| 104 |
) |
|
| 105 |
}, |
|
| 106 | 57x |
indent = indent |
| 107 |
), |
|
| 108 | 57x |
collapse = "\n" |
| 109 |
) |
|
| 110 | 57x |
rv <- paste0( |
| 111 | 57x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 112 | 57x |
preamble, |
| 113 | 57x |
"\n", |
| 114 | 57x |
rules_list, |
| 115 | 57x |
"\n\n" |
| 116 |
) |
|
| 117 | ||
| 118 | 57x |
if (asis) {
|
| 119 | 6x |
rv <- knitr::asis_output(rv) |
| 120 |
} |
|
| 121 | 57x |
rv |
| 122 |
} |
|
| 123 | ||
| 124 |
# StoppingAny ---- |
|
| 125 | ||
| 126 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 127 |
#' @inheritParams knit_print.StoppingList |
|
| 128 |
#' @rdname knit_print |
|
| 129 |
#' @export |
|
| 130 |
#' @method knit_print StoppingAny |
|
| 131 |
knit_print.StoppingAny <- function( |
|
| 132 |
x, |
|
| 133 |
..., |
|
| 134 |
preamble, |
|
| 135 |
asis = TRUE |
|
| 136 |
) {
|
|
| 137 | 32x |
if (missing(preamble)) {
|
| 138 | 32x |
case_string <- switch( |
| 139 | 32x |
as.character(length(x@stop_list)), |
| 140 | 32x |
`1` = c("this ", "rule is "),
|
| 141 | 32x |
`2` = c("either of the ", "rules are "),
|
| 142 | 32x |
c("any of the ", "rules are ") # this works as default case
|
| 143 |
) |
|
| 144 | 32x |
preamble <- paste0( |
| 145 | 32x |
"If ", |
| 146 | 32x |
case_string[1], |
| 147 | 32x |
"following ", |
| 148 | 32x |
case_string[2], |
| 149 | 32x |
"`TRUE`:\n" |
| 150 |
) |
|
| 151 |
} |
|
| 152 | 32x |
knit_print.StoppingList(x, ..., preamble = preamble, asis = asis) |
| 153 |
} |
|
| 154 | ||
| 155 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 156 |
#' @inheritParams knit_print.StoppingList |
|
| 157 |
#' @rdname knit_print |
|
| 158 |
#' @export |
|
| 159 |
#' @method knit_print StoppingAll |
|
| 160 |
knit_print.StoppingAll <- function( |
|
| 161 |
x, |
|
| 162 |
..., |
|
| 163 |
preamble, |
|
| 164 |
asis = TRUE |
|
| 165 |
) {
|
|
| 166 | 20x |
if (missing(preamble)) {
|
| 167 | 20x |
case_string <- switch( |
| 168 | 20x |
as.character(length(x@stop_list)), |
| 169 | 20x |
`1` = c("this ", "rule is "),
|
| 170 | 20x |
`2` = c("both of the ", "rules are "),
|
| 171 | 20x |
c("all of the ", "rules are ") # this works as default case
|
| 172 |
) |
|
| 173 | 20x |
preamble <- paste0( |
| 174 | 20x |
"If ", |
| 175 | 20x |
case_string[1], |
| 176 | 20x |
"following ", |
| 177 | 20x |
case_string[2], |
| 178 | 20x |
"`TRUE`:\n" |
| 179 |
) |
|
| 180 |
} |
|
| 181 | 20x |
knit_print.StoppingList(x, ..., preamble = preamble, asis = asis) |
| 182 |
} |
|
| 183 | ||
| 184 |
# StoppingTDCIRatio ---- |
|
| 185 | ||
| 186 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 187 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 188 |
#' @rdname knit_print |
|
| 189 |
#' @export |
|
| 190 |
#' @method knit_print StoppingTDCIRatio |
|
| 191 |
knit_print.StoppingTDCIRatio <- function( |
|
| 192 |
x, |
|
| 193 |
..., |
|
| 194 |
dose_label = "the next best dose", |
|
| 195 |
tox_label = "toxicity", |
|
| 196 |
fmt_string = paste0( |
|
| 197 |
"%sIf, at %s, the ratio of the upper to the lower limit of the posterior ", |
|
| 198 |
"95%% credible interval for %s (targetting %2.0f%%) is less than or equal to " |
|
| 199 |
), |
|
| 200 |
asis = TRUE |
|
| 201 |
) {
|
|
| 202 | 8x |
assert_flag(asis) |
| 203 | 6x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
| 204 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 205 | 6x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
| 206 | ||
| 207 | 6x |
rv <- paste0( |
| 208 | 6x |
sprintf( |
| 209 | 6x |
fmt_string, |
| 210 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 211 | 6x |
dose_label, |
| 212 | 6x |
tox_label, |
| 213 | 6x |
100 * x@prob_target |
| 214 |
), |
|
| 215 | 6x |
x@target_ratio, |
| 216 | 6x |
".\n\n" |
| 217 |
) |
|
| 218 | ||
| 219 | 6x |
if (asis) {
|
| 220 | 2x |
rv <- knitr::asis_output(rv) |
| 221 |
} |
|
| 222 | 6x |
rv |
| 223 |
} |
|
| 224 | ||
| 225 |
# StoppingTargetBiomarker ---- |
|
| 226 | ||
| 227 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 228 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 229 |
#' @param biomarker_label (`character`)\cr the term used to describe the biomarker |
|
| 230 |
#' @rdname knit_print |
|
| 231 |
#' @export |
|
| 232 |
#' @method knit_print StoppingTargetBiomarker |
|
| 233 |
knit_print.StoppingTargetBiomarker <- function( |
|
| 234 |
x, |
|
| 235 |
..., |
|
| 236 |
dose_label = "the next best dose", |
|
| 237 |
biomarker_label = "the target biomarker", |
|
| 238 |
fmt_string = paste0( |
|
| 239 |
"%sIf, at %s, the posterior probability that %s is in the range ", |
|
| 240 |
"(%.2f, %.2f)%s is %.0f%% or more.\n\n" |
|
| 241 |
), |
|
| 242 |
asis = TRUE |
|
| 243 |
) {
|
|
| 244 | 14x |
assert_flag(asis) |
| 245 | 12x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
| 246 | 12x |
assert_character(biomarker_label, len = 1, any.missing = FALSE) |
| 247 | ||
| 248 | 12x |
rv <- sprintf( |
| 249 | 12x |
fmt_string, |
| 250 | 12x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 251 | 12x |
dose_label, |
| 252 | 12x |
biomarker_label, |
| 253 | 12x |
x@target[1], |
| 254 | 12x |
x@target[2], |
| 255 | 12x |
ifelse( |
| 256 | 12x |
x@is_relative, |
| 257 | 12x |
paste0(", relative to the maximum value of ", biomarker_label, ","),
|
| 258 |
"" |
|
| 259 |
), |
|
| 260 | 12x |
100 * x@prob |
| 261 |
) |
|
| 262 | ||
| 263 | 12x |
if (asis) {
|
| 264 | 2x |
rv <- knitr::asis_output(rv) |
| 265 |
} |
|
| 266 | 12x |
rv |
| 267 |
} |
|
| 268 | ||
| 269 |
# StoppingLowestDoseHSRBeta ---- |
|
| 270 | ||
| 271 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 272 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 273 |
#' @rdname knit_print |
|
| 274 |
#' @export |
|
| 275 |
#' @method knit_print StoppingLowestDoseHSRBeta |
|
| 276 |
knit_print.StoppingLowestDoseHSRBeta <- function( |
|
| 277 |
x, |
|
| 278 |
..., |
|
| 279 |
tox_label = "toxicity", |
|
| 280 |
fmt_string = paste0( |
|
| 281 |
"%sIf, using a Hard Stopping Rule with a prior of Beta(%.0f, %.0f), the ", |
|
| 282 |
"lowest dose in the dose grid has a posterior probability of %s of ", |
|
| 283 |
"%.0f%% or more.\n\n" |
|
| 284 |
), |
|
| 285 |
asis = TRUE |
|
| 286 |
) {
|
|
| 287 | 8x |
assert_flag(asis) |
| 288 | 6x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
| 289 | ||
| 290 | 6x |
rv <- sprintf( |
| 291 | 6x |
fmt_string, |
| 292 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 293 | 6x |
x@a, |
| 294 | 6x |
x@b, |
| 295 | 6x |
tox_label, |
| 296 | 6x |
100 * x@prob |
| 297 |
) |
|
| 298 | ||
| 299 | 6x |
if (asis) {
|
| 300 | 2x |
rv <- knitr::asis_output(rv) |
| 301 |
} |
|
| 302 | 6x |
rv |
| 303 |
} |
|
| 304 | ||
| 305 |
# StoppingMTDCV ---- |
|
| 306 | ||
| 307 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 308 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 309 |
#' @rdname knit_print |
|
| 310 |
#' @export |
|
| 311 |
#' @method knit_print StoppingMTDCV |
|
| 312 |
knit_print.StoppingMTDCV <- function( |
|
| 313 |
x, |
|
| 314 |
..., |
|
| 315 |
fmt_string = paste0( |
|
| 316 |
"%sIf the posterior estimate of the robust coefficient of variation of ", |
|
| 317 |
"the MTD (targetting %2.0f%%), is than or equal to %.0f%%.\n\n" |
|
| 318 |
), |
|
| 319 |
asis = TRUE |
|
| 320 |
) {
|
|
| 321 | 8x |
assert_flag(asis) |
| 322 | 6x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
| 323 | ||
| 324 | 6x |
rv <- sprintf( |
| 325 | 6x |
fmt_string, |
| 326 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 327 | 6x |
100 * x@target, |
| 328 | 6x |
100 * x@thresh_cv |
| 329 |
) |
|
| 330 | ||
| 331 | 6x |
if (asis) {
|
| 332 | 2x |
rv <- knitr::asis_output(rv) |
| 333 |
} |
|
| 334 | 6x |
rv |
| 335 |
} |
|
| 336 | ||
| 337 |
# StoppingMTDdistribution ---- |
|
| 338 | ||
| 339 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 340 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 341 |
#' @rdname knit_print |
|
| 342 |
#' @export |
|
| 343 |
#' @method knit_print StoppingMTDdistribution |
|
| 344 |
knit_print.StoppingMTDdistribution <- function( |
|
| 345 |
x, |
|
| 346 |
..., |
|
| 347 |
fmt_string = "%sIf the mean posterior probability of %s at %.0f%% of %s is at least %4.2f.\n\n", |
|
| 348 |
dose_label = "the next best dose", |
|
| 349 |
tox_label = "toxicity", |
|
| 350 |
asis = TRUE |
|
| 351 |
) {
|
|
| 352 | 8x |
assert_flag(asis) |
| 353 | 6x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
| 354 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 355 | 6x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
| 356 | ||
| 357 | 6x |
rv <- sprintf( |
| 358 | 6x |
fmt_string, |
| 359 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 360 | 6x |
tox_label, |
| 361 | 6x |
100 * x@thresh, |
| 362 | 6x |
dose_label, |
| 363 | 6x |
x@prob |
| 364 |
) |
|
| 365 | ||
| 366 | 6x |
if (asis) {
|
| 367 | 2x |
rv <- knitr::asis_output(rv) |
| 368 |
} |
|
| 369 | 6x |
rv |
| 370 |
} |
|
| 371 | ||
| 372 |
# StoppingHighestDose ---- |
|
| 373 | ||
| 374 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 375 |
#' @param asis (`flag`)\cr Not used at present |
|
| 376 |
#' @rdname knit_print |
|
| 377 |
#' @export |
|
| 378 |
#' @method knit_print StoppingHighestDose |
|
| 379 |
knit_print.StoppingHighestDose <- function( |
|
| 380 |
x, |
|
| 381 |
..., |
|
| 382 |
dose_label = "the highest dose in the dose grid", |
|
| 383 |
asis = TRUE |
|
| 384 |
) {
|
|
| 385 | 8x |
rv <- paste0( |
| 386 | 8x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 387 | 8x |
"If the next best dose is ", |
| 388 | 8x |
dose_label, |
| 389 | 8x |
".\n\n" |
| 390 |
) |
|
| 391 | ||
| 392 | 8x |
if (asis) {
|
| 393 | 2x |
rv <- knitr::asis_output(rv) |
| 394 |
} |
|
| 395 | 6x |
rv |
| 396 |
} |
|
| 397 | ||
| 398 |
# StoppingSpecificDose ---- |
|
| 399 | ||
| 400 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 401 |
#' @param asis (`flag`)\cr Not used at present |
|
| 402 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 403 |
#' @rdname knit_print |
|
| 404 |
#' @export |
|
| 405 |
#' @method knit_print StoppingSpecificDose |
|
| 406 |
knit_print.StoppingSpecificDose <- function( |
|
| 407 |
x, |
|
| 408 |
..., |
|
| 409 |
dose_label = as.character(x@dose), |
|
| 410 |
asis = TRUE |
|
| 411 |
) {
|
|
| 412 | 8x |
x@rule@report_label <- x@report_label |
| 413 | 8x |
knit_print( |
| 414 | 8x |
x@rule, |
| 415 |
..., |
|
| 416 | 8x |
dose_label = dose_label, |
| 417 | 8x |
asis = asis |
| 418 |
) |
|
| 419 |
} |
|
| 420 | ||
| 421 |
# StoppingTargetProb ---- |
|
| 422 | ||
| 423 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 424 |
#' @param asis (`flag`)\cr Not used at present |
|
| 425 |
#' @param fmt_string (`character`)\cr the character string that defines the format |
|
| 426 |
#' of the output |
|
| 427 |
#' @param dose_label (`character`)\cr the term used to describe the target dose |
|
| 428 |
#' @param tox_label (`character`)\cr the term used to describe toxicity |
|
| 429 |
#' @rdname knit_print |
|
| 430 |
#' @export |
|
| 431 |
#' @method knit_print StoppingTargetProb |
|
| 432 |
knit_print.StoppingTargetProb <- function( |
|
| 433 |
x, |
|
| 434 |
..., |
|
| 435 |
fmt_string = paste0( |
|
| 436 |
"%sIf the probability of %s at %s is in the range [%4.2f, %4.2f] ", |
|
| 437 |
"is at least %4.2f.\n\n" |
|
| 438 |
), |
|
| 439 |
dose_label = "the next best dose", |
|
| 440 |
tox_label = "toxicity", |
|
| 441 |
asis = TRUE |
|
| 442 |
) {
|
|
| 443 | 60x |
assert_flag(asis) |
| 444 | 56x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
| 445 | 56x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
| 446 | 56x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
| 447 | ||
| 448 | 56x |
rv <- sprintf( |
| 449 | 56x |
fmt_string, |
| 450 | 56x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 451 | 56x |
tox_label, |
| 452 | 56x |
dose_label, |
| 453 | 56x |
x@target[1], |
| 454 | 56x |
x@target[2], |
| 455 | 56x |
x@prob |
| 456 |
) |
|
| 457 | ||
| 458 | 56x |
if (asis) {
|
| 459 | 6x |
rv <- knitr::asis_output(rv) |
| 460 |
} |
|
| 461 | 56x |
rv |
| 462 |
} |
|
| 463 | ||
| 464 |
# StoppingMinCohorts ---- |
|
| 465 | ||
| 466 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 467 |
#' @param asis (`flag`)\cr Not used at present |
|
| 468 |
#' @rdname knit_print |
|
| 469 |
#' @export |
|
| 470 |
#' @method knit_print StoppingMinCohorts |
|
| 471 |
knit_print.StoppingMinCohorts <- function( |
|
| 472 |
x, |
|
| 473 |
..., |
|
| 474 |
asis = TRUE |
|
| 475 |
) {
|
|
| 476 | 43x |
assert_flag(asis) |
| 477 | ||
| 478 | 41x |
rv <- paste0( |
| 479 | 41x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 480 | 41x |
"If ", |
| 481 | 41x |
x@nCohorts, |
| 482 | 41x |
" or more cohorts have been treated.\n\n" |
| 483 |
) |
|
| 484 | 41x |
if (asis) {
|
| 485 | 2x |
rv <- knitr::asis_output(rv) |
| 486 |
} |
|
| 487 | 41x |
rv |
| 488 |
} |
|
| 489 | ||
| 490 |
# StoppingMinPatients ---- |
|
| 491 | ||
| 492 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 493 |
#' @param label (`character`)\cr the term used to label participants |
|
| 494 |
#' @param asis (`flag`)\cr Not used at present |
|
| 495 |
#' @rdname knit_print |
|
| 496 |
#' @export |
|
| 497 |
#' @method knit_print StoppingMinPatients |
|
| 498 |
knit_print.StoppingMinPatients <- function( |
|
| 499 |
x, |
|
| 500 |
..., |
|
| 501 |
label = "participant", |
|
| 502 |
asis = TRUE |
|
| 503 |
) {
|
|
| 504 | 80x |
assert_flag(asis) |
| 505 | 78x |
label <- h_prepare_labels(label) |
| 506 | ||
| 507 | 78x |
rv <- paste0( |
| 508 | 78x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 509 | 78x |
"If ", |
| 510 | 78x |
x@nPatients, |
| 511 | 78x |
paste0(" or more ", label[2], " have been treated."),
|
| 512 | 78x |
"\n\n" |
| 513 |
) |
|
| 514 | 78x |
if (asis) {
|
| 515 | 2x |
rv <- knitr::asis_output(rv) |
| 516 |
} |
|
| 517 | 78x |
rv |
| 518 |
} |
|
| 519 | ||
| 520 |
# StoppingPatientsNearDose ---- |
|
| 521 | ||
| 522 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 523 |
#' @param label (`character`)\cr the term used to label participants |
|
| 524 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 525 |
#' @param asis (`flag`)\cr Not used at present |
|
| 526 |
#' @rdname knit_print |
|
| 527 |
#' @export |
|
| 528 |
#' @method knit_print StoppingPatientsNearDose |
|
| 529 |
knit_print.StoppingPatientsNearDose <- function( |
|
| 530 |
x, |
|
| 531 |
..., |
|
| 532 |
dose_label = "the next best dose", |
|
| 533 |
label = "participants", |
|
| 534 |
asis = TRUE |
|
| 535 |
) {
|
|
| 536 | 8x |
assert_flag(asis) |
| 537 | 6x |
assert_character(label, len = 1, any.missing = FALSE) |
| 538 | 6x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
| 539 | ||
| 540 | 6x |
rv <- paste0( |
| 541 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 542 | 6x |
"If ", |
| 543 | 6x |
x@nPatients, |
| 544 | 6x |
paste0(" or more ", label, " have been treated "),
|
| 545 | 6x |
ifelse( |
| 546 | 6x |
x@percentage == 0, |
| 547 | 6x |
"at ", |
| 548 | 6x |
paste0("within ", x@percentage, "% of ")
|
| 549 |
), |
|
| 550 | 6x |
dose_label, |
| 551 | 6x |
".\n\n" |
| 552 |
) |
|
| 553 | 6x |
if (asis) {
|
| 554 | 2x |
rv <- knitr::asis_output(rv) |
| 555 |
} |
|
| 556 | 6x |
rv |
| 557 |
} |
|
| 558 | ||
| 559 |
# StoppingCohortsNearDose ---- |
|
| 560 | ||
| 561 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 562 |
#' @param asis (`flag`)\cr Not used at present |
|
| 563 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 564 |
#' @rdname knit_print |
|
| 565 |
#' @export |
|
| 566 |
#' @method knit_print StoppingCohortsNearDose |
|
| 567 |
knit_print.StoppingCohortsNearDose <- function( |
|
| 568 |
x, |
|
| 569 |
..., |
|
| 570 |
dose_label = "the next best dose", |
|
| 571 |
asis = TRUE |
|
| 572 |
) {
|
|
| 573 | 8x |
assert_flag(asis) |
| 574 | 6x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
| 575 | ||
| 576 | 6x |
rv <- paste0( |
| 577 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 578 | 6x |
"If ", |
| 579 | 6x |
x@nCohorts, |
| 580 | 6x |
" or more cohorts have been treated ", |
| 581 | 6x |
ifelse( |
| 582 | 6x |
x@percentage == 0, |
| 583 | 6x |
"at ", |
| 584 | 6x |
paste0("within ", x@percentage, "% of ")
|
| 585 |
), |
|
| 586 | 6x |
dose_label, |
| 587 | 6x |
".\n\n" |
| 588 |
) |
|
| 589 | ||
| 590 | 6x |
if (asis) {
|
| 591 | 2x |
rv <- knitr::asis_output(rv) |
| 592 |
} |
|
| 593 | 6x |
rv |
| 594 |
} |
|
| 595 | ||
| 596 |
# StoppingMissingDose ---- |
|
| 597 | ||
| 598 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 599 |
#' @param asis (`flag`)\cr Not used at present |
|
| 600 |
#' @rdname knit_print |
|
| 601 |
#' @export |
|
| 602 |
#' @method knit_print StoppingMissingDose |
|
| 603 |
knit_print.StoppingMissingDose <- function( |
|
| 604 |
x, |
|
| 605 |
..., |
|
| 606 |
asis = TRUE |
|
| 607 |
) {
|
|
| 608 | 8x |
assert_flag(asis) |
| 609 | ||
| 610 | 6x |
rv <- paste0( |
| 611 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
| 612 | 6x |
"If the dose returned by <code>nextBest()</code> is ", |
| 613 | 6x |
"<code>NA</code>, or if the trial includes a placebo dose, the placebo dose.\n\n" |
| 614 |
) |
|
| 615 | ||
| 616 | 6x |
if (asis) {
|
| 617 | 2x |
rv <- knitr::asis_output(rv) |
| 618 |
} |
|
| 619 | 6x |
rv |
| 620 |
} |
| 1 |
#' Convenience function to make barplots of percentages |
|
| 2 |
#' |
|
| 3 |
#' @param x vector of samples |
|
| 4 |
#' @param description xlab string |
|
| 5 |
#' @param xaxisround rounding for xaxis labels (default: 0, i.e. integers will |
|
| 6 |
#' be used) |
|
| 7 |
#' |
|
| 8 |
#' @return the ggplot2 object |
|
| 9 |
#' |
|
| 10 |
#' @keywords internal |
|
| 11 |
#' @importFrom ggplot2 ggplot geom_histogram aes xlab ylab xlim |
|
| 12 |
h_barplot_percentages <- function(x, description, xaxisround = 0) {
|
|
| 13 | 38x |
assert_number(xaxisround, lower = 0) |
| 14 | 36x |
assert_character(description, len = 1, any.missing = FALSE) |
| 15 | 34x |
assert_numeric(x) |
| 16 | ||
| 17 | 33x |
tabx <- table(x) / length(x) |
| 18 | 33x |
dat <- data.frame(x = as.numeric(names(tabx)), perc = as.numeric(tabx) * 100) |
| 19 | 33x |
ggplot() + |
| 20 | 33x |
geom_bar( |
| 21 | 33x |
aes(x = x, y = perc), |
| 22 | 33x |
data = dat, |
| 23 | 33x |
stat = "identity", |
| 24 | 33x |
position = "identity", |
| 25 | 33x |
width = ifelse(nrow(dat) > 1, min(diff(dat$x)) / 2, 1) |
| 26 |
) + |
|
| 27 | 33x |
xlab(description) + |
| 28 | 33x |
ylab("Percent") +
|
| 29 | 33x |
scale_x_continuous( |
| 30 | 33x |
breaks = round(dat$x, xaxisround) |
| 31 |
) |
|
| 32 |
} |
|
| 33 | ||
| 34 | ||
| 35 |
#' Helper function to calculate percentage of true stopping rules for |
|
| 36 |
#' report label output |
|
| 37 |
#' calculates true column means and converts output into percentages |
|
| 38 |
#' before combining the output with the report label; output is passed |
|
| 39 |
#' to [`show()`] and output with cat to console |
|
| 40 |
#' |
|
| 41 |
#' @param stop_report object from summary method |
|
| 42 |
#' @return named list with label and percentage of rule activation |
|
| 43 | ||
| 44 |
h_calc_report_label_percentage <- function(stop_report) {
|
|
| 45 | 30x |
stop_pct <- colMeans(stop_report) * 100 |
| 46 | 30x |
stop_pct_to_print <- stop_pct[!is.na(names(stop_pct))] |
| 47 | 30x |
return(stop_pct_to_print) |
| 48 |
} |
|
| 49 | ||
| 50 | ||
| 51 |
#' Helper function to calculate average across iterations for each additional |
|
| 52 |
#' reporting parameter |
|
| 53 |
#' extracts parameter names as specified by user and averaged the values |
|
| 54 |
#' for each specified parameter to [`show()`] and output with cat to console |
|
| 55 |
#' |
|
| 56 |
#' @param stats_list object from simulation with nested parameter values |
|
| 57 |
#' (sublist for each parameter) |
|
| 58 |
#' @return list of parameter names and averaged values for console output |
|
| 59 | ||
| 60 |
h_summarize_add_stats <- function(stats_list) {
|
|
| 61 |
# Extract the parameter names |
|
| 62 | 2x |
param_names <- names(stats_list[[1]]) |
| 63 | ||
| 64 |
# Calculate the average for each parameter |
|
| 65 | 2x |
averages <- lapply(param_names, function(param) {
|
| 66 | 4x |
values <- sapply(stats_list, function(x) x[[param]]) |
| 67 | 4x |
mean(values) |
| 68 |
}) |
|
| 69 | ||
| 70 | 2x |
return(list(param_names, averages)) |
| 71 |
} |
| 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 |
#' Yeung, W.Y., Whitehead, J., Reigner, B., Beyer, U., Diack, Ch., Jaki, T. (2015), |
|
| 50 |
#' Bayesian adaptive dose-escalation procedures for binary and continuous responses utilizing a gain function, |
|
| 51 |
#' *Pharmaceutical Statistics*, |
|
| 52 |
#' \doi{10.1002/pst.1706} \cr
|
|
| 53 |
#' |
|
| 54 |
#' @export |
|
| 55 |
#' |
|
| 56 |
h_next_best_mg_ci <- function( |
|
| 57 |
dose_target, |
|
| 58 |
dose_mg, |
|
| 59 |
prob_target, |
|
| 60 |
placebo, |
|
| 61 |
model, |
|
| 62 |
model_eff |
|
| 63 |
) {
|
|
| 64 | 26x |
assert_number(dose_target, na.ok = TRUE) |
| 65 | 26x |
assert_number(dose_mg, na.ok = TRUE) |
| 66 | 26x |
assert_probability(prob_target) |
| 67 | 26x |
assert_flag(placebo) |
| 68 | 26x |
assert_class(model, "ModelTox") |
| 69 | 26x |
assert_class(model_eff, "Effloglog") |
| 70 | ||
| 71 |
# Find the variance of the log of target dose. |
|
| 72 | 26x |
mat <- matrix( |
| 73 | 26x |
c( |
| 74 | 26x |
-1 / (model@phi2), |
| 75 | 26x |
-(log(prob_target / (1 - prob_target)) - model@phi1) / (model@phi2)^2 |
| 76 |
), |
|
| 77 | 26x |
nrow = 1 |
| 78 |
) |
|
| 79 | 26x |
var_dose_target <- as.vector(mat %*% model@Pcov %*% t(mat)) |
| 80 | ||
| 81 |
# 95% credibility interval for target dose. |
|
| 82 | 26x |
ci_dose_target <- exp( |
| 83 | 26x |
log(dose_target) + c(-1, 1) * 1.96 * sqrt(var_dose_target) |
| 84 |
) |
|
| 85 | 26x |
cir_dose_target <- ci_dose_target[2] / ci_dose_target[1] |
| 86 | ||
| 87 |
# Find the variance of the log of dose_mg. |
|
| 88 |
# First, find the covariance matrix of all the parameters, phi1, phi2, theta1 and theta2 |
|
| 89 |
# given that phi1 and phi2 are independent of theta1 and theta2. |
|
| 90 | 26x |
log_dose_mg <- log(dose_mg + ifelse(placebo, model_eff@const, 0)) |
| 91 | ||
| 92 |
# Find a delta_g matrix for a variance according to Yeung et. al (2015). |
|
| 93 | 26x |
mean_eff_mg <- model_eff@theta1 + model_eff@theta2 * log(log_dose_mg) |
| 94 | 26x |
denom <- model@phi2 * mean_eff_mg * (1 + model@phi2 * log_dose_mg) |
| 95 | 26x |
dgphi1 <- -(mean_eff_mg * log_dose_mg * model@phi2 - model_eff@theta2) / denom |
| 96 | 26x |
dgphi2 <- -(log_dose_mg * |
| 97 | 26x |
(mean_eff_mg * (1 + log_dose_mg * model@phi2) - model_eff@theta2)) / |
| 98 | 26x |
denom |
| 99 | 26x |
dgtheta1 <- -(log_dose_mg * model@phi2) / denom |
| 100 | 26x |
dgtheta2_num <- -(exp(model@phi1 + model@phi2 * log_dose_mg) * |
| 101 | 26x |
(model@phi2 * log_dose_mg * log(log_dose_mg) - 1) - |
| 102 | 26x |
1) |
| 103 | 26x |
dgtheta2 <- dgtheta2_num / denom |
| 104 | 26x |
delta_g <- matrix(c(dgphi1, dgphi2, dgtheta1, dgtheta2), 4, 1) |
| 105 | ||
| 106 | 26x |
zero_matrix <- matrix(0, 2, 2) |
| 107 | 26x |
cov_beta <- cbind( |
| 108 | 26x |
rbind(model@Pcov, zero_matrix), |
| 109 | 26x |
rbind(zero_matrix, model_eff@Pcov) |
| 110 |
) |
|
| 111 | 26x |
var_log_dose_mg <- as.vector(t(delta_g) %*% cov_beta %*% delta_g) |
| 112 | ||
| 113 |
# 95% credibility interval for max gain dose. |
|
| 114 | 26x |
ci_mg <- exp(log_dose_mg + c(-1, 1) * 1.96 * sqrt(var_log_dose_mg)) |
| 115 | 26x |
ci_ratio_mg <- ci_mg[2] / ci_mg[1] |
| 116 | ||
| 117 | 26x |
list( |
| 118 | 26x |
ci_dose_target = ci_dose_target, |
| 119 | 26x |
ci_ratio_dose_target = cir_dose_target, |
| 120 | 26x |
ci_dose_mg = ci_mg, |
| 121 | 26x |
ci_ratio_dose_mg = ci_ratio_mg |
| 122 |
) |
|
| 123 |
} |
|
| 124 | ||
| 125 |
## next best at grid ---- |
|
| 126 | ||
| 127 |
#' Get Closest Grid Doses for a Given Target Doses for `nextBest-NextBestMaxGain` Method. |
|
| 128 |
#' |
|
| 129 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 130 |
#' |
|
| 131 |
#' Helper function that for a given target doses finds the dose in grid that is |
|
| 132 |
#' closest and below the target. There are four different targets in the context |
|
| 133 |
#' of [`nextBest-NextBestMaxGain()`] method: \eqn{min(`dose_mg`, `dose_target_drt`)},
|
|
| 134 |
#' `dose_mg`, `dose_target_drt` or `dose_target_eot`. |
|
| 135 |
#' |
|
| 136 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
| 137 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
| 138 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
| 139 |
#' @param dose_grid (`numeric`)\cr all possible doses. |
|
| 140 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 141 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level in the `dose_grid` |
|
| 142 |
#' is considered as placebo. |
|
| 143 |
#' |
|
| 144 |
#' @export |
|
| 145 |
#' |
|
| 146 |
h_next_best_mg_doses_at_grid <- function( |
|
| 147 |
dose_target_drt, |
|
| 148 |
dose_target_eot, |
|
| 149 |
dose_mg, |
|
| 150 |
dose_grid, |
|
| 151 |
doselimit, |
|
| 152 |
placebo |
|
| 153 |
) {
|
|
| 154 | 41x |
assert_number(dose_target_drt, na.ok = TRUE) |
| 155 | 41x |
assert_number(dose_target_eot, na.ok = TRUE) |
| 156 | 41x |
assert_number(dose_mg, na.ok = TRUE) |
| 157 | ||
| 158 | 41x |
doses_eligible <- h_next_best_eligible_doses(dose_grid, doselimit, placebo) |
| 159 | ||
| 160 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
| 161 | 41x |
next_dose_lev <- h_find_interval( |
| 162 | 41x |
min(dose_mg, dose_target_drt), |
| 163 | 41x |
doses_eligible |
| 164 |
) |
|
| 165 | 41x |
next_dose <- doses_eligible[next_dose_lev] |
| 166 | ||
| 167 | 41x |
next_dose_mg_lev <- h_find_interval(dose_mg, doses_eligible) |
| 168 | 41x |
next_dose_mg <- doses_eligible[next_dose_mg_lev] |
| 169 | ||
| 170 | 41x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
| 171 | 41x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
| 172 | ||
| 173 | 41x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
| 174 | 41x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
| 175 | ||
| 176 | 41x |
next_dose_list <- list( |
| 177 | 41x |
next_dose = next_dose, |
| 178 | 41x |
next_dose_drt = next_dose_drt, |
| 179 | 41x |
next_dose_eot = next_dose_eot, |
| 180 | 41x |
next_dose_mg = next_dose_mg |
| 181 |
) |
|
| 182 |
} |
|
| 183 | ||
| 184 |
## eligible doses ---- |
|
| 185 | ||
| 186 |
#' Get Eligible Doses from the Dose Grid. |
|
| 187 |
#' |
|
| 188 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 189 |
#' |
|
| 190 |
#' Helper function that gets the eligible doses from the dose grid. |
|
| 191 |
#' The eligible doses are the doses which do not exceed a given |
|
| 192 |
#' `doselimit`. For placebo design, if safety allows (i.e. if there is at least |
|
| 193 |
#' one non-placebo dose which does not exceed the dose limit), the placebo dose |
|
| 194 |
#' is then excluded from the eligible doses. |
|
| 195 |
#' |
|
| 196 |
#' @param dose_grid (`numeric`)\cr all possible doses. |
|
| 197 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 198 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level in the `dose_grid` |
|
| 199 |
#' is considered as placebo. |
|
| 200 |
#' @param levels (`flag`)\cr if `TRUE` the levels of eligible doses are returned, |
|
| 201 |
#' otherwise, the doses (default). |
|
| 202 |
#' |
|
| 203 |
#' @return A numeric vector with eligible doses or eligible dose levels if `levels` |
|
| 204 |
#' flag is `TRUE`. |
|
| 205 |
#' |
|
| 206 |
#' @export |
|
| 207 |
#' @examples |
|
| 208 |
#' dose_grid <- c(0.001, seq(25, 200, 25)) |
|
| 209 |
#' h_next_best_eligible_doses(dose_grid, 79, TRUE) |
|
| 210 |
#' h_next_best_eligible_doses(dose_grid, 24, TRUE) |
|
| 211 |
h_next_best_eligible_doses <- function( |
|
| 212 |
dose_grid, |
|
| 213 |
doselimit, |
|
| 214 |
placebo, |
|
| 215 |
levels = FALSE |
|
| 216 |
) {
|
|
| 217 | 485x |
assert_numeric( |
| 218 | 485x |
dose_grid, |
| 219 | 485x |
finite = TRUE, |
| 220 | 485x |
any.missing = FALSE, |
| 221 | 485x |
min.len = 1L, |
| 222 | 485x |
sorted = TRUE |
| 223 |
) |
|
| 224 | 483x |
assert_number(doselimit) |
| 225 | 483x |
assert_flag(placebo) |
| 226 | 483x |
assert_flag(levels) |
| 227 | ||
| 228 | 483x |
is_dose_eligible <- dose_grid <= doselimit |
| 229 | 483x |
if (placebo && sum(is_dose_eligible) > 1L) {
|
| 230 | 84x |
is_dose_eligible[1] <- FALSE |
| 231 |
} |
|
| 232 | ||
| 233 | 483x |
if (levels) {
|
| 234 | 362x |
is_dose_eligible |
| 235 |
} else {
|
|
| 236 | 121x |
dose_grid[is_dose_eligible] |
| 237 |
} |
|
| 238 |
} |
|
| 239 | ||
| 240 |
## plot ---- |
|
| 241 | ||
| 242 |
#' Building the Plot for `nextBest-NextBestNCRMLoss` Method. |
|
| 243 |
#' |
|
| 244 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 245 |
#' |
|
| 246 |
#' Helper function which creates the plot for [`nextBest-NextBestNCRMLoss()`] |
|
| 247 |
#' method. |
|
| 248 |
#' |
|
| 249 |
#' @param prob_mat (`numeric`)\cr matrix with probabilities of a grid doses |
|
| 250 |
#' to be in a given interval. If `is_unacceptable_specified` is `TRUE`, there |
|
| 251 |
#' must be 4 intervals (columns) in `prob_mat`: `underdosing`, `target`, |
|
| 252 |
#' `excessive`, `unacceptable`. Otherwise, there must be 3 intervals (columns): |
|
| 253 |
#' `underdosing`, `target`, `overdose`. Number of rows must be equal to number |
|
| 254 |
#' of doses in a grid. |
|
| 255 |
#' @param posterior_loss (`numeric`)\cr posterior losses. |
|
| 256 |
#' @param max_overdose_prob (`number`)\cr maximum overdose posterior |
|
| 257 |
#' probability that is allowed. |
|
| 258 |
#' @param dose_grid (`numeric`)\cr dose grid. |
|
| 259 |
#' @param max_eligible_dose_level (`number`)\cr maximum eligible dose level in |
|
| 260 |
#' the `dose_grid`. |
|
| 261 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 262 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 263 |
#' @param is_unacceptable_specified (`flag`)\cr is unacceptable interval specified? |
|
| 264 |
#' |
|
| 265 |
#' @export |
|
| 266 |
h_next_best_ncrm_loss_plot <- function( |
|
| 267 |
prob_mat, |
|
| 268 |
posterior_loss, |
|
| 269 |
max_overdose_prob, |
|
| 270 |
dose_grid, |
|
| 271 |
max_eligible_dose_level, |
|
| 272 |
doselimit, |
|
| 273 |
next_dose, |
|
| 274 |
is_unacceptable_specified |
|
| 275 |
) {
|
|
| 276 | 8x |
assert_numeric(dose_grid, finite = TRUE, any.missing = FALSE, sorted = TRUE) |
| 277 | 8x |
n_grid <- length(dose_grid) |
| 278 | 8x |
assert_flag(is_unacceptable_specified) |
| 279 | 8x |
assert_probabilities(prob_mat) |
| 280 | 8x |
assert_matrix( |
| 281 | 8x |
prob_mat, |
| 282 | 8x |
min.cols = 3, |
| 283 | 8x |
max.cols = 4, |
| 284 | 8x |
nrows = n_grid, |
| 285 | 8x |
col.names = "named" |
| 286 |
) |
|
| 287 | 8x |
if (!is_unacceptable_specified) {
|
| 288 | 4x |
assert_names( |
| 289 | 4x |
colnames(prob_mat), |
| 290 | 4x |
permutation.of = c("underdosing", "target", "overdose")
|
| 291 |
) |
|
| 292 |
} else {
|
|
| 293 | 4x |
assert_names( |
| 294 | 4x |
colnames(prob_mat), |
| 295 | 4x |
permutation.of = c("underdosing", "target", "excessive", "unacceptable")
|
| 296 |
) |
|
| 297 |
} |
|
| 298 | 8x |
assert_numeric( |
| 299 | 8x |
posterior_loss, |
| 300 | 8x |
finite = TRUE, |
| 301 | 8x |
any.missing = FALSE, |
| 302 | 8x |
len = n_grid |
| 303 |
) |
|
| 304 | 8x |
assert_probability(max_overdose_prob) |
| 305 | 8x |
assert_number(max_eligible_dose_level, lower = 0, upper = n_grid) |
| 306 | 8x |
assert_number(doselimit) |
| 307 | 8x |
assert_number(next_dose, na.ok = TRUE) |
| 308 | ||
| 309 |
# Build plots, first for the target probability. |
|
| 310 | 8x |
p1 <- ggplot() + |
| 311 | 8x |
geom_bar( |
| 312 | 8x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "target"] * 100), |
| 313 | 8x |
aes(x = .data$Dose, y = .data$y), |
| 314 | 8x |
stat = "identity", |
| 315 | 8x |
position = "identity", |
| 316 | 8x |
width = min(diff(dose_grid)) / 2, |
| 317 | 8x |
colour = "darkgreen", |
| 318 | 8x |
fill = "darkgreen" |
| 319 |
) + |
|
| 320 | 8x |
ylim(c(0, 100)) + |
| 321 | 8x |
ylab(paste("Target probability [%]"))
|
| 322 | ||
| 323 | 8x |
if (is.finite(doselimit)) {
|
| 324 | 5x |
p1 <- p1 + |
| 325 | 5x |
geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
| 326 |
} |
|
| 327 | ||
| 328 | 8x |
if (max_eligible_dose_level > 0) {
|
| 329 | 8x |
p1 <- p1 + |
| 330 | 8x |
geom_vline( |
| 331 | 8x |
xintercept = dose_grid[max_eligible_dose_level], |
| 332 | 8x |
lwd = 1.1, |
| 333 | 8x |
lty = 2, |
| 334 | 8x |
colour = "red" |
| 335 |
) |
|
| 336 |
} |
|
| 337 | ||
| 338 | 8x |
p_loss <- ggplot() + |
| 339 |
# For the loss function. |
|
| 340 | 8x |
geom_bar( |
| 341 | 8x |
data = data.frame(Dose = dose_grid, y = posterior_loss), |
| 342 | 8x |
aes(x = .data$Dose, y = .data$y), |
| 343 | 8x |
stat = "identity", |
| 344 | 8x |
position = "identity", |
| 345 | 8x |
width = min(diff(dose_grid)) / 2, |
| 346 | 8x |
colour = "darkgreen", |
| 347 | 8x |
fill = "darkgreen" |
| 348 |
) + |
|
| 349 | 8x |
geom_point( |
| 350 | 8x |
aes(x = next_dose, y = max(posterior_loss) + 0.2), |
| 351 | 8x |
size = 3, |
| 352 | 8x |
pch = 25, |
| 353 | 8x |
col = "red", |
| 354 | 8x |
bg = "red" |
| 355 |
) + |
|
| 356 | 8x |
ylab(paste("Loss function"))
|
| 357 | ||
| 358 | 8x |
if (!is_unacceptable_specified) {
|
| 359 |
# Second, for the overdosing probability. |
|
| 360 | 4x |
p2 <- ggplot() + |
| 361 | 4x |
geom_bar( |
| 362 | 4x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "overdose"] * 100), |
| 363 | 4x |
aes(x = .data$Dose, y = .data$y), |
| 364 | 4x |
stat = "identity", |
| 365 | 4x |
position = "identity", |
| 366 | 4x |
width = min(diff(dose_grid)) / 2, |
| 367 | 4x |
colour = "red", |
| 368 | 4x |
fill = "red" |
| 369 |
) + |
|
| 370 | 4x |
geom_hline( |
| 371 | 4x |
yintercept = max_overdose_prob * 100, |
| 372 | 4x |
lwd = 1.1, |
| 373 | 4x |
lty = 2, |
| 374 | 4x |
colour = "black" |
| 375 |
) + |
|
| 376 | 4x |
ylim(c(0, 100)) + |
| 377 | 4x |
ylab("Overdose probability [%]")
|
| 378 | ||
| 379 |
# Combine it all together. |
|
| 380 | 4x |
plots_single <- list(plot1 = p1, plot2 = p2, plot_loss = p_loss) |
| 381 | 4x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, p_loss, nrow = 3) |
| 382 |
} else {
|
|
| 383 |
# Plot in case of 4 toxicity intervals. Second, for the overdosing probability. |
|
| 384 | 4x |
p2 <- ggplot() + |
| 385 | 4x |
geom_bar( |
| 386 | 4x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "excessive"] * 100), |
| 387 | 4x |
aes(x = .data$Dose, y = .data$y), |
| 388 | 4x |
stat = "identity", |
| 389 | 4x |
position = "identity", |
| 390 | 4x |
width = min(diff(dose_grid)) / 2, |
| 391 | 4x |
colour = "red", |
| 392 | 4x |
fill = "red" |
| 393 |
) + |
|
| 394 | 4x |
ylim(c(0, 100)) + |
| 395 | 4x |
ylab("Excessive probability [%]")
|
| 396 | ||
| 397 | 4x |
p3 <- ggplot() + |
| 398 | 4x |
geom_bar( |
| 399 | 4x |
data = data.frame( |
| 400 | 4x |
Dose = dose_grid, |
| 401 | 4x |
y = prob_mat[, "unacceptable"] * 100 |
| 402 |
), |
|
| 403 | 4x |
aes(x = .data$Dose, y = .data$y), |
| 404 | 4x |
stat = "identity", |
| 405 | 4x |
position = "identity", |
| 406 | 4x |
width = min(diff(dose_grid)) / 2, |
| 407 | 4x |
colour = "red", |
| 408 | 4x |
fill = "red" |
| 409 |
) + |
|
| 410 | 4x |
ylim(c(0, 100)) + |
| 411 | 4x |
ylab("Unacceptable probability [%]")
|
| 412 | ||
| 413 |
# Combine it all together. |
|
| 414 | 4x |
plots_single <- list(plot1 = p1, plot2 = p2, plot3 = p3, plot_loss = p_loss) |
| 415 | 4x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, p3, p_loss, nrow = 4) |
| 416 |
} |
|
| 417 | ||
| 418 | 8x |
list(plots_single = plots_single, plot_joint = plot_joint) |
| 419 |
} |
|
| 420 | ||
| 421 |
#' Building the Plot for `nextBest-NextBestTDsamples` Method. |
|
| 422 |
#' |
|
| 423 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 424 |
#' |
|
| 425 |
#' Helper function which creates the plot for [`nextBest-NextBestTDsamples()`] |
|
| 426 |
#' method. |
|
| 427 |
#' |
|
| 428 |
#' @param dose_target_drt_samples (`numeric`)\cr vector of in-trial samples. |
|
| 429 |
#' @param dose_target_eot_samples (`numeric`)\cr vector of end-of-trial samples. |
|
| 430 |
#' @param dose_target_drt (`number`)\cr target in-trial estimate. |
|
| 431 |
#' @param dose_target_eot (`number`)\cr target end-of-trial estimate. |
|
| 432 |
#' @param dose_grid_range (`numeric`)\cr range of dose grid. |
|
| 433 |
#' @param nextBest (`NextBestTDsamples`)\cr the rule for the next best dose. |
|
| 434 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 435 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 436 |
#' |
|
| 437 |
#' @export |
|
| 438 |
#' |
|
| 439 |
h_next_best_tdsamples_plot <- function( |
|
| 440 |
dose_target_drt_samples, |
|
| 441 |
dose_target_eot_samples, |
|
| 442 |
dose_target_drt, |
|
| 443 |
dose_target_eot, |
|
| 444 |
dose_grid_range, |
|
| 445 |
nextBest, |
|
| 446 |
doselimit, |
|
| 447 |
next_dose |
|
| 448 |
) {
|
|
| 449 | 26x |
assert_numeric(dose_target_drt_samples, any.missing = FALSE) |
| 450 | 26x |
assert_numeric(dose_target_eot_samples, any.missing = FALSE) |
| 451 | 26x |
assert_number(dose_target_drt) |
| 452 | 26x |
assert_number(dose_target_eot) |
| 453 | 26x |
assert_range(dose_grid_range, finite = TRUE, unique = FALSE) |
| 454 | 26x |
assert_class(nextBest, "NextBestTDsamples") |
| 455 | 26x |
assert_number(doselimit) |
| 456 | 26x |
assert_number(next_dose, na.ok = TRUE) |
| 457 | ||
| 458 | 26x |
lbl1 <- paste("TD", nextBest@prob_target_drt * 100, "Estimate")
|
| 459 | 26x |
lbl2 <- paste("TD", nextBest@prob_target_eot * 100, "Estimate")
|
| 460 | 26x |
labels <- data.frame( |
| 461 | 26x |
Type = c("during", "end", "Max", "Next"),
|
| 462 | 26x |
Alpha = c(0.25, 0.25, 1, 1), |
| 463 | 26x |
x = c( |
| 464 | 26x |
dose_target_drt, |
| 465 | 26x |
dose_target_eot, |
| 466 | 26x |
min(doselimit, dose_grid_range[2]), |
| 467 | 26x |
next_dose |
| 468 |
) |
|
| 469 |
) |
|
| 470 | 26x |
p <- ggplot( |
| 471 | 26x |
data = rbind( |
| 472 | 26x |
data.frame(period = "during", TD = dose_target_drt_samples), |
| 473 | 26x |
data.frame(period = "end", TD = dose_target_eot_samples) |
| 474 |
), |
|
| 475 | 26x |
aes(x = .data$TD, colour = .data$period), |
| 476 |
) + |
|
| 477 | 26x |
geom_density( |
| 478 | 26x |
aes(fill = .data$period, colour = .data$period), |
| 479 | 26x |
alpha = 0.25, |
| 480 | 26x |
bounds = dose_grid_range, |
| 481 | 26x |
show.legend = FALSE |
| 482 |
) + |
|
| 483 | 26x |
geom_vline(data = labels, aes(xintercept = x, colour = Type)) + |
| 484 | 26x |
ylab("Posterior density") +
|
| 485 | 26x |
scale_colour_manual( |
| 486 | 26x |
name = NULL, |
| 487 | 26x |
values = c( |
| 488 | 26x |
"during" = "orange", |
| 489 | 26x |
"end" = "violet", |
| 490 | 26x |
"Max" = "red", |
| 491 | 26x |
"Next" = "blue" |
| 492 |
), |
|
| 493 | 26x |
labels = c("during" = lbl1, "end" = lbl2, "Max" = "Max", "Next" = "Next")
|
| 494 |
) + |
|
| 495 | 26x |
scale_fill_manual( |
| 496 | 26x |
values = c("during" = "orange", "end" = "violet")
|
| 497 |
) |
|
| 498 |
} |
|
| 499 | ||
| 500 |
#' Building the Plot for `nextBest-NextBestTD` Method. |
|
| 501 |
#' |
|
| 502 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 503 |
#' |
|
| 504 |
#' Helper function which creates the plot for [`nextBest-NextBestTD()`] method. |
|
| 505 |
#' |
|
| 506 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
| 507 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
| 508 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
| 509 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
| 510 |
#' @param data (`Data`)\cr the data object from which the dose grid will be fetched. |
|
| 511 |
#' @param prob_dlt (`numeric`)\cr DLT probabilities for doses in grid. |
|
| 512 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 513 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 514 |
#' |
|
| 515 |
#' @export |
|
| 516 |
#' |
|
| 517 |
h_next_best_td_plot <- function( |
|
| 518 |
prob_target_drt, |
|
| 519 |
dose_target_drt, |
|
| 520 |
prob_target_eot, |
|
| 521 |
dose_target_eot, |
|
| 522 |
data, |
|
| 523 |
prob_dlt, |
|
| 524 |
doselimit, |
|
| 525 |
next_dose |
|
| 526 |
) {
|
|
| 527 | 34x |
assert_probability(prob_target_drt) |
| 528 | 34x |
assert_number(dose_target_drt) |
| 529 | 34x |
assert_probability(prob_target_eot) |
| 530 | 34x |
assert_number(dose_target_eot) |
| 531 | 34x |
assert_class(data, "Data") |
| 532 | 34x |
assert_probabilities(prob_dlt, len = data@nGrid) |
| 533 | 34x |
assert_number(doselimit) |
| 534 | 34x |
assert_number(next_dose, na.ok = TRUE) |
| 535 | ||
| 536 | 34x |
dosegrid_range <- dose_grid_range(data) |
| 537 | ||
| 538 | 34x |
p <- ggplot( |
| 539 | 34x |
data = data.frame(x = data@doseGrid, y = prob_dlt), |
| 540 | 34x |
aes(x = .data$x, y = .data$y) |
| 541 |
) + |
|
| 542 | 34x |
geom_line(colour = "red", linewidth = 1.5) + |
| 543 | 34x |
coord_cartesian(xlim = c(0, dosegrid_range[2])) + |
| 544 | 34x |
ylim(c(0, 1)) + |
| 545 | 34x |
xlab("Dose Levels") +
|
| 546 | 34x |
ylab("Probability of DLT")
|
| 547 |
if ( |
|
| 548 | 34x |
h_in_range(dose_target_drt, range = dosegrid_range, bounds_closed = TRUE) |
| 549 |
) {
|
|
| 550 | 32x |
p <- p + |
| 551 | 32x |
geom_point( |
| 552 | 32x |
data = data.frame(x = dose_target_drt, y = prob_target_drt), |
| 553 | 32x |
aes(x = .data$x, y = .data$y), |
| 554 | 32x |
colour = "orange", |
| 555 | 32x |
shape = 15, |
| 556 | 32x |
size = 8 |
| 557 |
) + |
|
| 558 | 32x |
annotate( |
| 559 | 32x |
geom = "text", |
| 560 | 32x |
label = paste("TD", prob_target_drt * 100, "Estimate"),
|
| 561 | 32x |
x = dose_target_drt + 1, |
| 562 | 32x |
y = prob_target_drt - 0.2, |
| 563 | 32x |
size = 5, |
| 564 | 32x |
colour = "orange" |
| 565 |
) |
|
| 566 |
} |
|
| 567 | ||
| 568 |
if ( |
|
| 569 | 34x |
h_in_range(dose_target_eot, range = dosegrid_range, bounds_closed = TRUE) |
| 570 |
) {
|
|
| 571 | 32x |
p <- p + |
| 572 | 32x |
geom_point( |
| 573 | 32x |
data = data.frame(x = dose_target_eot, y = prob_target_eot), |
| 574 | 32x |
aes(x = .data$x, y = .data$y), |
| 575 | 32x |
colour = "violet", |
| 576 | 32x |
shape = 16, |
| 577 | 32x |
size = 8 |
| 578 |
) + |
|
| 579 | 32x |
annotate( |
| 580 | 32x |
geom = "text", |
| 581 | 32x |
label = paste("TD", prob_target_eot * 100, "Estimate"),
|
| 582 | 32x |
x = dose_target_eot + 1, |
| 583 | 32x |
y = prob_target_eot - 0.1, |
| 584 | 32x |
size = 5, |
| 585 | 32x |
colour = "violet" |
| 586 |
) |
|
| 587 |
} |
|
| 588 | ||
| 589 | 34x |
maxdoselimit <- min(doselimit, dosegrid_range[2]) |
| 590 | ||
| 591 | 34x |
p + |
| 592 | 34x |
geom_vline(xintercept = maxdoselimit, colour = "brown", lwd = 1.1) + |
| 593 | 34x |
geom_text( |
| 594 | 34x |
data = data.frame(x = maxdoselimit, y = 0), |
| 595 | 34x |
aes(x = .data$x, y = .data$y, label = "Max", hjust = +1, vjust = -30), |
| 596 | 34x |
angle = 90, |
| 597 | 34x |
vjust = 1.5, |
| 598 | 34x |
hjust = 0.5, |
| 599 | 34x |
colour = "brown", |
| 600 |
) + |
|
| 601 | 34x |
geom_vline(xintercept = next_dose, colour = "purple", lwd = 1.1) + |
| 602 | 34x |
geom_text( |
| 603 | 34x |
data = data.frame(x = next_dose, y = 0), |
| 604 | 34x |
aes(x = .data$x, y = .data$y, label = "Next", hjust = 0, vjust = -30), |
| 605 | 34x |
angle = 90, |
| 606 | 34x |
vjust = -0.5, |
| 607 | 34x |
hjust = 0.5, |
| 608 | 34x |
colour = "purple" |
| 609 |
) |
|
| 610 |
} |
|
| 611 | ||
| 612 |
#' Building the Plot for `nextBest-NextBestMaxGain` Method. |
|
| 613 |
#' |
|
| 614 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 615 |
#' |
|
| 616 |
#' Helper function which creates the plot for [`nextBest-NextBestMaxGain()`] method. |
|
| 617 |
#' |
|
| 618 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
| 619 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
| 620 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
| 621 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
| 622 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
| 623 |
#' @param max_gain (`number`)\cr the maximum gain estimate. |
|
| 624 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 625 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 626 |
#' @param data (`DataDual`)\cr the data object from which the dose grid will be fetched. |
|
| 627 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
| 628 |
#' @param model_eff (`Effloglog`)\cr the efficacy model. |
|
| 629 |
#' |
|
| 630 |
#' @export |
|
| 631 |
#' |
|
| 632 |
h_next_best_mg_plot <- function( |
|
| 633 |
prob_target_drt, |
|
| 634 |
dose_target_drt, |
|
| 635 |
prob_target_eot, |
|
| 636 |
dose_target_eot, |
|
| 637 |
dose_mg, |
|
| 638 |
max_gain, |
|
| 639 |
next_dose, |
|
| 640 |
doselimit, |
|
| 641 |
data, |
|
| 642 |
model, |
|
| 643 |
model_eff |
|
| 644 |
) {
|
|
| 645 | 26x |
assert_probability(prob_target_drt) |
| 646 | 26x |
assert_number(dose_target_drt) |
| 647 | 26x |
assert_probability(prob_target_eot) |
| 648 | 26x |
assert_number(dose_target_eot) |
| 649 | 26x |
assert_number(dose_mg, na.ok = TRUE) |
| 650 | 26x |
assert_number(max_gain, na.ok = TRUE) |
| 651 | 26x |
assert_number(next_dose, na.ok = TRUE) |
| 652 | 26x |
assert_number(doselimit) |
| 653 | 26x |
assert_class(data, "Data") |
| 654 | 26x |
assert_class(model, "ModelTox") |
| 655 | 26x |
assert_class(model_eff, "Effloglog") |
| 656 | ||
| 657 | 26x |
dosegrid_range <- dose_grid_range(data) |
| 658 | ||
| 659 | 26x |
data_plot <- data.frame( |
| 660 | 26x |
dose = rep(data@doseGrid, 3), |
| 661 | 26x |
y = c( |
| 662 | 26x |
prob(dose = data@doseGrid, model = model), |
| 663 | 26x |
efficacy(dose = data@doseGrid, model = model_eff), |
| 664 | 26x |
gain(dose = data@doseGrid, model_dle = model, model_eff = model_eff) |
| 665 |
), |
|
| 666 | 26x |
group = c( |
| 667 | 26x |
rep("p(DLE)", data@nGrid),
|
| 668 | 26x |
rep("Expected Efficacy", data@nGrid),
|
| 669 | 26x |
rep("Gain", data@nGrid)
|
| 670 |
) |
|
| 671 |
) |
|
| 672 | ||
| 673 | 26x |
p <- ggplot(data = data_plot, aes(x = .data$dose, y = .data$y)) + |
| 674 | 26x |
geom_line(aes(group = group, color = group), linewidth = 1.5) + |
| 675 | 26x |
ggplot2::scale_colour_manual( |
| 676 | 26x |
name = "curves", |
| 677 | 26x |
values = c("blue", "green3", "red")
|
| 678 |
) + |
|
| 679 | 26x |
coord_cartesian(xlim = c(0, dosegrid_range[2])) + |
| 680 | 26x |
ylim(range(data_plot$y)) + |
| 681 | 26x |
xlab("Dose Level") +
|
| 682 | 26x |
ylab("Values")
|
| 683 | ||
| 684 |
if ( |
|
| 685 | 26x |
h_in_range(dose_target_eot, range = dosegrid_range, bounds_closed = FALSE) |
| 686 |
) {
|
|
| 687 | 26x |
lab <- paste("TD", prob_target_eot * 100, "Estimate")
|
| 688 | 26x |
p <- p + |
| 689 | 26x |
geom_point( |
| 690 | 26x |
data = data.frame(x = dose_target_eot, y = prob_target_eot), |
| 691 | 26x |
aes(x = .data$x, y = .data$y), |
| 692 | 26x |
colour = "violet", |
| 693 | 26x |
shape = 16, |
| 694 | 26x |
size = 8 |
| 695 |
) + |
|
| 696 | 26x |
annotate( |
| 697 | 26x |
geom = "text", |
| 698 | 26x |
label = lab, |
| 699 | 26x |
x = dose_target_eot - 1, |
| 700 | 26x |
y = 0.2, |
| 701 | 26x |
size = 5, |
| 702 | 26x |
colour = "violet" |
| 703 |
) |
|
| 704 |
} |
|
| 705 | ||
| 706 | 26x |
if (h_in_range(dose_mg, range = dosegrid_range, bounds_closed = FALSE)) {
|
| 707 | 25x |
p <- p + |
| 708 | 25x |
geom_point( |
| 709 | 25x |
data = data.frame(x = dose_mg, y = max_gain), |
| 710 | 25x |
aes(x = .data$x, y = .data$y), |
| 711 | 25x |
colour = "green3", |
| 712 | 25x |
shape = 17, |
| 713 | 25x |
size = 8 |
| 714 |
) + |
|
| 715 | 25x |
annotate( |
| 716 | 25x |
"text", |
| 717 | 25x |
label = "Max Gain Estimate", |
| 718 | 25x |
x = dose_mg, |
| 719 | 25x |
y = max_gain - 0.1, |
| 720 | 25x |
size = 5, |
| 721 | 25x |
colour = "green3" |
| 722 |
) |
|
| 723 |
} |
|
| 724 | ||
| 725 |
if ( |
|
| 726 | 26x |
h_in_range(dose_target_drt, range = dosegrid_range, bounds_closed = FALSE) |
| 727 |
) {
|
|
| 728 | 26x |
lab <- paste("TD", prob_target_drt * 100, "Estimate")
|
| 729 | 26x |
p <- p + |
| 730 | 26x |
geom_point( |
| 731 | 26x |
data = data.frame(x = dose_target_drt, y = prob_target_drt), |
| 732 | 26x |
aes(x = .data$x, y = .data$y), |
| 733 | 26x |
colour = "orange", |
| 734 | 26x |
shape = 15, |
| 735 | 26x |
size = 8 |
| 736 |
) + |
|
| 737 | 26x |
annotate( |
| 738 | 26x |
geom = "text", |
| 739 | 26x |
label = lab, |
| 740 | 26x |
x = dose_target_drt + 25, |
| 741 | 26x |
y = prob_target_drt + 0.01, |
| 742 | 26x |
size = 5, |
| 743 | 26x |
colour = "orange" |
| 744 |
) |
|
| 745 |
} |
|
| 746 | ||
| 747 | 26x |
maxdoselimit <- min(doselimit, dosegrid_range[2]) |
| 748 | ||
| 749 | 26x |
p + |
| 750 | 26x |
geom_vline(xintercept = maxdoselimit, colour = "brown", lwd = 1.1) + |
| 751 | 26x |
annotate( |
| 752 | 26x |
geom = "text", |
| 753 | 26x |
label = "Max", |
| 754 | 26x |
x = maxdoselimit - 2, |
| 755 | 26x |
y = max(data_plot$y), |
| 756 | 26x |
size = 5, |
| 757 | 26x |
angle = 90, |
| 758 | 26x |
vjust = -0.5, |
| 759 | 26x |
hjust = 0.5, |
| 760 | 26x |
colour = "brown" |
| 761 |
) + |
|
| 762 | 26x |
geom_vline(xintercept = next_dose, colour = "purple", lwd = 1.1) + |
| 763 | 26x |
annotate( |
| 764 | 26x |
geom = "text", |
| 765 | 26x |
label = "Next", |
| 766 | 26x |
x = next_dose + 1, |
| 767 | 26x |
y = max(data_plot$y) - 0.05, |
| 768 | 26x |
size = 5, |
| 769 | 26x |
angle = 90, |
| 770 | 26x |
vjust = 1.5, |
| 771 | 26x |
hjust = 0.5, |
| 772 | 26x |
color = "purple" |
| 773 |
) |
|
| 774 |
} |
|
| 775 | ||
| 776 |
#' Building the Plot for `nextBest-NextBestMaxGainSamples` Method. |
|
| 777 |
#' |
|
| 778 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 779 |
#' |
|
| 780 |
#' Helper function which creates the plot for [`nextBest-NextBestMaxGainSamples()`] method. |
|
| 781 |
#' |
|
| 782 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
| 783 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
| 784 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
| 785 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
| 786 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
| 787 |
#' @param dose_mg_samples (`numeric`)\cr for every sample, the dose (from the dose grid) |
|
| 788 |
#' that gives the maximum gain value. |
|
| 789 |
#' @param next_dose (`number`)\cr next best dose. |
|
| 790 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
| 791 |
#' @param dose_grid_range (`numeric`)\cr dose grid range. |
|
| 792 |
#' |
|
| 793 |
#' @export |
|
| 794 |
#' |
|
| 795 |
h_next_best_mgsamples_plot <- function( |
|
| 796 |
prob_target_drt, |
|
| 797 |
dose_target_drt, |
|
| 798 |
prob_target_eot, |
|
| 799 |
dose_target_eot, |
|
| 800 |
dose_mg, |
|
| 801 |
dose_mg_samples, |
|
| 802 |
next_dose, |
|
| 803 |
doselimit, |
|
| 804 |
dose_grid_range |
|
| 805 |
) {
|
|
| 806 | 15x |
assert_range(dose_grid_range, finite = TRUE, unique = FALSE) |
| 807 | 15x |
assert_probability(prob_target_drt) |
| 808 | 15x |
assert_number(dose_target_drt) |
| 809 | 15x |
assert_probability(prob_target_eot) |
| 810 | 15x |
assert_number(dose_target_eot) |
| 811 | 15x |
assert_number(dose_mg, na.ok = TRUE) |
| 812 | 15x |
assert_numeric( |
| 813 | 15x |
dose_mg_samples, |
| 814 | 15x |
lower = dose_grid_range[1], |
| 815 | 15x |
upper = dose_grid_range[2], |
| 816 | 15x |
finite = TRUE, |
| 817 | 15x |
any.missing = FALSE |
| 818 |
) |
|
| 819 | 15x |
assert_number(next_dose, na.ok = TRUE) |
| 820 | 15x |
assert_number(doselimit) |
| 821 | ||
| 822 | 15x |
p <- ggplot() + |
| 823 | 15x |
geom_histogram( |
| 824 | 15x |
data = data.frame(Gstar = dose_mg_samples), |
| 825 | 15x |
aes(x = .data$Gstar), |
| 826 | 15x |
fill = "darkgreen", |
| 827 | 15x |
colour = "green3", |
| 828 | 15x |
binwidth = 25 |
| 829 |
) + |
|
| 830 | 15x |
coord_cartesian(xlim = c(0, dose_grid_range[2])) + |
| 831 | 15x |
ylab("Posterior density")
|
| 832 | ||
| 833 |
if ( |
|
| 834 | 15x |
h_in_range(dose_target_drt, range = dose_grid_range, bounds_closed = FALSE) |
| 835 |
) {
|
|
| 836 | 13x |
lab <- paste("TD", prob_target_drt * 100, "Estimate")
|
| 837 | 13x |
p <- p + |
| 838 | 13x |
geom_vline(xintercept = dose_target_drt, colour = "orange", lwd = 1.1) + |
| 839 | 13x |
annotate( |
| 840 | 13x |
geom = "text", |
| 841 | 13x |
label = lab, |
| 842 | 13x |
x = dose_target_drt, |
| 843 | 13x |
y = 0, |
| 844 | 13x |
hjust = -0.1, |
| 845 | 13x |
vjust = -20, |
| 846 | 13x |
size = 5, |
| 847 | 13x |
colour = "orange" |
| 848 |
) |
|
| 849 |
} |
|
| 850 | ||
| 851 |
if ( |
|
| 852 | 15x |
h_in_range(dose_target_eot, range = dose_grid_range, bounds_closed = FALSE) |
| 853 |
) {
|
|
| 854 | 13x |
lab <- paste("TD", prob_target_eot * 100, "Estimate")
|
| 855 | 13x |
p <- p + |
| 856 | 13x |
geom_vline(xintercept = dose_target_eot, colour = "violet", lwd = 1.1) + |
| 857 | 13x |
annotate( |
| 858 | 13x |
geom = "text", |
| 859 | 13x |
label = lab, |
| 860 | 13x |
x = dose_target_eot, |
| 861 | 13x |
y = 0, |
| 862 | 13x |
hjust = -0.1, |
| 863 | 13x |
vjust = -25, |
| 864 | 13x |
size = 5, |
| 865 | 13x |
colour = "violet" |
| 866 |
) |
|
| 867 |
} |
|
| 868 | ||
| 869 | 15x |
if (h_in_range(dose_mg, range = dose_grid_range, bounds_closed = FALSE)) {
|
| 870 | 8x |
lab <- "Gstar Estimate" |
| 871 | 8x |
p <- p + |
| 872 | 8x |
geom_vline(xintercept = dose_mg, colour = "green", lwd = 1.1) + |
| 873 | 8x |
annotate( |
| 874 | 8x |
geom = "text", |
| 875 | 8x |
label = lab, |
| 876 | 8x |
x = dose_mg, |
| 877 | 8x |
y = 0, |
| 878 | 8x |
hjust = -0.1, |
| 879 | 8x |
vjust = -25, |
| 880 | 8x |
size = 5, |
| 881 | 8x |
colour = "green" |
| 882 |
) |
|
| 883 |
} |
|
| 884 | ||
| 885 | 15x |
maxdoselimit <- min(doselimit, dose_grid_range[2]) |
| 886 | ||
| 887 | 15x |
p + |
| 888 | 15x |
geom_vline(xintercept = maxdoselimit, colour = "red", lwd = 1.1) + |
| 889 | 15x |
annotate( |
| 890 | 15x |
geom = "text", |
| 891 | 15x |
label = "Max", |
| 892 | 15x |
x = maxdoselimit, |
| 893 | 15x |
y = 0, |
| 894 | 15x |
hjust = +1, |
| 895 | 15x |
vjust = -35, |
| 896 | 15x |
colour = "red" |
| 897 |
) + |
|
| 898 | 15x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
| 899 | 15x |
annotate( |
| 900 | 15x |
geom = "text", |
| 901 | 15x |
label = "Next", |
| 902 | 15x |
x = next_dose, |
| 903 | 15x |
y = 0, |
| 904 | 15x |
hjust = 0.1, |
| 905 | 15x |
vjust = -30, |
| 906 | 15x |
colour = "blue" |
| 907 |
) |
|
| 908 |
} |
| 1 |
#' Helpers for stripping expressions of `covr`-inserted trace code |
|
| 2 |
#' |
|
| 3 |
#' Workarounds to allow the package to continue to work while running `covr` |
|
| 4 |
#' with minimal changes to the package code. |
|
| 5 |
#' |
|
| 6 |
#' @details |
|
| 7 |
#' When using `covr`, the source code for package objects are modified to add |
|
| 8 |
#' callbacks for each expression to log its execution. Given an arbitrary |
|
| 9 |
#' expression, such as: |
|
| 10 |
#' |
|
| 11 |
#' expr |
|
| 12 |
#' |
|
| 13 |
#' The code will be modified before executing any package code to look like: |
|
| 14 |
#' |
|
| 15 |
#' if (TRUE) {
|
|
| 16 |
#' covr:::count("file.R:1:2:3:4:5:6:7:8")
|
|
| 17 |
#' expr |
|
| 18 |
#' } |
|
| 19 |
#' |
|
| 20 |
#' These functions are used for stripping expressions of this code so that the |
|
| 21 |
#' package continues to work as intended while running tests as part of running |
|
| 22 |
#' `covr` to calculate package coverage. |
|
| 23 |
#' |
|
| 24 |
#' This method is non-exhaustive, covering only a subset of `covr`'s tracing |
|
| 25 |
#' behaviors necessary for this package. |
|
| 26 |
#' |
|
| 27 |
#' @param expr (`language`)\cr an R expression or call to test or strip of |
|
| 28 |
#' `covr` trace counters. |
|
| 29 |
#' |
|
| 30 |
#' @return A logical value or transformed expression with calls to |
|
| 31 |
#' `covr:::count` removed. |
|
| 32 |
#' |
|
| 33 |
#' @name h_covr_helpers |
|
| 34 |
#' @keywords internal |
|
| 35 |
#' |
|
| 36 |
NULL |
|
| 37 | ||
| 38 |
#' @describeIn h_covr_helpers |
|
| 39 |
#' Determine whether `covr` is currently running |
|
| 40 |
h_covr_active <- function() {
|
|
| 41 | 68769x |
identical(Sys.getenv("R_COVR"), "true")
|
| 42 |
} |
|
| 43 | ||
| 44 |
#' @describeIn h_covr_helpers |
|
| 45 |
#' Remove `covr` traces from an expression |
|
| 46 |
h_covr_detrace <- function(expr) {
|
|
| 47 | 67452x |
if (!h_covr_active()) {
|
| 48 | 2x |
return(expr) |
| 49 |
} |
|
| 50 | ||
| 51 | 67450x |
if (is.function(expr)) {
|
| 52 | 9x |
body(expr) <- h_covr_detrace(body(expr)) |
| 53 | 9x |
return(expr) |
| 54 |
} |
|
| 55 | ||
| 56 | 67441x |
detrace <- function(x) {
|
| 57 |
# returns "missing" expression to avoid errors with calls |
|
| 58 | 67441x |
if (identical(x, bquote())) {
|
| 59 | 345x |
return(x) |
| 60 |
} |
|
| 61 | ||
| 62 | 67096x |
x <- h_covr_detrace_call(x) |
| 63 | 67096x |
if (is.call(x)) {
|
| 64 | 33082x |
x[-1] <- lapply(x[-1], h_covr_detrace) |
| 65 |
} |
|
| 66 | 67096x |
x |
| 67 |
} |
|
| 68 | ||
| 69 | 67441x |
detrace(expr) |
| 70 |
} |
|
| 71 | ||
| 72 |
#' @describeIn h_covr_helpers |
|
| 73 |
#' Determine whether the current expression is a `covr`-modified expression |
|
| 74 |
h_is_covr_trace <- function(expr) {
|
|
| 75 |
# Matches `if (TRUE) { covr:::count(<trace>); <expr> }` (see covr:::trace_calls).
|
|
| 76 | 67101x |
is.call(expr) && |
| 77 | 67101x |
expr[[1]] == "if" && |
| 78 | 67101x |
expr[[2]] == quote(TRUE) && |
| 79 | 67101x |
expr[[3]][[1]] == "{" &&
|
| 80 | 67101x |
length(expr[[3]]) >= 3 && |
| 81 | 67101x |
is.call(expr[[3]][[2]]) && |
| 82 | 67101x |
expr[[3]][[2]][[1]] == call(":::", as.symbol("covr"), as.symbol("count"))
|
| 83 |
} |
|
| 84 | ||
| 85 |
#' @describeIn h_covr_helpers |
|
| 86 |
#' Extract the original expression from a `covr`-modified expression |
|
| 87 |
h_covr_detrace_call <- function(expr) {
|
|
| 88 | 4651x |
if (h_is_covr_trace(expr)) expr[[3]][[3]] else expr |
| 89 |
} |
| 1 |
# Integration with knitr ---- |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' We provide additional utility functions to allow human-friendly rendition of |
|
| 6 |
#' crmPack objects in Markdown and Quarto files |
|
| 7 |
#' |
|
| 8 |
#' @return a character string that represents the object in markdown. |
|
| 9 |
#' @name knit_print |
|
| 10 |
NULL |
|
| 11 | ||
| 12 |
# CohortSize ---- |
|
| 13 | ||
| 14 |
#' Render a `CohortSizeConst` Object |
|
| 15 |
#' |
|
| 16 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 17 |
#' @param x (`CohortSize`)\cr The object to knit_print. |
|
| 18 |
#' @param asis (`flag`)\cr Should the return value be wrapped in a call to `knitr::asis_output`? |
|
| 19 |
#' @param label (`character`)\cr The word used to label the participants. See Usage Notes below. |
|
| 20 |
#' @param ... Not used at present |
|
| 21 |
#' |
|
| 22 |
#' @section Usage Notes: |
|
| 23 |
#' `label` describes the trial's participants. |
|
| 24 |
#' |
|
| 25 |
#' It should be a character vector of length 1 or 2. If of length 2, the first |
|
| 26 |
#' element describes a `cohort_size` of 1 and the second describes all other |
|
| 27 |
#' `cohort_size`s. If of length 1, the character `s` is appended to the value |
|
| 28 |
#' when `cohort_size` is not 1. |
|
| 29 |
#' @return The markdown representation of the object, as a character string |
|
| 30 |
#' @seealso [`knit_print`] for more details. |
|
| 31 |
#' |
|
| 32 |
#' @export |
|
| 33 |
#' @method knit_print CohortSizeConst |
|
| 34 |
#' @rdname knit_print |
|
| 35 |
knit_print.CohortSizeConst <- function( |
|
| 36 |
x, |
|
| 37 |
..., |
|
| 38 |
asis = TRUE, |
|
| 39 |
label = c("participant", "participants")
|
|
| 40 |
) {
|
|
| 41 | 57x |
assert_flag(asis) |
| 42 | ||
| 43 | 55x |
label <- h_prepare_labels(label) |
| 44 | 55x |
rv <- paste0( |
| 45 | 55x |
"A constant size of ", |
| 46 | 55x |
x@size, |
| 47 |
" ", |
|
| 48 | 55x |
label[ifelse(x@size == 1, 1, 2)], |
| 49 | 55x |
".\n\n" |
| 50 |
) |
|
| 51 | 55x |
if (asis) {
|
| 52 | 5x |
rv <- knitr::asis_output(rv) |
| 53 |
} |
|
| 54 | 55x |
rv |
| 55 |
} |
|
| 56 | ||
| 57 |
#' Render a `CohortSizeRange` Object |
|
| 58 |
#' |
|
| 59 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 60 |
#' @param ... passed to `knitr::kable` |
|
| 61 |
#' @inherit knit_print.CohortSizeConst return |
|
| 62 |
#' @section Usage Notes: |
|
| 63 |
#' The default value of `col.names` is `c("Lower", "Upper", "Cohort size")` and
|
|
| 64 |
#' that of `caption` is `"Defined by the dose to be used in the next cohort"`. |
|
| 65 |
#' These values can be overridden by passing `col.names` and `caption` in the |
|
| 66 |
#' function call. |
|
| 67 |
#' @export |
|
| 68 |
#' @method knit_print CohortSizeRange |
|
| 69 |
#' @rdname knit_print |
|
| 70 |
knit_print.CohortSizeRange <- function(x, ..., asis = TRUE) {
|
|
| 71 | 40x |
assert_flag(asis) |
| 72 | ||
| 73 | 38x |
param <- list(...) |
| 74 | 38x |
if (!("col.names" %in% names(param))) {
|
| 75 | 38x |
param[["col.names"]] <- c("Lower", "Upper", "Cohort size")
|
| 76 |
} |
|
| 77 | 38x |
if (!("caption" %in% names(param))) {
|
| 78 | 38x |
param[["caption"]] <- "Defined by the dose to be used in the next cohort" |
| 79 |
} |
|
| 80 | 38x |
x <- tidy(x) |
| 81 | 38x |
param[["x"]] <- x |
| 82 | 38x |
rv <- kableExtra::add_header_above( |
| 83 | 38x |
do.call(knitr::kable, param), |
| 84 | 38x |
c("Dose" = 2, " " = 1)
|
| 85 |
) |
|
| 86 | 38x |
rv <- paste0(rv, "\n\n") |
| 87 | ||
| 88 | 38x |
if (asis) {
|
| 89 | 8x |
rv <- knitr::asis_output(rv) |
| 90 |
} |
|
| 91 | 38x |
rv |
| 92 |
} |
|
| 93 | ||
| 94 |
#' Render a `CohortSizeDLT` Object |
|
| 95 |
#' |
|
| 96 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 97 |
#' @inherit knit_print.CohortSizeConst return |
|
| 98 |
#' @param ... Passed to [knitr::kable()]. |
|
| 99 |
#' |
|
| 100 |
#' @section Usage Notes: |
|
| 101 |
#' The by default, the columns are labelled `Lower`, `Upper` and `Cohort size`. |
|
| 102 |
#' The table's caption is `Defined by the number of <tox_label[2]> so far observed`. |
|
| 103 |
#' These values can be overridden by passing `col.names` and `caption` in the |
|
| 104 |
#' function call. |
|
| 105 |
#' |
|
| 106 |
#' @export |
|
| 107 |
#' @method knit_print CohortSizeDLT |
|
| 108 |
#' @rdname knit_print |
|
| 109 |
knit_print.CohortSizeDLT <- function( |
|
| 110 |
x, |
|
| 111 |
..., |
|
| 112 |
tox_label = "toxicity", |
|
| 113 |
asis = TRUE |
|
| 114 |
) {
|
|
| 115 | 36x |
assert_flag(asis) |
| 116 | 34x |
param <- list(...) |
| 117 | 34x |
tox_label <- h_prepare_labels(tox_label) |
| 118 | ||
| 119 | 34x |
if (!("col.names" %in% names(param))) {
|
| 120 | 34x |
param[["col.names"]] <- c("Lower", "Upper", "Cohort size")
|
| 121 |
} |
|
| 122 | 34x |
if (!("caption" %in% names(param))) {
|
| 123 | 34x |
param[["caption"]] <- paste0( |
| 124 | 34x |
"Defined by the number of ", |
| 125 | 34x |
tox_label[2], |
| 126 | 34x |
" so far observed" |
| 127 |
) |
|
| 128 |
} |
|
| 129 | 34x |
param[["x"]] <- tidy(x) |
| 130 | 34x |
headers <- c(2, 1) |
| 131 | 34x |
names(headers) <- c(paste0("No of ", tox_label[2]), " ")
|
| 132 | 34x |
rv <- kableExtra::add_header_above( |
| 133 | 34x |
do.call(knitr::kable, param), |
| 134 | 34x |
headers |
| 135 |
) |
|
| 136 | 34x |
rv <- paste0(rv, "\n\n") |
| 137 | ||
| 138 | 34x |
if (asis) {
|
| 139 | 6x |
rv <- knitr::asis_output(rv) |
| 140 |
} |
|
| 141 | 34x |
rv |
| 142 |
} |
|
| 143 | ||
| 144 |
#' Render a `CohortSizeParts` Object |
|
| 145 |
#' |
|
| 146 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 147 |
#' @inherit knit_print.CohortSizeConst return |
|
| 148 |
#' @inheritSection knit_print.CohortSizeConst Usage Notes |
|
| 149 |
#' |
|
| 150 |
#' @export |
|
| 151 |
#' @method knit_print CohortSizeParts |
|
| 152 |
#' @rdname knit_print |
|
| 153 |
knit_print.CohortSizeParts <- function( |
|
| 154 |
x, |
|
| 155 |
..., |
|
| 156 |
asis = TRUE, |
|
| 157 |
label = c("participant", "participants")
|
|
| 158 |
) {
|
|
| 159 | 10x |
assert_flag(asis) |
| 160 | ||
| 161 | 8x |
label <- h_prepare_labels(label) |
| 162 | 8x |
rv <- paste0( |
| 163 | 8x |
"A size of ", |
| 164 | 8x |
x@cohort_sizes[1], |
| 165 |
" ", |
|
| 166 | 8x |
label[ifelse(x@cohort_sizes[1] == 1, 1, 2)], |
| 167 | 8x |
" in the first part and ", |
| 168 | 8x |
x@cohort_sizes[2], |
| 169 |
" ", |
|
| 170 | 8x |
label[ifelse(x@cohort_sizes[2] == 1, 1, 2)], |
| 171 | 8x |
" in the second.\n\n" |
| 172 |
) |
|
| 173 | 8x |
if (asis) {
|
| 174 | 5x |
rv <- knitr::asis_output(rv) |
| 175 |
} |
|
| 176 | 8x |
rv |
| 177 |
} |
|
| 178 | ||
| 179 |
#' Render a `CohortSizeMax` Object |
|
| 180 |
#' |
|
| 181 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 182 |
#' @inherit knit_print.CohortSizeConst return |
|
| 183 |
#' @param ... passed through to the `knit_print` methods of the constituent |
|
| 184 |
#' rules |
|
| 185 |
#' |
|
| 186 |
#' @export |
|
| 187 |
#' @method knit_print CohortSizeMax |
|
| 188 |
#' @rdname knit_print |
|
| 189 |
knit_print.CohortSizeMax <- function(x, ..., asis = TRUE) {
|
|
| 190 | 28x |
assert_flag(asis) |
| 191 | ||
| 192 | 26x |
params <- list(...) |
| 193 | 26x |
params[["asis"]] <- asis |
| 194 | 26x |
rv <- paste0( |
| 195 | 26x |
"The maximum of the cohort sizes defined in the following rules:", |
| 196 | 26x |
paste0( |
| 197 | 26x |
lapply( |
| 198 | 26x |
x@cohort_sizes, |
| 199 | 26x |
function(x) {
|
| 200 | 52x |
knit_print(x, ..., asis = asis) |
| 201 |
} |
|
| 202 |
), |
|
| 203 | 26x |
collapse = "\n" |
| 204 |
), |
|
| 205 | 26x |
"\n\n", |
| 206 | 26x |
paste = "\n" |
| 207 |
) |
|
| 208 | ||
| 209 | 26x |
if (asis) {
|
| 210 | 2x |
rv <- knitr::asis_output(rv) |
| 211 |
} |
|
| 212 | 26x |
rv |
| 213 |
} |
|
| 214 | ||
| 215 |
#' Render a `CohortSizeMin` Object |
|
| 216 |
#' |
|
| 217 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 218 |
#' @inherit knit_print.CohortSizeConst return |
|
| 219 |
#' @param ... passed through to the `knit_print` methods of the constituent |
|
| 220 |
#' rules |
|
| 221 |
#' |
|
| 222 |
#' @export |
|
| 223 |
#' @method knit_print CohortSizeMin |
|
| 224 |
#' @rdname knit_print |
|
| 225 |
knit_print.CohortSizeMin <- function(x, ..., asis = TRUE) {
|
|
| 226 | 6x |
assert_flag(asis) |
| 227 | 4x |
rv <- paste0( |
| 228 | 4x |
"The minimum of the cohort sizes defined in the following rules:", |
| 229 | 4x |
paste0( |
| 230 | 4x |
lapply( |
| 231 | 4x |
x@cohort_sizes, |
| 232 | 4x |
function(x, ...) {
|
| 233 | 8x |
knit_print(x, asis = asis, ...) |
| 234 |
} |
|
| 235 |
), |
|
| 236 | 4x |
collapse = "\n" |
| 237 |
), |
|
| 238 | 4x |
"\n\n", |
| 239 | 4x |
sep = "\n" |
| 240 |
) |
|
| 241 | 4x |
if (asis) {
|
| 242 | 2x |
rv <- knitr::asis_output(rv) |
| 243 |
} |
|
| 244 | 4x |
rv |
| 245 |
} |
|
| 246 | ||
| 247 |
#' Render a `CohortSizeOrdinal` Object |
|
| 248 |
#' |
|
| 249 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 250 |
#' @inherit knit_print.CohortSizeConst return |
|
| 251 |
#' @param ... passed through to the `knit_print` method of the standard rule |
|
| 252 |
#' |
|
| 253 |
#' @export |
|
| 254 |
#' @method knit_print CohortSizeOrdinal |
|
| 255 |
#' @rdname knit_print |
|
| 256 |
knit_print.CohortSizeOrdinal <- function( |
|
| 257 |
x, |
|
| 258 |
..., |
|
| 259 |
tox_label = "toxicity", |
|
| 260 |
asis = TRUE |
|
| 261 |
) {
|
|
| 262 | 21x |
assert_flag(asis) |
| 263 | 19x |
tox_label <- h_prepare_labels(tox_label) |
| 264 | ||
| 265 | 19x |
rv <- paste0( |
| 266 | 19x |
"Based on a ", |
| 267 | 19x |
tox_label[1], |
| 268 | 19x |
" grade of ", |
| 269 | 19x |
x@grade, |
| 270 |
": ", |
|
| 271 | 19x |
paste0(knit_print(x@rule, asis = asis, ...), collapse = "\n"), |
| 272 | 19x |
"\n\n", |
| 273 | 19x |
sep = "\n" |
| 274 |
) |
|
| 275 | ||
| 276 | 19x |
if (asis) {
|
| 277 | 2x |
rv <- knitr::asis_output(rv) |
| 278 |
} |
|
| 279 | 19x |
rv |
| 280 |
} |
| 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 [`NextBestInfTheory`] object |
|
| 144 |
#' contains valid `target` and `asymmetry` objects. |
|
| 145 |
v_next_best_inf_theory <- function(object) {
|
|
| 146 | 9x |
v <- Validate() |
| 147 | 9x |
v$check( |
| 148 | 9x |
test_probability(object@target, bounds_closed = FALSE), |
| 149 | 9x |
"target must be a probability value from (0, 1) interval" |
| 150 |
) |
|
| 151 | 9x |
v$check( |
| 152 | 9x |
test_number(object@asymmetry, finite = TRUE) && |
| 153 | 9x |
h_in_range(object@asymmetry, c(0, 2), FALSE), |
| 154 | 9x |
"asymmetry must be a number from (0, 2) interval" |
| 155 |
) |
|
| 156 | 9x |
v$result() |
| 157 |
} |
|
| 158 | ||
| 159 |
#' @describeIn v_next_best validates that the [`NextBestTD`] object |
|
| 160 |
#' contains valid `prob_target_drt` and `prob_target_eot` probabilities. |
|
| 161 |
v_next_best_td <- function(object) {
|
|
| 162 | 9x |
v <- Validate() |
| 163 | 9x |
v$check( |
| 164 | 9x |
test_probability(object@prob_target_drt, bounds_closed = FALSE), |
| 165 | 9x |
"prob_target_drt must be a probability value from (0, 1) interval" |
| 166 |
) |
|
| 167 | 9x |
v$check( |
| 168 | 9x |
test_probability(object@prob_target_eot, bounds_closed = FALSE), |
| 169 | 9x |
"prob_target_eot must be a probability value from (0, 1) interval" |
| 170 |
) |
|
| 171 | 9x |
v$result() |
| 172 |
} |
|
| 173 | ||
| 174 |
#' @describeIn v_next_best validates that the [`NextBestTDsamples`] object |
|
| 175 |
#' contains valid `derive` function. |
|
| 176 |
v_next_best_td_samples <- function(object) {
|
|
| 177 | 3x |
v <- Validate() |
| 178 | 3x |
v$check( |
| 179 | 3x |
test_function(object@derive, nargs = 1), |
| 180 | 3x |
"derive must have a single argument" |
| 181 |
) |
|
| 182 | 3x |
v$check( |
| 183 | 3x |
test_number(object@derive(1:5)), |
| 184 | 3x |
"derive must accept numerical vector as an argument and return a number" |
| 185 |
) |
|
| 186 | 3x |
v$result() |
| 187 |
} |
|
| 188 | ||
| 189 |
#' @describeIn v_next_best validates that the [`NextBestMaxGainSamples`] object |
|
| 190 |
#' contains valid `derive` and `mg_derive` functions. |
|
| 191 |
v_next_best_max_gain_samples <- function(object) {
|
|
| 192 | 5x |
v <- Validate() |
| 193 | 5x |
v$check( |
| 194 | 5x |
test_function(object@derive, nargs = 1), |
| 195 | 5x |
"derive must have a single argument" |
| 196 |
) |
|
| 197 | 5x |
v$check( |
| 198 | 5x |
test_number(object@derive(1:5)), |
| 199 | 5x |
"derive must accept numerical vector as an argument and return a number" |
| 200 |
) |
|
| 201 | 5x |
v$check( |
| 202 | 5x |
test_function(object@mg_derive, nargs = 1), |
| 203 | 5x |
"mg_derive must have a single argument" |
| 204 |
) |
|
| 205 | 5x |
v$check( |
| 206 | 5x |
test_number(object@mg_derive(1:5)), |
| 207 | 5x |
"mg_derive must accept numerical vector as an argument and return a number" |
| 208 |
) |
|
| 209 | 5x |
v$result() |
| 210 |
} |
|
| 211 | ||
| 212 |
#' @describeIn v_next_best validates that the [`NextBestProbMTDLTE`] object |
|
| 213 |
#' contains valid `target` probability and `method` string value. |
|
| 214 |
v_next_best_prob_mtd_lte <- function(object) {
|
|
| 215 | 5x |
v <- Validate() |
| 216 | 5x |
v$check( |
| 217 | 5x |
test_probability(object@target, bounds_closed = FALSE), |
| 218 | 5x |
"target must be a probability value from (0, 1) interval" |
| 219 |
) |
|
| 220 | 5x |
v$result() |
| 221 |
} |
|
| 222 | ||
| 223 |
#' @describeIn v_next_best validates that the [`NextBestProbMTDMinDist`] object |
|
| 224 |
#' contains valid `target` probability and `method` string value. |
|
| 225 |
v_next_best_prob_mtd_min_dist <- function(object) {
|
|
| 226 | 5x |
v <- Validate() |
| 227 | 5x |
v$check( |
| 228 | 5x |
test_probability(object@target, bounds_closed = FALSE), |
| 229 | 5x |
"target must be a probability value from (0, 1) interval" |
| 230 |
) |
|
| 231 | 5x |
v$result() |
| 232 |
} |
|
| 233 | ||
| 234 |
# Increments ---- |
|
| 235 | ||
| 236 |
#' Internal Helper Functions for Validation of [`Increments`] Objects |
|
| 237 |
#' |
|
| 238 |
#' @description `r lifecycle::badge("stable")`
|
|
| 239 |
#' |
|
| 240 |
#' These functions are only used internally to validate the format of an input |
|
| 241 |
#' [`Increments`] or inherited classes and therefore not exported. |
|
| 242 |
#' |
|
| 243 |
#' @name v_increments |
|
| 244 |
#' @param object (`Increments`)\cr object to validate. |
|
| 245 |
#' @return A `character` vector with the validation failure messages, |
|
| 246 |
#' or `TRUE` in case validation passes. |
|
| 247 |
NULL |
|
| 248 | ||
| 249 |
#' @describeIn v_increments validates that the [`IncrementsRelative`] object |
|
| 250 |
#' contains valid `intervals` and `increments` parameters. |
|
| 251 |
v_increments_relative <- function(object) {
|
|
| 252 | 6x |
v <- Validate() |
| 253 | 6x |
v$check( |
| 254 | 6x |
test_numeric( |
| 255 | 6x |
object@intervals, |
| 256 | 6x |
lower = 0, |
| 257 | 6x |
finite = TRUE, |
| 258 | 6x |
any.missing = FALSE, |
| 259 | 6x |
unique = TRUE, |
| 260 | 6x |
sorted = TRUE |
| 261 |
), |
|
| 262 | 6x |
"intervals has to be a numerical vector with unique, finite, non-negative and sorted non-missing values" |
| 263 |
) |
|
| 264 | 6x |
v$check( |
| 265 | 6x |
test_numeric( |
| 266 | 6x |
object@increments, |
| 267 | 6x |
finite = TRUE, |
| 268 | 6x |
any.missing = FALSE, |
| 269 | 6x |
len = length(object@intervals) |
| 270 |
), |
|
| 271 | 6x |
"increments has to be a numerical vector of the same length as `intervals` with finite values" |
| 272 |
) |
|
| 273 | 6x |
v$result() |
| 274 |
} |
|
| 275 | ||
| 276 |
#' @describeIn v_increments validates that the [`IncrementsRelativeParts`] object |
|
| 277 |
#' contains valid `dlt_start` and `clean_start` parameters. |
|
| 278 |
v_increments_relative_parts <- function(object) {
|
|
| 279 | 6x |
v <- Validate() |
| 280 | 6x |
is_dlt_start_ok <- test_int(object@dlt_start) |
| 281 | 6x |
v$check(is_dlt_start_ok, "dlt_start must be an integer number") |
| 282 | 6x |
if (is_dlt_start_ok) {
|
| 283 | 4x |
v$check( |
| 284 | 4x |
test_int(object@clean_start, lower = object@dlt_start), |
| 285 | 4x |
"clean_start must be an integer number and it must be >= dlt_start" |
| 286 |
) |
|
| 287 |
} |
|
| 288 | 6x |
v$result() |
| 289 |
} |
|
| 290 | ||
| 291 |
#' @describeIn v_increments validates that the [`IncrementsRelativeDLT`] object |
|
| 292 |
#' contains valid `intervals` and `increments` parameters. |
|
| 293 |
v_increments_relative_dlt <- function(object) {
|
|
| 294 | 6x |
v <- Validate() |
| 295 | 6x |
v$check( |
| 296 | 6x |
test_integer( |
| 297 | 6x |
object@intervals, |
| 298 | 6x |
lower = 0, |
| 299 | 6x |
any.missing = FALSE, |
| 300 | 6x |
unique = TRUE, |
| 301 | 6x |
sorted = TRUE |
| 302 |
), |
|
| 303 | 6x |
"intervals has to be an integer vector with unique, finite, non-negative and sorted non-missing values" |
| 304 |
) |
|
| 305 | 6x |
v$check( |
| 306 | 6x |
test_numeric( |
| 307 | 6x |
object@increments, |
| 308 | 6x |
finite = TRUE, |
| 309 | 6x |
any.missing = FALSE, |
| 310 | 6x |
len = length(object@intervals) |
| 311 |
), |
|
| 312 | 6x |
"increments has to be a numerical vector of the same length as `intervals` with finite values" |
| 313 |
) |
|
| 314 | 6x |
v$result() |
| 315 |
} |
|
| 316 | ||
| 317 |
#' @describeIn v_increments validates that the [`IncrementsDoseLevels`] object |
|
| 318 |
#' contains valid `levels` and `basis_level` option. |
|
| 319 |
v_increments_dose_levels <- function(object) {
|
|
| 320 | 9x |
v <- Validate() |
| 321 | 9x |
v$check( |
| 322 | 9x |
test_int(object@levels, lower = .Machine$double.xmin), |
| 323 | 9x |
"levels must be scalar positive integer" |
| 324 |
) |
|
| 325 | 9x |
v$check( |
| 326 | 9x |
test_string(object@basis_level, pattern = "^last$|^max$"), |
| 327 | 9x |
"basis_level must be either 'last' or 'max'" |
| 328 |
) |
|
| 329 | 9x |
v$result() |
| 330 |
} |
|
| 331 | ||
| 332 |
#' @describeIn v_increments validates that the [`IncrementsHSRBeta`] |
|
| 333 |
#' object contains valid probability target, threshold and shape parameters. |
|
| 334 |
v_increments_hsr_beta <- function(object) {
|
|
| 335 | 12x |
v <- Validate() |
| 336 | 12x |
v$check( |
| 337 | 12x |
test_probability(object@target, bounds_closed = FALSE), |
| 338 | 12x |
"target must be a probability value from (0, 1) interval" |
| 339 |
) |
|
| 340 | 12x |
v$check( |
| 341 | 12x |
test_probability(object@prob, bounds_closed = FALSE), |
| 342 | 12x |
"prob must be a probability value from (0, 1) interval" |
| 343 |
) |
|
| 344 | 12x |
v$check( |
| 345 | 12x |
test_number(object@a, lower = .Machine$double.xmin, finite = TRUE), |
| 346 | 12x |
"Beta distribution shape parameter a must be a positive scalar" |
| 347 |
) |
|
| 348 | 12x |
v$check( |
| 349 | 12x |
test_number(object@b, lower = .Machine$double.xmin, finite = TRUE), |
| 350 | 12x |
"Beta distribution shape parameter b must be a positive scalar" |
| 351 |
) |
|
| 352 | 12x |
v$result() |
| 353 |
} |
|
| 354 | ||
| 355 |
#' @describeIn v_increments validates that the [`IncrementsMin`] |
|
| 356 |
#' object contains a list with `Increments` objects. |
|
| 357 |
v_increments_min <- function(object) {
|
|
| 358 | 2x |
v <- Validate() |
| 359 | 2x |
v$check( |
| 360 | 2x |
all(sapply(object@increments_list, test_class, "Increments")), |
| 361 | 2x |
"all elements in increments_list must be of Increments class" |
| 362 |
) |
|
| 363 | 2x |
v$result() |
| 364 |
} |
|
| 365 | ||
| 366 |
#' @describeIn v_increments validates the [`IncrementsMaxToxProb`] |
|
| 367 |
v_increments_maxtoxprob <- function(object) {
|
|
| 368 | ! |
v <- Validate() |
| 369 | ! |
v$check( |
| 370 | ! |
test_probabilities(object@prob), |
| 371 | ! |
"prob must be a vector of probabilities with minimum length 1 and no missing values" |
| 372 |
) |
|
| 373 | ! |
v$result() |
| 374 |
} |
|
| 375 | ||
| 376 |
# Stopping ---- |
|
| 377 | ||
| 378 |
#' Internal Helper Functions for Validation of [`Stopping`] Objects |
|
| 379 |
#' |
|
| 380 |
#' @description `r lifecycle::badge("stable")`
|
|
| 381 |
#' |
|
| 382 |
#' These functions are only used internally to validate the format of an input |
|
| 383 |
#' [`Stopping`] or inherited classes and therefore not exported. |
|
| 384 |
#' |
|
| 385 |
#' @name v_stopping |
|
| 386 |
#' @param object (`Stopping`)\cr object to validate. |
|
| 387 |
#' @return A `character` vector with the validation failure messages, |
|
| 388 |
#' or `TRUE` in case validation passes. |
|
| 389 |
NULL |
|
| 390 | ||
| 391 |
#' @describeIn v_stopping validates that the [`StoppingCohortsNearDose`] |
|
| 392 |
#' object contains valid `nCohorts` and `percentage` parameters. |
|
| 393 |
v_stopping_cohorts_near_dose <- function(object) {
|
|
| 394 | 11x |
v <- Validate() |
| 395 | 11x |
v$check( |
| 396 | 11x |
test_int(object@nCohorts, lower = .Machine$double.xmin), |
| 397 | 11x |
"nCohorts must be positive integer scalar" |
| 398 |
) |
|
| 399 | 11x |
v$check( |
| 400 | 11x |
test_probability(object@percentage / 100), |
| 401 | 11x |
"percentage must be a number between 0 and 100" |
| 402 |
) |
|
| 403 | 11x |
v$result() |
| 404 |
} |
|
| 405 | ||
| 406 |
#' @describeIn v_stopping validates that the [`StoppingPatientsNearDose`] |
|
| 407 |
#' object contains valid `nPatients` and `percentage` parameters. |
|
| 408 |
v_stopping_patients_near_dose <- function(object) {
|
|
| 409 | 11x |
v <- Validate() |
| 410 | 11x |
v$check( |
| 411 | 11x |
test_int(object@nPatients, lower = .Machine$double.xmin), |
| 412 | 11x |
"nPatients must be positive integer scalar" |
| 413 |
) |
|
| 414 | 11x |
v$check( |
| 415 | 11x |
test_probability(object@percentage / 100), |
| 416 | 11x |
"percentage must be a number between 0 and 100" |
| 417 |
) |
|
| 418 | 11x |
v$result() |
| 419 |
} |
|
| 420 | ||
| 421 |
#' @describeIn v_stopping validates that the [`StoppingMinCohorts`] |
|
| 422 |
#' object contains valid `nCohorts` parameter. |
|
| 423 |
v_stopping_min_cohorts <- function(object) {
|
|
| 424 | 4x |
v <- Validate() |
| 425 | 4x |
v$check( |
| 426 | 4x |
test_int(object@nCohorts, lower = .Machine$double.xmin), |
| 427 | 4x |
"nCohorts must be positive integer scalar" |
| 428 |
) |
|
| 429 | 4x |
v$result() |
| 430 |
} |
|
| 431 | ||
| 432 |
#' @describeIn v_stopping validates that the [`StoppingMinPatients`] |
|
| 433 |
#' object contains valid `nPatients` parameter. |
|
| 434 |
v_stopping_min_patients <- function(object) {
|
|
| 435 | 4x |
v <- Validate() |
| 436 | 4x |
v$check( |
| 437 | 4x |
test_int(object@nPatients, lower = .Machine$double.xmin), |
| 438 | 4x |
"nPatients must be positive integer scalar" |
| 439 |
) |
|
| 440 | 4x |
v$result() |
| 441 |
} |
|
| 442 | ||
| 443 |
#' @describeIn v_stopping validates that the [`StoppingTargetProb`] |
|
| 444 |
#' object contains valid `target` and `prob` parameters. |
|
| 445 |
v_stopping_target_prob <- function(object) {
|
|
| 446 | 10x |
v <- Validate() |
| 447 | 10x |
v$check( |
| 448 | 10x |
test_probability_range(object@target), |
| 449 | 10x |
"target has to be a probability range" |
| 450 |
) |
|
| 451 | 10x |
v$check( |
| 452 | 10x |
test_probability(object@prob, bounds_closed = FALSE), |
| 453 | 10x |
"prob must be a probability value from (0, 1) interval" |
| 454 |
) |
|
| 455 | 10x |
v$result() |
| 456 |
} |
|
| 457 | ||
| 458 |
#' @describeIn v_stopping validates that the [`StoppingMTDdistribution`] |
|
| 459 |
#' object contains valid `target`, `thresh` and `prob` parameters. |
|
| 460 |
v_stopping_mtd_distribution <- function(object) {
|
|
| 461 | 13x |
v <- Validate() |
| 462 | 13x |
v$check( |
| 463 | 13x |
test_probability(object@target, bounds_closed = FALSE), |
| 464 | 13x |
"target must be a probability value from (0, 1) interval" |
| 465 |
) |
|
| 466 | 13x |
v$check( |
| 467 | 13x |
test_probability(object@thresh, bounds_closed = FALSE), |
| 468 | 13x |
"thresh must be a probability value from (0, 1) interval" |
| 469 |
) |
|
| 470 | 13x |
v$check( |
| 471 | 13x |
test_probability(object@prob, bounds_closed = FALSE), |
| 472 | 13x |
"prob must be a probability value from (0, 1) interval" |
| 473 |
) |
|
| 474 | 13x |
v$result() |
| 475 |
} |
|
| 476 | ||
| 477 |
#' @describeIn v_stopping validates that the [`StoppingMTDCV`] object |
|
| 478 |
#' contains valid probability target and percentage threshold. |
|
| 479 |
v_stopping_mtd_cv <- function(object) {
|
|
| 480 | 11x |
v <- Validate() |
| 481 | 11x |
v$check( |
| 482 | 11x |
test_probability(object@target, bounds_closed = FALSE), |
| 483 | 11x |
"target must be probability value from (0, 1) interval" |
| 484 |
) |
|
| 485 | 11x |
v$check( |
| 486 | 11x |
test_probability(object@thresh_cv / 100, bounds_closed = c(FALSE, TRUE)), |
| 487 | 11x |
"thresh_cv must be percentage > 0" |
| 488 |
) |
|
| 489 | 11x |
v$result() |
| 490 |
} |
|
| 491 | ||
| 492 |
#' @describeIn v_stopping validates that the [`StoppingTargetBiomarker`] object |
|
| 493 |
#' contains valid `target`, `is_relative` and `prob`slots. |
|
| 494 |
v_stopping_target_biomarker <- function(object) {
|
|
| 495 | 16x |
v <- Validate() |
| 496 | 16x |
v$check( |
| 497 | 16x |
test_flag(object@is_relative), |
| 498 | 16x |
"is_relative must be a flag" |
| 499 |
) |
|
| 500 | 16x |
if (isTRUE(object@is_relative)) {
|
| 501 | 10x |
v$check( |
| 502 | 10x |
test_probability_range(object@target), |
| 503 | 10x |
"target has to be a probability range when is_relative flag is 'TRUE'" |
| 504 |
) |
|
| 505 |
} else {
|
|
| 506 | 6x |
v$check( |
| 507 | 6x |
test_range(object@target, finite = TRUE), |
| 508 | 6x |
"target must be a numeric range" |
| 509 |
) |
|
| 510 |
} |
|
| 511 | 16x |
v$check( |
| 512 | 16x |
test_probability(object@prob, bounds_closed = FALSE), |
| 513 | 16x |
"prob must be a probability value from (0, 1) interval" |
| 514 |
) |
|
| 515 | 16x |
v$result() |
| 516 |
} |
|
| 517 | ||
| 518 |
#' @describeIn v_stopping validates that the [`StoppingList`] object |
|
| 519 |
#' contains valid `stop_list`, `summary` slots. |
|
| 520 |
v_stopping_list <- function(object) {
|
|
| 521 | 8x |
v <- Validate() |
| 522 | 8x |
v$check( |
| 523 | 8x |
all(sapply(object@stop_list, test_class, "Stopping")), |
| 524 | 8x |
"every stop_list element must be of class 'Stopping'" |
| 525 |
) |
|
| 526 | 8x |
is_summary_ok <- test_function(object@summary, nargs = 1) |
| 527 | 8x |
v$check( |
| 528 | 8x |
is_summary_ok, |
| 529 | 8x |
"summary must be a function that accepts a single argument, without ..." |
| 530 |
) |
|
| 531 | 8x |
if (is_summary_ok) {
|
| 532 | 5x |
summary_res <- object@summary( |
| 533 | 5x |
rep(c(TRUE, FALSE), length.out = length(object@stop_list)) |
| 534 |
) |
|
| 535 | 5x |
v$check( |
| 536 | 5x |
test_flag(summary_res), |
| 537 | 5x |
"summary must accept a logical vector of the same length as 'stop_list' and return a boolean value" |
| 538 |
) |
|
| 539 |
} |
|
| 540 | 8x |
v$result() |
| 541 |
} |
|
| 542 | ||
| 543 |
#' @describeIn v_stopping validates that the [`StoppingAll`] object |
|
| 544 |
#' contains valid `stop_list` slot. |
|
| 545 |
v_stopping_all <- function(object) {
|
|
| 546 | 3x |
v <- Validate() |
| 547 | 3x |
v$check( |
| 548 | 3x |
all(sapply(object@stop_list, test_class, "Stopping")), |
| 549 | 3x |
"every stop_list element must be of class 'Stopping'" |
| 550 |
) |
|
| 551 | 3x |
v$result() |
| 552 |
} |
|
| 553 | ||
| 554 |
#' @describeIn v_stopping validates that the [`StoppingTDCIRatio`] object |
|
| 555 |
#' contains valid `target_ratio` and `prob_target` slots. |
|
| 556 |
v_stopping_tdci_ratio <- function(object) {
|
|
| 557 | 9x |
v <- Validate() |
| 558 | 9x |
v$check( |
| 559 | 9x |
test_number( |
| 560 | 9x |
object@target_ratio, |
| 561 | 9x |
lower = .Machine$double.xmin, |
| 562 | 9x |
finite = TRUE |
| 563 |
), |
|
| 564 | 9x |
"target_ratio must be a positive number" |
| 565 |
) |
|
| 566 | 9x |
v$check( |
| 567 | 9x |
test_probability(object@prob_target), |
| 568 | 9x |
"prob_target must be a probability value from [0, 1] interval" |
| 569 |
) |
|
| 570 | 9x |
v$result() |
| 571 |
} |
|
| 572 | ||
| 573 |
# CohortSize ---- |
|
| 574 | ||
| 575 |
#' Internal Helper Functions for Validation of [`CohortSize`] Objects |
|
| 576 |
#' |
|
| 577 |
#' @description `r lifecycle::badge("stable")`
|
|
| 578 |
#' |
|
| 579 |
#' These functions are only used internally to validate the format of an input |
|
| 580 |
#' [`CohortSize`] or inherited classes and therefore not exported. |
|
| 581 |
#' |
|
| 582 |
#' @name v_cohort_size |
|
| 583 |
#' @param object (`CohortSize`)\cr object to validate. |
|
| 584 |
#' @return A `character` vector with the validation failure messages, |
|
| 585 |
#' or `TRUE` in case validation passes. |
|
| 586 |
NULL |
|
| 587 | ||
| 588 |
#' @describeIn v_cohort_size validates that the [`CohortSizeRange`] object |
|
| 589 |
#' contains valid `intervals` and `cohort_size` slots. |
|
| 590 |
v_cohort_size_range <- function(object) {
|
|
| 591 | 13x |
v <- Validate() |
| 592 | 13x |
v$check( |
| 593 | 13x |
test_numeric( |
| 594 | 13x |
object@intervals, |
| 595 | 13x |
lower = 0, |
| 596 | 13x |
finite = TRUE, |
| 597 | 13x |
any.missing = FALSE, |
| 598 | 13x |
min.len = 1, |
| 599 | 13x |
unique = TRUE, |
| 600 | 13x |
sorted = TRUE |
| 601 |
), |
|
| 602 | 13x |
"intervals must be a numeric vector with non-negative, sorted (asc.) and unique values" |
| 603 |
) |
|
| 604 | 13x |
v$check( |
| 605 | 13x |
test_integer( |
| 606 | 13x |
object@cohort_size, |
| 607 | 13x |
lower = 0, |
| 608 | 13x |
any.missing = FALSE, |
| 609 | 13x |
len = length(object@intervals) |
| 610 |
), |
|
| 611 | 13x |
"cohort_size must be an integer vector of the same length as intervals, containing non-negative values only" |
| 612 |
) |
|
| 613 | 13x |
v$result() |
| 614 |
} |
|
| 615 | ||
| 616 |
#' @describeIn v_cohort_size validates that the [`CohortSizeDLT`] object |
|
| 617 |
#' contains valid `intervals` and `cohort_size` slots. |
|
| 618 |
v_cohort_size_dlt <- function(object) {
|
|
| 619 | 12x |
v <- Validate() |
| 620 | 12x |
v$check( |
| 621 | 12x |
test_integer( |
| 622 | 12x |
object@intervals, |
| 623 | 12x |
lower = 0, |
| 624 | 12x |
any.missing = FALSE, |
| 625 | 12x |
min.len = 1, |
| 626 | 12x |
unique = TRUE, |
| 627 | 12x |
sorted = TRUE |
| 628 |
), |
|
| 629 | 12x |
"intervals must be an integer vector with non-negative, sorted (asc.) and unique values" |
| 630 |
) |
|
| 631 | 12x |
v$check( |
| 632 | 12x |
test_integer( |
| 633 | 12x |
object@cohort_size, |
| 634 | 12x |
lower = 0, |
| 635 | 12x |
any.missing = FALSE, |
| 636 | 12x |
len = length(object@intervals) |
| 637 |
), |
|
| 638 | 12x |
"cohort_size must be an integer vector of the same length as intervals, containing non-negative values only" |
| 639 |
) |
|
| 640 | 12x |
v$result() |
| 641 |
} |
|
| 642 | ||
| 643 |
#' @describeIn v_cohort_size validates that the [`CohortSizeConst`] object |
|
| 644 |
#' contains valid `size` slot. |
|
| 645 |
v_cohort_size_const <- function(object) {
|
|
| 646 | 5x |
v <- Validate() |
| 647 | 5x |
v$check( |
| 648 | 5x |
test_int(object@size, lower = 0), |
| 649 | 5x |
"size needs to be a non-negative scalar" |
| 650 |
) |
|
| 651 | 5x |
v$result() |
| 652 |
} |
|
| 653 | ||
| 654 |
#' @describeIn v_cohort_size validates that the [`CohortSizeParts`] object |
|
| 655 |
#' contains valid `sizes` slot. |
|
| 656 |
v_cohort_size_parts <- function(object) {
|
|
| 657 | 9x |
v <- Validate() |
| 658 | 9x |
v$check( |
| 659 | 9x |
test_integer( |
| 660 | 9x |
object@cohort_sizes, |
| 661 | 9x |
lower = .Machine$double.xmin, |
| 662 | 9x |
any.missing = FALSE, |
| 663 | 9x |
len = 2 |
| 664 |
), |
|
| 665 | 9x |
"cohort_sizes needs to be an integer vector of length 2 with all elements positive" |
| 666 |
) |
|
| 667 | 9x |
v$result() |
| 668 |
} |
|
| 669 | ||
| 670 |
#' @describeIn v_cohort_size validates that the [`CohortSizeMax`] object |
|
| 671 |
#' contains valid `cohort_sizes` slot. |
|
| 672 |
v_cohort_size_max <- function(object) {
|
|
| 673 | 8x |
v <- Validate() |
| 674 | 8x |
v$check( |
| 675 | 8x |
test_list( |
| 676 | 8x |
object@cohort_sizes, |
| 677 | 8x |
types = "CohortSize", |
| 678 | 8x |
any.missing = FALSE, |
| 679 | 8x |
min.len = 2, |
| 680 | 8x |
unique = TRUE |
| 681 |
), |
|
| 682 | 8x |
"cohort_sizes must be a list of CohortSize (unique) objects only and be of length >= 2" |
| 683 |
) |
|
| 684 | 8x |
v$result() |
| 685 |
} |
|
| 686 | ||
| 687 |
# SafetyWindow ---- |
|
| 688 | ||
| 689 |
#' Internal Helper Functions for Validation of [`SafetyWindow`] Objects |
|
| 690 |
#' |
|
| 691 |
#' @description `r lifecycle::badge("stable")`
|
|
| 692 |
#' |
|
| 693 |
#' These functions are only used internally to validate the format of an input |
|
| 694 |
#' [`SafetyWindow`] or inherited classes and therefore not exported. |
|
| 695 |
#' |
|
| 696 |
#' @name v_safety_window |
|
| 697 |
#' @param object (`SafetyWindow`)\cr object to validate. |
|
| 698 |
#' @return A `character` vector with the validation failure messages, |
|
| 699 |
#' or `TRUE` in case validation passes. |
|
| 700 |
NULL |
|
| 701 | ||
| 702 |
#' @describeIn v_safety_window validates that the [`SafetyWindowSize`] object |
|
| 703 |
#' contains valid slots. |
|
| 704 |
v_safety_window_size <- function(object) {
|
|
| 705 | 21x |
v <- Validate() |
| 706 | 21x |
v$check( |
| 707 | 21x |
test_list(object@gap, types = "integer", any.missing = FALSE, min.len = 1), |
| 708 | 21x |
"gap must be a list of length >= 1 with integer vectors only" |
| 709 |
) |
|
| 710 | 21x |
v$check( |
| 711 | 21x |
all(sapply( |
| 712 | 21x |
object@gap, |
| 713 | 21x |
test_integer, |
| 714 | 21x |
lower = 0, |
| 715 | 21x |
any.missing = FALSE, |
| 716 | 21x |
min.len = 1 |
| 717 |
)), |
|
| 718 | 21x |
"every element in gap list has to be an integer vector with non-negative and non-missing values" |
| 719 |
) |
|
| 720 | 21x |
pg_len <- length(object@gap) |
| 721 | 21x |
v$check( |
| 722 | 21x |
test_integer( |
| 723 | 21x |
object@size, |
| 724 | 21x |
lower = .Machine$double.xmin, |
| 725 | 21x |
any.missing = FALSE, |
| 726 | 21x |
len = pg_len, |
| 727 | 21x |
unique = TRUE, |
| 728 | 21x |
sorted = TRUE |
| 729 |
), |
|
| 730 | 21x |
"size has to be an integer vector, of the same length as gap, with positive, unique and sorted non-missing values" |
| 731 |
) |
|
| 732 | 21x |
v$check( |
| 733 | 21x |
test_int(object@follow, lower = .Machine$double.xmin), |
| 734 | 21x |
"follow has to be a positive integer number" |
| 735 |
) |
|
| 736 | 21x |
v$check( |
| 737 | 21x |
test_int(object@follow_min, lower = .Machine$double.xmin), |
| 738 | 21x |
"follow_min has to be a positive integer number" |
| 739 |
) |
|
| 740 | 21x |
v$result() |
| 741 |
} |
|
| 742 | ||
| 743 |
#' @describeIn v_safety_window validates that the [`SafetyWindowConst`] object |
|
| 744 |
#' contains valid slots. |
|
| 745 |
v_safety_window_const <- function(object) {
|
|
| 746 | 15x |
v <- Validate() |
| 747 | 15x |
v$check( |
| 748 | 15x |
test_integer(object@gap, lower = 0, any.missing = FALSE), |
| 749 | 15x |
"gap has to be an integer vector with non-negative and non-missing elements" |
| 750 |
) |
|
| 751 | 15x |
v$check( |
| 752 | 15x |
test_int(object@follow, lower = .Machine$double.xmin), |
| 753 | 15x |
"follow has to be a positive integer number" |
| 754 |
) |
|
| 755 | 15x |
v$check( |
| 756 | 15x |
test_int(object@follow_min, lower = .Machine$double.xmin), |
| 757 | 15x |
"follow_min has to be a positive integer number" |
| 758 |
) |
|
| 759 | 15x |
v$result() |
| 760 |
} |
|
| 761 | ||
| 762 |
#' @describeIn v_next_best validates that the [`NextBestOrdinal`] object |
|
| 763 |
#' contains valid `grade` and standard `NextBest` rule. |
|
| 764 |
v_next_best_ordinal <- function(object) {
|
|
| 765 | ! |
v <- Validate() |
| 766 | ! |
v$check( |
| 767 | ! |
test_integer(object@grade, lower = 1), |
| 768 | ! |
"grade must be a positive integer" |
| 769 |
) |
|
| 770 | ! |
v$check( |
| 771 | ! |
test_class(object@rule, "NextBest"), |
| 772 | ! |
"rule must be a NextBest object" |
| 773 |
) |
|
| 774 | ! |
v$result() |
| 775 |
} |
|
| 776 | ||
| 777 |
#' @describeIn v_increments validates that the [`IncrementsOrdinal`] object |
|
| 778 |
#' contains valid `grade` and standard `Increments` rule. |
|
| 779 |
v_increments_ordinal <- function(object) {
|
|
| 780 | ! |
v <- Validate() |
| 781 | ! |
v$check( |
| 782 | ! |
test_integer(object@grade, lower = 1), |
| 783 | ! |
"grade must be a positive integer" |
| 784 |
) |
|
| 785 | ! |
v$check( |
| 786 | ! |
test_class(object@rule, "Increments"), |
| 787 | ! |
"rule must be a Increments object" |
| 788 |
) |
|
| 789 | ! |
v$result() |
| 790 |
} |
|
| 791 | ||
| 792 |
#' @describeIn v_increments validates that the [`CohortSizeOrdinal`] object |
|
| 793 |
#' contains valid `grade` and standard `CohortSize` rule. |
|
| 794 |
v_cohort_size_ordinal <- function(object) {
|
|
| 795 | ! |
v <- Validate() |
| 796 | ! |
v$check( |
| 797 | ! |
test_integer(object@grade, lower = 1), |
| 798 | ! |
"grade must be a positive integer" |
| 799 |
) |
|
| 800 | ! |
v$check( |
| 801 | ! |
test_class(object@rule, "CohortSize"), |
| 802 | ! |
"rule must be a CohortSize object" |
| 803 |
) |
|
| 804 | ! |
v$result() |
| 805 |
} |
| 1 |
# Validate-class ---- |
|
| 2 | ||
| 3 |
#' `Validate` |
|
| 4 |
#' |
|
| 5 |
#' @description `r lifecycle::badge("stable")`
|
|
| 6 |
#' |
|
| 7 |
#' The [`Validate`] class is a Reference Class |
|
| 8 |
#' to help programming validation for new S4 classes. |
|
| 9 |
#' |
|
| 10 |
#' @details Starting from an empty `msg` vector, with each check |
|
| 11 |
#' that is returning `FALSE` the vector gets a new element - the string |
|
| 12 |
#' explaining the failure of the validation. |
|
| 13 |
#' |
|
| 14 |
#' @name Validate |
|
| 15 |
#' @field msg (`character`)\cr the cumulative messages. |
|
| 16 |
#' |
|
| 17 |
Validate <- setRefClass( |
|
| 18 |
Class = "Validate", |
|
| 19 |
fields = list(msg = "character"), |
|
| 20 |
methods = list( |
|
| 21 |
check = function(test, string = "") {
|
|
| 22 | 82522x |
"Check whether the \\code{test} is \\code{TRUE}; if so, return \\code{NULL}.
|
| 23 | 82522x |
Otherwise, add the \\code{string} message into the cumulative messages vector \\code{msg}."
|
| 24 | 82522x |
assert_flag(test) |
| 25 | 82522x |
assert_string(string) |
| 26 | 82522x |
if (test) {
|
| 27 | 82010x |
NULL |
| 28 |
} else {
|
|
| 29 | 512x |
msg <<- c(msg, string) |
| 30 |
} |
|
| 31 |
}, |
|
| 32 |
result = function() {
|
|
| 33 | 21130x |
"Return either cumulative messages vector \\code{msg}
|
| 34 | 21130x |
(which contains the error messages from all the checks), |
| 35 | 21130x |
or \\code{NULL}, if \\code{msg} is empty (i.e. all the checks were successful)."
|
| 36 | 21130x |
if (length(msg) > 0) {
|
| 37 | 437x |
msg |
| 38 |
} else {
|
|
| 39 | 20693x |
TRUE |
| 40 |
} |
|
| 41 |
} |
|
| 42 |
) |
|
| 43 |
) |
|
| 44 | ||
| 45 |
# positive_number-class ---- |
|
| 46 | ||
| 47 |
#' `positive_number` |
|
| 48 |
#' |
|
| 49 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 50 |
#' |
|
| 51 |
#' The [`positive_number`] class is a class to store not `NULL`, non `NA`, |
|
| 52 |
#' finite and strictly positive numerical value. It is mainly used to store |
|
| 53 |
#' reference dose value in model classes. |
|
| 54 |
#' |
|
| 55 |
#' @name positive_number |
|
| 56 |
#' |
|
| 57 |
positive_number <- setClass( |
|
| 58 |
Class = "positive_number", |
|
| 59 |
contains = "numeric", |
|
| 60 |
validity = function(object) {
|
|
| 61 |
v <- Validate() |
|
| 62 |
v$check( |
|
| 63 |
test_number(object, finite = TRUE) && object > 0, |
|
| 64 |
"Object's value must be strictly positive" |
|
| 65 |
) |
|
| 66 |
v$result() |
|
| 67 |
} |
|
| 68 |
) |
|
| 69 | ||
| 70 |
# nolint start |
|
| 71 | ||
| 72 |
##' Helper function for value matching with tolerance |
|
| 73 |
##' |
|
| 74 |
##' This is a modified version of \code{match} that supports tolerance.
|
|
| 75 |
##' |
|
| 76 |
##' @param x the values to be matched |
|
| 77 |
##' @param table the values to be matched against |
|
| 78 |
##' @return A vector of the same length as \code{x} or
|
|
| 79 |
##' empty vector if \code{table} is empty.
|
|
| 80 |
##' |
|
| 81 |
##' @export |
|
| 82 |
##' @keywords programming |
|
| 83 |
match_within_tolerance <- function(x, table) {
|
|
| 84 | 1802x |
if (length(table) == 0) {
|
| 85 | 31x |
return(integer()) |
| 86 |
} |
|
| 87 | ||
| 88 | 1771x |
as.integer(sapply(x, function(.x) {
|
| 89 | 7328x |
which(sapply(table, function(.table) {
|
| 90 | 194622x |
isTRUE(all.equal( |
| 91 | 194622x |
.x, |
| 92 | 194622x |
.table, |
| 93 | 194622x |
tolerance = 1e-10, |
| 94 | 194622x |
check.names = FALSE, |
| 95 | 194622x |
check.attributes = FALSE |
| 96 |
)) |
|
| 97 | 7328x |
}))[1] |
| 98 |
})) |
|
| 99 |
} |
|
| 100 | ||
| 101 |
##' checks for whole numbers (integers) |
|
| 102 |
##' |
|
| 103 |
##' @param x the numeric vector |
|
| 104 |
##' @param tol the tolerance |
|
| 105 |
##' @return TRUE or FALSE for each element of x |
|
| 106 |
##' |
|
| 107 |
##' @keywords internal |
|
| 108 |
is.wholenumber <- function(x, tol = .Machine$double.eps^0.5) {
|
|
| 109 | ! |
abs(x - round(x)) < tol |
| 110 |
} |
|
| 111 | ||
| 112 | ||
| 113 |
##' Safe conversion to integer vector |
|
| 114 |
##' |
|
| 115 |
##' @param x the numeric vector |
|
| 116 |
##' @return the integer vector |
|
| 117 |
##' |
|
| 118 |
##' @keywords internal |
|
| 119 |
safeInteger <- function(x) {
|
|
| 120 | ! |
testres <- is.wholenumber(x) |
| 121 | ! |
if (!all(testres)) {
|
| 122 | ! |
notInt <- which(!testres) |
| 123 | ! |
stop(paste( |
| 124 | ! |
"elements", |
| 125 | ! |
paste(notInt, sep = ", "), |
| 126 | ! |
"of vector are not integers!" |
| 127 |
)) |
|
| 128 |
} |
|
| 129 | ! |
as.integer(x) |
| 130 |
} |
|
| 131 | ||
| 132 |
##' Shorthand for logit function |
|
| 133 |
##' |
|
| 134 |
##' @param x the function argument |
|
| 135 |
##' @return the logit(x) |
|
| 136 |
##' |
|
| 137 |
##' @export |
|
| 138 |
##' @keywords programming |
|
| 139 |
logit <- function(x) {
|
|
| 140 | 1834x |
qlogis(x) |
| 141 |
} |
|
| 142 | ||
| 143 |
##' Shorthand for probit function |
|
| 144 |
##' |
|
| 145 |
##' @param x the function argument |
|
| 146 |
##' @return the probit(x) |
|
| 147 |
##' |
|
| 148 |
##' @export |
|
| 149 |
##' @keywords programming |
|
| 150 |
probit <- function(x) {
|
|
| 151 | 6x |
qnorm(x) |
| 152 |
} |
|
| 153 | ||
| 154 |
##' Open the example pdf for crmPack |
|
| 155 |
##' |
|
| 156 |
##' Calling this helper function should open the example.pdf document, |
|
| 157 |
##' residing in the doc subfolder of the package installation directory. |
|
| 158 |
##' |
|
| 159 |
##' @return nothing |
|
| 160 |
##' @export |
|
| 161 |
##' @keywords documentation |
|
| 162 |
##' @author Daniel Sabanes Bove \email{sabanesd@@rconis.com}
|
|
| 163 |
crmPackExample <- function() {
|
|
| 164 | ! |
crmPath <- system.file(package = "crmPack") |
| 165 | ! |
printVignette(list(PDF = "example.pdf", Dir = crmPath)) |
| 166 |
## instead of utils:::print.vignette |
|
| 167 |
} |
|
| 168 | ||
| 169 |
##' Open the browser with help pages for crmPack |
|
| 170 |
##' |
|
| 171 |
##' This convenience function opens your browser with the help pages for |
|
| 172 |
##' crmPack. |
|
| 173 |
##' |
|
| 174 |
##' @return nothing |
|
| 175 |
##' @export |
|
| 176 |
##' @importFrom utils help |
|
| 177 |
##' @keywords documentation |
|
| 178 |
##' @author Daniel Sabanes Bove \email{sabanesd@@rconis.com}
|
|
| 179 |
crmPackHelp <- function() {
|
|
| 180 | ! |
utils::help(package = "crmPack", help_type = "html") |
| 181 |
} |
|
| 182 | ||
| 183 |
#' Plot `gtable` Objects |
|
| 184 |
#' |
|
| 185 |
#' This is needed because `crmPack` uses [gridExtra::arrangeGrob()] to combine |
|
| 186 |
#' `ggplot2` plots, and the resulting `gtable` object is not plotted otherwise |
|
| 187 |
#' when implicitly printing it in the console, e.g. |
|
| 188 |
#' |
|
| 189 |
#' @method plot gtable |
|
| 190 |
#' @param x (`gtable`)\cr object to plot. |
|
| 191 |
#' @param ... additional parameters for [grid::grid.draw()]. |
|
| 192 |
#' |
|
| 193 |
#' @export |
|
| 194 |
plot.gtable <- function(x, ...) {
|
|
| 195 | 1x |
grid::grid.draw(x, ...) |
| 196 |
} |
|
| 197 | ||
| 198 |
#' @method print gtable |
|
| 199 |
#' @rdname plot.gtable |
|
| 200 |
#' @export |
|
| 201 |
print.gtable <- function(x, ...) {
|
|
| 202 | ! |
plot(x, ...) |
| 203 |
} |
|
| 204 | ||
| 205 |
##' Taken from utils package (print.vignette) |
|
| 206 |
##' |
|
| 207 |
##' @importFrom tools file_ext |
|
| 208 |
##' @importFrom utils browseURL |
|
| 209 |
##' @keywords internal |
|
| 210 |
printVignette <- function(x, ...) {
|
|
| 211 | ! |
if (nzchar(out <- x$PDF)) {
|
| 212 | ! |
ext <- tools::file_ext(out) |
| 213 | ! |
out <- file.path(x$Dir, "doc", out) |
| 214 | ! |
if (tolower(ext) == "pdf") {
|
| 215 | ! |
pdfviewer <- getOption("pdfviewer")
|
| 216 | ! |
if (identical(pdfviewer, "false")) {} else if (
|
| 217 | ! |
.Platform$OS.type == "windows" && |
| 218 | ! |
identical( |
| 219 | ! |
pdfviewer, |
| 220 | ! |
file.path(R.home("bin"), "open.exe")
|
| 221 |
) |
|
| 222 |
) {
|
|
| 223 | ! |
shell.exec(out) |
| 224 |
} else {
|
|
| 225 | ! |
system2(pdfviewer, shQuote(out), wait = FALSE) |
| 226 |
} |
|
| 227 |
} else {
|
|
| 228 | ! |
browseURL(out) |
| 229 |
} |
|
| 230 |
} else {
|
|
| 231 | ! |
warning( |
| 232 | ! |
gettextf("vignette %s has no PDF/HTML", sQuote(x$Topic)),
|
| 233 | ! |
call. = FALSE, |
| 234 | ! |
domain = NA |
| 235 |
) |
|
| 236 |
} |
|
| 237 | ! |
invisible(x) |
| 238 |
} |
|
| 239 | ||
| 240 |
##' Compute the density of Inverse gamma distribution |
|
| 241 |
##' @param x vector of quantiles |
|
| 242 |
##' @param a the shape parameter of the inverse gamma distribution |
|
| 243 |
##' @param b the scale parameter of the inverse gamma distribution |
|
| 244 |
##' @param log logical; if TRUE, probabilities p are given as log(p) |
|
| 245 |
##' @param normalize logical; if TRUE, the output will be normalized |
|
| 246 |
##' |
|
| 247 |
##' @keywords internal |
|
| 248 |
dinvGamma <- function(x, a, b, log = FALSE, normalize = TRUE) {
|
|
| 249 | ! |
ret <- -(a + 1) * log(x) - b / x |
| 250 | ! |
if (normalize) {
|
| 251 | ! |
ret <- ret + a * log(b) - lgamma(a) |
| 252 |
} |
|
| 253 | ! |
if (log) {
|
| 254 | ! |
return(ret) |
| 255 |
} else {
|
|
| 256 | ! |
return(exp(ret)) |
| 257 |
} |
|
| 258 |
} |
|
| 259 | ||
| 260 |
##' Compute the distribution function of Inverse gamma distribution |
|
| 261 |
##' |
|
| 262 |
##' @param q vector of quantiles |
|
| 263 |
##' @param a the shape parameter of the inverse gamma distribution |
|
| 264 |
##' @param b the scale parameter of the inverse gamma distribution |
|
| 265 |
##' @param lower.tail logical; if TRUE (default), probabilities are `P(X > x)`, |
|
| 266 |
##' otherwise, `P(X <= x)`. |
|
| 267 |
##' @param log.p if TRUE, probabilities/densities p are returned as `log(p)` |
|
| 268 |
##' |
|
| 269 |
##' @keywords internal |
|
| 270 |
pinvGamma <- function(q, a, b, lower.tail = TRUE, log.p = FALSE) {
|
|
| 271 | ! |
pgamma( |
| 272 | ! |
q = 1 / q, |
| 273 | ! |
shape = a, |
| 274 | ! |
rate = b, |
| 275 | ! |
lower.tail = !lower.tail, |
| 276 | ! |
log.p = log.p |
| 277 |
) |
|
| 278 |
} |
|
| 279 | ||
| 280 |
##' Compute the quantile function of Inverse gamma distribution |
|
| 281 |
##' @param p vector of probabilities |
|
| 282 |
##' @param a the shape parameter of the inverse gamma distribution |
|
| 283 |
##' @param b the scale parameter of the inverse gamma distribution |
|
| 284 |
##' @param lower.tail logical; if TRUE (default), probabilities are `P(X > x)`, |
|
| 285 |
##' otherwise, `P(X <= x)`. |
|
| 286 |
##' @param log.p FALSE if TRUE, probabilities/densities p are returned as `log(p)` |
|
| 287 |
##' |
|
| 288 |
##' @keywords internal |
|
| 289 |
qinvGamma <- function(p, a, b, lower.tail = TRUE, log.p = FALSE) {
|
|
| 290 | ! |
1 / |
| 291 | ! |
qgamma( |
| 292 | ! |
p = p, |
| 293 | ! |
shape = a, |
| 294 | ! |
rate = b, |
| 295 | ! |
lower.tail = !lower.tail, |
| 296 | ! |
log.p = log.p |
| 297 |
) |
|
| 298 |
} |
|
| 299 |
##' The random generation of the Inverse gamma distribution |
|
| 300 |
##' @param n the number of observations |
|
| 301 |
##' @param a the shape parameter of the inverse gamma distribution |
|
| 302 |
##' @param b the scale parameter of the inverse gamma distribution |
|
| 303 |
##' |
|
| 304 |
##' @keywords internal |
|
| 305 |
rinvGamma <- function(n, a, b) {
|
|
| 306 | 43644x |
1 / rgamma(n, shape = a, rate = b) |
| 307 |
} |
|
| 308 | ||
| 309 |
# nolint end |
|
| 310 | ||
| 311 |
#' Combining S4 Class Validation Results |
|
| 312 |
#' |
|
| 313 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 314 |
#' |
|
| 315 |
#' A simple helper function that combines two outputs from calls to `result()` |
|
| 316 |
#' function which is placed in a slot of [Validate()] reference class. |
|
| 317 |
#' |
|
| 318 |
#' @param v1 (`logical` or `character`)\cr an output from `result()` function from |
|
| 319 |
#' [Validate()] reference class, to be combined with `v2`. |
|
| 320 |
#' @param v2 (`logical` or `character`)\cr an output from `result()` function from |
|
| 321 |
#' [Validate()] reference class, to be combined with `v1`. |
|
| 322 |
#' |
|
| 323 |
#' @export |
|
| 324 |
#' @examples |
|
| 325 |
#' h_validate_combine_results(TRUE, "some_message") |
|
| 326 |
h_validate_combine_results <- function(v1, v2) {
|
|
| 327 | ! |
assert_true( |
| 328 | ! |
test_true(v1) || test_character(v1, any.missing = FALSE, min.len = 1L) |
| 329 |
) |
|
| 330 | ! |
assert_true( |
| 331 | ! |
test_true(v2) || test_character(v2, any.missing = FALSE, min.len = 1L) |
| 332 |
) |
|
| 333 | ||
| 334 | ! |
isTRUEv2 <- isTRUE(v2) |
| 335 | ! |
if (isTRUE(v1)) {
|
| 336 | ! |
if (isTRUEv2) {
|
| 337 | ! |
TRUE |
| 338 |
} else {
|
|
| 339 | ! |
v2 |
| 340 |
} |
|
| 341 |
} else {
|
|
| 342 | ! |
if (isTRUEv2) {
|
| 343 | ! |
v1 |
| 344 |
} else {
|
|
| 345 | ! |
c(v1, v2) |
| 346 |
} |
|
| 347 |
} |
|
| 348 |
} |
|
| 349 | ||
| 350 |
#' Comparison with Numerical Tolerance and Without Name Comparison |
|
| 351 |
#' |
|
| 352 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 353 |
#' |
|
| 354 |
#' This helper function ensures a default tolerance level equal to `1e-10`, |
|
| 355 |
#' and ignores names and other attributes. |
|
| 356 |
#' In contrast to [all.equal()], it always returns a logical type object. |
|
| 357 |
#' |
|
| 358 |
#' @param target (`numeric`)\cr target values. |
|
| 359 |
#' @param current (`numeric`)\cr current values. |
|
| 360 |
#' @param tolerance (`number`) relative differences smaller than this are not |
|
| 361 |
#' reported. |
|
| 362 |
#' @return `TRUE` when `target` and `current` do not differ |
|
| 363 |
#' up to desired tolerance and without looking at names or other attributes, |
|
| 364 |
#' `FALSE` otherwise. |
|
| 365 |
#' |
|
| 366 |
#' @export |
|
| 367 |
#' |
|
| 368 |
h_all_equivalent <- function(target, current, tolerance = 1e-10) {
|
|
| 369 | 2018x |
assert_numeric(target) |
| 370 | 2018x |
assert_numeric(current) |
| 371 | 2018x |
assert_number(tolerance) |
| 372 | ||
| 373 | 2018x |
tmp <- all.equal( |
| 374 | 2018x |
target = target, |
| 375 | 2018x |
current = current, |
| 376 | 2018x |
tolerance = tolerance, |
| 377 | 2018x |
check.names = FALSE, |
| 378 | 2018x |
check.attributes = FALSE |
| 379 |
) |
|
| 380 | 2018x |
isTRUE(tmp) |
| 381 |
} |
|
| 382 | ||
| 383 |
#' Preparing Data for Plotting |
|
| 384 |
#' |
|
| 385 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 386 |
#' |
|
| 387 |
#' This helper function prepares a `data.frame` object based on `Data` class |
|
| 388 |
#' object. The resulting data frame is used by the plot function for `Data` |
|
| 389 |
#' class objects. |
|
| 390 |
#' |
|
| 391 |
#' @param data (`Data`)\cr object from which data is extracted and converted |
|
| 392 |
#' into a data frame. |
|
| 393 |
#' @param blind (`flag`)\cr should data be blinded? |
|
| 394 |
#' If `TRUE`, then for each cohort, all DLTs are assigned to the first |
|
| 395 |
#' subjects in the cohort. In addition, the placebo (if any) is set to the |
|
| 396 |
#' active dose level for that cohort. |
|
| 397 |
#' @param ... further arguments passed to `data.frame` constructor. |
|
| 398 |
#' It can be e.g. an extra `column_name = value` pair based on a slot |
|
| 399 |
#' from `x` (which in this case might be a subclass of `Data`) |
|
| 400 |
#' which does not appear in `Data`. |
|
| 401 |
#' @return A [`data.frame`] object with values to plot. |
|
| 402 |
#' |
|
| 403 |
h_plot_data_df <- function(data, blind = FALSE, ...) {
|
|
| 404 |
df <- data.frame( |
|
| 405 |
patient = seq_along(data@x), |
|
| 406 |
ID = paste(" ", data@ID),
|
|
| 407 |
cohort = data@cohort, |
|
| 408 |
dose = data@x, |
|
| 409 |
toxicity = ifelse(data@y == 1, "Yes", "No"), |
|
| 410 |
... |
|
| 411 |
) |
|
| 412 | ||
| 413 |
if (blind) {
|
|
| 414 |
# This is to blind the data. |
|
| 415 |
# For each cohort, all DLTs are assigned to the first subjects in the cohort. |
|
| 416 |
# In addition, the placebo (if any) is set to the active dose level for that |
|
| 417 |
# cohort. |
|
| 418 |
# Notice: dapply reorders records of df according to the lexicographic order |
|
| 419 |
# of cohort. |
|
| 420 |
df <- dapply(df, f = ~cohort, FUN = function(coh) {
|
|
| 421 |
coh$toxicity <- sort(coh$toxicity, decreasing = TRUE) |
|
| 422 |
coh$dose <- max(coh$dose) |
|
| 423 |
coh |
|
| 424 |
}) |
|
| 425 |
} else if (data@placebo) {
|
|
| 426 |
# Placebo will be plotted at y = 0 level. |
|
| 427 |
df$dose[df$dose == data@doseGrid[1]] <- 0 |
|
| 428 |
} |
|
| 429 | ||
| 430 |
df |
|
| 431 |
} |
|
| 432 | ||
| 433 |
#' Preparing Cohort Lines for Data Plot |
|
| 434 |
#' |
|
| 435 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 436 |
#' |
|
| 437 |
#' This helper function prepares a `ggplot` geom with reference lines |
|
| 438 |
#' separating different cohorts on the plot of `Data` class object. |
|
| 439 |
#' Lines are either vertical or horizontal of green color and longdash type. |
|
| 440 |
#' |
|
| 441 |
#' @details The geom object is returned if and only if `placebo` is equal to |
|
| 442 |
#' `TRUE` and there are more than one unique values in `cohort`. Otherwise, |
|
| 443 |
#' this function returns `NULL` object. |
|
| 444 |
#' |
|
| 445 |
#' @param cohort (`integer`)\cr the cohort indices. |
|
| 446 |
#' @param placebo (`flag`)\cr is placebo included in the doses? |
|
| 447 |
#' If it so, this function returns `NULL` object as in this case all doses |
|
| 448 |
#' in a given cohort are equal and there is no need to separate them. |
|
| 449 |
#' @param vertical (`flag`)\cr should the line be vertical? Otherwise it is |
|
| 450 |
#' horizontal. |
|
| 451 |
#' |
|
| 452 |
h_plot_data_cohort_lines <- function(cohort, placebo, vertical = TRUE) {
|
|
| 453 | 15x |
assert_integer(cohort) |
| 454 | 15x |
assert_flag(placebo) |
| 455 | 15x |
assert_flag(vertical) |
| 456 | ||
| 457 |
# If feasible, add vertical or horizontal green lines separating sub-sequent |
|
| 458 |
# cohorts. |
|
| 459 | 15x |
if (placebo && length(unique(cohort)) > 1) {
|
| 460 | 11x |
intercept <- head(cumsum(table(cohort)), n = -1) + 0.5 |
| 461 | 11x |
if (vertical) {
|
| 462 | 9x |
geom_vline( |
| 463 | 9x |
xintercept = intercept, |
| 464 | 9x |
colour = "green", |
| 465 | 9x |
linetype = "longdash" |
| 466 | 9x |
) # nolintr |
| 467 |
} else {
|
|
| 468 | 2x |
geom_hline( |
| 469 | 2x |
yintercept = intercept, |
| 470 | 2x |
colour = "green", |
| 471 | 2x |
linetype = "longdash" |
| 472 | 2x |
) # nolintr |
| 473 |
} |
|
| 474 |
} else {
|
|
| 475 | 4x |
NULL |
| 476 |
} |
|
| 477 |
} |
|
| 478 | ||
| 479 |
#' Checking Formals of a Function |
|
| 480 |
#' |
|
| 481 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 482 |
#' |
|
| 483 |
#' This helper function checks whether a given function `fun` has required or |
|
| 484 |
#' allowed arguments. The argument check is based only on the names of the |
|
| 485 |
#' arguments. No any further logic is verified here. |
|
| 486 |
#' |
|
| 487 |
#' @param fun (`function`)\cr a function name whose argument names will be |
|
| 488 |
#' checked. |
|
| 489 |
#' @param mandatory (`character` or `NULL`)\cr the names of the arguments which |
|
| 490 |
#' must be present in `fun`. If `mandatory` is specified as `NULL` (default) |
|
| 491 |
#' this requirement is ignored. |
|
| 492 |
#' @param allowed (`character` or `NULL`)\cr the names of the arguments which |
|
| 493 |
#' are allowed in `fun`. Names that do not belong to `allowed` are simply not |
|
| 494 |
#' allowed. The `allowed` parameter is independent from the `mandatory`, in a |
|
| 495 |
#' sense that if `mandatory` is specified as a `character` vector, it does not |
|
| 496 |
#' have to be repeated in `allowed`. If `allowed` is specified as `NULL` |
|
| 497 |
#' (default), then it means that there must be no any arguments in `fun` |
|
| 498 |
#' (except these ones which are specified in `mandatory`). |
|
| 499 |
#' |
|
| 500 |
#' @export |
|
| 501 |
#' |
|
| 502 |
h_check_fun_formals <- function(fun, mandatory = NULL, allowed = NULL) {
|
|
| 503 | 1347x |
assert_function(fun) |
| 504 | 1347x |
assert_character(mandatory, null.ok = TRUE) |
| 505 | 1347x |
assert_character(allowed, null.ok = TRUE) |
| 506 | ||
| 507 | 1347x |
arg_names <- names(formals(fun)) |
| 508 | 1347x |
mandatory_check <- all(mandatory %in% arg_names) |
| 509 | 1347x |
allowed_check <- all(arg_names %in% c(mandatory, allowed)) |
| 510 | ||
| 511 | 1347x |
mandatory_check && allowed_check |
| 512 |
} |
|
| 513 | ||
| 514 |
#' Getting the Slots from a S4 Object |
|
| 515 |
#' |
|
| 516 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 517 |
#' |
|
| 518 |
#' This helper function extracts requested slots from the S4 class object. |
|
| 519 |
#' It is a simple wrapper of [methods::slot()] function. |
|
| 520 |
#' |
|
| 521 |
#' @param object (`S4`)\cr an object from a formally defined S4 class. |
|
| 522 |
#' @param names (`character`)\cr a vector with names of slots to be fetched. |
|
| 523 |
#' This function assumes that for every element in `names`, there exists a |
|
| 524 |
#' slot of the same name in the `object`. |
|
| 525 |
#' @param simplify (`flag`)\cr should an output be simplified? This has an |
|
| 526 |
#' effect if and only if a single slot is about to be extracted, i.e. |
|
| 527 |
#' `names` is just a single string. |
|
| 528 |
#' |
|
| 529 |
#' @return `list` with the slots extracted from `object` according to `names`, |
|
| 530 |
#' or single slot if simplification is required and possible. |
|
| 531 |
#' |
|
| 532 |
#' @export |
|
| 533 |
#' |
|
| 534 |
h_slots <- function(object, names, simplify = FALSE) {
|
|
| 535 | 4457x |
assert_true(isS4(object)) |
| 536 | 4457x |
assert_character(names, any.missing = FALSE, null.ok = TRUE) |
| 537 | 4457x |
assert_true(all(names %in% slotNames(object))) |
| 538 | ||
| 539 | 4456x |
if (is.null(names) || length(names) == 0L) {
|
| 540 | 701x |
return(list()) |
| 541 |
} |
|
| 542 | ||
| 543 | 3755x |
slots_list <- sapply( |
| 544 | 3755x |
names, |
| 545 | 3755x |
function(n) slot(object, n), |
| 546 | 3755x |
simplify = FALSE, |
| 547 | 3755x |
USE.NAMES = TRUE |
| 548 |
) |
|
| 549 | ||
| 550 | 3755x |
if (simplify && length(names) == 1) {
|
| 551 | 17x |
slots_list[[1]] |
| 552 |
} else {
|
|
| 553 | 3738x |
slots_list |
| 554 |
} |
|
| 555 |
} |
|
| 556 | ||
| 557 |
#' Conditional Formatting Using C-style Formats |
|
| 558 |
#' |
|
| 559 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 560 |
#' |
|
| 561 |
#' This helper function conditionally formats a number with [formatC()] |
|
| 562 |
#' function using `"E"` format and specific number of digits as given by the |
|
| 563 |
#' user. A number is formatted if and only if its absolute value is less than |
|
| 564 |
#' `0.001` or greater than `10000`. Otherwise, the number is not formatted. |
|
| 565 |
#' Additionally, custom prefix or suffix can be appended to character string |
|
| 566 |
#' with formatted number, so that the changes are marked. |
|
| 567 |
#' |
|
| 568 |
#' @note This function was primarily designed as a helper for |
|
| 569 |
#' [h_jags_write_model()] function. |
|
| 570 |
#' |
|
| 571 |
#' @param x (`number`)\cr a number to be formatted. |
|
| 572 |
#' @param digits (`function`)\cr the desired number of significant digits. |
|
| 573 |
#' @param prefix (`string`)\cr a prefix to be added in front of the formatted |
|
| 574 |
#' number. |
|
| 575 |
#' @param suffix (`string`)\cr a suffix to be appended after the formatted |
|
| 576 |
#' number. |
|
| 577 |
#' |
|
| 578 |
#' @return Either formatted `x` as `string` or unchanged `x` if the |
|
| 579 |
#' formatting condition is not met. |
|
| 580 |
#' |
|
| 581 |
#' @export |
|
| 582 |
#' @examples |
|
| 583 |
#' h_format_number(50000) |
|
| 584 |
#' h_format_number(50000, prefix = "P", suffix = "S") |
|
| 585 |
h_format_number <- function(x, digits = 5, prefix = "", suffix = "") {
|
|
| 586 | 3243x |
assert_number(x) |
| 587 | 3243x |
assert_int(digits) |
| 588 | 3243x |
assert_string(prefix) |
| 589 | 3243x |
assert_string(suffix) |
| 590 | ||
| 591 | 3243x |
if ((abs(x) < 1e-3) || (abs(x) > 1e+4)) {
|
| 592 | 218x |
paste0(prefix, formatC(x, digits = digits, format = "E"), suffix) |
| 593 |
} else {
|
|
| 594 | 3025x |
x |
| 595 |
} |
|
| 596 |
} |
|
| 597 | ||
| 598 |
#' Recursively Apply a Function to a List |
|
| 599 |
#' |
|
| 600 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 601 |
#' |
|
| 602 |
#' This helper function recursively iterates through a "list-like" object and it |
|
| 603 |
#' checks whether an element is of a given class. If it so, then it replaces |
|
| 604 |
#' that element by the result of an execution of a given function. Otherwise, |
|
| 605 |
#' and if the element is of length greater than 1 (i.e. not a scalar), it |
|
| 606 |
#' replaces that element by the result of `h_rapply()`, recursively called for |
|
| 607 |
#' that element. In the remaining case, that is, the element is not of a given |
|
| 608 |
#' class and is a scalar, then that element remains unchanged. |
|
| 609 |
#' |
|
| 610 |
#' @note This helper function is conceptually similar the same as [rapply()] |
|
| 611 |
#' function. However, it differs from [rapply()] in two major ways. First, the |
|
| 612 |
#' `h_rapply()` is not limited to objects of type `list` or `expression` only. |
|
| 613 |
#' It can be any "list-like" object of any type for which subsetting operator |
|
| 614 |
#' [`[[`][Extract] is defined. This can be, for example, an object of type |
|
| 615 |
#' `language`, often obtained from the [body()] function. The second |
|
| 616 |
#' difference is that the flexibility of [rapply()] on how the result is |
|
| 617 |
#' structured is not available with `h_rapply()` for the user. That is, with |
|
| 618 |
#' `h_rapply()` each element of `x`, which has a class included in `classes`, |
|
| 619 |
#' is replaced by the result of applying `fun` to the element. This behavior |
|
| 620 |
#' corresponds to [rapply()] when invoked with fixed `how = replace`. |
|
| 621 |
#' This function was primarily designed as a helper for [h_jags_write_model()] |
|
| 622 |
#' function. |
|
| 623 |
#' |
|
| 624 |
#' @param x (`any`)\cr "list-like" object for which subsetting operator |
|
| 625 |
#' [`[[`][Extract] is defined. |
|
| 626 |
#' @param fun (`function`)\cr a function of one "principal" argument, passing |
|
| 627 |
#' further arguments via `...`. |
|
| 628 |
#' @param classes (`character`)\cr class names. |
|
| 629 |
#' @param ... further arguments passed to function `fun`. |
|
| 630 |
#' |
|
| 631 |
#' @return "list-like" object of similar structure as `x`. |
|
| 632 |
#' |
|
| 633 |
#' @export |
|
| 634 |
#' @example examples/helpers-rapply.R |
|
| 635 |
#' |
|
| 636 |
h_rapply <- function(x, fun, classes, ...) {
|
|
| 637 | 21269x |
assert_function(fun) |
| 638 | 21269x |
assert_character(classes, min.len = 1L) |
| 639 |
# To assert that `x` is subsettable. |
|
| 640 |
# If it works, fine, we don't see the result, otherwise it gives the error. |
|
| 641 | 21269x |
force(x[[1]]) |
| 642 | ||
| 643 | 21269x |
for (i in seq_along(x)) {
|
| 644 | 63938x |
if (class(x[[i]]) %in% classes) {
|
| 645 | 3240x |
x[[i]] <- do.call(fun, c(list(x[[i]]), ...)) |
| 646 | 60698x |
} else if (length(x[[i]]) > 1L) {
|
| 647 | 20786x |
x[[i]] <- h_rapply(x[[i]], fun, classes, ...) |
| 648 |
} |
|
| 649 |
} |
|
| 650 | 21269x |
x |
| 651 |
} |
|
| 652 | ||
| 653 |
#' Getting `NULL` for `NA` |
|
| 654 |
#' |
|
| 655 |
#' @description `r lifecycle::badge("stable")`
|
|
| 656 |
#' |
|
| 657 |
#' A simple helper function that replaces `NA` object by `NULL` object. |
|
| 658 |
#' |
|
| 659 |
#' @param x (`any`)\cr atomic object of length `1`. For the definition of |
|
| 660 |
#' "atomic", see [is.atomic()]. |
|
| 661 |
#' |
|
| 662 |
#' @return `NULL` if `x` is `NA`, otherwise, `x`. |
|
| 663 |
#' |
|
| 664 |
#' @export |
|
| 665 |
#' @examples |
|
| 666 |
#' h_null_if_na(NA) |
|
| 667 |
h_null_if_na <- function(x) {
|
|
| 668 | 875x |
assert_atomic(x, len = 1L) |
| 669 | ||
| 670 | 872x |
if (is.na(x)) {
|
| 671 | 118x |
NULL |
| 672 |
} else {
|
|
| 673 | 754x |
x |
| 674 |
} |
|
| 675 |
} |
|
| 676 | ||
| 677 |
#' Getting the default value for an empty object |
|
| 678 |
#' |
|
| 679 |
#' @description `r lifecycle::badge("stable")`
|
|
| 680 |
#' |
|
| 681 |
#' A simple helper function that sets a default value for an empty or missing object, |
|
| 682 |
#' that is an object for which [length()] function returns `0L` or it has length 1 |
|
| 683 |
#' and [is.na()] returns `TRUE`. |
|
| 684 |
#' |
|
| 685 |
#' @param x (`any`) \cr an object to handle. It can be any object for which |
|
| 686 |
#' [length()] function is defined. |
|
| 687 |
#' @param default (`any`) \cr a default value for `x` object. |
|
| 688 |
#' |
|
| 689 |
#' @export |
|
| 690 |
#' @examples |
|
| 691 |
#' h_default_if_empty(character(0), default = "default label") |
|
| 692 |
#' h_default_if_empty("custom label", default = "default label")
|
|
| 693 |
#' h_default_if_empty(NA, default = "default label") |
|
| 694 |
h_default_if_empty <- function(x, default) {
|
|
| 695 | 2334x |
if (length(x) == 0L || (length(x) == 1L && is.na(x))) {
|
| 696 | 2294x |
default |
| 697 |
} else {
|
|
| 698 | 40x |
x |
| 699 |
} |
|
| 700 |
} |
|
| 701 |
#' Testing Matrix for Positive Definiteness |
|
| 702 |
#' |
|
| 703 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 704 |
#' |
|
| 705 |
#' This helper function checks whether a given numerical matrix `x` is a |
|
| 706 |
#' positive-definite square matrix of a given size, without any missing |
|
| 707 |
#' values. This function is used to test if a given matrix is a covariance |
|
| 708 |
#' matrix, since every symmetric positive semi-definite matrix is a covariance |
|
| 709 |
#' matrix. |
|
| 710 |
#' |
|
| 711 |
#' @details The positive definiteness test implemented in this function |
|
| 712 |
#' is based on the following characterization valid for real matrices: |
|
| 713 |
#' `A symmetric matrix is positive-definite if and only if all of its |
|
| 714 |
#' eigenvalues are positive.` In this function an eigenvalue is considered |
|
| 715 |
#' as positive if and only if it is greater than the `tol`. |
|
| 716 |
#' |
|
| 717 |
#' @param x (`matrix`)\cr a matrix to be checked. |
|
| 718 |
#' @param size (`integer`)\cr a size of the square matrix `x` to be checked |
|
| 719 |
#' against for. |
|
| 720 |
#' @param tol (`number`)\cr a given tolerance number used to check whether |
|
| 721 |
#' an eigenvalue is positive or not. An eigenvalue is considered |
|
| 722 |
#' as positive if and only if it is greater than the `tol`. |
|
| 723 |
#' |
|
| 724 |
#' @return `TRUE` if a given matrix is a positive-definite, `FALSE` otherwise. |
|
| 725 |
#' |
|
| 726 |
#' @export |
|
| 727 |
#' |
|
| 728 |
h_is_positive_definite <- function(x, size = 2, tol = 1e-08) {
|
|
| 729 | 3089x |
assert_number(tol) |
| 730 | ||
| 731 | 3089x |
is_matrix <- test_matrix( |
| 732 | 3089x |
x, |
| 733 | 3089x |
mode = "numeric", |
| 734 | 3089x |
nrows = size, |
| 735 | 3089x |
ncols = size, |
| 736 | 3089x |
any.missing = FALSE |
| 737 |
) |
|
| 738 | ||
| 739 | 3089x |
if (is_matrix) {
|
| 740 | 3078x |
is_symmetric <- all.equal(x, t(x), tolerance = tol) |
| 741 | 3078x |
if (isTRUE(is_symmetric)) {
|
| 742 | 3072x |
ev <- eigen(x, only.values = TRUE)$values |
| 743 | 3072x |
all(ev > tol) |
| 744 |
} else {
|
|
| 745 | 6x |
FALSE |
| 746 |
} |
|
| 747 |
} else {
|
|
| 748 | 11x |
FALSE |
| 749 |
} |
|
| 750 |
} |
|
| 751 | ||
| 752 |
#' Check that an argument is a named vector of type numeric |
|
| 753 |
#' |
|
| 754 |
#' @description `r lifecycle::badge("stable")`
|
|
| 755 |
#' |
|
| 756 |
#' A simple helper function that tests whether an object is a named numerical |
|
| 757 |
#' vector. |
|
| 758 |
#' |
|
| 759 |
#' @note This function is based on [`checkmate::test_numeric()`] and |
|
| 760 |
#' [`checkmate::test_names()`] functions. |
|
| 761 |
#' |
|
| 762 |
#' @param x (`any`)\cr object to check. |
|
| 763 |
#' @inheritParams checkmate::test_names |
|
| 764 |
#' @inheritParams checkmate::test_numeric |
|
| 765 |
#' @param ... further parameters passed to [`checkmate::test_numeric()`]. |
|
| 766 |
#' |
|
| 767 |
#' @return `TRUE` if `x` is a named vector of type numeric, otherwise `FALSE`. |
|
| 768 |
#' |
|
| 769 |
#' @export |
|
| 770 |
#' @examples |
|
| 771 |
#' h_test_named_numeric(1:2, permutation.of = c("a", "b"))
|
|
| 772 |
#' h_test_named_numeric(c(a = 1, b = 2), permutation.of = c("a", "b"))
|
|
| 773 |
#' h_test_named_numeric(c(a = 1, b = 2), permutation.of = c("b", "a"))
|
|
| 774 |
h_test_named_numeric <- function( |
|
| 775 |
x, |
|
| 776 |
subset.of = NULL, # nolintr |
|
| 777 |
must.include = NULL, # nolintr |
|
| 778 |
permutation.of = NULL, # nolintr |
|
| 779 |
identical.to = NULL, # nolintr |
|
| 780 |
disjunct.from = NULL, # nolintr |
|
| 781 |
lower = 0 + .Machine$double.xmin, |
|
| 782 |
finite = TRUE, |
|
| 783 |
any.missing = FALSE, # nolintr |
|
| 784 |
len = 2, |
|
| 785 |
... |
|
| 786 |
) {
|
|
| 787 | 962x |
is_valid_num <- test_numeric( |
| 788 | 962x |
x, |
| 789 | 962x |
lower = lower, |
| 790 | 962x |
finite = finite, |
| 791 | 962x |
any.missing = any.missing, |
| 792 | 962x |
len = len, |
| 793 |
..., |
|
| 794 | 962x |
names = "named" |
| 795 |
) |
|
| 796 | 962x |
are_names_valid <- test_names( |
| 797 | 962x |
names(x), |
| 798 | 962x |
subset.of = subset.of, |
| 799 | 962x |
must.include = must.include, |
| 800 | 962x |
permutation.of = permutation.of, |
| 801 | 962x |
identical.to = identical.to, |
| 802 | 962x |
disjunct.from = disjunct.from, |
| 803 |
) |
|
| 804 | 962x |
is_valid_num && are_names_valid |
| 805 |
} |
|
| 806 | ||
| 807 |
#' Check which elements are in a given range |
|
| 808 |
#' |
|
| 809 |
#' @description `r lifecycle::badge("stable")`
|
|
| 810 |
#' |
|
| 811 |
#' A simple helper function that tests whether elements of a given vector or |
|
| 812 |
#' matrix are within specified interval. |
|
| 813 |
#' |
|
| 814 |
#' @param x (`numeric`)\cr vector or matrix with elements to test. |
|
| 815 |
#' @param range (`numeric`)\cr an interval, i.e. sorted two-elements vector. |
|
| 816 |
#' @param bounds_closed (`logical`)\cr should bounds in the `range` be treated |
|
| 817 |
#' as closed? This can be a scalar or vector of length two. If it is a scalar, |
|
| 818 |
#' then its value applies to lower bound `range[1]` and upper bound |
|
| 819 |
#' `range[2]`. If this is a vector with two flags, the first flag corresponds |
|
| 820 |
#' to the lower bound only, and the second to the upper bound only. |
|
| 821 |
#' |
|
| 822 |
#' @return A logical vector or matrix of length equal to the length of `x`, that |
|
| 823 |
#' for every element of `x`, indicates whether a given element of `x` is in |
|
| 824 |
#' the `range`. |
|
| 825 |
#' |
|
| 826 |
#' @export |
|
| 827 |
#' @examples |
|
| 828 |
#' x <- 1:4 |
|
| 829 |
#' h_in_range(x, range = c(1, 3)) |
|
| 830 |
#' h_in_range(x, range = c(1, 3), bounds_closed = FALSE) |
|
| 831 |
#' h_in_range(x, range = c(1, 3), bounds_closed = c(FALSE, TRUE)) |
|
| 832 |
#' mat <- matrix(c(2, 5, 3, 10, 4, 9, 1, 8, 7), nrow = 3) |
|
| 833 |
#' h_in_range(mat, range = c(1, 5)) |
|
| 834 |
h_in_range <- function(x, range = c(0, 1), bounds_closed = TRUE) {
|
|
| 835 | 19966x |
assert_numeric(x) |
| 836 | 19965x |
assert_numeric(range, any.missing = FALSE, len = 2, sorted = TRUE) |
| 837 | 19961x |
assert_logical(bounds_closed, min.len = 1, max.len = 2, any.missing = FALSE) |
| 838 | ||
| 839 | 19959x |
above_lwr <- if (bounds_closed[1]) {
|
| 840 | 7725x |
x >= range[1] |
| 841 |
} else {
|
|
| 842 | 12234x |
x > range[1] |
| 843 |
} |
|
| 844 | ||
| 845 | 19959x |
below_upr <- if (tail(bounds_closed, 1)) {
|
| 846 | 16258x |
x <= range[2] |
| 847 |
} else {
|
|
| 848 | 3701x |
x < range[2] |
| 849 |
} |
|
| 850 | ||
| 851 | 19959x |
above_lwr & below_upr |
| 852 |
} |
|
| 853 | ||
| 854 |
#' Find Interval Numbers or Indices and Return Custom Number For 0. |
|
| 855 |
#' |
|
| 856 |
#' @description `r lifecycle::badge("stable")`
|
|
| 857 |
#' |
|
| 858 |
#' A simple wrapper of [`findInterval()`] function that invokes |
|
| 859 |
#' [`findInterval()`], takes its output and replaces all the |
|
| 860 |
#' elements with \eqn{0} value to a custom number as specified in `replacement`
|
|
| 861 |
#' argument. |
|
| 862 |
#' |
|
| 863 |
#' @inheritDotParams base::findInterval |
|
| 864 |
#' @param replacement (`number`)\cr a custom number to be used as a replacement |
|
| 865 |
#' for \eqn{0}. Default to `-Inf`.
|
|
| 866 |
#' |
|
| 867 |
#' @export |
|
| 868 |
#' @examples |
|
| 869 |
#' h_find_interval(1, c(2, 4, 6)) |
|
| 870 |
#' h_find_interval(3, c(2, 4, 6)) |
|
| 871 |
#' h_find_interval(1, c(2, 4, 6), replacement = -1) |
|
| 872 |
h_find_interval <- function(..., replacement = -Inf) {
|
|
| 873 | 288x |
assert_number(replacement) |
| 874 | ||
| 875 | 288x |
x <- findInterval(...) |
| 876 | 288x |
ifelse(x == 0, yes = replacement, no = x) |
| 877 |
} |
|
| 878 | ||
| 879 | ||
| 880 |
#' Group Together Mono and Combo Data |
|
| 881 |
#' |
|
| 882 |
#' This is only used in the simulation method for `DesignGrouped` to combine |
|
| 883 |
#' the separately generated data sets from mono and combo arms and to fit the |
|
| 884 |
#' combined logistic regression model. |
|
| 885 |
#' Hence the ID and cohort information is not relevant and will be |
|
| 886 |
#' arbitrarily assigned to avoid problems with the [`DataGrouped`] validation. |
|
| 887 |
#' |
|
| 888 |
#' @param mono_data (`Data`)\cr mono data. |
|
| 889 |
#' @param combo_data (`Data`)\cr combo data. |
|
| 890 |
#' |
|
| 891 |
#' @return A [`DataGrouped`] object containing both `mono_data` and `combo_data`, |
|
| 892 |
#' but with arbitrary ID and cohort slots. |
|
| 893 |
#' |
|
| 894 |
#' @keywords internal |
|
| 895 |
h_group_data <- function(mono_data, combo_data) {
|
|
| 896 | 72x |
assert_class(mono_data, "Data") |
| 897 | 72x |
assert_class(combo_data, "Data") |
| 898 | ||
| 899 | 72x |
df <- data.frame( |
| 900 | 72x |
x = c(mono_data@x, combo_data@x), |
| 901 | 72x |
y = c(mono_data@y, combo_data@y), |
| 902 | 72x |
group = rep( |
| 903 | 72x |
c("mono", "combo"),
|
| 904 | 72x |
c(length(mono_data@x), length(combo_data@x)) |
| 905 |
) |
|
| 906 |
) |
|
| 907 | 72x |
df <- df[order(df$x), ] |
| 908 | ||
| 909 | 72x |
DataGrouped( |
| 910 | 72x |
x = df$x, |
| 911 | 72x |
y = df$y, |
| 912 | 72x |
ID = seq_along(df$x), |
| 913 | 72x |
cohort = as.integer(factor(df$x)), |
| 914 | 72x |
doseGrid = sort(unique(c(mono_data@doseGrid, combo_data@doseGrid))), |
| 915 | 72x |
group = df$group |
| 916 |
) |
|
| 917 |
} |
| 1 |
# Integration with knitr ---- |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' We provide additional utility functions to allow human-friendly rendition of |
|
| 6 |
#' crmPack objects in Markdown and Quarto files. This file contains methods for |
|
| 7 |
#' all design classes, not just those that are direct descendants of `Design`. |
|
| 8 |
#' |
|
| 9 |
#' @return a character string that represents the object in markdown. |
|
| 10 |
#' @name knit_print |
|
| 11 |
NULL |
|
| 12 | ||
| 13 |
#' Internal Helper Functions for Validation of [`StartingDose`] Objects |
|
| 14 |
#' |
|
| 15 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 16 |
#' |
|
| 17 |
#' Validates that the `StartingDose` object contains valid `starting_dose`. |
|
| 18 |
#' |
|
| 19 |
#' @param object (`StartingDose`)\cr object to validate. |
|
| 20 |
#' @return A `character` vector with the validation failure messages, |
|
| 21 |
#' or `TRUE` in case validation passes. |
|
| 22 |
#' @keywords internal |
|
| 23 |
v_starting_dose <- function(object) {
|
|
| 24 | ! |
v <- Validate() |
| 25 | ! |
v$check( |
| 26 | ! |
test_number(object@starting_dose, finite = TRUE, lower = 0), |
| 27 | ! |
"starting_dose must be a non-negative, finite number" |
| 28 |
) |
|
| 29 | ! |
v$result() |
| 30 |
} |
|
| 31 | ||
| 32 |
# Helper class |
|
| 33 | ||
| 34 |
#' `StartingDose` |
|
| 35 |
#' |
|
| 36 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 37 |
#' |
|
| 38 |
#' [`StartingDose`] is a simple wrapper class for the `startingDose` slot of all |
|
| 39 |
#' design classes. It is used internally by `knit_print` methods |
|
| 40 |
#' |
|
| 41 |
#' @slot starting_dose (`numeric`)\cr the starting dose |
|
| 42 |
#' @rdname StartingDose-class |
|
| 43 |
#' @keywords internal |
|
| 44 |
.StartingDose <- setClass( |
|
| 45 |
Class = "StartingDose", |
|
| 46 |
slots = c( |
|
| 47 |
starting_dose = "numeric" |
|
| 48 |
), |
|
| 49 |
prototype = prototype( |
|
| 50 |
starting_dose = 1 |
|
| 51 |
), |
|
| 52 |
validity = v_starting_dose, |
|
| 53 |
contains = "CrmPackClass" |
|
| 54 |
) |
|
| 55 | ||
| 56 |
## constructor ---- |
|
| 57 | ||
| 58 |
#' @rdname StartingDose-class |
|
| 59 |
#' @param starting_dose (`positive_number`)\cr see slot definition. |
|
| 60 |
StartingDose <- function(starting_dose) {
|
|
| 61 | 75x |
new( |
| 62 | 75x |
"StartingDose", |
| 63 | 75x |
starting_dose = starting_dose |
| 64 |
) |
|
| 65 |
} |
|
| 66 | ||
| 67 |
#' @rdname StartingDose-class |
|
| 68 |
#' @note Typically, end users will not use the `.DefaultStartingDose()` function. |
|
| 69 |
.DefaultStartingDose <- function() {
|
|
| 70 | 5x |
StartingDose(starting_dose = 5) |
| 71 |
} |
|
| 72 | ||
| 73 |
# Helper functions ---- |
|
| 74 | ||
| 75 |
h_knit_print_design <- function( |
|
| 76 |
x, |
|
| 77 |
..., |
|
| 78 |
level = 2L, |
|
| 79 |
title = "Design", |
|
| 80 |
default_sections = NA, |
|
| 81 |
user_sections = NA, |
|
| 82 |
ignore_slots = c(), |
|
| 83 |
asis = TRUE |
|
| 84 |
) {
|
|
| 85 | 83x |
assert_flag(asis) |
| 86 |
# Because subsections use level + 1 and 6 is the lowest markdown header level |
|
| 87 | 65x |
assert_int(level, lower = 1, upper = 5) |
| 88 | 65x |
assert_character(title, any.missing = FALSE, len = 1L) |
| 89 | ||
| 90 | 65x |
slots_to_process <- setdiff(slotNames(x), ignore_slots) |
| 91 | ||
| 92 | 65x |
args <- list(...) |
| 93 | 65x |
units <- ifelse("units" %in% names(args), paste0(" ", args[["units"]]), "")
|
| 94 | 65x |
section_labels <- h_prepare_section_labels( |
| 95 | 65x |
x, |
| 96 | 65x |
default_sections, |
| 97 | 65x |
user_sections |
| 98 |
) |
|
| 99 | 65x |
assert_subset(slots_to_process, names(section_labels)) |
| 100 | ||
| 101 | 65x |
rv <- paste0( |
| 102 | 65x |
h_markdown_header(title, level = level), |
| 103 | 65x |
paste0( |
| 104 | 65x |
lapply( |
| 105 | 65x |
slots_to_process, |
| 106 | 65x |
function(nm) {
|
| 107 | 478x |
tmp <- switch( |
| 108 | 478x |
nm, |
| 109 | 478x |
starting_dose = knit_print( |
| 110 | 478x |
StartingDose(x@starting_dose), |
| 111 | 478x |
asis = FALSE, |
| 112 | 478x |
level = level + 1L, |
| 113 |
... |
|
| 114 |
), |
|
| 115 | 478x |
startingDose = knit_print( |
| 116 | 478x |
StartingDose(x@startingDose), |
| 117 | 478x |
asis = FALSE, |
| 118 | 478x |
level = level + 1L, |
| 119 |
... |
|
| 120 |
), |
|
| 121 | 478x |
pl_cohort_size = ifelse( |
| 122 | 478x |
identical(slot(x, "pl_cohort_size"), CohortSizeConst(0)), |
| 123 | 478x |
"Placebo will not be administered in the trial.\n\n", |
| 124 | 478x |
knit_print( |
| 125 | 478x |
slot(x, "pl_cohort_size"), |
| 126 | 478x |
asis = FALSE, |
| 127 | 478x |
level = level + 1L, |
| 128 |
... |
|
| 129 |
) |
|
| 130 |
), |
|
| 131 |
{
|
|
| 132 | 361x |
knit_print(slot(x, nm), asis = FALSE, level = level + 1L, ...) |
| 133 |
} |
|
| 134 |
) |
|
| 135 | 478x |
paste0(h_markdown_header(section_labels[nm], level + 1L), tmp) |
| 136 |
} |
|
| 137 |
), |
|
| 138 | 65x |
collapse = "\n\n" |
| 139 |
), |
|
| 140 | 65x |
"\n\n" |
| 141 |
) |
|
| 142 | ||
| 143 | 65x |
if (asis) {
|
| 144 | 27x |
rv <- knitr::asis_output(rv) |
| 145 |
} |
|
| 146 | 65x |
rv |
| 147 |
} |
|
| 148 | ||
| 149 |
#' @description A Helper Function to create Markdown Headers |
|
| 150 |
#' |
|
| 151 |
#' @param text (`character`) the header text |
|
| 152 |
#' @param level (`positive_integer`) the level of the header. Must be between 1 and 6. |
|
| 153 |
#' @return the Markdown header string: a newline, `#` repeated `level` times, |
|
| 154 |
#' a space, `text` followed by two newlines. |
|
| 155 |
#' @keywords internal |
|
| 156 |
#' @noRd |
|
| 157 |
h_markdown_header <- function(text, level = 2L) {
|
|
| 158 | 567x |
assert_character(text, any.missing = FALSE, len = 1L, min.chars = 2L) |
| 159 | 564x |
assert_int(level, lower = 1, upper = 6) |
| 160 | ||
| 161 | 561x |
paste0( |
| 162 | 561x |
"\n", |
| 163 | 561x |
stringr::str_dup("#", level),
|
| 164 |
" ", |
|
| 165 | 561x |
text, |
| 166 | 561x |
"\n\n" |
| 167 |
) |
|
| 168 |
} |
|
| 169 | ||
| 170 |
#' Modify a Set of Default Slot Labels With Custom Custom Labels |
|
| 171 |
#' |
|
| 172 |
#' x (`S4`)\cr the S4 object for which slot labels are required |
|
| 173 |
#' default_labels (`character`)\cr a vector of slot labels whose names are a |
|
| 174 |
#' superset of the slot names of `x` |
|
| 175 |
#' user_labels (`character`)\cr a vector of slot labels whose names are a |
|
| 176 |
#' superset of the slot names of `x`. Can be `NA`, in which case no updates |
|
| 177 |
#' are made |
|
| 178 |
#' @returns `default_labels` updated according to `user_labels` |
|
| 179 |
#' @noRd |
|
| 180 |
#' @keywords internal |
|
| 181 |
h_prepare_section_labels <- function(x, default_labels, user_labels = NA) {
|
|
| 182 | 71x |
assert_true(isS4(x)) |
| 183 | 71x |
assert_character(default_labels, any.missing = FALSE) |
| 184 | ||
| 185 | 71x |
if (!any(is.na(user_labels))) {
|
| 186 | 9x |
assert_character(user_labels, any.missing = FALSE) |
| 187 | 9x |
assert_subset(names(user_labels), slotNames(x)) |
| 188 | ||
| 189 | 9x |
for (nm in names(user_labels)) {
|
| 190 | 7x |
default_labels[nm] <- user_labels[nm] |
| 191 |
} |
|
| 192 |
} |
|
| 193 | 71x |
default_labels |
| 194 |
} |
|
| 195 | ||
| 196 |
# Methods ---- |
|
| 197 | ||
| 198 |
# StartingDose ---- |
|
| 199 | ||
| 200 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 201 |
#' @rdname knit_print |
|
| 202 |
#' @export |
|
| 203 |
#' @method knit_print StartingDose |
|
| 204 |
knit_print.StartingDose <- function(x, ..., asis = TRUE) {
|
|
| 205 | 73x |
assert_flag(asis) |
| 206 | ||
| 207 | 71x |
args <- list(...) |
| 208 | 71x |
units <- ifelse("units" %in% names(args), paste0(" ", args[["units"]]), "")
|
| 209 | 71x |
rv <- paste0( |
| 210 | 71x |
"The starting dose is ", |
| 211 | 71x |
paste0(x@starting_dose, units), |
| 212 | 71x |
".\n\n" |
| 213 |
) |
|
| 214 | ||
| 215 | 71x |
if (asis) {
|
| 216 | 2x |
rv <- knitr::asis_output(rv) |
| 217 |
} |
|
| 218 | 71x |
rv |
| 219 |
} |
|
| 220 | ||
| 221 |
# RuleDesign ---- |
|
| 222 | ||
| 223 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 224 |
#' @inheritParams knit_print.StoppingTargetProb |
|
| 225 |
#' @param level (`positive_integer`) The level of the headings used to separate |
|
| 226 |
#' slots. Must be between 1 and 6. |
|
| 227 |
#' @param title (`character`) The text of the heading of the section describing |
|
| 228 |
#' the design |
|
| 229 |
#' @param sections (`character`) a named vector of length at least 4 defining |
|
| 230 |
#' the headings used to define the sections corresponding to the design's slots. |
|
| 231 |
#' The element names must match the Design's slot names. |
|
| 232 |
#' @rdname knit_print |
|
| 233 |
#' @export |
|
| 234 |
#' @method knit_print RuleDesign |
|
| 235 |
knit_print.RuleDesign <- function( |
|
| 236 |
x, |
|
| 237 |
..., |
|
| 238 |
level = 2L, |
|
| 239 |
title = "Design", |
|
| 240 |
sections = NA, |
|
| 241 |
asis = TRUE |
|
| 242 |
) {
|
|
| 243 | 8x |
h_knit_print_design( |
| 244 | 8x |
x, |
| 245 |
..., |
|
| 246 | 8x |
level = 2L, |
| 247 | 8x |
title = "Design", |
| 248 | 8x |
default_sections = c( |
| 249 | 8x |
"nextBest" = "Dose recommendation", |
| 250 | 8x |
"cohort_size" = "Cohort size", |
| 251 | 8x |
"data" = "Observed data", |
| 252 | 8x |
"startingDose" = "Starting dose" |
| 253 |
), |
|
| 254 | 8x |
user_sections = sections, |
| 255 | 8x |
asis = asis |
| 256 |
) |
|
| 257 |
} |
|
| 258 | ||
| 259 |
# Design ---- |
|
| 260 | ||
| 261 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 262 |
#' @inheritParams knit_print.RuleDesign |
|
| 263 |
#' @rdname knit_print |
|
| 264 |
#' @export |
|
| 265 |
#' @method knit_print Design |
|
| 266 |
knit_print.Design <- function( |
|
| 267 |
x, |
|
| 268 |
..., |
|
| 269 |
level = 2L, |
|
| 270 |
title = "Design", |
|
| 271 |
sections = NA, |
|
| 272 |
asis = TRUE |
|
| 273 |
) {
|
|
| 274 | 22x |
h_knit_print_design( |
| 275 | 22x |
x, |
| 276 |
..., |
|
| 277 | 22x |
level = 2L, |
| 278 | 22x |
title = "Design", |
| 279 | 22x |
default_sections = c( |
| 280 | 22x |
"nextBest" = "Dose recommendation", |
| 281 | 22x |
"cohort_size" = "Cohort size", |
| 282 | 22x |
"data" = "Observed data", |
| 283 | 22x |
"startingDose" = "Starting dose", |
| 284 | 22x |
"increments" = "Escalation rule", |
| 285 | 22x |
"stopping" = "Stopping rule", |
| 286 | 22x |
"model" = "Dose toxicity model", |
| 287 | 22x |
"pl_cohort_size" = "Use of placebo" |
| 288 |
), |
|
| 289 | 22x |
user_sections = sections, |
| 290 | 22x |
asis = asis |
| 291 |
) |
|
| 292 |
} |
|
| 293 | ||
| 294 |
# DualDesign ---- |
|
| 295 | ||
| 296 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 297 |
#' @inheritParams knit_print.RuleDesign |
|
| 298 |
#' @rdname knit_print |
|
| 299 |
#' @export |
|
| 300 |
#' @method knit_print DualDesign |
|
| 301 |
knit_print.DualDesign <- function( |
|
| 302 |
x, |
|
| 303 |
..., |
|
| 304 |
level = 2L, |
|
| 305 |
title = "Design", |
|
| 306 |
sections = NA, |
|
| 307 |
asis = TRUE |
|
| 308 |
) {
|
|
| 309 | 8x |
assert_flag(asis) |
| 310 | 6x |
assert_character(title, len = 1, any.missing = FALSE) |
| 311 | 6x |
assert_integer(level, len = 1, lower = 1, upper = 6) |
| 312 | ||
| 313 | 6x |
args <- list(...) |
| 314 | 6x |
bLabel <- ifelse( |
| 315 | 6x |
"biomarker_label" %in% names(args), |
| 316 | 6x |
args[["biomarker_label"]], |
| 317 | 6x |
"biomarker" |
| 318 |
) |
|
| 319 | 6x |
tLabel <- ifelse( |
| 320 | 6x |
"tox_label" %in% names(args), |
| 321 | 6x |
args[["tox_label"]], |
| 322 | 6x |
"toxicity" |
| 323 |
) |
|
| 324 | ||
| 325 | 6x |
if (is.na(sections)) {
|
| 326 | 6x |
sections <- c( |
| 327 | 6x |
"model" = paste0("Dose-", tLabel, " and dose-", bLabel, " models")
|
| 328 |
) |
|
| 329 |
} else {
|
|
| 330 | ! |
if (!("model" %in% names(sections))) {
|
| 331 | ! |
sections["model"] <- paste0( |
| 332 | ! |
"Dose-", |
| 333 | ! |
tLabel, |
| 334 | ! |
" and dose-", |
| 335 | ! |
bLabel, |
| 336 | ! |
" models" |
| 337 |
) |
|
| 338 |
} |
|
| 339 |
} |
|
| 340 | ||
| 341 | 6x |
knit_print.Design( |
| 342 | 6x |
x, |
| 343 | 6x |
level = level, |
| 344 | 6x |
title = title, |
| 345 | 6x |
sections = sections, |
| 346 | 6x |
asis = asis, |
| 347 |
... |
|
| 348 |
) |
|
| 349 |
} |
|
| 350 | ||
| 351 |
# DADesign ---- |
|
| 352 | ||
| 353 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 354 |
#' @inheritParams knit_print.RuleDesign |
|
| 355 |
#' @rdname knit_print |
|
| 356 |
#' @export |
|
| 357 |
#' @method knit_print DADesign |
|
| 358 |
knit_print.DADesign <- function( |
|
| 359 |
x, |
|
| 360 |
..., |
|
| 361 |
level = 2L, |
|
| 362 |
title = "Design", |
|
| 363 |
sections = NA, |
|
| 364 |
asis = TRUE |
|
| 365 |
) {
|
|
| 366 | 8x |
h_knit_print_design( |
| 367 | 8x |
x, |
| 368 |
..., |
|
| 369 | 8x |
level = 2L, |
| 370 | 8x |
title = "Design", |
| 371 | 8x |
default_sections = c( |
| 372 | 8x |
"nextBest" = "Dose recommendation", |
| 373 | 8x |
"cohort_size" = "Cohort size", |
| 374 | 8x |
"data" = "Observed data", |
| 375 | 8x |
"startingDose" = "Starting dose", |
| 376 | 8x |
"increments" = "Escalation rule", |
| 377 | 8x |
"stopping" = "Stopping rule", |
| 378 | 8x |
"model" = "Dose toxicity model", |
| 379 | 8x |
"pl_cohort_size" = "Use of placebo", |
| 380 | 8x |
"safetyWindow" = "Safety window" |
| 381 |
), |
|
| 382 | 8x |
user_sections = sections, |
| 383 | 8x |
asis = asis |
| 384 |
) |
|
| 385 |
} |
|
| 386 | ||
| 387 |
# TDDesign ---- |
|
| 388 | ||
| 389 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 390 |
#' @inheritParams knit_print.RuleDesign |
|
| 391 |
#' @rdname knit_print |
|
| 392 |
#' @export |
|
| 393 |
#' @method knit_print TDDesign |
|
| 394 |
knit_print.TDDesign <- function( |
|
| 395 |
x, |
|
| 396 |
..., |
|
| 397 |
level = 2L, |
|
| 398 |
title = "Design", |
|
| 399 |
sections = NA, |
|
| 400 |
asis = TRUE |
|
| 401 |
) {
|
|
| 402 | 8x |
knit_print.Design( |
| 403 | 8x |
x, |
| 404 | 8x |
level = level, |
| 405 | 8x |
title = title, |
| 406 | 8x |
sections = sections, |
| 407 | 8x |
asis = asis, |
| 408 |
... |
|
| 409 |
) |
|
| 410 |
} |
|
| 411 | ||
| 412 |
# DualResponsesDesign ---- |
|
| 413 | ||
| 414 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 415 |
#' @inheritParams knit_print.RuleDesign |
|
| 416 |
#' @rdname knit_print |
|
| 417 |
#' @export |
|
| 418 |
#' @method knit_print DualResponsesDesign |
|
| 419 |
knit_print.DualResponsesDesign <- function( |
|
| 420 |
x, |
|
| 421 |
..., |
|
| 422 |
level = 2L, |
|
| 423 |
title = "Design", |
|
| 424 |
sections = NA, |
|
| 425 |
asis = TRUE |
|
| 426 |
) {
|
|
| 427 |
knit_print.Design( |
|
| 428 |
x, |
|
| 429 |
level = level, |
|
| 430 |
title = title, |
|
| 431 |
sections = sections, |
|
| 432 |
asis = asis, |
|
| 433 |
... |
|
| 434 |
) |
|
| 435 |
} |
|
| 436 | ||
| 437 |
# DesignOrdinal ---- |
|
| 438 | ||
| 439 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 440 |
#' @inheritParams knit_print.RuleDesign |
|
| 441 |
#' @rdname knit_print |
|
| 442 |
#' @export |
|
| 443 |
#' @method knit_print DesignOrdinal |
|
| 444 |
knit_print.DesignOrdinal <- function( |
|
| 445 |
x, |
|
| 446 |
..., |
|
| 447 |
level = 2L, |
|
| 448 |
title = "Design", |
|
| 449 |
sections = NA, |
|
| 450 |
asis = TRUE |
|
| 451 |
) {
|
|
| 452 | 6x |
h_knit_print_design( |
| 453 | 6x |
x, |
| 454 |
..., |
|
| 455 | 6x |
level = 2L, |
| 456 | 6x |
title = "Design", |
| 457 | 6x |
default_sections = c( |
| 458 | 6x |
"next_best" = "Dose recommendation", |
| 459 | 6x |
"cohort_size" = "Cohort size", |
| 460 | 6x |
"data" = "Observed data", |
| 461 | 6x |
"starting_dose" = "Starting dose", |
| 462 | 6x |
"increments" = "Escalation rule", |
| 463 | 6x |
"stopping" = "Stopping rule", |
| 464 | 6x |
"model" = "Dose toxicity model", |
| 465 | 6x |
"pl_cohort_size" = "Use of placebo" |
| 466 |
), |
|
| 467 | 6x |
user_sections = sections, |
| 468 | 6x |
asis = asis |
| 469 |
) |
|
| 470 |
} |
|
| 471 | ||
| 472 |
# DesignGrouped ---- |
|
| 473 | ||
| 474 |
# Needs special handling because of the empty models and nested rules in the |
|
| 475 |
# mono and combo slots and because of the many slots that are of built-in types |
|
| 476 |
# rather than being of crmPack-specific types |
|
| 477 | ||
| 478 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 479 |
#' @inheritParams knit_print.RuleDesign |
|
| 480 |
#' @rdname knit_print |
|
| 481 |
#' @export |
|
| 482 |
#' @method knit_print DesignGrouped |
|
| 483 |
knit_print.DesignGrouped <- function( |
|
| 484 |
x, |
|
| 485 |
..., |
|
| 486 |
level = 2L, |
|
| 487 |
title = "Design", |
|
| 488 |
sections = c( |
|
| 489 |
"model" = "Dose toxicity model", |
|
| 490 |
"mono" = "Monotherapy rules", |
|
| 491 |
"combo" = "Combination therapy rules", |
|
| 492 |
"other" = "Other details" |
|
| 493 |
), |
|
| 494 |
asis = TRUE |
|
| 495 |
) {
|
|
| 496 | 6x |
assert_flag(asis) |
| 497 | 4x |
assert_character(title, len = 1, any.missing = FALSE) |
| 498 | 4x |
assert_integer(level, len = 1, lower = 1, upper = 6) |
| 499 | ||
| 500 | 4x |
rv <- paste0( |
| 501 | 4x |
h_markdown_header(sections["model"], level = level), |
| 502 | 4x |
knit_print(x@model, asis = FALSE, ...), |
| 503 | 4x |
h_markdown_header(sections["mono"], level = level), |
| 504 | 4x |
h_knit_print_design( |
| 505 | 4x |
x@mono, |
| 506 | 4x |
asis = FALSE, |
| 507 | 4x |
level = level + 1L, |
| 508 | 4x |
ignore_slots = c("model"),
|
| 509 | 4x |
default_sections = c( |
| 510 | 4x |
"nextBest" = "Dose recommendation", |
| 511 | 4x |
"cohort_size" = "Cohort size", |
| 512 | 4x |
"data" = "Observed monotherapy data", |
| 513 | 4x |
"startingDose" = "Starting dose", |
| 514 | 4x |
"increments" = "Escalation rule", |
| 515 | 4x |
"stopping" = "Stopping rule", |
| 516 | 4x |
"pl_cohort_size" = "Use of placebo" |
| 517 |
), |
|
| 518 | 4x |
sections = sections[["mono"]], |
| 519 |
... |
|
| 520 |
), |
|
| 521 | 4x |
h_markdown_header(sections["combo"], level = level), |
| 522 | 4x |
h_knit_print_design( |
| 523 | 4x |
x@combo, |
| 524 | 4x |
asis = FALSE, |
| 525 | 4x |
level = level + 1L, |
| 526 | 4x |
ignore_slots = "model", |
| 527 | 4x |
default_sections = c( |
| 528 | 4x |
"nextBest" = "Dose recommendation", |
| 529 | 4x |
"cohort_size" = "Cohort size", |
| 530 | 4x |
"data" = "Observed combination therapy data", |
| 531 | 4x |
"startingDose" = "Starting dose", |
| 532 | 4x |
"increments" = "Escalation rule", |
| 533 | 4x |
"stopping" = "Stopping rule", |
| 534 | 4x |
"pl_cohort_size" = "Use of placebo" |
| 535 |
), |
|
| 536 | 4x |
sections = sections[["combo"]], |
| 537 |
... |
|
| 538 |
), |
|
| 539 | 4x |
h_markdown_header(sections["other"], level = level), |
| 540 | 4x |
ifelse( |
| 541 | 4x |
x@first_cohort_mono_only, |
| 542 | 4x |
paste0( |
| 543 | 4x |
"No combination dosing may occur until the results of at least one ", |
| 544 | 4x |
"monotherapy cohort are available.\n\n" |
| 545 |
), |
|
| 546 | 4x |
"Simultaneous combination and monotherapy dosing is permitted from the outset.\n\n" |
| 547 |
), |
|
| 548 | 4x |
ifelse( |
| 549 | 4x |
x@same_dose_for_start, |
| 550 | 4x |
paste0( |
| 551 | 4x |
"When monotherapy and combination therapy are used in the same cohort ", |
| 552 | 4x |
"for the first time, the same dose must be used for both regimens.\n\n" |
| 553 |
), |
|
| 554 | 4x |
paste0( |
| 555 | 4x |
"When monotherapy and combination therapy are used in the same cohort ", |
| 556 | 4x |
"for the first time, the use of a different dose in each regimen is permitted.\n\n" |
| 557 |
) |
|
| 558 |
), |
|
| 559 | 4x |
ifelse( |
| 560 | 4x |
x@same_dose_for_all, |
| 561 | 4x |
paste0( |
| 562 | 4x |
"Whenever monotherapy and combination therapy are used in the same cohort, ", |
| 563 | 4x |
"the same dose must be used for both regimens.\n\n" |
| 564 |
), |
|
| 565 | 4x |
paste0( |
| 566 | 4x |
"Whenever monotherapy and combination therapy are used in the same cohort, ", |
| 567 | 4x |
"the use of a different dose in each regimen is permitted.\n\n" |
| 568 |
) |
|
| 569 |
) |
|
| 570 |
) |
|
| 571 | ||
| 572 | 4x |
if (asis) {
|
| 573 | 2x |
rv <- knitr::asis_output(rv) |
| 574 |
} |
|
| 575 | 4x |
rv |
| 576 |
} |
|
| 577 | ||
| 578 |
# TDsamplesDesign ---- |
|
| 579 | ||
| 580 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 581 |
#' @inheritParams knit_print.RuleDesign |
|
| 582 |
#' @rdname knit_print |
|
| 583 |
#' @export |
|
| 584 |
#' @method knit_print TDsamplesDesign |
|
| 585 |
knit_print.TDsamplesDesign <- function( |
|
| 586 |
x, |
|
| 587 |
..., |
|
| 588 |
level = 2L, |
|
| 589 |
title = "Design", |
|
| 590 |
sections = NA, |
|
| 591 |
asis = TRUE |
|
| 592 |
) {
|
|
| 593 | 6x |
h_knit_print_design( |
| 594 | 6x |
x, |
| 595 |
..., |
|
| 596 | 6x |
level = level, |
| 597 | 6x |
title = title, |
| 598 | 6x |
default_sections = c( |
| 599 | 6x |
"nextBest" = "Dose recommendation", |
| 600 | 6x |
"cohort_size" = "Cohort size", |
| 601 | 6x |
"data" = "Observed data", |
| 602 | 6x |
"startingDose" = "Starting dose", |
| 603 | 6x |
"model" = "Dose toxicity model", |
| 604 | 6x |
"stopping" = "Stopping rule", |
| 605 | 6x |
"increments" = "Escalation rule", |
| 606 | 6x |
"pl_cohort_size" = "Use of placebo" |
| 607 |
), |
|
| 608 | 6x |
user_sections = sections, |
| 609 | 6x |
asis = asis |
| 610 |
) |
|
| 611 |
} |
|
| 612 | ||
| 613 |
# DualResponsesDesign ---- |
|
| 614 | ||
| 615 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 616 |
#' @inheritParams knit_print.RuleDesign |
|
| 617 |
#' @rdname knit_print |
|
| 618 |
#' @export |
|
| 619 |
#' @method knit_print DualResponsesDesign |
|
| 620 |
knit_print.DualResponsesDesign <- function( |
|
| 621 |
x, |
|
| 622 |
..., |
|
| 623 |
level = 2L, |
|
| 624 |
title = "Design", |
|
| 625 |
sections = NA, |
|
| 626 |
asis = TRUE |
|
| 627 |
) {
|
|
| 628 | 8x |
h_knit_print_design( |
| 629 | 8x |
x, |
| 630 |
..., |
|
| 631 | 8x |
level = level, |
| 632 | 8x |
title = title, |
| 633 | 8x |
default_sections = c( |
| 634 | 8x |
"nextBest" = "Dose recommendation", |
| 635 | 8x |
"cohort_size" = "Cohort size", |
| 636 | 8x |
"data" = "Observed data", |
| 637 | 8x |
"startingDose" = "Starting dose", |
| 638 | 8x |
"increments" = "Escalation rule", |
| 639 | 8x |
"stopping" = "Stopping rule", |
| 640 | 8x |
"model" = "Dose-toxicity model", |
| 641 | 8x |
"eff_model" = "Dose-efficacy model", |
| 642 | 8x |
"pl_cohort_size" = "Use of placebo" |
| 643 |
), |
|
| 644 | 8x |
ignore_sections = c("model", "eff_model"),
|
| 645 | 8x |
sections = sections, |
| 646 | 8x |
asis = asis |
| 647 |
) |
|
| 648 |
} |
|
| 649 | ||
| 650 |
# DualResponsesSamplesDesign ---- |
|
| 651 | ||
| 652 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 653 |
#' @inheritParams knit_print.RuleDesign |
|
| 654 |
#' @rdname knit_print |
|
| 655 |
#' @export |
|
| 656 |
#' @method knit_print DualResponsesSamplesDesign |
|
| 657 |
knit_print.DualResponsesSamplesDesign <- function( |
|
| 658 |
x, |
|
| 659 |
..., |
|
| 660 |
level = 2L, |
|
| 661 |
title = "Design", |
|
| 662 |
sections = NA, |
|
| 663 |
asis = TRUE |
|
| 664 |
) {
|
|
| 665 | 8x |
h_knit_print_design( |
| 666 | 8x |
x, |
| 667 |
..., |
|
| 668 | 8x |
level = level, |
| 669 | 8x |
title = title, |
| 670 | 8x |
default_sections = c( |
| 671 | 8x |
"nextBest" = "Dose recommendation", |
| 672 | 8x |
"cohort_size" = "Cohort size", |
| 673 | 8x |
"data" = "Observed data", |
| 674 | 8x |
"startingDose" = "Starting dose", |
| 675 | 8x |
"increments" = "Escalation rule", |
| 676 | 8x |
"stopping" = "Stopping rule", |
| 677 | 8x |
"model" = "Dose-toxicity model", |
| 678 | 8x |
"eff_model" = "Dose-efficacy model", |
| 679 | 8x |
"pl_cohort_size" = "Use of placebo" |
| 680 |
), |
|
| 681 | 8x |
ignore_sections = c("model", "eff_model"),
|
| 682 | 8x |
sections = sections, |
| 683 | 8x |
asis = asis |
| 684 |
) |
|
| 685 |
} |
|
| 686 | ||
| 687 |
# RuleDesignOrdinal ---- |
|
| 688 | ||
| 689 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 690 |
#' @inheritParams knit_print.RuleDesign |
|
| 691 |
#' @rdname knit_print |
|
| 692 |
#' @export |
|
| 693 |
#' @method knit_print RuleDesignOrdinal |
|
| 694 |
knit_print.RuleDesignOrdinal <- function( |
|
| 695 |
x, |
|
| 696 |
..., |
|
| 697 |
level = 2L, |
|
| 698 |
title = "Design", |
|
| 699 |
sections = NA, |
|
| 700 |
asis = TRUE |
|
| 701 |
) {
|
|
| 702 | 9x |
h_knit_print_design( |
| 703 | 9x |
x, |
| 704 |
..., |
|
| 705 | 9x |
level = 2L, |
| 706 | 9x |
title = "Design", |
| 707 | 9x |
default_sections = c( |
| 708 | 9x |
"next_best" = "Dose recommendation", |
| 709 | 9x |
"cohort_size" = "Cohort size", |
| 710 | 9x |
"data" = "Observed data", |
| 711 | 9x |
"starting_dose" = "Starting dose" |
| 712 |
), |
|
| 713 | 9x |
user_sections = sections, |
| 714 | 9x |
asis = asis |
| 715 |
) |
|
| 716 |
} |
| 1 |
#' Update [`DualEndpoint`] class model components with regard to biomarker |
|
| 2 |
#' regression variance. |
|
| 3 |
#' |
|
| 4 |
#' @description `r lifecycle::badge("stable")`
|
|
| 5 |
#' |
|
| 6 |
#' A simple helper function that takes [`DualEndpoint`] model existing components |
|
| 7 |
#' (`priormodel`, `modelspecs`, `init`, `sample`), and updates them with regard to |
|
| 8 |
#' to biomarker regression variance `sigma2W`. |
|
| 9 |
#' |
|
| 10 |
#' @param use_fixed (`flag`)\cr indicates whether a fixed value for the biomarker |
|
| 11 |
#' regression variance `sigma2W` should be used or not. If `sigma2W` is not |
|
| 12 |
#' supposed to be a fixed value, a prior distribution from the Inverse-Gamma |
|
| 13 |
#' distribution will be used. See the details below, under `sigma2W` argument. |
|
| 14 |
#' @param sigma2W (`numeric`)\cr the biomarker variance. Either a fixed value or |
|
| 15 |
#' Inverse-Gamma distribution parameters, i.e. vector with two elements named |
|
| 16 |
#' `a` and `b`. |
|
| 17 |
#' @param comp (`list`)\cr a named list with model components that will be updated. |
|
| 18 |
#' The names should be: `priormodel`, `modelspecs`, `init`, `sample`. For |
|
| 19 |
#' definitions of the components, see [`GeneralModel`] class. |
|
| 20 |
#' The `modelspecs` and `init` components on `comp` list are specified up to |
|
| 21 |
#' the body of corresponding `GeneralModel@modelspecs` and `GeneralModel@init` |
|
| 22 |
#' functions. These bodies are simply a lists itself. |
|
| 23 |
#' |
|
| 24 |
#' @return `list` with updated model components. |
|
| 25 |
#' |
|
| 26 |
#' @export |
|
| 27 |
h_model_dual_endpoint_sigma2W <- function( |
|
| 28 |
use_fixed, # nolintr |
|
| 29 |
sigma2W, |
|
| 30 |
comp |
|
| 31 |
) {
|
|
| 32 | 211x |
if (use_fixed) {
|
| 33 | 114x |
assert_number(sigma2W, lower = 0 + .Machine$double.xmin, finite = TRUE) |
| 34 | 112x |
comp$modelspecs$precW <- 1 / sigma2W |
| 35 |
} else {
|
|
| 36 | 97x |
assert_true(h_test_named_numeric(sigma2W, permutation.of = c("a", "b")))
|
| 37 | 96x |
comp$priormodel <- h_jags_join_models( |
| 38 | 96x |
comp$priormodel, |
| 39 | 96x |
function() {
|
| 40 | ! |
precW ~ dgamma(precWa, precWb) |
| 41 |
} |
|
| 42 |
) |
|
| 43 | 96x |
comp$modelspecs$precWa <- sigma2W[["a"]] |
| 44 | 96x |
comp$modelspecs$precWb <- sigma2W[["b"]] |
| 45 | 96x |
comp$init$precW <- 1 |
| 46 | 96x |
comp$sample <- c(comp$sample, "precW") |
| 47 |
} |
|
| 48 | 208x |
comp |
| 49 |
} |
|
| 50 | ||
| 51 |
#' Update [`DualEndpoint`] class model components with regard to DLT and biomarker |
|
| 52 |
#' correlation. |
|
| 53 |
#' |
|
| 54 |
#' @description `r lifecycle::badge("stable")`
|
|
| 55 |
#' |
|
| 56 |
#' A simple helper function that takes [`DualEndpoint`] model existing components |
|
| 57 |
#' (`priormodel`, `modelspecs`, `init`, `sample`), and updates them with regard to |
|
| 58 |
#' DLT and biomarker correlation `rho`. |
|
| 59 |
#' |
|
| 60 |
#' @param use_fixed (`flag`)\cr indicates whether a fixed value for DLT and |
|
| 61 |
#' biomarker correlation `rho` should be used or not. If `rho` is not supposed |
|
| 62 |
#' to be a fixed value, a prior distribution from the scaled Beta family will |
|
| 63 |
#' be used. See the details below, under `rho` argument. |
|
| 64 |
#' @param rho (`numeric`)\cr DLT and biomarker correlation. It must be either a |
|
| 65 |
#' fixed value (between `-1` and `1`), or a named vector with two elements, |
|
| 66 |
#' named `a` and `b` for the Beta prior on the transformation |
|
| 67 |
#' `kappa = (rho + 1) / 2`, which is in `(0, 1)`. For example, `a = 1, b = 1` |
|
| 68 |
#' leads to a uniform prior on `rho`. |
|
| 69 |
#' @param comp (`list`)\cr a named list with model components that will be updated. |
|
| 70 |
#' The names should be: `priormodel`, `modelspecs`, `init`, `sample`. For |
|
| 71 |
#' definitions of the components, see [`GeneralModel`] class. |
|
| 72 |
#' The `modelspecs` and `init` components on `comp` list are specified up to |
|
| 73 |
#' the body of corresponding `GeneralModel@modelspecs` and `GeneralModel@init` |
|
| 74 |
#' functions. These bodies are simply a lists itself. |
|
| 75 |
#' |
|
| 76 |
#' @return A `list` with updated model components. |
|
| 77 |
#' |
|
| 78 |
#' @export |
|
| 79 |
h_model_dual_endpoint_rho <- function(use_fixed, rho, comp) {
|
|
| 80 | 212x |
rmin <- .Machine$double.xmin |
| 81 | 212x |
if (use_fixed) {
|
| 82 | 115x |
assert_number(rho, lower = -1 + rmin, upper = 1 - rmin) |
| 83 | 112x |
comp$modelspecs$rho <- rho |
| 84 |
} else {
|
|
| 85 | 97x |
assert_true(h_test_named_numeric(rho, permutation.of = c("a", "b")))
|
| 86 | 96x |
comp$priormodel <- h_jags_join_models( |
| 87 | 96x |
comp$priormodel, |
| 88 | 96x |
function() {
|
| 89 | ! |
kappa ~ dbeta(rhoa, rhob) |
| 90 | ! |
rho <- 2 * kappa - 1 |
| 91 |
} |
|
| 92 |
) |
|
| 93 | 96x |
comp$modelspecs$rhoa <- rho[["a"]] |
| 94 | 96x |
comp$modelspecs$rhob <- rho[["b"]] |
| 95 | 96x |
comp$init$kappa <- 0.5 |
| 96 | 96x |
comp$sample <- c(comp$sample, "rho") |
| 97 |
} |
|
| 98 | 208x |
comp |
| 99 |
} |
|
| 100 | ||
| 101 |
#' Update certain components of [`DualEndpoint`] model with regard to prior variance |
|
| 102 |
#' factor of the random walk. |
|
| 103 |
#' |
|
| 104 |
#' @description `r lifecycle::badge("stable")`
|
|
| 105 |
#' |
|
| 106 |
#' A simple helper function that takes [`DualEndpoint`] object and updates |
|
| 107 |
#' `priormodel`, `modelspecs`, `init`, `sample` slots according to the random walk |
|
| 108 |
#' variance. |
|
| 109 |
#' |
|
| 110 |
#' @param use_fixed (`flag`)\cr indicates whether a fixed value for |
|
| 111 |
#' `sigma2betaW` should be used or not. If `sigma2betaW` is not supposed |
|
| 112 |
#' to be a fixed value, a prior distribution from the Inverse-Gamma distribution |
|
| 113 |
#' will be used. See the details below, under `sigma2betaW` argument. |
|
| 114 |
#' @param sigma2betaW (`numeric`)\cr the prior variance factor of the random walk |
|
| 115 |
#' prior for the biomarker model. Either a fixed value or Inverse-Gamma distribution |
|
| 116 |
#' parameters, i.e. vector with two elements named `a` and `b`. |
|
| 117 |
#' @param de (`DualEnpoint`)\cr dual endpoint model whose slots will be updated. |
|
| 118 |
#' |
|
| 119 |
#' @return A [`DualEndpoint`] model with updated `priormodel`, `modelspecs`, |
|
| 120 |
#' `init`, `sample` slots. |
|
| 121 |
#' |
|
| 122 |
#' @seealso [`DualEndpointRW`]. |
|
| 123 |
#' @export |
|
| 124 |
h_model_dual_endpoint_sigma2betaW <- function( |
|
| 125 |
use_fixed, # nolintr |
|
| 126 |
sigma2betaW, |
|
| 127 |
de |
|
| 128 |
) {
|
|
| 129 | 62x |
modelspecs <- de@modelspecs |
| 130 | 62x |
init <- de@init |
| 131 | ||
| 132 | 62x |
if (use_fixed) {
|
| 133 | 50x |
assert_number(sigma2betaW, lower = 0 + .Machine$double.xmin, finite = TRUE) |
| 134 | 48x |
ms <- list(precBetaW = 1 / sigma2betaW) |
| 135 |
} else {
|
|
| 136 | 12x |
assert_true(h_test_named_numeric(sigma2betaW, permutation.of = c("a", "b")))
|
| 137 |
# gamma prior for random walk precision. |
|
| 138 | 11x |
de@priormodel <- h_jags_join_models( |
| 139 | 11x |
de@priormodel, |
| 140 | 11x |
function() {
|
| 141 | ! |
precBetaW ~ dgamma(precBetaWa, precBetaWb) |
| 142 |
} |
|
| 143 |
) |
|
| 144 | 11x |
ms <- list(precBetaWa = sigma2betaW[["a"]], precBetaWb = sigma2betaW[["b"]]) |
| 145 | 11x |
de@init <- function(y) {
|
| 146 | 10x |
c(init(y), list(precBetaW = 1)) |
| 147 |
} |
|
| 148 | 11x |
de@sample <- c(de@sample, "precBetaW") |
| 149 |
} |
|
| 150 | 59x |
de@modelspecs <- function(from_prior) {
|
| 151 | 45x |
c(modelspecs(from_prior), ms) |
| 152 |
} |
|
| 153 | 59x |
de |
| 154 |
} |
|
| 155 | ||
| 156 |
#' Update certain components of [`DualEndpoint`] model with regard to parameters |
|
| 157 |
#' of the function that models dose-biomarker relationship defined in the |
|
| 158 |
#' [`DualEndpointBeta`] class. |
|
| 159 |
#' |
|
| 160 |
#' @description `r lifecycle::badge("stable")`
|
|
| 161 |
#' |
|
| 162 |
#' A simple helper function that takes [`DualEndpoint`] object and updates |
|
| 163 |
#' `use_fixed`, `priormodel`, `modelspecs`, `init`, `sample` slots with regard |
|
| 164 |
#' to a given parameter of the dose-biomarker relationship \eqn{f(x)} defined in
|
|
| 165 |
#' the [`DualEndpointBeta`] class. This update solely depends on whether a given |
|
| 166 |
#' parameter's value `param` is a fixed-valued scalar or two-elements numeric |
|
| 167 |
#' vector. In the later case, it is assumed that `param` represents two |
|
| 168 |
#' parameters of a probability distribution that will be used in `priormodel` |
|
| 169 |
#' function to generate values for the `param_name` parameter of \eqn{f(x)}.
|
|
| 170 |
#' See the help page for [`DualEndpointBeta`] class for more details. |
|
| 171 |
#' |
|
| 172 |
#' @param param (`numeric`)\cr the value of a given `param_name` parameter of |
|
| 173 |
#' the dose-biomarker relationship function \eqn{f(x)}. Either a fixed-valued
|
|
| 174 |
#' scalar or vector with two elements that are the parameters of a probability |
|
| 175 |
#' distribution that will be used in `priormodel` function to generate values |
|
| 176 |
#' for the `param_name` parameter of \eqn{f(x)}.
|
|
| 177 |
#' @param param_name (`string`)\cr the name of the parameter of \eqn{f(x)},
|
|
| 178 |
#' whose value depends on `param`. |
|
| 179 |
#' @param param_suffix (`character`)\cr the two suffixes to be appended to |
|
| 180 |
#' the elements of `param_name` and then used when updating `modelspecs`. |
|
| 181 |
#' The value of this argument is ignored when `param` is a scalar. |
|
| 182 |
#' @param priormodel (`function` or `NULL`)\cr a function representing the |
|
| 183 |
#' `JAGS` prior specification that will be appended to existing |
|
| 184 |
#' `de@priormodel` specification if `param` is not a scalar. Otherwise, |
|
| 185 |
#' `de@priormodel` remains unchanged. |
|
| 186 |
#' @param de (`DualEnpoint`)\cr dual endpoint model whose slots will be updated. |
|
| 187 |
#' |
|
| 188 |
#' @return A [`DualEndpoint`] model with updated `use_fixed`, `priormodel`, |
|
| 189 |
#' `modelspecs`, `init`, `sample` slots. |
|
| 190 |
#' |
|
| 191 |
#' @export |
|
| 192 |
h_model_dual_endpoint_beta <- function( |
|
| 193 |
param, |
|
| 194 |
param_name, |
|
| 195 |
param_suffix = c("_low", "_high"),
|
|
| 196 |
priormodel = NULL, |
|
| 197 |
de |
|
| 198 |
) {
|
|
| 199 | 179x |
assert_numeric(param, min.len = 1, max.len = 2, any.missing = FALSE) |
| 200 | 178x |
assert_string(param_name) |
| 201 | 177x |
assert_class(de, "DualEndpoint") |
| 202 | ||
| 203 | 177x |
use_fixed <- setNames(test_number(param), param_name) |
| 204 | 177x |
modelspecs <- de@modelspecs |
| 205 | 177x |
init <- de@init |
| 206 | ||
| 207 | 177x |
if (use_fixed) {
|
| 208 | 81x |
ms <- setNames(list(param), param_name) |
| 209 |
} else {
|
|
| 210 | 96x |
assert_character(param_suffix, len = 2, unique = TRUE, any.missing = FALSE) |
| 211 | 95x |
assert_function(priormodel) |
| 212 | 94x |
param_name2 <- paste0(param_name, param_suffix) |
| 213 | ||
| 214 | 94x |
de@priormodel <- h_jags_join_models( |
| 215 | 94x |
de@priormodel, |
| 216 | 94x |
priormodel |
| 217 |
) |
|
| 218 | 94x |
ms <- setNames(list(param[1], param[2]), param_name2) |
| 219 | 94x |
de@init <- function(y) {
|
| 220 | 21x |
c(init(y), setNames(list(mean(param)), param_name)) |
| 221 |
} |
|
| 222 | 94x |
de@sample <- c(de@sample, param_name) |
| 223 |
} |
|
| 224 | 175x |
de@modelspecs <- function(from_prior) {
|
| 225 | 60x |
c(modelspecs(from_prior), ms) |
| 226 |
} |
|
| 227 | 175x |
de@use_fixed <- c(de@use_fixed, use_fixed) |
| 228 | 175x |
de |
| 229 |
} |
|
| 230 | ||
| 231 |
#' Convert an ordinal CRM model to the Equivalent Binary CRM Model for a Specific |
|
| 232 |
#' Grade |
|
| 233 |
#' |
|
| 234 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 235 |
#' |
|
| 236 |
#' A simple helper function that takes a [`LogisticLogNormalOrdinal`] and an |
|
| 237 |
#' integer grade and converts them to the equivalent `LogisticLogNormal` model. |
|
| 238 |
#' |
|
| 239 |
#' @param x (`LogisticLogNormalOrdinal`)\cr the `LogisticLogNormalOrdinal` |
|
| 240 |
#' model to covert |
|
| 241 |
#' @param grade (`integer`)\cr the toxicity grade for which the equivalent model |
|
| 242 |
#' is required. |
|
| 243 |
#' @return A [`LogisticLogNormal`] model. |
|
| 244 |
#' |
|
| 245 |
#' @export |
|
| 246 |
h_convert_ordinal_model <- function(x, grade) {
|
|
| 247 |
# Validate |
|
| 248 | 23x |
assert_integer(grade, len = 1, lower = 1) |
| 249 | 23x |
assert_class(x, "LogisticLogNormalOrdinal") |
| 250 |
# Execute |
|
| 251 | 23x |
LogisticLogNormal( |
| 252 | 23x |
mean = x@params@mean[-grade], |
| 253 | 23x |
cov = x@params@cov[-grade, -grade], |
| 254 | 23x |
ref_dose = x@ref_dose |
| 255 |
) |
|
| 256 |
} |
| 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 | 2352x |
if (is.list(slot(obj, slot_name))) {
|
| 76 | 61x |
return( |
| 77 | 61x |
lapply( |
| 78 | 61x |
slot(obj, slot_name), |
| 79 | 61x |
function(x) {
|
| 80 | 116x |
if (is.data.frame(x)) {
|
| 81 | 11x |
return(x) |
| 82 | 61x |
} else if ( |
| 83 | 105x |
is.list(x) && |
| 84 | 105x |
stringr::str_detect(class(x)[1], stringr::fixed("tbl_"))
|
| 85 |
) {
|
|
| 86 |
# Already tidied to a list. |
|
| 87 | ! |
return(x) |
| 88 | 105x |
} else if (is.numeric(x) | is.character(x)) {
|
| 89 |
# tidy.numeric & tidy.character are deprecated |
|
| 90 | 6x |
return(tibble::tibble(!!{{ slot_name }} := x))
|
| 91 |
} else {
|
|
| 92 | 99x |
return(x %>% tidy()) |
| 93 |
} |
|
| 94 |
} |
|
| 95 |
) |
|
| 96 |
) |
|
| 97 |
} |
|
| 98 | 2291x |
if (is(slot(obj, slot_name), "CrmPackClass")) {
|
| 99 | 302x |
rv <- slot(obj, slot_name) %>% |
| 100 | 302x |
tidy() |
| 101 |
} else {
|
|
| 102 | 1989x |
if (is.null(col)) {
|
| 103 | 1989x |
col <- slot_name |
| 104 |
} |
|
| 105 | 1989x |
rv <- tibble::tibble({{ col }} := slot(obj, slot_name))
|
| 106 |
} |
|
| 107 | 2291x |
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 | 2291x |
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 | 676x |
slot_names <- slotNames(obj) |
| 132 | 676x |
rv <- list() |
| 133 | 676x |
for (nm in slot_names) {
|
| 134 | 2552x |
if (!is.function(slot(obj, nm))) {
|
| 135 | 2292x |
rv[[nm]] <- h_tidy_slot(obj, nm, ...) |
| 136 |
} |
|
| 137 |
} |
|
| 138 |
# Column bind of all list elements have the same number of rows |
|
| 139 | 676x |
if (length(rv) > 1 && length(unique(sapply(rv, nrow))) == 1) {
|
| 140 | 369x |
rv <- rv %>% dplyr::bind_cols() # nolint |
| 141 |
} |
|
| 142 | 676x |
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 | 1186x |
cls <- class(obj) |
| 162 | 1186x |
class(d) <- c(paste0("tbl_", cls[1]), class(d))
|
| 163 | 1186x |
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 | 242x |
vals <- x %>% dplyr::pull({{ col }})
|
| 207 | 242x |
tibble( |
| 208 | 242x |
{{ min_col }} := c(range_min, vals),
|
| 209 | 242x |
{{ max_col }} := c(vals, range_max)
|
| 210 |
) |
|
| 211 |
} |
| 1 |
#' @include helpers_knitr_CohortSize.R |
|
| 2 | ||
| 3 |
# Increments ---- |
|
| 4 | ||
| 5 |
#' Render a `IncrementsRelative` Object |
|
| 6 |
#' |
|
| 7 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 8 |
#' @inherit knit_print.CohortSizeConst return |
|
| 9 |
#' @param ... passed to [knitr::kable()] |
|
| 10 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 11 |
#' @section Usage Notes: |
|
| 12 |
#' The default value of `col.names` is `c("Min", "Max", "Increment")` and that
|
|
| 13 |
#' of `caption` is `"Defined by highest dose administered so far"`. These |
|
| 14 |
#' values can be overridden by passing `col.names` and `caption` in the function |
|
| 15 |
#' call. |
|
| 16 |
#' @export |
|
| 17 |
#' @method knit_print IncrementsRelative |
|
| 18 |
#' @rdname knit_print |
|
| 19 |
knit_print.IncrementsRelative <- function(x, ..., asis = TRUE) {
|
|
| 20 | 66x |
assert_flag(asis) |
| 21 | ||
| 22 | 64x |
param <- list(...) |
| 23 | 64x |
if (!("col.names" %in% names(param))) {
|
| 24 | 64x |
param[["col.names"]] <- c("Min", "Max", "Increment")
|
| 25 |
} |
|
| 26 | 64x |
if (!("caption" %in% names(param))) {
|
| 27 | 64x |
param[["caption"]] <- "Defined by highest dose administered so far" |
| 28 |
} |
|
| 29 | 64x |
x <- tidy(x) |
| 30 | 64x |
param[["x"]] <- x |
| 31 | 64x |
rv <- kableExtra::add_header_above( |
| 32 | 64x |
do.call(knitr::kable, param), |
| 33 | 64x |
c("Dose" = 2, " " = 1)
|
| 34 |
) |
|
| 35 | 64x |
rv <- paste0(rv, "\n\n") |
| 36 | ||
| 37 | 64x |
if (asis) {
|
| 38 | 6x |
rv <- knitr::asis_output(rv) |
| 39 |
} |
|
| 40 | 64x |
rv |
| 41 |
} |
|
| 42 | ||
| 43 |
#' Render an `IncrementsRelativeDLT` object |
|
| 44 |
#' |
|
| 45 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 46 |
#' @inherit knit_print.CohortSizeConst return |
|
| 47 |
#' @param ... passed to [knitr::kable()] |
|
| 48 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 49 |
#' @section Usage Notes: |
|
| 50 |
#' The default value of `col.names` is `c("Min", "Max", "Increment")` and that
|
|
| 51 |
#' of `caption` is `"Defined by number of DLTs reported so far"`. These values |
|
| 52 |
#' can be overridden by passing `col.names` and `caption` in the function call. |
|
| 53 |
#' @export |
|
| 54 |
#' @method knit_print IncrementsRelativeDLT |
|
| 55 |
#' @rdname knit_print |
|
| 56 |
knit_print.IncrementsRelativeDLT <- function(x, ..., asis = TRUE) {
|
|
| 57 | 10x |
assert_flag(asis) |
| 58 | ||
| 59 | 8x |
param <- list(...) |
| 60 | 8x |
if (!("col.names" %in% names(param))) {
|
| 61 | 8x |
param[["col.names"]] <- c("Min", "Max", "Increment")
|
| 62 |
} |
|
| 63 | 8x |
if (!("caption" %in% names(param))) {
|
| 64 | 8x |
param[["caption"]] <- "Defined by number of DLTs reported so far" |
| 65 |
} |
|
| 66 | ||
| 67 | 8x |
param[["x"]] <- tidy(x) |
| 68 | 8x |
rv <- kableExtra::add_header_above( |
| 69 | 8x |
do.call(knitr::kable, param), |
| 70 | 8x |
c("No DLTs" = 2, " " = 1)
|
| 71 |
) |
|
| 72 | 8x |
rv <- paste0(rv, "\n\n") |
| 73 | ||
| 74 | 8x |
if (asis) {
|
| 75 | 4x |
rv <- knitr::asis_output(rv) |
| 76 |
} |
|
| 77 | 8x |
rv |
| 78 |
} |
|
| 79 | ||
| 80 |
#' Render an `IncrementsDoseLevels` object |
|
| 81 |
#' |
|
| 82 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 83 |
#' @inherit knit_print.CohortSizeConst return |
|
| 84 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 85 |
#' @export |
|
| 86 |
#' @rdname knit_print |
|
| 87 |
#' @method knit_print IncrementsDoseLevels |
|
| 88 |
knit_print.IncrementsDoseLevels <- function(x, ..., asis = TRUE) {
|
|
| 89 | 6x |
assert_flag(asis) |
| 90 | ||
| 91 | 4x |
rv <- paste0( |
| 92 | 4x |
"The maximum increment between cohorts is ", |
| 93 | 4x |
x@levels, |
| 94 | 4x |
ifelse(x@levels == 1, " level", " levels"), |
| 95 | 4x |
" relative to the ", |
| 96 | 4x |
ifelse( |
| 97 | 4x |
x@basis_level == "last", |
| 98 | 4x |
"dose used in the previous cohort.", |
| 99 | 4x |
"highest dose used so far." |
| 100 |
), |
|
| 101 | 4x |
"\n\n" |
| 102 |
) |
|
| 103 | ||
| 104 | 4x |
if (asis) {
|
| 105 | 2x |
rv <- knitr::asis_output(rv) |
| 106 |
} |
|
| 107 | 4x |
rv |
| 108 |
} |
|
| 109 | ||
| 110 |
#' Render an `IncrementsHSRBeta` object |
|
| 111 |
#' |
|
| 112 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 113 |
#' @inherit knit_print.CohortSizeConst return |
|
| 114 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 115 |
#' @export |
|
| 116 |
#' @method knit_print IncrementsHSRBeta |
|
| 117 |
#' @rdname knit_print |
|
| 118 |
knit_print.IncrementsHSRBeta <- function(x, ..., asis = TRUE) {
|
|
| 119 | 6x |
assert_flag(asis) |
| 120 | ||
| 121 | 4x |
rv <- paste0( |
| 122 | 4x |
"The maximum increment is defined by a hard safety rule, independent of the CRM model, ", |
| 123 | 4x |
" and based on a beta(",
|
| 124 | 4x |
x@a, |
| 125 |
", ", |
|
| 126 | 4x |
x@b, |
| 127 |
") ", |
|
| 128 | 4x |
"prior with a target toxicity rate of ", |
| 129 | 4x |
x@target, |
| 130 | 4x |
" and a probability threshold of ", |
| 131 | 4x |
x@prob, |
| 132 | 4x |
".\n\n" |
| 133 |
) |
|
| 134 | ||
| 135 | 4x |
if (asis) {
|
| 136 | 2x |
rv <- knitr::asis_output(rv) |
| 137 |
} |
|
| 138 | 4x |
rv |
| 139 |
} |
|
| 140 | ||
| 141 |
#' Render an `IncrementsMin` object |
|
| 142 |
#' |
|
| 143 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 144 |
#' @inherit knit_print.CohortSizeConst return |
|
| 145 |
#' @param ... passed through to the `knit_print` methods of the constituent |
|
| 146 |
#' rules |
|
| 147 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 148 |
#' @export |
|
| 149 |
#' @method knit_print IncrementsMin |
|
| 150 |
#' @rdname knit_print |
|
| 151 |
knit_print.IncrementsMin <- function(x, ..., asis = TRUE) {
|
|
| 152 | 6x |
assert_flag(asis) |
| 153 | ||
| 154 | 4x |
rv <- paste0( |
| 155 | 4x |
"The minimum of the increments defined in the following rules:", |
| 156 | 4x |
paste0( |
| 157 | 4x |
lapply( |
| 158 | 4x |
x@increments_list, |
| 159 | 4x |
function(x, ...) {
|
| 160 | 8x |
knit_print(x, asis = asis, ...) |
| 161 |
} |
|
| 162 |
), |
|
| 163 | 4x |
collapse = "\n" |
| 164 |
), |
|
| 165 | 4x |
"\n\n", |
| 166 | 4x |
paste = "\n" |
| 167 |
) |
|
| 168 | ||
| 169 | 4x |
if (asis) {
|
| 170 | 2x |
rv <- knitr::asis_output(rv) |
| 171 |
} |
|
| 172 | 4x |
rv |
| 173 |
} |
|
| 174 | ||
| 175 |
#' Render an `IncrementsOrdinal` object |
|
| 176 |
#' @inherit knit_print.CohortSizeConst return |
|
| 177 |
#' @param ... passed through to the `knit_print` method of the standard rule |
|
| 178 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 179 |
#' @export |
|
| 180 |
#' @method knit_print IncrementsOrdinal |
|
| 181 |
#' @rdname knit_print |
|
| 182 |
knit_print.IncrementsOrdinal <- function(x, ..., asis = TRUE) {
|
|
| 183 | 10x |
assert_flag(asis) |
| 184 | ||
| 185 | 8x |
rv <- paste0( |
| 186 | 8x |
"Based on a toxicity grade of ", |
| 187 | 8x |
x@grade, |
| 188 |
": ", |
|
| 189 | 8x |
paste0(knit_print(x@rule, asis = asis, ...), collapse = "\n"), |
| 190 | 8x |
"\n\n", |
| 191 | 8x |
paste = "\n" |
| 192 |
) |
|
| 193 | ||
| 194 | 8x |
if (asis) {
|
| 195 | 2x |
rv <- knitr::asis_output(rv) |
| 196 |
} |
|
| 197 | 8x |
rv |
| 198 |
} |
|
| 199 | ||
| 200 |
#' Render an `IncrementsRelativeParts` object |
|
| 201 |
#' |
|
| 202 |
#' @inherit knit_print.CohortSizeConst return |
|
| 203 |
#' @param tox_label (`character`)\cr The word used to describe toxicities. See |
|
| 204 |
#' Usage Notes below. |
|
| 205 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 206 |
#' @section Usage Notes: |
|
| 207 |
#' `label` defines how toxicities are described. |
|
| 208 |
#' |
|
| 209 |
#' It should be a character vector of length 1 or 2. If of length 2, the first |
|
| 210 |
#' element describes a single toxicity and the second describes all other |
|
| 211 |
#' toxicity counts. If of length 1, the character `s` is appended to the value |
|
| 212 |
#' describing a single toxicity. |
|
| 213 |
#' |
|
| 214 |
#' @export |
|
| 215 |
#' @method knit_print IncrementsRelativeParts |
|
| 216 |
#' @rdname knit_print |
|
| 217 |
knit_print.IncrementsRelativeParts <- function( |
|
| 218 |
x, |
|
| 219 |
..., |
|
| 220 |
asis = TRUE, |
|
| 221 |
tox_label = c("toxicity", "toxicities")
|
|
| 222 |
) {
|
|
| 223 | 7x |
assert_flag(asis) |
| 224 | ||
| 225 | 5x |
tox_label <- h_prepare_labels(tox_label) |
| 226 | 5x |
rv <- paste0( |
| 227 | 5x |
"The maximum increment in Part 1 is defined by the `part1Ladder` slot of ", |
| 228 | 5x |
"the associated `DataParts` object.\n\n", |
| 229 | 5x |
"If no ", |
| 230 | 5x |
tox_label[2], |
| 231 | 5x |
" are reported in Part 1, the starting dose for Part 2 ", |
| 232 | 5x |
"will be ", |
| 233 | 5x |
ifelse( |
| 234 | 5x |
x@clean_start == 0, |
| 235 | 5x |
"the highest dose used in Part 1.\n\n", |
| 236 | 5x |
paste0( |
| 237 | 5x |
x@clean_start, |
| 238 | 5x |
ifelse(abs(x@clean_start) == 1, " dose ", " doses "), |
| 239 | 5x |
ifelse(x@clean_start < 0, "below ", "above "), |
| 240 | 5x |
"the highest dose used in Part 1.\n\n" |
| 241 |
) |
|
| 242 |
), |
|
| 243 | 5x |
"If one or more ", |
| 244 | 5x |
tox_label[2], |
| 245 | 5x |
" are reported in Part 1, the starting dose for Part 2 ", |
| 246 | 5x |
"will be ", |
| 247 | 5x |
ifelse( |
| 248 | 5x |
x@dlt_start == 0, |
| 249 | 5x |
"the highest dose used in Part 1.\n\n", |
| 250 | 5x |
paste0( |
| 251 | 5x |
abs(x@dlt_start), |
| 252 | 5x |
ifelse(abs(x@dlt_start) == 1, " dose ", " doses "), |
| 253 | 5x |
ifelse(x@dlt_start < 0, "below ", "above "), |
| 254 | 5x |
"the highest dose used in Part 1.\n\n" |
| 255 |
) |
|
| 256 |
), |
|
| 257 | 5x |
"Once Part 2 has started, the maximum increment in dose levels will be based ", |
| 258 | 5x |
"on the number of ", |
| 259 | 5x |
tox_label[2], |
| 260 | 5x |
" reported so far, as described in the ", |
| 261 | 5x |
"following table:" |
| 262 |
) |
|
| 263 | ||
| 264 | 5x |
param <- list(...) |
| 265 | 5x |
if (!("col.names" %in% names(param))) {
|
| 266 | 5x |
param[["col.names"]] <- c("Lower", "Upper", "Increment")
|
| 267 |
} |
|
| 268 | 5x |
if (!("caption" %in% names(param))) {
|
| 269 | 5x |
param[["caption"]] <- paste0( |
| 270 | 5x |
"Defined by the number of ", |
| 271 | 5x |
tox_label[2], |
| 272 | 5x |
" reported so far" |
| 273 |
) |
|
| 274 |
} |
|
| 275 | 5x |
header <- c(2, 1) |
| 276 | 5x |
headerLabel <- tox_label[2] |
| 277 | 5x |
substr(headerLabel, 1, 1) <- toupper(substr(headerLabel, 1, 1)) |
| 278 | 5x |
names(header) <- c(headerLabel, " ") |
| 279 | 5x |
param[["x"]] <- tibble( |
| 280 | 5x |
intervals = x@intervals |
| 281 |
) %>% |
|
| 282 | 5x |
h_range_to_minmax(intervals) %>% |
| 283 | 5x |
tibble::add_column(increments = c(0, x@increments)) |
| 284 | 5x |
d_tab <- kableExtra::add_header_above( |
| 285 | 5x |
do.call(knitr::kable, param), |
| 286 | 5x |
header |
| 287 |
) |
|
| 288 | 5x |
rv <- paste(rv, d_tab, "\n\n") |
| 289 | 5x |
if (asis) {
|
| 290 | 3x |
rv <- knitr::asis_output(rv) |
| 291 |
} |
|
| 292 | 5x |
rv |
| 293 |
} |
|
| 294 | ||
| 295 |
#' Render an `IncrementsRelativeDLTCurrent` object |
|
| 296 |
#' |
|
| 297 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 298 |
#' @inherit knit_print.CohortSizeConst return |
|
| 299 |
#' @param tox_label (`character`)\cr The word used to describe toxicities. See |
|
| 300 |
#' Usage Notes below. |
|
| 301 |
#' @param ... passed to [knitr::kable()] |
|
| 302 |
#' @inheritParams knit_print.CohortSizeConst |
|
| 303 |
#' @section Usage Notes: |
|
| 304 |
#' The default value of `col.names` is `c("Min", "Max", "Increment")` and that
|
|
| 305 |
#' of `caption` is `"Defined by number of DLTs in the current cohort"`. These values |
|
| 306 |
#' can be overridden by passing `col.names` and `caption` in the function call. |
|
| 307 |
#' |
|
| 308 |
#' `tox_label` defines how toxicities are described. |
|
| 309 |
#' |
|
| 310 |
#' It should be a character vector of length 1 or 2. If of length 2, the first |
|
| 311 |
#' element describes a single toxicity and the second describes all other |
|
| 312 |
#' toxicity counts. If of length 1, the character `s` is appended to the value |
|
| 313 |
#' describing a single toxicity. |
|
| 314 |
#' |
|
| 315 |
#' @export |
|
| 316 |
#' @method knit_print IncrementsRelativeDLTCurrent |
|
| 317 |
#' @rdname knit_print |
|
| 318 |
knit_print.IncrementsRelativeDLTCurrent <- function( |
|
| 319 |
x, |
|
| 320 |
..., |
|
| 321 |
asis = TRUE, |
|
| 322 |
tox_label = c("DLT", "DLTs")
|
|
| 323 |
) {
|
|
| 324 | 6x |
assert_flag(asis) |
| 325 | 4x |
assert_character(tox_label, min.len = 1, max.len = 2, any.missing = FALSE) |
| 326 | ||
| 327 | 4x |
if (length(tox_label) == 1) {
|
| 328 | ! |
tox_label[2] <- paste0(tox_label[1], "s") |
| 329 |
} |
|
| 330 | ||
| 331 | 4x |
param <- list(...) |
| 332 | 4x |
if (!("col.names" %in% names(param))) {
|
| 333 | 4x |
param[["col.names"]] <- c("Min", "Max", "Increment")
|
| 334 |
} |
|
| 335 | 4x |
if (!("caption" %in% names(param))) {
|
| 336 | 4x |
param[["caption"]] <- paste0( |
| 337 | 4x |
"Defined by number of ", |
| 338 | 4x |
tox_label[2], |
| 339 | 4x |
" reported in the current cohort" |
| 340 |
) |
|
| 341 |
} |
|
| 342 | 4x |
param[["x"]] <- tidy(x) |
| 343 | 4x |
header_text <- c(2, 1) |
| 344 | 4x |
names(header_text) <- c(paste0("No ", tox_label[2]), " ")
|
| 345 | 4x |
rv <- kableExtra::add_header_above( |
| 346 | 4x |
do.call(knitr::kable, param), |
| 347 | 4x |
header_text |
| 348 |
) |
|
| 349 | 4x |
rv <- paste0(rv, "\n\n") |
| 350 | ||
| 351 | 4x |
if (asis) {
|
| 352 | 2x |
rv <- knitr::asis_output(rv) |
| 353 |
} |
|
| 354 | 4x |
rv |
| 355 |
} |
| 1 |
# Design ---- |
|
| 2 | ||
| 3 |
#' Internal Helper Functions for Validation of [`RuleDesign`] Objects |
|
| 4 |
#' |
|
| 5 |
#' @description `r lifecycle::badge("stable")`
|
|
| 6 |
#' |
|
| 7 |
#' These functions are only used internally to validate the format of an input |
|
| 8 |
#' [`RuleDesign`] or inherited classes and therefore not exported. |
|
| 9 |
#' |
|
| 10 |
#' @name v_design |
|
| 11 |
#' @param object (`RuleDesign`)\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_design validates that the [`RuleDesign`] object |
|
| 17 |
#' contains valid `startingDose`. |
|
| 18 |
v_rule_design <- function(object) {
|
|
| 19 | 10x |
v <- Validate() |
| 20 | 10x |
v$check( |
| 21 | 10x |
test_number(object@startingDose, finite = TRUE), |
| 22 | 10x |
"startingDose must be a number" |
| 23 |
) |
|
| 24 | 10x |
v$check( |
| 25 | 10x |
test_subset( |
| 26 | 10x |
object@startingDose, |
| 27 | 10x |
choices = object@data@doseGrid, |
| 28 | 10x |
empty.ok = FALSE |
| 29 |
), |
|
| 30 | 10x |
"startingDose must be included in data@doseGrid" |
| 31 |
) |
|
| 32 | 10x |
v$result() |
| 33 |
} |
|
| 34 | ||
| 35 |
#' Internal Helper Functions for Validation of [`RuleDesignOrdinal`] Objects |
|
| 36 |
#' |
|
| 37 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 38 |
#' |
|
| 39 |
#' These functions are only used internally to validate the format of an input |
|
| 40 |
#' [`RuleDesignOrdinal`] or inherited classes and therefore not exported. |
|
| 41 |
#' |
|
| 42 |
#' @name v_design |
|
| 43 |
#' @param object (`RuleDesignOrdinal`)\cr object to validate. |
|
| 44 |
#' @return A `character` vector with the validation failure messages, |
|
| 45 |
#' or `TRUE` in case validation passes. |
|
| 46 |
NULL |
|
| 47 | ||
| 48 |
#' @describeIn v_design validates that the [`RuleDesignOrdinal`] object |
|
| 49 |
#' contains valid `starting_dose`. |
|
| 50 |
v_rule_design_ordinal <- function(object) {
|
|
| 51 | ! |
v <- Validate() |
| 52 | ! |
v$check( |
| 53 | ! |
test_number(object@starting_dose, finite = TRUE), |
| 54 | ! |
"starting_dose must be a number" |
| 55 |
) |
|
| 56 | ! |
v$check( |
| 57 | ! |
test_subset( |
| 58 | ! |
object@starting_dose, |
| 59 | ! |
choices = object@data@doseGrid, |
| 60 | ! |
empty.ok = FALSE |
| 61 |
), |
|
| 62 | ! |
"starting_dose must be included in data@doseGrid" |
| 63 |
) |
|
| 64 | ! |
v$result() |
| 65 |
} |
|
| 66 | ||
| 67 | ||
| 68 |
#' @describeIn v_design validates that the [`DesignGrouped`] object |
|
| 69 |
#' contains valid flags. |
|
| 70 |
v_design_grouped <- function(object) {
|
|
| 71 | 2x |
v <- Validate() |
| 72 | 2x |
v$check( |
| 73 | 2x |
test_flag(object@first_cohort_mono_only), |
| 74 | 2x |
"first_cohort_mono_only must be a flag" |
| 75 |
) |
|
| 76 | 2x |
v$check( |
| 77 | 2x |
test_flag(object@same_dose_for_all), |
| 78 | 2x |
"same_dose_for_all must be a flag" |
| 79 |
) |
|
| 80 | 2x |
v$check( |
| 81 | 2x |
test_flag(object@same_dose_for_all), |
| 82 | 2x |
"same_dose_for_start must be a flag" |
| 83 |
) |
|
| 84 | 2x |
v$result() |
| 85 |
} |
| 1 |
# GeneralSimulations ---- |
|
| 2 | ||
| 3 |
#' Internal Helper Functions for Validation of [`GeneralSimulations`] Objects |
|
| 4 |
#' |
|
| 5 |
#' @description `r lifecycle::badge("stable")`
|
|
| 6 |
#' |
|
| 7 |
#' These functions are only used internally to validate the format of an input |
|
| 8 |
#' [`GeneralSimulations`] or inherited classes and therefore not exported. |
|
| 9 |
#' |
|
| 10 |
#' @name v_general_simulations |
|
| 11 |
#' @param object (`GeneralSimulations`)\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_general_simulations validates that the [`GeneralSimulations`] object |
|
| 17 |
#' contains valid `data` object and valid `dose` simulations. |
|
| 18 | ||
| 19 |
v_general_simulations <- function(object) {
|
|
| 20 | ! |
v <- Validate() |
| 21 | ||
| 22 | ! |
nSims <- length(object@data) |
| 23 | ||
| 24 | ! |
v$check( |
| 25 | ! |
all(sapply(object@data, is, "Data")), |
| 26 | ! |
"all data elements must be Data objects" |
| 27 |
) |
|
| 28 | ! |
v$check( |
| 29 | ! |
identical(length(object@doses), nSims), |
| 30 | ! |
"doses must have same length as the data list" |
| 31 |
) |
|
| 32 | ||
| 33 | ! |
v$result() |
| 34 |
} |
|
| 35 | ||
| 36 |
#' @describeIn v_general_simulations validates that the [`Simulations`] object |
|
| 37 |
#' contains valid object `fit`, `stop_reasons`, `stop_report`, and |
|
| 38 |
#' `additional_stats` compared to the general class [`GeneralSimulations`]. |
|
| 39 |
#' |
|
| 40 |
v_simulations <- function(object) {
|
|
| 41 | ! |
v <- Validate() |
| 42 | ||
| 43 | ! |
nSims <- length(object@data) |
| 44 | ||
| 45 | ! |
v$check( |
| 46 | ! |
identical(length(object@fit), nSims), |
| 47 | ! |
"fit must have same length as data" |
| 48 |
) |
|
| 49 | ! |
v$check( |
| 50 | ! |
identical(length(object@stop_reasons), nSims), |
| 51 | ! |
"stop_reasons must have same length as data" |
| 52 |
) |
|
| 53 | ||
| 54 | ! |
v$check( |
| 55 | ! |
checkmate::test_matrix( |
| 56 | ! |
object@stop_report, |
| 57 | ! |
mode = "logical", |
| 58 | ! |
nrows = nSims, |
| 59 | ! |
min.cols = 1, |
| 60 | ! |
any.missing = FALSE |
| 61 |
), |
|
| 62 | ! |
"stop_report must be a matrix of mode logical in which the number of rows |
| 63 | ! |
equals the number of simulations and which must not contain any missing values" |
| 64 |
) |
|
| 65 | ||
| 66 | ! |
v$result() |
| 67 |
} |
|
| 68 | ||
| 69 |
#' @describeIn v_general_simulations validates that the [`DualSimulations`] object and |
|
| 70 |
#' capture the dose-biomarker `fits`, and the `sigma2W` and `rho` estimates. |
|
| 71 |
#' |
|
| 72 |
v_dual_simulations <- function(object) {
|
|
| 73 | ! |
v <- Validate() |
| 74 | ||
| 75 | ! |
nSims <- length(object@data) |
| 76 | ||
| 77 | ! |
v$check( |
| 78 | ! |
identical(length(object@fit_biomarker), nSims), |
| 79 | ! |
"fit_biomarker list has to have same length as data" |
| 80 |
) |
|
| 81 | ! |
v$check( |
| 82 | ! |
identical(length(object@rho_est), nSims), |
| 83 | ! |
"rho_est vector has to have same length as data" |
| 84 |
) |
|
| 85 | ! |
v$check( |
| 86 | ! |
identical(length(object@sigma2w_est), nSims), |
| 87 | ! |
"sigma2w_est has to have same length as data" |
| 88 |
) |
|
| 89 | ||
| 90 | ! |
v$result() |
| 91 |
} |
|
| 92 | ||
| 93 |
# PseudoSimulations ---- |
|
| 94 | ||
| 95 |
#' Internal Helper Functions for Validation of [`PseudoSimulations`] Objects |
|
| 96 |
#' |
|
| 97 |
#' @description `r lifecycle::badge("stable")`
|
|
| 98 |
#' |
|
| 99 |
#' These functions are only used internally to validate the format of an input |
|
| 100 |
#' [`PseudoSimulations`] or inherited classes and therefore not exported. |
|
| 101 |
#' |
|
| 102 |
#' @name v_pseudo_simulations |
|
| 103 |
#' @param object (`PseudoSimulations`)\cr object to validate. |
|
| 104 |
#' @return A `character` vector with the validation failure messages, |
|
| 105 |
#' or `TRUE` in case validation passes. |
|
| 106 |
NULL |
|
| 107 | ||
| 108 |
#' @describeIn v_pseudo_simulations validates that the [`PseudoSimulations`] object |
|
| 109 |
#' contains valid `fit`, `FinalTDtargetEndOfTrialEstimates` , |
|
| 110 |
#' `FinalTDtargetDuringTrialAtDoseGrid`,`FinalTDtargetEndOfTrialAtDoseGrid` , |
|
| 111 |
#' `FinalTDEOTCIs`, `FinalTDEOTRatios`, `FinalCIs`, `FinalRatios`, |
|
| 112 |
#' object and valid `stopReasons` simulations. |
|
| 113 | ||
| 114 |
v_pseudo_simulations <- function(object) {
|
|
| 115 | ! |
v <- Validate() |
| 116 | ||
| 117 | ! |
nSims <- length(object@data) |
| 118 | ! |
v$check( |
| 119 | ! |
identical(length(object@stop_reasons), nSims), |
| 120 | ! |
"stopReasons must have same length as data" |
| 121 |
) |
|
| 122 | ||
| 123 | ! |
v$result() |
| 124 |
} |
|
| 125 | ||
| 126 |
#' @describeIn v_pseudo_simulations validates that the [`PseudoDualSimulations`] object |
|
| 127 |
#' contains valid `fit_eff`, `final_gstar_estimates` , `final_gstar_at_dose_grid`, |
|
| 128 |
#' `final_gstar_cis` , `final_gstar_ratios`, `final_optimal_dose`, `final_optimal_dose_at_dose_grid` |
|
| 129 |
#' object and valid `sigma2_est` simulations. |
|
| 130 | ||
| 131 |
v_pseudo_dual_simulations <- function(object) {
|
|
| 132 | ! |
v <- Validate() |
| 133 | ! |
nSims <- length(object@data) |
| 134 | ! |
v$check( |
| 135 | ! |
identical(length(object@sigma2_est), nSims), |
| 136 | ! |
"sigma2_est has to have same length as data" |
| 137 |
) |
|
| 138 | ! |
v$result() |
| 139 |
} |
|
| 140 | ||
| 141 |
#' @describeIn v_pseudo_simulations validates that the [`PseudoDualFlexiSimulations`] |
|
| 142 |
#' object contains valid `sigma2_beta_w_est` vector of the final posterior mean |
|
| 143 |
#' sigma2betaW estimates.`FinalGstarEstimates` , `FinalGstarAtDoseGrid`, |
|
| 144 |
#' |
|
| 145 |
v_pseudo_dual_flex_simulations <- function(object) {
|
|
| 146 | ! |
v <- Validate() |
| 147 | ! |
nSims <- length(object@data) |
| 148 | ! |
v$check( |
| 149 | ! |
identical(length(object@sigma2_beta_w_est), nSims), |
| 150 | ! |
"sigma2_beta_w_est has to have same length as data" |
| 151 |
) |
|
| 152 | ! |
v$result() |
| 153 |
} |
|
| 154 | ||
| 155 |
#' @describeIn v_general_simulations validates that the [`DASimulations`] object |
|
| 156 |
#' contains valid `trial_duration` the vector of trial duration values for all |
|
| 157 |
#' simulations. |
|
| 158 | ||
| 159 |
v_da_simulations <- function(object) {
|
|
| 160 | ! |
v <- Validate() |
| 161 | ||
| 162 | ! |
nSims <- length(object@data) |
| 163 | ||
| 164 | ! |
v$check( |
| 165 | ! |
identical(length(object@trial_duration), nSims), |
| 166 | ! |
"trial_duration vector has to have same length as data" |
| 167 |
) |
|
| 168 | ||
| 169 | ! |
v$result() |
| 170 |
} |
| 1 |
#' @include McmcOptions-class.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
# size ---- |
|
| 5 | ||
| 6 |
## generic ---- |
|
| 7 | ||
| 8 |
#' Size of an Object |
|
| 9 |
#' |
|
| 10 |
#' @description `r lifecycle::badge("stable")`
|
|
| 11 |
#' |
|
| 12 |
#' A method that computes the size of a given object. This can be for instance |
|
| 13 |
#' a size of a MCMC sample, or the size of a cohort. See the help of a specific |
|
| 14 |
#' method for more details. |
|
| 15 |
#' |
|
| 16 |
#' @param object (`McmcOptions` or `Samples` or `CohortSize`)\cr an object |
|
| 17 |
#' for which the size is computed. |
|
| 18 |
#' @param ... further arguments passed to `size` specific methods. |
|
| 19 |
#' |
|
| 20 |
#' @return A size of a given object. |
|
| 21 |
#' @export |
|
| 22 |
#' |
|
| 23 |
setGeneric( |
|
| 24 |
name = "size", |
|
| 25 |
def = function(object, ...) {
|
|
| 26 | 89240x |
standardGeneric("size")
|
| 27 |
}, |
|
| 28 |
valueClass = "integer" |
|
| 29 |
) |
|
| 30 | ||
| 31 |
## McmcOptions ---- |
|
| 32 | ||
| 33 |
#' @describeIn size compute the number of MCMC samples based on `McmcOptions` |
|
| 34 |
#' object. |
|
| 35 |
#' @aliases size-McmcOptions |
|
| 36 |
#' @example examples/McmcOptions-methods-size.R |
|
| 37 |
setMethod( |
|
| 38 |
f = "size", |
|
| 39 |
signature = signature(object = "McmcOptions"), |
|
| 40 |
definition = function(object, ...) {
|
|
| 41 | 46153x |
iterations_relative <- object@iterations - object@burnin |
| 42 | 46153x |
if (iterations_relative <= 0) {
|
| 43 | ! |
return(0L) |
| 44 |
} |
|
| 45 | 46153x |
as.integer(floor(iterations_relative / object@step)) |
| 46 |
} |
|
| 47 |
) |
|
| 48 | ||
| 49 |
# saveSample ---- |
|
| 50 | ||
| 51 |
## generic ---- |
|
| 52 | ||
| 53 |
#' Determining if this Sample Should be Saved |
|
| 54 |
#' |
|
| 55 |
#' @description `r lifecycle::badge("stable")`
|
|
| 56 |
#' |
|
| 57 |
#' A method that determines if a sample from a given `iteration` should be |
|
| 58 |
#' saved. The sample should be saved if and only if: |
|
| 59 |
#' it is not in burn-in period and it matches the `step`. |
|
| 60 |
#' |
|
| 61 |
#' @param object (`McmcOptions`)\cr object based on which the answer is |
|
| 62 |
#' determined. |
|
| 63 |
#' @param iteration (`count`)\cr the current iteration index. |
|
| 64 |
#' @param ... not used. |
|
| 65 |
#' @return `TRUE` if this sample should be saved. |
|
| 66 |
#' @export |
|
| 67 |
#' |
|
| 68 |
setGeneric( |
|
| 69 |
name = "saveSample", |
|
| 70 |
def = function(object, iteration, ...) {
|
|
| 71 | 21914x |
standardGeneric("saveSample")
|
| 72 |
}, |
|
| 73 |
valueClass = "logical" |
|
| 74 |
) |
|
| 75 | ||
| 76 |
## McmcOptions ---- |
|
| 77 | ||
| 78 |
#' @describeIn saveSample determine if a sample should be saved. |
|
| 79 |
#' @aliases saveSample-McmcOptions |
|
| 80 |
#' @example examples/McmcOptions-methods-saveSample.R |
|
| 81 |
setMethod( |
|
| 82 |
f = "saveSample", |
|
| 83 |
signature = signature(object = "McmcOptions"), |
|
| 84 |
definition = function(object, iteration, ...) {
|
|
| 85 | 21914x |
iteration_relative <- iteration - object@burnin |
| 86 | 21914x |
iteration_relative > 0 && ((iteration_relative %% object@step) == 0) |
| 87 |
} |
|
| 88 |
) |
| 1 |
# v_mcmc_options ---- |
|
| 2 | ||
| 3 |
#' Internal Helper Functions for Validation of [`McmcOptions`] Objects |
|
| 4 |
#' |
|
| 5 |
#' @description `r lifecycle::badge("stable")`
|
|
| 6 |
#' |
|
| 7 |
#' These functions are only used internally to validate the format of an input |
|
| 8 |
#' [`McmcOptions`] or inherited classes and therefore not exported. |
|
| 9 |
#' |
|
| 10 |
#' @name v_mcmcoptions_objects |
|
| 11 |
#' @param object (`McmcOptions`)\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_mcmcoptions_objects validates that the [`McmcOptions`] |
|
| 17 |
#' object contains valid integer scalars `iterations`, `burnin` and `step` |
|
| 18 |
#' as well as proper parameters for Random Number Generator. |
|
| 19 |
v_mcmc_options <- function(object) {
|
|
| 20 | 4x |
v <- Validate() |
| 21 | 4x |
allowed_rng_kinds <- c( |
| 22 | 4x |
"base::Wichmann-Hill", |
| 23 | 4x |
"base::Marsaglia-Multicarry", |
| 24 | 4x |
"base::Super-Duper", |
| 25 | 4x |
"base::Mersenne-Twister", |
| 26 | 4x |
NA_character_ |
| 27 |
) |
|
| 28 | ||
| 29 | 4x |
v$check( |
| 30 | 4x |
test_int(object@iterations, lower = 1L), |
| 31 | 4x |
"iterations must be integer scalar greater than or equal to 1" |
| 32 |
) |
|
| 33 | 4x |
v$check( |
| 34 | 4x |
test_int(object@burnin, lower = 0L), |
| 35 | 4x |
"burn-in must be non-negative integer scalar" |
| 36 |
) |
|
| 37 |
# This below check should not be conducted in above test, using |
|
| 38 |
# `upper = object@iterations -1` argument, since object@iterations might be |
|
| 39 |
# not-valid. In such a case `test_int` throws an internal error. |
|
| 40 | 4x |
v$check( |
| 41 | 4x |
test_true(object@burnin < object@iterations), |
| 42 | 4x |
"burn-in must be lower than iterations" |
| 43 |
) |
|
| 44 | 4x |
v$check( |
| 45 | 4x |
test_int(object@step, lower = 1L), |
| 46 | 4x |
"step must be integer scalar greater than or equal to 1" |
| 47 |
) |
|
| 48 | ||
| 49 | 4x |
is_rng_kind_scalar <- test_string(object@rng_kind, na.ok = TRUE) |
| 50 | 4x |
v$check(is_rng_kind_scalar, "rng_kind must be a single string") |
| 51 | 4x |
v$check( |
| 52 | 4x |
test_subset(object@rng_kind, allowed_rng_kinds), |
| 53 | 4x |
paste0( |
| 54 | 4x |
"rng_kind must one of the following: ", |
| 55 | 4x |
paste(allowed_rng_kinds, collapse = ", "), |
| 56 | 4x |
". User specifies the rng_kind without `base::` prefix" |
| 57 |
) |
|
| 58 |
) |
|
| 59 | ||
| 60 | 4x |
is_rng_seed_scalar <- test_int(object@rng_seed, na.ok = TRUE) |
| 61 | 4x |
v$check(is_rng_seed_scalar, "rng_seed must be an integer scalar") |
| 62 | ||
| 63 |
# Below `if` condition is not only reasonable but also needed as R CMD check |
|
| 64 |
# is activating some stricter checks and it fails when arguments to `||` are |
|
| 65 |
# not scalars, even if `||` works well with vectors of length > 1. |
|
| 66 | 4x |
if (is_rng_kind_scalar && is_rng_seed_scalar) {
|
| 67 | 3x |
v$check( |
| 68 | 3x |
!is.na(object@rng_kind) || is.na(object@rng_seed), |
| 69 | 3x |
"rng_seed supplied but rng_kind not set" |
| 70 |
) |
|
| 71 |
} |
|
| 72 | 4x |
v$result() |
| 73 |
} |
| 1 |
#' Verbose Logging |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' A family of wrappers of selected [`futile.logger`] functions that control |
|
| 6 |
#' the logging mechanism in `crmPack`. The `crmPack` uses [`futile.logger`] |
|
| 7 |
#' package for the logging purposes. All the messages logged in `crmPack` are |
|
| 8 |
#' logged into `crmPack` logger at the [`futile.logger::TRACE`] level. Hence, |
|
| 9 |
#' enabling verbose logging means that the logging threshold will be set to |
|
| 10 |
#' [`futile.logger::TRACE`] for the `crmPack` logger, and disabling verbose |
|
| 11 |
#' logging means that it will be set to [`futile.logger::FATAL`]. |
|
| 12 |
#' |
|
| 13 |
#' @describeIn enable_logging A simple wrapper of |
|
| 14 |
#' [futile.logger::flog.threshold()] that enables `crmPack` verbose logging by |
|
| 15 |
#' setting logging threshold to [`futile.logger::TRACE`] for `crmPack` logger. |
|
| 16 |
#' |
|
| 17 |
#' @export |
|
| 18 |
#' |
|
| 19 |
enable_logging <- function() {
|
|
| 20 | 3x |
invisible( |
| 21 | 3x |
futile.logger::flog.threshold(futile.logger::TRACE, name = "crmPack") |
| 22 |
) |
|
| 23 |
} |
|
| 24 | ||
| 25 |
#' @describeIn enable_logging A simple wrapper of |
|
| 26 |
#' [futile.logger::flog.threshold()] that disables `crmPack` verbose logging |
|
| 27 |
#' by setting logging threshold to [`futile.logger::FATAL`] for `crmPack` |
|
| 28 |
#' logger. |
|
| 29 |
#' |
|
| 30 |
#' @export |
|
| 31 |
#' |
|
| 32 |
disable_logging <- function() {
|
|
| 33 | 6x |
invisible( |
| 34 | 6x |
futile.logger::flog.threshold(futile.logger::FATAL, name = "crmPack") |
| 35 |
) |
|
| 36 |
} |
|
| 37 | ||
| 38 |
#' @describeIn enable_logging A simple wrapper of |
|
| 39 |
#' [futile.logger::flog.logger()] that checks whether current threshold level |
|
| 40 |
#' for `crmPack` logger is verbose, which is [`futile.logger::TRACE`]. |
|
| 41 |
#' It returns `TRUE` if the current logging level is verbose, `FALSE` |
|
| 42 |
#' otherwise. |
|
| 43 |
#' |
|
| 44 |
#' @export |
|
| 45 |
#' |
|
| 46 |
is_logging_enabled <- function() {
|
|
| 47 | 877x |
logger <- futile.logger::flog.logger(name = "crmPack") |
| 48 | 877x |
unname(logger$threshold == futile.logger::TRACE) |
| 49 |
} |
|
| 50 | ||
| 51 |
#' @describeIn enable_logging A simple wrapper of |
|
| 52 |
#' [futile.logger::flog.trace()] that prints a log message in the `crmPack` |
|
| 53 |
#' logger. |
|
| 54 |
#' |
|
| 55 |
#' @inheritParams futile.logger::flog.trace |
|
| 56 |
#' |
|
| 57 |
#' @export |
|
| 58 |
#' |
|
| 59 |
log_trace <- function(msg, ..., capture = FALSE) {
|
|
| 60 | 1355x |
assert_string(msg) |
| 61 | 1355x |
assert_flag(capture) |
| 62 | ||
| 63 | 1355x |
futile.logger::flog.trace(msg = msg, ..., name = "crmPack", capture = capture) |
| 64 |
} |
| 1 |
# tidy ---- |
|
| 2 | ||
| 3 |
## generic ---- |
|
| 4 | ||
| 5 |
#' Tidying `CrmPackClass` objects |
|
| 6 |
#' |
|
| 7 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 8 |
#' |
|
| 9 |
#' In the spirit of the `broom` package, provide a method to convert a |
|
| 10 |
#' `CrmPackClass` object to a (list of) tibbles. |
|
| 11 |
#' |
|
| 12 |
#' @param x (`CrmPackClass`)\cr the object to be tidied. |
|
| 13 |
#' @param ... potentially used by class-specific methods |
|
| 14 |
#' |
|
| 15 |
#' @return A (list of) tibble(s) representing the object in tidy form. |
|
| 16 |
#' |
|
| 17 |
#' @export |
|
| 18 |
#' |
|
| 19 |
setGeneric( |
|
| 20 |
name = "tidy", |
|
| 21 |
def = function(x, ...) {
|
|
| 22 | 924x |
standardGeneric("tidy")
|
| 23 |
} |
|
| 24 |
) |
|
| 25 | ||
| 26 |
## CrmPackClass ---- |
|
| 27 | ||
| 28 |
#' Tidy a `CrmPackClass` Object |
|
| 29 |
#' |
|
| 30 |
#' Following the principles of the `broom` package, convert a `CrmPackClass` |
|
| 31 |
#' object to a (list of) tibbles. This is a basic, default representation. |
|
| 32 |
#' |
|
| 33 |
#' @param x (`CrmPackClass`)\cr the object to be tidied. |
|
| 34 |
#' @param ... potentially used by class-specific methods. |
|
| 35 |
#' @rdname tidy |
|
| 36 |
#' @aliases tidy-CrmPackClass |
|
| 37 |
#' @keywords methods |
|
| 38 |
#' @example examples/CrmPackClass-method-tidy.R |
|
| 39 |
#' |
|
| 40 |
#' @export |
|
| 41 |
setMethod( |
|
| 42 |
f = "tidy", |
|
| 43 |
signature = signature(x = "CrmPackClass"), |
|
| 44 |
definition = function(x, ...) {
|
|
| 45 | 436x |
rv <- h_tidy_all_slots(x) %>% h_tidy_class(x) |
| 46 | 436x |
if (length(rv) == 1) {
|
| 47 | 77x |
rv[[names(rv)[1]]] %>% h_tidy_class(x) |
| 48 |
} else {
|
|
| 49 | 359x |
rv |
| 50 |
} |
|
| 51 |
} |
|
| 52 |
) |
| 1 |
#' Internal Helper Functions for Validation of Model Parameters Objects |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' These functions are only used internally to validate the format of an object |
|
| 6 |
#' with model parameters or inherited classes and therefore not exported. |
|
| 7 |
#' |
|
| 8 |
#' @name v_model_params |
|
| 9 |
#' @return A `character` vector with the validation failure messages, |
|
| 10 |
#' or `TRUE` in case validation passes. |
|
| 11 |
NULL |
|
| 12 | ||
| 13 |
#' @describeIn v_model_params a helper function that validates multivariate normal |
|
| 14 |
#' parameters. |
|
| 15 |
#' @param object (`ModelParamsNormal`)\cr multivariate normal parameters object |
|
| 16 |
#' to validate. |
|
| 17 |
v_model_params_normal <- function(object) {
|
|
| 18 | 9x |
v <- Validate() |
| 19 | ||
| 20 | 9x |
v$check( |
| 21 | 9x |
test_numeric(x = object@mean, min.len = 2L, any.missing = FALSE), |
| 22 | 9x |
"mean must have length of at least 2 and no missing values are allowed" |
| 23 |
) |
|
| 24 | 9x |
is_cov_valid <- h_is_positive_definite(object@cov, length(object@mean)) |
| 25 | 9x |
v$check( |
| 26 | 9x |
is_cov_valid, |
| 27 | 9x |
"cov must be positive-definite matrix without any missing values" |
| 28 |
) |
|
| 29 | 9x |
is_prec_valid <- h_is_positive_definite(object@prec, length(object@mean)) |
| 30 | 9x |
v$check( |
| 31 | 9x |
is_prec_valid, |
| 32 | 9x |
"prec must be positive-definite matrix without any missing values" |
| 33 |
) |
|
| 34 | 9x |
if (is_cov_valid && is_prec_valid) {
|
| 35 | 5x |
v$check( |
| 36 | 5x |
all.equal( |
| 37 | 5x |
object@cov %*% object@prec, |
| 38 | 5x |
diag(1, length(object@mean)), |
| 39 | 5x |
check.attributes = FALSE |
| 40 |
) == |
|
| 41 | 5x |
TRUE, |
| 42 | 5x |
"prec must be inverse of cov" |
| 43 |
) |
|
| 44 |
} |
|
| 45 | 9x |
v$result() |
| 46 |
} |
| 1 |
#' @include helpers.R |
|
| 2 |
#' @include McmcOptions-validity.R |
|
| 3 |
#' @include CrmPackClass-class.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 |
# McmcOptions ---- |
|
| 7 | ||
| 8 |
## class ---- |
|
| 9 | ||
| 10 |
#' `McmcOptions` |
|
| 11 |
#' |
|
| 12 |
#' @description `r lifecycle::badge("stable")`
|
|
| 13 |
#' |
|
| 14 |
#' [`McmcOptions`] is a class for the three canonical MCMC options as well as |
|
| 15 |
#' Random Number Generator settings. |
|
| 16 |
#' |
|
| 17 |
#' @slot iterations (`count`)\cr number of MCMC iterations. |
|
| 18 |
#' @slot burnin (`count`)\cr number of burn-in iterations which are not saved. |
|
| 19 |
#' @slot step (`count`)\cr only every `step`-th iteration is saved after |
|
| 20 |
#' the `burnin`. In other words, a sample from iteration |
|
| 21 |
#' `i = 1,...,iterations`, is saved if and only if |
|
| 22 |
#' `(i - burnin) mod step = 0`.\cr |
|
| 23 |
#' For example, for `iterations = 6`, `burnin = 0` and `step = 2`, only |
|
| 24 |
#' samples from iterations `2,4,6` will be saved. |
|
| 25 |
#' @slot rng_kind (`string`)\cr a Random Number Generator (RNG) type used by |
|
| 26 |
#' [`rjags`]. It must be one out of the following four values: |
|
| 27 |
#' `base::Wichmann-Hill`, `base::Marsaglia-Multicarry`, |
|
| 28 |
#' `base::Super-Duper`, `base::Mersenne-Twister`, or `NA_character_`. |
|
| 29 |
#' If it is `NA_character_` (default), then the RNG kind will be chosen by |
|
| 30 |
#' [`rjags`]. |
|
| 31 |
#' @slot rng_seed (`number`)\cr a Random Number Generator (RNG) seed |
|
| 32 |
#' used by [`rjags`] for a chosen `rng_kind`. It must be an integer scalar or |
|
| 33 |
#' `NA_integer_`, which means that the seed will be chosen by [`rjags`]. |
|
| 34 |
#' |
|
| 35 |
#' @aliases McmcOptions |
|
| 36 |
#' @export |
|
| 37 |
#' |
|
| 38 |
.McmcOptions <- setClass( |
|
| 39 |
Class = "McmcOptions", |
|
| 40 |
slots = c( |
|
| 41 |
iterations = "integer", |
|
| 42 |
burnin = "integer", |
|
| 43 |
step = "integer", |
|
| 44 |
rng_kind = "character", |
|
| 45 |
rng_seed = "integer" |
|
| 46 |
), |
|
| 47 |
prototype = prototype( |
|
| 48 |
iterations = 1000L, |
|
| 49 |
burnin = 100L, |
|
| 50 |
step = 2L, |
|
| 51 |
rng_kind = NA_character_, |
|
| 52 |
rng_seed = NA_integer_ |
|
| 53 |
), |
|
| 54 |
contains = "CrmPackClass", |
|
| 55 |
validity = v_mcmc_options |
|
| 56 |
) |
|
| 57 | ||
| 58 |
## constructor ---- |
|
| 59 | ||
| 60 |
#' @rdname McmcOptions-class |
|
| 61 |
#' |
|
| 62 |
#' @param burnin (`count`)\cr number of burn-in iterations which are not saved. |
|
| 63 |
#' @param step (`count`)\cr only every step-th iteration is saved after |
|
| 64 |
#' the burn-in. |
|
| 65 |
#' @param samples (`count`)\cr number of resulting samples. |
|
| 66 |
#' @param rng_kind (`string`)\cr the name of the RNG type. Possible types are: |
|
| 67 |
#' `Wichmann-Hill`, `Marsaglia-Multicarry`, `Super-Duper`, `Mersenne-Twister`. |
|
| 68 |
#' If it is `NA` (default), then the RNG kind will be chosen by `[rjags`]. |
|
| 69 |
#' @param rng_seed (`number`)\cr RNG seed corresponding to chosen `rng_kind`. |
|
| 70 |
#' It must be an integer value or `NA` (default), which means that the seed |
|
| 71 |
#' will be chosen by `[rjags`]. |
|
| 72 |
#' |
|
| 73 |
#' @export |
|
| 74 |
#' @example examples/McmcOptions-class-McmcOptions.R |
|
| 75 |
#' |
|
| 76 |
McmcOptions <- function( |
|
| 77 |
burnin = 1e4L, |
|
| 78 |
step = 2L, |
|
| 79 |
samples = 1e4L, |
|
| 80 |
rng_kind = NA_character_, |
|
| 81 |
rng_seed = NA_integer_ |
|
| 82 |
) {
|
|
| 83 | 3389x |
assert_count(burnin, positive = TRUE) |
| 84 | 3389x |
assert_count(step, positive = TRUE) |
| 85 | 3389x |
assert_count(samples, positive = TRUE) |
| 86 | 3389x |
assert_string(rng_kind, na.ok = TRUE) |
| 87 | 3389x |
assert_count(rng_seed, na.ok = TRUE) |
| 88 | ||
| 89 | 3389x |
if (!is.na(rng_kind)) {
|
| 90 | 353x |
rng_kind <- paste0("base::", rng_kind)
|
| 91 |
} else {
|
|
| 92 | 3036x |
rng_kind <- NA_character_ |
| 93 |
} |
|
| 94 | 3389x |
if (!is.na(rng_seed)) {
|
| 95 | 352x |
rng_seed <- as.integer(rng_seed) |
| 96 |
} else {
|
|
| 97 | 3037x |
rng_seed <- NA_integer_ |
| 98 |
} |
|
| 99 | ||
| 100 | 3389x |
.McmcOptions( |
| 101 | 3389x |
iterations = as.integer(burnin + (step * samples)), |
| 102 | 3389x |
burnin = as.integer(burnin), |
| 103 | 3389x |
step = as.integer(step), |
| 104 | 3389x |
rng_kind = rng_kind, |
| 105 | 3389x |
rng_seed = as.integer(rng_seed) |
| 106 |
) |
|
| 107 |
} |
|
| 108 | ||
| 109 |
## default constructor ---- |
|
| 110 | ||
| 111 |
#' @rdname McmcOptions-class |
|
| 112 |
#' @note Typically, end users will not use the `.DefaultMcmcOptions()` function. |
|
| 113 |
#' @export |
|
| 114 |
.DefaultMcmcOptions <- function() {
|
|
| 115 | 16x |
McmcOptions( |
| 116 | 16x |
burnin = 250, |
| 117 | 16x |
samples = 1000 |
| 118 |
) |
|
| 119 |
} |
| 1 |
#' @include McmcOptions-class.R |
|
| 2 |
#' @include Samples-validity.R |
|
| 3 |
#' @include CrmPackClass-class.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 |
# Samples ---- |
|
| 7 | ||
| 8 |
## class ---- |
|
| 9 | ||
| 10 |
#' `Samples` |
|
| 11 |
#' |
|
| 12 |
#' @description `r lifecycle::badge("stable")`
|
|
| 13 |
#' |
|
| 14 |
#' [`Samples`] is the class to store the MCMC samples. |
|
| 15 |
#' |
|
| 16 |
#' @slot data (`list`)\cr MCMC samples of the parameter. Each entry in this list |
|
| 17 |
#' must be a vector (in case of a scalar parameter) or matrix (in case of a |
|
| 18 |
#' vector-valued parameter) with samples. |
|
| 19 |
#' In case of matrix, every row is a separate sample, while columns correspond |
|
| 20 |
#' to the dimension of the parameter. |
|
| 21 |
#' @slot options (`McmcOptions`)\cr MCMC options that were used to generate the |
|
| 22 |
#' samples. |
|
| 23 |
#' |
|
| 24 |
#' @aliases Samples |
|
| 25 |
#' @export |
|
| 26 |
#' |
|
| 27 |
.Samples <- setClass( |
|
| 28 |
Class = "Samples", |
|
| 29 |
slots = c( |
|
| 30 |
data = "list", |
|
| 31 |
options = "McmcOptions" |
|
| 32 |
), |
|
| 33 |
prototype = prototype( |
|
| 34 |
data = list(), |
|
| 35 |
options = McmcOptions() |
|
| 36 |
), |
|
| 37 |
contains = "CrmPackClass", |
|
| 38 |
validity = v_samples |
|
| 39 |
) |
|
| 40 | ||
| 41 |
## constructor ---- |
|
| 42 | ||
| 43 |
#' @rdname Samples-class |
|
| 44 |
#' |
|
| 45 |
#' @param data see slot definition. |
|
| 46 |
#' @param options see slot definition. |
|
| 47 |
#' |
|
| 48 |
#' @export |
|
| 49 |
#' @example examples/Samples-class.R |
|
| 50 |
#' |
|
| 51 |
Samples <- function(data, options) {
|
|
| 52 | 3709x |
new("Samples", data = data, options = options)
|
| 53 |
} |
|
| 54 | ||
| 55 |
## default constructor ---- |
|
| 56 | ||
| 57 |
#' @rdname Samples-class |
|
| 58 |
#' @note Typically, end users will not use the `.DefaultSamples()` function. |
|
| 59 |
#' @export |
|
| 60 |
.DefaultSamples <- function() {
|
|
| 61 | 5x |
mcmc( |
| 62 | 5x |
data = .DefaultData(), |
| 63 | 5x |
model = .DefaultLogisticLogNormal(), |
| 64 | 5x |
options = .DefaultMcmcOptions() |
| 65 |
) |
|
| 66 |
} |
| 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` @description `r lifecycle::badge("stable")`
|
|
| 12 |
#' This class captures trial simulations. |
|
| 13 |
#' Here also the random generator state before starting the simulation is |
|
| 14 |
#' saved, in order to be able to reproduce the outcome. For this just use |
|
| 15 |
#' [`set.seed`] with the `seed` as argument before running |
|
| 16 |
#' [`simulate,Design-method`]. |
|
| 17 |
#' |
|
| 18 |
#' @slot data (`list`)\cr produced [`Data`] objects. |
|
| 19 |
#' @slot doses (`numeric`)\cr final dose recommendations. |
|
| 20 |
#' @slot seed (`integer`)\cr random generator state before starting the simulation. |
|
| 21 |
#' |
|
| 22 |
#' @aliases GeneralSimulations |
|
| 23 |
#' @export |
|
| 24 |
.GeneralSimulations <- |
|
| 25 |
setClass( |
|
| 26 |
Class = "GeneralSimulations", |
|
| 27 |
slots = c( |
|
| 28 |
data = "list", |
|
| 29 |
doses = "numeric", |
|
| 30 |
seed = "integer" |
|
| 31 |
), |
|
| 32 |
prototype = prototype( |
|
| 33 |
data = list( |
|
| 34 |
Data( |
|
| 35 |
x = 1:2, |
|
| 36 |
y = 0:1, |
|
| 37 |
doseGrid = 1:2, |
|
| 38 |
ID = 1L:2L, |
|
| 39 |
cohort = 1L:2L |
|
| 40 |
), |
|
| 41 |
Data( |
|
| 42 |
x = 3:4, |
|
| 43 |
y = 0:1, |
|
| 44 |
doseGrid = 3:4, |
|
| 45 |
ID = 1L:2L, |
|
| 46 |
cohort = 1L:2L |
|
| 47 |
) |
|
| 48 |
), |
|
| 49 |
doses = c(1, 2), |
|
| 50 |
seed = 1L |
|
| 51 |
), |
|
| 52 |
contains = "CrmPackClass", |
|
| 53 |
validity = v_general_simulations |
|
| 54 |
) |
|
| 55 | ||
| 56 |
## constructor ---- |
|
| 57 | ||
| 58 |
#' @rdname GeneralSimulations-class |
|
| 59 |
#' |
|
| 60 |
#' @param data (`list`)\cr see slot definition. |
|
| 61 |
#' @param doses (`numeric`)\cr see slot definition. |
|
| 62 |
#' @param seed (`integer`)\cr see slot definition. |
|
| 63 |
#' |
|
| 64 |
#' @example examples/Simulations-class-GeneralSimulations.R |
|
| 65 |
#' @export |
|
| 66 |
GeneralSimulations <- function(data, doses, seed) {
|
|
| 67 | 62x |
assert_integerish(seed) |
| 68 | 62x |
.GeneralSimulations( |
| 69 | 62x |
data = data, |
| 70 | 62x |
doses = doses, |
| 71 | 62x |
seed = as.integer(seed) |
| 72 |
) |
|
| 73 |
} |
|
| 74 | ||
| 75 | ||
| 76 |
## default constructor |
|
| 77 | ||
| 78 |
#' @rdname GeneralSimulations-class |
|
| 79 |
#' @note Typically, end users will not use the `.DefaultGeneralSimulations()` function. |
|
| 80 |
#' @export |
|
| 81 |
.DefaultGeneralSimulations <- function() {
|
|
| 82 | 5x |
GeneralSimulations( |
| 83 | 5x |
data = list( |
| 84 | 5x |
Data(x = 1:3, y = c(0, 1, 0), doseGrid = 1:3, ID = 1L:3L, cohort = 1L:3L), |
| 85 | 5x |
Data(x = 4:6, y = c(0, 1, 0), doseGrid = 4:6, ID = 1L:3L, cohort = 1L:3L) |
| 86 |
), |
|
| 87 | 5x |
doses = c(1, 2), |
| 88 | 5x |
seed = 123 |
| 89 |
) |
|
| 90 |
} |
|
| 91 | ||
| 92 | ||
| 93 |
# Simulations ---- |
|
| 94 | ||
| 95 |
## class ---- |
|
| 96 | ||
| 97 |
#' `Simulations` |
|
| 98 |
#' |
|
| 99 |
#' @description `r lifecycle::badge("stable")`
|
|
| 100 |
#' |
|
| 101 |
#' This class captures the trial simulations from model based designs. |
|
| 102 |
#' Additional slots `fit`, `stop_reasons`, `stop_report`,`additional_stats` compared to |
|
| 103 |
#' the general class [`GeneralSimulations`]. |
|
| 104 |
#' |
|
| 105 |
#' @slot fit (`list`)\cr final fits |
|
| 106 |
#' @slot stop_reasons (`list`)\cr stopping reasons for each simulation run |
|
| 107 |
#' @slot stop_report matrix of stopping rule outcomes |
|
| 108 |
#' @slot additional_stats list of additional statistical summary |
|
| 109 |
#' @aliases Simulations |
|
| 110 |
#' @export |
|
| 111 |
.Simulations <- |
|
| 112 |
setClass( |
|
| 113 |
Class = "Simulations", |
|
| 114 |
slots = c( |
|
| 115 |
fit = "list", |
|
| 116 |
stop_report = "matrix", |
|
| 117 |
stop_reasons = "list", |
|
| 118 |
additional_stats = "list" |
|
| 119 |
), |
|
| 120 |
prototype = prototype( |
|
| 121 |
fit = list( |
|
| 122 |
c(0.1, 0.2), |
|
| 123 |
c(0.1, 0.2) |
|
| 124 |
), |
|
| 125 |
stop_report = matrix(TRUE, nrow = 2), |
|
| 126 |
stop_reasons = list("A", "A"),
|
|
| 127 |
additional_stats = list(a = 1, b = 1) |
|
| 128 |
), |
|
| 129 |
contains = "GeneralSimulations", |
|
| 130 |
validity = v_simulations |
|
| 131 |
) |
|
| 132 | ||
| 133 |
## constructor ---- |
|
| 134 | ||
| 135 |
#' @rdname Simulations-class |
|
| 136 |
#' |
|
| 137 |
#' @param fit (`list`)\cr see slot definition. |
|
| 138 |
#' @param stop_reasons (`list`)\cr see slot definition. |
|
| 139 |
#' @param stop_report see [`Simulations`] |
|
| 140 |
#' @param additional_stats (`list`)\cr see slot definition. |
|
| 141 |
#' @param \dots additional parameters from [`GeneralSimulations`] |
|
| 142 |
#' |
|
| 143 |
#' @example examples/Simulations-class-Simulations.R |
|
| 144 |
#' @export |
|
| 145 |
Simulations <- function(fit, stop_reasons, stop_report, additional_stats, ...) {
|
|
| 146 | 42x |
start <- GeneralSimulations(...) |
| 147 | 42x |
.Simulations( |
| 148 | 42x |
start, |
| 149 | 42x |
fit = fit, |
| 150 | 42x |
stop_report = stop_report, |
| 151 | 42x |
stop_reasons = stop_reasons, |
| 152 | 42x |
additional_stats = additional_stats |
| 153 |
) |
|
| 154 |
} |
|
| 155 | ||
| 156 |
## default constructor ---- |
|
| 157 | ||
| 158 |
#' @rdname Simulations-class |
|
| 159 |
#' @note Typically, end users will not use the `.DefaultSimulations()` function. |
|
| 160 |
#' @export |
|
| 161 |
.DefaultSimulations <- function() {
|
|
| 162 | ! |
design <- .DefaultDesign() |
| 163 | ! |
myTruth <- probFunction(design@model, alpha0 = 7, alpha1 = 8) |
| 164 | ||
| 165 | ! |
simulate( |
| 166 | ! |
design, |
| 167 | ! |
args = NULL, |
| 168 | ! |
truth = myTruth, |
| 169 | ! |
nsim = 1, |
| 170 | ! |
seed = 819, |
| 171 | ! |
mcmcOptions = .DefaultMcmcOptions(), |
| 172 | ! |
parallel = FALSE |
| 173 |
) |
|
| 174 |
} |
|
| 175 | ||
| 176 |
# DualSimulations ---- |
|
| 177 | ||
| 178 |
## class ---- |
|
| 179 | ||
| 180 |
#' `DualSimulations` |
|
| 181 |
#' |
|
| 182 |
#' @description `r lifecycle::badge("stable")`
|
|
| 183 |
#' |
|
| 184 |
#' This class captures the trial simulations from dual-endpoint model based |
|
| 185 |
#' designs. In comparison to the parent class [`Simulations`], |
|
| 186 |
#' it contains additional slots to capture the dose-biomarker `fits`, and the |
|
| 187 |
#' `sigma2W` and `rho` estimates. |
|
| 188 |
#' |
|
| 189 |
#' @slot rho_est (`numeric`)\cr vector of final posterior median rho estimates |
|
| 190 |
#' @slot sigma2w_est (`numeric`)\cr vector of final posterior median sigma2W estimates |
|
| 191 |
#' @slot fit_biomarker (`list`)\cr with the final dose-biomarker curve fits |
|
| 192 |
#' @aliases DualSimulations |
|
| 193 |
#' @export |
|
| 194 |
.DualSimulations <- |
|
| 195 |
setClass( |
|
| 196 |
Class = "DualSimulations", |
|
| 197 |
slots = c( |
|
| 198 |
rho_est = "numeric", |
|
| 199 |
sigma2w_est = "numeric", |
|
| 200 |
fit_biomarker = "list" |
|
| 201 |
), |
|
| 202 |
prototype = prototype( |
|
| 203 |
rho_est = c(0.2, 0.3), |
|
| 204 |
sigma2w_est = c(0.2, 0.3), |
|
| 205 |
fit_biomarker = list( |
|
| 206 |
c(0.1, 0.2), |
|
| 207 |
c(0.1, 0.2) |
|
| 208 |
) |
|
| 209 |
), |
|
| 210 |
contains = "Simulations", |
|
| 211 |
validity = v_dual_simulations |
|
| 212 |
) |
|
| 213 | ||
| 214 | ||
| 215 |
## constructor ---- |
|
| 216 | ||
| 217 |
#' @rdname DualSimulations-class |
|
| 218 |
#' |
|
| 219 |
#' @param rho_est (`numeric`)\cr see [`DualSimulations`] |
|
| 220 |
#' @param sigma2w_est (`numeric`)\cr [`DualSimulations`] |
|
| 221 |
#' @param fit_biomarker (`list`)\cr see [`DualSimulations`] |
|
| 222 |
#' @param \dots additional parameters from [`Simulations`] |
|
| 223 |
#' |
|
| 224 |
#' @example examples/Simulations-class-DualSimulations.R |
|
| 225 |
#' @export |
|
| 226 |
DualSimulations <- function(rho_est, sigma2w_est, fit_biomarker, ...) {
|
|
| 227 | 7x |
start <- Simulations(...) |
| 228 | 7x |
.DualSimulations( |
| 229 | 7x |
start, |
| 230 | 7x |
rho_est = rho_est, |
| 231 | 7x |
sigma2w_est = sigma2w_est, |
| 232 | 7x |
fit_biomarker = fit_biomarker |
| 233 |
) |
|
| 234 |
} |
|
| 235 | ||
| 236 |
## default constructor ---- |
|
| 237 | ||
| 238 |
#' @rdname DualSimulations-class |
|
| 239 |
#' @note Typically, end users will not use the `.DefaultDualSimulations()` function. |
|
| 240 |
#' @export |
|
| 241 |
.DefaultDualSimulations <- function() {
|
|
| 242 | ! |
DualSimulations( |
| 243 | ! |
rho_est = c(0.25, 0.35), |
| 244 | ! |
sigma2w_est = c(0.15, 0.25), |
| 245 | ! |
fit_biomarker = list(c(0.3, 0.4), c(0.4, 0.5)), |
| 246 | ! |
fit = list( |
| 247 | ! |
c(0.1, 0.2), |
| 248 | ! |
c(0.3, 0.4) |
| 249 |
), |
|
| 250 | ! |
stop_report = matrix(c(TRUE, FALSE), nrow = 2), |
| 251 | ! |
stop_reasons = list("A", "B"),
|
| 252 | ! |
additional_stats = list(a = 1, b = 1), |
| 253 | ! |
data = list( |
| 254 | ! |
Data( |
| 255 | ! |
x = 1:2, |
| 256 | ! |
y = 0:1, |
| 257 | ! |
doseGrid = 1:2, |
| 258 | ! |
ID = 1L:2L, |
| 259 | ! |
cohort = 1L:2L |
| 260 |
), |
|
| 261 | ! |
Data( |
| 262 | ! |
x = 3:4, |
| 263 | ! |
y = 0:1, |
| 264 | ! |
doseGrid = 3:4, |
| 265 | ! |
ID = 1L:2L, |
| 266 | ! |
cohort = 1L:2L |
| 267 |
) |
|
| 268 |
), |
|
| 269 | ! |
doses = c(1, 2), |
| 270 | ! |
seed = 123L |
| 271 |
) |
|
| 272 |
} |
|
| 273 | ||
| 274 |
#' `GeneralSimulationsSummary` |
|
| 275 |
#' |
|
| 276 |
#' @description `r lifecycle::badge("stable")`
|
|
| 277 |
#' |
|
| 278 |
#' This class captures the summary of general simulations output. Note that objects |
|
| 279 |
#' should not be created by users, therefore no initialization |
|
| 280 |
#' function is provided for this class. |
|
| 281 |
#' |
|
| 282 |
#' @slot target (`numeric`)\cr target toxicity interval |
|
| 283 |
#' @slot target_dose_interval (`numeric`)\cr corresponding target dose interval |
|
| 284 |
#' @slot nsim (`integer`)\cr number of simulations |
|
| 285 |
#' @slot prop_dlts (`ANY`)\cr A numeric array (multi-dimensional) or list representing proportions of DLTs in the trials |
|
| 286 |
#' @slot mean_tox_risk (`numeric`)\cr mean toxicity risks for the patients |
|
| 287 |
#' @slot dose_selected (`numeric`)\cr doses selected as MTD |
|
| 288 |
#' @slot tox_at_doses_selected (`numeric`)\cr true toxicity at doses selected |
|
| 289 |
#' @slot prop_at_target (`numeric`)\cr Proportion of trials selecting target MTD |
|
| 290 |
#' @slot dose_most_selected (`numeric`)\cr dose most often selected as MTD |
|
| 291 |
#' @slot obs_tox_rate_at_dose_most_selected (`numeric`)\cr observed toxicity rate at dose most often selected |
|
| 292 |
#' @slot n_obs (`ANY`)\cr A numeric array (multi-dimensional) or list representing number of patients overall. |
|
| 293 |
#' @slot n_above_target (`integer`)\cr number of patients treated above target tox interval |
|
| 294 |
#' @slot dose_grid (`numeric`)\cr the dose grid that has been used |
|
| 295 |
#' @slot placebo (`logical`)\cr set to TRUE (default is FALSE) for a design with placebo |
|
| 296 |
#' @aliases GeneralSimulationsSummary |
|
| 297 |
#' @export |
|
| 298 |
.GeneralSimulationsSummary <- |
|
| 299 |
setClass( |
|
| 300 |
Class = "GeneralSimulationsSummary", |
|
| 301 |
slots = c( |
|
| 302 |
target = "numeric", |
|
| 303 |
target_dose_interval = "numeric", |
|
| 304 |
nsim = "integer", |
|
| 305 |
prop_dlts = "ANY", |
|
| 306 |
mean_tox_risk = "numeric", |
|
| 307 |
dose_selected = "numeric", |
|
| 308 |
tox_at_doses_selected = "numeric", |
|
| 309 |
prop_at_target = "numeric", |
|
| 310 |
dose_most_selected = "numeric", |
|
| 311 |
obs_tox_rate_at_dose_most_selected = "numeric", |
|
| 312 |
n_obs = "ANY", |
|
| 313 |
n_above_target = "integer", |
|
| 314 |
dose_grid = "numeric", |
|
| 315 |
placebo = "logical" |
|
| 316 |
) |
|
| 317 |
) |
|
| 318 | ||
| 319 |
## default constructor ---- |
|
| 320 | ||
| 321 |
#' @rdname GeneralSimulationsSummary-class |
|
| 322 |
#' @note Typically, end users will not use the `.DefaultGeneralSimulationsSummary()` function. |
|
| 323 |
#' @export |
|
| 324 |
.DefaultGeneralSimulationsSummary <- function() {
|
|
| 325 | 2x |
stop( |
| 326 | 2x |
paste( |
| 327 | 2x |
"Class GeneralSimulationsSummary cannot be instantiated directly.", |
| 328 | 2x |
"Please use one of its subclasses instead." |
| 329 |
) |
|
| 330 |
) |
|
| 331 |
} |
|
| 332 | ||
| 333 |
## SimulationsSummary ---- |
|
| 334 | ||
| 335 |
## class ---- |
|
| 336 | ||
| 337 |
#' `SimulationsSummary` |
|
| 338 |
#' |
|
| 339 |
#' @description `r lifecycle::badge("stable")`
|
|
| 340 |
#' |
|
| 341 |
#' In addition to the slots in the parent class [`GeneralSimulationsSummary`], |
|
| 342 |
#' it contains two slots with model fit information. |
|
| 343 |
#' |
|
| 344 |
#' @slot stop_report (`matrix`)\cr matrix of stopping rule outcomes |
|
| 345 |
#' @slot fit_at_dose_most_selected (`numeric`)\cr fitted toxicity rate at dose most often selected |
|
| 346 |
#' @slot additional_stats (`list`)\cr list of additional statistical summary |
|
| 347 |
#' @slot mean_fit (`list`)\cr list with the average, lower (2.5%) and upper (97.5%) |
|
| 348 |
#' quantiles of the mean fitted toxicity at each dose level |
|
| 349 |
#' |
|
| 350 |
#' @aliases SimulationsSummary |
|
| 351 |
#' @export |
|
| 352 |
.SimulationsSummary <- |
|
| 353 |
setClass( |
|
| 354 |
Class = "SimulationsSummary", |
|
| 355 |
slots = c( |
|
| 356 |
stop_report = "matrix", |
|
| 357 |
fit_at_dose_most_selected = "numeric", |
|
| 358 |
additional_stats = "list", |
|
| 359 |
mean_fit = "list" |
|
| 360 |
), |
|
| 361 |
contains = "GeneralSimulationsSummary" |
|
| 362 |
) |
|
| 363 | ||
| 364 |
## default constructor ---- |
|
| 365 | ||
| 366 |
#' @rdname SimulationsSummary-class |
|
| 367 |
#' @note Typically, end users will not use the `.DefaultSimulationsSummary()` function. |
|
| 368 |
#' @export |
|
| 369 |
.DefaultSimulationsSummary <- function() {
|
|
| 370 | 1x |
stop(paste( |
| 371 | 1x |
"Class SimulationsSummary cannot be instantiated directly.", |
| 372 | 1x |
"Please use one of its subclasses instead." |
| 373 |
)) |
|
| 374 |
} |
|
| 375 | ||
| 376 |
# DualSimulationsSummary ---- |
|
| 377 | ||
| 378 |
# class ---- |
|
| 379 | ||
| 380 |
#' `DualSimulationsSummary` |
|
| 381 |
#' |
|
| 382 |
#' @description `r lifecycle::badge("stable")`
|
|
| 383 |
#' This class captures the summary of dual-endpoint simulations output. |
|
| 384 |
#' In comparison to its parent class [`SimulationsSummary`], it has additional slots. |
|
| 385 |
#' |
|
| 386 |
#' @slot biomarker_fit_at_dose_most_selected (`numeric`)\cr fitted biomarker level at most often selected dose. |
|
| 387 |
#' @slot mean_biomarker_fit (`list`)\cr list with average, lower (2.5%) and upper (97.5%) quantiles of |
|
| 388 |
#' mean fitted biomarker level at each dose |
|
| 389 |
#' @aliases DualSimulationsSummary |
|
| 390 |
#' @export |
|
| 391 |
.DualSimulationsSummary <- |
|
| 392 |
setClass( |
|
| 393 |
Class = "DualSimulationsSummary", |
|
| 394 |
slots = c( |
|
| 395 |
biomarker_fit_at_dose_most_selected = "numeric", |
|
| 396 |
mean_biomarker_fit = "list" |
|
| 397 |
), |
|
| 398 |
contains = "SimulationsSummary" |
|
| 399 |
) |
|
| 400 | ||
| 401 |
# default constructor |
|
| 402 | ||
| 403 |
#' @rdname DualSimulationsSummary-class |
|
| 404 |
#' @note Typically, end users will not use the `.DefaultDualSimulationsSummary()` function. |
|
| 405 |
#' @export |
|
| 406 |
.DefaultDualSimulationsSummary <- function() {
|
|
| 407 | 1x |
empty_data <- DataDual(doseGrid = c(1, 3, 5, 10, 15, 20, 25, 30)) |
| 408 | ||
| 409 | 1x |
my_model <- DualEndpointRW( |
| 410 | 1x |
mean = c(0, 1), |
| 411 | 1x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
| 412 | 1x |
sigma2betaW = 0.01, |
| 413 | 1x |
sigma2W = c(a = 0.1, b = 0.1), |
| 414 | 1x |
rho = c(a = 1, b = 1), |
| 415 | 1x |
rw1 = TRUE |
| 416 |
) |
|
| 417 | ||
| 418 | 1x |
my_next_best <- NextBestDualEndpoint( |
| 419 | 1x |
target = c(0.9, 1), |
| 420 | 1x |
overdose = c(0.35, 1), |
| 421 | 1x |
max_overdose_prob = 0.25 |
| 422 |
) |
|
| 423 | ||
| 424 | 1x |
my_size1 <- CohortSizeRange( |
| 425 | 1x |
intervals = c(0, 30), |
| 426 | 1x |
cohort_size = c(1, 3) |
| 427 |
) |
|
| 428 | 1x |
my_size2 <- CohortSizeDLT( |
| 429 | 1x |
intervals = c(0, 1), |
| 430 | 1x |
cohort_size = c(1, 3) |
| 431 |
) |
|
| 432 | 1x |
my_size <- maxSize(my_size1, my_size2) |
| 433 | ||
| 434 | 1x |
my_stopping1 <- StoppingTargetBiomarker( |
| 435 | 1x |
target = c(0.9, 1), |
| 436 | 1x |
prob = 0.5 |
| 437 |
) |
|
| 438 | ||
| 439 | 1x |
my_stopping <- my_stopping1 | StoppingMinPatients(10) | StoppingMissingDose() |
| 440 | ||
| 441 | 1x |
my_increments <- IncrementsRelative( |
| 442 | 1x |
intervals = c(0, 20), |
| 443 | 1x |
increments = c(1, 0.33) |
| 444 |
) |
|
| 445 | ||
| 446 | 1x |
my_design <- DualDesign( |
| 447 | 1x |
model = my_model, |
| 448 | 1x |
data = empty_data, |
| 449 | 1x |
nextBest = my_next_best, |
| 450 | 1x |
stopping = my_stopping, |
| 451 | 1x |
increments = my_increments, |
| 452 | 1x |
cohort_size = CohortSizeConst(3), |
| 453 | 1x |
startingDose = 3 |
| 454 |
) |
|
| 455 | ||
| 456 | 1x |
beta_mod <- function(dose, e0, eMax, delta1, delta2, scal) {
|
| 457 | 4x |
maxDens <- (delta1^delta1) * |
| 458 | 4x |
(delta2^delta2) / |
| 459 | 4x |
((delta1 + delta2)^(delta1 + delta2)) |
| 460 | 4x |
dose <- dose / scal |
| 461 | 4x |
e0 + eMax / maxDens * (dose^delta1) * (1 - dose)^delta2 |
| 462 |
} |
|
| 463 | ||
| 464 | 1x |
true_biomarker <- function(dose) {
|
| 465 | 4x |
beta_mod( |
| 466 | 4x |
dose, |
| 467 | 4x |
e0 = 0.2, |
| 468 | 4x |
eMax = 0.6, |
| 469 | 4x |
delta1 = 5, |
| 470 | 4x |
delta2 = 5 * 0.5 / 0.5, |
| 471 | 4x |
scal = 100 |
| 472 |
) |
|
| 473 |
} |
|
| 474 | ||
| 475 | 1x |
true_tox <- function(dose) {
|
| 476 | 4x |
pnorm((dose - 60) / 10) |
| 477 |
} |
|
| 478 | ||
| 479 | 1x |
x <- simulate( |
| 480 | 1x |
object = my_design, |
| 481 | 1x |
trueTox = true_tox, |
| 482 | 1x |
trueBiomarker = true_biomarker, |
| 483 | 1x |
sigma2W = 0.01, |
| 484 | 1x |
rho = 0, |
| 485 | 1x |
nsim = 1, |
| 486 | 1x |
parallel = FALSE, |
| 487 | 1x |
seed = 3, |
| 488 | 1x |
startingDose = 6, |
| 489 | 1x |
mcmcOptions = .DefaultMcmcOptions() |
| 490 |
) |
|
| 491 |
} |
|
| 492 | ||
| 493 |
# PseudoSimulations ---- |
|
| 494 | ||
| 495 |
## class ---- |
|
| 496 | ||
| 497 |
#' `PseudoSimulations` |
|
| 498 |
#' |
|
| 499 |
#' @description `r lifecycle::badge("stable")`
|
|
| 500 |
#' This class captures trial simulations from designs using pseudo model. |
|
| 501 |
#' It has additional slots `fit` and `stop_reasons` compared to the |
|
| 502 |
#' general class [`GeneralSimulations`]. |
|
| 503 |
#' |
|
| 504 |
#' @slot fit (`list`)\cr final fit values. |
|
| 505 |
#' @slot final_td_target_during_trial_estimates (`numeric`)\cr final estimates of the `td_target_during_trial`. |
|
| 506 |
#' @slot final_td_target_end_of_trial_estimates (`numeric`)\cr final estimates of the `td_target_end_of_trial`. |
|
| 507 |
#' @slot final_td_target_during_trial_at_dose_grid (`numeric`) |
|
| 508 |
#' \cr dose levels at dose grid closest below the final `td_target_during_trial` estimates. |
|
| 509 |
#' @slot final_td_target_end_of_trial_at_dose_grid (`numeric`) |
|
| 510 |
#' \cr dose levels at dose grid closest below the final `td_target_end_of_trial` estimates. |
|
| 511 |
#' @slot final_tdeot_cis (`list`)\cr 95% credibility intervals of the final estimates for `td_target_end_of_trial`. |
|
| 512 |
#' @slot final_tdeot_ratios (`numeric`)\cr ratio of the upper to the lower 95% |
|
| 513 |
#' credibility intervals for `td_target_end_of_trial`. |
|
| 514 |
#' @slot final_cis (`list`)\cr final 95% credibility intervals for `td_target_end_of_trial` estimates. |
|
| 515 |
#' @slot final_ratios (`numeric`)\cr final ratios of the upper to the lower 95% |
|
| 516 |
#' credibility interval for `td_target_end_of_trial`. |
|
| 517 |
#' @slot stop_report (`matrix`)\cr outcomes of stopping rules. |
|
| 518 |
#' @slot stop_reasons (`list`)\cr reasons for stopping each simulation run. |
|
| 519 |
#' |
|
| 520 |
#' @aliases PseudoSimulations |
|
| 521 |
#' @export |
|
| 522 |
.PseudoSimulations <- |
|
| 523 |
setClass( |
|
| 524 |
Class = "PseudoSimulations", |
|
| 525 |
slots = c( |
|
| 526 |
fit = "list", |
|
| 527 |
final_td_target_during_trial_estimates = "numeric", |
|
| 528 |
final_td_target_end_of_trial_estimates = "numeric", |
|
| 529 |
final_td_target_during_trial_at_dose_grid = "numeric", |
|
| 530 |
final_td_target_end_of_trial_at_dose_grid = "numeric", |
|
| 531 |
final_tdeot_cis = "list", |
|
| 532 |
final_tdeot_ratios = "numeric", |
|
| 533 |
final_cis = "list", |
|
| 534 |
final_ratios = "numeric", |
|
| 535 |
stop_report = "matrix", |
|
| 536 |
stop_reasons = "list" |
|
| 537 |
), |
|
| 538 |
prototype = prototype( |
|
| 539 |
final_td_target_during_trial_estimates = c(0.1, 0.1), |
|
| 540 |
final_td_target_end_of_trial_estimates = c(0.1, 0.1), |
|
| 541 |
final_td_target_during_trial_at_dose_grid = c(0.1, 0.1), |
|
| 542 |
final_td_target_end_of_trial_at_dose_grid = c(0.1, 0.1), |
|
| 543 |
final_tdeot_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
| 544 |
final_tdeot_ratios = c(0.1, 0.1), |
|
| 545 |
final_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
| 546 |
final_ratios = c(0.1, 0.1), |
|
| 547 |
stop_report = matrix(TRUE, nrow = 2), |
|
| 548 |
stop_reasons = list("A", "A")
|
|
| 549 |
), |
|
| 550 |
contains = "GeneralSimulations", |
|
| 551 |
validity = v_pseudo_simulations |
|
| 552 |
) |
|
| 553 | ||
| 554 |
## constructor ---- |
|
| 555 | ||
| 556 |
#' @rdname PseudoSimulations-class |
|
| 557 |
#' |
|
| 558 |
#' @param fit (`list`)\cr see slot definition. |
|
| 559 |
#' @param final_td_target_during_trial_estimates (`numeric`)\cr see slot definition. |
|
| 560 |
#' @param final_td_target_end_of_trial_estimates (`numeric`)\cr see slot definition. |
|
| 561 |
#' @param final_td_target_during_trial_at_dose_grid (`numeric`)\cr see slot definition. |
|
| 562 |
#' @param final_td_target_end_of_trial_at_dose_grid (`numeric`)\cr see slot definition. |
|
| 563 |
#' @param final_tdeot_cis (`list`)\cr see slot definition. |
|
| 564 |
#' @param final_tdeot_ratios (`numeric`)\cr see slot definition. |
|
| 565 |
#' @param final_cis (`list`)\cr see slot definition. |
|
| 566 |
#' @param final_ratios (`numeric`)\cr see slot definition. |
|
| 567 |
#' @param stop_report see [`PseudoSimulations`] |
|
| 568 |
#' @param stop_reasons (`list`)\cr see slot definition. |
|
| 569 |
#' @param \dots additional parameters from [`GeneralSimulations`] |
|
| 570 |
#' |
|
| 571 |
#' @export |
|
| 572 |
PseudoSimulations <- function( |
|
| 573 |
fit, |
|
| 574 |
final_td_target_during_trial_estimates, |
|
| 575 |
final_td_target_end_of_trial_estimates, |
|
| 576 |
final_td_target_during_trial_at_dose_grid, |
|
| 577 |
final_td_target_end_of_trial_at_dose_grid, |
|
| 578 |
final_tdeot_cis, |
|
| 579 |
final_tdeot_ratios, |
|
| 580 |
final_cis, |
|
| 581 |
final_ratios, |
|
| 582 |
stop_report, |
|
| 583 |
stop_reasons, |
|
| 584 |
... |
|
| 585 |
) {
|
|
| 586 | 13x |
start <- GeneralSimulations(...) |
| 587 | 13x |
.PseudoSimulations( |
| 588 | 13x |
start, |
| 589 | 13x |
fit = fit, |
| 590 | 13x |
final_td_target_during_trial_estimates = final_td_target_during_trial_estimates, |
| 591 | 13x |
final_td_target_end_of_trial_estimates = final_td_target_end_of_trial_estimates, |
| 592 | 13x |
final_td_target_during_trial_at_dose_grid = final_td_target_during_trial_at_dose_grid, |
| 593 | 13x |
final_td_target_end_of_trial_at_dose_grid = final_td_target_end_of_trial_at_dose_grid, |
| 594 | 13x |
final_tdeot_cis = final_tdeot_cis, |
| 595 | 13x |
final_tdeot_ratios = final_tdeot_ratios, |
| 596 | 13x |
final_cis = final_cis, |
| 597 | 13x |
final_ratios = final_ratios, |
| 598 | 13x |
stop_report = stop_report, |
| 599 | 13x |
stop_reasons = stop_reasons |
| 600 |
) |
|
| 601 |
} |
|
| 602 | ||
| 603 |
## default constructor ---- |
|
| 604 | ||
| 605 |
#' @rdname PseudoSimulations-class |
|
| 606 |
#' @note Typically, end users will not use the `.DefaultPseudoSimulations()` function. |
|
| 607 |
#' @export |
|
| 608 |
.DefaultPseudoSimulations <- function() {
|
|
| 609 | 2x |
stop( |
| 610 | 2x |
"Class PseudoSimulations cannot be instantiated directly. Please use one of its subclasses instead." |
| 611 |
) |
|
| 612 |
} |
|
| 613 | ||
| 614 |
# PseudoDualSimulations ---- |
|
| 615 | ||
| 616 |
## class ---- |
|
| 617 | ||
| 618 |
#' `PseudoDualSimulations` |
|
| 619 |
#' |
|
| 620 |
#' @description `r lifecycle::badge("stable")`
|
|
| 621 |
#' This class conducts trial simulations for designs using both the |
|
| 622 |
#' DLE and efficacy responses. It defines final values for |
|
| 623 |
#' efficacy fit and DLE, estimates of Gstar, optimal dose and sigma2. |
|
| 624 |
#' |
|
| 625 |
#' @slot fit_eff (`list`)\cr final values of efficacy fit. |
|
| 626 |
#' @slot final_gstar_estimates (`numeric`)\cr final Gstar estimates. |
|
| 627 |
#' @slot final_gstar_at_dose_grid (`numeric`)\cr final Gstar estimates at dose grid. |
|
| 628 |
#' @slot final_gstar_cis (`list`)\cr list of 95% confidence interval for Gstar estimates. |
|
| 629 |
#' @slot final_gstar_ratios (`numeric`)\cr ratios of confidence intervals for Gstar estimates. |
|
| 630 |
#' @slot final_optimal_dose (`numeric`)\cr final optimal dose. |
|
| 631 |
#' @slot final_optimal_dose_at_dose_grid (`numeric`)\cr final optimal dose at dose grid. |
|
| 632 |
#' @slot sigma2_est (`numeric`)\cr final sigma2 estimates. |
|
| 633 |
#' |
|
| 634 |
#' @aliases PseudoDualSimulations |
|
| 635 |
#' @export |
|
| 636 |
.PseudoDualSimulations <- |
|
| 637 |
setClass( |
|
| 638 |
Class = "PseudoDualSimulations", |
|
| 639 |
slots = c( |
|
| 640 |
fit_eff = "list", |
|
| 641 |
final_gstar_estimates = "numeric", |
|
| 642 |
final_gstar_at_dose_grid = "numeric", |
|
| 643 |
final_gstar_cis = "list", |
|
| 644 |
final_gstar_ratios = "numeric", |
|
| 645 |
final_optimal_dose = "numeric", |
|
| 646 |
final_optimal_dose_at_dose_grid = "numeric", |
|
| 647 |
sigma2_est = "numeric" |
|
| 648 |
), |
|
| 649 |
prototype = prototype( |
|
| 650 |
final_gstar_estimates = c(0.1, 0.1), |
|
| 651 |
final_gstar_at_dose_grid = c(0.1, 0.1), |
|
| 652 |
final_gstar_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
| 653 |
final_gstar_ratios = c(0.01, 0.01), |
|
| 654 |
final_optimal_dose = c(0.01, 0.01), |
|
| 655 |
final_optimal_dose_at_dose_grid = c(0.01, 0.01), |
|
| 656 |
sigma2_est = c(0.001, 0.002) |
|
| 657 |
), |
|
| 658 |
contains = "PseudoSimulations", |
|
| 659 |
validity = v_pseudo_dual_simulations |
|
| 660 |
) |
|
| 661 | ||
| 662 |
## constructor ---- |
|
| 663 | ||
| 664 |
#' @rdname PseudoDualSimulations-class |
|
| 665 |
#' |
|
| 666 |
#' @param fit_eff (`list`)\cr see slot definition. |
|
| 667 |
#' @param final_gstar_estimates (`numeric`)\cr see slot definition. |
|
| 668 |
#' @param final_gstar_at_dose_grid (`numeric`)\cr see slot definition. |
|
| 669 |
#' @param final_gstar_cis (`list`)\cr see slot definition. |
|
| 670 |
#' @param final_gstar_ratios (`numeric`)\cr see slot definition. |
|
| 671 |
#' @param final_optimal_dose (`numeric`)\cr see slot definition. |
|
| 672 |
#' @param final_optimal_dose_at_dose_grid (`numeric`)\cr see slot definition. |
|
| 673 |
#' @param sigma2_est (`numeric`)\cr see slot definition. |
|
| 674 |
#' @param \dots additional parameters from [`PseudoSimulations`] |
|
| 675 |
#' @export |
|
| 676 |
PseudoDualSimulations <- function( |
|
| 677 |
fit_eff, |
|
| 678 |
final_gstar_estimates, |
|
| 679 |
final_gstar_at_dose_grid, |
|
| 680 |
final_gstar_cis, |
|
| 681 |
final_gstar_ratios, |
|
| 682 |
final_optimal_dose, |
|
| 683 |
final_optimal_dose_at_dose_grid, |
|
| 684 |
sigma2_est, |
|
| 685 |
... |
|
| 686 |
) {
|
|
| 687 | 7x |
start <- PseudoSimulations(...) |
| 688 | 7x |
.PseudoDualSimulations( |
| 689 | 7x |
start, |
| 690 | 7x |
fit_eff = fit_eff, |
| 691 | 7x |
final_gstar_estimates = final_gstar_estimates, |
| 692 | 7x |
final_gstar_at_dose_grid = final_gstar_at_dose_grid, |
| 693 | 7x |
final_gstar_cis = final_gstar_cis, |
| 694 | 7x |
final_gstar_ratios = final_gstar_ratios, |
| 695 | 7x |
final_optimal_dose = final_optimal_dose, |
| 696 | 7x |
final_optimal_dose_at_dose_grid = final_optimal_dose_at_dose_grid, |
| 697 | 7x |
sigma2_est = sigma2_est |
| 698 |
) |
|
| 699 |
} |
|
| 700 | ||
| 701 |
## default constructor ---- |
|
| 702 | ||
| 703 |
#' @rdname PseudoDualSimulations-class |
|
| 704 |
#' @note Do not use the `.DefaultPseudoDualSimulations()` function. |
|
| 705 |
#' @export |
|
| 706 |
.DefaultPseudoDualSimulations <- function() {
|
|
| 707 | 1x |
stop( |
| 708 | 1x |
"Class PseudoDualSimulations cannot be instantiated directly. Please use a subclass." |
| 709 |
) |
|
| 710 |
} |
|
| 711 | ||
| 712 |
# PseudoDualFlexiSimulations ---- |
|
| 713 | ||
| 714 |
## class ---- |
|
| 715 | ||
| 716 |
#' `PseudoDualFlexiSimulations` |
|
| 717 |
#' |
|
| 718 |
#' @description `r lifecycle::badge("stable")`
|
|
| 719 |
#' This class captures the trial simulations design using both the DLE and |
|
| 720 |
#' efficacy responses using [`EffFlexi`] efficacy model. |
|
| 721 |
#' It extends [`PseudoDualSimulations`] by adding the capability to capture the sigma2betaW estimates. |
|
| 722 |
#' |
|
| 723 |
#' @slot sigma2_beta_w_est (`numeric`)\cr the vector of the final posterior mean sigma2betaW estimates |
|
| 724 |
#' @aliases PseudoDualFlexiSimulations |
|
| 725 |
#' @export |
|
| 726 |
.PseudoDualFlexiSimulations <- |
|
| 727 |
setClass( |
|
| 728 |
Class = "PseudoDualFlexiSimulations", |
|
| 729 |
slots = c(sigma2_beta_w_est = "numeric"), |
|
| 730 |
prototype = prototype(sigma2_beta_w_est = c(0.001, 0.002)), |
|
| 731 |
contains = "PseudoDualSimulations" |
|
| 732 |
) |
|
| 733 | ||
| 734 |
## constructor ---- |
|
| 735 | ||
| 736 |
#' @rdname PseudoDualFlexiSimulations-class |
|
| 737 |
#' |
|
| 738 |
#' @param sigma2_beta_w_est (`numeric`)\cr the vector of the final posterior mean sigma2betaW estimates |
|
| 739 |
#' @param \dots additional parameters from [`PseudoDualSimulations`] |
|
| 740 |
#' |
|
| 741 |
#' @export |
|
| 742 |
PseudoDualFlexiSimulations <- function(sigma2_beta_w_est, ...) {
|
|
| 743 | 2x |
start <- PseudoDualSimulations(...) |
| 744 | 2x |
.PseudoDualFlexiSimulations(start, sigma2_beta_w_est = sigma2_beta_w_est) |
| 745 |
} |
|
| 746 | ||
| 747 |
## default constructor ---- |
|
| 748 | ||
| 749 |
#' @rdname PseudoDualFlexiSimulations-class |
|
| 750 |
#' @note Typically, end users will not use the `.DefaultPseudoFlexiSimulations()` function. |
|
| 751 |
#' @export |
|
| 752 |
.DefaultPseudoDualFlexiSimulations <- function() {
|
|
| 753 | 1x |
stop( |
| 754 | 1x |
"Class PseudoFlexiSimulations cannot be instantiated directly. Please use one of its subclasses instead." |
| 755 |
) |
|
| 756 |
} |
|
| 757 | ||
| 758 |
# PseudoSimulationsSummary ---- |
|
| 759 | ||
| 760 |
## class ---- |
|
| 761 | ||
| 762 |
#' `PseudoSimulationsSummary` |
|
| 763 |
#' |
|
| 764 |
#' @description `r lifecycle::badge("stable")`
|
|
| 765 |
#' |
|
| 766 |
#' This class captures the summary of pseudo-models simulations output. |
|
| 767 |
#' Note that objects should not be created by users, therefore no |
|
| 768 |
#' initialization function is provided for this class. |
|
| 769 |
#' |
|
| 770 |
#' @slot target_end_of_trial (`numeric`)\cr the target probability of DLE wanted at the end of a trial |
|
| 771 |
#' @slot target_dose_end_of_trial (`numeric`)\cr the dose level corresponds to the target probability |
|
| 772 |
#' of DLE wanted at the end of a trial, TDEOT |
|
| 773 |
#' @slot target_dose_end_of_trial_at_dose_grid (`numeric`)\cr the dose level at dose grid corresponds to the |
|
| 774 |
#' target probability of DLE wanted at the end of a trial |
|
| 775 |
#' @slot target_during_trial (`numeric`)\cr the target probability of DLE wanted during a trial |
|
| 776 |
#' @slot target_dose_during_trial (`numeric`)\cr the dose level corresponds to the target probability of DLE |
|
| 777 |
#' wanted during the trial. TDDT |
|
| 778 |
#' @slot target_dose_during_trial_at_dose_grid (`numeric`)\cr the dose level at dose grid corresponds to the |
|
| 779 |
#' target probability of DLE wanted during a trial |
|
| 780 |
#' @slot tdeot_summary (`table`)\cr the six-number table summary, include the lowest, the 25th percentile |
|
| 781 |
#' (lower quartile), the 50th percentile (median), the mean, the 75th percentile and the highest values of the |
|
| 782 |
#' final dose levels obtained corresponds to the target probability of DLE |
|
| 783 |
#' want at the end of a trial across all simulations |
|
| 784 |
#' @slot tddt_summary (`table`)\cr the six-number table summary, include the lowest, the 25th percentile |
|
| 785 |
#' (lower quartile), the 50th percentile (median), the mean, the 75th percentile and the highest values of the |
|
| 786 |
#' final dose levels obtained corresponds to the target probability of DLE |
|
| 787 |
#' want during a trial across all simulations |
|
| 788 |
#' @slot final_dose_rec_summary (`table`)\cr the six-number table summary, include the lowest, the 25th percentile |
|
| 789 |
#' (lower quartile), the 50th percentile (median), the mean, the 75th percentile and the highest values of the |
|
| 790 |
#' final optimal doses, which is either the TDEOT when only DLE response are incorporated into |
|
| 791 |
#' the escalation procedure or the minimum of the TDEOT and Gstar when DLE and efficacy responses are |
|
| 792 |
#' incorporated, across all simulations |
|
| 793 |
#' @slot ratio_tdeot_summary (`table`)\cr the six-number summary table of the final ratios of the upper to the |
|
| 794 |
#' lower 95% credibility intervals of the final TDEOTs across all simulations |
|
| 795 |
#' @slot final_ratio_summary (`table`)\cr the six-number summary table of the final ratios of the upper to the |
|
| 796 |
#' lower 95% credibility intervals of the final optimal doses across all simulations |
|
| 797 |
#' @slot nsim (`integer`)\cr number of simulations |
|
| 798 |
#' @slot prop_dle (`numeric`)\cr proportions of DLE in the trials |
|
| 799 |
#' @slot mean_tox_risk (`numeric`)\cr mean toxicity risks for the patients |
|
| 800 |
#' @slot dose_selected (`numeric`)\cr doses selected as MTD (target_dose_end_of_trial) |
|
| 801 |
#' @slot tox_at_doses_selected (`numeric`)\cr true toxicity at doses selected |
|
| 802 |
#' @slot prop_at_target_end_of_trial (`numeric`)\cr Proportion of trials selecting at the dose_grid closest below the |
|
| 803 |
#' MTD, the target_dose_end_of_trial |
|
| 804 |
#' @slot prop_at_target_during_trial (`numeric`)\cr Proportion of trials selecting at the dose_grid closest below |
|
| 805 |
#' the target_dose_during_trial |
|
| 806 |
#' @slot dose_most_selected (`numeric`)\cr dose most often selected as MTD |
|
| 807 |
#' @slot obs_tox_rate_at_dose_most_selected (`numeric`)\cr observed toxicity rate at dose most often |
|
| 808 |
#' selected |
|
| 809 |
#' @slot n_obs (`integer`)\cr number of patients overall |
|
| 810 |
#' @slot n_above_target_end_of_trial (`integer`)\cr number of patients treated above target_dose_end_of_trial |
|
| 811 |
#' @slot n_above_target_during_trial (`integer`)\cr number of patients treated above target_dose_during_trial |
|
| 812 |
#' @slot dose_grid (`numeric`)\cr the dose grid that has been used |
|
| 813 |
#' @slot fit_at_dose_most_selected (`numeric`)\cr fitted toxicity rate at dose most often selected |
|
| 814 |
#' @slot mean_fit (`list`)\cr list with the average, lower (2.5%) and upper (97.5%) |
|
| 815 |
#' quantiles of the mean fitted toxicity at each dose level |
|
| 816 |
#' @slot stop_report (`matrix`)\cr matrix of stopping rule outcomes |
|
| 817 |
#' |
|
| 818 |
#' @aliases PseudoSimulationsSummary |
|
| 819 |
#' @export |
|
| 820 |
.PseudoSimulationsSummary <- |
|
| 821 |
setClass( |
|
| 822 |
Class = "PseudoSimulationsSummary", |
|
| 823 |
slots = c( |
|
| 824 |
target_end_of_trial = "numeric", |
|
| 825 |
target_dose_end_of_trial = "numeric", |
|
| 826 |
target_dose_end_of_trial_at_dose_grid = "numeric", |
|
| 827 |
target_during_trial = "numeric", |
|
| 828 |
target_dose_during_trial = "numeric", |
|
| 829 |
target_dose_during_trial_at_dose_grid = "numeric", |
|
| 830 |
tdeot_summary = "table", |
|
| 831 |
tddt_summary = "table", |
|
| 832 |
final_dose_rec_summary = "table", |
|
| 833 |
ratio_tdeot_summary = "table", |
|
| 834 |
final_ratio_summary = "table", |
|
| 835 |
nsim = "integer", |
|
| 836 |
prop_dle = "numeric", |
|
| 837 |
mean_tox_risk = "numeric", |
|
| 838 |
dose_selected = "numeric", |
|
| 839 |
tox_at_doses_selected = "numeric", |
|
| 840 |
prop_at_target_end_of_trial = "numeric", |
|
| 841 |
prop_at_target_during_trial = "numeric", |
|
| 842 |
dose_most_selected = "numeric", |
|
| 843 |
obs_tox_rate_at_dose_most_selected = "numeric", |
|
| 844 |
n_obs = "integer", |
|
| 845 |
n_above_target_end_of_trial = "integer", |
|
| 846 |
n_above_target_during_trial = "integer", |
|
| 847 |
dose_grid = "numeric", |
|
| 848 |
fit_at_dose_most_selected = "numeric", |
|
| 849 |
mean_fit = "list", |
|
| 850 |
stop_report = "matrix" |
|
| 851 |
) |
|
| 852 |
) |
|
| 853 | ||
| 854 |
## default constructor ---- |
|
| 855 | ||
| 856 |
#' @rdname PseudoSimulationsSummary-class |
|
| 857 |
#' @note Typically, end users will not use the `.DefaultPseudoSimulationsSummary()` function. |
|
| 858 |
#' @export |
|
| 859 |
.DefaultPseudoSimulationsSummary <- function() {
|
|
| 860 | 2x |
stop( |
| 861 | 2x |
"Class PseudoSimulationsSummary cannot be instantiated directly. Please use one of its subclasses instead." |
| 862 |
) |
|
| 863 |
} |
|
| 864 | ||
| 865 |
# PseudoDualSimulationsSummary ---- |
|
| 866 | ||
| 867 |
## class ---- |
|
| 868 | ||
| 869 |
#' `PseudoDualSimulationsSummary` |
|
| 870 |
#' |
|
| 871 |
#' @description `r lifecycle::badge("stable")`
|
|
| 872 |
#' |
|
| 873 |
#' This class captures the summary of the dual responses simulations using pseudo models. |
|
| 874 |
#' It contains all slots from [`PseudoSimulationsSummary`] object. In addition to |
|
| 875 |
#' the slots in the parent class [`PseudoSimulationsSummary`], it contains additional |
|
| 876 |
#' slots for the efficacy model fit information. |
|
| 877 |
#' |
|
| 878 |
#' Note that objects should not be created by users, therefore no initialization function |
|
| 879 |
#' is provided for this class. |
|
| 880 |
#' |
|
| 881 |
#' @slot target_gstar (`numeric`)\cr the target dose level such that its gain value is at maximum |
|
| 882 |
#' @slot target_gstar_at_dose_grid (`numeric`)\cr the dose level at dose Grid closest and below Gstar |
|
| 883 |
#' @slot gstar_summary (`table`)\cr the six-number table summary (lowest, 25th, 50th (median), 75th percentile, mean |
|
| 884 |
#' and highest value) of the final Gstar values obtained across all simulations |
|
| 885 |
#' @slot ratio_gstar_summary (`table`)\cr the six-number summary table of the ratios of the upper to the lower 95% |
|
| 886 |
#' credibility intervals of the final Gstar across all simulations |
|
| 887 |
#' @slot eff_fit_at_dose_most_selected (`numeric`)\cr fitted expected mean efficacy value at dose most often |
|
| 888 |
#' selected |
|
| 889 |
#' @slot mean_eff_fit (`list`)\cr list with mean, lower (2.5%) and upper (97.5%) quantiles of the fitted expected |
|
| 890 |
#' efficacy value at each dose level. |
|
| 891 |
#' |
|
| 892 |
#' @aliases PseudoDualSimulationsSummary |
|
| 893 |
#' @export |
|
| 894 |
.PseudoDualSimulationsSummary <- |
|
| 895 |
setClass( |
|
| 896 |
Class = "PseudoDualSimulationsSummary", |
|
| 897 |
contains = "PseudoSimulationsSummary", |
|
| 898 |
slots = c( |
|
| 899 |
target_gstar = "numeric", |
|
| 900 |
target_gstar_at_dose_grid = "numeric", |
|
| 901 |
gstar_summary = "table", |
|
| 902 |
ratio_gstar_summary = "table", |
|
| 903 |
eff_fit_at_dose_most_selected = "numeric", |
|
| 904 |
mean_eff_fit = "list" |
|
| 905 |
) |
|
| 906 |
) |
|
| 907 | ||
| 908 |
## default constructor ---- |
|
| 909 | ||
| 910 |
#' @rdname PseudoDualSimulationsSummary-class |
|
| 911 |
#' @note Typically, end users will not use the `.DefaultPseudoDualSimulationsSummary()` function. |
|
| 912 |
#' @export |
|
| 913 |
.DefaultPseudoDualSimulationsSummary <- function() {
|
|
| 914 | 2x |
stop( |
| 915 | 2x |
"Class PseudoDualSimulationsSummary cannot be instantiated directly. Please use one of its subclasses instead." |
| 916 |
) |
|
| 917 |
} |
|
| 918 | ||
| 919 |
# DASimulations ---- |
|
| 920 | ||
| 921 |
## class ---- |
|
| 922 | ||
| 923 |
#' `DASimulations` |
|
| 924 |
#' |
|
| 925 |
#' @description `r lifecycle::badge("stable")`
|
|
| 926 |
#' |
|
| 927 |
#' This class captures the trial simulations from DA based designs. |
|
| 928 |
#' In comparison to the parent class [`Simulations`], |
|
| 929 |
#' it contains additional slots to capture the time to DLT fits, additional |
|
| 930 |
#' parameters and the trial duration. |
|
| 931 |
#' |
|
| 932 |
#' @slot trial_duration (`numeric`)\cr the vector of trial duration values for all simulations. |
|
| 933 |
#' |
|
| 934 |
#' @aliases DASimulations |
|
| 935 |
#' @export |
|
| 936 |
.DASimulations <- |
|
| 937 |
setClass( |
|
| 938 |
Class = "DASimulations", |
|
| 939 |
slots = c(trial_duration = "numeric"), |
|
| 940 |
prototype = prototype(trial_duration = rep(0, 2)), |
|
| 941 |
contains = "Simulations", |
|
| 942 |
validity = v_da_simulations |
|
| 943 |
) |
|
| 944 | ||
| 945 |
## constructor ---- |
|
| 946 | ||
| 947 |
#' @rdname DASimulations-class |
|
| 948 |
#' |
|
| 949 |
#' @param trial_duration (`numeric`)\cr see [`DASimulations`] |
|
| 950 |
#' @param \dots additional parameters from [`Simulations`] |
|
| 951 |
#' |
|
| 952 |
#' @export |
|
| 953 |
DASimulations <- function(trial_duration, ...) {
|
|
| 954 | 3x |
start <- Simulations(...) |
| 955 | 3x |
.DASimulations(start, trial_duration = trial_duration) |
| 956 |
} |
|
| 957 | ||
| 958 | ||
| 959 |
## default constructor ---- |
|
| 960 | ||
| 961 |
#' @rdname DASimulations-class |
|
| 962 |
#' @note Typically, end users will not use the `.DefaultDASimulations()` function. |
|
| 963 |
#' @export |
|
| 964 |
.DefaultDASimulations <- function() {
|
|
| 965 | ! |
design <- .DefaultDADesign() |
| 966 | ! |
myTruth <- probFunction(design@model, alpha0 = 2, alpha1 = 3) |
| 967 | ! |
exp_cond_cdf <- function(x, onset = 15) {
|
| 968 | ! |
a <- stats::pexp(28, 1 / onset, lower.tail = FALSE) |
| 969 | ! |
1 - (stats::pexp(x, 1 / onset, lower.tail = FALSE) - a) / (1 - a) |
| 970 |
} |
|
| 971 | ||
| 972 | ! |
simulate( |
| 973 | ! |
design, |
| 974 | ! |
args = NULL, |
| 975 | ! |
truthTox = myTruth, |
| 976 | ! |
truthSurv = exp_cond_cdf, |
| 977 | ! |
trueTmax = 80, |
| 978 | ! |
nsim = 2, |
| 979 | ! |
seed = 819, |
| 980 | ! |
mcmcOptions = .DefaultMcmcOptions(), |
| 981 | ! |
firstSeparate = TRUE, |
| 982 | ! |
deescalate = FALSE, |
| 983 | ! |
parallel = FALSE |
| 984 |
) |
|
| 985 |
} |
|
| 986 | ||
| 987 |
# tidy |
|
| 988 | ||
| 989 |
## tidy-Simulations ---- |
|
| 990 | ||
| 991 |
#' @rdname tidy |
|
| 992 |
#' @aliases tidy-Simulations |
|
| 993 |
#' @example examples/Simulations-method-tidy.R |
|
| 994 |
#' @export |
|
| 995 |
setMethod( |
|
| 996 |
f = "tidy", |
|
| 997 |
signature = signature(x = "Simulations"), |
|
| 998 |
definition = function(x, ...) {
|
|
| 999 | 8x |
slot_names <- slotNames(x) |
| 1000 | 8x |
rv <- list() |
| 1001 | 8x |
for (nm in slot_names) {
|
| 1002 | 64x |
if (!is.function(slot(x, nm))) {
|
| 1003 | 64x |
if (nm %in% c("stop_reasons", "additional_stats")) {} else {
|
| 1004 | 48x |
rv[[nm]] <- h_tidy_slot(x, nm) |
| 1005 |
} |
|
| 1006 |
} |
|
| 1007 |
} |
|
| 1008 |
# Column bind of all list elements have the same number of rows |
|
| 1009 | 8x |
if (length(rv) > 1 & length(unique(sapply(rv, nrow))) == 1) {
|
| 1010 | ! |
rv <- rv %>% dplyr::bind_cols() |
| 1011 |
} |
|
| 1012 | 8x |
rv %>% h_tidy_class(x) |
| 1013 |
} |
|
| 1014 |
) |
| 1 |
#' @include McmcOptions-methods.R |
|
| 2 |
NULL |
|
| 3 | ||
| 4 |
# v_samples ---- |
|
| 5 | ||
| 6 |
#' Internal Helper Functions for Validation of [`Samples`] Objects |
|
| 7 |
#' |
|
| 8 |
#' @description These functions are only used internally to validate the format |
|
| 9 |
#' of an input [`Samples`] or inherited classes and therefore not exported. |
|
| 10 |
#' |
|
| 11 |
#' @name v_samples_objects |
|
| 12 |
#' @param object (`Samples`)\cr object to validate. |
|
| 13 |
#' @return A `character` vector with the validation failure messages, or `TRUE` |
|
| 14 |
#' in case validation passes. |
|
| 15 |
NULL |
|
| 16 | ||
| 17 |
#' @describeIn v_samples_objects validates that the [`Samples`] object contains |
|
| 18 |
#' valid `data` slot. |
|
| 19 |
v_samples <- function(object) {
|
|
| 20 | 7x |
v <- Validate() |
| 21 | 7x |
v$check( |
| 22 | 7x |
all(sapply(object@data, NROW) == size(object@options)), |
| 23 | 7x |
"Every element in data must be of the same length (no. of rows) as the sample size was" |
| 24 |
) |
|
| 25 | 7x |
v$check( |
| 26 | 7x |
all(sapply(object@data, test_numeric, finite = TRUE, any.missing = FALSE)), |
| 27 | 7x |
"Every element in data must be a finite object of type integer or double" |
| 28 |
) |
|
| 29 | 7x |
v$result() |
| 30 |
} |
| 1 |
#' Check That Labels Are Valid and Useful |
|
| 2 |
#' |
|
| 3 |
#' A vector of labels is valid and useful if it is of length 2, of type character |
|
| 4 |
#' and its values are distinct. |
|
| 5 |
#' |
|
| 6 |
#' If `x` is a scalar, a second element is added, whose value is the value of the |
|
| 7 |
#' scalar with "s" appended. If `x` is `"toxicity"`, the plural is handled appropriately. |
|
| 8 |
#' |
|
| 9 |
#' @param x (`character`)\cr The vector to be checked |
|
| 10 |
#' @keywords internal |
|
| 11 |
#' @return a character vector of length 2 whose values are distinct |
|
| 12 |
h_prepare_labels <- function(x) {
|
|
| 13 | 602x |
assert_character( |
| 14 | 602x |
x, |
| 15 | 602x |
min.len = 1, |
| 16 | 602x |
max.len = 2, |
| 17 | 602x |
any.missing = FALSE, |
| 18 | 602x |
unique = TRUE |
| 19 |
) |
|
| 20 | ||
| 21 | 600x |
if (length(x) == 1) {
|
| 22 | 391x |
if (x == "toxicity") {
|
| 23 | 165x |
x <- c("toxicity", "toxicities")
|
| 24 |
} else {
|
|
| 25 | 226x |
x[2] <- paste0(x[1], "s") |
| 26 |
} |
|
| 27 |
} |
|
| 28 | 600x |
x |
| 29 |
} |
|
| 30 | ||
| 31 |
#' Append Units to a Numeric Dose |
|
| 32 |
#' |
|
| 33 |
#' @param units (`character`)\cr the units to be displayed |
|
| 34 |
#' @keywords internal |
|
| 35 |
#' @return if `units` is `NA`, then `NA`. Otherwise, `units`, ensuring that exactly |
|
| 36 |
#' one space precedes the first non-whitespace character |
|
| 37 |
h_prepare_units <- function(units = NA) {
|
|
| 38 | 222x |
assert_character(units, len = 1) |
| 39 | ||
| 40 | 222x |
ifelse( |
| 41 | 222x |
is.na(units), |
| 42 |
"", |
|
| 43 | 222x |
paste0(" ", stringr::str_trim(units, "left"))
|
| 44 |
) |
|
| 45 |
} |
| 1 |
#' Apply a Function to Subsets of Data Frame. |
|
| 2 |
#' |
|
| 3 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 4 |
#' |
|
| 5 |
#' `dapply` splits the data `df` into the subsets defined by `f`, |
|
| 6 |
#' and applies function `FUN` to each of the subset. |
|
| 7 |
#' All the results are row-binded and returned as `data.frame` object. |
|
| 8 |
#' |
|
| 9 |
#' @param df (`data frame`)\cr data set to be divided into groups. |
|
| 10 |
#' @param f (`factor` or `formula` or `list`)\cr a factor in the sense that |
|
| 11 |
#' `as.factor(f)` defines the grouping, or a `list` of such factors in which |
|
| 12 |
#' case their interaction is used for the grouping. `f` can also be a formula |
|
| 13 |
#' of the form `~ g1 + ... + gk` to split by the interaction of the variables |
|
| 14 |
#' `g1, ..., gk`. This parameter is passed directly into [split()] function. |
|
| 15 |
#' @param FUN (`function`)\cr the function to be applied to each subset of `df` |
|
| 16 |
#' defined by `f`. |
|
| 17 |
#' @param ... parameters passed to [lapply()], which is used when applying a |
|
| 18 |
#' function `FUN` over groups defined by `f`. |
|
| 19 |
#' |
|
| 20 |
#' @return The [`data.frame`] object with results from `FUN`. |
|
| 21 |
#' |
|
| 22 |
#' @export |
|
| 23 |
#' @example examples/utils-dapply.R |
|
| 24 |
#' |
|
| 25 |
dapply <- function(df, f, FUN, ...) {
|
|
| 26 | 11x |
assert_data_frame(df) |
| 27 | ||
| 28 | 11x |
list_df <- split(df, f = f) |
| 29 | 11x |
list_df <- lapply(list_df, FUN, ...) |
| 30 | 11x |
df2 <- do.call(rbind, list_df) |
| 31 | 11x |
rownames(df2) <- NULL |
| 32 | 11x |
df2 |
| 33 |
} |
| 1 |
#' @include helpers.R |
|
| 2 |
#' @include ModelParams-validity.R |
|
| 3 |
#' @include CrmPackClass-class.R |
|
| 4 |
NULL |
|
| 5 | ||
| 6 |
# ModelParamsNormal ---- |
|
| 7 | ||
| 8 |
## class ---- |
|
| 9 | ||
| 10 |
#' `ModelParamsNormal` |
|
| 11 |
#' |
|
| 12 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 13 |
#' |
|
| 14 |
#' [`ModelParamsNormal`] is the class for a bivariate normal model parameters, |
|
| 15 |
#' i.e. the mean vector, covariance matrix and precision matrix. |
|
| 16 |
#' The precision matrix is an inverse of the covariance matrix in the |
|
| 17 |
#' `JAGS` and it is computed internally by the object constructor function. |
|
| 18 |
#' |
|
| 19 |
#' @slot mean (`numeric`)\cr the mean vector. |
|
| 20 |
#' @slot cov (`matrix`)\cr the covariance matrix. |
|
| 21 |
#' @slot prec (`matrix`)\cr the precision matrix, which is an inverse matrix of the `cov`. |
|
| 22 |
#' |
|
| 23 |
#' @seealso [`ModelLogNormal`], [`LogisticNormalMixture`]. |
|
| 24 |
#' |
|
| 25 |
#' @aliases ModelParamsNormal |
|
| 26 |
#' @export |
|
| 27 |
#' |
|
| 28 |
.ModelParamsNormal <- setClass( |
|
| 29 |
Class = "ModelParamsNormal", |
|
| 30 |
slots = c( |
|
| 31 |
mean = "numeric", |
|
| 32 |
cov = "matrix", |
|
| 33 |
prec = "matrix" |
|
| 34 |
), |
|
| 35 |
contains = "CrmPackClass", |
|
| 36 |
validity = v_model_params_normal |
|
| 37 |
) |
|
| 38 | ||
| 39 |
## constructor ---- |
|
| 40 | ||
| 41 |
#' @rdname ModelParamsNormal-class |
|
| 42 |
#' |
|
| 43 |
#' @param mean (`numeric`)\cr the prior mean vector. |
|
| 44 |
#' @param cov (`matrix`)\cr the prior covariance matrix. The precision matrix |
|
| 45 |
#' `prec` is internally calculated as an inverse of `cov`. |
|
| 46 |
#' |
|
| 47 |
#' @export |
|
| 48 |
#' @examples |
|
| 49 |
#' ModelParamsNormal(mean = c(1, 6), cov = diag(2)) |
|
| 50 |
ModelParamsNormal <- function(mean, cov) {
|
|
| 51 | 775x |
assert_matrix( |
| 52 | 775x |
cov, |
| 53 | 775x |
mode = "numeric", |
| 54 | 775x |
any.missing = FALSE, |
| 55 | 775x |
nrows = length(mean), |
| 56 | 775x |
ncols = length(mean) |
| 57 |
) |
|
| 58 |
# To ensure that `cov` is invertible: |
|
| 59 | 775x |
assert_true(h_is_positive_definite(cov, length(mean))) |
| 60 | ||
| 61 | 775x |
.ModelParamsNormal( |
| 62 | 775x |
mean = mean, |
| 63 | 775x |
cov = cov, |
| 64 | 775x |
prec = solve(cov) |
| 65 |
) |
|
| 66 |
} |
|
| 67 | ||
| 68 |
## default constructor ---- |
|
| 69 | ||
| 70 |
#' @rdname ModelParamsNormal-class |
|
| 71 |
#' @note Typically, end users will not use the `.ModelPAramsNormal()` function. |
|
| 72 |
#' @export |
|
| 73 |
.DefaultModelParamsNormal <- function() {
|
|
| 74 | 5x |
ModelParamsNormal( |
| 75 | 5x |
mean = c(1, 0), |
| 76 | 5x |
cov = matrix(c(1, 0, 0, 1), nrow = 2) |
| 77 |
) |
|
| 78 |
} |
| 1 |
#' Convert a Samples Object from an ordinal Model to the Equivalent Samples Object |
|
| 2 |
#' from a Binary Model |
|
| 3 |
#' |
|
| 4 |
#' @description `r lifecycle::badge("experimental")`
|
|
| 5 |
#' |
|
| 6 |
#' A simple helper function that converts a [`Samples`] object from the fit of an |
|
| 7 |
#' ordinal CRM model to that which would have been obtained from fitting a binary |
|
| 8 |
#' CRM model for toxicities of a specified grade to the same observed data. |
|
| 9 |
#' |
|
| 10 |
#' @param obj (`Samples`)\cr the `Samples` object to covert |
|
| 11 |
#' @param grade (`integer`)\cr the toxicity grade for which the equivalent data |
|
| 12 |
#' is required. |
|
| 13 |
#' @return A [`Samples`] object. |
|
| 14 |
#' |
|
| 15 |
#' @export |
|
| 16 |
h_convert_ordinal_samples <- function(obj, grade) {
|
|
| 17 |
# Validate |
|
| 18 | 29x |
assert_integer(grade, len = 1, lower = 1) |
| 19 | 27x |
assert_class(obj, "Samples") |
| 20 | 26x |
assert_subset(c(paste0("alpha", 1:grade), "beta"), names(obj@data))
|
| 21 |
# Execute |
|
| 22 | 25x |
d <- list( |
| 23 | 25x |
"alpha0" = obj@data[[paste0("alpha", grade)]],
|
| 24 | 25x |
"alpha1" = obj@data$beta |
| 25 |
) |
|
| 26 | 25x |
Samples(data = d, options = obj@options) |
| 27 |
} |
| 1 |
# nolint start |
|
| 2 | ||
| 3 |
#' Object-oriented implementation of CRM designs |
|
| 4 |
#' |
|
| 5 |
#' @name crmPack |
|
| 6 |
#' @title Object-oriented implementation of CRM designs |
|
| 7 |
#' @import checkmate |
|
| 8 |
#' @import ggplot2 |
|
| 9 |
#' @import methods |
|
| 10 |
#' @import tibble |
|
| 11 |
#' @importFrom grid grid.draw |
|
| 12 |
#' @importFrom gridExtra arrangeGrob |
|
| 13 |
#' @importFrom graphics plot hist legend lines matlines matplot |
|
| 14 |
#' @importFrom stats binomial coef cov2cor gaussian glm lm median model.matrix |
|
| 15 |
#' optim pgamma plogis pnorm qgamma qlogis qnorm quantile rbinom rgamma |
|
| 16 |
#' approxfun rnorm runif uniroot var vcov step mad pbeta dbeta dgamma |
|
| 17 |
#' setNames |
|
| 18 |
#' @importFrom utils data head tail capture.output |
|
| 19 |
#' @importFrom lifecycle badge |
|
| 20 |
#' @importFrom rjags jags.model jags.samples |
|
| 21 |
#' @importFrom futile.logger flog.threshold flog.logger flog.trace TRACE FATAL |
|
| 22 |
#' @importFrom knitr knit_print |
|
| 23 |
#' @importFrom kableExtra kbl add_header_above column_spec collapse_rows |
|
| 24 |
#' kable_styling add_footnote kable |
|
| 25 |
#' |
|
| 26 |
#' @keywords package |
|
| 27 |
#' @references Sabanes Bove D, Yeung WY, Palermo G, Jaki T (2019). |
|
| 28 |
#' "Model-Based Dose Escalation Designs in R with crmPack." |
|
| 29 |
#' Journal of Statistical Software, 89(10), 1-22. |
|
| 30 |
#' doi:10.18637/jss.v089.i10 (URL: http://doi.org/10.18637/jss.v089.i10). |
|
| 31 |
"_PACKAGE" |
|
| 32 | ||
| 33 |
##' @keywords internal |
|
| 34 |
.onAttach <- function(libname, pkgname) {
|
|
| 35 | 3x |
packageStartupMessage( |
| 36 | 3x |
"Type crmPackHelp() to open help browser\n", |
| 37 | 3x |
"Type crmPackExample() to open example\n" |
| 38 |
) |
|
| 39 |
} |
|
| 40 | ||
| 41 |
## need to declare global variable / function |
|
| 42 |
## names in order to avoid R CMD check notes: |
|
| 43 |
globalVariables(c( |
|
| 44 |
"log.betaZ", |
|
| 45 |
"precW", |
|
| 46 |
"pow", |
|
| 47 |
"nObs", |
|
| 48 |
"betaZ", |
|
| 49 |
"x", |
|
| 50 |
"betaW", |
|
| 51 |
"xLevel", |
|
| 52 |
"precW", |
|
| 53 |
"z", |
|
| 54 |
"nGrid", |
|
| 55 |
"doseGrid", |
|
| 56 |
"betaWintercept", |
|
| 57 |
"delta", |
|
| 58 |
"deltaStart", |
|
| 59 |
"delta2", |
|
| 60 |
"Effsamples", |
|
| 61 |
"logit<-", |
|
| 62 |
"rho0", |
|
| 63 |
"alpha0", |
|
| 64 |
"delta0", |
|
| 65 |
"alpha1", |
|
| 66 |
"delta1", |
|
| 67 |
"inverse", |
|
| 68 |
"priorCov", |
|
| 69 |
"theta", |
|
| 70 |
"comp0", |
|
| 71 |
"w", |
|
| 72 |
"DLTs", |
|
| 73 |
"y", |
|
| 74 |
"group", |
|
| 75 |
"annotate", |
|
| 76 |
"probSamples", |
|
| 77 |
"prec", |
|
| 78 |
"nu", |
|
| 79 |
"samples", |
|
| 80 |
"Type", |
|
| 81 |
"patient", |
|
| 82 |
"toxicity", |
|
| 83 |
"ID", |
|
| 84 |
"biomarker", |
|
| 85 |
"traj", |
|
| 86 |
"Statistic", |
|
| 87 |
"perc", |
|
| 88 |
"..density..", |
|
| 89 |
"middle", |
|
| 90 |
"lower", |
|
| 91 |
"upper", |
|
| 92 |
"middleBiomarker", |
|
| 93 |
"lowerBiomarker", |
|
| 94 |
"upperBiomarker", |
|
| 95 |
"nObsshare", |
|
| 96 |
"xshare", |
|
| 97 |
"yshare", |
|
| 98 |
"thisProb.PL", |
|
| 99 |
"thisMeanEff.PL", |
|
| 100 |
"thisSize.PL", |
|
| 101 |
"probit<-", |
|
| 102 |
"refDose", |
|
| 103 |
"Tmax", |
|
| 104 |
"u", |
|
| 105 |
"eps", |
|
| 106 |
"h", |
|
| 107 |
"lambda", |
|
| 108 |
"cadj", |
|
| 109 |
"A", |
|
| 110 |
"lambda_p", |
|
| 111 |
"cond", |
|
| 112 |
"t0", |
|
| 113 |
"tend", |
|
| 114 |
"t0_case", |
|
| 115 |
"tend_case", |
|
| 116 |
"yhat", |
|
| 117 |
"ref_dose", |
|
| 118 |
"comp", |
|
| 119 |
"X", |
|
| 120 |
"skel_probs", |
|
| 121 |
"is_combo", |
|
| 122 |
"results", |
|
| 123 |
"k", |
|
| 124 |
"value", |
|
| 125 |
"Parameter", |
|
| 126 |
"intervals", |
|
| 127 |
"Group", |
|
| 128 |
"Tox", |
|
| 129 |
"MaxOverdoseProb", |
|
| 130 |
"DoseGrid", |
|
| 131 |
"NGrid", |
|
| 132 |
"NObs", |
|
| 133 |
"XLevel" |
|
| 134 |
)) |
|
| 135 | ||
| 136 |
# nolint end |