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, |
|
61 |
cohort_size, |
|
62 |
data, |
|
63 |
startingDose) { |
|
64 | 101x |
new( |
65 | 101x |
"RuleDesign", |
66 | 101x |
nextBest = nextBest, |
67 | 101x |
cohort_size = cohort_size, |
68 | 101x |
data = data, |
69 | 101x |
startingDose = as.numeric(startingDose) |
70 |
) |
|
71 |
} |
|
72 | ||
73 |
#' @rdname RuleDesign-class |
|
74 |
#' @note Typically, end users will not use the `.DefaultRuleDesign()` function. |
|
75 |
#' @export |
|
76 | ||
77 |
.DefaultRuleDesign <- function() { |
|
78 | 8x |
RuleDesign( |
79 | 8x |
nextBest = NextBestThreePlusThree(), |
80 | 8x |
cohort_size = CohortSizeConst(size = 3L), |
81 | 8x |
data = Data(doseGrid = c(5, 10, 15, 25, 35, 50, 80)), |
82 | 8x |
startingDose = 5 |
83 |
) |
|
84 |
} |
|
85 | ||
86 |
## ThreePlusThreeDesign ---- |
|
87 | ||
88 |
#' @describeIn RuleDesign-class creates a new 3+3 design object from a dose grid. |
|
89 |
#' |
|
90 |
#' @param doseGrid (`numeric`)\cr the dose grid to be used (sorted). |
|
91 |
#' |
|
92 |
#' @export |
|
93 |
#' @example examples/Design-class-ThreePlusThreeDesign.R |
|
94 |
#' |
|
95 |
ThreePlusThreeDesign <- function(doseGrid) { |
|
96 | 3x |
empty_data <- Data(doseGrid = doseGrid) |
97 | ||
98 |
# Using a constant cohort size of 3 we obtain exactly the 3+3 design. |
|
99 | 3x |
RuleDesign( |
100 | 3x |
nextBest = NextBestThreePlusThree(), |
101 | 3x |
data = empty_data, |
102 | 3x |
cohort_size = CohortSizeConst(size = 3L), |
103 | 3x |
startingDose = doseGrid[1] |
104 |
) |
|
105 |
} |
|
106 | ||
107 |
# Design ---- |
|
108 | ||
109 |
## class ---- |
|
110 | ||
111 |
#' `Design` |
|
112 |
#' |
|
113 |
#' @description `r lifecycle::badge("stable")` |
|
114 |
#' |
|
115 |
#' [`Design`] is the class for rule-based designs. The difference between |
|
116 |
#' this class and its parent [`RuleDesign`] class is that [`Design`] class |
|
117 |
#' contains additional `model`, `stopping` and `increments` slots. |
|
118 |
#' |
|
119 |
#' @slot model (`GeneralModel`)\cr the model to be used. |
|
120 |
#' @slot stopping (`Stopping`)\cr stopping rule(s) for the trial. |
|
121 |
#' @slot increments (`Increments`)\cr how to control increments between dose levels. |
|
122 |
#' @slot pl_cohort_size (`CohortSize`)\cr rules for the cohort sizes for placebo, |
|
123 |
#' if any planned (defaults to constant 0 placebo patients). |
|
124 |
#' |
|
125 |
#' @aliases Design |
|
126 |
#' @export |
|
127 |
#' |
|
128 |
.Design <- setClass( |
|
129 |
Class = "Design", |
|
130 |
slots = c( |
|
131 |
model = "GeneralModel", |
|
132 |
stopping = "Stopping", |
|
133 |
increments = "Increments", |
|
134 |
pl_cohort_size = "CohortSize" |
|
135 |
), |
|
136 |
prototype = prototype( |
|
137 |
model = .LogisticNormal(), |
|
138 |
nextBest = .NextBestNCRM(), |
|
139 |
stopping = .StoppingMinPatients(), |
|
140 |
increments = .IncrementsRelative(), |
|
141 |
pl_cohort_size = CohortSizeConst(0L) |
|
142 |
), |
|
143 |
contains = "RuleDesign" |
|
144 |
) |
|
145 | ||
146 |
## constructor ---- |
|
147 | ||
148 |
#' @rdname Design-class |
|
149 |
#' |
|
150 |
#' @param model (`GeneralModel`)\cr see slot definition. |
|
151 |
#' @param stopping (`Stopping`)\cr see slot definition. |
|
152 |
#' @param increments (`Increments`)\cr see slot definition. |
|
153 |
#' @param pl_cohort_size (`CohortSize`)\cr see slot definition. |
|
154 |
#' @inheritDotParams RuleDesign |
|
155 |
#' |
|
156 |
#' @export |
|
157 |
#' @example examples/Design-class-Design.R |
|
158 |
#' |
|
159 |
#' |
|
160 |
Design <- function(model, |
|
161 |
stopping, |
|
162 |
increments, |
|
163 |
pl_cohort_size = CohortSizeConst(0L), |
|
164 |
...) { |
|
165 | 50x |
start <- RuleDesign(...) |
166 | 50x |
new( |
167 | 50x |
"Design", |
168 | 50x |
start, |
169 | 50x |
model = model, |
170 | 50x |
stopping = stopping, |
171 | 50x |
increments = increments, |
172 | 50x |
pl_cohort_size = pl_cohort_size |
173 |
) |
|
174 |
} |
|
175 | ||
176 |
## default constructor ---- |
|
177 | ||
178 |
#' @rdname Design-class |
|
179 |
#' @note Typically, end users will not use the `.DefaultDesign()` function. |
|
180 |
#' @export |
|
181 |
.DefaultDesign <- function() { |
|
182 | 7x |
my_size1 <- CohortSizeRange( |
183 | 7x |
intervals = c(0, 30), |
184 | 7x |
cohort_size = c(1, 3) |
185 |
) |
|
186 | 7x |
my_size2 <- CohortSizeDLT( |
187 | 7x |
intervals = c(0, 1), |
188 | 7x |
cohort_size = c(1, 3) |
189 |
) |
|
190 | 7x |
my_size <- maxSize(my_size1, my_size2) |
191 | ||
192 | 7x |
my_stopping1 <- StoppingMinCohorts(nCohorts = 3) |
193 | 7x |
my_stopping2 <- StoppingTargetProb( |
194 | 7x |
target = c(0.2, 0.35), |
195 | 7x |
prob = 0.5 |
196 |
) |
|
197 | 7x |
my_stopping3 <- StoppingMinPatients(nPatients = 20) |
198 | 7x |
my_stopping <- (my_stopping1 & my_stopping2) | my_stopping3 |
199 | ||
200 |
# Initialize the design. |
|
201 | 7x |
design <- Design( |
202 | 7x |
model = LogisticLogNormal( |
203 | 7x |
mean = c(-0.85, 1), |
204 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
205 | 7x |
ref_dose = 56 |
206 |
), |
|
207 | 7x |
nextBest = NextBestNCRM( |
208 | 7x |
target = c(0.2, 0.35), |
209 | 7x |
overdose = c(0.35, 1), |
210 | 7x |
max_overdose_prob = 0.25 |
211 |
), |
|
212 | 7x |
stopping = my_stopping, |
213 | 7x |
increments = IncrementsRelative( |
214 | 7x |
intervals = c(0, 20), |
215 | 7x |
increments = c(1, 0.33) |
216 |
), |
|
217 | 7x |
cohort_size = my_size, |
218 | 7x |
data = Data(doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100)), |
219 | 7x |
startingDose = 3 |
220 |
) |
|
221 |
} |
|
222 | ||
223 |
# DualDesign ---- |
|
224 | ||
225 |
## class ---- |
|
226 | ||
227 |
#' `DualDesign` |
|
228 |
#' |
|
229 |
#' @description `r lifecycle::badge("stable")` |
|
230 |
#' |
|
231 |
#' [`DualDesign`] is the class for the dual-endpoint CRM design. This class has |
|
232 |
#' special requirements for the `model` and `data` slots in comparison to the |
|
233 |
#' parent class [`Design`]. |
|
234 |
#' |
|
235 |
#' @note the `nextBest` slot can be of any class, this allows for easy comparison |
|
236 |
#' with recommendation methods that don't use the biomarker information. |
|
237 |
#' |
|
238 |
#' @slot model (`DualEndpoint`)\cr the model to be used. |
|
239 |
#' @slot data (`DataDual`)\cr specifies dose grid, any previous data, etc. |
|
240 |
#' |
|
241 |
#' @aliases DualDesign |
|
242 |
#' @export |
|
243 |
#' |
|
244 |
.DualDesign <- setClass( |
|
245 |
Class = "DualDesign", |
|
246 |
slots = c( |
|
247 |
model = "DualEndpoint", |
|
248 |
data = "DataDual" |
|
249 |
), |
|
250 |
prototype = prototype( |
|
251 |
model = .DualEndpoint(), |
|
252 |
nextBest = .NextBestDualEndpoint(), |
|
253 |
data = DataDual(doseGrid = 1:2), |
|
254 |
startingDose = 1 |
|
255 |
), |
|
256 |
contains = "Design" |
|
257 |
) |
|
258 | ||
259 |
## constructor ---- |
|
260 | ||
261 |
#' @rdname DualDesign-class |
|
262 |
#' |
|
263 |
#' @param model (`DualEndpoint`)\cr see slot definition. |
|
264 |
#' @param data (`DataDual`)\cr see slot definition. |
|
265 |
#' @inheritDotParams Design |
|
266 |
#' |
|
267 |
#' @export |
|
268 |
#' @example examples/Design-class-DualDesign.R |
|
269 |
#' |
|
270 |
DualDesign <- function(model, |
|
271 |
data, |
|
272 |
...) { |
|
273 | 10x |
start <- Design(model = model, data = data, ...) |
274 | 10x |
new( |
275 | 10x |
"DualDesign", |
276 | 10x |
start, |
277 | 10x |
model = model, |
278 | 10x |
data = data |
279 |
) |
|
280 |
} |
|
281 | ||
282 |
## default constructor ---- |
|
283 | ||
284 |
#' @rdname DualDesign-class |
|
285 |
#' @note Typically, end users will not use the `.DefaultDualDesign()` function. |
|
286 |
#' @export |
|
287 |
.DefaultDualDesign <- function() { |
|
288 | 8x |
my_model <- DualEndpointRW( |
289 | 8x |
mean = c(0, 1), |
290 | 8x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
291 | 8x |
sigma2betaW = 0.01, |
292 | 8x |
sigma2W = c(a = 0.1, b = 0.1), |
293 | 8x |
rho = c(a = 1, b = 1), |
294 | 8x |
rw1 = TRUE |
295 |
) |
|
296 | ||
297 |
# Choose the rule for selecting the next dose. |
|
298 | 8x |
my_next_best <- NextBestDualEndpoint( |
299 | 8x |
target = c(0.9, 1), |
300 | 8x |
overdose = c(0.35, 1), |
301 | 8x |
max_overdose_prob = 0.25 |
302 |
) |
|
303 | ||
304 |
# Choose the rule for the cohort-size. |
|
305 | 8x |
my_size1 <- CohortSizeRange( |
306 | 8x |
intervals = c(0, 30), |
307 | 8x |
cohort_size = c(1, 3) |
308 |
) |
|
309 | 8x |
my_size2 <- CohortSizeDLT( |
310 | 8x |
intervals = c(0, 1), |
311 | 8x |
cohort_size = c(1, 3) |
312 |
) |
|
313 | 8x |
my_size <- maxSize(my_size1, my_size2) |
314 | ||
315 |
# Choose the rule for stopping. |
|
316 | 8x |
my_stopping1 <- StoppingTargetBiomarker( |
317 | 8x |
target = c(0.9, 1), |
318 | 8x |
prob = 0.5 |
319 |
) |
|
320 | 8x |
my_stopping <- my_stopping1 | StoppingMinPatients(40) |
321 | ||
322 |
# Choose the rule for dose increments. |
|
323 | 8x |
my_increments <- IncrementsRelative( |
324 | 8x |
intervals = c(0, 20), |
325 | 8x |
increments = c(1, 0.33) |
326 |
) |
|
327 | ||
328 |
# Initialize the design. |
|
329 | 8x |
DualDesign( |
330 | 8x |
model = my_model, |
331 | 8x |
data = DataDual(doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100)), |
332 | 8x |
nextBest = my_next_best, |
333 | 8x |
stopping = my_stopping, |
334 | 8x |
increments = my_increments, |
335 | 8x |
cohort_size = my_size, |
336 | 8x |
startingDose = 3 |
337 |
) |
|
338 |
} |
|
339 | ||
340 |
# TDsamplesDesign ---- |
|
341 | ||
342 |
## class ---- |
|
343 | ||
344 |
#' `TDsamplesDesign` |
|
345 |
#' |
|
346 |
#' @description `r lifecycle::badge("stable")` |
|
347 |
#' |
|
348 |
#' [`TDsamplesDesign`] is the class of design based only on DLT responses using |
|
349 |
#' [`ModelTox`] class model (i.e. [`LogisticIndepBeta`]) as well as MCMC samples |
|
350 |
#' obtained for this model. |
|
351 |
#' |
|
352 |
#' @slot model (`ModelTox`)\cr the pseudo DLT model to be used. |
|
353 |
#' @slot stopping (`Stopping`)\cr stopping rule(s) for the trial. |
|
354 |
#' @slot increments (`Increments`)\cr how to control increments between dose levels. |
|
355 |
#' @slot pl_cohort_size (`CohortSize`)\cr rules for the cohort sizes for placebo, |
|
356 |
#' if any planned (defaults to constant 0 placebo patients). |
|
357 |
#' |
|
358 |
#' @aliases TDsamplesDesign |
|
359 |
#' @export |
|
360 |
#' |
|
361 |
.TDsamplesDesign <- setClass( |
|
362 |
Class = "TDsamplesDesign", |
|
363 |
slots = c( |
|
364 |
model = "ModelTox", |
|
365 |
stopping = "Stopping", |
|
366 |
increments = "Increments", |
|
367 |
pl_cohort_size = "CohortSize" |
|
368 |
), |
|
369 |
prototype = prototype( |
|
370 |
model = .LogisticIndepBeta(), |
|
371 |
nextBest = .NextBestTDsamples(), |
|
372 |
stopping = .StoppingMinPatients(), |
|
373 |
increments = .IncrementsRelative(), |
|
374 |
pl_cohort_size = CohortSizeConst(0L) |
|
375 |
), |
|
376 |
contains = "RuleDesign" |
|
377 |
) |
|
378 | ||
379 |
## constructor ---- |
|
380 | ||
381 |
#' @rdname TDsamplesDesign-class |
|
382 |
#' |
|
383 |
#' @param model (`ModelTox`)\cr see slot definition. |
|
384 |
#' @param stopping (`Stopping`)\cr see slot definition. |
|
385 |
#' @param increments (`Increments`)\cr see slot definition. |
|
386 |
#' @param pl_cohort_size (`CohortSize`)\cr see slot definition. |
|
387 |
#' @inheritDotParams RuleDesign |
|
388 |
#' |
|
389 |
#' @export |
|
390 |
#' @example examples/Design-class-TDsamplesDesign.R |
|
391 |
#' |
|
392 |
TDsamplesDesign <- function(model, |
|
393 |
stopping, |
|
394 |
increments, |
|
395 |
pl_cohort_size = CohortSizeConst(0L), |
|
396 |
...) { |
|
397 | 17x |
start <- RuleDesign(...) |
398 | 17x |
new( |
399 | 17x |
"TDsamplesDesign", |
400 | 17x |
start, |
401 | 17x |
model = model, |
402 | 17x |
stopping = stopping, |
403 | 17x |
increments = increments, |
404 | 17x |
pl_cohort_size = pl_cohort_size |
405 |
) |
|
406 |
} |
|
407 | ||
408 |
## default constructor ---- |
|
409 | ||
410 |
#' @rdname TDsamplesDesign-class |
|
411 |
#' @note Typically, end users will not use the `.DefaultTDsamplesDesign()` function. |
|
412 |
#' @export |
|
413 |
.DefaultTDsamplesDesign <- function() { |
|
414 | 5x |
empty_data <- Data(doseGrid = seq(25, 300, 25)) |
415 | ||
416 | 5x |
my_model <- LogisticIndepBeta( |
417 | 5x |
binDLE = c(1.05, 1.8), |
418 | 5x |
DLEweights = c(3, 3), |
419 | 5x |
DLEdose = c(25, 300), |
420 | 5x |
data = empty_data |
421 |
) |
|
422 | ||
423 | 5x |
TDsamplesDesign( |
424 | 5x |
model = my_model, |
425 | 5x |
stopping = StoppingMinPatients(nPatients = 36), |
426 | 5x |
increments = IncrementsRelative( |
427 | 5x |
intervals = range(empty_data@doseGrid), |
428 | 5x |
increments = c(2, 2) |
429 |
), |
|
430 | 5x |
nextBest = NextBestTDsamples( |
431 | 5x |
prob_target_drt = 0.35, |
432 | 5x |
prob_target_eot = 0.3, |
433 | 5x |
derive = function(samples) { |
434 | 5x |
as.numeric(quantile(samples, probs = 0.3)) |
435 |
} |
|
436 |
), |
|
437 | 5x |
cohort_size = CohortSizeConst(size = 3), |
438 | 5x |
data = empty_data, |
439 | 5x |
startingDose = 25 |
440 |
) |
|
441 |
} |
|
442 | ||
443 |
# TDDesign ---- |
|
444 | ||
445 |
## class ---- |
|
446 | ||
447 |
#' `TDDesign` |
|
448 |
#' |
|
449 |
#' @description `r lifecycle::badge("stable")` |
|
450 |
#' |
|
451 |
#' [`TDDesign`] is the class of design based only on DLT responses using |
|
452 |
#' [`ModelTox`] class model (i.e. [`LogisticIndepBeta`]) without MCMC samples. |
|
453 |
#' |
|
454 |
#' @slot model (`ModelTox`)\cr the pseudo DLT model to be used. |
|
455 |
#' @slot stopping (`Stopping`)\cr stopping rule(s) for the trial. |
|
456 |
#' @slot increments (`Increments`)\cr how to control increments between dose levels. |
|
457 |
#' @slot pl_cohort_size (`CohortSize`)\cr rules for the cohort sizes for placebo, |
|
458 |
#' if any planned (defaults to constant 0 placebo patients). |
|
459 |
#' |
|
460 |
#' @aliases TDDesign |
|
461 |
#' @export |
|
462 |
#' |
|
463 |
.TDDesign <- setClass( |
|
464 |
Class = "TDDesign", |
|
465 |
slots = c( |
|
466 |
model = "ModelTox", |
|
467 |
stopping = "Stopping", |
|
468 |
increments = "Increments", |
|
469 |
pl_cohort_size = "CohortSize" |
|
470 |
), |
|
471 |
prototype = prototype( |
|
472 |
model = .LogisticIndepBeta(), |
|
473 |
nextBest = .NextBestTD(), |
|
474 |
stopping = .StoppingMinPatients(), |
|
475 |
increments = .IncrementsRelative(), |
|
476 |
pl_cohort_size = CohortSizeConst(0L) |
|
477 |
), |
|
478 |
contains = "RuleDesign" |
|
479 |
) |
|
480 | ||
481 |
## constructor ---- |
|
482 | ||
483 |
#' @rdname TDDesign-class |
|
484 |
#' |
|
485 |
#' @param model (`ModelTox`)\cr see slot definition. |
|
486 |
#' @param stopping (`Stopping`)\cr see slot definition. |
|
487 |
#' @param increments (`Increments`)\cr see slot definition. |
|
488 |
#' @param pl_cohort_size (`CohortSize`)\cr see slot definition. |
|
489 |
#' @inheritDotParams RuleDesign |
|
490 |
#' |
|
491 |
#' @export |
|
492 |
#' @example examples/Design-class-TDDesign.R |
|
493 |
#' |
|
494 |
TDDesign <- function(model, |
|
495 |
stopping, |
|
496 |
increments, |
|
497 |
pl_cohort_size = CohortSizeConst(0L), |
|
498 |
...) { |
|
499 | 19x |
start <- RuleDesign(...) |
500 | 19x |
new( |
501 | 19x |
"TDDesign", |
502 | 19x |
start, |
503 | 19x |
model = model, |
504 | 19x |
stopping = stopping, |
505 | 19x |
increments = increments, |
506 | 19x |
pl_cohort_size = pl_cohort_size |
507 |
) |
|
508 |
} |
|
509 | ||
510 |
## default constructor ---- |
|
511 | ||
512 |
#' @rdname TDDesign-class |
|
513 |
#' @note Typically, end users will not use the `.DefaultTDDesign()` function. |
|
514 |
#' @export |
|
515 |
.DefaultTDDesign <- function() { |
|
516 | 7x |
empty_data <- Data(doseGrid = seq(25, 300, 25)) |
517 | ||
518 | 7x |
my_model <- LogisticIndepBeta( |
519 | 7x |
binDLE = c(1.05, 1.8), |
520 | 7x |
DLEweights = c(3, 3), |
521 | 7x |
DLEdose = c(25, 300), |
522 | 7x |
data = empty_data |
523 |
) |
|
524 | ||
525 | 7x |
TDDesign( |
526 | 7x |
model = my_model, |
527 | 7x |
stopping = StoppingMinPatients(nPatients = 36), |
528 | 7x |
increments = IncrementsRelative( |
529 | 7x |
intervals = range(empty_data@doseGrid), |
530 | 7x |
increments = c(2, 2) |
531 |
), |
|
532 | 7x |
nextBest = NextBestTD( |
533 | 7x |
prob_target_drt = 0.35, |
534 | 7x |
prob_target_eot = 0.3 |
535 |
), |
|
536 | 7x |
cohort_size = CohortSizeConst(size = 3), |
537 | 7x |
data = empty_data, |
538 | 7x |
startingDose = 25 |
539 |
) |
|
540 |
} |
|
541 | ||
542 |
# DualResponsesSamplesDesign ---- |
|
543 | ||
544 |
## class ---- |
|
545 | ||
546 |
#' `DualResponsesSamplesDesign` |
|
547 |
#' |
|
548 |
#' @description `r lifecycle::badge("stable")` |
|
549 |
#' |
|
550 |
#' This is a class of design based on DLE responses using the [`LogisticIndepBeta`] model |
|
551 |
# and efficacy responses using [`ModelEff`] model class |
|
552 |
#' with DLE and efficacy samples. It contain all slots in |
|
553 |
#' [`RuleDesign`] and [`TDsamplesDesign`] class objects. |
|
554 |
# |
|
555 |
#' @slot data (`DataDual`)\cr the data set. |
|
556 |
#' @slot eff_model (`ModelEff`)\cr the pseudo efficacy model to be used. |
|
557 |
#' |
|
558 |
#' @aliases DualResponsesSamplesDesign |
|
559 |
#' @export |
|
560 |
#' |
|
561 |
.DualResponsesSamplesDesign <- |
|
562 |
setClass( |
|
563 |
Class = "DualResponsesSamplesDesign", |
|
564 |
slots = c( |
|
565 |
eff_model = "ModelEff", |
|
566 |
data = "DataDual" |
|
567 |
), |
|
568 |
prototype = prototype( |
|
569 |
nextBest = .NextBestMaxGainSamples(), |
|
570 |
data = DataDual(doseGrid = 1:2), |
|
571 |
startingDose = 1, |
|
572 |
model = .LogisticIndepBeta() |
|
573 |
), |
|
574 |
contains = "TDsamplesDesign" |
|
575 |
) |
|
576 | ||
577 |
## constructor ---- |
|
578 | ||
579 |
#' @rdname DualResponsesSamplesDesign-class |
|
580 |
#' |
|
581 |
#' @param data (`DataDual`)\cr see slot definition. |
|
582 |
#' @param eff_model (`ModelEff`)\cr see slot definition. |
|
583 |
#' @inheritDotParams TDsamplesDesign |
|
584 |
#' |
|
585 |
#' @example examples/Design-class-DualResponsesSamplesDesign.R |
|
586 |
#' @export |
|
587 |
#' |
|
588 |
DualResponsesSamplesDesign <- function(eff_model, |
|
589 |
data, |
|
590 |
...) { |
|
591 | 9x |
start <- TDsamplesDesign(data = data, ...) |
592 | 9x |
.DualResponsesSamplesDesign( |
593 | 9x |
start, |
594 | 9x |
eff_model = eff_model, |
595 | 9x |
data = data |
596 |
) |
|
597 |
} |
|
598 | ||
599 |
## default constructor ---- |
|
600 | ||
601 |
#' @rdname DualResponsesSamplesDesign-class |
|
602 |
#' @note Typically, end users will not use the `.DefaultDualResponsesSamplesDesign()` function. |
|
603 |
#' @export |
|
604 |
.DefaultDualResponsesSamplesDesign <- function() { |
|
605 | 7x |
empty_data <- DataDual(doseGrid = seq(25, 300, 25)) |
606 | ||
607 | 7x |
tox_model <- LogisticIndepBeta( |
608 | 7x |
binDLE = c(1.05, 1.8), |
609 | 7x |
DLEweights = c(3, 3), |
610 | 7x |
DLEdose = c(25, 300), |
611 | 7x |
data = empty_data |
612 |
) |
|
613 | 7x |
options <- McmcOptions(burnin = 100, step = 2, samples = 200) |
614 | 7x |
tox_samples <- mcmc(empty_data, tox_model, options) |
615 | ||
616 | 7x |
eff_model <- Effloglog( |
617 | 7x |
eff = c(1.223, 2.513), |
618 | 7x |
eff_dose = c(25, 300), |
619 | 7x |
nu = c(a = 1, b = 0.025), |
620 | 7x |
data = empty_data |
621 |
) |
|
622 | 7x |
eff_samples <- mcmc(empty_data, eff_model, options) |
623 | ||
624 | 7x |
my_next_best <- NextBestMaxGainSamples( |
625 | 7x |
prob_target_drt = 0.35, |
626 | 7x |
prob_target_eot = 0.3, |
627 | 7x |
derive = function(samples) { |
628 | 7x |
as.numeric(quantile(samples, prob = 0.3)) |
629 |
}, |
|
630 | 7x |
mg_derive = function(mg_samples) { |
631 | 7x |
as.numeric(quantile(mg_samples, prob = 0.5)) |
632 |
} |
|
633 |
) |
|
634 | ||
635 | 7x |
DualResponsesSamplesDesign( |
636 | 7x |
nextBest = my_next_best, |
637 | 7x |
cohort_size = CohortSizeConst(size = 3), |
638 | 7x |
startingDose = 25, |
639 | 7x |
model = tox_model, |
640 | 7x |
eff_model = eff_model, |
641 | 7x |
data = empty_data, |
642 | 7x |
stopping = StoppingMinPatients(nPatients = 36), |
643 | 7x |
increments = IncrementsRelative( |
644 | 7x |
intervals = c(25, 300), |
645 | 7x |
increments = c(2, 2) |
646 |
) |
|
647 |
) |
|
648 |
} |
|
649 | ||
650 |
# DualResponsesDesign.R ---- |
|
651 | ||
652 |
## class ---- |
|
653 | ||
654 |
#' `DualResponsesDesign.R` |
|
655 |
#' |
|
656 |
#' @description `r lifecycle::badge("stable")` |
|
657 |
#' |
|
658 |
#' This is a class of design based on DLE responses using the [`LogisticIndepBeta`] model |
|
659 |
# and efficacy responses using the [`ModelEff`] model class |
|
660 |
#' without DLE and efficacy samples. It contains all slots from the |
|
661 |
#' [`RuleDesign`] and [`TDsamplesDesign`] classes. |
|
662 |
# |
|
663 |
#' @slot data (`DataDual`)\cr the data set. |
|
664 |
#' @slot eff_model (`ModelEff`)\cr the pseudo efficacy model to be used. |
|
665 |
#' |
|
666 |
#' @aliases DualResponsesDesign |
|
667 |
#' @export |
|
668 |
#' |
|
669 |
.DualResponsesDesign <- |
|
670 |
setClass( |
|
671 |
Class = "DualResponsesDesign", |
|
672 |
slots = c( |
|
673 |
eff_model = "ModelEff", |
|
674 |
data = "DataDual" |
|
675 |
), |
|
676 |
prototype = prototype( |
|
677 |
nextBest = .NextBestMaxGain(), |
|
678 |
data = DataDual(doseGrid = 1:2), |
|
679 |
startingDose = 1, |
|
680 |
model = .LogisticIndepBeta() |
|
681 |
), |
|
682 |
contains = "TDDesign" |
|
683 |
) |
|
684 | ||
685 |
## constructor ---- |
|
686 | ||
687 |
#' @rdname DualResponsesDesign-class |
|
688 |
#' |
|
689 |
#' @param data (`DataDual`)\cr see slot definition. |
|
690 |
#' @param eff_model (`ModelEff`)\cr see slot definition. |
|
691 |
#' @inheritDotParams TDDesign |
|
692 |
#' |
|
693 |
#' @example examples/Design-class-DualResponsesDesign.R |
|
694 |
#' @export |
|
695 |
#' |
|
696 |
DualResponsesDesign <- function(eff_model, |
|
697 |
data, |
|
698 |
...) { |
|
699 | 9x |
start <- TDDesign(data = data, ...) |
700 | 9x |
.DualResponsesDesign( |
701 | 9x |
start, |
702 | 9x |
eff_model = eff_model, |
703 | 9x |
data = data |
704 |
) |
|
705 |
} |
|
706 | ||
707 |
## default constructor ---- |
|
708 | ||
709 |
#' @rdname DualResponsesDesign-class |
|
710 |
#' @note Typically, end users will not use the `.DefaultDualResponsesDesign()` function. |
|
711 |
#' @export |
|
712 |
.DefaultDualResponsesDesign <- function() { |
|
713 | 7x |
empty_data <- DataDual(doseGrid = seq(25, 300, 25)) |
714 | ||
715 | 7x |
DualResponsesDesign( |
716 | 7x |
nextBest = NextBestMaxGain( |
717 | 7x |
prob_target_drt = 0.35, |
718 | 7x |
prob_target_eot = 0.3 |
719 |
), |
|
720 | 7x |
cohort_size = CohortSizeConst(size = 3), |
721 | 7x |
startingDose = 25, |
722 | 7x |
model = LogisticIndepBeta( |
723 | 7x |
binDLE = c(1.05, 1.8), |
724 | 7x |
DLEweights = c(3, 3), |
725 | 7x |
DLEdose = c(25, 300), |
726 | 7x |
data = empty_data |
727 |
), |
|
728 | 7x |
eff_model = Effloglog( |
729 | 7x |
eff = c(1.223, 2.513), |
730 | 7x |
eff_dose = c(25, 300), |
731 | 7x |
nu = c(a = 1, b = 0.025), |
732 | 7x |
data = empty_data |
733 |
), |
|
734 | 7x |
data = empty_data, |
735 | 7x |
stopping = StoppingMinPatients(nPatients = 36), |
736 | 7x |
increments = IncrementsRelative( |
737 | 7x |
intervals = c(25, 300), |
738 | 7x |
increments = c(2, 2) |
739 |
) |
|
740 |
) |
|
741 |
} |
|
742 | ||
743 | ||
744 |
# DADesign ---- |
|
745 | ||
746 |
## class ---- |
|
747 | ||
748 |
#' `DADesign` |
|
749 |
#' |
|
750 |
#' @description `r lifecycle::badge("stable")` |
|
751 |
#' |
|
752 |
#' This class has special requirements for the `model` and `data` |
|
753 |
#' slots in comparison to the parent class [`Design`]: |
|
754 |
#' |
|
755 |
#' @slot model (`GeneralModel`)\cr the model to use, see in particular [`DALogisticLogNormal`] and |
|
756 |
#' [`TITELogisticLogNormal`] which make use of the time-to-DLT data. |
|
757 |
#' @slot data (`DataDA`)\cr what is the dose grid, any previous data, etc. |
|
758 |
#' @slot safetyWindow (`SafetyWindow`)\cr the safety window to apply between cohorts. |
|
759 |
#' |
|
760 |
#' @details |
|
761 |
#' The `safetyWindow` slot should be an instance of the `SafetyWindow` class. |
|
762 |
#' It can be customized to specify the duration of the safety window for your trial. |
|
763 |
#' The safety window represents the time period required to observe toxicity data |
|
764 |
#' from the ongoing cohort before opening the next cohort. |
|
765 |
#' Note that even after opening the next cohort, |
|
766 |
#' further toxicity data will be collected and analyzed to make dose escalation decisions. |
|
767 |
#' |
|
768 |
#' |
|
769 |
#' To specify a constant safety window, use the `SafetyWindowConst` constructor. For example: |
|
770 |
#' |
|
771 |
#' \code{mysafetywindow <- SafetyWindowConst(c(6, 2), 10, 20)} |
|
772 |
#' |
|
773 |
#' @seealso [`SafetyWindowConst`] for creating a constant safety window. |
|
774 |
#' |
|
775 |
#' @aliases DADesign |
|
776 |
#' @export |
|
777 |
#' |
|
778 |
.DADesign <- |
|
779 |
setClass( |
|
780 |
Class = "DADesign", |
|
781 |
slots = c( |
|
782 |
model = "GeneralModel", |
|
783 |
data = "DataDA", |
|
784 |
safetyWindow = "SafetyWindow" |
|
785 |
), |
|
786 |
prototype = prototype( |
|
787 |
model = .DALogisticLogNormal(), |
|
788 |
nextBest = .NextBestNCRM(), |
|
789 |
data = DataDA(doseGrid = 1:2), |
|
790 |
safetyWindow = .SafetyWindowConst() |
|
791 |
), |
|
792 |
contains = "Design" |
|
793 |
) |
|
794 | ||
795 | ||
796 |
## constructor ---- |
|
797 | ||
798 |
#' @rdname DADesign-class |
|
799 |
#' |
|
800 |
#' @param model (`GeneralModel`)\cr see slot definition. |
|
801 |
#' @param data (`DataDA`)\cr see slot definition. |
|
802 |
#' @param safetyWindow (`SafetyWindow`)\cr see slot definition. |
|
803 |
#' @inheritDotParams Design |
|
804 |
#' |
|
805 |
#' @example examples/Design-class-DADesign.R |
|
806 |
#' @export |
|
807 |
#' |
|
808 |
DADesign <- function(model, data, |
|
809 |
safetyWindow, |
|
810 |
...) { |
|
811 | 9x |
start <- Design( |
812 | 9x |
data = data, |
813 | 9x |
model = model, |
814 |
... |
|
815 |
) |
|
816 | 9x |
.DADesign(start, |
817 | 9x |
safetyWindow = safetyWindow |
818 |
) |
|
819 |
} |
|
820 | ||
821 |
## default constructor ---- |
|
822 | ||
823 |
#' @rdname DADesign-class |
|
824 |
#' @note Typically, end users will not use the `.DefaultDADesign()` function. |
|
825 |
#' @export |
|
826 |
.DefaultDADesign <- function() { |
|
827 | 7x |
emptydata <- DataDA( |
828 | 7x |
doseGrid = c(0.1, 0.5, 1, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)), |
829 | 7x |
Tmax = 60 |
830 |
) |
|
831 | ||
832 | 7x |
npiece_ <- 10 |
833 | 7x |
t_max_ <- 60 |
834 | ||
835 | 7x |
lambda_prior <- function(k) { |
836 | 7x |
npiece_ / (t_max_ * (npiece_ - k + 0.5)) |
837 |
} |
|
838 | ||
839 | 7x |
model <- DALogisticLogNormal( |
840 | 7x |
mean = c(-0.85, 1), |
841 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
842 | 7x |
ref_dose = 56, |
843 | 7x |
npiece = npiece_, |
844 | 7x |
l = as.numeric(t(apply(as.matrix(c(1:npiece_), 1, npiece_), 2, lambda_prior))), |
845 | 7x |
c_par = 2 |
846 |
) |
|
847 | ||
848 | 7x |
mySize1 <- CohortSizeRange( |
849 | 7x |
intervals = c(0, 30), |
850 | 7x |
cohort_size = c(1, 3) |
851 |
) |
|
852 | 7x |
mySize2 <- CohortSizeDLT( |
853 | 7x |
intervals = c(0, 1), |
854 | 7x |
cohort_size = c(1, 3) |
855 |
) |
|
856 | 7x |
mySize <- maxSize(mySize1, mySize2) |
857 | ||
858 | 7x |
myStopping1 <- StoppingTargetProb( |
859 | 7x |
target = c(0.2, 0.35), |
860 | 7x |
prob = 0.5 |
861 |
) |
|
862 | 7x |
myStopping2 <- StoppingMinPatients(nPatients = 50) |
863 | 7x |
myStopping <- (myStopping1 | myStopping2) |
864 | ||
865 | 7x |
DADesign( |
866 | 7x |
model = model, |
867 | 7x |
increments = IncrementsRelative( |
868 | 7x |
intervals = c(0, 20), |
869 | 7x |
increments = c(1, 0.33) |
870 |
), |
|
871 | 7x |
nextBest = NextBestNCRM( |
872 | 7x |
target = c(0.2, 0.35), |
873 | 7x |
overdose = c(0.35, 1), |
874 | 7x |
max_overdose_prob = 0.25 |
875 |
), |
|
876 | 7x |
stopping = myStopping, |
877 | 7x |
cohort_size = mySize, |
878 | 7x |
data = emptydata, |
879 | 7x |
safetyWindow = SafetyWindowConst(c(6, 2), 7, 7), |
880 | 7x |
startingDose = 3 |
881 |
) |
|
882 |
} |
|
883 |
# DesignGrouped ---- |
|
884 | ||
885 |
## class ---- |
|
886 | ||
887 |
#' `DesignGrouped` |
|
888 |
#' |
|
889 |
#' @description `r lifecycle::badge("experimental")` |
|
890 |
#' |
|
891 |
#' [`DesignGrouped`] combines two [`Design`] objects: one for the mono and one |
|
892 |
#' for the combo arm of a joint dose escalation design. |
|
893 |
#' |
|
894 |
#' @slot model (`LogisticLogNormalGrouped`)\cr the model to be used, currently only one |
|
895 |
#' class is allowed. |
|
896 |
#' @slot mono (`Design`)\cr defines the dose escalation rules for the mono arm, see |
|
897 |
#' details. |
|
898 |
#' @slot combo (`Design`)\cr defines the dose escalation rules for the combo arm, see |
|
899 |
#' details. |
|
900 |
#' @slot first_cohort_mono_only (`flag`)\cr whether first test one mono agent cohort, and then |
|
901 |
#' once its DLT data has been collected, we proceed from the second cohort onwards with |
|
902 |
#' concurrent mono and combo cohorts. |
|
903 |
#' @slot same_dose_for_all (`flag`)\cr whether the lower dose of the separately determined mono and combo |
|
904 |
#' doses should be used as the next dose for both mono and combo in all cohorts. |
|
905 |
#' @slot same_dose_for_start (`flag`)\cr indicates whether, when mono and combo are |
|
906 |
#' used in the same cohort for the first time, the same dose should be used for both. |
|
907 |
#' Note that this is different from `same_dose_for_all` which will always force |
|
908 |
#' them to be the same. If `same_dose_for_all = TRUE`, this is therefore ignored. See Details. |
|
909 |
#' |
|
910 |
#' @details |
|
911 |
#' |
|
912 |
#' - Note that the model slots inside the `mono` and `combo` parameters |
|
913 |
#' are ignored (because we don't fit separate regression models for the mono and |
|
914 |
#' combo arms). Instead, the `model` parameter is used to fit a joint regression |
|
915 |
#' model for the mono and combo arms together. |
|
916 |
#' - `same_dose_for_start = TRUE` is useful as an option when we want to use `same_dose_for_all = FALSE` |
|
917 |
#' combined with `first_cohort_mono_only = TRUE`. |
|
918 |
#' This will allow to randomize patients to the mono and combo arms at the same dose |
|
919 |
#' as long as the selected dose for the cohorts stay the same. This can therefore |
|
920 |
#' further mitigate bias as long as possible between the mono and combo arms. |
|
921 |
#' |
|
922 |
#' @aliases DesignGrouped |
|
923 |
#' @export |
|
924 |
#' |
|
925 |
.DesignGrouped <- setClass( |
|
926 |
Class = "DesignGrouped", |
|
927 |
slots = c( |
|
928 |
model = "LogisticLogNormalGrouped", |
|
929 |
mono = "Design", |
|
930 |
combo = "Design", |
|
931 |
first_cohort_mono_only = "logical", |
|
932 |
same_dose_for_all = "logical", |
|
933 |
same_dose_for_start = "logical" |
|
934 |
), |
|
935 |
prototype = prototype( |
|
936 |
model = .DefaultLogisticLogNormalGrouped(), |
|
937 |
mono = .Design(), |
|
938 |
combo = .Design(), |
|
939 |
first_cohort_mono_only = TRUE, |
|
940 |
same_dose_for_all = TRUE, |
|
941 |
same_dose_for_start = FALSE |
|
942 |
), |
|
943 |
validity = v_design_grouped, |
|
944 |
contains = "CrmPackClass" |
|
945 |
) |
|
946 | ||
947 |
## constructor ---- |
|
948 | ||
949 |
#' @rdname DesignGrouped-class |
|
950 |
#' |
|
951 |
#' @param model (`LogisticLogNormalGrouped`)\cr see slot definition. |
|
952 |
#' @param mono (`Design`)\cr see slot definition. |
|
953 |
#' @param combo (`Design`)\cr see slot definition. |
|
954 |
#' @param first_cohort_mono_only (`flag`)\cr see slot definition. |
|
955 |
#' @param same_dose_for_all (`flag`)\cr see slot definition. |
|
956 |
#' @param same_dose_for_start (`flag`)\cr see slot definition. |
|
957 |
#' @param stop_mono_with_combo (`flag`)\cr whether the mono arm should be stopped when the combo |
|
958 |
#' arm is stopped (this makes sense when the only real trial objective is the recommended combo dose). |
|
959 |
#' @param ... not used. |
|
960 |
#' |
|
961 |
#' @export |
|
962 |
#' @example examples/Design-class-DesignGrouped.R |
|
963 |
#' |
|
964 |
DesignGrouped <- function(model, |
|
965 |
mono, |
|
966 |
combo = mono, |
|
967 |
first_cohort_mono_only = TRUE, |
|
968 |
same_dose_for_all = !same_dose_for_start, |
|
969 |
same_dose_for_start = FALSE, |
|
970 |
stop_mono_with_combo = FALSE, |
|
971 |
...) { |
|
972 | 10x |
assert_flag(stop_mono_with_combo) |
973 | 10x |
assert_class(mono, "Design") |
974 | 10x |
force(combo) |
975 | 10x |
if (stop_mono_with_combo) { |
976 | 3x |
mono@stopping <- mono@stopping | |
977 | 3x |
StoppingExternal(report_label = "Stop Mono with Combo") |
978 |
} |
|
979 | ||
980 | 10x |
.DesignGrouped( |
981 | 10x |
model = model, |
982 | 10x |
mono = mono, |
983 | 10x |
combo = combo, |
984 | 10x |
first_cohort_mono_only = first_cohort_mono_only, |
985 | 10x |
same_dose_for_all = same_dose_for_all, |
986 | 10x |
same_dose_for_start = same_dose_for_start |
987 |
) |
|
988 |
} |
|
989 | ||
990 |
## default constructor ---- |
|
991 | ||
992 |
#' @rdname DesignGrouped-class |
|
993 |
#' @note Typically, end-users will not use the `.DefaultDesignGrouped()` function. |
|
994 |
#' @export |
|
995 |
.DefaultDesignGrouped <- .DesignGrouped |
|
996 | ||
997 |
# RuleDesignOrdinal ---- |
|
998 | ||
999 |
## class ---- |
|
1000 | ||
1001 |
#' `RuleDesignOrdinal` |
|
1002 |
#' |
|
1003 |
#' @description `r lifecycle::badge("experimental")` |
|
1004 |
#' |
|
1005 |
#' [`RuleDesignOrdinal`] is the class for rule-based designs. The difference between |
|
1006 |
#' this class and the [`DesignOrdinal`] class is that [`RuleDesignOrdinal`] |
|
1007 |
#' does not contain `model`, `stopping` and `increments` slots. |
|
1008 |
#' |
|
1009 |
#' @slot next_best (`NextBestOrdinal`)\cr how to find the next best dose. |
|
1010 |
#' @slot cohort_size (`CohortSizeOrdinal`)\cr rules for the cohort sizes. |
|
1011 |
#' @slot data (`DataOrdinal`)\cr specifies dose grid, any previous data, etc. |
|
1012 |
#' @slot starting_dose (`number`)\cr the starting dose, it must lie on the dose |
|
1013 |
#' grid in `data`. |
|
1014 |
#' |
|
1015 |
#' @aliases RuleDesignOrdinal |
|
1016 |
#' @export |
|
1017 |
#' |
|
1018 |
.RuleDesignOrdinal <- setClass( |
|
1019 |
Class = "RuleDesignOrdinal", |
|
1020 |
slots = c( |
|
1021 |
next_best = "NextBestOrdinal", |
|
1022 |
cohort_size = "CohortSizeOrdinal", |
|
1023 |
data = "DataOrdinal", |
|
1024 |
starting_dose = "numeric" |
|
1025 |
), |
|
1026 |
prototype = prototype( |
|
1027 |
next_best = .NextBestOrdinal(), |
|
1028 |
cohort_size = CohortSizeOrdinal(1L, CohortSizeConst(3L)), |
|
1029 |
data = DataOrdinal(doseGrid = 1:3), |
|
1030 |
starting_dose = 1 |
|
1031 |
), |
|
1032 |
contains = "CrmPackClass", |
|
1033 |
validity = v_rule_design_ordinal |
|
1034 |
) |
|
1035 | ||
1036 |
## constructor ---- |
|
1037 | ||
1038 |
#' @rdname RuleDesignOrdinal-class |
|
1039 |
#' |
|
1040 |
#' @param next_best (`NextBestOrdinal`)\cr see slot definition. |
|
1041 |
#' @param cohort_size (`CohortSizeOrdinal`)\cr see slot definition. |
|
1042 |
#' @param data (`DataOrdinal`)\cr see slot definition. |
|
1043 |
#' @param starting_dose (`number`)\cr see slot definition. |
|
1044 |
#' |
|
1045 |
#' @export |
|
1046 |
#' @example examples/Design-class-RuleDesignOrdinal.R |
|
1047 |
#' |
|
1048 |
RuleDesignOrdinal <- function( |
|
1049 |
next_best, |
|
1050 |
cohort_size, |
|
1051 |
data, |
|
1052 |
starting_dose) { |
|
1053 | 17x |
new( |
1054 | 17x |
"RuleDesignOrdinal", |
1055 | 17x |
next_best = next_best, |
1056 | 17x |
cohort_size = cohort_size, |
1057 | 17x |
data = data, |
1058 | 17x |
starting_dose = as.numeric(starting_dose) |
1059 |
) |
|
1060 |
} |
|
1061 | ||
1062 |
#' @rdname RuleDesignOrdinal-class |
|
1063 |
#' @note Typically, end users will not use the `.DefaultRuleDesignOrdinal()` function. |
|
1064 |
#' @export |
|
1065 | ||
1066 |
.DefaultRuleDesignOrdinal <- function() { |
|
1067 | 9x |
RuleDesignOrdinal( |
1068 | 9x |
next_best = NextBestOrdinal( |
1069 | 9x |
1L, |
1070 | 9x |
NextBestMTD(target = 0.25, derive = function(x) mean(x, na.rm = TRUE)) |
1071 |
), |
|
1072 | 9x |
cohort_size = CohortSizeOrdinal(1L, CohortSizeConst(size = 3L)), |
1073 | 9x |
data = DataOrdinal(doseGrid = c(5, 10, 15, 25, 35, 50, 80)), |
1074 | 9x |
starting_dose = 5 |
1075 |
) |
|
1076 |
} |
|
1077 | ||
1078 |
# DesignOrdinal ---- |
|
1079 | ||
1080 |
## class ---- |
|
1081 | ||
1082 |
#' `DesignOrdinal` |
|
1083 |
#' |
|
1084 |
#' @description `r lifecycle::badge("experimental")` |
|
1085 |
#' |
|
1086 |
#' [`DesignOrdinal`] is the class for rule-based ordinal designs. The difference |
|
1087 |
#' between this class and its parent [`RuleDesignOrdinal`] class is that the |
|
1088 |
#' [`DesignOrdinal`] class contains additional `model`, `stopping`, |
|
1089 |
#' `increments` and `pl_cohort_size` slots. |
|
1090 |
#' |
|
1091 |
#' @slot model (`LogisticLogNormalOrdinal`)\cr the model to be used. |
|
1092 |
#' @slot stopping (`StoppingOrdinal`)\cr stopping rule(s) for the trial. |
|
1093 |
#' @slot increments (`IncrementsOrdinal`)\cr how to control increments between dose levels. |
|
1094 |
#' @slot pl_cohort_size (`CohortSizeOrdinal`)\cr rules for the cohort sizes for placebo, |
|
1095 |
#' if any planned (defaults to constant 0 placebo patients). |
|
1096 |
#' |
|
1097 |
#' @aliases DesignOrdinal |
|
1098 |
#' @export |
|
1099 |
#' |
|
1100 |
.DesignOrdinal <- setClass( |
|
1101 |
Class = "DesignOrdinal", |
|
1102 |
slots = c( |
|
1103 |
model = "LogisticLogNormalOrdinal", |
|
1104 |
stopping = "StoppingOrdinal", |
|
1105 |
increments = "IncrementsOrdinal", |
|
1106 |
pl_cohort_size = "CohortSizeOrdinal" |
|
1107 |
), |
|
1108 |
prototype = prototype( |
|
1109 |
model = .LogisticLogNormalOrdinal(), |
|
1110 |
next_best = .NextBestOrdinal(), |
|
1111 |
stopping = .StoppingOrdinal(), |
|
1112 |
increments = .IncrementsOrdinal(), |
|
1113 |
pl_cohort_size = CohortSizeOrdinal(1L, CohortSizeConst(3L)) |
|
1114 |
), |
|
1115 |
contains = "RuleDesignOrdinal" |
|
1116 |
) |
|
1117 | ||
1118 |
## constructor ---- |
|
1119 | ||
1120 |
#' @rdname DesignOrdinal-class |
|
1121 |
#' |
|
1122 |
#' @param model (`LogisticLogNormalOrdinal`)\cr see slot definition. |
|
1123 |
#' @param stopping (`StoppingOrdinal`)\cr see slot definition. |
|
1124 |
#' @param increments (`IncrementsOrdinal`)\cr see slot definition. |
|
1125 |
#' @param pl_cohort_size (`CohortSizeOrdinal`)\cr see slot definition. |
|
1126 |
#' @inheritDotParams RuleDesignOrdinal |
|
1127 |
#' |
|
1128 |
#' @export |
|
1129 |
#' @example examples/Design-class-DesignOrdinal.R |
|
1130 |
#' |
|
1131 |
#' |
|
1132 |
DesignOrdinal <- function( |
|
1133 |
model, |
|
1134 |
stopping, |
|
1135 |
increments, |
|
1136 |
pl_cohort_size = CohortSizeOrdinal(1L, CohortSizeConst(0L)), |
|
1137 |
...) { |
|
1138 | 7x |
start <- RuleDesignOrdinal(...) |
1139 | 7x |
new( |
1140 | 7x |
"DesignOrdinal", |
1141 | 7x |
start, |
1142 | 7x |
model = model, |
1143 | 7x |
stopping = stopping, |
1144 | 7x |
increments = increments, |
1145 | 7x |
pl_cohort_size = pl_cohort_size |
1146 |
) |
|
1147 |
} |
|
1148 | ||
1149 |
## default constructor ---- |
|
1150 | ||
1151 |
#' @rdname DesignOrdinal-class |
|
1152 |
#' @note Typically, end users will not use the `.DefaultDesignOrdinal()` function. |
|
1153 |
#' @export |
|
1154 |
.DefaultDesignOrdinal <- function() { |
|
1155 | 5x |
my_size1 <- CohortSizeRange( |
1156 | 5x |
intervals = c(0, 30), |
1157 | 5x |
cohort_size = c(1, 3) |
1158 |
) |
|
1159 | 5x |
my_size2 <- CohortSizeDLT( |
1160 | 5x |
intervals = c(0, 1), |
1161 | 5x |
cohort_size = c(1, 3) |
1162 |
) |
|
1163 | 5x |
my_size <- CohortSizeOrdinal(1L, maxSize(my_size1, my_size2)) |
1164 | ||
1165 | 5x |
my_stopping1 <- StoppingMinCohorts(nCohorts = 3) |
1166 | 5x |
my_stopping2 <- StoppingTargetProb( |
1167 | 5x |
target = c(0.2, 0.35), |
1168 | 5x |
prob = 0.5 |
1169 |
) |
|
1170 | 5x |
my_stopping3 <- StoppingMinPatients(nPatients = 20) |
1171 | 5x |
my_stopping <- StoppingOrdinal(1L, (my_stopping1 & my_stopping2) | my_stopping3) |
1172 | ||
1173 |
# Initialize the design. |
|
1174 | 5x |
design <- DesignOrdinal( |
1175 | 5x |
model = LogisticLogNormalOrdinal( |
1176 | 5x |
mean = c(-3, -4, 1), |
1177 | 5x |
cov = diag(c(3, 4, 1)), |
1178 | 5x |
ref_dose = 50 |
1179 |
), |
|
1180 | 5x |
next_best = NextBestOrdinal( |
1181 | 5x |
1L, |
1182 | 5x |
NextBestNCRM( |
1183 | 5x |
target = c(0.2, 0.35), |
1184 | 5x |
overdose = c(0.35, 1), |
1185 | 5x |
max_overdose_prob = 0.25 |
1186 |
) |
|
1187 |
), |
|
1188 | 5x |
stopping = my_stopping, |
1189 | 5x |
increments = IncrementsOrdinal( |
1190 | 5x |
1L, |
1191 | 5x |
IncrementsRelative( |
1192 | 5x |
intervals = c(0, 20), |
1193 | 5x |
increments = c(1, 0.33) |
1194 |
) |
|
1195 |
), |
|
1196 | 5x |
cohort_size = my_size, |
1197 | 5x |
data = DataOrdinal( |
1198 | 5x |
doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100), |
1199 | 5x |
yCategories = c("No tox" = 0L, "Sub-tox AE" = 1L, "DLT" = 2L) |
1200 |
), |
|
1201 | 5x |
starting_dose = 3 |
1202 |
) |
|
1203 |
} |
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 | 408x |
if (!missing(doselimit)) { |
45 | 349x |
assert_number(doselimit, lower = 0, finite = FALSE) |
46 |
} |
|
47 | 408x |
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(data@doseGrid, doselimit, data@placebo) |
77 | 4x |
next_dose_level <- which.min(abs(doses_eligible - dose_target)) |
78 | 4x |
next_dose <- doses_eligible[next_dose_level] |
79 | ||
80 |
# Create a plot. |
|
81 | 4x |
p <- ggplot( |
82 | 4x |
data = data.frame(x = dose_target_samples), |
83 | 4x |
aes(.data$x), |
84 | 4x |
fill = "grey50", |
85 | 4x |
colour = "grey50" |
86 |
) + |
|
87 | 4x |
geom_density() + |
88 | 4x |
coord_cartesian(xlim = range(data@doseGrid)) + |
89 | 4x |
geom_vline(xintercept = dose_target, colour = "black", lwd = 1.1) + |
90 | 4x |
geom_text( |
91 | 4x |
data = data.frame(x = dose_target), |
92 | 4x |
aes(.data$x, 0), |
93 | 4x |
label = "Est", |
94 | 4x |
vjust = -0.5, |
95 | 4x |
hjust = 0.5, |
96 | 4x |
colour = "black", |
97 | 4x |
angle = 90 |
98 |
) + |
|
99 | 4x |
xlab("MTD") + |
100 | 4x |
ylab("Posterior density") |
101 | ||
102 | 4x |
if (is.finite(doselimit)) { |
103 | 2x |
p <- p + |
104 | 2x |
geom_vline(xintercept = doselimit, colour = "red", lwd = 1.1) + |
105 | 2x |
geom_text( |
106 | 2x |
data = data.frame(x = doselimit), |
107 | 2x |
aes(.data$x, 0), |
108 | 2x |
label = "Max", |
109 | 2x |
vjust = -0.5, |
110 | 2x |
hjust = -0.5, |
111 | 2x |
colour = "red", |
112 | 2x |
angle = 90 |
113 |
) |
|
114 |
} |
|
115 | ||
116 | 4x |
p <- p + |
117 | 4x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
118 | 4x |
geom_text( |
119 | 4x |
data = data.frame(x = next_dose), |
120 | 4x |
aes(.data$x, 0), |
121 | 4x |
label = "Next", |
122 | 4x |
vjust = -0.5, |
123 | 4x |
hjust = -1.5, |
124 | 4x |
colour = "blue", |
125 | 4x |
angle = 90 |
126 |
) |
|
127 | ||
128 | 4x |
list(value = next_dose, plot = p) |
129 |
} |
|
130 |
) |
|
131 | ||
132 |
## NextBestNCRM ---- |
|
133 | ||
134 |
#' @describeIn nextBest find the next best dose based on the NCRM method. The |
|
135 |
#' additional element `probs` in the output's list contains the target and |
|
136 |
#' overdosing probabilities (across all doses in the dose grid) used in the |
|
137 |
#' derivation of the next best dose. |
|
138 |
#' |
|
139 |
#' @aliases nextBest-NextBestNCRM |
|
140 |
#' |
|
141 |
#' @export |
|
142 |
#' @example examples/Rules-method-nextBest-NextBestNCRM.R |
|
143 |
#' |
|
144 |
setMethod( |
|
145 |
f = "nextBest", |
|
146 |
signature = signature( |
|
147 |
nextBest = "NextBestNCRM", |
|
148 |
doselimit = "numeric", |
|
149 |
samples = "Samples", |
|
150 |
model = "GeneralModel", |
|
151 |
data = "Data" |
|
152 |
), |
|
153 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) { |
|
154 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
155 | 209x |
prob_samples <- sapply(data@doseGrid, prob, model = model, samples = samples, ...) |
156 | ||
157 |
# Estimates of posterior probabilities that are based on the prob. samples |
|
158 |
# which are within overdose/target interval. |
|
159 | 209x |
prob_overdose <- colMeans(h_in_range(prob_samples, nextBest@overdose, bounds_closed = c(FALSE, TRUE))) |
160 | 209x |
prob_target <- colMeans(h_in_range(prob_samples, nextBest@target)) |
161 | ||
162 |
# Eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
163 | 209x |
is_dose_eligible <- h_next_best_eligible_doses(data@doseGrid, doselimit, data@placebo, levels = TRUE) & |
164 | 209x |
(prob_overdose <= nextBest@max_overdose_prob) |
165 | ||
166 | 209x |
next_dose <- if (any(is_dose_eligible)) { |
167 |
# If maximum target probability is higher than some numerical threshold, |
|
168 |
# then take that level, otherwise stick to the maximum level that is OK. |
|
169 |
# next_best_level is relative to eligible doses. |
|
170 | 194x |
next_best_level <- ifelse( |
171 | 194x |
test = any(prob_target[is_dose_eligible] > 0.05), |
172 | 194x |
yes = which.max(prob_target[is_dose_eligible]), |
173 | 194x |
no = sum(is_dose_eligible) |
174 |
) |
|
175 | 194x |
data@doseGrid[is_dose_eligible][next_best_level] |
176 |
} else { |
|
177 | 209x |
NA_real_ |
178 |
} |
|
179 | ||
180 |
# Build plots, first for the target probability. |
|
181 | 209x |
p1 <- ggplot() + |
182 | 209x |
geom_bar( |
183 | 209x |
data = data.frame(Dose = data@doseGrid, y = prob_target * 100), |
184 | 209x |
aes(x = .data$Dose, y = .data$y), |
185 | 209x |
stat = "identity", |
186 | 209x |
position = "identity", |
187 | 209x |
width = min(diff(data@doseGrid)) / 2, |
188 | 209x |
colour = "darkgreen", |
189 | 209x |
fill = "darkgreen" |
190 |
) + |
|
191 | 209x |
coord_cartesian(ylim = c(0, 100)) + |
192 | 209x |
ylab(paste("Target probability [%]")) |
193 | ||
194 | 209x |
if (is.finite(doselimit)) { |
195 | 206x |
p1 <- p1 + geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
196 |
} |
|
197 | ||
198 | 209x |
if (any(is_dose_eligible)) { |
199 | 194x |
p1 <- p1 + |
200 | 194x |
geom_vline(xintercept = data@doseGrid[sum(is_dose_eligible)], lwd = 1.1, lty = 2, colour = "red") + |
201 | 194x |
geom_point( |
202 | 194x |
data = data.frame(x = next_dose, y = prob_target[is_dose_eligible][next_best_level] * 100 + 0.03), |
203 | 194x |
aes(x = x, y = y), |
204 | 194x |
size = 3, |
205 | 194x |
pch = 25, |
206 | 194x |
col = "red", |
207 | 194x |
bg = "red" |
208 |
) |
|
209 |
} |
|
210 | ||
211 |
# Second, for the overdosing probability. |
|
212 | 209x |
p2 <- ggplot() + |
213 | 209x |
geom_bar( |
214 | 209x |
data = data.frame(Dose = data@doseGrid, y = prob_overdose * 100), |
215 | 209x |
aes(x = .data$Dose, y = .data$y), |
216 | 209x |
stat = "identity", |
217 | 209x |
position = "identity", |
218 | 209x |
width = min(diff(data@doseGrid)) / 2, |
219 | 209x |
colour = "red", |
220 | 209x |
fill = "red" |
221 |
) + |
|
222 | 209x |
geom_hline(yintercept = nextBest@max_overdose_prob * 100, lwd = 1.1, lty = 2, colour = "black") + |
223 | 209x |
ylim(c(0, 100)) + |
224 | 209x |
ylab("Overdose probability [%]") |
225 | ||
226 |
# Place them below each other. |
|
227 | 209x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, nrow = 2) |
228 | ||
229 | 209x |
list( |
230 | 209x |
value = next_dose, |
231 | 209x |
plot = plot_joint, |
232 | 209x |
singlePlots = list(plot1 = p1, plot2 = p2), |
233 | 209x |
probs = cbind( |
234 | 209x |
dose = data@doseGrid, |
235 | 209x |
target = prob_target, |
236 | 209x |
overdose = prob_overdose |
237 |
) |
|
238 |
) |
|
239 |
} |
|
240 |
) |
|
241 | ||
242 |
## NextBestNCRM-DataParts ---- |
|
243 | ||
244 |
#' @describeIn nextBest find the next best dose based on the NCRM method when |
|
245 |
#' two parts trial is used. |
|
246 |
#' |
|
247 |
#' @aliases nextBest-NextBestNCRM-DataParts |
|
248 |
#' |
|
249 |
#' @export |
|
250 |
#' @example examples/Rules-method-nextBest-NextBestNCRM-DataParts.R |
|
251 |
#' |
|
252 |
setMethod( |
|
253 |
f = "nextBest", |
|
254 |
signature = signature( |
|
255 |
nextBest = "NextBestNCRM", |
|
256 |
doselimit = "numeric", |
|
257 |
samples = "Samples", |
|
258 |
model = "GeneralModel", |
|
259 |
data = "DataParts" |
|
260 |
), |
|
261 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) { |
|
262 |
# Exception when we are in part I or about to start part II! |
|
263 | 4x |
if (all(data@part == 1L)) { |
264 |
# Propose the highest possible dose (assuming that the dose limit came |
|
265 |
# from reasonable increments rule, i.e. incrementsRelativeParts). |
|
266 | 2x |
if (is.infinite(doselimit)) { |
267 | 1x |
stop("A finite doselimit needs to be specified for Part I.") |
268 |
} |
|
269 | 1x |
list(value = doselimit, plot = NULL) |
270 |
} else { |
|
271 |
# Otherwise we will just do the standard thing. |
|
272 | 2x |
callNextMethod(nextBest, doselimit, samples, model, data, ...) |
273 |
} |
|
274 |
} |
|
275 |
) |
|
276 | ||
277 |
## NextBestNCRMLoss ---- |
|
278 | ||
279 |
#' @describeIn nextBest find the next best dose based on the NCRM method and |
|
280 |
#' loss function. |
|
281 |
#' |
|
282 |
#' @aliases nextBest-NextBestNCRMLoss |
|
283 |
#' |
|
284 |
#' @export |
|
285 |
#' @example examples/Rules-method-nextBest-NextBestNCRMLoss.R |
|
286 |
#' |
|
287 |
setMethod("nextBest", |
|
288 |
signature = signature( |
|
289 |
nextBest = "NextBestNCRMLoss", |
|
290 |
doselimit = "numeric", |
|
291 |
samples = "Samples", |
|
292 |
model = "GeneralModel", |
|
293 |
data = "Data" |
|
294 |
), |
|
295 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) { |
|
296 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
297 | 5x |
prob_samples <- sapply(data@doseGrid, prob, model = model, samples = samples, ...) |
298 |
# Compute probabilities to be in target and overdose tox interval. |
|
299 | 5x |
prob_underdosing <- colMeans(prob_samples < nextBest@target[1]) |
300 | 5x |
prob_target <- colMeans(h_in_range(prob_samples, nextBest@target)) |
301 | 5x |
prob_overdose <- colMeans(h_in_range(prob_samples, nextBest@overdose, bounds_closed = c(FALSE, TRUE))) |
302 | 5x |
prob_mean <- colMeans(prob_samples) |
303 | 5x |
prob_sd <- apply(prob_samples, 2, stats::sd) |
304 | ||
305 | 5x |
is_unacceptable_specified <- any(nextBest@unacceptable != c(1, 1)) |
306 | ||
307 | 5x |
prob_mat <- if (!is_unacceptable_specified) { |
308 | 2x |
cbind(underdosing = prob_underdosing, target = prob_target, overdose = prob_overdose) |
309 |
} else { |
|
310 | 3x |
prob_unacceptable <- colMeans( |
311 | 3x |
h_in_range(prob_samples, nextBest@unacceptable, bounds_closed = c(FALSE, TRUE)) |
312 |
) |
|
313 | 3x |
prob_excessive <- prob_overdose |
314 | 3x |
prob_overdose <- prob_excessive + prob_unacceptable |
315 | 3x |
cbind( |
316 | 3x |
underdosing = prob_underdosing, |
317 | 3x |
target = prob_target, |
318 | 3x |
excessive = prob_excessive, |
319 | 3x |
unacceptable = prob_unacceptable |
320 |
) |
|
321 |
} |
|
322 | ||
323 | 5x |
posterior_loss <- as.vector(nextBest@losses %*% t(prob_mat)) |
324 | 5x |
names(posterior_loss) <- data@doseGrid |
325 | ||
326 | 5x |
probs <- cbind( |
327 | 5x |
dose = data@doseGrid, |
328 | 5x |
prob_mat = prob_mat, |
329 | 5x |
mean = prob_mean, |
330 | 5x |
std_dev = prob_sd, |
331 | 5x |
posterior_loss = posterior_loss |
332 |
) |
|
333 | ||
334 |
# Eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
335 | 5x |
is_dose_eligible <- h_next_best_eligible_doses(data@doseGrid, doselimit, data@placebo, levels = TRUE) & |
336 | 5x |
(prob_overdose < nextBest@max_overdose_prob) |
337 | ||
338 |
# Next best dose is the dose with the minimum loss function. |
|
339 | 5x |
next_dose <- if (any(is_dose_eligible)) { |
340 | 5x |
next_best_level <- which.min(posterior_loss[is_dose_eligible]) |
341 | 5x |
data@doseGrid[is_dose_eligible][next_best_level] |
342 |
} else { |
|
343 | 5x |
NA_real_ |
344 |
} |
|
345 | ||
346 |
# Build plot. |
|
347 | 5x |
p <- h_next_best_ncrm_loss_plot( |
348 | 5x |
prob_mat = prob_mat, |
349 | 5x |
posterior_loss = posterior_loss, |
350 | 5x |
max_overdose_prob = nextBest@max_overdose_prob, |
351 | 5x |
dose_grid = data@doseGrid, |
352 | 5x |
max_eligible_dose_level = sum(is_dose_eligible), |
353 | 5x |
doselimit = doselimit, |
354 | 5x |
next_dose = next_dose, |
355 | 5x |
is_unacceptable_specified = is_unacceptable_specified |
356 |
) |
|
357 | ||
358 | 5x |
c(list(value = next_dose, probs = probs), p) |
359 |
} |
|
360 |
) |
|
361 | ||
362 |
## NextBestThreePlusThree ---- |
|
363 | ||
364 |
#' @describeIn nextBest find the next best dose based on the 3+3 method. |
|
365 |
#' |
|
366 |
#' @aliases nextBest-NextBestThreePlusThree |
|
367 |
#' |
|
368 |
#' @export |
|
369 |
#' @example examples/Rules-method-nextBest-NextBestThreePlusThree.R |
|
370 |
#' |
|
371 |
setMethod( |
|
372 |
f = "nextBest", |
|
373 |
signature = signature( |
|
374 |
nextBest = "NextBestThreePlusThree", |
|
375 |
doselimit = "missing", |
|
376 |
samples = "missing", |
|
377 |
model = "missing", |
|
378 |
data = "Data" |
|
379 |
), |
|
380 |
definition = function(nextBest, doselimit, samples, model, data, ...) { |
|
381 |
# The last dose level tested (not necessarily the maximum one). |
|
382 | 59x |
last_level <- tail(data@xLevel, 1L) |
383 | ||
384 |
# Get number of patients per grid's dose and DLT rate at the last level. |
|
385 | 59x |
nPatients <- table(factor(data@x, levels = data@doseGrid)) |
386 | 59x |
n_dlts_last_level <- sum(data@y[data@xLevel == last_level]) |
387 | 59x |
dlt_rate_last_level <- n_dlts_last_level / nPatients[last_level] |
388 | ||
389 | 59x |
level_change <- if (dlt_rate_last_level < 1 / 3) { |
390 |
# Escalate it, unless this is the highest level or the higher dose was already tried. |
|
391 | 23x |
ifelse((last_level == data@nGrid) || (nPatients[last_level + 1L] > 0), 0L, 1L) |
392 |
} else { |
|
393 |
# Rate is too high, deescalate it, unless an edge case of 1/3, where the decision |
|
394 |
# depends on the num. of patients: if >3, then deescalate it, otherwise stay. |
|
395 | 36x |
ifelse((dlt_rate_last_level == 1 / 3) && (nPatients[last_level] <= 3L), 0L, -1L) |
396 |
} |
|
397 | 59x |
next_dose_level <- last_level + level_change |
398 | ||
399 |
# Do we stop here? Only if we have no MTD, or the next level has been tried |
|
400 |
# enough (more than three patients already). |
|
401 | 59x |
if (next_dose_level == 0L) { |
402 | 3x |
next_dose <- NA |
403 | 3x |
stop_here <- TRUE |
404 |
} else { |
|
405 | 56x |
next_dose <- data@doseGrid[next_dose_level] |
406 | 56x |
stop_here <- nPatients[next_dose_level] > 3L |
407 |
} |
|
408 | ||
409 | 59x |
list(value = next_dose, stopHere = stop_here) |
410 |
} |
|
411 |
) |
|
412 | ||
413 |
## NextBestDualEndpoint ---- |
|
414 | ||
415 |
#' @describeIn nextBest find the next best dose based on the dual endpoint |
|
416 |
#' model. The additional list element `probs` contains the target and |
|
417 |
#' overdosing probabilities (across all doses in the dose grid) used in the |
|
418 |
#' derivation of the next best dose. |
|
419 |
#' |
|
420 |
#' @aliases nextBest-NextBestDualEndpoint |
|
421 |
#' |
|
422 |
#' @export |
|
423 |
#' @example examples/Rules-method-nextBest-NextBestDualEndpoint.R |
|
424 |
#' |
|
425 |
setMethod( |
|
426 |
f = "nextBest", |
|
427 |
signature = signature( |
|
428 |
nextBest = "NextBestDualEndpoint", |
|
429 |
doselimit = "numeric", |
|
430 |
samples = "Samples", |
|
431 |
model = "DualEndpoint", |
|
432 |
data = "Data" |
|
433 |
), |
|
434 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) { |
|
435 |
# Biomarker samples at the dose grid points. |
|
436 | 8x |
biom_samples <- samples@data$betaW |
437 | ||
438 | 8x |
prob_target <- if (nextBest@target_relative) { |
439 |
# If 'Emax' parameter available, target biomarker level will be relative to 'Emax', |
|
440 |
# otherwise, it will be relative to the maximum biomarker level achieved |
|
441 |
# in dose range for a given sample. |
|
442 | 6x |
if ("Emax" %in% names(samples)) { |
443 | 1x |
lwr <- nextBest@target[1] * samples@data$Emax |
444 | 1x |
upr <- nextBest@target[2] * samples@data$Emax |
445 | 1x |
colMeans(apply(biom_samples, 2L, function(s) (s >= lwr) & (s <= upr))) |
446 |
} else { |
|
447 | 5x |
target_levels <- apply(biom_samples, 1L, function(x) { |
448 | 1204x |
rng <- range(x) |
449 | 1204x |
min(which(h_in_range(x, nextBest@target * diff(rng) + rng[1] + c(0, 1e-10), bounds_closed = c(FALSE, TRUE)))) |
450 |
}) |
|
451 | 5x |
prob_target <- as.vector(table(factor(target_levels, levels = 1:data@nGrid))) |
452 | 5x |
prob_target / nrow(biom_samples) |
453 |
} |
|
454 |
} else { |
|
455 | 2x |
colMeans(h_in_range(biom_samples, nextBest@target)) |
456 |
} |
|
457 | ||
458 |
# Now, compute probabilities to be in overdose tox interval, then flag |
|
459 |
# eligible grid doses after accounting for maximum possible dose and discarding overdoses. |
|
460 | 8x |
prob_samples <- sapply(data@doseGrid, prob, model = model, samples = samples) |
461 | 8x |
prob_overdose <- colMeans(h_in_range(prob_samples, nextBest@overdose, bounds_closed = c(FALSE, TRUE))) |
462 | ||
463 | 8x |
is_dose_eligible <- h_next_best_eligible_doses(data@doseGrid, doselimit, data@placebo, levels = TRUE) & |
464 | 8x |
(prob_overdose < nextBest@max_overdose_prob) |
465 | ||
466 | 8x |
next_dose <- if (any(is_dose_eligible)) { |
467 |
# If maximum target probability is higher the threshold, then take that |
|
468 |
# level, otherwise stick to the maximum level that is eligible. |
|
469 |
# next_dose_level is relative to eligible doses. |
|
470 | 8x |
next_dose_level <- ifelse( |
471 | 8x |
test = any(prob_target[is_dose_eligible] > nextBest@target_thresh), |
472 | 8x |
yes = which.max(prob_target[is_dose_eligible]), |
473 | 8x |
no = sum(is_dose_eligible) |
474 |
) |
|
475 | 8x |
data@doseGrid[is_dose_eligible][next_dose_level] |
476 |
} else { |
|
477 | 8x |
NA_real_ |
478 |
} |
|
479 | ||
480 |
# Build plots, first for the target probability. |
|
481 | 8x |
p1 <- ggplot() + |
482 | 8x |
geom_bar( |
483 | 8x |
data = data.frame(Dose = data@doseGrid, y = prob_target * 100), |
484 | 8x |
aes(x = .data$Dose, y = .data$y), |
485 | 8x |
stat = "identity", |
486 | 8x |
position = "identity", |
487 | 8x |
width = min(diff(data@doseGrid)) / 2, |
488 | 8x |
colour = "darkgreen", |
489 | 8x |
fill = "darkgreen" |
490 |
) + |
|
491 | 8x |
ylim(c(0, 100)) + |
492 | 8x |
ylab(paste("Target probability [%]")) |
493 | ||
494 | 8x |
if (is.finite(doselimit)) { |
495 | 7x |
p1 <- p1 + geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
496 |
} |
|
497 | ||
498 | 8x |
if (any(is_dose_eligible)) { |
499 | 8x |
p1 <- p1 + |
500 | 8x |
geom_vline(xintercept = data@doseGrid[sum(is_dose_eligible)], lwd = 1.1, lty = 2, colour = "red") + |
501 | 8x |
geom_point( |
502 | 8x |
data = data.frame(x = next_dose, y = prob_target[is_dose_eligible][next_dose_level] * 100 + 0.03), |
503 | 8x |
aes(x = x, y = y), |
504 | 8x |
size = 3, |
505 | 8x |
pch = 25, |
506 | 8x |
col = "red", |
507 | 8x |
bg = "red" |
508 |
) |
|
509 |
} |
|
510 | ||
511 |
# Second, for the overdosing probability. |
|
512 | 8x |
p2 <- ggplot() + |
513 | 8x |
geom_bar( |
514 | 8x |
data = data.frame(Dose = data@doseGrid, y = prob_overdose * 100), |
515 | 8x |
aes(x = .data$Dose, y = .data$y), |
516 | 8x |
stat = "identity", |
517 | 8x |
position = "identity", |
518 | 8x |
width = min(diff(data@doseGrid)) / 2, |
519 | 8x |
colour = "red", |
520 | 8x |
fill = "red" |
521 |
) + |
|
522 | 8x |
geom_hline(yintercept = nextBest@max_overdose_prob * 100, lwd = 1.1, lty = 2, colour = "black") + |
523 | 8x |
ylim(c(0, 100)) + |
524 | 8x |
ylab("Overdose probability [%]") |
525 | ||
526 |
# Place them below each other. |
|
527 | 8x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, nrow = 2) |
528 | ||
529 | 8x |
list( |
530 | 8x |
value = next_dose, |
531 | 8x |
plot = plot_joint, |
532 | 8x |
singlePlots = list(plot1 = p1, plot2 = p2), |
533 | 8x |
probs = cbind(dose = data@doseGrid, target = prob_target, overdose = prob_overdose) |
534 |
) |
|
535 |
} |
|
536 |
) |
|
537 | ||
538 |
## NextBestMinDist ---- |
|
539 | ||
540 |
#' @describeIn nextBest gives the dose which is below the dose limit and has an |
|
541 |
#' estimated DLT probability which is closest to the target dose. |
|
542 |
#' |
|
543 |
#' @aliases nextBest-NextBestMinDist |
|
544 |
#' |
|
545 |
#' @export |
|
546 |
#' |
|
547 |
setMethod( |
|
548 |
f = "nextBest", |
|
549 |
signature = signature( |
|
550 |
nextBest = "NextBestMinDist", |
|
551 |
doselimit = "numeric", |
|
552 |
samples = "Samples", |
|
553 |
model = "GeneralModel", |
|
554 |
data = "Data" |
|
555 |
), |
|
556 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) { |
|
557 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
558 | 3x |
prob_samples <- sapply(data@doseGrid, prob, model = model, samples = samples, ...) |
559 | 3x |
dlt_prob <- colMeans(prob_samples) |
560 | ||
561 |
# Determine the dose with the closest distance. |
|
562 | 3x |
dose_target <- data@doseGrid[which.min(abs(dlt_prob - nextBest@target))] |
563 | ||
564 |
# Determine next dose. |
|
565 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
566 | 3x |
data@doseGrid, |
567 | 3x |
doselimit, |
568 | 3x |
data@placebo |
569 |
) |
|
570 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
571 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
572 | ||
573 |
# Create a plot. |
|
574 | 3x |
p <- ggplot( |
575 | 3x |
data = data.frame(x = data@doseGrid, y = dlt_prob), |
576 | 3x |
aes(.data$x, .data$y), |
577 | 3x |
fill = "grey50", |
578 | 3x |
colour = "grey50" |
579 |
) + |
|
580 | 3x |
geom_line() + |
581 | 3x |
geom_point() + |
582 | 3x |
coord_cartesian(xlim = range(data@doseGrid)) + |
583 | 3x |
scale_x_continuous( |
584 | 3x |
labels = as.character(data@doseGrid), |
585 | 3x |
breaks = data@doseGrid, |
586 | 3x |
guide = guide_axis(check.overlap = TRUE) |
587 |
) + |
|
588 | 3x |
geom_hline(yintercept = nextBest@target, linetype = "dotted") + |
589 | 3x |
geom_vline(xintercept = dose_target, colour = "black", lwd = 1.1) + |
590 | 3x |
geom_text( |
591 | 3x |
data = data.frame(x = dose_target), |
592 | 3x |
aes(.data$x, 0), |
593 | 3x |
label = "Est", |
594 | 3x |
vjust = -0.5, |
595 | 3x |
hjust = 0.5, |
596 | 3x |
colour = "black", |
597 | 3x |
angle = 90 |
598 |
) + |
|
599 | 3x |
xlab("Dose") + |
600 | 3x |
ylab("Posterior toxicity probability") |
601 | ||
602 | 3x |
if (is.finite(doselimit)) { |
603 | 2x |
p <- p + |
604 | 2x |
geom_vline(xintercept = doselimit, colour = "red", lwd = 1.1) + |
605 | 2x |
geom_text( |
606 | 2x |
data = data.frame(x = doselimit), |
607 | 2x |
aes(.data$x, 0), |
608 | 2x |
label = "Max", |
609 | 2x |
vjust = -0.5, |
610 | 2x |
hjust = -0.5, |
611 | 2x |
colour = "red", |
612 | 2x |
angle = 90 |
613 |
) |
|
614 |
} |
|
615 | ||
616 | 3x |
p <- p + |
617 | 3x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
618 | 3x |
geom_text( |
619 | 3x |
data = data.frame(x = next_dose), |
620 | 3x |
aes(.data$x, 0), |
621 | 3x |
label = "Next", |
622 | 3x |
vjust = -0.5, |
623 | 3x |
hjust = -1.5, |
624 | 3x |
colour = "blue", |
625 | 3x |
angle = 90 |
626 |
) |
|
627 | ||
628 | 3x |
list( |
629 | 3x |
value = next_dose, |
630 | 3x |
probs = cbind(dose = data@doseGrid, dlt_prob = dlt_prob), |
631 | 3x |
plot = p |
632 |
) |
|
633 |
} |
|
634 |
) |
|
635 | ||
636 |
## NextBestInfTheory ---- |
|
637 | ||
638 |
#' @describeIn nextBest gives the appropriate dose within an information |
|
639 |
#' theoretic framework. |
|
640 |
#' |
|
641 |
#' @aliases nextBest-NextBestInfTheory |
|
642 |
#' |
|
643 |
#' @export |
|
644 |
#' |
|
645 |
setMethod( |
|
646 |
f = "nextBest", |
|
647 |
signature = signature( |
|
648 |
nextBest = "NextBestInfTheory", |
|
649 |
doselimit = "numeric", |
|
650 |
samples = "Samples", |
|
651 |
model = "GeneralModel", |
|
652 |
data = "Data" |
|
653 |
), |
|
654 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) { |
|
655 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
656 | 57x |
prob_samples <- sapply(data@doseGrid, prob, model = model, samples = samples, ...) |
657 | ||
658 | 57x |
criterion <- colMeans(h_info_theory_dist(prob_samples, nextBest@target, nextBest@asymmetry)) |
659 | ||
660 | 57x |
is_dose_eligible <- h_next_best_eligible_doses(data@doseGrid, doselimit, data@placebo, levels = TRUE) |
661 | 57x |
doses_eligible <- data@doseGrid[is_dose_eligible] |
662 | 57x |
next_best_level <- which.min(criterion[is_dose_eligible]) |
663 | 57x |
next_best <- doses_eligible[next_best_level] |
664 | 57x |
list(value = next_best) |
665 |
} |
|
666 |
) |
|
667 | ||
668 |
## NextBestTD ---- |
|
669 | ||
670 |
#' @describeIn nextBest find the next best dose based only on the DLT responses |
|
671 |
#' and for [`LogisticIndepBeta`] model class object without DLT samples. |
|
672 |
#' |
|
673 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
674 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
675 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
676 |
#' are outside of the dose grid range, the information message is printed by |
|
677 |
#' this method. |
|
678 |
#' |
|
679 |
#' @aliases nextBest-NextBestTD |
|
680 |
#' |
|
681 |
#' @export |
|
682 |
#' @example examples/Rules-method-nextBest-NextBestTD.R |
|
683 |
#' |
|
684 |
setMethod( |
|
685 |
f = "nextBest", |
|
686 |
signature = signature( |
|
687 |
nextBest = "NextBestTD", |
|
688 |
doselimit = "numeric", |
|
689 |
samples = "missing", |
|
690 |
model = "LogisticIndepBeta", |
|
691 |
data = "Data" |
|
692 |
), |
|
693 |
definition = function(nextBest, doselimit = Inf, model, data, in_sim = FALSE, ...) { |
|
694 | 16x |
assert_flag(in_sim) |
695 | ||
696 |
# 'drt' - during the trial, 'eot' end of trial. |
|
697 | 16x |
prob_target_drt <- nextBest@prob_target_drt |
698 | 16x |
prob_target_eot <- nextBest@prob_target_eot |
699 | ||
700 |
# Target dose estimates, i.e. the dose with probability of the occurrence of |
|
701 |
# a DLT that equals to the prob_target_drt or prob_target_eot. |
|
702 | 16x |
dose_target_drt <- dose(x = prob_target_drt, model, ...) |
703 | 16x |
dose_target_eot <- dose(x = prob_target_eot, model, ...) |
704 | ||
705 |
# Find the next best doses in the doseGrid. The next best dose is the dose |
|
706 |
# at level closest and below the target dose estimate. |
|
707 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
708 | 16x |
doses_eligible <- h_next_best_eligible_doses(data@doseGrid, doselimit, data@placebo) |
709 | ||
710 | 16x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
711 | 16x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
712 | ||
713 | 16x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
714 | 16x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
715 | ||
716 |
# Find the variance of the log of the dose_target_eot. |
|
717 | 16x |
mat <- matrix( |
718 | 16x |
c( |
719 | 16x |
-1 / (model@phi2), |
720 | 16x |
-(log(prob_target_eot / (1 - prob_target_eot)) - model@phi1) / (model@phi2)^2 |
721 |
), |
|
722 | 16x |
nrow = 1 |
723 |
) |
|
724 | 16x |
var_dose_target_eot <- as.vector(mat %*% model@Pcov %*% t(mat)) |
725 | ||
726 |
# 95% credibility interval. |
|
727 | 16x |
ci_dose_target_eot <- exp(log(dose_target_eot) + c(-1, 1) * 1.96 * sqrt(var_dose_target_eot)) |
728 | 16x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
729 | ||
730 |
# Build plot. |
|
731 | 16x |
p <- h_next_best_td_plot( |
732 | 16x |
prob_target_drt = prob_target_drt, |
733 | 16x |
dose_target_drt = dose_target_drt, |
734 | 16x |
prob_target_eot = prob_target_eot, |
735 | 16x |
dose_target_eot = dose_target_eot, |
736 | 16x |
data = data, |
737 | 16x |
prob_dlt = prob(dose = data@doseGrid, model = model, ...), |
738 | 16x |
doselimit = doselimit, |
739 | 16x |
next_dose = next_dose_drt |
740 |
) |
|
741 | ||
742 | 16x |
if (!h_in_range(dose_target_drt, range = dose_grid_range(data), bounds_closed = TRUE) && !in_sim) { |
743 | 1x |
warning(paste("TD", prob_target_drt * 100, "=", dose_target_drt, "not within dose grid")) |
744 |
} |
|
745 | 16x |
if (!h_in_range(dose_target_eot, range = dose_grid_range(data), bounds_closed = TRUE) && !in_sim) { |
746 | 1x |
warning(paste("TD", prob_target_eot * 100, "=", dose_target_eot, "not within dose grid")) |
747 |
} |
|
748 | ||
749 | 16x |
list( |
750 | 16x |
next_dose_drt = next_dose_drt, |
751 | 16x |
prob_target_drt = prob_target_drt, |
752 | 16x |
dose_target_drt = dose_target_drt, |
753 | 16x |
next_dose_eot = next_dose_eot, |
754 | 16x |
prob_target_eot = prob_target_eot, |
755 | 16x |
dose_target_eot = dose_target_eot, |
756 | 16x |
ci_dose_target_eot = ci_dose_target_eot, |
757 | 16x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
758 | 16x |
plot = p |
759 |
) |
|
760 |
} |
|
761 |
) |
|
762 | ||
763 |
## NextBestTDsamples ---- |
|
764 | ||
765 |
#' @describeIn nextBest find the next best dose based only on the DLT responses |
|
766 |
#' and for [`LogisticIndepBeta`] model class object involving DLT samples. |
|
767 |
#' |
|
768 |
#' @aliases nextBest-NextBestTDsamples |
|
769 |
#' |
|
770 |
#' @export |
|
771 |
#' @example examples/Rules-method-nextBest-NextBestTDsamples.R |
|
772 |
#' |
|
773 |
setMethod( |
|
774 |
f = "nextBest", |
|
775 |
signature = signature( |
|
776 |
nextBest = "NextBestTDsamples", |
|
777 |
doselimit = "numeric", |
|
778 |
samples = "Samples", |
|
779 |
model = "LogisticIndepBeta", |
|
780 |
data = "Data" |
|
781 |
), |
|
782 |
definition = function(nextBest, doselimit = Inf, samples, model, data, in_sim, ...) { |
|
783 |
# Generate target dose samples, i.e. the doses with probability of the |
|
784 |
# occurrence of a DLT that equals to the nextBest@prob_target_drt |
|
785 |
# (or nextBest@prob_target_eot, respectively). |
|
786 | 15x |
dose_target_drt_samples <- dose(x = nextBest@prob_target_drt, model, samples, ...) |
787 | 15x |
dose_target_eot_samples <- dose(x = nextBest@prob_target_eot, model, samples, ...) |
788 | ||
789 |
# Derive the prior/posterior estimates based on two above samples. |
|
790 | 15x |
dose_target_drt <- nextBest@derive(dose_target_drt_samples) |
791 | 15x |
dose_target_eot <- nextBest@derive(dose_target_eot_samples) |
792 | ||
793 |
# Find the next doses in the doseGrid. The next dose is the dose at level |
|
794 |
# closest and below the dose_target_drt (or dose_target_eot, respectively). |
|
795 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
796 | 15x |
doses_eligible <- h_next_best_eligible_doses(data@doseGrid, doselimit, data@placebo) |
797 | ||
798 | 15x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
799 | 15x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
800 | ||
801 | 15x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
802 | 15x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
803 | ||
804 |
# 95% credibility interval. |
|
805 | 15x |
ci_dose_target_eot <- as.numeric(quantile(dose_target_eot_samples, probs = c(0.025, 0.975))) |
806 | 15x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
807 | ||
808 |
# Build plot. |
|
809 | 15x |
p <- h_next_best_tdsamples_plot( |
810 | 15x |
dose_target_drt_samples = dose_target_drt_samples, |
811 | 15x |
dose_target_eot_samples = dose_target_eot_samples, |
812 | 15x |
dose_target_drt = dose_target_drt, |
813 | 15x |
dose_target_eot = dose_target_eot, |
814 | 15x |
dose_grid_range = range(data@doseGrid), |
815 | 15x |
nextBest = nextBest, |
816 | 15x |
doselimit = doselimit, |
817 | 15x |
next_dose = next_dose_drt |
818 |
) |
|
819 | ||
820 | 15x |
list( |
821 | 15x |
next_dose_drt = next_dose_drt, |
822 | 15x |
prob_target_drt = nextBest@prob_target_drt, |
823 | 15x |
dose_target_drt = dose_target_drt, |
824 | 15x |
next_dose_eot = next_dose_eot, |
825 | 15x |
prob_target_eot = nextBest@prob_target_eot, |
826 | 15x |
dose_target_eot = dose_target_eot, |
827 | 15x |
ci_dose_target_eot = ci_dose_target_eot, |
828 | 15x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
829 | 15x |
plot = p |
830 |
) |
|
831 |
} |
|
832 |
) |
|
833 | ||
834 |
## NextBestMaxGain ---- |
|
835 | ||
836 |
#' @describeIn nextBest find the next best dose based only on pseudo DLT model |
|
837 |
#' [`ModelTox`] and [`Effloglog`] efficacy model without samples. |
|
838 |
#' |
|
839 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
840 |
#' @param model_eff (`Effloglog`)\cr the efficacy model. |
|
841 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
842 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
843 |
#' are outside of the dose grid range, the information message is printed by |
|
844 |
#' this method. |
|
845 |
#' |
|
846 |
#' @aliases nextBest-NextBestMaxGain |
|
847 |
#' |
|
848 |
#' @export |
|
849 |
#' @example examples/Rules-method-nextBest-NextBestMaxGain.R |
|
850 |
#' |
|
851 |
setMethod( |
|
852 |
f = "nextBest", |
|
853 |
signature = signature( |
|
854 |
nextBest = "NextBestMaxGain", |
|
855 |
doselimit = "numeric", |
|
856 |
samples = "missing", |
|
857 |
model = "ModelTox", |
|
858 |
data = "DataDual" |
|
859 |
), |
|
860 |
definition = function(nextBest, doselimit = Inf, model, data, model_eff, in_sim = FALSE, ...) { |
|
861 | 15x |
assert_class(model_eff, "Effloglog") |
862 | 15x |
assert_flag(in_sim) |
863 | ||
864 |
# 'drt' - during the trial, 'eot' end of trial. |
|
865 | 15x |
prob_target_drt <- nextBest@prob_target_drt |
866 | 15x |
prob_target_eot <- nextBest@prob_target_eot |
867 | ||
868 |
# Target dose estimates, i.e. the dose with probability of the occurrence of |
|
869 |
# a DLT that equals to the prob_target_drt or prob_target_eot. |
|
870 | 15x |
dose_target_drt <- dose(x = prob_target_drt, model, ...) |
871 | 15x |
dose_target_eot <- dose(x = prob_target_eot, model, ...) |
872 | ||
873 |
# Find the dose which gives the maximum gain. |
|
874 | 15x |
dosegrid_range <- dose_grid_range(data) |
875 | 15x |
opt <- optim( |
876 | 15x |
par = dosegrid_range[1], |
877 | 15x |
fn = function(DOSE) { |
878 | 555x |
-gain(DOSE, model_dle = model, model_eff = model_eff, ...) |
879 |
}, |
|
880 | 15x |
method = "L-BFGS-B", |
881 | 15x |
lower = dosegrid_range[1], |
882 | 15x |
upper = dosegrid_range[2] |
883 |
) |
|
884 | 15x |
dose_mg <- opt$par # this is G*. # no lintr |
885 | 15x |
max_gain <- -opt$value |
886 | ||
887 |
# Print info message if dose target is outside of the range. |
|
888 | 15x |
if (!h_in_range(dose_target_drt, range = dose_grid_range(data), bounds_closed = FALSE) && !in_sim) { |
889 | ! |
print(paste("Estimated TD", prob_target_drt * 100, "=", dose_target_drt, "not within dose grid")) |
890 |
} |
|
891 | 15x |
if (!h_in_range(dose_target_eot, range = dose_grid_range(data), bounds_closed = FALSE) && !in_sim) { |
892 | ! |
print(paste("Estimated TD", prob_target_eot * 100, "=", dose_target_eot, "not within dose grid")) |
893 |
} |
|
894 | 15x |
if (!h_in_range(dose_mg, range = dose_grid_range(data), bounds_closed = FALSE) && !in_sim) { |
895 | ! |
print(paste("Estimated max gain dose =", dose_mg, "not within dose grid")) |
896 |
} |
|
897 | ||
898 |
# Get closest grid doses for a given target doses. |
|
899 | 15x |
nb_doses_at_grid <- h_next_best_mg_doses_at_grid( |
900 | 15x |
dose_target_drt = dose_target_drt, |
901 | 15x |
dose_target_eot = dose_target_eot, |
902 | 15x |
dose_mg = dose_mg, |
903 | 15x |
dose_grid = data@doseGrid, |
904 | 15x |
doselimit = doselimit, |
905 | 15x |
placebo = data@placebo |
906 |
) |
|
907 | ||
908 |
# 95% credibility intervals and corresponding ratios for maximum gain dose and target dose eot. |
|
909 | 15x |
ci <- h_next_best_mg_ci( |
910 | 15x |
dose_target = dose_target_eot, |
911 | 15x |
dose_mg = dose_mg, |
912 | 15x |
prob_target = prob_target_eot, |
913 | 15x |
placebo = data@placebo, |
914 | 15x |
model = model, |
915 | 15x |
model_eff = model_eff |
916 |
) |
|
917 | ||
918 |
# Build plot. |
|
919 | 15x |
p <- h_next_best_mg_plot( |
920 | 15x |
prob_target_drt = prob_target_drt, |
921 | 15x |
dose_target_drt = dose_target_drt, |
922 | 15x |
prob_target_eot = prob_target_eot, |
923 | 15x |
dose_target_eot = dose_target_eot, |
924 | 15x |
dose_mg = dose_mg, |
925 | 15x |
max_gain = max_gain, |
926 | 15x |
next_dose = nb_doses_at_grid$next_dose, |
927 | 15x |
doselimit = doselimit, |
928 | 15x |
data = data, |
929 | 15x |
model = model, |
930 | 15x |
model_eff = model_eff |
931 |
) |
|
932 | ||
933 | 15x |
list( |
934 | 15x |
next_dose = nb_doses_at_grid$next_dose, |
935 | 15x |
prob_target_drt = prob_target_drt, |
936 | 15x |
dose_target_drt = dose_target_drt, |
937 | 15x |
next_dose_drt = nb_doses_at_grid$next_dose_drt, |
938 | 15x |
prob_target_eot = prob_target_eot, |
939 | 15x |
dose_target_eot = dose_target_eot, |
940 | 15x |
next_dose_eot = nb_doses_at_grid$next_dose_eot, |
941 | 15x |
dose_max_gain = dose_mg, |
942 | 15x |
next_dose_max_gain = nb_doses_at_grid$next_dose_mg, |
943 | 15x |
ci_dose_target_eot = ci$ci_dose_target, |
944 | 15x |
ci_ratio_dose_target_eot = ci$ci_ratio_dose_target, |
945 | 15x |
ci_dose_max_gain = ci$ci_dose_mg, |
946 | 15x |
ci_ratio_dose_max_gain = ci$ci_ratio_dose_mg, |
947 | 15x |
plot = p |
948 |
) |
|
949 |
} |
|
950 |
) |
|
951 | ||
952 |
## NextBestMaxGainSamples ---- |
|
953 | ||
954 |
#' @describeIn nextBest find the next best dose based on DLT and efficacy |
|
955 |
#' responses with DLT and efficacy samples. |
|
956 |
#' |
|
957 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
958 |
#' @param model_eff (`Effloglog` or `EffFlexi`)\cr the efficacy model. |
|
959 |
#' @param samples_eff (`Samples`)\cr posterior samples from `model_eff` parameters |
|
960 |
#' given `data`. |
|
961 |
#' @param in_sim (`flag`)\cr is this method used in simulations? Default as `FALSE`. |
|
962 |
#' If this flag is `TRUE` and target dose estimates (during trial and end-of-trial) |
|
963 |
#' are outside of the dose grid range, the information message is printed by |
|
964 |
#' this method. |
|
965 |
#' |
|
966 |
#' @aliases nextBest-NextBestMaxGainSamples |
|
967 |
#' |
|
968 |
#' @export |
|
969 |
#' @example examples/Rules-method-nextBest-NextBestMaxGainSamples.R |
|
970 |
#' |
|
971 |
setMethod( |
|
972 |
f = "nextBest", |
|
973 |
signature = signature( |
|
974 |
nextBest = "NextBestMaxGainSamples", |
|
975 |
doselimit = "numeric", |
|
976 |
samples = "Samples", |
|
977 |
model = "ModelTox", |
|
978 |
data = "DataDual" |
|
979 |
), |
|
980 |
definition = function(nextBest, doselimit = Inf, samples, model, data, model_eff, samples_eff, in_sim = FALSE, ...) { |
|
981 | 7x |
assert_true(test_class(model_eff, "Effloglog") || test_class(model_eff, "EffFlexi")) |
982 | 7x |
assert_class(samples_eff, "Samples") |
983 | 7x |
assert_flag(in_sim) |
984 | ||
985 |
# 'drt' - during the trial, 'eot' end of trial. |
|
986 | 7x |
prob_target_drt <- nextBest@prob_target_drt |
987 | 7x |
prob_target_eot <- nextBest@prob_target_eot |
988 | ||
989 |
# Generate target dose samples, i.e. the doses with probability of the |
|
990 |
# occurrence of a DLT that equals to the prob_target_drt or prob_target_eot. |
|
991 | 7x |
dose_target_drt_samples <- dose(x = prob_target_drt, model, samples = samples, ...) |
992 | 7x |
dose_target_eot_samples <- dose(x = prob_target_eot, model, samples = samples, ...) |
993 | ||
994 |
# Derive the prior/posterior estimates based on two above samples. |
|
995 | 7x |
dose_target_drt <- nextBest@derive(dose_target_drt_samples) |
996 | 7x |
dose_target_eot <- nextBest@derive(dose_target_eot_samples) |
997 | ||
998 |
# Gain samples. |
|
999 | 7x |
gain_samples <- sapply(data@doseGrid, gain, model, samples, model_eff, samples_eff, ...) |
1000 |
# For every sample, get the dose (from the dose grid) that gives the maximum gain value. |
|
1001 | 7x |
dose_lev_mg_samples <- apply(gain_samples, 1, which.max) |
1002 | 7x |
dose_mg_samples <- data@doseGrid[dose_lev_mg_samples] |
1003 |
# Maximum gain dose estimate is the nth percentile of the maximum gain dose samples. |
|
1004 | 7x |
dose_mg <- nextBest@mg_derive(dose_mg_samples) |
1005 | 7x |
gain_values <- apply(gain_samples, 2, FUN = nextBest@mg_derive) |
1006 | ||
1007 |
# Print info message if dose target is outside of the range. |
|
1008 | 7x |
dosegrid_range <- dose_grid_range(data) |
1009 | 7x |
if (!h_in_range(dose_target_drt, range = dosegrid_range, bounds_closed = FALSE) && !in_sim) { |
1010 | ! |
print(paste("Estimated TD", prob_target_drt * 100, "=", dose_target_drt, "not within dose grid")) |
1011 |
} |
|
1012 | 7x |
if (!h_in_range(dose_target_eot, range = dosegrid_range, bounds_closed = FALSE) && !in_sim) { |
1013 | ! |
print(paste("Estimated TD", prob_target_eot * 100, "=", dose_target_eot, "not within dose grid")) |
1014 |
} |
|
1015 | 7x |
if (!h_in_range(dose_mg, range = dosegrid_range, bounds_closed = FALSE) && !in_sim) { |
1016 | ! |
print(paste("Estimated max gain dose =", dose_mg, "not within dose grid")) |
1017 |
} |
|
1018 | ||
1019 |
# Get closest grid doses for a given target doses. |
|
1020 | 7x |
nb_doses_at_grid <- h_next_best_mg_doses_at_grid( |
1021 | 7x |
dose_target_drt = dose_target_drt, |
1022 | 7x |
dose_target_eot = dose_target_eot, |
1023 | 7x |
dose_mg = dose_mg, |
1024 | 7x |
dose_grid = data@doseGrid, |
1025 | 7x |
doselimit = doselimit, |
1026 | 7x |
placebo = data@placebo |
1027 |
) |
|
1028 | ||
1029 |
# 95% credibility intervals and corresponding ratios for maximum gain dose and target dose eot. |
|
1030 | 7x |
ci_dose_mg <- as.numeric(quantile(dose_mg_samples, probs = c(0.025, 0.975))) |
1031 | 7x |
cir_dose_mg <- ci_dose_mg[2] / ci_dose_mg[1] |
1032 | ||
1033 | 7x |
ci_dose_target_eot <- as.numeric(quantile(dose_target_eot, probs = c(0.025, 0.975))) |
1034 | 7x |
cir_dose_target_eot <- ci_dose_target_eot[2] / ci_dose_target_eot[1] |
1035 | ||
1036 |
# Build plot. |
|
1037 | 7x |
p <- h_next_best_mgsamples_plot( |
1038 | 7x |
prob_target_drt = prob_target_drt, |
1039 | 7x |
dose_target_drt = dose_target_drt, |
1040 | 7x |
prob_target_eot = prob_target_eot, |
1041 | 7x |
dose_target_eot = dose_target_eot, |
1042 | 7x |
dose_mg = dose_mg, |
1043 | 7x |
dose_mg_samples = dose_mg_samples, |
1044 | 7x |
next_dose = nb_doses_at_grid$next_dose, |
1045 | 7x |
doselimit = doselimit, |
1046 | 7x |
dose_grid_range = dosegrid_range |
1047 |
) |
|
1048 | ||
1049 | 7x |
list( |
1050 | 7x |
next_dose = nb_doses_at_grid$next_dose, |
1051 | 7x |
prob_target_drt = prob_target_drt, |
1052 | 7x |
dose_target_drt = dose_target_drt, |
1053 | 7x |
next_dose_drt = nb_doses_at_grid$next_dose_drt, |
1054 | 7x |
prob_target_eot = prob_target_eot, |
1055 | 7x |
dose_target_eot = dose_target_eot, |
1056 | 7x |
next_dose_eot = nb_doses_at_grid$next_dose_eot, |
1057 | 7x |
dose_max_gain = dose_mg, |
1058 | 7x |
next_dose_max_gain = nb_doses_at_grid$next_dose_mg, |
1059 | 7x |
ci_dose_target_eot = ci_dose_target_eot, |
1060 | 7x |
ci_ratio_dose_target_eot = cir_dose_target_eot, |
1061 | 7x |
ci_dose_max_gain = ci_dose_mg, |
1062 | 7x |
ci_ratio_dose_max_gain = cir_dose_mg, |
1063 | 7x |
plot = p |
1064 |
) |
|
1065 |
} |
|
1066 |
) |
|
1067 | ||
1068 |
## NextBestProbMTDLTE ---- |
|
1069 | ||
1070 |
#' @describeIn nextBest find the next best dose based with the highest |
|
1071 |
#' probability of having a toxicity rate less or equal to the target toxicity |
|
1072 |
#' level. |
|
1073 |
#' |
|
1074 |
#' @aliases nextBest-NextBestProbMTDLTE |
|
1075 |
#' |
|
1076 |
#' @export |
|
1077 |
#' @example examples/Rules-method-nextBest-NextBestProbMTDLTE.R |
|
1078 |
#' |
|
1079 |
setMethod( |
|
1080 |
f = "nextBest", |
|
1081 |
signature = signature( |
|
1082 |
nextBest = "NextBestProbMTDLTE", |
|
1083 |
doselimit = "numeric", |
|
1084 |
samples = "Samples", |
|
1085 |
model = "GeneralModel", |
|
1086 |
data = "Data" |
|
1087 |
), |
|
1088 |
definition = function(nextBest, doselimit, samples, model, data, ...) { |
|
1089 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
1090 | 3x |
prob_samples <- sapply(data@doseGrid, prob, model = model, samples = samples, ...) |
1091 | ||
1092 |
# Determine the maximum dose level with a toxicity probability below or |
|
1093 |
# equal to the target and calculate how often a dose is selected as MTD |
|
1094 |
# across iterations. |
|
1095 |
# The first element of the vector is the relative frequency that no |
|
1096 |
# dose in the grid is below or equal to the target, the |
|
1097 |
# second element that the 1st dose of the grid is the MTD, etc.. |
|
1098 | 3x |
prob_mtd_lte <- prop.table( |
1099 | 3x |
table(factor( |
1100 | 3x |
rowSums(prob_samples <= nextBest@target), |
1101 | 3x |
levels = 0:data@nGrid |
1102 |
)) |
|
1103 |
) |
|
1104 | ||
1105 | 3x |
allocation_crit <- as.vector(prob_mtd_lte) |
1106 | 3x |
names(allocation_crit) <- as.character(c(0, data@doseGrid)) |
1107 | ||
1108 |
# In case that placebo is used, placebo and the portion that is not assigned |
|
1109 |
# to any dose of the grid are merged. |
|
1110 | 3x |
if (data@placebo) { |
1111 | 1x |
allocation_crit[1] <- sum(allocation_crit[1:2]) |
1112 | 1x |
allocation_crit <- allocation_crit[-2] |
1113 |
} |
|
1114 | ||
1115 |
# Handling of the portion that is not assigned to an active dose of |
|
1116 |
# the dose grid. The portion is added to the minimum active dose |
|
1117 |
# of the dose grid. |
|
1118 | 3x |
allocation_crit[2] <- sum(allocation_crit[1:2]) |
1119 | 3x |
allocation_crit <- allocation_crit[-1] |
1120 | ||
1121 |
# Determine the dose with the highest relative frequency. |
|
1122 | 3x |
allocation_crit_dose <- as.numeric(names(allocation_crit)) |
1123 | 3x |
dose_target <- allocation_crit_dose[which.max(allocation_crit)] |
1124 | ||
1125 |
# Determine next dose. |
|
1126 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
1127 | 3x |
data@doseGrid, |
1128 | 3x |
doselimit, |
1129 | 3x |
data@placebo |
1130 |
) |
|
1131 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
1132 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
1133 | ||
1134 |
# Create a plot. |
|
1135 | 3x |
plt_data <- if (data@placebo && (data@doseGrid[1] == next_dose)) { |
1136 | ! |
data.frame( |
1137 | ! |
x = as.factor(data@doseGrid), |
1138 | ! |
y = c(0, as.numeric(allocation_crit)) * 100 |
1139 |
) |
|
1140 |
} else { |
|
1141 | 3x |
data.frame( |
1142 | 3x |
x = as.factor(allocation_crit_dose), |
1143 | 3x |
y = as.numeric(allocation_crit) * 100 |
1144 |
) |
|
1145 |
} |
|
1146 | ||
1147 | 3x |
p <- ggplot( |
1148 | 3x |
data = plt_data, |
1149 | 3x |
fill = "grey50", |
1150 | 3x |
colour = "grey50" |
1151 |
) + |
|
1152 | 3x |
geom_col(aes(x, y), fill = "grey75") + |
1153 | 3x |
scale_x_discrete(drop = FALSE, guide = guide_axis(check.overlap = TRUE)) + |
1154 | 3x |
geom_vline( |
1155 | 3x |
xintercept = as.factor(dose_target), |
1156 | 3x |
lwd = 1.1, |
1157 | 3x |
colour = "black" |
1158 |
) + |
|
1159 | 3x |
geom_text( |
1160 | 3x |
data = data.frame(x = as.factor(dose_target)), |
1161 | 3x |
aes(.data$x, 0), |
1162 | 3x |
label = "Est", |
1163 | 3x |
vjust = -0.5, |
1164 | 3x |
hjust = -0.5, |
1165 | 3x |
colour = "black", |
1166 | 3x |
angle = 90 |
1167 |
) + |
|
1168 | 3x |
xlab("Dose") + |
1169 | 3x |
ylab(paste("Allocation criterion [%]")) |
1170 | ||
1171 | 3x |
if (is.finite(doselimit)) { |
1172 | 2x |
doselimit_level <- if (sum(allocation_crit_dose == doselimit) > 0) { |
1173 | ! |
which(allocation_crit_dose == doselimit) |
1174 |
} else { |
|
1175 | 2x |
ifelse(test = data@placebo && (data@doseGrid[1] == next_dose), |
1176 | 2x |
yes = 1.5, |
1177 | 2x |
no = sum(allocation_crit_dose < doselimit) + 0.5 |
1178 |
) |
|
1179 |
} |
|
1180 | ||
1181 | 2x |
p <- p + |
1182 | 2x |
geom_vline( |
1183 | 2x |
xintercept = doselimit_level, |
1184 | 2x |
colour = "red", lwd = 1.1 |
1185 |
) + |
|
1186 | 2x |
geom_text( |
1187 | 2x |
data = data.frame(x = doselimit_level), |
1188 | 2x |
aes(.data$x, 0), |
1189 | 2x |
label = "Max", |
1190 | 2x |
vjust = -0.5, |
1191 | 2x |
hjust = -1.5, |
1192 | 2x |
colour = "red", |
1193 | 2x |
angle = 90 |
1194 |
) |
|
1195 |
} |
|
1196 | ||
1197 | 3x |
p <- p + |
1198 | 3x |
geom_vline( |
1199 | 3x |
xintercept = as.factor(next_dose), |
1200 | 3x |
colour = "blue", lwd = 1.1 |
1201 |
) + |
|
1202 | 3x |
geom_text( |
1203 | 3x |
data = data.frame(x = as.factor(next_dose)), |
1204 | 3x |
aes(.data$x, 0), |
1205 | 3x |
label = "Next", |
1206 | 3x |
vjust = -0.5, |
1207 | 3x |
hjust = -2.5, |
1208 | 3x |
colour = "blue", |
1209 | 3x |
angle = 90 |
1210 |
) |
|
1211 | ||
1212 | 3x |
list( |
1213 | 3x |
value = next_dose, |
1214 | 3x |
allocation = cbind(dose = allocation_crit_dose, allocation = allocation_crit), |
1215 | 3x |
plot = p |
1216 |
) |
|
1217 |
} |
|
1218 |
) |
|
1219 | ||
1220 |
## NextBestProbMTDMinDist ---- |
|
1221 | ||
1222 |
#' @describeIn nextBest find the next best dose based with the highest |
|
1223 |
#' probability of having a toxicity rate with minimum distance to the |
|
1224 |
#' target toxicity level. |
|
1225 |
#' |
|
1226 |
#' @aliases nextBest-NextBestProbMTDMinDist |
|
1227 |
#' |
|
1228 |
#' @export |
|
1229 |
#' @example examples/Rules-method-nextBest-NextBestProbMtdMinDist.R |
|
1230 |
#' |
|
1231 |
setMethod( |
|
1232 |
f = "nextBest", |
|
1233 |
signature = signature( |
|
1234 |
nextBest = "NextBestProbMTDMinDist", |
|
1235 |
doselimit = "numeric", |
|
1236 |
samples = "Samples", |
|
1237 |
model = "GeneralModel", |
|
1238 |
data = "Data" |
|
1239 |
), |
|
1240 |
definition = function(nextBest, doselimit, samples, model, data, ...) { |
|
1241 |
# Matrix with samples from the dose-tox curve at the dose grid points. |
|
1242 | 3x |
prob_samples <- sapply(data@doseGrid, prob, model = model, samples = samples, ...) |
1243 | ||
1244 |
# Determine which dose level has the minimum distance to target. |
|
1245 | 3x |
dose_min_mtd_dist <- apply( |
1246 | 3x |
prob_samples, 1, function(x) which.min(abs(x - nextBest@target)) |
1247 |
) |
|
1248 | ||
1249 | 3x |
allocation_crit <- prop.table( |
1250 | 3x |
table(factor(dose_min_mtd_dist, levels = 1:data@nGrid)) |
1251 |
) |
|
1252 | 3x |
names(allocation_crit) <- as.character(data@doseGrid) |
1253 | ||
1254 |
# In case that placebo is used, placebo and the first non-placebo dose |
|
1255 |
# of the grid are merged. |
|
1256 | 3x |
if (data@placebo) { |
1257 | 1x |
allocation_crit[2] <- sum(allocation_crit[1:2]) |
1258 | 1x |
allocation_crit <- allocation_crit[-1] |
1259 |
} |
|
1260 | ||
1261 |
# Determine the dose with the highest relative frequency. |
|
1262 | 3x |
allocation_crit_dose <- as.numeric(names(allocation_crit)) |
1263 | 3x |
dose_target <- allocation_crit_dose[which.max(allocation_crit)] |
1264 | ||
1265 |
# Determine next dose. |
|
1266 | 3x |
doses_eligible <- h_next_best_eligible_doses( |
1267 | 3x |
data@doseGrid, |
1268 | 3x |
doselimit, |
1269 | 3x |
data@placebo |
1270 |
) |
|
1271 | 3x |
next_dose_level_eligible <- which.min(abs(doses_eligible - dose_target)) |
1272 | 3x |
next_dose <- doses_eligible[next_dose_level_eligible] |
1273 | ||
1274 |
# Create a plot. |
|
1275 | 3x |
plt_data <- if (data@placebo && data@doseGrid[1] == next_dose) { |
1276 | ! |
data.frame( |
1277 | ! |
x = as.factor(data@doseGrid), |
1278 | ! |
y = c(0, as.numeric(allocation_crit)) * 100 |
1279 |
) |
|
1280 |
} else { |
|
1281 | 3x |
data.frame( |
1282 | 3x |
x = as.factor(allocation_crit_dose), |
1283 | 3x |
y = as.numeric(allocation_crit) * 100 |
1284 |
) |
|
1285 |
} |
|
1286 | ||
1287 | 3x |
p <- ggplot( |
1288 | 3x |
data = plt_data, |
1289 | 3x |
fill = "grey50", |
1290 | 3x |
colour = "grey50" |
1291 |
) + |
|
1292 | 3x |
geom_col(aes(x, y), fill = "grey75") + |
1293 | 3x |
scale_x_discrete(guide = guide_axis(check.overlap = TRUE)) + |
1294 | 3x |
geom_vline( |
1295 | 3x |
xintercept = as.factor(dose_target), |
1296 | 3x |
lwd = 1.1, |
1297 | 3x |
colour = "black" |
1298 |
) + |
|
1299 | 3x |
geom_text( |
1300 | 3x |
data = data.frame(x = as.factor(dose_target)), |
1301 | 3x |
aes(.data$x, 0), |
1302 | 3x |
label = "Est", |
1303 | 3x |
vjust = -0.5, |
1304 | 3x |
hjust = -0.5, |
1305 | 3x |
colour = "black", |
1306 | 3x |
angle = 90 |
1307 |
) + |
|
1308 | 3x |
xlab("Dose") + |
1309 | 3x |
ylab(paste("Allocation criterion [%]")) |
1310 | ||
1311 | ||
1312 | 3x |
if (is.finite(doselimit)) { |
1313 | 2x |
doselimit_level <- if (any(allocation_crit_dose == doselimit)) { |
1314 | ! |
which(allocation_crit_dose == doselimit) |
1315 |
} else { |
|
1316 | 2x |
ifelse(test = data@placebo && data@doseGrid[1] == next_dose, |
1317 | 2x |
yes = 1.5, |
1318 | 2x |
no = sum(allocation_crit_dose < doselimit) + 0.5 |
1319 |
) |
|
1320 |
} |
|
1321 | ||
1322 | 2x |
p <- p + |
1323 | 2x |
geom_vline( |
1324 | 2x |
xintercept = doselimit_level, |
1325 | 2x |
colour = "red", lwd = 1.1 |
1326 |
) + |
|
1327 | 2x |
geom_text( |
1328 | 2x |
data = data.frame(x = doselimit_level), |
1329 | 2x |
aes(.data$x, 0), |
1330 | 2x |
label = "Max", |
1331 | 2x |
vjust = -0.5, |
1332 | 2x |
hjust = -1.5, |
1333 | 2x |
colour = "red", |
1334 | 2x |
angle = 90 |
1335 |
) |
|
1336 |
} |
|
1337 | ||
1338 | 3x |
p <- p + |
1339 | 3x |
geom_vline( |
1340 | 3x |
xintercept = as.factor(next_dose), |
1341 | 3x |
colour = "blue", lwd = 1.1 |
1342 |
) + |
|
1343 | 3x |
geom_text( |
1344 | 3x |
data = data.frame(x = as.factor(next_dose)), |
1345 | 3x |
aes(.data$x, 0), |
1346 | 3x |
label = "Next", |
1347 | 3x |
vjust = -0.5, |
1348 | 3x |
hjust = -2.5, |
1349 | 3x |
colour = "blue", |
1350 | 3x |
angle = 90 |
1351 |
) |
|
1352 | ||
1353 | 3x |
list( |
1354 | 3x |
value = next_dose, |
1355 | 3x |
allocation = cbind(dose = allocation_crit_dose, allocation = allocation_crit), |
1356 | 3x |
plot = p |
1357 |
) |
|
1358 |
} |
|
1359 |
) |
|
1360 | ||
1361 |
## NextBestOrdinal ---- |
|
1362 | ||
1363 |
#' @describeIn nextBest find the next best dose for ordinal CRM models. |
|
1364 |
#' |
|
1365 |
#' @aliases nextBest-NextBestOrdinal |
|
1366 |
#' |
|
1367 |
#' @export |
|
1368 |
#' @example examples/Rules-method-nextBest-NextBestOrdinal.R |
|
1369 |
#' |
|
1370 |
setMethod( |
|
1371 |
f = "nextBest", |
|
1372 |
signature = signature( |
|
1373 |
nextBest = "NextBestOrdinal", |
|
1374 |
doselimit = "numeric", |
|
1375 |
samples = "Samples", |
|
1376 |
model = "GeneralModel", |
|
1377 |
data = "Data" |
|
1378 |
), |
|
1379 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) { |
|
1380 | 1x |
stop( |
1381 | 1x |
paste0( |
1382 | 1x |
"NextBestOrdinal objects can only be used with LogisticLogNormalOrdinal ", |
1383 | 1x |
"models and DataOrdinal data objects. In this case, the model is a '", |
1384 | 1x |
class(model), |
1385 | 1x |
"' object and the data is in a ", |
1386 | 1x |
class(data), |
1387 | 1x |
" object." |
1388 |
) |
|
1389 |
) |
|
1390 |
} |
|
1391 |
) |
|
1392 | ||
1393 |
#' @describeIn nextBest find the next best dose for ordinal CRM models. |
|
1394 |
#' |
|
1395 |
#' @aliases nextBest-NextBestOrdinal |
|
1396 |
#' |
|
1397 |
#' @export |
|
1398 |
#' @example examples/Rules-method-nextBest-NextBestOrdinal.R |
|
1399 |
#' |
|
1400 |
setMethod( |
|
1401 |
f = "nextBest", |
|
1402 |
signature = signature( |
|
1403 |
nextBest = "NextBestOrdinal", |
|
1404 |
doselimit = "numeric", |
|
1405 |
samples = "Samples", |
|
1406 |
model = "LogisticLogNormalOrdinal", |
|
1407 |
data = "DataOrdinal" |
|
1408 |
), |
|
1409 |
definition = function(nextBest, doselimit = Inf, samples, model, data, ...) { |
|
1410 | 1x |
nextBest( |
1411 | 1x |
nextBest = nextBest@rule, |
1412 | 1x |
doselimit = doselimit, |
1413 | 1x |
samples = h_convert_ordinal_samples(samples, nextBest@grade), |
1414 | 1x |
model = h_convert_ordinal_model(model, nextBest@grade), |
1415 | 1x |
data = h_convert_ordinal_data(data, nextBest@grade), |
1416 |
... |
|
1417 |
) |
|
1418 |
} |
|
1419 |
) |
|
1420 | ||
1421 |
# maxDose ---- |
|
1422 | ||
1423 |
## generic ---- |
|
1424 | ||
1425 |
#' Determine the Maximum Possible Next Dose |
|
1426 |
#' |
|
1427 |
#' @description `r lifecycle::badge("stable")` |
|
1428 |
#' |
|
1429 |
#' This function determines the upper limit of the next dose based on the |
|
1430 |
#' `increments`and the `data`. |
|
1431 |
#' |
|
1432 |
#' @param increments (`Increments`)\cr the rule for the next best dose. |
|
1433 |
#' @param data (`Data`)\cr input data. |
|
1434 |
#' @param ... additional arguments without method dispatch. |
|
1435 |
#' |
|
1436 |
#' @return A `number`, the maximum possible next dose. |
|
1437 |
#' |
|
1438 |
#' @export |
|
1439 |
#' |
|
1440 |
setGeneric( |
|
1441 |
name = "maxDose", |
|
1442 |
def = function(increments, data, ...) { |
|
1443 | 380x |
standardGeneric("maxDose") |
1444 |
}, |
|
1445 |
valueClass = "numeric" |
|
1446 |
) |
|
1447 | ||
1448 |
## IncrementsRelative ---- |
|
1449 | ||
1450 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
1451 |
#' relative increments. |
|
1452 |
#' |
|
1453 |
#' @aliases maxDose-IncrementsRelative |
|
1454 |
#' |
|
1455 |
#' @export |
|
1456 |
#' @example examples/Rules-method-maxDose-IncrementsRelative.R |
|
1457 |
#' |
|
1458 |
setMethod( |
|
1459 |
f = "maxDose", |
|
1460 |
signature = signature( |
|
1461 |
increments = "IncrementsRelative", |
|
1462 |
data = "Data" |
|
1463 |
), |
|
1464 |
definition = function(increments, data, ...) { |
|
1465 | 222x |
last_dose <- data@x[data@nObs] |
1466 |
# Determine in which interval the `last_dose` is. |
|
1467 | 222x |
assert_true(last_dose >= head(increments@intervals, 1)) |
1468 | 219x |
last_dose_interval <- findInterval(x = last_dose, vec = increments@intervals) |
1469 | 219x |
(1 + increments@increments[last_dose_interval]) * last_dose |
1470 |
} |
|
1471 |
) |
|
1472 | ||
1473 |
## IncrementsRelativeDLT ---- |
|
1474 | ||
1475 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
1476 |
#' relative increments determined by DLTs so far. |
|
1477 |
#' |
|
1478 |
#' @aliases maxDose-IncrementsRelativeDLT |
|
1479 |
#' |
|
1480 |
#' @export |
|
1481 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeDLT.R |
|
1482 |
#' |
|
1483 |
setMethod( |
|
1484 |
f = "maxDose", |
|
1485 |
signature = signature( |
|
1486 |
increments = "IncrementsRelativeDLT", |
|
1487 |
data = "Data" |
|
1488 |
), |
|
1489 |
definition = function(increments, data, ...) { |
|
1490 | 11x |
dlt_count <- sum(data@y) |
1491 |
# Determine in which interval the `dlt_count` is. |
|
1492 | 11x |
assert_true(dlt_count >= increments@intervals[1]) |
1493 | 8x |
dlt_count_interval <- findInterval(x = dlt_count, vec = increments@intervals) |
1494 | 8x |
(1 + increments@increments[dlt_count_interval]) * data@x[data@nObs] |
1495 |
} |
|
1496 |
) |
|
1497 | ||
1498 |
## IncrementsRelativeDLTCurrent ---- |
|
1499 | ||
1500 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
1501 |
#' relative increments determined by DLTs in the current cohort. |
|
1502 |
#' |
|
1503 |
#' @aliases maxDose-IncrementsRelativeDLTCurrent |
|
1504 |
#' |
|
1505 |
#' @export |
|
1506 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeDLTCurrent.R |
|
1507 |
#' |
|
1508 |
setMethod( |
|
1509 |
f = "maxDose", |
|
1510 |
signature = signature( |
|
1511 |
increments = "IncrementsRelativeDLTCurrent", |
|
1512 |
data = "Data" |
|
1513 |
), |
|
1514 |
definition = function(increments, data, ...) { |
|
1515 | 12x |
last_dose <- data@x[data@nObs] |
1516 | ||
1517 |
# Determine how many DLTs have occurred in the last cohort. |
|
1518 | 12x |
last_cohort <- data@cohort[data@nObs] |
1519 | 12x |
last_cohort_indices <- which(data@cohort == last_cohort) |
1520 | 12x |
dlt_count_lcohort <- sum(data@y[last_cohort_indices]) |
1521 | ||
1522 |
# Determine in which interval the `dlt_count_lcohort` is. |
|
1523 | 12x |
assert_true(dlt_count_lcohort >= increments@intervals[1]) |
1524 | 9x |
dlt_count_lcohort_int <- findInterval(x = dlt_count_lcohort, vec = increments@intervals) |
1525 | 9x |
(1 + increments@increments[dlt_count_lcohort_int]) * last_dose |
1526 |
} |
|
1527 |
) |
|
1528 | ||
1529 |
## IncrementsRelativeParts ---- |
|
1530 | ||
1531 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
1532 |
#' relative increments as well as part 1 and beginning of part 2. |
|
1533 |
#' |
|
1534 |
#' @aliases maxDose-IncrementsRelativeParts |
|
1535 |
#' |
|
1536 |
#' @export |
|
1537 |
#' @example examples/Rules-method-maxDose-IncrementsRelativeParts.R |
|
1538 |
#' |
|
1539 |
setMethod( |
|
1540 |
f = "maxDose", |
|
1541 |
signature = signature( |
|
1542 |
increments = "IncrementsRelativeParts", |
|
1543 |
data = "DataParts" |
|
1544 |
), |
|
1545 |
definition = function(increments, data, ...) { |
|
1546 | 10x |
all_in_part1 <- all(data@part == 1L) |
1547 | 10x |
incrmnt <- if (all_in_part1) { |
1548 | 9x |
part2_started <- data@nextPart == 2L |
1549 | 9x |
if (part2_started) { |
1550 | 7x |
any_dlt <- any(data@y == 1L) |
1551 | 7x |
if (any_dlt) { |
1552 | 4x |
increments@dlt_start |
1553 | 3x |
} else if (increments@clean_start <= 0L) { |
1554 | 2x |
increments@clean_start |
1555 |
} |
|
1556 |
} else { |
|
1557 | 2x |
1L |
1558 |
} |
|
1559 |
} |
|
1560 | ||
1561 | 10x |
if (is.null(incrmnt)) { |
1562 | 2x |
callNextMethod(increments, data, ...) |
1563 |
} else { |
|
1564 | 8x |
max_dose_lev_part1 <- match_within_tolerance(max(data@x), data@part1Ladder) |
1565 | 8x |
new_max_dose_level <- max_dose_lev_part1 + incrmnt |
1566 | 8x |
assert_true(new_max_dose_level >= 0L) |
1567 | 6x |
assert_true(new_max_dose_level <= length(data@part1Ladder)) |
1568 | 3x |
data@part1Ladder[new_max_dose_level] |
1569 |
} |
|
1570 |
} |
|
1571 |
) |
|
1572 | ||
1573 |
## IncrementsDoseLevels ---- |
|
1574 | ||
1575 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
1576 |
#' the number of dose grid levels. That is, the max dose is determined as |
|
1577 |
#' the one which level is equal to: base dose level + level increment. |
|
1578 |
#' The base dose level is the level of the last dose in grid or the level |
|
1579 |
#' of the maximum dose applied, which is defined in `increments` object. |
|
1580 |
#' Find out more in [`IncrementsDoseLevels`]. |
|
1581 |
#' |
|
1582 |
#' @aliases maxDose-IncrementsDoseLevels |
|
1583 |
#' |
|
1584 |
#' @export |
|
1585 |
#' @example examples/Rules-method-maxDose-IncrementsDoseLevels.R |
|
1586 |
#' |
|
1587 |
setMethod( |
|
1588 |
f = "maxDose", |
|
1589 |
signature = signature( |
|
1590 |
increments = "IncrementsDoseLevels", |
|
1591 |
data = "Data" |
|
1592 |
), |
|
1593 |
definition = function(increments, data, ...) { |
|
1594 |
# Determine what is the basis level for increment, |
|
1595 |
# i.e. the last dose or the max dose applied. |
|
1596 | 106x |
basis_dose_level <- ifelse( |
1597 | 106x |
increments@basis_level == "last", data@xLevel[data@nObs], max(data@xLevel) |
1598 |
) |
|
1599 | 106x |
max_dose_level <- min(basis_dose_level + increments@levels, data@nGrid) |
1600 | 106x |
data@doseGrid[max_dose_level] |
1601 |
} |
|
1602 |
) |
|
1603 | ||
1604 |
## IncrementsHSRBeta ---- |
|
1605 | ||
1606 |
#' @describeIn maxDose determine the maximum possible next dose for escalation. |
|
1607 |
#' |
|
1608 |
#' @aliases maxDose-IncrementsHSRBeta |
|
1609 |
#' |
|
1610 |
#' @export |
|
1611 |
#' @example examples/Rules-method-maxDose-IncrementsHSRBeta.R |
|
1612 |
#' |
|
1613 |
setMethod( |
|
1614 |
f = "maxDose", |
|
1615 |
signature = signature( |
|
1616 |
increments = "IncrementsHSRBeta", |
|
1617 |
data = "Data" |
|
1618 |
), |
|
1619 |
definition = function(increments, data, ...) { |
|
1620 |
# Summary of observed data per dose level. |
|
1621 | 7x |
y <- factor(data@y, levels = c("0", "1")) |
1622 | 7x |
dlt_tab <- table(y, data@x) |
1623 | ||
1624 |
# Ignore placebo if applied. |
|
1625 | 7x |
if (data@placebo == TRUE & min(data@x) == data@doseGrid[1]) { |
1626 | 4x |
dlt_tab <- dlt_tab[, -1] |
1627 |
} |
|
1628 | ||
1629 |
# Extract dose names as these get lost if only one dose available. |
|
1630 | 7x |
non_plcb_doses <- unique(sort(as.numeric(colnames(dlt_tab)))) |
1631 | ||
1632 |
# Toxicity probability per dose level. |
|
1633 | 7x |
x <- dlt_tab[2, ] |
1634 | 7x |
n <- apply(dlt_tab, 2, sum) |
1635 | 7x |
tox_prob <- pbeta( |
1636 | 7x |
increments@target, |
1637 | 7x |
x + increments@a, |
1638 | 7x |
n - x + increments@b, |
1639 | 7x |
lower.tail = FALSE |
1640 |
) |
|
1641 | ||
1642 |
# Return the min toxic dose level or maximum dose level if no dose is toxic, |
|
1643 |
# while ignoring placebo. |
|
1644 | 7x |
dose_tox <- if (sum(tox_prob > increments@prob) > 0) { |
1645 | 5x |
min(non_plcb_doses[which(tox_prob > increments@prob)]) |
1646 |
} else { |
|
1647 |
# Add small value to max dose, so that the max dose is always smaller. |
|
1648 | 2x |
max(data@doseGrid) + 0.01 |
1649 |
} |
|
1650 | ||
1651 |
# Determine the next maximum possible dose. |
|
1652 |
# In case that the first active dose is above probability threshold, |
|
1653 |
# the first active dose is reported as maximum. I.e. in case that placebo is used, |
|
1654 |
# the second dose is reported. Please note that this rule should be used together |
|
1655 |
# with the hard safety stopping rule to avoid inconsistent results. |
|
1656 | 7x |
max(data@doseGrid[data@doseGrid < dose_tox], data@doseGrid[data@placebo + 1]) |
1657 |
} |
|
1658 |
) |
|
1659 | ||
1660 |
## IncrementsMin ---- |
|
1661 | ||
1662 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
1663 |
#' multiple increment rules, taking the minimum across individual increments. |
|
1664 |
#' |
|
1665 |
#' @aliases maxDose-IncrementsMin |
|
1666 |
#' |
|
1667 |
#' @export |
|
1668 |
#' @example examples/Rules-method-maxDose-IncrementsMin.R |
|
1669 |
#' |
|
1670 |
setMethod( |
|
1671 |
f = "maxDose", |
|
1672 |
signature = signature( |
|
1673 |
increments = "IncrementsMin", |
|
1674 |
data = "Data" |
|
1675 |
), |
|
1676 |
definition = function(increments, data, ...) { |
|
1677 | 2x |
individual_results <- sapply(increments@increments_list, maxDose, data = data, ...) |
1678 | 2x |
min(individual_results) |
1679 |
} |
|
1680 |
) |
|
1681 | ||
1682 |
#' @describeIn maxDose determine the maximum possible next dose based on |
|
1683 |
#' multiple increment rules, taking the minimum across individual increments. |
|
1684 |
#' |
|
1685 |
#' @aliases maxDose-IncrementsMin |
|
1686 |
#' |
|
1687 |
#' @export |
|
1688 |
setMethod( |
|
1689 |
f = "maxDose", |
|
1690 |
signature = signature( |
|
1691 |
increments = "IncrementsMin", |
|
1692 |
data = "DataOrdinal" |
|
1693 |
), |
|
1694 |
definition = function(increments, data, ...) { |
|
1695 | 2x |
individual_results <- sapply(increments@increments_list, maxDose, data = data, ...) |
1696 | 2x |
min(individual_results) |
1697 |
} |
|
1698 |
) |
|
1699 | ||
1700 |
## IncrementsOrdinal ---- |
|
1701 | ||
1702 |
#' @describeIn maxDose determine the maximum possible next dose in an ordinal |
|
1703 |
#' CRM trial |
|
1704 |
#' |
|
1705 |
#' @aliases maxDose-IncrementsOrdinal |
|
1706 |
#' |
|
1707 |
#' @export |
|
1708 |
#' @example examples/Rules-method-maxDose-IncrementsOrdinal.R |
|
1709 |
#' |
|
1710 |
setMethod( |
|
1711 |
f = "maxDose", |
|
1712 |
signature = signature( |
|
1713 |
increments = "IncrementsOrdinal", |
|
1714 |
data = "DataOrdinal" |
|
1715 |
), |
|
1716 |
definition = function(increments, data, ...) { |
|
1717 | 6x |
maxDose( |
1718 | 6x |
increments = increments@rule, |
1719 | 6x |
data = h_convert_ordinal_data( |
1720 | 6x |
data, |
1721 | 6x |
increments@grade, |
1722 |
... |
|
1723 |
) |
|
1724 |
) |
|
1725 |
} |
|
1726 |
) |
|
1727 | ||
1728 |
## IncrementsMaxToxProb ---- |
|
1729 | ||
1730 |
#' @describeIn maxDose determine the maximum possible next dose based on the |
|
1731 |
#' probability of toxicity |
|
1732 |
#' @param model (`GeneralModel`)\cr The model on which probabilities will be based |
|
1733 |
#' @param samples (`Samples`)\cr The MCMC samples to which `model` will be applied |
|
1734 |
#' |
|
1735 |
#' @aliases maxDose-IncrementsMaxToxProb |
|
1736 |
#' |
|
1737 |
#' @export |
|
1738 |
#' @example examples/Rules-method-maxDose-IncrementsMaxToxProb.R |
|
1739 |
#' |
|
1740 |
setMethod( |
|
1741 |
f = "maxDose", |
|
1742 |
signature = signature( |
|
1743 |
increments = "IncrementsMaxToxProb", |
|
1744 |
data = "DataOrdinal" |
|
1745 |
), |
|
1746 |
definition = function(increments, data, model, samples, ...) { |
|
1747 | 3x |
assert_class(samples, "Samples") |
1748 | 3x |
assert_true(length(increments@prob) == length(data@yCategories) - 1) |
1749 | 3x |
nm <- utils::tail(names(data@yCategories), -1) |
1750 | 3x |
assert_set_equal(names(increments@prob), nm) |
1751 | ||
1752 | 3x |
probs <- dplyr::bind_rows( |
1753 | 3x |
lapply( |
1754 | 3x |
seq_along(increments@prob), |
1755 | 3x |
function(g) { |
1756 | 6x |
fitted_probs <- fit(samples, model, data, grade = g, ...) |
1757 | 6x |
safe_fitted_probs <- dplyr::filter(fitted_probs, middle < increments@prob[nm[g]]) |
1758 | 6x |
highest_safe_fitted_prob <- utils::tail(safe_fitted_probs, 1) |
1759 |
} |
|
1760 |
) |
|
1761 |
) |
|
1762 | 3x |
min(probs$dose) |
1763 |
} |
|
1764 |
) |
|
1765 |
#' @describeIn maxDose determine the maximum possible next dose based on the |
|
1766 |
#' probability of toxicity |
|
1767 |
#' @param model (`GeneralModel`)\cr The model on which probabilities will be based |
|
1768 |
#' @param samples (`Samples`)\cr The MCMC samples to which `model` will be applied |
|
1769 |
#' |
|
1770 |
#' @aliases maxDose-IncrementsMaxToxProb |
|
1771 |
#' |
|
1772 |
#' @export |
|
1773 |
#' @example examples/Rules-method-maxDose-IncrementsMaxToxProb.R |
|
1774 |
#' |
|
1775 |
setMethod( |
|
1776 |
f = "maxDose", |
|
1777 |
signature = signature( |
|
1778 |
increments = "IncrementsMaxToxProb", |
|
1779 |
data = "Data" |
|
1780 |
), |
|
1781 |
definition = function(increments, data, model, samples, ...) { |
|
1782 | 1x |
assert_class(samples, "Samples") |
1783 | 1x |
assert_true(length(increments@prob) == 1) |
1784 | ||
1785 | 1x |
fitted_prob <- fit(samples, model, data, ...) |
1786 | 1x |
safe_fitted_prob <- dplyr::filter(fitted_prob, middle < increments@prob) |
1787 | 1x |
highest_safe_fitted_prob <- utils::tail(safe_fitted_prob, 1) |
1788 | 1x |
highest_safe_fitted_prob$dose |
1789 |
} |
|
1790 |
) |
|
1791 | ||
1792 |
## tidy-IncrementsMaxToxProb ---- |
|
1793 | ||
1794 |
#' @rdname tidy |
|
1795 |
#' @aliases tidy-IncrementsMaxToxProb |
|
1796 |
#' @example examples/Rules-method-tidyIncrementsMaxToxProb.R |
|
1797 |
#' @export |
|
1798 |
setMethod( |
|
1799 |
f = "tidy", |
|
1800 |
signature = signature(x = "IncrementsMaxToxProb"), |
|
1801 |
definition = function(x, ...) { |
|
1802 | 2x |
grades <- names(x@prob) |
1803 | 2x |
if (is.null(grades)) { |
1804 | ! |
grades <- "1" |
1805 |
} |
|
1806 | 2x |
tibble( |
1807 | 2x |
Grade = grades, |
1808 | 2x |
Prob = x@prob |
1809 |
) %>% |
|
1810 | 2x |
h_tidy_class(x) |
1811 |
} |
|
1812 |
) |
|
1813 | ||
1814 |
# nolint start |
|
1815 | ||
1816 |
## ============================================================ |
|
1817 | ||
1818 |
## -------------------------------------------------- |
|
1819 |
## "AND" combination of stopping rules |
|
1820 |
## -------------------------------------------------- |
|
1821 | ||
1822 |
##' The method combining two atomic stopping rules |
|
1823 |
##' |
|
1824 |
##' @param e1 First \code{\linkS4class{Stopping}} object |
|
1825 |
##' @param e2 Second \code{\linkS4class{Stopping}} object |
|
1826 |
##' @return The \code{\linkS4class{StoppingAll}} object |
|
1827 |
##' |
|
1828 |
##' @example examples/Rules-method-and-stopping-stopping.R |
|
1829 |
##' @keywords methods |
|
1830 |
setMethod("&", |
|
1831 |
signature( |
|
1832 |
e1 = "Stopping", |
|
1833 |
e2 = "Stopping" |
|
1834 |
), |
|
1835 |
def = |
|
1836 |
function(e1, e2) { |
|
1837 | 17x |
StoppingAll(list(e1, e2)) |
1838 |
} |
|
1839 |
) |
|
1840 | ||
1841 |
##' The method combining a stopping list and an atomic |
|
1842 |
##' |
|
1843 |
##' @param e1 \code{\linkS4class{StoppingAll}} object |
|
1844 |
##' @param e2 \code{\linkS4class{Stopping}} object |
|
1845 |
##' @return The modified \code{\linkS4class{StoppingAll}} object |
|
1846 |
##' |
|
1847 |
##' @example examples/Rules-method-and-stoppingAll-stopping.R |
|
1848 |
##' @keywords methods |
|
1849 |
setMethod("&", |
|
1850 |
signature( |
|
1851 |
e1 = "StoppingAll", |
|
1852 |
e2 = "Stopping" |
|
1853 |
), |
|
1854 |
def = |
|
1855 |
function(e1, e2) { |
|
1856 | 1x |
e1@stop_list <- c( |
1857 | 1x |
e1@stop_list, |
1858 | 1x |
e2 |
1859 |
) |
|
1860 | 1x |
return(e1) |
1861 |
} |
|
1862 |
) |
|
1863 | ||
1864 |
##' The method combining an atomic and a stopping list |
|
1865 |
##' |
|
1866 |
##' @param e1 \code{\linkS4class{Stopping}} object |
|
1867 |
##' @param e2 \code{\linkS4class{StoppingAll}} object |
|
1868 |
##' @return The modified \code{\linkS4class{StoppingAll}} object |
|
1869 |
##' |
|
1870 |
##' @example examples/Rules-method-and-stopping-stoppingAll.R |
|
1871 |
##' @keywords methods |
|
1872 |
setMethod("&", |
|
1873 |
signature( |
|
1874 |
e1 = "Stopping", |
|
1875 |
e2 = "StoppingAll" |
|
1876 |
), |
|
1877 |
def = |
|
1878 |
function(e1, e2) { |
|
1879 | 1x |
e2@stop_list <- c( |
1880 | 1x |
e1, |
1881 | 1x |
e2@stop_list |
1882 |
) |
|
1883 | 1x |
return(e2) |
1884 |
} |
|
1885 |
) |
|
1886 | ||
1887 |
## -------------------------------------------------- |
|
1888 |
## "OR" combination of stopping rules |
|
1889 |
## -------------------------------------------------- |
|
1890 | ||
1891 |
##' The method combining two atomic stopping rules |
|
1892 |
##' |
|
1893 |
##' @param e1 First \code{\linkS4class{Stopping}} object |
|
1894 |
##' @param e2 Second \code{\linkS4class{Stopping}} object |
|
1895 |
##' @return The \code{\linkS4class{StoppingAny}} object |
|
1896 |
##' |
|
1897 |
##' @aliases |,Stopping,Stopping-method |
|
1898 |
##' @name or-Stopping-Stopping |
|
1899 |
##' @example examples/Rules-method-or-stopping-stopping.R |
|
1900 |
##' @keywords methods |
|
1901 |
setMethod("|", |
|
1902 |
signature( |
|
1903 |
e1 = "Stopping", |
|
1904 |
e2 = "Stopping" |
|
1905 |
), |
|
1906 |
def = |
|
1907 |
function(e1, e2) { |
|
1908 | 37x |
StoppingAny(list(e1, e2)) |
1909 |
} |
|
1910 |
) |
|
1911 | ||
1912 |
##' The method combining a stopping list and an atomic |
|
1913 |
##' |
|
1914 |
##' @param e1 \code{\linkS4class{StoppingAny}} object |
|
1915 |
##' @param e2 \code{\linkS4class{Stopping}} object |
|
1916 |
##' @return The modified \code{\linkS4class{StoppingAny}} object |
|
1917 |
##' |
|
1918 |
##' @aliases |,StoppingAny,Stopping-method |
|
1919 |
##' @name or-Stopping-StoppingAny |
|
1920 |
##' @example examples/Rules-method-or-stoppingAny-stopping.R |
|
1921 |
##' @keywords methods |
|
1922 |
setMethod("|", |
|
1923 |
signature( |
|
1924 |
e1 = "StoppingAny", |
|
1925 |
e2 = "Stopping" |
|
1926 |
), |
|
1927 |
def = |
|
1928 |
function(e1, e2) { |
|
1929 | 1x |
e1@stop_list <- c( |
1930 | 1x |
e1@stop_list, |
1931 | 1x |
e2 |
1932 |
) |
|
1933 | 1x |
return(e1) |
1934 |
} |
|
1935 |
) |
|
1936 | ||
1937 |
##' The method combining an atomic and a stopping list |
|
1938 |
##' |
|
1939 |
##' @param e1 \code{\linkS4class{Stopping}} object |
|
1940 |
##' @param e2 \code{\linkS4class{StoppingAny}} object |
|
1941 |
##' @return The modified \code{\linkS4class{StoppingAny}} object |
|
1942 |
##' |
|
1943 |
##' @aliases |,Stopping,StoppingAny-method |
|
1944 |
##' @name or-StoppingAny-Stopping |
|
1945 |
##' @example examples/Rules-method-or-stopping-stoppingAny.R |
|
1946 |
##' @keywords methods |
|
1947 |
setMethod("|", |
|
1948 |
signature( |
|
1949 |
e1 = "Stopping", |
|
1950 |
e2 = "StoppingAny" |
|
1951 |
), |
|
1952 |
def = |
|
1953 |
function(e1, e2) { |
|
1954 | 1x |
e2@stop_list <- c( |
1955 | 1x |
e1, |
1956 | 1x |
e2@stop_list |
1957 |
) |
|
1958 | 1x |
return(e2) |
1959 |
} |
|
1960 |
) |
|
1961 | ||
1962 |
# nolint end |
|
1963 | ||
1964 |
# Stopping ---- |
|
1965 | ||
1966 |
## generic ---- |
|
1967 | ||
1968 |
#' Stop the trial? |
|
1969 |
#' |
|
1970 |
#' @description `r lifecycle::badge("stable")` |
|
1971 |
#' |
|
1972 |
#' This function returns whether to stop the trial. |
|
1973 |
#' |
|
1974 |
#' @param stopping (`Stopping`)\cr the rule for stopping the trial. |
|
1975 |
#' @param dose the recommended next best dose. |
|
1976 |
#' @param samples (`Samples`)\cr the mcmc samples. |
|
1977 |
#' @param model (`GeneralModel`)\cr the model. |
|
1978 |
#' @param data (`Data`)\cr input data. |
|
1979 |
#' @param ... additional arguments without method dispatch. |
|
1980 |
#' |
|
1981 |
#' @return logical value: `TRUE` if the trial can be stopped, `FALSE` |
|
1982 |
#' otherwise. It should have an attribute `message` which gives the reason |
|
1983 |
#' for the decision. |
|
1984 |
#' |
|
1985 |
#' @export |
|
1986 |
#' @example examples/Rules-method-CombiningStoppingRulesAndOr.R |
|
1987 |
setGeneric( |
|
1988 |
name = "stopTrial", |
|
1989 |
def = function(stopping, dose, samples, model, data, ...) { |
|
1990 | 2541x |
standardGeneric("stopTrial") |
1991 |
}, |
|
1992 |
valueClass = "logical" |
|
1993 |
) |
|
1994 | ||
1995 |
## StoppingMissingDose ---- |
|
1996 | ||
1997 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
1998 |
#' |
|
1999 |
#' @description `r lifecycle::badge("experimental")` |
|
2000 |
#' |
|
2001 |
#' @aliases stopTrial-StoppingMissingDose |
|
2002 |
#' @example examples/Rules-method-stopTrial-StoppingMissingDose.R |
|
2003 |
#' |
|
2004 |
setMethod( |
|
2005 |
f = "stopTrial", |
|
2006 |
signature = signature( |
|
2007 |
stopping = "StoppingMissingDose", |
|
2008 |
dose = "numeric", |
|
2009 |
samples = "ANY", |
|
2010 |
model = "ANY", |
|
2011 |
data = "Data" |
|
2012 |
), |
|
2013 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
2014 | 15x |
do_stop <- is.na(dose) || (data@placebo && dose == min(data@doseGrid)) |
2015 | ||
2016 | 15x |
msg <- paste( |
2017 | 15x |
"Next dose is", |
2018 | 15x |
ifelse( |
2019 | 15x |
do_stop, |
2020 | 15x |
paste( |
2021 | 15x |
ifelse( |
2022 | 15x |
data@placebo && dose == min(data@doseGrid), |
2023 | 15x |
"placebo dose", |
2024 | 15x |
"NA" |
2025 |
), |
|
2026 | 15x |
", i.e., no active dose is safe enough according to the NextBest rule." |
2027 |
), |
|
2028 | 15x |
"available at the dose grid." |
2029 |
) |
|
2030 |
) |
|
2031 | ||
2032 | 15x |
structure(do_stop, |
2033 | 15x |
message = msg, |
2034 | 15x |
report_label = stopping@report_label |
2035 |
) |
|
2036 |
} |
|
2037 |
) |
|
2038 | ||
2039 |
# nolint start |
|
2040 | ||
2041 |
## -------------------------------------------------- |
|
2042 |
## Stopping based on multiple stopping rules |
|
2043 |
## -------------------------------------------------- |
|
2044 | ||
2045 |
##' @describeIn stopTrial Stop based on multiple stopping rules |
|
2046 |
##' @example examples/Rules-method-stopTrial-StoppingList.R |
|
2047 |
setMethod("stopTrial", |
|
2048 |
signature = |
|
2049 |
signature( |
|
2050 |
stopping = "StoppingList", |
|
2051 |
dose = "ANY", |
|
2052 |
samples = "ANY", |
|
2053 |
model = "ANY", |
|
2054 |
data = "ANY" |
|
2055 |
), |
|
2056 |
def = |
|
2057 |
function(stopping, dose, samples, model, data, ...) { |
|
2058 |
## evaluate the individual stopping rules |
|
2059 |
## in the list |
|
2060 | 14x |
individualResults <- |
2061 | 14x |
if (missing(samples)) { |
2062 | 7x |
lapply(stopping@stop_list, |
2063 | 7x |
stopTrial, |
2064 | 7x |
dose = dose, |
2065 | 7x |
model = model, |
2066 | 7x |
data = data, |
2067 |
... |
|
2068 |
) |
|
2069 |
} else { |
|
2070 | 7x |
lapply(stopping@stop_list, |
2071 | 7x |
stopTrial, |
2072 | 7x |
dose = dose, |
2073 | 7x |
samples = samples, |
2074 | 7x |
model = model, |
2075 | 7x |
data = data, |
2076 |
... |
|
2077 |
) |
|
2078 |
} |
|
2079 | ||
2080 |
## summarize to obtain overall result |
|
2081 | 14x |
overallResult <- stopping@summary(as.logical(individualResults)) |
2082 | ||
2083 |
## retrieve individual text messages, |
|
2084 |
## but let them in the list structure |
|
2085 | 14x |
overallText <- lapply(individualResults, attr, "message") |
2086 | ||
2087 | 14x |
return(structure(overallResult, |
2088 | 14x |
message = overallText, |
2089 | 14x |
individual = individualResults |
2090 |
)) |
|
2091 |
} |
|
2092 |
) |
|
2093 | ||
2094 |
## -------------------------------------------------- |
|
2095 |
## Stopping based on fulfillment of all multiple stopping rules |
|
2096 |
## -------------------------------------------------- |
|
2097 | ||
2098 |
##' @describeIn stopTrial Stop based on fulfillment of all multiple stopping |
|
2099 |
##' rules |
|
2100 |
##' |
|
2101 |
##' @example examples/Rules-method-stopTrial-StoppingAll.R |
|
2102 |
setMethod("stopTrial", |
|
2103 |
signature = |
|
2104 |
signature( |
|
2105 |
stopping = "StoppingAll", |
|
2106 |
dose = "ANY", |
|
2107 |
samples = "ANY", |
|
2108 |
model = "ANY", |
|
2109 |
data = "ANY" |
|
2110 |
), |
|
2111 |
def = |
|
2112 |
function(stopping, dose, samples, model, data, ...) { |
|
2113 |
## evaluate the individual stopping rules |
|
2114 |
## in the list |
|
2115 | 72x |
individualResults <- |
2116 | 72x |
if (missing(samples)) { |
2117 | 6x |
lapply(stopping@stop_list, |
2118 | 6x |
stopTrial, |
2119 | 6x |
dose = dose, |
2120 | 6x |
model = model, |
2121 | 6x |
data = data, |
2122 |
... |
|
2123 |
) |
|
2124 |
} else { |
|
2125 | 66x |
lapply(stopping@stop_list, |
2126 | 66x |
stopTrial, |
2127 | 66x |
dose = dose, |
2128 | 66x |
samples = samples, |
2129 | 66x |
model = model, |
2130 | 66x |
data = data, |
2131 |
... |
|
2132 |
) |
|
2133 |
} |
|
2134 | ||
2135 |
## summarize to obtain overall result |
|
2136 | 72x |
overallResult <- all(as.logical(individualResults)) |
2137 | ||
2138 |
## retrieve individual text messages, |
|
2139 |
## but let them in the list structure |
|
2140 | 72x |
overallText <- lapply(individualResults, attr, "message") |
2141 | ||
2142 | 72x |
return(structure(overallResult, |
2143 | 72x |
message = overallText, |
2144 | 72x |
individual = individualResults, |
2145 | 72x |
report_label = stopping@report_label |
2146 |
)) |
|
2147 |
} |
|
2148 |
) |
|
2149 | ||
2150 | ||
2151 |
## -------------------------------------------------- |
|
2152 |
## Stopping based on fulfillment of any stopping rule |
|
2153 |
## -------------------------------------------------- |
|
2154 | ||
2155 |
##' @describeIn stopTrial Stop based on fulfillment of any stopping rule |
|
2156 |
##' |
|
2157 |
##' @example examples/Rules-method-stopTrial-StoppingAny.R |
|
2158 |
setMethod("stopTrial", |
|
2159 |
signature = |
|
2160 |
signature( |
|
2161 |
stopping = "StoppingAny", |
|
2162 |
dose = "ANY", |
|
2163 |
samples = "ANY", |
|
2164 |
model = "ANY", |
|
2165 |
data = "ANY" |
|
2166 |
), |
|
2167 |
def = |
|
2168 |
function(stopping, dose, samples, model, data, ...) { |
|
2169 |
## evaluate the individual stopping rules |
|
2170 |
## in the list |
|
2171 | 92x |
individualResults <- |
2172 | 92x |
if (missing(samples)) { |
2173 | 6x |
lapply(stopping@stop_list, |
2174 | 6x |
stopTrial, |
2175 | 6x |
dose = dose, |
2176 | 6x |
model = model, |
2177 | 6x |
data = data, |
2178 |
... |
|
2179 |
) |
|
2180 |
} else { |
|
2181 | 86x |
lapply(stopping@stop_list, |
2182 | 86x |
stopTrial, |
2183 | 86x |
dose = dose, |
2184 | 86x |
samples = samples, |
2185 | 86x |
model = model, |
2186 | 86x |
data = data, |
2187 |
... |
|
2188 |
) |
|
2189 |
} |
|
2190 | ||
2191 |
## summarize to obtain overall result |
|
2192 | 92x |
overallResult <- any(as.logical(individualResults)) |
2193 | ||
2194 |
## retrieve individual text messages, |
|
2195 |
## but let them in the list structure |
|
2196 | 92x |
overallText <- lapply(individualResults, attr, "message") |
2197 | ||
2198 | 92x |
return(structure(overallResult, |
2199 | 92x |
message = overallText, |
2200 | 92x |
individual = individualResults, |
2201 | 92x |
report_label = stopping@report_label |
2202 |
)) |
|
2203 |
} |
|
2204 |
) |
|
2205 | ||
2206 | ||
2207 | ||
2208 | ||
2209 |
## -------------------------------------------------- |
|
2210 |
## Stopping based on number of cohorts near to next best dose |
|
2211 |
## -------------------------------------------------- |
|
2212 | ||
2213 |
##' @describeIn stopTrial Stop based on number of cohorts near to next best dose |
|
2214 |
##' |
|
2215 |
##' @example examples/Rules-method-stopTrial-StoppingCohortsNearDose.R |
|
2216 |
setMethod("stopTrial", |
|
2217 |
signature = |
|
2218 |
signature( |
|
2219 |
stopping = "StoppingCohortsNearDose", |
|
2220 |
dose = "numeric", |
|
2221 |
samples = "ANY", |
|
2222 |
model = "ANY", |
|
2223 |
data = "Data" |
|
2224 |
), |
|
2225 |
def = |
|
2226 |
function(stopping, dose, samples, model, data, ...) { |
|
2227 |
## determine the range where the cohorts must lie in |
|
2228 | 14x |
lower <- (100 - stopping@percentage) / 100 * dose |
2229 | 14x |
upper <- (100 + stopping@percentage) / 100 * dose |
2230 | ||
2231 |
## which patients lie there? |
|
2232 | 14x |
indexPatients <- which((data@x >= lower) & (data@x <= upper)) |
2233 | ||
2234 |
## how many cohorts? |
|
2235 | 14x |
nCohorts <- length(unique(data@cohort[indexPatients])) |
2236 | ||
2237 |
## so can we stop? |
|
2238 | 14x |
doStop <- nCohorts >= stopping@nCohorts |
2239 | ||
2240 |
## generate message |
|
2241 | 14x |
text <- paste(nCohorts, |
2242 | 14x |
" cohorts lie within ", |
2243 | 14x |
stopping@percentage, |
2244 | 14x |
"% of the next best dose ", |
2245 | 14x |
dose, |
2246 | 14x |
". This ", |
2247 | 14x |
ifelse(doStop, "reached", "is below"), |
2248 | 14x |
" the required ", |
2249 | 14x |
stopping@nCohorts, |
2250 | 14x |
" cohorts", |
2251 | 14x |
sep = "" |
2252 |
) |
|
2253 | ||
2254 |
## return both |
|
2255 | 14x |
return(structure(doStop, |
2256 | 14x |
message = text, |
2257 | 14x |
report_label = stopping@report_label |
2258 |
)) |
|
2259 |
} |
|
2260 |
) |
|
2261 | ||
2262 | ||
2263 |
## ------------------------------------------------------------- |
|
2264 |
## Stopping based on number of patients near to next best dose |
|
2265 |
## ------------------------------------------------------------- |
|
2266 | ||
2267 |
##' @describeIn stopTrial Stop based on number of patients near to next best |
|
2268 |
##' dose |
|
2269 |
##' |
|
2270 |
##' @example examples/Rules-method-stopTrial-StoppingPatientsNearDose.R |
|
2271 |
setMethod("stopTrial", |
|
2272 |
signature = |
|
2273 |
signature( |
|
2274 |
stopping = "StoppingPatientsNearDose", |
|
2275 |
dose = "numeric", |
|
2276 |
samples = "ANY", |
|
2277 |
model = "ANY", |
|
2278 |
data = "Data" |
|
2279 |
), |
|
2280 |
def = |
|
2281 |
function(stopping, dose, samples, model, data, ...) { |
|
2282 |
## determine the range where the cohorts must lie in |
|
2283 | 2x |
lower <- (100 - stopping@percentage) / 100 * dose |
2284 | 2x |
upper <- (100 + stopping@percentage) / 100 * dose |
2285 | ||
2286 |
## how many patients lie there? |
|
2287 | 2x |
nPatients <- ifelse( |
2288 | 2x |
is.na(dose), |
2289 | 2x |
0, |
2290 | 2x |
sum((data@x >= lower) & (data@x <= upper)) |
2291 |
) |
|
2292 | ||
2293 |
## so can we stop? |
|
2294 | 2x |
doStop <- nPatients >= stopping@nPatients |
2295 | ||
2296 |
## generate message |
|
2297 | 2x |
text <- paste(nPatients, |
2298 | 2x |
" patients lie within ", |
2299 | 2x |
stopping@percentage, |
2300 | 2x |
"% of the next best dose ", |
2301 | 2x |
dose, |
2302 | 2x |
". This ", |
2303 | 2x |
ifelse(doStop, "reached", "is below"), |
2304 | 2x |
" the required ", |
2305 | 2x |
stopping@nPatients, |
2306 | 2x |
" patients", |
2307 | 2x |
sep = "" |
2308 |
) |
|
2309 | ||
2310 |
## return both |
|
2311 | 2x |
return(structure(doStop, |
2312 | 2x |
message = text, |
2313 | 2x |
report_label = stopping@report_label |
2314 |
)) |
|
2315 |
} |
|
2316 |
) |
|
2317 | ||
2318 |
## -------------------------------------------------- |
|
2319 |
## Stopping based on minimum number of cohorts |
|
2320 |
## -------------------------------------------------- |
|
2321 | ||
2322 |
##' @describeIn stopTrial Stop based on minimum number of cohorts |
|
2323 |
##' |
|
2324 |
##' @example examples/Rules-method-stopTrial-StoppingMinCohorts.R |
|
2325 |
setMethod("stopTrial", |
|
2326 |
signature = |
|
2327 |
signature( |
|
2328 |
stopping = "StoppingMinCohorts", |
|
2329 |
dose = "ANY", |
|
2330 |
samples = "ANY", |
|
2331 |
model = "ANY", |
|
2332 |
data = "Data" |
|
2333 |
), |
|
2334 |
def = |
|
2335 |
function(stopping, dose, samples, model, data, ...) { |
|
2336 |
## determine number of cohorts |
|
2337 | 99x |
nCohorts <- length(unique(data@cohort)) |
2338 | ||
2339 |
## so can we stop? |
|
2340 | 99x |
doStop <- nCohorts >= stopping@nCohorts |
2341 | ||
2342 |
## generate message |
|
2343 | 99x |
text <- |
2344 | 99x |
paste( |
2345 | 99x |
"Number of cohorts is", |
2346 | 99x |
nCohorts, |
2347 | 99x |
"and thus", |
2348 | 99x |
ifelse(doStop, "reached", "below"), |
2349 | 99x |
"the prespecified minimum number", |
2350 | 99x |
stopping@nCohorts |
2351 |
) |
|
2352 | ||
2353 |
## return both |
|
2354 | 99x |
return(structure(doStop, |
2355 | 99x |
message = text, |
2356 | 99x |
report_label = stopping@report_label |
2357 |
)) |
|
2358 |
} |
|
2359 |
) |
|
2360 | ||
2361 |
## -------------------------------------------------- |
|
2362 |
## Stopping based on minimum number of patients |
|
2363 |
## -------------------------------------------------- |
|
2364 | ||
2365 |
##' @describeIn stopTrial Stop based on minimum number of patients |
|
2366 |
##' |
|
2367 |
##' @example examples/Rules-method-stopTrial-StoppingMinPatients.R |
|
2368 |
setMethod("stopTrial", |
|
2369 |
signature = |
|
2370 |
signature( |
|
2371 |
stopping = "StoppingMinPatients", |
|
2372 |
dose = "ANY", |
|
2373 |
samples = "ANY", |
|
2374 |
model = "ANY", |
|
2375 |
data = "Data" |
|
2376 |
), |
|
2377 |
def = |
|
2378 |
function(stopping, dose, samples, model, data, ...) { |
|
2379 |
## so can we stop? |
|
2380 | 293x |
doStop <- data@nObs >= stopping@nPatients |
2381 | ||
2382 |
## generate message |
|
2383 | 293x |
text <- |
2384 | 293x |
paste( |
2385 | 293x |
"Number of patients is", |
2386 | 293x |
data@nObs, |
2387 | 293x |
"and thus", |
2388 | 293x |
ifelse(doStop, "reached", "below"), |
2389 | 293x |
"the prespecified minimum number", |
2390 | 293x |
stopping@nPatients |
2391 |
) |
|
2392 | ||
2393 |
## return both |
|
2394 | 293x |
return(structure(doStop, |
2395 | 293x |
message = text, |
2396 | 293x |
report_label = stopping@report_label |
2397 |
)) |
|
2398 |
} |
|
2399 |
) |
|
2400 | ||
2401 |
# nolint end |
|
2402 | ||
2403 |
## StoppingTargetProb ---- |
|
2404 | ||
2405 |
#' @describeIn stopTrial Stop based on probability of target tox interval |
|
2406 |
#' |
|
2407 |
#' @aliases stopTrial-StoppingTargetProb |
|
2408 |
#' @example examples/Rules-method-stopTrial-StoppingTargetProb.R |
|
2409 |
setMethod( |
|
2410 |
f = "stopTrial", |
|
2411 |
signature = |
|
2412 |
signature( |
|
2413 |
stopping = "StoppingTargetProb", |
|
2414 |
dose = "numeric", |
|
2415 |
samples = "Samples", |
|
2416 |
model = "GeneralModel", |
|
2417 |
data = "ANY" |
|
2418 |
), |
|
2419 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
2420 |
# Compute probability to be in target interval. |
|
2421 | 106x |
prob_target <- ifelse( |
2422 | 106x |
is.na(dose), |
2423 | 106x |
0, |
2424 | 106x |
mean( |
2425 | 106x |
prob(dose = dose, model, samples, ...) >= stopping@target[1] & |
2426 | 106x |
prob(dose = dose, model, samples, ...) <= stopping@target[2] |
2427 |
) |
|
2428 |
) |
|
2429 | ||
2430 | 106x |
do_stop <- prob_target >= stopping@prob |
2431 | ||
2432 | 106x |
msg <- paste( |
2433 | 106x |
"Probability for target toxicity is", |
2434 | 106x |
round(prob_target * 100), |
2435 | 106x |
"% for dose", |
2436 | 106x |
dose, |
2437 | 106x |
"and thus", |
2438 | 106x |
ifelse(do_stop, "above", "below"), |
2439 | 106x |
"the required", |
2440 | 106x |
round(stopping@prob * 100), |
2441 |
"%" |
|
2442 |
) |
|
2443 | ||
2444 | 106x |
structure( |
2445 | 106x |
do_stop, |
2446 | 106x |
message = msg, |
2447 | 106x |
report_label = stopping@report_label |
2448 |
) |
|
2449 |
} |
|
2450 |
) |
|
2451 | ||
2452 |
# nolint start |
|
2453 | ||
2454 |
## -------------------------------------------------- |
|
2455 |
## Stopping based on MTD distribution |
|
2456 |
## -------------------------------------------------- |
|
2457 | ||
2458 |
##' @describeIn stopTrial Stop based on MTD distribution |
|
2459 |
##' |
|
2460 |
##' @example examples/Rules-method-stopTrial-StoppingMTDdistribution.R |
|
2461 |
setMethod("stopTrial", |
|
2462 |
signature = |
|
2463 |
signature( |
|
2464 |
stopping = "StoppingMTDdistribution", |
|
2465 |
dose = "numeric", |
|
2466 |
samples = "Samples", |
|
2467 |
model = "GeneralModel", |
|
2468 |
data = "ANY" |
|
2469 |
), |
|
2470 |
def = |
|
2471 |
function(stopping, dose, samples, model, data, ...) { |
|
2472 |
## First, generate the MTD samples. |
|
2473 | ||
2474 |
## add prior data and samples to the |
|
2475 |
## function environment so that they |
|
2476 |
## can be used. |
|
2477 | 751x |
mtdSamples <- dose( |
2478 | 751x |
x = stopping@target, |
2479 | 751x |
model, |
2480 | 751x |
samples, |
2481 |
... |
|
2482 |
) |
|
2483 | ||
2484 |
## what is the absolute threshold? |
|
2485 | 751x |
absThresh <- stopping@thresh * dose |
2486 | ||
2487 |
## what is the probability to be above this dose? |
|
2488 | 751x |
prob <- ifelse( |
2489 | 751x |
is.na(absThresh), |
2490 | 751x |
0, |
2491 | 751x |
mean(mtdSamples > absThresh) |
2492 |
) |
|
2493 | ||
2494 |
## so can we stop? |
|
2495 | 751x |
doStop <- prob >= stopping@prob |
2496 | ||
2497 |
## generate message |
|
2498 | 751x |
text <- |
2499 | 751x |
paste( |
2500 | 751x |
"Probability of MTD above", |
2501 | 751x |
round(stopping@thresh * 100), |
2502 | 751x |
"% of current dose", |
2503 | 751x |
dose, |
2504 | 751x |
"is", |
2505 | 751x |
round(prob * 100), |
2506 | 751x |
"% and thus", |
2507 | 751x |
ifelse(doStop, "greater than or equal to", "strictly less than"), |
2508 | 751x |
"the required", |
2509 | 751x |
round(stopping@prob * 100), |
2510 |
"%" |
|
2511 |
) |
|
2512 | ||
2513 |
## return both |
|
2514 | 751x |
return(structure(doStop, |
2515 | 751x |
message = text, |
2516 | 751x |
report_label = stopping@report_label |
2517 |
)) |
|
2518 |
} |
|
2519 |
) |
|
2520 | ||
2521 |
# nolint end |
|
2522 | ||
2523 |
## StoppingMTDCV ---- |
|
2524 | ||
2525 |
#' @rdname stopTrial |
|
2526 |
#' |
|
2527 |
#' @description Stopping rule based precision of the MTD estimation. |
|
2528 |
#' The trial is stopped, when the MTD can be estimated with sufficient precision. |
|
2529 |
#' The criteria is based on the robust coefficient of variation (CV) calculated |
|
2530 |
#' from the posterior distribution. |
|
2531 |
#' The robust CV is defined `mad(MTD) / median(MTD)`, where `mad` is the median |
|
2532 |
#' absolute deviation. |
|
2533 |
#' |
|
2534 |
#' @aliases stopTrial-StoppingMTDCV |
|
2535 |
#' @example examples/Rules-method-stopTrial-StoppingMTDCV.R |
|
2536 |
#' @export |
|
2537 |
#' |
|
2538 |
setMethod( |
|
2539 |
f = "stopTrial", |
|
2540 |
signature = signature( |
|
2541 |
stopping = "StoppingMTDCV", |
|
2542 |
dose = "numeric", |
|
2543 |
samples = "Samples", |
|
2544 |
model = "GeneralModel", |
|
2545 |
data = "ANY" |
|
2546 |
), |
|
2547 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
2548 | 3x |
mtd_samples <- dose( |
2549 | 3x |
x = stopping@target, |
2550 | 3x |
model, |
2551 | 3x |
samples, |
2552 |
... |
|
2553 |
) |
|
2554 |
# CV of MTD expressed as percentage, derived based on MTD posterior samples. |
|
2555 | 3x |
mtd_cv <- (mad(mtd_samples) / median(mtd_samples)) * 100 |
2556 | 3x |
do_stop <- mtd_cv <= stopping@thresh_cv |
2557 | ||
2558 | 3x |
msg <- paste( |
2559 | 3x |
"CV of MTD is", |
2560 | 3x |
round(mtd_cv), |
2561 | 3x |
"% and thus", |
2562 | 3x |
ifelse(do_stop, "below", "above"), |
2563 | 3x |
"the required precision threshold of", |
2564 | 3x |
round(stopping@thresh_cv), |
2565 |
"%" |
|
2566 |
) |
|
2567 | ||
2568 | 3x |
structure( |
2569 | 3x |
do_stop, |
2570 | 3x |
message = msg, |
2571 | 3x |
report_label = stopping@report_label |
2572 |
) |
|
2573 |
} |
|
2574 |
) |
|
2575 | ||
2576 | ||
2577 |
## StoppingLowestDoseHSRBeta ---- |
|
2578 | ||
2579 |
#' @rdname stopTrial |
|
2580 |
#' |
|
2581 |
#' @description Stopping based based on the lowest non placebo dose. The trial is |
|
2582 |
#' stopped when the lowest non placebo dose meets the Hard |
|
2583 |
#' Safety Rule, i.e. it is deemed to be overly toxic. Stopping is based on the |
|
2584 |
#' observed data at the lowest dose level using a Bin-Beta model |
|
2585 |
#' based on DLT probability. |
|
2586 |
#' |
|
2587 |
#' @aliases stopTrial-StoppingLowestDoseHSRBeta |
|
2588 |
#' @example examples/Rules-method-stopTrial-StoppingLowestDoseHSRBeta.R |
|
2589 |
#' @export |
|
2590 |
setMethod( |
|
2591 |
f = "stopTrial", |
|
2592 |
signature = signature( |
|
2593 |
stopping = "StoppingLowestDoseHSRBeta", |
|
2594 |
dose = "numeric", |
|
2595 |
samples = "Samples" |
|
2596 |
), |
|
2597 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
2598 |
# Actual number of patients at first active dose. |
|
2599 | 7x |
n <- sum(data@x == data@doseGrid[data@placebo + 1]) |
2600 | ||
2601 |
# Determine toxicity probability of the first active dose. |
|
2602 | 7x |
tox_prob_first_dose <- |
2603 | 7x |
if (n > 0) { |
2604 | 5x |
x <- sum(data@y[which(data@x == data@doseGrid[data@placebo + 1])]) |
2605 | 5x |
pbeta(stopping@target, x + stopping@a, n - x + stopping@b, lower.tail = FALSE) |
2606 |
} else { |
|
2607 | 2x |
0 |
2608 |
} |
|
2609 | ||
2610 | 7x |
do_stop <- tox_prob_first_dose > stopping@prob |
2611 | ||
2612 |
# generate message |
|
2613 | 7x |
msg <- if (n == 0) { |
2614 | 2x |
"Lowest active dose not tested, stopping rule not applied." |
2615 |
} else { |
|
2616 | 5x |
paste( |
2617 | 5x |
"Probability that the lowest active dose of ", |
2618 | 5x |
data@doseGrid[data@placebo + 1], |
2619 | 5x |
" being toxic based on posterior Beta distribution using a Beta(", |
2620 | 5x |
stopping@a, ",", stopping@b, ") prior is ", |
2621 | 5x |
round(tox_prob_first_dose * 100), |
2622 | 5x |
"% and thus ", |
2623 | 5x |
ifelse(do_stop, "above", "below"), |
2624 | 5x |
" the required ", |
2625 | 5x |
round(stopping@prob * 100), |
2626 | 5x |
"% threshold.", |
2627 | 5x |
sep = "" |
2628 |
) |
|
2629 |
} |
|
2630 | ||
2631 | 7x |
structure( |
2632 | 7x |
do_stop, |
2633 | 7x |
message = msg, |
2634 | 7x |
report_label = stopping@report_label |
2635 |
) |
|
2636 |
} |
|
2637 |
) |
|
2638 | ||
2639 |
## StoppingTargetBiomarker ---- |
|
2640 | ||
2641 |
#' @describeIn stopTrial Stop based on probability of targeting biomarker |
|
2642 |
#' |
|
2643 |
#' @aliases stopTrial-StoppingTargetBiomarker |
|
2644 |
#' @example examples/Rules-method-stopTrial-StoppingTargetBiomarker.R |
|
2645 |
setMethod( |
|
2646 |
f = "stopTrial", |
|
2647 |
signature = signature( |
|
2648 |
stopping = "StoppingTargetBiomarker", |
|
2649 |
dose = "numeric", |
|
2650 |
samples = "Samples", |
|
2651 |
model = "DualEndpoint", |
|
2652 |
data = "ANY" |
|
2653 |
), |
|
2654 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
2655 |
# Compute the target biomarker prob at this dose. |
|
2656 |
# Get the biomarker level samples at the dose grid points. |
|
2657 | 46x |
biom_level_samples <- biomarker(xLevel = seq_len(data@nGrid), model, samples, ...) |
2658 | ||
2659 |
# If target is relative to maximum. |
|
2660 | 46x |
if (stopping@is_relative) { |
2661 |
# If there is an 'Emax' parameter, target biomarker level will |
|
2662 |
# be relative to 'Emax', otherwise will be relative to the |
|
2663 |
# maximum biomarker level achieved in the given dose range. |
|
2664 | 46x |
if ("Emax" %in% names(samples)) { |
2665 |
# For each sample, look which dose is maximizing the |
|
2666 |
# simultaneous probability to be in the target biomarker |
|
2667 |
# range and below overdose toxicity. |
|
2668 | ! |
prob_target <- numeric(ncol(biom_level_samples)) |
2669 | ! |
prob_target <- sapply( |
2670 | ! |
seq(1, ncol(biom_level_samples)), |
2671 | ! |
function(x) { |
2672 | ! |
sum(biom_level_samples[, x] >= stopping@target[1] * samples@data$Emax & |
2673 | ! |
biom_level_samples[, x] <= stopping@target[2] * samples@data$Emax) / |
2674 | ! |
nrow(biom_level_samples) |
2675 |
} |
|
2676 |
) |
|
2677 |
} else { |
|
2678 |
# For each sample, look which was the minimum dose giving |
|
2679 |
# relative target level. |
|
2680 | 46x |
targetIndex <- apply( |
2681 | 46x |
biom_level_samples, 1L, |
2682 | 46x |
function(x) { |
2683 | 21704x |
rnx <- range(x) |
2684 | 21704x |
min(which( |
2685 | 21704x |
(x >= stopping@target[1] * diff(rnx) + rnx[1]) & |
2686 | 21704x |
(x <= stopping@target[2] * diff(rnx) + rnx[1] + 1e-10) |
2687 |
)) |
|
2688 |
} |
|
2689 |
) |
|
2690 | 46x |
prob_target <- numeric(ncol(biom_level_samples)) |
2691 | 46x |
tab <- table(targetIndex) |
2692 | 46x |
prob_target[as.numeric(names(tab))] <- tab |
2693 | 46x |
prob_target <- prob_target / nrow(biom_level_samples) |
2694 |
} |
|
2695 |
} else { |
|
2696 |
# Otherwise the target is absolute. |
|
2697 |
# For each sample, look which dose is maximizing the |
|
2698 |
# simultaneous probability to be in the target biomarker |
|
2699 |
# range and below overdose toxicity. |
|
2700 | ! |
prob_target <- numeric(ncol(biom_level_samples)) |
2701 | ! |
prob_target <- sapply( |
2702 | ! |
seq(1, ncol(biom_level_samples)), |
2703 | ! |
function(x) { |
2704 | ! |
sum(biom_level_samples[, x] >= stopping@target[1] & biom_level_samples[, x] <= stopping@target[2]) / |
2705 | ! |
nrow(biom_level_samples) |
2706 |
} |
|
2707 |
) |
|
2708 |
} |
|
2709 | ||
2710 | 46x |
prob_target <- ifelse( |
2711 | 46x |
is.na(dose), |
2712 | 46x |
0, |
2713 | 46x |
prob_target[which(data@doseGrid == dose)] |
2714 |
) |
|
2715 | ||
2716 | 46x |
do_stop <- prob_target >= stopping@prob |
2717 | ||
2718 | 46x |
msg <- paste( |
2719 | 46x |
"Probability for target biomarker is", |
2720 | 46x |
round(prob_target * 100), |
2721 | 46x |
"% for dose", |
2722 | 46x |
dose, |
2723 | 46x |
"and thus", |
2724 | 46x |
ifelse(do_stop, "above", "below"), |
2725 | 46x |
"the required", |
2726 | 46x |
round(stopping@prob * 100), |
2727 |
"%" |
|
2728 |
) |
|
2729 | ||
2730 | 46x |
structure( |
2731 | 46x |
do_stop, |
2732 | 46x |
message = msg, |
2733 | 46x |
report_label = stopping@report_label |
2734 |
) |
|
2735 |
} |
|
2736 |
) |
|
2737 | ||
2738 |
## StoppingSpecificDose ---- |
|
2739 | ||
2740 |
#' @describeIn stopTrial if Stopping rule is met for specific dose of the planned |
|
2741 |
#' dose grid and not just for the default next best dose. |
|
2742 |
#' |
|
2743 |
#' @aliases stopTrial-StoppingSpecificDose |
|
2744 |
#' |
|
2745 |
#' @export |
|
2746 |
#' @example examples/Rules-method-stopTrial-StoppingSpecificDose.R |
|
2747 |
#' |
|
2748 |
setMethod( |
|
2749 |
f = "stopTrial", |
|
2750 |
signature = signature( |
|
2751 |
stopping = "StoppingSpecificDose", |
|
2752 |
dose = "numeric", |
|
2753 |
samples = "ANY", |
|
2754 |
model = "ANY", |
|
2755 |
data = "Data" |
|
2756 |
), |
|
2757 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
2758 |
# Specific dose must be a part of the dose grid. |
|
2759 | 6x |
assert_subset(x = stopping@dose@.Data, choices = data@doseGrid) |
2760 | ||
2761 |
# Evaluate the original (wrapped) stopping rule at the specific dose. |
|
2762 | 6x |
result <- stopTrial( |
2763 | 6x |
stopping = stopping@rule, |
2764 | 6x |
dose = stopping@dose@.Data, |
2765 | 6x |
samples = samples, |
2766 | 6x |
model = model, |
2767 | 6x |
data = data, |
2768 |
... |
|
2769 |
) |
|
2770 |
# Correct the text message from the original stopping rule. |
|
2771 | 6x |
attr(result, "message") <- gsub( |
2772 | 6x |
pattern = "next best", |
2773 | 6x |
replacement = "specific", |
2774 | 6x |
x = attr(result, "message"), |
2775 | 6x |
ignore.case = TRUE |
2776 |
) |
|
2777 | ||
2778 | 6x |
attr(result, "report_label") <- stopping@report_label |
2779 | ||
2780 | 6x |
result |
2781 |
} |
|
2782 |
) |
|
2783 | ||
2784 |
# nolint start |
|
2785 | ||
2786 |
## -------------------------------------------------- |
|
2787 |
## Stopping when the highest dose is reached |
|
2788 |
## -------------------------------------------------- |
|
2789 | ||
2790 |
##' @describeIn stopTrial Stop when the highest dose is reached |
|
2791 |
##' |
|
2792 |
##' @example examples/Rules-method-stopTrial-StoppingHighestDose.R |
|
2793 |
setMethod("stopTrial", |
|
2794 |
signature = |
|
2795 |
signature( |
|
2796 |
stopping = "StoppingHighestDose", |
|
2797 |
dose = "numeric", |
|
2798 |
samples = "ANY", |
|
2799 |
model = "ANY", |
|
2800 |
data = "Data" |
|
2801 |
), |
|
2802 |
def = |
|
2803 |
function(stopping, dose, samples, model, data, ...) { |
|
2804 | 34x |
isHighestDose <- ifelse( |
2805 | 34x |
is.na(dose), |
2806 | 34x |
FALSE, |
2807 | 34x |
(dose == data@doseGrid[data@nGrid]) |
2808 |
) |
|
2809 | 34x |
return(structure(isHighestDose, |
2810 | 34x |
message = |
2811 | 34x |
paste( |
2812 | 34x |
"Next best dose is", dose, "and thus", |
2813 | 34x |
ifelse(isHighestDose, "the", |
2814 | 34x |
"not the" |
2815 |
), |
|
2816 | 34x |
"highest dose" |
2817 |
), |
|
2818 | 34x |
report_label = stopping@report_label |
2819 |
)) |
|
2820 |
} |
|
2821 |
) |
|
2822 | ||
2823 |
## StoppingOrdinal ---- |
|
2824 | ||
2825 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
2826 |
#' |
|
2827 |
#' @description `r lifecycle::badge("experimental")` |
|
2828 |
#' |
|
2829 |
#' @aliases stopTrial-StoppingOrdinal |
|
2830 |
#' @example examples/Rules-method-stopTrial-StoppingOrdinal.R |
|
2831 |
#' |
|
2832 |
setMethod( |
|
2833 |
f = "stopTrial", |
|
2834 |
signature = signature( |
|
2835 |
stopping = "StoppingOrdinal", |
|
2836 |
dose = "numeric", |
|
2837 |
samples = "ANY", |
|
2838 |
model = "LogisticLogNormalOrdinal", |
|
2839 |
data = "DataOrdinal" |
|
2840 |
), |
|
2841 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
2842 | 20x |
stopTrial( |
2843 | 20x |
stopping = stopping@rule, |
2844 | 20x |
dose = dose, |
2845 | 20x |
samples = h_convert_ordinal_samples(samples, stopping@grade), |
2846 | 20x |
model = h_convert_ordinal_model(model, stopping@grade), |
2847 | 20x |
data = h_convert_ordinal_data(data, stopping@grade), |
2848 |
... |
|
2849 |
) |
|
2850 |
} |
|
2851 |
) |
|
2852 | ||
2853 |
#' @describeIn stopTrial Stop based on value returned by next best dose. |
|
2854 |
#' |
|
2855 |
#' @description `r lifecycle::badge("experimental")` |
|
2856 |
#' |
|
2857 |
#' @aliases stopTrial-StoppingOrdinal |
|
2858 |
#' @example examples/Rules-method-stopTrial-StoppingOrdinal.R |
|
2859 |
#' |
|
2860 |
setMethod( |
|
2861 |
f = "stopTrial", |
|
2862 |
signature = signature( |
|
2863 |
stopping = "StoppingOrdinal", |
|
2864 |
dose = "numeric", |
|
2865 |
samples = "ANY", |
|
2866 |
model = "ANY", |
|
2867 |
data = "ANY" |
|
2868 |
), |
|
2869 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
2870 | ! |
stop( |
2871 | ! |
paste0( |
2872 | ! |
"StoppingOrdinal objects can only be used with LogisticLogNormalOrdinal ", |
2873 | ! |
"models and DataOrdinal data objects. In this case, the model is a '", |
2874 | ! |
class(model), |
2875 | ! |
"' object and the data is in a ", |
2876 | ! |
class(data), |
2877 | ! |
" object." |
2878 |
) |
|
2879 |
) |
|
2880 |
} |
|
2881 |
) |
|
2882 | ||
2883 |
## StoppingExternal ---- |
|
2884 | ||
2885 |
#' @describeIn stopTrial Stop based on an external flag. |
|
2886 |
#' |
|
2887 |
#' @description `r lifecycle::badge("experimental")` |
|
2888 |
#' @param external (`flag`)\cr whether to stop based on the external |
|
2889 |
#' result or not. |
|
2890 |
#' |
|
2891 |
#' @aliases stopTrial-StoppingExternal |
|
2892 |
#' @example examples/Rules-method-stopTrial-StoppingExternal.R |
|
2893 |
#' |
|
2894 |
setMethod( |
|
2895 |
f = "stopTrial", |
|
2896 |
signature = signature( |
|
2897 |
stopping = "StoppingExternal", |
|
2898 |
dose = "numeric", |
|
2899 |
samples = "ANY", |
|
2900 |
model = "ANY", |
|
2901 |
data = "ANY" |
|
2902 |
), |
|
2903 |
definition = function(stopping, dose, samples, model, data, external, ...) { |
|
2904 | 6x |
assert_flag(external) |
2905 | ||
2906 | 6x |
msg <- paste( |
2907 | 6x |
"Based on external result", |
2908 | 6x |
ifelse(external, "stop", "continue") |
2909 |
) |
|
2910 | ||
2911 | 6x |
structure( |
2912 | 6x |
external, |
2913 | 6x |
message = msg, |
2914 | 6x |
report_label = stopping@report_label |
2915 |
) |
|
2916 |
} |
|
2917 |
) |
|
2918 | ||
2919 | ||
2920 |
## ============================================================ |
|
2921 | ||
2922 |
## -------------------------------------------------- |
|
2923 |
## "MAX" combination of cohort size rules |
|
2924 |
## -------------------------------------------------- |
|
2925 | ||
2926 |
##' "MAX" combination of cohort size rules |
|
2927 |
##' |
|
2928 |
##' This function combines cohort size rules by taking |
|
2929 |
##' the maximum of all sizes. |
|
2930 |
##' |
|
2931 |
##' @param \dots Objects of class \code{\linkS4class{CohortSize}} |
|
2932 |
##' @return the combination as an object of class |
|
2933 |
##' \code{\linkS4class{CohortSizeMax}} |
|
2934 |
##' |
|
2935 |
##' @seealso \code{\link{minSize}} |
|
2936 |
##' @export |
|
2937 |
##' @keywords methods |
|
2938 |
setGeneric("maxSize", |
|
2939 |
def = |
|
2940 |
function(...) { |
|
2941 |
## there should be no default method, |
|
2942 |
## therefore just forward to next method! |
|
2943 | 30x |
standardGeneric("maxSize") |
2944 |
}, |
|
2945 |
valueClass = "CohortSizeMax" |
|
2946 |
) |
|
2947 | ||
2948 |
##' @describeIn maxSize The method combining cohort size rules by taking maximum |
|
2949 |
##' @example examples/Rules-method-maxSize.R |
|
2950 |
setMethod("maxSize", |
|
2951 |
"CohortSize", |
|
2952 |
def = |
|
2953 |
function(...) { |
|
2954 | 30x |
CohortSizeMax(list(...)) |
2955 |
} |
|
2956 |
) |
|
2957 | ||
2958 |
## -------------------------------------------------- |
|
2959 |
## "MIN" combination of cohort size rules |
|
2960 |
## -------------------------------------------------- |
|
2961 | ||
2962 |
##' "MIN" combination of cohort size rules |
|
2963 |
##' |
|
2964 |
##' This function combines cohort size rules by taking |
|
2965 |
##' the minimum of all sizes. |
|
2966 |
##' |
|
2967 |
##' @param \dots Objects of class \code{\linkS4class{CohortSize}} |
|
2968 |
##' @return the combination as an object of class |
|
2969 |
##' \code{\linkS4class{CohortSizeMin}} |
|
2970 |
##' |
|
2971 |
##' @seealso \code{\link{maxSize}} |
|
2972 |
##' @export |
|
2973 |
##' @keywords methods |
|
2974 |
setGeneric("minSize", |
|
2975 |
def = |
|
2976 |
function(...) { |
|
2977 |
## there should be no default method, |
|
2978 |
## therefore just forward to next method! |
|
2979 | 1x |
standardGeneric("minSize") |
2980 |
}, |
|
2981 |
valueClass = "CohortSizeMin" |
|
2982 |
) |
|
2983 | ||
2984 |
##' @describeIn minSize The method combining cohort size rules by taking minimum |
|
2985 |
##' @example examples/Rules-method-minSize.R |
|
2986 |
setMethod("minSize", |
|
2987 |
"CohortSize", |
|
2988 |
def = |
|
2989 |
function(...) { |
|
2990 | 1x |
CohortSizeMin(list(...)) |
2991 |
} |
|
2992 |
) |
|
2993 | ||
2994 |
# size ---- |
|
2995 | ||
2996 |
## CohortSizeRange ---- |
|
2997 | ||
2998 |
#' @describeIn size Determines the size of the next cohort based on the range |
|
2999 |
#' into which the next dose falls into. |
|
3000 |
#' |
|
3001 |
#' @param dose the next dose. |
|
3002 |
#' @param data the data input, an object of class [`Data`]. |
|
3003 |
#' |
|
3004 |
#' @aliases size-CohortSizeRange |
|
3005 |
#' @example examples/Rules-method-size-CohortSizeRange.R |
|
3006 |
#' |
|
3007 |
setMethod( |
|
3008 |
f = "size", |
|
3009 |
signature = signature( |
|
3010 |
object = "CohortSizeRange" |
|
3011 |
), |
|
3012 |
definition = function(object, dose, data) { |
|
3013 |
# If the recommended next dose is NA, don't check it and return 0. |
|
3014 | 82x |
if (is.na(dose)) { |
3015 | 1x |
return(0L) |
3016 |
} |
|
3017 | 81x |
assert_class(data, "Data") |
3018 | ||
3019 |
# Determine in which interval the next dose is. |
|
3020 | 81x |
interval <- findInterval(x = dose, vec = object@intervals) |
3021 | 81x |
object@cohort_size[interval] |
3022 |
} |
|
3023 |
) |
|
3024 | ||
3025 |
## CohortSizeDLT ---- |
|
3026 | ||
3027 |
#' @describeIn size Determines the size of the next cohort based on the number |
|
3028 |
#' of DLTs so far. |
|
3029 |
#' |
|
3030 |
#' @param dose the next dose. |
|
3031 |
#' @param data the data input, an object of class [`Data`]. |
|
3032 |
#' |
|
3033 |
#' @aliases size-CohortSizeDLT |
|
3034 |
#' @example examples/Rules-method-size-CohortSizeDLT.R |
|
3035 |
#' |
|
3036 |
setMethod( |
|
3037 |
f = "size", |
|
3038 |
signature = signature( |
|
3039 |
object = "CohortSizeDLT" |
|
3040 |
), |
|
3041 |
definition = function(object, dose, data) { |
|
3042 |
# If the recommended next dose is NA, don't check it and return 0. |
|
3043 | 53x |
if (is.na(dose)) { |
3044 | 1x |
return(0L) |
3045 |
} |
|
3046 | 52x |
assert_class(data, "Data") |
3047 | ||
3048 |
# Determine how many DLTs have occurred so far. |
|
3049 | 52x |
dlt_happened <- sum(data@y) |
3050 | ||
3051 |
# Determine in which interval this is. |
|
3052 | 52x |
interval <- findInterval(x = dlt_happened, vec = object@intervals) |
3053 | 52x |
object@cohort_size[interval] |
3054 |
} |
|
3055 |
) |
|
3056 | ||
3057 |
## CohortSizeMax ---- |
|
3058 | ||
3059 |
#' @describeIn size Determines the size of the next cohort based on maximum of |
|
3060 |
#' multiple cohort size rules. |
|
3061 |
#' |
|
3062 |
#' @param dose the next dose. |
|
3063 |
#' @param data the data input, an object of class [`Data`]. |
|
3064 |
#' |
|
3065 |
#' @aliases size-CohortSizeMax |
|
3066 |
#' @example examples/Rules-method-size-CohortSizeMax.R |
|
3067 |
#' |
|
3068 |
setMethod( |
|
3069 |
f = "size", |
|
3070 |
signature = signature( |
|
3071 |
object = "CohortSizeMax" |
|
3072 |
), |
|
3073 |
definition = function(object, dose, data) { |
|
3074 |
# If the recommended next dose is NA, don't check it and return 0. |
|
3075 | 24x |
if (is.na(dose)) { |
3076 | 1x |
return(0L) |
3077 |
} |
|
3078 | 23x |
assert_class(data, "Data") |
3079 | ||
3080 |
# Evaluate the individual cohort size rules in the list. |
|
3081 | 23x |
individual_results <- sapply( |
3082 | 23x |
object@cohort_sizes, |
3083 | 23x |
size, |
3084 | 23x |
dose = dose, |
3085 | 23x |
data = data |
3086 |
) |
|
3087 |
# The overall result. |
|
3088 | 23x |
max(individual_results) |
3089 |
} |
|
3090 |
) |
|
3091 | ||
3092 |
## CohortSizeMin ---- |
|
3093 | ||
3094 |
#' @describeIn size Determines the size of the next cohort based on minimum of |
|
3095 |
#' multiple cohort size rules. |
|
3096 |
#' |
|
3097 |
#' @param dose the next dose. |
|
3098 |
#' @param data the data input, an object of class [`Data`]. |
|
3099 |
#' |
|
3100 |
#' @aliases size-CohortSizeMin |
|
3101 |
#' @example examples/Rules-method-size-CohortSizeMin.R |
|
3102 |
#' |
|
3103 |
setMethod( |
|
3104 |
f = "size", |
|
3105 |
signature = signature( |
|
3106 |
object = "CohortSizeMin" |
|
3107 |
), |
|
3108 |
definition = function(object, dose, data) { |
|
3109 |
# If the recommended next dose is NA, don't check it and return 0. |
|
3110 | 21x |
if (is.na(dose)) { |
3111 | 1x |
return(0L) |
3112 |
} |
|
3113 | 20x |
assert_class(data, "Data") |
3114 | ||
3115 |
# Evaluate the individual cohort size rules in the list. |
|
3116 | 20x |
individual_results <- sapply( |
3117 | 20x |
object@cohort_sizes, |
3118 | 20x |
size, |
3119 | 20x |
dose = dose, |
3120 | 20x |
data = data |
3121 |
) |
|
3122 |
# The overall result. |
|
3123 | 20x |
min(individual_results) |
3124 |
} |
|
3125 |
) |
|
3126 | ||
3127 |
## CohortSizeConst ---- |
|
3128 | ||
3129 |
#' @describeIn size Constant cohort size. |
|
3130 |
#' |
|
3131 |
#' @param dose the next dose. |
|
3132 |
#' @param ... not used. |
|
3133 |
#' |
|
3134 |
#' @aliases size-CohortSizeConst |
|
3135 |
#' @example examples/Rules-method-size-CohortSizeConst.R |
|
3136 |
#' |
|
3137 |
setMethod( |
|
3138 |
f = "size", |
|
3139 |
signature = signature( |
|
3140 |
object = "CohortSizeConst" |
|
3141 |
), |
|
3142 |
definition = function(object, dose, ...) { |
|
3143 |
# If the recommended next dose is NA, don't check it and return 0. |
|
3144 | 292x |
if (is.na(dose)) { |
3145 | 1x |
0L |
3146 |
} else { |
|
3147 | 291x |
object@size |
3148 |
} |
|
3149 |
} |
|
3150 |
) |
|
3151 | ||
3152 |
## CohortSizeParts ---- |
|
3153 | ||
3154 |
#' @describeIn size Determines the size of the next cohort based on the parts. |
|
3155 |
#' |
|
3156 |
#' @param dose the next dose. |
|
3157 |
#' @param data the data input, an object of class [`Data`]. |
|
3158 |
#' |
|
3159 |
#' @aliases size-CohortSizeParts |
|
3160 |
#' @example examples/Rules-method-size-CohortSizeParts.R |
|
3161 |
#' |
|
3162 |
setMethod( |
|
3163 |
f = "size", |
|
3164 |
signature = signature( |
|
3165 |
object = "CohortSizeParts" |
|
3166 |
), |
|
3167 |
definition = function(object, dose, data) { |
|
3168 |
# If the recommended next dose is NA, don't check it and return 0. |
|
3169 | 12x |
if (is.na(dose)) { |
3170 | 2x |
return(0L) |
3171 |
} else { |
|
3172 | 10x |
assert_class(data, "DataParts") |
3173 | 10x |
object@cohort_sizes[data@nextPart] |
3174 |
} |
|
3175 |
} |
|
3176 |
) |
|
3177 | ||
3178 |
## CohortSizeOrdinal ---- |
|
3179 | ||
3180 |
#' @describeIn size Determines the size of the next cohort in a ordinal CRM trial. |
|
3181 |
#' |
|
3182 |
#' @param dose (`numeric`) the next dose. |
|
3183 |
#' @param data the data input, an object of class [`DataOrdinal`]. |
|
3184 |
#' |
|
3185 |
#' @aliases size-CohortSizeOrdinal |
|
3186 |
#' @example examples/Rules-method-size-CohortSizeOrdinal.R |
|
3187 |
#' |
|
3188 |
setMethod( |
|
3189 |
f = "size", |
|
3190 |
signature = signature( |
|
3191 |
object = "CohortSizeOrdinal" |
|
3192 |
), |
|
3193 |
definition = function(object, dose, data, ...) { |
|
3194 |
# Validate |
|
3195 | ! |
assert_numeric(dose, len = 1, lower = 0) |
3196 | ! |
assert_class(data, "DataOrdinal") |
3197 |
# Execute |
|
3198 | ||
3199 | ! |
size( |
3200 | ! |
object@rule, |
3201 | ! |
dose = dose, |
3202 | ! |
data = h_convert_ordinal_data(data, object@grade), |
3203 |
... |
|
3204 |
) |
|
3205 |
} |
|
3206 |
) |
|
3207 | ||
3208 |
## ------------------------------------------------------------------------------------------------ |
|
3209 |
## Stopping based on a target ratio of the upper to the lower 95% credibility interval |
|
3210 |
## ------------------------------------------------------------------------------------------------ |
|
3211 |
##' @describeIn stopTrial Stop based on 'StoppingTDCIRatio' class when |
|
3212 |
##' reaching the target ratio of the upper to the lower 95% credibility |
|
3213 |
##' interval of the estimate (TDtargetEndOfTrial). This is a stopping rule which incorporate only |
|
3214 |
##' DLE responses and DLE samples are given |
|
3215 |
##' |
|
3216 |
##' @example examples/Rules-method-stopTrialCITDsamples.R |
|
3217 |
##' |
|
3218 |
##' @export |
|
3219 |
##' @keywords methods |
|
3220 |
setMethod( |
|
3221 |
f = "stopTrial", |
|
3222 |
signature = signature( |
|
3223 |
stopping = "StoppingTDCIRatio", |
|
3224 |
dose = "ANY", |
|
3225 |
samples = "Samples", |
|
3226 |
model = "ModelTox", |
|
3227 |
data = "ANY" |
|
3228 |
), |
|
3229 |
definition = function(stopping, dose, samples, model, data, ...) { |
|
3230 | 481x |
assert_probability(stopping@prob_target) |
3231 | ||
3232 | 481x |
dose_target_samples <- dose( |
3233 | 481x |
x = stopping@prob_target, |
3234 | 481x |
model = model, |
3235 | 481x |
samples = samples, |
3236 |
... |
|
3237 |
) |
|
3238 |
# 95% credibility interval. |
|
3239 | 481x |
dose_target_ci <- quantile(dose_target_samples, probs = c(0.025, 0.975)) |
3240 | 481x |
dose_target_ci_ratio <- dose_target_ci[[2]] / dose_target_ci[[1]] |
3241 | ||
3242 | 481x |
do_stop <- dose_target_ci_ratio <= stopping@target_ratio |
3243 | 481x |
text <- paste0( |
3244 | 481x |
"95% CI is (", |
3245 | 481x |
paste(dose_target_ci, collapse = ", "), |
3246 | 481x |
"), Ratio = ", |
3247 | 481x |
round(dose_target_ci_ratio, 4), |
3248 | 481x |
" is ", |
3249 | 481x |
ifelse(do_stop, "less than or equal to ", "greater than "), |
3250 | 481x |
"target_ratio = ", stopping@target_ratio |
3251 |
) |
|
3252 | 481x |
structure(do_stop, |
3253 | 481x |
message = text, |
3254 | 481x |
report_label = stopping@report_label |
3255 |
) |
|
3256 |
} |
|
3257 |
) |
|
3258 | ||
3259 |
## ---------------------------------------------------------------------------------------------- |
|
3260 |
## Stopping based on a target ratio of the upper to the lower 95% credibility interval |
|
3261 |
## ------------------------------------------------------------------------------------------------ |
|
3262 |
##' @describeIn stopTrial Stop based on 'StoppingTDCIRatio' class |
|
3263 |
##' when reaching the target ratio of the upper to the lower 95% credibility |
|
3264 |
##' interval of the estimate (TDtargetEndOfTrial). This is a stopping rule which incorporate only |
|
3265 |
##' DLE responses and no DLE samples are involved |
|
3266 |
##' @example examples/Rules-method-stopTrialCITD.R |
|
3267 |
setMethod("stopTrial", |
|
3268 |
signature = |
|
3269 |
signature( |
|
3270 |
stopping = "StoppingTDCIRatio", |
|
3271 |
dose = "ANY", |
|
3272 |
samples = "missing", |
|
3273 |
model = "ModelTox", |
|
3274 |
data = "ANY" |
|
3275 |
), |
|
3276 |
def = |
|
3277 |
function(stopping, dose, model, data, ...) { |
|
3278 | 480x |
assert_probability(stopping@prob_target) |
3279 | ||
3280 | 480x |
prob_target <- stopping@prob_target |
3281 | 480x |
dose_target_samples <- dose(x = prob_target, model = model, ...) |
3282 |
## Find the variance of the log of the dose_target_samples(eta) |
|
3283 | 480x |
M1 <- matrix(c(-1 / (model@phi2), -(log(prob_target / (1 - prob_target)) - model@phi1) / (model@phi2)^2), 1, 2) |
3284 | 480x |
M2 <- model@Pcov |
3285 | 480x |
varEta <- as.vector(M1 %*% M2 %*% t(M1)) |
3286 | ||
3287 |
## Find the upper and lower limit of the 95% credibility interval |
|
3288 | 480x |
CI <- exp(log(dose_target_samples) + c(-1, 1) * 1.96 * sqrt(varEta)) |
3289 | 480x |
ratio <- CI[2] / CI[1] |
3290 | ||
3291 |
## so can we stop? |
|
3292 | 480x |
doStop <- ratio <= stopping@target_ratio |
3293 |
## generate message |
|
3294 | 480x |
text <- paste( |
3295 | 480x |
"95% CI is (", round(CI[1], 4), ",", round(CI[2], 4), "), Ratio =", round(ratio, 4), "is ", ifelse(doStop, "is less than or equal to", "greater than"), |
3296 | 480x |
"target_ratio =", stopping@target_ratio |
3297 |
) |
|
3298 |
## return both |
|
3299 | 480x |
return(structure( |
3300 | 480x |
doStop, |
3301 | 480x |
message = text, |
3302 | 480x |
report_label = stopping@report_label |
3303 |
)) |
|
3304 |
} |
|
3305 |
) |
|
3306 | ||
3307 |
## -------------------------------------------------------------------------------------------------- |
|
3308 |
## Stopping based on a target ratio of the upper to the lower 95% credibility interval |
|
3309 |
## ------------------------------------------------------------------------------------------------ |
|
3310 |
##' @describeIn stopTrial Stop based on reaching the target ratio of the upper to the lower 95% credibility |
|
3311 |
##' interval of the estimate (the minimum of Gstar and TDtargetEndOfTrial). This is a stopping rule which |
|
3312 |
##' incorporate DLE and efficacy responses and DLE and efficacy samples are also used. |
|
3313 |
##' |
|
3314 |
##' @param TDderive the function which derives from the input, a vector of the posterior samples called |
|
3315 |
##' \code{TDsamples} of the dose |
|
3316 |
##' which has the probability of the occurrence of DLE equals to either the targetDuringTrial or |
|
3317 |
##' targetEndOfTrial, the final next best TDtargetDuringTrial (the dose with probability of the |
|
3318 |
##' occurrence of DLE equals to the targetDuringTrial)and TDtargetEndOfTrial estimate. |
|
3319 |
##' @param Effmodel the efficacy model of \code{\linkS4class{ModelEff}} class object |
|
3320 |
##' @param Effsamples the efficacy samples of \code{\linkS4class{Samples}} class object |
|
3321 |
##' @param Gstarderive the function which derives from the input, a vector of the posterior Gstar (the dose |
|
3322 |
##' which gives the maximum gain value) samples |
|
3323 |
##' called \code{Gstarsamples}, the final next best Gstar estimate. |
|
3324 |
##' |
|
3325 |
##' @example examples/Rules-method-stopTrialCIMaxGainSamples.R |
|
3326 |
setMethod("stopTrial", |
|
3327 |
signature = |
|
3328 |
signature( |
|
3329 |
stopping = "StoppingMaxGainCIRatio", |
|
3330 |
dose = "ANY", |
|
3331 |
samples = "Samples", |
|
3332 |
model = "ModelTox", |
|
3333 |
data = "DataDual" |
|
3334 |
), |
|
3335 |
def = |
|
3336 |
function(stopping, dose, samples, model, data, TDderive, Effmodel, Effsamples, Gstarderive, ...) { |
|
3337 | ! |
prob_target <- stopping@prob_target |
3338 | ||
3339 |
## checks |
|
3340 | ! |
assert_probability(prob_target) |
3341 | ! |
stopifnot(is(Effmodel, "ModelEff")) |
3342 | ! |
stopifnot(is(Effsamples, "Samples")) |
3343 | ! |
stopifnot(is.function(TDderive)) |
3344 | ! |
stopifnot(is.function(Gstarderive)) |
3345 | ||
3346 |
## find the TDtarget End of Trial samples |
|
3347 | ! |
TDtargetEndOfTrialSamples <- dose( |
3348 | ! |
x = prob_target, |
3349 | ! |
model = model, |
3350 | ! |
samples = samples, |
3351 |
... |
|
3352 |
) |
|
3353 |
## Find the TDtarget End of trial estimate |
|
3354 | ! |
TDtargetEndOfTrialEstimate <- TDderive(TDtargetEndOfTrialSamples) |
3355 | ||
3356 |
## Find the gain value samples then the GstarSamples |
|
3357 | ! |
points <- data@doseGrid |
3358 | ||
3359 | ! |
GainSamples <- matrix( |
3360 | ! |
nrow = size(samples), |
3361 | ! |
ncol = length(points) |
3362 |
) |
|
3363 | ||
3364 |
## evaluate the probs, for all gain samples. |
|
3365 | ! |
for (i in seq_along(points)) |
3366 |
{ |
|
3367 |
## Now we want to evaluate for the |
|
3368 |
## following dose: |
|
3369 | ! |
GainSamples[, i] <- gain( |
3370 | ! |
dose = points[i], |
3371 | ! |
model, |
3372 | ! |
samples, |
3373 | ! |
Effmodel, |
3374 | ! |
Effsamples, |
3375 |
... |
|
3376 |
) |
|
3377 |
} |
|
3378 | ||
3379 |
## Find the maximum gain value samples |
|
3380 | ! |
MaxGainSamples <- apply(GainSamples, 1, max) |
3381 | ||
3382 |
## Obtain Gstar samples, samples for the dose level which gives the maximum gain value |
|
3383 | ! |
IndexG <- apply(GainSamples, 1, which.max) |
3384 | ! |
GstarSamples <- data@doseGrid[IndexG] |
3385 | ||
3386 |
## Find the Gstar estimate |
|
3387 | ||
3388 | ! |
Gstar <- Gstarderive(GstarSamples) |
3389 |
## Find the 95% credibility interval of Gstar and its ratio of the upper to the lower limit |
|
3390 | ! |
CIGstar <- quantile(GstarSamples, probs = c(0.025, 0.975)) |
3391 | ! |
ratioGstar <- as.numeric(CIGstar[2] / CIGstar[1]) |
3392 | ||
3393 |
## Find the 95% credibility interval of TDtargetEndOfTrial and its ratio of the upper to the lower limit |
|
3394 | ! |
CITDEOT <- quantile(TDtargetEndOfTrialSamples, probs = c(0.025, 0.975)) |
3395 | ! |
ratioTDEOT <- as.numeric(CITDEOT[2] / CITDEOT[1]) |
3396 | ||
3397 |
## Find which is smaller (TDtargetEndOfTrialEstimate or Gstar) |
|
3398 | ||
3399 | ! |
if (TDtargetEndOfTrialEstimate <= Gstar) { |
3400 |
## Find the upper and lower limit of the 95% credibility interval and its ratio of the smaller |
|
3401 | ! |
CI <- CITDEOT |
3402 | ! |
ratio <- ratioTDEOT |
3403 | ! |
chooseTD <- TRUE |
3404 |
} else { |
|
3405 | ! |
CI <- CIGstar |
3406 | ! |
ratio <- ratioGstar |
3407 | ! |
chooseTD <- FALSE |
3408 |
} |
|
3409 | ||
3410 |
## so can we stop? |
|
3411 | ! |
doStop <- ratio <= stopping@target_ratio |
3412 |
## generate message |
|
3413 | ! |
text1 <- paste( |
3414 | ! |
"Gstar estimate is", round(Gstar, 4), "with 95% CI (", round(CIGstar[1], 4), ",", round(CIGstar[2], 4), |
3415 | ! |
") and its ratio =", |
3416 | ! |
round(ratioGstar, 4) |
3417 |
) |
|
3418 | ! |
text2 <- paste( |
3419 | ! |
"TDtargetEndOfTrial estimate is ", round(TDtargetEndOfTrialEstimate, 4), |
3420 | ! |
"with 95% CI (", round(CITDEOT[1], 4), ",", round(CITDEOT[2], 4), ") and its ratio=", |
3421 | ! |
round(ratioTDEOT, 4) |
3422 |
) |
|
3423 | ! |
text3 <- paste( |
3424 | ! |
ifelse(chooseTD, "TDtargetEndOfTrial estimate", "Gstar estimate"), "is smaller with ratio =", |
3425 | ! |
round(ratio, 4), " which is ", ifelse(doStop, "is less than or equal to", "greater than"), |
3426 | ! |
"target_ratio =", stopping@target_ratio |
3427 |
) |
|
3428 | ! |
text <- c(text1, text2, text3) |
3429 |
## return both |
|
3430 | ! |
return(structure(doStop, |
3431 | ! |
message = text, |
3432 | ! |
report_label = stopping@report_label |
3433 |
)) |
|
3434 |
} |
|
3435 |
) |
|
3436 | ||
3437 |
## ----------------------------------------------------------------------------------------------- |
|
3438 |
## Stopping based on a target ratio of the upper to the lower 95% credibility interval |
|
3439 |
## -------------------------------------------------------------------------------------------- |
|
3440 |
##' @describeIn stopTrial Stop based on reaching the target ratio of the upper to the lower 95% credibility |
|
3441 |
##' interval of the estimate (the minimum of Gstar and TDtargetEndOfTrial). This is a stopping rule which |
|
3442 |
##' incorporate DLE and efficacy responses without DLE and efficacy samples involved. |
|
3443 |
##' @example examples/Rules-method-stopTrialCIMaxGain.R |
|
3444 |
setMethod("stopTrial", |
|
3445 |
signature = |
|
3446 |
signature( |
|
3447 |
stopping = "StoppingMaxGainCIRatio", |
|
3448 |
dose = "ANY", |
|
3449 |
samples = "missing", |
|
3450 |
model = "ModelTox", |
|
3451 |
data = "DataDual" |
|
3452 |
), |
|
3453 |
def = |
|
3454 |
function(stopping, dose, model, data, Effmodel, ...) { |
|
3455 | ! |
prob_target <- stopping@prob_target |
3456 | ||
3457 |
## checks |
|
3458 | ! |
assert_probability(prob_target) |
3459 | ! |
stopifnot(is(Effmodel, "ModelEff")) |
3460 | ||
3461 | ||
3462 |
## find the TDtarget End of Trial |
|
3463 | ! |
TDtargetEndOfTrial <- dose( |
3464 | ! |
x = prob_target, |
3465 | ! |
model = model, |
3466 |
... |
|
3467 |
) |
|
3468 | ||
3469 |
## Find the dose with maximum gain value |
|
3470 | ! |
Gainfun <- function(DOSE) { |
3471 | ! |
-gain(DOSE, model_dle = model, model_eff = Effmodel, ...) |
3472 |
} |
|
3473 | ||
3474 |
# if(data@placebo) { |
|
3475 |
# n <- length(data@doseGrid) |
|
3476 |
# LowestDose <- sort(data@doseGrid)[2]} else { |
|
3477 | ! |
LowestDose <- min(data@doseGrid) |
3478 |
# } |
|
3479 | ||
3480 | ! |
Gstar <- (optim(LowestDose, Gainfun, method = "L-BFGS-B", lower = LowestDose, upper = max(data@doseGrid))$par) |
3481 | ! |
MaxGain <- -(optim(LowestDose, Gainfun, method = "L-BFGS-B", lower = LowestDose, upper = max(data@doseGrid))$value) |
3482 | ! |
if (data@placebo) { |
3483 | ! |
logGstar <- log(Gstar + Effmodel@const) |
3484 |
} else { |
|
3485 | ! |
logGstar <- log(Gstar) |
3486 |
} |
|
3487 | ||
3488 | ||
3489 | ||
3490 |
## From paper (Yeung et. al 2015) |
|
3491 | ||
3492 | ! |
meanEffGstar <- Effmodel@theta1 + Effmodel@theta2 * log(logGstar) |
3493 | ||
3494 | ! |
denom <- (model@phi2) * (meanEffGstar) * (1 + logGstar * model@phi2) |
3495 | ||
3496 | ! |
dgphi1 <- -(meanEffGstar * logGstar * model@phi2 - Effmodel@theta2) / denom |
3497 | ||
3498 | ! |
dgphi2 <- -((meanEffGstar) * logGstar + meanEffGstar * (logGstar)^2 * model@phi2 - Effmodel@theta2 * logGstar) / denom |
3499 | ||
3500 | ! |
dgtheta1 <- -(logGstar * model@phi2) / denom |
3501 | ||
3502 | ! |
dgtheta2 <- -(logGstar * exp(model@phi1 + model@phi2 * logGstar) * model@phi2 * log(logGstar) - 1 - exp(model@phi1 + model@phi2 * logGstar)) / denom |
3503 | ||
3504 |
# DLEPRO <- exp(model@phi1+model@phi2*logGstar) |
|
3505 | ||
3506 |
# dgphi1 <- Effmodel@theta2*DLEPRO - logGstar*model@phi2*meanEffGstar*DLEPRO |
|
3507 | ||
3508 |
# dgphi2 <- logGstar*DLEPRO *(Effmodel@theta2-(meanEffGstar)+model@phi2) |
|
3509 | ||
3510 |
# dgtheta1 <- -logGstar*DLEPRO*model@phi2 |
|
3511 | ||
3512 |
# dgtheta2 <- 1+DLEPRO-logGstar*DLEPRO*model@phi2*log(logGstar) |
|
3513 | ||
3514 | ! |
deltaG <- matrix(c(dgphi1, dgphi2, dgtheta1, dgtheta2), 4, 1) |
3515 | ||
3516 | ||
3517 |
## Find the variance of the log Gstar |
|
3518 |
## First find the covariance matrix of all the parameters, phi1, phi2, theta1 and theta2 |
|
3519 |
## such that phi1 and phi2 and independent of theta1 and theta2 |
|
3520 | ! |
emptyMatrix <- matrix(0, 2, 2) |
3521 | ! |
covBETA <- cbind(rbind(model@Pcov, emptyMatrix), rbind(emptyMatrix, Effmodel@Pcov)) |
3522 | ! |
varlogGstar <- as.vector(t(deltaG) %*% covBETA %*% deltaG) |
3523 | ||
3524 |
## Find the upper and lower limit of the 95% credibility interval of Gstar |
|
3525 | ! |
CIGstar <- exp(logGstar + c(-1, 1) * 1.96 * sqrt(varlogGstar)) |
3526 | ||
3527 |
## The ratio of the upper to the lower 95% credibility interval |
|
3528 | ! |
ratioGstar <- CIGstar[2] / CIGstar[1] |
3529 | ||
3530 |
## Find the variance of the log of the TDtargetEndOfTrial(eta) |
|
3531 | ! |
M1 <- matrix(c(-1 / (model@phi2), -(log(prob_target / (1 - prob_target)) - model@phi1) / (model@phi2)^2), 1, 2) |
3532 | ! |
M2 <- model@Pcov |
3533 | ||
3534 | ! |
varEta <- as.vector(M1 %*% M2 %*% t(M1)) |
3535 | ||
3536 |
## Find the upper and lower limit of the 95% credibility interval of |
|
3537 |
## TDtargetEndOfTrial |
|
3538 | ! |
CITDEOT <- exp(log(TDtargetEndOfTrial) + c(-1, 1) * 1.96 * sqrt(varEta)) |
3539 | ||
3540 |
## The ratio of the upper to the lower 95% credibility interval |
|
3541 | ! |
ratioTDEOT <- CITDEOT[2] / CITDEOT[1] |
3542 | ||
3543 | ! |
if (Gstar <= TDtargetEndOfTrial) { |
3544 | ! |
chooseTD <- FALSE |
3545 | ! |
CI <- c() |
3546 | ! |
CI[2] <- CIGstar[2] |
3547 | ! |
CI[1] <- CIGstar[1] |
3548 | ! |
ratio <- ratioGstar |
3549 |
} else { |
|
3550 | ! |
chooseTD <- TRUE |
3551 | ! |
CI <- c() |
3552 | ! |
CI[2] <- CITDEOT[2] |
3553 | ! |
CI[1] <- CITDEOT[1] |
3554 | ! |
ratio <- ratioTDEOT |
3555 |
} |
|
3556 |
## so can we stop? |
|
3557 | ! |
doStop <- ratio <= stopping@target_ratio |
3558 |
## generate message |
|
3559 | ||
3560 | ! |
text1 <- paste( |
3561 | ! |
"Gstar estimate is", round(Gstar, 4), "with 95% CI (", round(CIGstar[1], 4), ",", round(CIGstar[2], 4), |
3562 | ! |
") and its ratio =", |
3563 | ! |
round(ratioGstar, 4) |
3564 |
) |
|
3565 | ! |
text2 <- paste( |
3566 | ! |
"TDtargetEndOfTrial estimate is ", round(TDtargetEndOfTrial, 4), |
3567 | ! |
"with 95% CI (", round(CITDEOT[1], 4), ",", round(CITDEOT[2], 4), ") and its ratio=", |
3568 | ! |
round(ratioTDEOT, 4) |
3569 |
) |
|
3570 | ! |
text3 <- paste( |
3571 | ! |
ifelse(chooseTD, "TDatrgetEndOfTrial estimate", "Gstar estimate"), "is smaller with ratio =", |
3572 | ! |
round(ratio, 4), "which is ", ifelse(doStop, "is less than or equal to", "greater than"), |
3573 | ! |
"target_ratio =", stopping@target_ratio |
3574 |
) |
|
3575 | ! |
text <- c(text1, text2, text3) |
3576 |
## return both |
|
3577 | ! |
return(structure(doStop, |
3578 | ! |
message = text, |
3579 | ! |
report_label = stopping@report_label |
3580 |
)) |
|
3581 |
} |
|
3582 |
) |
|
3583 | ||
3584 | ||
3585 |
## ============================================================ |
|
3586 | ||
3587 |
## ----------------------------------------------------- |
|
3588 |
## Determine the safety window length of the next cohort |
|
3589 |
## ----------------------------------------------------- |
|
3590 | ||
3591 |
##' Determine the safety window length of the next cohort |
|
3592 |
##' |
|
3593 |
##' This function determines the safety window length of |
|
3594 |
##' the next cohort. |
|
3595 |
##' |
|
3596 |
##' @param safetyWindow The rule, an object of class |
|
3597 |
##' \code{\linkS4class{SafetyWindow}} |
|
3598 |
##' @param size The next cohort size |
|
3599 |
##' @param data The data input, an object of class \code{\linkS4class{DataDA}} |
|
3600 |
##' @param \dots additional arguments |
|
3601 |
##' |
|
3602 |
##' @return the `windowLength` as a list of safety window parameters |
|
3603 |
##' (`gap`, `follow`, `follow_min`) |
|
3604 |
##' |
|
3605 |
##' @export |
|
3606 |
##' @keywords methods |
|
3607 |
setGeneric("windowLength", |
|
3608 |
def = |
|
3609 |
function(safetyWindow, size, ...) { |
|
3610 |
## there should be no default method, |
|
3611 |
## therefore just forward to next method! |
|
3612 | 63x |
standardGeneric("windowLength") |
3613 |
}, |
|
3614 |
valueClass = "list" |
|
3615 |
) |
|
3616 | ||
3617 | ||
3618 |
## ============================================================ |
|
3619 | ||
3620 |
## -------------------------------------------------- |
|
3621 |
## The SafetyWindowSize method |
|
3622 |
## -------------------------------------------------- |
|
3623 | ||
3624 |
##' @describeIn windowLength Determine safety window length based |
|
3625 |
##' on the cohort size |
|
3626 |
##' |
|
3627 |
##' @example examples/Rules-method-windowLength-SafetyWindowSize.R |
|
3628 |
setMethod("windowLength", |
|
3629 |
signature = |
|
3630 |
signature( |
|
3631 |
safetyWindow = "SafetyWindowSize", |
|
3632 |
size = "ANY" |
|
3633 |
), |
|
3634 |
def = |
|
3635 |
function(safetyWindow, size, data, ...) { |
|
3636 |
## determine in which interval the next size is |
|
3637 | 30x |
interval <- |
3638 | 30x |
findInterval( |
3639 | 30x |
x = size, |
3640 | 30x |
vec = safetyWindow@size |
3641 |
) |
|
3642 | ||
3643 |
## so the safety window length is |
|
3644 | 30x |
patientGap <- head(c( |
3645 | 30x |
0, safetyWindow@gap[[interval]], |
3646 | 30x |
rep(tail(safetyWindow@gap[[interval]], 1), 100) |
3647 | 30x |
), size) |
3648 | 30x |
patientFollow <- safetyWindow@follow |
3649 | 30x |
patientFollowMin <- safetyWindow@follow_min |
3650 | ||
3651 | 30x |
ret <- list(patientGap = patientGap, patientFollow = patientFollow, patientFollowMin = patientFollowMin) |
3652 | ||
3653 | 30x |
return(ret) |
3654 |
} |
|
3655 |
) |
|
3656 | ||
3657 |
## ============================================================ |
|
3658 | ||
3659 |
## -------------------------------------------------- |
|
3660 |
## Constant safety window length |
|
3661 |
## -------------------------------------------------- |
|
3662 | ||
3663 |
##' @describeIn windowLength Constant safety window length |
|
3664 |
##' @example examples/Rules-method-windowLength-SafetyWindowConst.R |
|
3665 |
setMethod("windowLength", |
|
3666 |
signature = |
|
3667 |
signature( |
|
3668 |
safetyWindow = "SafetyWindowConst", |
|
3669 |
size = "ANY" |
|
3670 |
), |
|
3671 |
def = |
|
3672 |
function(safetyWindow, size, ...) { |
|
3673 |
## first element should be 0. |
|
3674 | 33x |
patientGap <- head(c( |
3675 | 33x |
0, safetyWindow@gap, |
3676 | 33x |
rep(tail(safetyWindow@gap, 1), 100) |
3677 | 33x |
), size) |
3678 | 33x |
patientFollow <- safetyWindow@follow |
3679 | 33x |
patientFollowMin <- safetyWindow@follow_min |
3680 | ||
3681 | 33x |
ret <- list( |
3682 | 33x |
patientGap = patientGap, |
3683 | 33x |
patientFollow = patientFollow, |
3684 | 33x |
patientFollowMin = patientFollowMin |
3685 |
) |
|
3686 | ||
3687 | 33x |
return(ret) |
3688 |
} |
|
3689 |
) |
|
3690 | ||
3691 |
# nolint end |
|
3692 | ||
3693 |
# tidy ---- |
|
3694 | ||
3695 |
## tidy-IncrementsRelative ---- |
|
3696 | ||
3697 |
#' @rdname tidy |
|
3698 |
#' @aliases tidy-IncrementsRelative |
|
3699 |
#' @example examples/Rules-method-tidyIncrementsRelative.R |
|
3700 |
#' @export |
|
3701 |
setMethod( |
|
3702 |
f = "tidy", |
|
3703 |
signature = signature(x = "IncrementsRelative"), |
|
3704 |
definition = function(x, ...) { |
|
3705 |
h_tidy_all_slots(x) %>% |
|
3706 |
dplyr::bind_cols() %>% |
|
3707 |
h_range_to_minmax(.data$intervals) %>% |
|
3708 |
dplyr::filter(max > 0) %>% |
|
3709 |
tibble::add_column(increment = x@increments) %>% |
|
3710 |
h_tidy_class(x) |
|
3711 |
} |
|
3712 |
) |
|
3713 | ||
3714 |
## tidy-CohortSizeDLT ---- |
|
3715 | ||
3716 |
#' @rdname tidy |
|
3717 |
#' @aliases tidy-CohortSizeDLT |
|
3718 |
#' @example examples/Rules-method-tidyCohortSizeDLT.R |
|
3719 |
#' @export |
|
3720 |
setMethod( |
|
3721 |
f = "tidy", |
|
3722 |
signature = signature(x = "CohortSizeDLT"), |
|
3723 |
definition = function(x, ...) { |
|
3724 | 52x |
h_tidy_all_slots(x) %>% |
3725 | 52x |
dplyr::bind_cols() %>% |
3726 | 52x |
h_range_to_minmax(.data$intervals) %>% |
3727 | 52x |
dplyr::filter(max > 0) %>% |
3728 | 52x |
tibble::add_column(cohort_size = x@cohort_size) %>% |
3729 | 52x |
h_tidy_class(x) |
3730 |
} |
|
3731 |
) |
|
3732 | ||
3733 |
## tidy-CohortSizeMin ---- |
|
3734 | ||
3735 |
#' @rdname tidy |
|
3736 |
#' @aliases tidy-CohortSizeMin |
|
3737 |
#' @example examples/Rules-method-tidyCohortSizeMin.R |
|
3738 |
#' @export |
|
3739 |
setMethod( |
|
3740 |
f = "tidy", |
|
3741 |
signature = signature(x = "CohortSizeMin"), |
|
3742 |
definition = function(x, ...) { |
|
3743 | 3x |
callNextMethod() %>% h_tidy_class(x) |
3744 |
} |
|
3745 |
) |
|
3746 | ||
3747 |
## tidy-CohortSizeMax ---- |
|
3748 | ||
3749 |
#' @rdname tidy |
|
3750 |
#' @aliases tidy-CohortSizeMax |
|
3751 |
#' @example examples/Rules-method-tidyCohortSizeMax.R |
|
3752 |
#' @export |
|
3753 |
setMethod( |
|
3754 |
f = "tidy", |
|
3755 |
signature = signature(x = "CohortSizeMax"), |
|
3756 |
definition = function(x, ...) { |
|
3757 | 12x |
callNextMethod() %>% h_tidy_class(x) |
3758 |
} |
|
3759 |
) |
|
3760 | ||
3761 |
## tidy-CohortSizeRange ---- |
|
3762 | ||
3763 |
#' @rdname tidy |
|
3764 |
#' @aliases tidy-CohortSizeRange |
|
3765 |
#' @example examples/Rules-method-tidyCohortSizeRange.R |
|
3766 |
#' @export |
|
3767 |
setMethod( |
|
3768 |
f = "tidy", |
|
3769 |
signature = signature(x = "CohortSizeRange"), |
|
3770 |
definition = function(x, ...) { |
|
3771 | 58x |
h_tidy_all_slots(x) %>% |
3772 | 58x |
dplyr::bind_cols() %>% |
3773 | 58x |
h_range_to_minmax(.data$intervals) %>% |
3774 | 58x |
dplyr::filter(max > 0) %>% |
3775 | 58x |
tibble::add_column(cohort_size = x@cohort_size) %>% |
3776 | 58x |
h_tidy_class(x) |
3777 |
} |
|
3778 |
) |
|
3779 | ||
3780 |
## tidy-CohortSizeParts ---- |
|
3781 | ||
3782 |
#' @rdname tidy |
|
3783 |
#' @aliases tidy-CohortSizeParts |
|
3784 |
#' @example examples/Rules-method-tidyCohortSizeParts.R |
|
3785 |
#' @export |
|
3786 |
setMethod( |
|
3787 |
f = "tidy", |
|
3788 |
signature = signature(x = "CohortSizeParts"), |
|
3789 |
definition = function(x, ...) { |
|
3790 | 3x |
tibble::tibble( |
3791 | 3x |
part = seq_along(x@cohort_sizes), |
3792 | 3x |
cohort_size = x@cohort_sizes |
3793 |
) %>% |
|
3794 | 3x |
h_tidy_class(x) |
3795 |
} |
|
3796 |
) |
|
3797 | ||
3798 |
## tidy-IncrementsMin ---- |
|
3799 | ||
3800 |
#' @rdname tidy |
|
3801 |
#' @aliases tidy-IncrementsMin |
|
3802 |
#' @example examples/Rules-method-tidyIncrementsMin.R |
|
3803 |
#' @export |
|
3804 |
setMethod( |
|
3805 |
f = "tidy", |
|
3806 |
signature = signature(x = "IncrementsMin"), |
|
3807 |
definition = function(x, ...) { |
|
3808 | 3x |
callNextMethod() %>% h_tidy_class(x) |
3809 |
} |
|
3810 |
) |
|
3811 | ||
3812 |
## tidy-IncrementsRelative ---- |
|
3813 | ||
3814 |
#' @rdname tidy |
|
3815 |
#' @aliases tidy-IncrementsRelative |
|
3816 |
#' @example examples/Rules-method-tidyIncrementsRelative.R |
|
3817 |
#' @export |
|
3818 |
setMethod( |
|
3819 |
f = "tidy", |
|
3820 |
signature = signature(x = "IncrementsRelative"), |
|
3821 |
definition = function(x, ...) { |
|
3822 | 94x |
h_tidy_all_slots(x) %>% |
3823 | 94x |
h_range_to_minmax(.data$intervals) %>% |
3824 | 94x |
dplyr::filter(dplyr::row_number() > 1) %>% |
3825 | 94x |
tibble::add_column(increment = x@increments) %>% |
3826 | 94x |
h_tidy_class(x) |
3827 |
} |
|
3828 |
) |
|
3829 | ||
3830 |
## tidy-IncrementsRelativeDLT ---- |
|
3831 | ||
3832 |
#' @rdname tidy |
|
3833 |
#' @aliases tidy-IncrementsRelativeDLT |
|
3834 |
#' @example examples/Rules-method-tidyIncrementsRelativeDLT.R |
|
3835 |
#' @export |
|
3836 |
setMethod( |
|
3837 |
f = "tidy", |
|
3838 |
signature = signature(x = "IncrementsRelativeDLT"), |
|
3839 |
definition = function(x, ...) { |
|
3840 | 20x |
h_tidy_all_slots(x) %>% |
3841 | 20x |
h_range_to_minmax(.data$intervals) %>% |
3842 | 20x |
dplyr::filter(dplyr::row_number() > 1) %>% |
3843 | 20x |
tibble::add_column(increment = x@increments) %>% |
3844 | 20x |
h_tidy_class(x) |
3845 |
} |
|
3846 |
) |
|
3847 | ||
3848 |
## tidy-IncrementsRelative ---- |
|
3849 | ||
3850 |
#' @rdname tidy |
|
3851 |
#' @aliases tidy-IncrementsRelativeParts |
|
3852 |
#' @example examples/Rules-method-tidyIncrementsRelativeParts.R |
|
3853 |
#' @export |
|
3854 |
setMethod( |
|
3855 |
f = "tidy", |
|
3856 |
signature = signature(x = "IncrementsRelativeParts"), |
|
3857 |
definition = function(x, ...) { |
|
3858 | 3x |
slot_names <- slotNames(x) |
3859 | 3x |
rv <- list() |
3860 | 3x |
for (nm in slot_names) { |
3861 | 12x |
if (!is.function(slot(x, nm))) { |
3862 | 12x |
rv[[nm]] <- h_tidy_slot(x, nm, ...) |
3863 |
} |
|
3864 |
} |
|
3865 |
# Column bind of all list elements have the same number of rows. |
|
3866 | 3x |
if (length(rv) > 1 & length(unique(sapply(rv, nrow))) == 1) { |
3867 | ! |
rv <- rv %>% dplyr::bind_cols() |
3868 |
} |
|
3869 | 3x |
rv <- rv %>% h_tidy_class(x) |
3870 | 3x |
if (length(rv) == 1) { |
3871 | ! |
rv[[names(rv)[1]]] %>% h_tidy_class(x) |
3872 |
} else { |
|
3873 | 3x |
rv |
3874 |
} |
|
3875 |
} |
|
3876 |
) |
|
3877 | ||
3878 |
## tidy-NextBestNCRM ---- |
|
3879 | ||
3880 |
#' @rdname tidy |
|
3881 |
#' @aliases tidy-NextBestNCRM |
|
3882 |
#' @example examples/Rules-method-tidyNextBestNCRM.R |
|
3883 |
#' @export |
|
3884 |
setMethod( |
|
3885 |
f = "tidy", |
|
3886 |
signature = signature(x = "NextBestNCRM"), |
|
3887 |
definition = function(x, ...) { |
|
3888 | 13x |
h_tidy_all_slots(x) %>% |
3889 | 13x |
dplyr::bind_cols() %>% |
3890 | 13x |
h_range_to_minmax(.data$target, range_min = 0, range_max = 1) %>% |
3891 | 13x |
add_column(max_prob = c(NA, NA, x@max_overdose_prob)) %>% |
3892 | 13x |
add_column(Range = c("Underdose", "Target", "Overdose"), .before = 1) %>% |
3893 | 13x |
h_tidy_class(x) |
3894 |
} |
|
3895 |
) |
|
3896 | ||
3897 |
## tidy-NextBestNCRMLoss ---- |
|
3898 | ||
3899 |
#' @rdname tidy |
|
3900 |
#' @aliases tidy-NextBestNCRMLoss |
|
3901 |
#' @example examples/Rules-method-tidyNextBestNCRMLoss.R |
|
3902 |
#' @export |
|
3903 |
setMethod( |
|
3904 |
f = "tidy", |
|
3905 |
signature = signature(x = "NextBestNCRMLoss"), |
|
3906 |
definition = function(x, ...) { |
|
3907 | 9x |
tibble( |
3908 | 9x |
Range = "Underdose", |
3909 | 9x |
Lower = 0, |
3910 | 9x |
Upper = x@target[1] |
3911 |
) %>% |
|
3912 | 9x |
dplyr::bind_rows( |
3913 | 9x |
lapply( |
3914 | 9x |
c("target", "overdose", "unacceptable"), |
3915 | 9x |
function(nm, obj) { |
3916 | 27x |
tibble::tibble( |
3917 | 27x |
Range = stringr::str_to_sentence(nm), |
3918 | 27x |
Lower = slot(obj, nm)[1], |
3919 | 27x |
Upper = slot(obj, nm)[2] |
3920 |
) |
|
3921 |
}, |
|
3922 | 9x |
obj = x |
3923 | 9x |
) %>% dplyr::bind_rows() |
3924 |
) %>% |
|
3925 | 9x |
add_column(LossCoefficient = x@losses) %>% |
3926 | 9x |
add_column(MaxOverdoseProb = x@max_overdose_prob) %>% |
3927 | 9x |
h_tidy_class(x) |
3928 |
} |
|
3929 |
) |
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 |
# Validate |
|
20 | 21x |
assert_flag(asis) |
21 | 19x |
assert_character(target_label, len = 1, any.missing = FALSE) |
22 | ||
23 | 19x |
tox_label <- h_prepare_labels(tox_label) |
24 |
# Execute |
|
25 | 19x |
rv <- paste0( |
26 | 19x |
"The dose level recommended for the next cohort will be selected as follows:\n\n", |
27 | 19x |
"- First, ", |
28 | 19x |
target_label, |
29 | 19x |
" of the posterior distribution of ", |
30 | 19x |
tox_label[1], |
31 | 19x |
" will be calculated for all dose levels that are eligible according to the ", |
32 | 19x |
" Increments rule.\n", |
33 | 19x |
"- Next, the \"target dose\" (which may not be part of the dose grid) for which ", |
34 | 19x |
target_label, |
35 | 19x |
" of the posterior distribution of ", |
36 | 19x |
tox_label[1], |
37 | 19x |
" is exactly equal to the target rate of ", |
38 | 19x |
x@target, |
39 | 19x |
" will be determined.\n", |
40 | 19x |
"- Finally, the dose level whose absolute distance from the target dose ", |
41 | 19x |
"is smallest will be selected as the recommended dose for the next cohort\n\n" |
42 |
) |
|
43 | ||
44 | 19x |
if (asis) { |
45 | 4x |
rv <- knitr::asis_output(rv) |
46 |
} |
|
47 | 19x |
rv |
48 |
} |
|
49 | ||
50 |
# NextBestNCRM ---- |
|
51 | ||
52 |
#' @description `r lifecycle::badge("experimental")` |
|
53 |
#' @inheritParams knit_print.StoppingTargetProb |
|
54 |
#' @rdname knit_print |
|
55 |
#' @export |
|
56 |
#' @method knit_print NextBestNCRM |
|
57 |
knit_print.NextBestNCRM <- function( |
|
58 |
x, |
|
59 |
..., |
|
60 |
tox_label = "toxicity", |
|
61 |
asis = TRUE) { |
|
62 |
# Validate |
|
63 | 32x |
assert_flag(asis) |
64 | 30x |
assert_character(tox_label, max.len = 2, any.missing = FALSE) |
65 | ||
66 |
# Execute |
|
67 | 30x |
rv <- paste0( |
68 | 30x |
"The dose recommended for the next cohort will be chosen in the following ", |
69 | 30x |
"way. First, doses that are ineligible according to the increments rule ", |
70 | 30x |
"will be discarded. Next, any dose for which the mean posterior probability of ", |
71 | 30x |
tox_label, |
72 | 30x |
" being in the overdose range - (", |
73 | 30x |
x@overdose[1], ", ", x@overdose[2], |
74 | 30x |
"] - is ", |
75 | 30x |
x@max_overdose_prob, |
76 | 30x |
" or more will also be discarded. Finally, the dose amongst those remaining ", |
77 | 30x |
"which has the highest chance that the mean posterior probability of ", |
78 | 30x |
tox_label, |
79 | 30x |
" is in the target ", |
80 | 30x |
tox_label, |
81 | 30x |
" range of ", |
82 | 30x |
x@target[1], |
83 | 30x |
" to ", |
84 | 30x |
x@target[2], |
85 | 30x |
" (inclusive) will be selected.\n\n" |
86 |
) |
|
87 | ||
88 | 30x |
if (asis) { |
89 | 2x |
rv <- knitr::asis_output(rv) |
90 |
} |
|
91 | 30x |
rv |
92 |
} |
|
93 | ||
94 |
# NextBestThreePlusThree ---- |
|
95 | ||
96 |
#' @description `r lifecycle::badge("experimental")` |
|
97 |
#' @param label (`character`)\cr The term used to label the participants. |
|
98 |
#' @param tox_label (`character`)\cr the term used to describe toxicity. See |
|
99 |
#' Usage Notes below. |
|
100 |
#' See Usage Notes below. |
|
101 |
#' @section Usage Notes: |
|
102 |
#' This section describes the use of `label` and `tox_label`, collectively |
|
103 |
#' referred to as `label`s. |
|
104 |
#' A `label` should be a scalar or a vector of length 2. If a scalar, it is |
|
105 |
#' converted by adding a second element that is equal to the first, suffixed by `s`. |
|
106 |
#' For example, `tox_label = "DLT"` becomes `tox_label = c("DLT", "DLTs")`. The |
|
107 |
#' first element of the vector is used to describe a count of 1. The second |
|
108 |
#' is used in all other cases. |
|
109 |
#' @rdname knit_print |
|
110 |
#' @export |
|
111 |
#' @method knit_print NextBestThreePlusThree |
|
112 |
knit_print.NextBestThreePlusThree <- function( |
|
113 |
x, |
|
114 |
..., |
|
115 |
tox_label = c("toxicity", "toxicities"), |
|
116 |
label = "participant", |
|
117 |
asis = TRUE) { |
|
118 |
# Validate |
|
119 | 14x |
assert_flag(asis) |
120 | ||
121 |
# Prepare |
|
122 | 12x |
tox_label <- h_prepare_labels(tox_label) |
123 | 12x |
label <- h_prepare_labels(label) |
124 | ||
125 |
# Execute |
|
126 | 12x |
rv <- paste0( |
127 | 12x |
"The dose recommended for the next cohort will be chosen using the \"Three ", |
128 | 12x |
"Plus Three\" rule.\n\n- If no ", |
129 | 12x |
tox_label[2], |
130 | 12x |
" have been reported at the current dose level, escalate by one dose level.\n", |
131 | 12x |
"- If the observed ", |
132 | 12x |
tox_label[1], |
133 | 12x |
" rate at the current dose level is exactly 1/3 and no more than three ", |
134 | 12x |
label[2], |
135 | 12x |
" treated at the current dose level are evaluable, remain at the current ", |
136 | 12x |
"dose level.\n", |
137 | 12x |
"- Otherwise, recommend that the trial stops and identify the MTD as dose ", |
138 | 12x |
"level immediately below the current one.\n\n" |
139 |
) |
|
140 | ||
141 | 12x |
if (asis) { |
142 | 2x |
rv <- knitr::asis_output(rv) |
143 |
} |
|
144 | 12x |
rv |
145 |
} |
|
146 | ||
147 |
# NextBestDualEndpoint ---- |
|
148 | ||
149 |
#' @description `r lifecycle::badge("experimental")` |
|
150 |
#' @inheritParams knit_print.StoppingTargetProb |
|
151 |
#' @param biomarker_label (`character`)\cr the term used to describe the biomarker |
|
152 |
#' @param biomarker_units (`character`)\cr the units in which the biomarker is |
|
153 |
#' measured |
|
154 |
#' @rdname knit_print |
|
155 |
#' @export |
|
156 |
#' @method knit_print NextBestDualEndpoint |
|
157 |
knit_print.NextBestDualEndpoint <- function( |
|
158 |
x, |
|
159 |
..., |
|
160 |
tox_label = "toxicity", |
|
161 |
biomarker_label = "the biomarker", |
|
162 |
biomarker_units = ifelse(x@target_relative, "%", ""), |
|
163 |
asis = TRUE) { |
|
164 | 14x |
assert_flag(asis) |
165 | 12x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
166 | 12x |
assert_character(biomarker_label, len = 1, any.missing = FALSE) |
167 | 12x |
assert_character(biomarker_units, len = 1, any.missing = FALSE) |
168 | ||
169 | 12x |
rv <- paste0( |
170 | 12x |
"The dose recommended for the next cohort will be chosen in the following ", |
171 | 12x |
"way. First, doses that are ineligible according to the increments rule ", |
172 | 12x |
"will be discarded. Next, any dose for which the mean posterior probability of ", |
173 | 12x |
tox_label, |
174 | 12x |
" being in the overdose range - (", |
175 | 12x |
x@overdose[1], ", ", x@overdose[2], |
176 | 12x |
"] - is ", |
177 | 12x |
x@max_overdose_prob, |
178 | 12x |
" or more will also be discarded. Finally, the dose amongst those remaining ", |
179 | 12x |
"which has the highest chance that the mean posterior probability that ", |
180 | 12x |
biomarker_label, |
181 | 12x |
" is in the target range for ", |
182 | 12x |
biomarker_label, |
183 | 12x |
", which is ", |
184 | 12x |
x@target[1], |
185 | 12x |
ifelse(x@target_relative, "", stringr::str_squish(paste0(" ", biomarker_units))), |
186 | 12x |
" to ", |
187 | 12x |
x@target[2], |
188 | 12x |
ifelse(x@target_relative, "", stringr::str_squish(paste0(" ", biomarker_units))), |
189 | 12x |
" (inclusive),", |
190 | 12x |
ifelse( |
191 | 12x |
x@target_relative, |
192 | 12x |
paste0(" of the maximum ", biomarker_label, " value"), |
193 |
"" |
|
194 |
), |
|
195 | 12x |
" will be selected, provided that this probability exceeds ", |
196 | 12x |
x@target_thresh, |
197 | 12x |
". If no dose meets this threshold, then the highest eligible dose will ", |
198 | 12x |
"be selected.\n\n" |
199 |
) |
|
200 | ||
201 | 12x |
if (asis) { |
202 | 2x |
rv <- knitr::asis_output(rv) |
203 |
} |
|
204 | 12x |
rv |
205 |
} |
|
206 | ||
207 |
# NextBestMinDist ---- |
|
208 | ||
209 |
#' @description `r lifecycle::badge("experimental")` |
|
210 |
#' @inheritParams knit_print.StoppingTargetProb |
|
211 |
#' @rdname knit_print |
|
212 |
#' @export |
|
213 |
#' @method knit_print NextBestMinDist |
|
214 |
knit_print.NextBestMinDist <- function( |
|
215 |
x, |
|
216 |
..., |
|
217 |
tox_label = "toxicity", |
|
218 |
asis = TRUE) { |
|
219 |
# Validate |
|
220 | 8x |
assert_flag(asis) |
221 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
222 | ||
223 |
# Execute |
|
224 | 6x |
rv <- paste0( |
225 | 6x |
"The dose recommended for the next cohort will be the one which is both ", |
226 | 6x |
"eligible and which has the smallest absolute difference between ", |
227 | 6x |
"its mean posterior estimate of the probability of ", |
228 | 6x |
tox_label, |
229 | 6x |
" and the target ", |
230 | 6x |
tox_label, |
231 | 6x |
" rate [", |
232 | 6x |
x@target, |
233 | 6x |
"].\n\n" |
234 |
) |
|
235 | ||
236 | 6x |
if (asis) { |
237 | 2x |
rv <- knitr::asis_output(rv) |
238 |
} |
|
239 | 6x |
rv |
240 |
} |
|
241 | ||
242 |
# NextBestInfTheory ---- |
|
243 | ||
244 |
#' @description `r lifecycle::badge("experimental")` |
|
245 |
#' @inheritParams knit_print.StoppingTargetProb |
|
246 |
#' @param citation_text (`character`)\cr the text used to cite Mozgunov & Jaki |
|
247 |
#' @param citation_link (`character`)\cr the link to Mozgunov & Jaki |
|
248 |
#' @section Usage Notes: |
|
249 |
#' To use a BibTeX-style citation, specify (for example) `citation_text = |
|
250 |
#' "@MOZGUNOV", citation_link = ""`. |
|
251 |
#' @rdname knit_print |
|
252 |
#' @export |
|
253 |
#' @method knit_print NextBestInfTheory |
|
254 |
knit_print.NextBestInfTheory <- function( |
|
255 |
x, |
|
256 |
..., |
|
257 |
tox_label = "toxicity", |
|
258 |
citation_text = "Mozgunov & Jaki (2019)", |
|
259 |
citation_link = "https://doi.org/10.1002/sim.8450", |
|
260 |
asis = TRUE) { |
|
261 |
# Validate |
|
262 | 8x |
assert_flag(asis) |
263 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
264 | 6x |
assert_character(citation_text, len = 1, any.missing = FALSE) |
265 | 6x |
assert_character(citation_link, len = 1, any.missing = FALSE) |
266 | ||
267 |
# Execute |
|
268 | 6x |
rv <- paste0( |
269 | 6x |
"The recommended dose for the next cohort will be chosen using the ", |
270 | 6x |
"complex infinite bounds penalisation (CIBP) criterion of ", |
271 | 6x |
"[", citation_text, "]", |
272 | 6x |
ifelse(nchar(citation_link) > 0, paste0("(", citation_link, ")"), ""), |
273 | 6x |
". Let\n\n", |
274 | 6x |
"$$ \\delta(\\hat{p}_d, \\gamma) = \\frac{(\\hat{p}_d - \\gamma)^2}", |
275 | 6x |
"{\\hat{p}_d^a \\cdot (1 - \\hat{p}_d)^{2 - a}} $$\n\n", |
276 | 6x |
"where a is the non-centrality parameter with a value of ", |
277 | 6x |
x@asymmetry, |
278 | 6x |
", γ is the target ", |
279 | 6x |
tox_label, |
280 | 6x |
" rate with a value of ", |
281 | 6x |
x@target, |
282 | 6x |
" and $\\hat{p}_d$ is the mean posterior estimate of the probability of ", |
283 | 6x |
tox_label, |
284 | 6x |
" at dose level d.\n\n", |
285 | 6x |
"The recommended dose for the next cohort will be ", |
286 | 6x |
"the value of d that minimises $\\delta(\\hat{p}_d, \\gamma)$.\n\n" |
287 |
) |
|
288 | ||
289 | 6x |
if (asis) { |
290 | 2x |
rv <- knitr::asis_output(rv) |
291 |
} |
|
292 | 6x |
rv |
293 |
} |
|
294 | ||
295 |
# NextBestTD ---- |
|
296 | ||
297 |
#' @description `r lifecycle::badge("experimental")` |
|
298 |
#' @inheritParams knit_print.StoppingTargetProb |
|
299 |
#' @rdname knit_print |
|
300 |
#' @export |
|
301 |
#' @method knit_print NextBestTD |
|
302 |
knit_print.NextBestTD <- function( |
|
303 |
x, |
|
304 |
..., |
|
305 |
tox_label = "toxicity", |
|
306 |
asis = TRUE) { |
|
307 |
# Validate |
|
308 | 14x |
assert_flag(asis) |
309 | 12x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
310 | ||
311 |
# Execute |
|
312 | 12x |
rv <- paste0( |
313 | 12x |
"The dose recommended for the next cohort will be the one which is both ", |
314 | 12x |
"eligible and which is the highest dose in the dose grid strictly less than ", |
315 | 12x |
"the dose (which may not be in the dose grid) that has a posterior plug-in ", |
316 | 12x |
"estimate of the probability of ", |
317 | 12x |
tox_label, |
318 | 12x |
" exactly equal to the target ", |
319 | 12x |
tox_label, |
320 | 12x |
" rate, either during [", |
321 | 12x |
x@prob_target_drt, |
322 | 12x |
"] or at the end of the trial [", |
323 | 12x |
x@prob_target_eot, |
324 | 12x |
"].\n\n" |
325 |
) |
|
326 | ||
327 | 12x |
if (asis) { |
328 | 2x |
rv <- knitr::asis_output(rv) |
329 |
} |
|
330 | 12x |
rv |
331 |
} |
|
332 | ||
333 |
# NextBestMaxGain ---- |
|
334 | ||
335 |
#' @description `r lifecycle::badge("experimental")` |
|
336 |
#' @inheritParams knit_print.StoppingTargetProb |
|
337 |
#' @rdname knit_print |
|
338 |
#' @export |
|
339 |
#' @method knit_print NextBestMaxGain |
|
340 |
knit_print.NextBestMaxGain <- function( |
|
341 |
x, |
|
342 |
..., |
|
343 |
tox_label = "toxicity", |
|
344 |
asis = TRUE) { |
|
345 |
# Validate |
|
346 | 14x |
assert_flag(asis) |
347 | 12x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
348 | ||
349 |
# Execute |
|
350 | 12x |
rv <- paste0( |
351 | 12x |
"The dose recommended for the next cohort will be the one which is closest to ", |
352 | 12x |
"Gstar, the dose that maximises the gain for probability of ", |
353 | 12x |
tox_label, |
354 | 12x |
" exactly equal to the target ", |
355 | 12x |
tox_label, |
356 | 12x |
" rate, either during [", |
357 | 12x |
x@prob_target_drt, |
358 | 12x |
"] or at the end of the trial [", |
359 | 12x |
x@prob_target_eot, |
360 | 12x |
"].\n\n" |
361 |
) |
|
362 | ||
363 | 12x |
if (asis) { |
364 | 2x |
rv <- knitr::asis_output(rv) |
365 |
} |
|
366 | 12x |
rv |
367 |
} |
|
368 | ||
369 |
# NextBestProbMTDLTE ---- |
|
370 | ||
371 |
#' @description `r lifecycle::badge("experimental")` |
|
372 |
#' @inheritParams knit_print.StoppingTargetProb |
|
373 |
#' @rdname knit_print |
|
374 |
#' @export |
|
375 |
#' @method knit_print NextBestProbMTDLTE |
|
376 |
knit_print.NextBestProbMTDLTE <- function( |
|
377 |
x, |
|
378 |
..., |
|
379 |
tox_label = "toxicity", |
|
380 |
asis = TRUE) { |
|
381 |
# Validate |
|
382 | 8x |
assert_flag(asis) |
383 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
384 | ||
385 |
# Execute |
|
386 | 6x |
rv <- paste0( |
387 | 6x |
"The dose recommended for the next cohort will be the dose level with ", |
388 | 6x |
"the highest probability of being the highest dose with an estimated ", |
389 | 6x |
"probability of ", |
390 | 6x |
tox_label, |
391 | 6x |
" less than or equal to ", |
392 | 6x |
x@target, |
393 | 6x |
".\n\n" |
394 |
) |
|
395 | ||
396 | 6x |
if (asis) { |
397 | 2x |
rv <- knitr::asis_output(rv) |
398 |
} |
|
399 | 6x |
rv |
400 |
} |
|
401 | ||
402 |
# NextBestProbMTDMinDist ---- |
|
403 | ||
404 |
#' @description `r lifecycle::badge("experimental")` |
|
405 |
#' @inheritParams knit_print.StoppingTargetProb |
|
406 |
#' @rdname knit_print |
|
407 |
#' @export |
|
408 |
#' @method knit_print NextBestProbMTDMinDist |
|
409 |
knit_print.NextBestProbMTDMinDist <- function( |
|
410 |
x, |
|
411 |
..., |
|
412 |
tox_label = "toxicity", |
|
413 |
asis = TRUE) { |
|
414 |
# Validate |
|
415 | 8x |
assert_flag(asis) |
416 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
417 | ||
418 |
# Execute |
|
419 | 6x |
rv <- paste0( |
420 | 6x |
"The dose recommended for the next cohort will be the dose level with ", |
421 | 6x |
"the highest probability of being the highest dose with an estimated ", |
422 | 6x |
"probability of ", |
423 | 6x |
tox_label, |
424 | 6x |
" closest to ", |
425 | 6x |
x@target, |
426 | 6x |
".\n\n" |
427 |
) |
|
428 | ||
429 | 6x |
if (asis) { |
430 | 2x |
rv <- knitr::asis_output(rv) |
431 |
} |
|
432 | 6x |
rv |
433 |
} |
|
434 | ||
435 |
# NextBestNCRMLoss ---- |
|
436 | ||
437 |
#' @description `r lifecycle::badge("experimental")` |
|
438 |
#' @inheritParams knit_print.StoppingTargetProb |
|
439 |
#' @param format_func (`function`)\cr The function used to format the range table. |
|
440 |
#' @importFrom rlang .data |
|
441 |
#' @rdname knit_print |
|
442 |
#' @export |
|
443 |
#' @method knit_print NextBestNCRMLoss |
|
444 |
knit_print.NextBestNCRMLoss <- function( |
|
445 |
x, |
|
446 |
..., |
|
447 |
tox_label = "toxicity", |
|
448 |
asis = TRUE, |
|
449 |
format_func = function(x) { |
|
450 | 5x |
kableExtra::kable_styling( |
451 | 5x |
x, |
452 | 5x |
bootstrap_options = c("striped", "hover", "condensed") |
453 |
) |
|
454 |
}) { |
|
455 |
# Validate |
|
456 | 8x |
assert_flag(asis) |
457 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
458 | ||
459 |
# Execute |
|
460 | 6x |
param <- list(...) |
461 | 6x |
param[["x"]] <- x %>% |
462 | 6x |
tidy() %>% |
463 | 6x |
dplyr::select(-MaxOverdoseProb) |
464 | 6x |
param[["col.names"]] <- c("Range", "Lower", "Upper", "Loss Coefficient") |
465 | 6x |
rv <- paste0( |
466 | 6x |
"The dose recommended for the next cohort will be chosen in the following ", |
467 | 6x |
"way:\n\n- First, the chance that the probability of ", |
468 | 6x |
tox_label, |
469 | 6x |
" falls into each of the underdose, target ", |
470 | 6x |
ifelse( |
471 | 6x |
any(x@unacceptable != c(1, 1)), |
472 | 6x |
", overdose and unacceptable", |
473 | 6x |
" and overdose" |
474 |
), |
|
475 | 6x |
" dose ranges is calculated for element of the dose grid.\n", |
476 | 6x |
"- Next, the loss associated with each dose is calculated by multiplying ", |
477 | 6x |
"these probabilities by the corresponding loss coefficient and summing the result.\n", |
478 | 6x |
"- Then ineligible doses, and those with a probability of being in the ", |
479 | 6x |
ifelse( |
480 | 6x |
length(x@losses) == 3, |
481 | 6x |
"overdose range", |
482 | 6x |
"overdose or unaccaptable ranges" |
483 |
), |
|
484 | 6x |
" that is greater than ", |
485 | 6x |
x@max_overdose_prob, |
486 | 6x |
", are discarded.\n", |
487 | 6x |
"- Finally, the dose level with the smallest loss is selected as the ", |
488 | 6x |
"recommended dose for the next cohort.\n\n", |
489 | 6x |
ifelse( |
490 | 6x |
toupper(tox_label) == tox_label, |
491 | 6x |
tox_label, |
492 | 6x |
stringr::str_to_sentence(tox_label) |
493 |
), |
|
494 | 6x |
" ranges and loss coefficients are given in the following table:\n\n", |
495 | 6x |
paste((do.call(knitr::kable, param)) %>% format_func(), collapse = "\n"), |
496 | 6x |
"\n\n" |
497 |
) |
|
498 | ||
499 | 6x |
if (asis) { |
500 | 2x |
rv <- knitr::asis_output(rv) |
501 |
} |
|
502 | 6x |
rv |
503 |
} |
|
504 | ||
505 |
# NextBestTDsamples ---- |
|
506 | ||
507 |
#' @description `r lifecycle::badge("experimental")` |
|
508 |
#' @inheritParams knit_print.StoppingTargetProb |
|
509 |
#' @rdname knit_print |
|
510 |
#' @export |
|
511 |
#' @method knit_print NextBestTDsamples |
|
512 |
knit_print.NextBestTDsamples <- function( |
|
513 |
x, |
|
514 |
..., |
|
515 |
tox_label = "toxicity", |
|
516 |
asis = TRUE) { |
|
517 |
# Validate |
|
518 | 12x |
assert_flag(asis) |
519 | 10x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
520 | ||
521 |
# Execute |
|
522 | 10x |
rv <- paste0( |
523 | 10x |
"The dose recommended for the next cohort will be the one which is both ", |
524 | 10x |
"eligible and which is the highest dose in the dose grid strictly less than ", |
525 | 10x |
"the dose (which may not be in the dose grid) that has a full Bayes posterior ", |
526 | 10x |
"estimate of the probability of ", |
527 | 10x |
tox_label, |
528 | 10x |
" exactly equal to the target ", |
529 | 10x |
tox_label, |
530 | 10x |
" rate, either during [", |
531 | 10x |
x@prob_target_drt, |
532 | 10x |
"] or at the end of the trial [", |
533 | 10x |
x@prob_target_eot, |
534 | 10x |
"].\n\n" |
535 |
) |
|
536 | ||
537 | 10x |
if (asis) { |
538 | 2x |
rv <- knitr::asis_output(rv) |
539 |
} |
|
540 | 10x |
rv |
541 |
} |
|
542 | ||
543 | ||
544 |
# NextBestMaxGainSamples ---- |
|
545 | ||
546 |
#' @description `r lifecycle::badge("experimental")` |
|
547 |
#' @inheritParams knit_print.StoppingTargetProb |
|
548 |
#' @rdname knit_print |
|
549 |
#' @export |
|
550 |
#' @method knit_print NextBestMaxGainSamples |
|
551 |
knit_print.NextBestMaxGainSamples <- function( |
|
552 |
x, |
|
553 |
..., |
|
554 |
tox_label = "toxicity", |
|
555 |
asis = TRUE) { |
|
556 |
# Validate |
|
557 | 14x |
assert_flag(asis) |
558 | 12x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
559 | ||
560 |
# Execute |
|
561 | 12x |
rv <- paste0( |
562 | 12x |
"The dose recommended for the next cohort will be the one which is closest to ", |
563 | 12x |
"Gstar, the dose for which the full Bayes posterior estimate of the probability of ", |
564 | 12x |
tox_label, |
565 | 12x |
" maximises the gain relative to the target ", |
566 | 12x |
tox_label, |
567 | 12x |
" rate, either during [", |
568 | 12x |
x@prob_target_drt, |
569 | 12x |
"] or at the end of the trial [", |
570 | 12x |
x@prob_target_eot, |
571 | 12x |
"].\n\n" |
572 |
) |
|
573 | ||
574 | 12x |
if (asis) { |
575 | 2x |
rv <- knitr::asis_output(rv) |
576 |
} |
|
577 | 12x |
rv |
578 |
} |
|
579 | ||
580 |
# NextBestOrdinal ---- |
|
581 | ||
582 |
#' @description `r lifecycle::badge("experimental")` |
|
583 |
#' @inheritParams knit_print.StoppingTargetProb |
|
584 |
#' @rdname knit_print |
|
585 |
#' @export |
|
586 |
#' @method knit_print NextBestOrdinal |
|
587 |
knit_print.NextBestOrdinal <- function( |
|
588 |
x, |
|
589 |
..., |
|
590 |
tox_label = "toxicity", |
|
591 |
asis = TRUE) { |
|
592 | 19x |
assert_flag(asis) |
593 | 17x |
assert_character(tox_label, max.len = 2, any.missing = FALSE) |
594 | ||
595 | 17x |
tox_label <- h_prepare_labels(tox_label) |
596 | 17x |
rv <- paste0( |
597 | 17x |
"Based on a ", |
598 | 17x |
tox_label[1], |
599 | 17x |
" grade of ", |
600 | 17x |
x@grade, |
601 |
": ", |
|
602 | 17x |
paste0(knit_print(x@rule, asis = asis, tox_label = tox_label, ...), collapse = "\n"), |
603 | 17x |
"\n\n" |
604 |
) |
|
605 | ||
606 | 17x |
if (asis) { |
607 | 2x |
rv <- knitr::asis_output(rv) |
608 |
} |
|
609 | 17x |
rv |
610 |
} |
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("Class GeneralModel should not be instantiated directly. Please use one of its subclasses instead.")) |
86 |
} |
|
87 | ||
88 | ||
89 |
# ModelLogNormal ---- |
|
90 | ||
91 |
## class ---- |
|
92 | ||
93 |
#' `ModelLogNormal` |
|
94 |
#' |
|
95 |
#' @description `r lifecycle::badge("stable")` |
|
96 |
#' |
|
97 |
#' [`ModelLogNormal`] is the class for a model with a reference dose and bivariate |
|
98 |
#' normal prior on the model parameters `alpha0` and natural logarithm of `alpha1`, |
|
99 |
#' i.e.: \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov),}. Transformations other |
|
100 |
#' than `log`, e.g. identity, can be specified too in `priormodel` slot. |
|
101 |
#' The parameter `alpha1` has a log-normal distribution by default to ensure |
|
102 |
#' positivity of `alpha1` which further guarantees `exp(alpha1) > 1`. |
|
103 |
#' The slots of this class contain the mean vector, the covariance and |
|
104 |
#' precision matrices of the bivariate normal distribution, as well as the |
|
105 |
#' reference dose. Note that the precision matrix is an inverse of the |
|
106 |
#' covariance matrix in the `JAGS`. |
|
107 |
#' All ("normal") model specific classes inherit from this class. |
|
108 |
#' |
|
109 |
#' @slot params (`ModelParamsNormal`)\cr bivariate normal prior parameters. |
|
110 |
#' @slot ref_dose (`positive_number`)\cr the reference dose. |
|
111 |
#' |
|
112 |
#' @seealso [`ModelParamsNormal`], [`LogisticNormal`], [`LogisticLogNormal`], |
|
113 |
#' [`LogisticLogNormalSub`], [`ProbitLogNormal`], [`ProbitLogNormalRel`]. |
|
114 |
#' |
|
115 |
#' @aliases ModelLogNormal |
|
116 |
#' @export |
|
117 |
#' |
|
118 |
.ModelLogNormal <- setClass( |
|
119 |
Class = "ModelLogNormal", |
|
120 |
contains = "GeneralModel", |
|
121 |
slots = c( |
|
122 |
params = "ModelParamsNormal", |
|
123 |
ref_dose = "positive_number" |
|
124 |
) |
|
125 |
) |
|
126 | ||
127 |
## constructor ---- |
|
128 | ||
129 |
#' @rdname ModelLogNormal-class |
|
130 |
#' |
|
131 |
#' @param mean (`numeric`)\cr the prior mean vector. |
|
132 |
#' @param cov (`matrix`)\cr the prior covariance matrix. The precision matrix |
|
133 |
#' `prec` is internally calculated as an inverse of `cov`. |
|
134 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*} (strictly positive |
|
135 |
#' number). |
|
136 |
#' |
|
137 |
#' @export |
|
138 |
#' |
|
139 |
ModelLogNormal <- function(mean, cov, ref_dose = 1) { |
|
140 | 298x |
params <- ModelParamsNormal(mean, cov) |
141 | 298x |
.ModelLogNormal( |
142 | 298x |
params = params, |
143 | 298x |
ref_dose = positive_number(ref_dose), |
144 | 298x |
priormodel = function() { |
145 | ! |
theta ~ dmnorm(mean, prec) |
146 | ! |
alpha0 <- theta[1] |
147 | ! |
alpha1 <- exp(theta[2]) |
148 |
}, |
|
149 | 298x |
modelspecs = function(from_prior) { |
150 | 163x |
ms <- list(mean = params@mean, prec = params@prec) |
151 | 163x |
if (!from_prior) { |
152 | 152x |
ms$ref_dose <- ref_dose |
153 |
} |
|
154 | 163x |
ms |
155 |
}, |
|
156 | 298x |
init = function() { |
157 | 180x |
list(theta = c(0, 1)) |
158 |
}, |
|
159 | 298x |
datanames = c("nObs", "y", "x"), |
160 | 298x |
sample = c("alpha0", "alpha1") |
161 |
) |
|
162 |
} |
|
163 | ||
164 |
## default constructor ---- |
|
165 | ||
166 |
#' @rdname ModelLogNormal-class |
|
167 |
#' @note Typically, end users will not use the `.DefaultModelLogNormal()` function. |
|
168 |
#' @export |
|
169 |
.DefaultModelLogNormal <- function() { |
|
170 | 6x |
ModelLogNormal(mean = c(-0.85, 1), cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2)) |
171 |
} |
|
172 | ||
173 |
# LogisticNormal ---- |
|
174 | ||
175 |
## class ---- |
|
176 | ||
177 |
#' `LogisticNormal` |
|
178 |
#' |
|
179 |
#' @description `r lifecycle::badge("stable")` |
|
180 |
#' |
|
181 |
#' [`LogisticNormal`] is the class for the usual logistic regression model with |
|
182 |
#' a bivariate normal prior on the intercept and slope. |
|
183 |
#' |
|
184 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided by |
|
185 |
#' the reference dose \eqn{x*}, i.e.: |
|
186 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),} |
|
187 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}. |
|
188 |
#' The prior \deqn{(alpha0, alpha1) ~ Normal(mean, cov).} |
|
189 |
#' |
|
190 |
#' @seealso [`ModelLogNormal`], [`LogisticLogNormal`], [`LogisticLogNormalSub`], |
|
191 |
#' [`ProbitLogNormal`], [`ProbitLogNormalRel`], [`LogisticNormalMixture`]. |
|
192 |
#' |
|
193 |
#' @aliases LogisticNormal |
|
194 |
#' @export |
|
195 |
#' |
|
196 |
.LogisticNormal <- setClass( |
|
197 |
Class = "LogisticNormal", |
|
198 |
contains = "ModelLogNormal" |
|
199 |
) |
|
200 | ||
201 |
## constructor ---- |
|
202 | ||
203 |
#' @rdname LogisticNormal-class |
|
204 |
#' |
|
205 |
#' @inheritParams ModelLogNormal |
|
206 |
#' |
|
207 |
#' @export |
|
208 |
#' @example examples/Model-class-LogisticNormal.R |
|
209 |
#' |
|
210 |
LogisticNormal <- function(mean, cov, ref_dose = 1) { |
|
211 | 24x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
212 | ||
213 | 24x |
.LogisticNormal( |
214 | 24x |
model_ln, |
215 | 24x |
datamodel = function() { |
216 | ! |
for (i in 1:nObs) { |
217 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
218 | ! |
y[i] ~ dbern(p[i]) |
219 |
} |
|
220 |
}, |
|
221 | 24x |
priormodel = function() { |
222 | ! |
theta ~ dmnorm(mean, prec) |
223 | ! |
alpha0 <- theta[1] |
224 | ! |
alpha1 <- theta[2] |
225 |
} |
|
226 |
) |
|
227 |
} |
|
228 | ||
229 |
## default constructor ---- |
|
230 | ||
231 |
#' @rdname LogisticNormal-class |
|
232 |
#' @note Typically, end users will not use the `.DefaultLogisticNormal()` function. |
|
233 |
#' @export |
|
234 |
.DefaultLogisticNormal <- function() { |
|
235 | 7x |
LogisticNormal(mean = c(-0.85, 1), cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2)) |
236 |
} |
|
237 | ||
238 | ||
239 |
# LogisticLogNormal ---- |
|
240 | ||
241 |
## class ---- |
|
242 | ||
243 |
#' `LogisticLogNormal` |
|
244 |
#' |
|
245 |
#' @description `r lifecycle::badge("stable")` |
|
246 |
#' |
|
247 |
#' [`LogisticLogNormal`] is the class for the usual logistic regression model |
|
248 |
#' with a bivariate normal prior on the intercept and log slope. |
|
249 |
#' |
|
250 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided by |
|
251 |
#' the reference dose \eqn{x*}, i.e.: |
|
252 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),} |
|
253 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}. |
|
254 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).} |
|
255 |
#' |
|
256 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormalSub`], |
|
257 |
#' [`ProbitLogNormal`], [`ProbitLogNormalRel`], [`LogisticLogNormalMixture`], |
|
258 |
#' [`DALogisticLogNormal`]. |
|
259 |
#' |
|
260 |
#' @aliases LogisticLogNormal |
|
261 |
#' @export |
|
262 |
#' |
|
263 |
.LogisticLogNormal <- setClass( |
|
264 |
Class = "LogisticLogNormal", |
|
265 |
contains = "ModelLogNormal" |
|
266 |
) |
|
267 | ||
268 |
## constructor ---- |
|
269 | ||
270 |
#' @rdname LogisticLogNormal-class |
|
271 |
#' |
|
272 |
#' @inheritParams ModelLogNormal |
|
273 |
#' |
|
274 |
#' @export |
|
275 |
#' @example examples/Model-class-LogisticLogNormal.R |
|
276 |
#' |
|
277 |
LogisticLogNormal <- function(mean, cov, ref_dose = 1) { |
|
278 | 210x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
279 | ||
280 | 210x |
.LogisticLogNormal( |
281 | 210x |
model_ln, |
282 | 210x |
datamodel = function() { |
283 | ! |
for (i in 1:nObs) { |
284 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
285 | ! |
y[i] ~ dbern(p[i]) |
286 |
} |
|
287 |
} |
|
288 |
) |
|
289 |
} |
|
290 | ||
291 |
## default constructor ---- |
|
292 | ||
293 |
#' @rdname LogisticLogNormal-class |
|
294 |
#' @note Typically, end users will not use the `.DefaultLogisticLogNormal()` function. |
|
295 |
#' @export |
|
296 |
.DefaultLogisticLogNormal <- function() { |
|
297 | 14x |
LogisticLogNormal( |
298 | 14x |
mean = c(-0.85, 1), |
299 | 14x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
300 | 14x |
ref_dose = 50 |
301 |
) |
|
302 |
} |
|
303 | ||
304 |
# LogisticLogNormalSub ---- |
|
305 | ||
306 |
## class ---- |
|
307 | ||
308 |
#' `LogisticLogNormalSub` |
|
309 |
#' |
|
310 |
#' @description `r lifecycle::badge("stable")` |
|
311 |
#' |
|
312 |
#' [`LogisticLogNormalSub`] is the class for a standard logistic model with |
|
313 |
#' bivariate (log) normal prior with subtractive dose standardization. |
|
314 |
#' |
|
315 |
#' @details The covariate is the dose \eqn{x} minus the reference dose \eqn{x*}, |
|
316 |
#' i.e.: |
|
317 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * (x - x*),} |
|
318 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}. |
|
319 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).} |
|
320 |
#' |
|
321 |
#' @slot params (`ModelParamsNormal`)\cr bivariate normal prior parameters. |
|
322 |
#' @slot ref_dose (`number`)\cr the reference dose \eqn{x*}. |
|
323 |
#' |
|
324 |
#' @seealso [`LogisticNormal`], [`LogisticLogNormal`], [`ProbitLogNormal`], |
|
325 |
#' [`ProbitLogNormalRel`]. |
|
326 |
#' |
|
327 |
#' @aliases LogisticLogNormalSub |
|
328 |
#' @export |
|
329 |
#' |
|
330 |
.LogisticLogNormalSub <- setClass( |
|
331 |
Class = "LogisticLogNormalSub", |
|
332 |
slots = c( |
|
333 |
params = "ModelParamsNormal", |
|
334 |
ref_dose = "numeric" |
|
335 |
), |
|
336 |
contains = "GeneralModel" |
|
337 |
) |
|
338 | ||
339 |
## constructor ---- |
|
340 | ||
341 |
#' @rdname LogisticLogNormalSub-class |
|
342 |
#' |
|
343 |
#' @param mean (`numeric`)\cr the prior mean vector. |
|
344 |
#' @param cov (`matrix`)\cr the prior covariance matrix. The precision matrix |
|
345 |
#' `prec` is internally calculated as an inverse of `cov`. |
|
346 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*}. |
|
347 |
#' |
|
348 |
#' @export |
|
349 |
#' @example examples/Model-class-LogisticLogNormalSub.R |
|
350 |
#' |
|
351 |
LogisticLogNormalSub <- function(mean, cov, ref_dose = 0) { |
|
352 | 19x |
params <- ModelParamsNormal(mean, cov) |
353 | 19x |
.LogisticLogNormalSub( |
354 | 19x |
params = params, |
355 | 19x |
ref_dose = ref_dose, |
356 | 19x |
datamodel = function() { |
357 | ! |
for (i in 1:nObs) { |
358 | ! |
logit(p[i]) <- alpha0 + alpha1 * (x[i] - ref_dose) |
359 | ! |
y[i] ~ dbern(p[i]) |
360 |
} |
|
361 |
}, |
|
362 | 19x |
priormodel = function() { |
363 | ! |
theta ~ dmnorm(mean, prec) |
364 | ! |
alpha0 <- theta[1] |
365 | ! |
alpha1 <- exp(theta[2]) |
366 |
}, |
|
367 | 19x |
modelspecs = function(from_prior) { |
368 | 2x |
ms <- list(mean = params@mean, prec = params@prec) |
369 | 2x |
if (!from_prior) { |
370 | 1x |
ms$ref_dose <- ref_dose |
371 |
} |
|
372 | 2x |
ms |
373 |
}, |
|
374 | 19x |
init = function() { |
375 | 2x |
list(theta = c(0, -20)) |
376 |
}, |
|
377 | 19x |
datanames = c("nObs", "y", "x"), |
378 | 19x |
sample = c("alpha0", "alpha1") |
379 |
) |
|
380 |
} |
|
381 | ||
382 | ||
383 |
## default constructor ---- |
|
384 | ||
385 |
#' @rdname LogisticLogNormalSub-class |
|
386 |
#' @note Typically, end-users will not use the `.DefaultLogisticLogNormalSub()` function. |
|
387 |
#' @export |
|
388 |
.DefaultLogisticLogNormalSub <- function() { |
|
389 | 7x |
LogisticLogNormalSub( |
390 | 7x |
mean = c(-0.85, 1), |
391 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
392 | 7x |
ref_dose = 50 |
393 |
) |
|
394 |
} |
|
395 | ||
396 |
# ProbitLogNormal ---- |
|
397 | ||
398 |
## class ---- |
|
399 | ||
400 |
#' `ProbitLogNormal` |
|
401 |
#' |
|
402 |
#' @description `r lifecycle::badge("stable")` |
|
403 |
#' |
|
404 |
#' [`ProbitLogNormal`] is the class for probit regression model with a |
|
405 |
#' bivariate normal prior on the intercept and log slope. |
|
406 |
#' |
|
407 |
#' @details The covariate is the natural logarithm of dose \eqn{x} divided by a |
|
408 |
#' reference dose \eqn{x*}, i.e.: |
|
409 |
#' \deqn{probit[p(x)] = alpha0 + alpha1 * log(x/x*),} |
|
410 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}. |
|
411 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).} |
|
412 |
#' |
|
413 |
#' @note This model is also used in the [`DualEndpoint`] classes, so this class |
|
414 |
#' can be used to check the prior assumptions on the dose-toxicity model, even |
|
415 |
#' when sampling from the prior distribution of the dual endpoint model is not |
|
416 |
#' possible. |
|
417 |
#' |
|
418 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`], |
|
419 |
#' [`LogisticLogNormalSub`], [`ProbitLogNormalRel`]. |
|
420 |
#' |
|
421 |
#' @aliases ProbitLogNormalLogDose |
|
422 |
#' @export |
|
423 |
#' |
|
424 |
.ProbitLogNormal <- setClass( |
|
425 |
Class = "ProbitLogNormal", |
|
426 |
contains = "ModelLogNormal" |
|
427 |
) |
|
428 | ||
429 |
## constructor ---- |
|
430 | ||
431 |
#' @rdname ProbitLogNormal-class |
|
432 |
#' |
|
433 |
#' @inheritParams ModelLogNormal |
|
434 |
#' |
|
435 |
#' @export |
|
436 |
#' @example examples/Model-class-ProbitLogNormal.R |
|
437 |
#' |
|
438 |
ProbitLogNormal <- function(mean, cov, ref_dose = 1) { |
|
439 | 37x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
440 | ||
441 | 37x |
.ProbitLogNormal( |
442 | 37x |
model_ln, |
443 | 37x |
datamodel = function() { |
444 | ! |
for (i in 1:nObs) { |
445 | ! |
probit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
446 | ! |
y[i] ~ dbern(p[i]) |
447 |
} |
|
448 |
} |
|
449 |
) |
|
450 |
} |
|
451 | ||
452 |
## default constructor ---- |
|
453 | ||
454 |
#' @rdname ProbitLogNormal-class |
|
455 |
#' @note Typically, end users will not use the `.DefaultProbitLogNormal()` function. |
|
456 |
#' @export |
|
457 |
.DefaultProbitLogNormal <- function() { |
|
458 | 7x |
ProbitLogNormal( |
459 | 7x |
mean = c(-0.85, 1), |
460 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
461 | 7x |
ref_dose = 7.2 |
462 |
) |
|
463 |
} |
|
464 | ||
465 |
# ProbitLogNormalRel ---- |
|
466 | ||
467 |
## class ---- |
|
468 | ||
469 |
#' `ProbitLogNormalRel` |
|
470 |
#' |
|
471 |
#' @description `r lifecycle::badge("stable")` |
|
472 |
#' |
|
473 |
#' [`ProbitLogNormalRel`] is the class for probit regression model with a bivariate |
|
474 |
#' normal prior on the intercept and log slope. |
|
475 |
#' |
|
476 |
#' @details The covariate is the dose \eqn{x} divided by a reference dose \eqn{x*}, |
|
477 |
#' i.e.: |
|
478 |
#' \deqn{probit[p(x)] = alpha0 + alpha1 * x/x*,} |
|
479 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}. |
|
480 |
#' The prior \deqn{(alpha0, log(alpha1)) ~ Normal(mean, cov).} |
|
481 |
#' |
|
482 |
#' @note This model is also used in the [`DualEndpoint`] classes, so this class |
|
483 |
#' can be used to check the prior assumptions on the dose-toxicity model, even |
|
484 |
#' when sampling from the prior distribution of the dual endpoint model is not |
|
485 |
#' possible. |
|
486 |
#' |
|
487 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`], |
|
488 |
#' [`LogisticLogNormalSub`], [`ProbitLogNormal`]. |
|
489 |
#' |
|
490 |
#' @aliases ProbitLogNormalRel |
|
491 |
#' @export |
|
492 |
#' |
|
493 |
.ProbitLogNormalRel <- setClass( |
|
494 |
Class = "ProbitLogNormalRel", |
|
495 |
contains = "ModelLogNormal" |
|
496 |
) |
|
497 | ||
498 |
## constructor ---- |
|
499 | ||
500 |
#' @rdname ProbitLogNormalRel-class |
|
501 |
#' |
|
502 |
#' @inheritParams ModelLogNormal |
|
503 |
#' |
|
504 |
#' @export |
|
505 |
#' @example examples/Model-class-ProbitLogNormalRel.R |
|
506 |
#' |
|
507 |
ProbitLogNormalRel <- function(mean, cov, ref_dose = 1) { |
|
508 | 19x |
model_ln <- ModelLogNormal(mean = mean, cov = cov, ref_dose = ref_dose) |
509 | ||
510 | 19x |
.ProbitLogNormalRel( |
511 | 19x |
model_ln, |
512 | 19x |
datamodel = function() { |
513 | ! |
for (i in 1:nObs) { |
514 | ! |
probit(p[i]) <- alpha0 + alpha1 * (x[i] / ref_dose) |
515 | ! |
y[i] ~ dbern(p[i]) |
516 |
} |
|
517 |
} |
|
518 |
) |
|
519 |
} |
|
520 | ||
521 |
## default constructor ---- |
|
522 | ||
523 |
#' @rdname ProbitLogNormalRel-class |
|
524 |
#' @note Typically, end users will not use the `.DefaultProbitLogNormalRel()` function. |
|
525 |
#' @export |
|
526 |
.DefaultProbitLogNormalRel <- function() { |
|
527 | 7x |
ProbitLogNormalRel(mean = c(-0.85, 1), cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2)) |
528 |
} |
|
529 | ||
530 |
# LogisticLogNormalGrouped ---- |
|
531 | ||
532 |
## class ---- |
|
533 | ||
534 |
#' `LogisticLogNormalGrouped` |
|
535 |
#' |
|
536 |
#' @description `r lifecycle::badge("experimental")` |
|
537 |
#' |
|
538 |
#' [`LogisticLogNormalGrouped`] is the class for a logistic regression model |
|
539 |
#' for both the mono and the combo arms of the simultaneous dose escalation |
|
540 |
#' design. |
|
541 |
#' |
|
542 |
#' @details The continuous covariate is the natural logarithm of the dose \eqn{x} divided by |
|
543 |
#' the reference dose \eqn{x*} as in [`LogisticLogNormal`]. In addition, |
|
544 |
#' \eqn{I_c} is a binary indicator covariate which is 1 for the combo arm and 0 for the mono arm. |
|
545 |
#' The model is then defined as: |
|
546 |
#' \deqn{logit[p(x)] = (alpha0 + I_c * delta0) + (alpha1 + I_c * delta1) * log(x / x*),} |
|
547 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}, |
|
548 |
#' and `delta0` and `delta1` are the differences in the combo arm compared to the mono intercept |
|
549 |
#' and slope parameters `alpha0` and `alpha1`. |
|
550 |
#' The prior is defined as \deqn{(alpha0, log(delta0), log(alpha1), log(delta1)) ~ Normal(mean, cov).} |
|
551 |
#' |
|
552 |
#' @seealso [`ModelLogNormal`], [`LogisticLogNormal`]. |
|
553 |
#' |
|
554 |
#' @aliases LogisticLogNormalGrouped |
|
555 |
#' @export |
|
556 |
#' |
|
557 |
.LogisticLogNormalGrouped <- setClass( |
|
558 |
Class = "LogisticLogNormalGrouped", |
|
559 |
contains = "ModelLogNormal" |
|
560 |
) |
|
561 | ||
562 |
## constructor ---- |
|
563 | ||
564 |
#' @rdname LogisticLogNormalGrouped-class |
|
565 |
#' |
|
566 |
#' @inheritParams ModelLogNormal |
|
567 |
#' |
|
568 |
#' @export |
|
569 |
#' @example examples/Model-class-LogisticLogNormalGrouped.R |
|
570 |
#' |
|
571 |
LogisticLogNormalGrouped <- function(mean, cov, ref_dose = 1) { |
|
572 | 32x |
params <- ModelParamsNormal(mean, cov) |
573 | 32x |
.LogisticLogNormalGrouped( |
574 | 32x |
params = params, |
575 | 32x |
ref_dose = positive_number(ref_dose), |
576 | 32x |
priormodel = function() { |
577 | ! |
theta ~ dmnorm(mean, prec) |
578 | ! |
alpha0 <- theta[1] |
579 | ! |
delta0 <- exp(theta[2]) |
580 | ! |
alpha1 <- exp(theta[3]) |
581 | ! |
delta1 <- exp(theta[4]) |
582 |
}, |
|
583 | 32x |
datamodel = function() { |
584 | ! |
for (i in 1:nObs) { |
585 | ! |
logit(p[i]) <- (alpha0 + is_combo[i] * delta0) + |
586 | ! |
(alpha1 + is_combo[i] * delta1) * log(x[i] / ref_dose) |
587 | ! |
y[i] ~ dbern(p[i]) |
588 |
} |
|
589 |
}, |
|
590 | 32x |
modelspecs = function(group, from_prior) { |
591 | 75x |
ms <- list( |
592 | 75x |
mean = params@mean, |
593 | 75x |
prec = params@prec |
594 |
) |
|
595 | 75x |
if (!from_prior) { |
596 | 74x |
ms$ref_dose <- ref_dose |
597 | 74x |
ms$is_combo <- as.integer(group == "combo") |
598 |
} |
|
599 | 75x |
ms |
600 |
}, |
|
601 | 32x |
init = function() { |
602 | 75x |
list(theta = c(0, 1, 1, 1)) |
603 |
}, |
|
604 | 32x |
datanames = c("nObs", "y", "x"), |
605 | 32x |
sample = c("alpha0", "delta0", "alpha1", "delta1") |
606 |
) |
|
607 |
} |
|
608 | ||
609 |
## default constructor ---- |
|
610 | ||
611 |
#' @rdname LogisticLogNormalGrouped-class |
|
612 |
#' @note Typically, end users will not use the `.DefaultLogisticLogNormalGrouped()` function. |
|
613 |
#' @export |
|
614 |
.DefaultLogisticLogNormalGrouped <- function() { |
|
615 | 22x |
LogisticLogNormalGrouped( |
616 | 22x |
mean = rep(0, 4), |
617 | 22x |
cov = diag(rep(1, 4)), |
618 |
) |
|
619 |
} |
|
620 | ||
621 |
# LogisticKadane ---- |
|
622 | ||
623 |
## class ---- |
|
624 | ||
625 |
#' `LogisticKadane` |
|
626 |
#' |
|
627 |
#' @description `r lifecycle::badge("stable")` |
|
628 |
#' |
|
629 |
#' [`LogisticKadane`] is the class for the logistic model in the parametrization |
|
630 |
#' of Kadane et al. (1980). |
|
631 |
#' |
|
632 |
#' @details Let `rho0 = p(xmin)` be the probability of a DLT at the minimum dose |
|
633 |
#' `xmin`, and let `gamma` be the dose with target toxicity probability `theta`, |
|
634 |
#' i.e. \eqn{p(gamma) = theta}. Then it can easily be shown that the logistic |
|
635 |
#' regression model has intercept |
|
636 |
#' \deqn{[gamma * logit(rho0) - xmin * logit(theta)] / [gamma - xmin]} |
|
637 |
#' and slope |
|
638 |
#' \deqn{[logit(theta) - logit(rho0)] / [gamma - xmin].} |
|
639 |
#' |
|
640 |
#' The priors are \deqn{gamma ~ Unif(xmin, xmax).} and |
|
641 |
#' \deqn{rho0 ~ Unif(0, theta).} |
|
642 |
#' |
|
643 |
#' @note The slots of this class, required for creating the model, are the target |
|
644 |
#' toxicity, as well as the minimum and maximum of the dose range. Note that |
|
645 |
#' these can be different from the minimum and maximum of the dose grid in the |
|
646 |
#' data later on. |
|
647 |
#' |
|
648 |
#' @slot theta (`proportion`)\cr the target toxicity probability. |
|
649 |
#' @slot xmin (`number`)\cr the minimum of the dose range. |
|
650 |
#' @slot xmax (`number`)\cr the maximum of the dose range. |
|
651 |
#' |
|
652 |
#' @seealso [`ModelLogNormal`] |
|
653 |
#' |
|
654 |
#' @aliases LogisticKadane |
|
655 |
#' @export |
|
656 |
#' |
|
657 |
.LogisticKadane <- setClass( |
|
658 |
Class = "LogisticKadane", |
|
659 |
contains = "GeneralModel", |
|
660 |
slots = c( |
|
661 |
theta = "numeric", |
|
662 |
xmin = "numeric", |
|
663 |
xmax = "numeric" |
|
664 |
), |
|
665 |
prototype = prototype( |
|
666 |
theta = 0.3, |
|
667 |
xmin = 0.1, |
|
668 |
xmax = 1 |
|
669 |
), |
|
670 |
validity = v_model_logistic_kadane |
|
671 |
) |
|
672 | ||
673 |
## constructor ---- |
|
674 | ||
675 |
#' @rdname LogisticKadane-class |
|
676 |
#' |
|
677 |
#' @param theta (`proportion`)\cr the target toxicity probability. |
|
678 |
#' @param xmin (`number`)\cr the minimum of the dose range. |
|
679 |
#' @param xmax (`number`)\cr the maximum of the dose range. |
|
680 |
#' |
|
681 |
#' @export |
|
682 |
#' @example examples/Model-class-LogisticKadane.R |
|
683 |
#' |
|
684 |
LogisticKadane <- function(theta, xmin, xmax) { |
|
685 | 72x |
.LogisticKadane( |
686 | 72x |
theta = theta, |
687 | 72x |
xmin = xmin, |
688 | 72x |
xmax = xmax, |
689 | 72x |
datamodel = function() { |
690 | ! |
for (i in 1:nObs) { |
691 | ! |
logit(p[i]) <- (1 / (gamma - xmin)) * |
692 | ! |
(gamma * logit(rho0) - xmin * logit(theta) + x[i] * (logit(theta) - logit(rho0))) |
693 | ! |
y[i] ~ dbern(p[i]) |
694 |
} |
|
695 |
}, |
|
696 | 72x |
priormodel = function() { |
697 | ! |
rho0 ~ dunif(0, theta) |
698 | ! |
gamma ~ dunif(xmin, xmax) |
699 |
}, |
|
700 | 72x |
modelspecs = function() { |
701 | 27x |
list(theta = theta, xmin = xmin, xmax = xmax) |
702 |
}, |
|
703 | 72x |
init = function() { |
704 | 29x |
list(rho0 = theta / 10, gamma = (xmax - xmin) / 2) |
705 |
}, |
|
706 | 72x |
datanames = c("nObs", "y", "x"), |
707 | 72x |
sample = c("rho0", "gamma") |
708 |
) |
|
709 |
} |
|
710 | ||
711 |
## default constructor ---- |
|
712 | ||
713 |
#' @rdname LogisticKadane-class |
|
714 |
#' @note Typically, end-users will not use the `.DefaultLogisticKadane()` function. |
|
715 |
#' @export |
|
716 |
.DefaultLogisticKadane <- function() { |
|
717 | 7x |
LogisticKadane(theta = 0.33, xmin = 1, xmax = 200) |
718 |
} |
|
719 | ||
720 | ||
721 |
# LogisticKadaneBetaGamma ---- |
|
722 | ||
723 |
## class ---- |
|
724 | ||
725 |
#' `LogisticKadaneBetaGamma` |
|
726 |
#' |
|
727 |
#' @description `r lifecycle::badge("experimental")` |
|
728 |
#' |
|
729 |
#' [`LogisticKadaneBetaGamma`] is the class for the logistic model in the parametrization |
|
730 |
#' of Kadane et al. (1980), using a beta and a gamma distribution as the model priors. |
|
731 |
#' |
|
732 |
#' @details Let `rho0 = p(xmin)` be the probability of a DLT at the minimum dose |
|
733 |
#' `xmin`, and let `gamma` be the dose with target toxicity probability `theta`, |
|
734 |
#' i.e. \eqn{p(gamma) = theta}. Then it can easily be shown that the logistic |
|
735 |
#' regression model has intercept |
|
736 |
#' \deqn{[gamma * logit(rho0) - xmin * logit(theta)] / [gamma - xmin]} |
|
737 |
#' and slope |
|
738 |
#' \deqn{[logit(theta) - logit(rho0)] / [gamma - xmin].} |
|
739 |
#' |
|
740 |
#' The prior for `gamma`, is \deqn{gamma ~ Gamma(shape, rate).}. |
|
741 |
#' The prior for `rho0 = p(xmin)`, is \deqn{rho0 ~ Beta(alpha, beta).} |
|
742 |
#' |
|
743 |
#' @note The slots of this class, required for creating the model, are the same |
|
744 |
#' as in the `LogisticKadane` class. In addition, the shape parameters of the |
|
745 |
#' Beta prior distribution of `rho0` and the shape and rate parameters of the |
|
746 |
#' Gamma prior distribution of `gamma`, are required for creating the prior model. |
|
747 |
#' |
|
748 |
#' @slot theta (`proportion`)\cr the target toxicity probability. |
|
749 |
#' @slot xmin (`number`)\cr the minimum of the dose range. |
|
750 |
#' @slot xmax (`number`)\cr the maximum of the dose range. |
|
751 |
#' @slot alpha (`number`)\cr the first shape parameter of the Beta prior distribution |
|
752 |
#' of `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
753 |
#' @slot beta (`number`)\cr the second shape parameter of the Beta prior distribution |
|
754 |
#' of `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
755 |
#' @slot shape (`number`)\cr the shape parameter of the Gamma prior distribution |
|
756 |
#' of `gamma` the dose with target toxicity probability `theta`. |
|
757 |
#' @slot rate (`number`)\cr the rate parameter of the Gamma prior distribution |
|
758 |
#' of `gamma` the dose with target toxicity probability `theta`. |
|
759 |
#' |
|
760 |
#' @seealso [`ModelLogNormal`], [`LogisticKadane`]. |
|
761 |
#' |
|
762 |
#' @aliases LogisticKadaneBetaGamma |
|
763 |
#' @export |
|
764 |
#' |
|
765 |
.LogisticKadaneBetaGamma <- setClass( |
|
766 |
Class = "LogisticKadaneBetaGamma", |
|
767 |
contains = "LogisticKadane", |
|
768 |
slots = c( |
|
769 |
alpha = "numeric", |
|
770 |
beta = "numeric", |
|
771 |
shape = "numeric", |
|
772 |
rate = "numeric" |
|
773 |
), |
|
774 |
prototype = prototype( |
|
775 |
theta = 0.3, |
|
776 |
xmin = 0.1, |
|
777 |
xmax = 1, |
|
778 |
alpha = 1, |
|
779 |
beta = 0.5, |
|
780 |
shape = 1.2, |
|
781 |
rate = 2.5 |
|
782 |
), |
|
783 |
validity = v_model_logistic_kadane_beta_gamma |
|
784 |
) |
|
785 | ||
786 |
## constructor ---- |
|
787 | ||
788 |
#' @rdname LogisticKadaneBetaGamma-class |
|
789 |
#' |
|
790 |
#' @inheritParams LogisticKadane |
|
791 |
#' |
|
792 |
#' @param alpha (`number`)\cr the first shape parameter of the Beta prior distribution |
|
793 |
#' `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
794 |
#' @param beta (`number`)\cr the second shape parameter of the Beta prior distribution |
|
795 |
#' `rho0 = p(xmin)` the probability of a DLT at the minimum dose `xmin`. |
|
796 |
#' @param shape (`number`)\cr the shape parameter of the Gamma prior distribution |
|
797 |
#' `gamma` the dose with target toxicity probability `theta`. |
|
798 |
#' @param rate (`number`)\cr the rate parameter of the Gamma prior distribution |
|
799 |
#' `gamma` the dose with target toxicity probability `theta`. |
|
800 |
#' |
|
801 |
#' @export |
|
802 |
#' @example examples/Model-class-LogisticKadaneBetaGamma.R |
|
803 |
#' |
|
804 |
LogisticKadaneBetaGamma <- function(theta, xmin, xmax, alpha, beta, shape, rate) { |
|
805 | 24x |
model_lk <- LogisticKadane(theta = theta, xmin = xmin, xmax = xmax) |
806 | 24x |
.LogisticKadaneBetaGamma( |
807 | 24x |
model_lk, |
808 | 24x |
alpha = alpha, |
809 | 24x |
beta = beta, |
810 | 24x |
shape = shape, |
811 | 24x |
rate = rate, |
812 | 24x |
priormodel = function() { |
813 | ! |
rho0 ~ dbeta(alpha, beta) |
814 | ! |
gamma ~ dgamma(shape, rate) |
815 | ! |
lowestdose <- xmin |
816 | ! |
highestdose <- xmax |
817 | ! |
DLTtarget <- theta |
818 |
}, |
|
819 | 24x |
modelspecs = function() { |
820 | 2x |
list( |
821 | 2x |
theta = theta, |
822 | 2x |
xmin = xmin, |
823 | 2x |
xmax = xmax, |
824 | 2x |
alpha = alpha, |
825 | 2x |
beta = beta, |
826 | 2x |
shape = shape, |
827 | 2x |
rate = rate |
828 |
) |
|
829 |
} |
|
830 |
) |
|
831 |
} |
|
832 | ||
833 |
## default constructor ---- |
|
834 | ||
835 |
#' @rdname LogisticKadaneBetaGamma-class |
|
836 |
#' @note Typically, end users will not use the `.Default()` function. |
|
837 |
#' @export |
|
838 |
.DefaultLogisticKadaneBetaGamma <- function() { |
|
839 | 7x |
LogisticKadaneBetaGamma( |
840 | 7x |
theta = 0.3, |
841 | 7x |
xmin = 0, |
842 | 7x |
xmax = 7, |
843 | 7x |
alpha = 1, |
844 | 7x |
beta = 19, |
845 | 7x |
shape = 0.5625, |
846 | 7x |
rate = 0.125 |
847 |
) |
|
848 |
} |
|
849 | ||
850 |
# LogisticNormalMixture ---- |
|
851 | ||
852 |
## class ---- |
|
853 | ||
854 |
#' `LogisticNormalMixture` |
|
855 |
#' |
|
856 |
#' @description `r lifecycle::badge("stable")` |
|
857 |
#' |
|
858 |
#' [`LogisticNormalMixture`] is the class for standard logistic regression model |
|
859 |
#' with a mixture of two bivariate normal priors on the intercept and slope parameters. |
|
860 |
#' |
|
861 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided by |
|
862 |
#' the reference dose \eqn{x*}, i.e.: |
|
863 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),} |
|
864 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}. |
|
865 |
#' The prior |
|
866 |
#' \deqn{(alpha0, alpha1) ~ w * Normal(mean1, cov1) + (1 - w) * Normal(mean2, cov2).} |
|
867 |
#' The weight w for the first component is assigned a beta prior `B(a, b)`. |
|
868 |
#' |
|
869 |
#' @note The weight of the two normal priors is a model parameter, hence it is a |
|
870 |
#' flexible mixture. This type of prior is often used with a mixture of a minimal |
|
871 |
#' informative and an informative component, in order to make the CRM more robust |
|
872 |
#' to data deviations from the informative component. |
|
873 |
#' |
|
874 |
#' @slot comp1 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
875 |
#' the first component. |
|
876 |
#' @slot comp2 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
877 |
#' the second component. |
|
878 |
#' @slot weightpar (`numeric`)\cr the beta parameters for the weight of the |
|
879 |
#' first component. It must a be a named vector of length 2 with names `a` and |
|
880 |
#' `b` and with strictly positive values. |
|
881 |
#' @slot ref_dose (`positive_number`)\cr the reference dose. |
|
882 |
#' |
|
883 |
#' @seealso [`ModelParamsNormal`], [`ModelLogNormal`], |
|
884 |
#' [`LogisticNormalFixedMixture`], [`LogisticLogNormalMixture`]. |
|
885 |
#' |
|
886 |
#' @aliases LogisticNormalMixture |
|
887 |
#' @export |
|
888 |
#' |
|
889 |
.LogisticNormalMixture <- setClass( |
|
890 |
Class = "LogisticNormalMixture", |
|
891 |
contains = "GeneralModel", |
|
892 |
slots = c( |
|
893 |
comp1 = "ModelParamsNormal", |
|
894 |
comp2 = "ModelParamsNormal", |
|
895 |
weightpar = "numeric", |
|
896 |
ref_dose = "numeric" |
|
897 |
), |
|
898 |
prototype = prototype( |
|
899 |
comp1 = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
900 |
comp2 = ModelParamsNormal(mean = c(-1, 1), cov = diag(2)), |
|
901 |
weightpar = c(a = 1, b = 1), |
|
902 |
ref_dose = 1 |
|
903 |
), |
|
904 |
validity = v_model_logistic_normal_mix |
|
905 |
) |
|
906 | ||
907 |
## constructor ---- |
|
908 | ||
909 |
#' @rdname LogisticNormalMixture-class |
|
910 |
#' |
|
911 |
#' @param comp1 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
912 |
#' the first component. See [`ModelParamsNormal`] for more details. |
|
913 |
#' @param comp2 (`ModelParamsNormal`)\cr bivariate normal prior specification of |
|
914 |
#' the second component. See [`ModelParamsNormal`] for more details. |
|
915 |
#' @param weightpar (`numeric`)\cr the beta parameters for the weight of the |
|
916 |
#' first component. It must a be a named vector of length 2 with names `a` and |
|
917 |
#' `b` and with strictly positive values. |
|
918 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*} |
|
919 |
#' (strictly positive number). |
|
920 |
#' |
|
921 |
#' @export |
|
922 |
#' @example examples/Model-class-LogisticNormalMixture.R |
|
923 |
#' |
|
924 |
LogisticNormalMixture <- function(comp1, |
|
925 |
comp2, |
|
926 |
weightpar, |
|
927 |
ref_dose) { |
|
928 | 21x |
assert_number(ref_dose) |
929 | ||
930 | 21x |
.LogisticNormalMixture( |
931 | 21x |
comp1 = comp1, |
932 | 21x |
comp2 = comp2, |
933 | 21x |
weightpar = weightpar, |
934 | 21x |
ref_dose = ref_dose, |
935 | 21x |
datamodel = function() { |
936 |
# The logistic likelihood - the same as for non-mixture case. |
|
937 | ! |
for (i in 1:nObs) { |
938 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
939 | ! |
y[i] ~ dbern(p[i]) |
940 |
} |
|
941 |
}, |
|
942 | 21x |
priormodel = function() { |
943 | ! |
w ~ dbeta(weightpar[1], weightpar[2]) |
944 | ! |
wc <- 1 - w |
945 | ! |
comp0 ~ dbern(wc) |
946 | ! |
comp <- comp0 + 1 |
947 |
# Conditional on the component index "comp", which is 1 or 2. |
|
948 |
# comp = 1 with probability "w" and comp = 2 with probability "1 - w". |
|
949 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
950 | ! |
alpha0 <- theta[1] |
951 | ! |
alpha1 <- theta[2] |
952 |
}, |
|
953 | 21x |
modelspecs = function(from_prior) { |
954 | 2x |
ms <- list( |
955 | 2x |
mean = cbind(comp1@mean, comp2@mean), |
956 | 2x |
prec = array(data = c(comp1@prec, comp2@prec), dim = c(2, 2, 2)), |
957 | 2x |
weightpar = weightpar |
958 |
) |
|
959 | 2x |
if (!from_prior) { |
960 | 1x |
ms$ref_dose <- ref_dose |
961 |
} |
|
962 | 2x |
ms |
963 |
}, |
|
964 | 21x |
init = function() { |
965 | 2x |
list(theta = c(0, 1)) |
966 |
}, |
|
967 | 21x |
datanames = c("nObs", "y", "x"), |
968 | 21x |
sample = c("alpha0", "alpha1", "w") |
969 |
) |
|
970 |
} |
|
971 | ||
972 |
## default constructor ---- |
|
973 | ||
974 |
#' @rdname LogisticNormalMixture-class |
|
975 |
#' @note Typically, end-users will not use the `.DefaultLogisticNormalMixture()` function. |
|
976 |
#' @export |
|
977 |
.DefaultLogisticNormalMixture <- function() { # nolint |
|
978 | 7x |
LogisticNormalMixture( |
979 | 7x |
comp1 = ModelParamsNormal( |
980 | 7x |
mean = c(-0.85, 1), |
981 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
982 |
), |
|
983 | 7x |
comp2 = ModelParamsNormal( |
984 | 7x |
mean = c(1, 1.5), |
985 | 7x |
cov = matrix(c(1.2, -0.45, -0.45, 0.6), nrow = 2) |
986 |
), |
|
987 | 7x |
weightpar = c(a = 1, b = 1), |
988 | 7x |
ref_dose = 50 |
989 |
) |
|
990 |
} |
|
991 | ||
992 |
# LogisticNormalFixedMixture ---- |
|
993 | ||
994 |
## class ---- |
|
995 | ||
996 |
#' `LogisticNormalFixedMixture` |
|
997 |
#' |
|
998 |
#' @description `r lifecycle::badge("stable")` |
|
999 |
#' |
|
1000 |
#' [`LogisticNormalFixedMixture`] is the class for standard logistic regression |
|
1001 |
#' model with fixed mixture of multiple bivariate (log) normal priors on the |
|
1002 |
#' intercept and slope parameters. The weights of the normal priors are fixed, |
|
1003 |
#' hence no additional model parameters are introduced. This type of prior is |
|
1004 |
#' often used to better approximate a given posterior distribution, or when the |
|
1005 |
#' information is given in terms of a mixture. |
|
1006 |
#' |
|
1007 |
#' @details The covariate is the natural logarithm of the dose \eqn{x} divided |
|
1008 |
#' by the reference dose \eqn{x*}, i.e.: |
|
1009 |
#' \deqn{logit[p(x)] = alpha0 + alpha1 * log(x/x*),} |
|
1010 |
#' where \eqn{p(x)} is the probability of observing a DLT for a given dose \eqn{x}. |
|
1011 |
#' The prior |
|
1012 |
#' \deqn{(alpha0, alpha1) ~ w1 * Normal(mean1, cov1) + ... + wK * Normal(meanK, covK),} |
|
1013 |
#' if a normal prior is used and |
|
1014 |
#' \deqn{(alpha0, log(alpha1)) ~ w1 * Normal(mean1, cov1) + ... + wK * Normal(meanK, covK),} |
|
1015 |
#' if a log normal prior is used. |
|
1016 |
#' The weights \eqn{w1, ..., wK} of the components are fixed and sum to 1. |
|
1017 |
#' |
|
1018 |
#' The slots of this class comprise a list with components parameters. Every |
|
1019 |
#' single component contains the mean vector and the covariance matrix of |
|
1020 |
#' bivariate normal distributions. Remaining slots are the weights of the |
|
1021 |
#' components as well as the reference dose. Moreover, a special indicator |
|
1022 |
#' slot specifies whether a log normal prior is used. |
|
1023 |
#' |
|
1024 |
#' @slot components (`list`)\cr the specifications of the mixture components, |
|
1025 |
#' a list with [`ModelParamsNormal`] objects for each bivariate (log) normal |
|
1026 |
#' prior. |
|
1027 |
#' @slot weights (`numeric`)\cr the weights of the components; these must be |
|
1028 |
#' positive and must sum to 1. |
|
1029 |
#' @slot ref_dose (`positive_number`)\cr the reference dose. |
|
1030 |
#' @slot log_normal (`flag`)\cr should a log normal prior be used, such |
|
1031 |
#' that the mean vectors and covariance matrices are valid for the intercept |
|
1032 |
#' and log slope? |
|
1033 |
#' |
|
1034 |
#' @seealso [`ModelParamsNormal`], [`ModelLogNormal`], |
|
1035 |
#' [`LogisticNormalMixture`], [`LogisticLogNormalMixture`]. |
|
1036 |
#' |
|
1037 |
#' @aliases LogisticNormalFixedMixture |
|
1038 |
#' @export |
|
1039 |
#' |
|
1040 |
.LogisticNormalFixedMixture <- setClass( |
|
1041 |
Class = "LogisticNormalFixedMixture", |
|
1042 |
contains = "GeneralModel", |
|
1043 |
slots = c( |
|
1044 |
components = "list", |
|
1045 |
weights = "numeric", |
|
1046 |
ref_dose = "numeric", |
|
1047 |
log_normal = "logical" |
|
1048 |
), |
|
1049 |
prototype = prototype( |
|
1050 |
components = list( |
|
1051 |
comp1 = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
1052 |
comp2 = ModelParamsNormal(mean = c(-1, 1), cov = diag(2)) |
|
1053 |
), |
|
1054 |
weights = c(0.5, 0.5), |
|
1055 |
ref_dose = 1, |
|
1056 |
log_normal = FALSE |
|
1057 |
), |
|
1058 |
validity = v_model_logistic_normal_fixed_mix |
|
1059 |
) |
|
1060 | ||
1061 |
## constructor ---- |
|
1062 | ||
1063 |
#' @rdname LogisticNormalFixedMixture-class |
|
1064 |
#' |
|
1065 |
#' @param components (`list`)\cr the specifications of the mixture components, |
|
1066 |
#' a list with [`ModelParamsNormal`] objects for each bivariate (log) normal |
|
1067 |
#' prior. |
|
1068 |
#' @param weights (`numeric`)\cr the weights of the components; these must be |
|
1069 |
#' positive and will be normalized to sum to 1. |
|
1070 |
#' @param ref_dose (`number`)\cr the reference dose \eqn{x*} |
|
1071 |
#' (strictly positive number). |
|
1072 |
#' @param log_normal (`flag`)\cr should a log normal prior be specified, such |
|
1073 |
#' that the mean vectors and covariance matrices are valid for the intercept |
|
1074 |
#' and log slope? |
|
1075 |
#' |
|
1076 |
#' @export |
|
1077 |
#' @example examples/Model-class-LogisticNormalFixedMixture.R |
|
1078 |
#' |
|
1079 |
LogisticNormalFixedMixture <- function(components, |
|
1080 |
weights, |
|
1081 |
ref_dose, |
|
1082 |
log_normal = FALSE) { |
|
1083 | 28x |
assert_numeric(weights) |
1084 | 28x |
assert_number(ref_dose) |
1085 | 28x |
assert_flag(log_normal) |
1086 | ||
1087 |
# Normalize weights to sum to 1. |
|
1088 | 28x |
weights <- weights / sum(weights) |
1089 | ||
1090 | 28x |
.LogisticNormalFixedMixture( |
1091 | 28x |
components = components, |
1092 | 28x |
weights = weights, |
1093 | 28x |
ref_dose = positive_number(ref_dose), |
1094 | 28x |
log_normal = log_normal, |
1095 | 28x |
datamodel = function() { |
1096 | ! |
for (i in 1:nObs) { |
1097 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
1098 | ! |
y[i] ~ dbern(p[i]) |
1099 |
} |
|
1100 |
}, |
|
1101 | 28x |
priormodel = if (log_normal) { |
1102 | 2x |
function() { |
1103 | ! |
comp ~ dcat(weights) |
1104 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
1105 | ! |
alpha0 <- theta[1] |
1106 | ! |
alpha1 <- exp(theta[2]) |
1107 |
} |
|
1108 |
} else { |
|
1109 | 26x |
function() { |
1110 | ! |
comp ~ dcat(weights) |
1111 | ! |
theta ~ dmnorm(mean[1:2, comp], prec[1:2, 1:2, comp]) |
1112 | ! |
alpha0 <- theta[1] |
1113 | ! |
alpha1 <- theta[2] |
1114 |
} |
|
1115 |
}, |
|
1116 | 28x |
modelspecs = function(from_prior) { |
1117 | 4x |
ms <- list( |
1118 | 4x |
weights = weights, |
1119 | 4x |
mean = do.call(cbind, lapply(components, h_slots, "mean", simplify = TRUE)), |
1120 | 4x |
prec = array( |
1121 | 4x |
do.call(c, lapply(components, h_slots, "prec", simplify = TRUE)), |
1122 | 4x |
dim = c(2, 2, length(components)) |
1123 |
) |
|
1124 |
) |
|
1125 | 4x |
if (!from_prior) { |
1126 | 2x |
ms$ref_dose <- ref_dose |
1127 |
} |
|
1128 | 4x |
ms |
1129 |
}, |
|
1130 | 28x |
init = function() { |
1131 | 4x |
list(theta = c(0, 1)) |
1132 |
}, |
|
1133 | 28x |
datanames = c("nObs", "y", "x"), |
1134 | 28x |
sample = c("alpha0", "alpha1") |
1135 |
) |
|
1136 |
} |
|
1137 | ||
1138 |
## default constructor ---- |
|
1139 | ||
1140 |
#' @rdname LogisticNormalFixedMixture-class |
|
1141 |
#' @note Typically, end-users will not use the `.DefaultLogisticNormalFixedMixture()` |
|
1142 |
#' function. |
|
1143 |
#' @export |
|
1144 |
.DefaultLogisticNormalFixedMixture <- function() { # nolint |
|
1145 | 7x |
LogisticNormalFixedMixture( |
1146 | 7x |
components = list( |
1147 | 7x |
comp1 = ModelParamsNormal( |
1148 | 7x |
mean = c(-0.85, 1), |
1149 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2) |
1150 |
), |
|
1151 | 7x |
comp2 = ModelParamsNormal( |
1152 | 7x |
mean = c(1, 1.5), |
1153 | 7x |
cov = matrix(c(1.2, -0.45, -0.45, 0.6), nrow = 2) |
1154 |
) |
|
1155 |
), |
|
1156 | 7x |
weights = c(0.3, 0.7), |
1157 | 7x |
ref_dose = 50 |
1158 |
) |
|
1159 |
} |
|
1160 | ||
1161 |
# LogisticLogNormalMixture ---- |
|
1162 | ||
1163 |
## class ---- |
|
1164 | ||
1165 |
#' `LogisticLogNormalMixture` |
|
1166 |
#' |
|
1167 |
#' @description `r lifecycle::badge("stable")` |
|
1168 |
#' |
|
1169 |
#' [`LogisticLogNormalMixture`] is the class for standard logistic model with |
|
1170 |
#' online mixture of two bivariate log normal priors. |
|
1171 |
#' |
|
1172 |
#' @details This model can be used when data is arising online from the informative |
|
1173 |
#' component of the prior, at the same time with the data of the trial of |
|
1174 |
#' main interest. Formally, this is achieved by assuming that the probability |
|
1175 |
#' of a DLT at dose \eqn{x} is given by |
|
1176 |
#' \deqn{p(x) = \pi * p1(x) + (1 - \pi) * p2(x)} |
|
1177 |
#' where \eqn{\pi} is the probability for the model \eqn{p(x)} being the same |
|
1178 |
#' as the model \eqn{p1(x)}, which is the informative component of the prior. |
|
1179 |
#' From this model data arises in parallel: at doses `xshare`, DLT information |
|
1180 |
#' `yshare` is observed, in total `nObsshare` data points (see [`DataMixture`]). |
|
1181 |
#' On the other hand, \eqn{1 - \pi}, is the probability of a separate model |
|
1182 |
#' \eqn{p2(x)}. Both components have the same log normal prior distribution, |
|
1183 |
#' which can be specified by the user, and which is inherited from the |
|
1184 |
#' [`LogisticLogNormal`] class. |
|
1185 |
#' |
|
1186 |
#' @slot share_weight (`proportion`)\cr the prior weight for the share component |
|
1187 |
#' \eqn{p_{1}(x)}. |
|
1188 |
#' |
|
1189 |
#' @seealso [`ModelLogNormal`], [`LogisticNormalMixture`], |
|
1190 |
#' [`LogisticNormalFixedMixture`]. |
|
1191 |
#' |
|
1192 |
#' @aliases LogisticLogNormalMixture |
|
1193 |
#' @export |
|
1194 |
#' |
|
1195 |
.LogisticLogNormalMixture <- setClass( |
|
1196 |
Class = "LogisticLogNormalMixture", |
|
1197 |
contains = "LogisticLogNormal", |
|
1198 |
slots = c( |
|
1199 |
share_weight = "numeric" |
|
1200 |
), |
|
1201 |
prototype = prototype( |
|
1202 |
share_weight = 0.1 |
|
1203 |
), |
|
1204 |
validity = v_model_logistic_log_normal_mix |
|
1205 |
) |
|
1206 | ||
1207 |
## constructor ---- |
|
1208 | ||
1209 |
#' @rdname LogisticLogNormalMixture-class |
|
1210 |
#' |
|
1211 |
#' @inheritParams ModelLogNormal |
|
1212 |
#' @param share_weight (`proportion`)\cr the prior weight for the share component. |
|
1213 |
#' |
|
1214 |
#' @export |
|
1215 |
#' @example examples/Model-class-LogisticLogNormalMixture.R |
|
1216 |
#' |
|
1217 |
LogisticLogNormalMixture <- function(mean, |
|
1218 |
cov, |
|
1219 |
ref_dose, |
|
1220 |
share_weight) { |
|
1221 | 20x |
assert_number(ref_dose) |
1222 | ||
1223 | 20x |
params <- ModelParamsNormal(mean, cov) |
1224 | 20x |
.LogisticLogNormalMixture( |
1225 | 20x |
params = params, |
1226 | 20x |
ref_dose = positive_number(ref_dose), |
1227 | 20x |
share_weight = share_weight, |
1228 | 20x |
datamodel = function() { |
1229 | ! |
for (i in 1:nObs) { |
1230 |
# comp gives the component: non-informative (1) or share (2) the two components. |
|
1231 | ! |
stand_log_dose[i] <- log(x[i] / ref_dose) |
1232 | ! |
logit(p[i]) <- alpha0[comp] + alpha1[comp] * stand_log_dose[i] |
1233 | ! |
y[i] ~ dbern(p[i]) |
1234 |
} |
|
1235 | ! |
for (j in 1:nObsshare) { |
1236 | ! |
stand_log_dose_share[j] <- log(xshare[j] / ref_dose) |
1237 | ! |
logit(pshare[j]) <- alpha0[2] + alpha1[2] * stand_log_dose_share[j] |
1238 | ! |
yshare[j] ~ dbern(pshare[j]) |
1239 |
} |
|
1240 |
}, |
|
1241 | 20x |
priormodel = function() { |
1242 | ! |
for (k in 1:2) { |
1243 | ! |
theta[k, 1:2] ~ dmnorm(mean, prec) |
1244 | ! |
alpha0[k] <- theta[k, 1] |
1245 | ! |
alpha1[k] <- exp(theta[k, 2]) |
1246 |
} |
|
1247 |
# The component indicator. |
|
1248 | ! |
comp ~ dcat(cat_probs) |
1249 |
}, |
|
1250 | 20x |
modelspecs = function(from_prior) { |
1251 | 2x |
ms <- list( |
1252 | 2x |
cat_probs = c(1 - share_weight, share_weight), |
1253 | 2x |
mean = params@mean, |
1254 | 2x |
prec = params@prec |
1255 |
) |
|
1256 | 2x |
if (!from_prior) { |
1257 | 1x |
ms$ref_dose <- ref_dose |
1258 |
} |
|
1259 | 2x |
ms |
1260 |
}, |
|
1261 | 20x |
init = function() { |
1262 | 2x |
list(theta = matrix(c(0, 0, 1, 1), nrow = 2)) |
1263 |
}, |
|
1264 | 20x |
datanames = c("nObs", "y", "x", "nObsshare", "yshare", "xshare"), |
1265 | 20x |
sample = c("alpha0", "alpha1", "comp") |
1266 |
) |
|
1267 |
} |
|
1268 | ||
1269 |
## default constructor ---- |
|
1270 | ||
1271 |
#' @rdname LogisticLogNormalMixture-class |
|
1272 |
#' @note Typically, end users will not use the `.DefaultLogNormalMixture()` function. |
|
1273 |
#' @export |
|
1274 |
.DefaultLogisticLogNormalMixture <- function() { # nolint |
|
1275 | 7x |
LogisticLogNormalMixture( |
1276 | 7x |
share_weight = 0.1, |
1277 | 7x |
mean = c(-0.85, 1), |
1278 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
1279 | 7x |
ref_dose = 50 |
1280 |
) |
|
1281 |
} |
|
1282 | ||
1283 |
# DualEndpoint ---- |
|
1284 | ||
1285 |
## class ---- |
|
1286 | ||
1287 |
#' `DualEndpoint` |
|
1288 |
#' |
|
1289 |
#' @description `r lifecycle::badge("experimental")` |
|
1290 |
#' |
|
1291 |
#' [`DualEndpoint`] is the general class for the dual endpoint model. |
|
1292 |
#' |
|
1293 |
#' @details The idea of the dual-endpoint models is to model not only the |
|
1294 |
#' dose-toxicity relationship, but also to model, at the same time, the |
|
1295 |
#' relationship of a PD biomarker with the dose. The sub-classes of this class |
|
1296 |
#' define how the dose-biomarker relationship is parametrized. This class here |
|
1297 |
#' shall contain all the common features to reduce duplicate code. |
|
1298 |
#' (This class however, must not be virtual as we need to create objects |
|
1299 |
#' of it during the construction of subclass objects.) |
|
1300 |
#' |
|
1301 |
#' The dose-toxicity relationship is modeled with probit regression model |
|
1302 |
#' \deqn{probit[p(x)] = betaZ1 + betaZ2 * x/x*,} |
|
1303 |
#' or |
|
1304 |
#' \deqn{probit[p(x)] = betaZ1 + betaZ2 * log(x/x*),} |
|
1305 |
#' in case when the option `use_log_dose` is `TRUE`. |
|
1306 |
#' Here, \eqn{p(x)} is the probability of observing a DLT for a given |
|
1307 |
#' dose \eqn{x} and \eqn{x*} is the reference dose. |
|
1308 |
#' The prior \deqn{(betaZ1, log(betaZ2)) ~ Normal(mean, cov).} |
|
1309 |
#' |
|
1310 |
#' For the biomarker response \eqn{w} at a dose \eqn{x}, we assume |
|
1311 |
#' \deqn{w(x) ~ Normal(f(x), sigma2W),} |
|
1312 |
#' where \eqn{f(x)} is a function of the dose \eqn{x}, which is further |
|
1313 |
#' specified in sub-classes. The biomarker variance \eqn{sigma2W} can be fixed |
|
1314 |
#' or assigned an Inverse-Gamma prior distribution; see the details below under |
|
1315 |
#' slot `sigma2W`. |
|
1316 |
#' |
|
1317 |
#' Finally, the two endpoints \eqn{y} (the binary DLT variable) and \eqn{w} |
|
1318 |
#' (the biomarker) can be correlated, by assuming a correlation of level |
|
1319 |
#' \eqn{rho} between the underlying continuous latent toxicity variable \eqn{z} |
|
1320 |
#' and the biomarker \eqn{w}. Again, this correlation can be fixed or assigned |
|
1321 |
#' a prior distribution from the scaled Beta family; see the details below |
|
1322 |
#' under slot `rho`. |
|
1323 |
#' |
|
1324 |
#' Please see the example vignette by typing `crmPackExample()` for a full example. |
|
1325 |
#' |
|
1326 |
#' @slot betaZ_params (`ModelParamsNormal`)\cr for the probit toxicity model, it |
|
1327 |
#' contains the prior mean, covariance matrix and precision matrix which is |
|
1328 |
#' internally calculated as an inverse of the covariance matrix. |
|
1329 |
#' @slot ref_dose (`positive_number`)\cr for the probit toxicity model, the |
|
1330 |
#' reference dose. |
|
1331 |
#' @slot use_log_dose (`flag`)\cr for the probit toxicity model, whether a log |
|
1332 |
#' transformation of the (standardized) dose should be used? |
|
1333 |
#' @slot sigma2W (`numeric`)\cr the biomarker variance. Either a fixed value or |
|
1334 |
#' Inverse-Gamma distribution parameters, i.e. vector with two elements named |
|
1335 |
#' `a` and `b`. |
|
1336 |
#' @slot rho (`numeric`)\cr either a fixed value for the correlation |
|
1337 |
#' (between `-1` and `1`), or a named vector with two elements named `a` and `b` |
|
1338 |
#' for the Beta prior on the transformation `kappa = (rho + 1) / 2`, which is |
|
1339 |
#' in `(0, 1)`. For example, `a = 1, b = 1` leads to a uniform prior on `rho`. |
|
1340 |
#' @slot use_fixed (`logical`)\cr indicates whether a fixed value for `sigma2W` |
|
1341 |
#' or `rho` (for each parameter separately) is used or not. This slot is |
|
1342 |
#' needed for internal purposes and must not be touched by the user. |
|
1343 |
#' |
|
1344 |
#' @seealso [`DualEndpointRW`], [`DualEndpointBeta`], [`DualEndpointEmax`]. |
|
1345 |
#' |
|
1346 |
#' @aliases DualEndpoint |
|
1347 |
#' @export |
|
1348 |
#' |
|
1349 |
.DualEndpoint <- setClass( |
|
1350 |
Class = "DualEndpoint", |
|
1351 |
slots = c( |
|
1352 |
betaZ_params = "ModelParamsNormal", |
|
1353 |
ref_dose = "positive_number", |
|
1354 |
use_log_dose = "logical", |
|
1355 |
sigma2W = "numeric", |
|
1356 |
rho = "numeric", |
|
1357 |
use_fixed = "logical" |
|
1358 |
), |
|
1359 |
prototype = prototype( |
|
1360 |
betaZ_params = ModelParamsNormal(mean = c(0, 1), cov = diag(2)), |
|
1361 |
ref_dose = positive_number(1), |
|
1362 |
use_log_dose = FALSE, |
|
1363 |
sigma2W = 1, |
|
1364 |
rho = 0, |
|
1365 |
use_fixed = c(sigma2W = TRUE, rho = TRUE) |
|
1366 |
), |
|
1367 |
contains = "GeneralModel", |
|
1368 |
validity = v_model_dual_endpoint |
|
1369 |
) |
|
1370 | ||
1371 |
## constructor ---- |
|
1372 | ||
1373 |
#' @rdname DualEndpoint-class |
|
1374 |
#' |
|
1375 |
#' @param mean (`numeric`)\cr for the probit toxicity model, the prior mean vector. |
|
1376 |
#' @param cov (`matrix`)\cr for the probit toxicity model, the prior covariance |
|
1377 |
#' matrix. The precision matrix is internally calculated as an inverse of `cov`. |
|
1378 |
#' @param ref_dose (`number`)\cr for the probit toxicity model, the reference |
|
1379 |
#' dose \eqn{x*} (strictly positive number). |
|
1380 |
#' @param use_log_dose (`flag`)\cr for the probit toxicity model, whether a log |
|
1381 |
#' transformation of the (standardized) dose should be used? |
|
1382 |
#' @param sigma2W (`numeric`)\cr the biomarker variance. Either a fixed value or |
|
1383 |
#' Inverse-Gamma distribution parameters, i.e. vector with two elements named |
|
1384 |
#' `a` and `b`. |
|
1385 |
#' @param rho (`numeric`)\cr either a fixed value for the correlation |
|
1386 |
#' (between `-1` and `1`), or a named vector with two elements named `a` and `b` |
|
1387 |
#' for the Beta prior on the transformation `kappa = (rho + 1) / 2`, which is |
|
1388 |
#' in `(0, 1)`. For example, `a = 1, b = 1` leads to a uniform prior on `rho`. |
|
1389 |
#' |
|
1390 |
#' @export |
|
1391 |
#' |
|
1392 |
DualEndpoint <- function(mean, |
|
1393 |
cov, |
|
1394 |
ref_dose = 1, |
|
1395 |
use_log_dose = FALSE, |
|
1396 |
sigma2W, |
|
1397 |
rho) { |
|
1398 | 201x |
assert_number(ref_dose) |
1399 | 201x |
assert_numeric(sigma2W, min.len = 1, max.len = 2) |
1400 | 201x |
assert_numeric(rho, min.len = 1, max.len = 2) |
1401 | ||
1402 | 201x |
use_fixed <- c( |
1403 | 201x |
sigma2W = test_number(sigma2W), |
1404 | 201x |
rho = test_number(rho) |
1405 |
) |
|
1406 | 201x |
beta_z_params <- ModelParamsNormal(mean, cov) |
1407 | ||
1408 | 201x |
datamodel <- function() { |
1409 | ! |
for (i in 1:nObs) { |
1410 |
# The toxicity model. |
|
1411 | ! |
stand_dose_temp[i] <- x[i] / ref_dose |
1412 | ! |
stand_dose[i] <- ifelse(use_log_dose, log(stand_dose_temp[i]), stand_dose_temp[i]) |
1413 | ! |
meanZ[i] <- betaZ[1] + betaZ[2] * stand_dose[i] |
1414 | ! |
z[i] ~ dnorm(meanZ[i], 1) |
1415 | ! |
y[i] ~ dinterval(z[i], 0) |
1416 | ||
1417 |
# The conditional biomarker model; betaW defined in subclasses! |
|
1418 | ! |
condMeanW[i] <- betaW[xLevel[i]] + rho / sqrt(precW) * (z[i] - meanZ[i]) |
1419 | ! |
w[i] ~ dnorm(condMeanW[i], condPrecW) |
1420 |
} |
|
1421 |
} |
|
1422 | 201x |
priormodel <- function() { |
1423 |
# Priors for betaW defined in subclasses! |
|
1424 | ! |
theta ~ dmnorm(betaZ_mean, betaZ_prec) |
1425 | ! |
betaZ[1] <- theta[1] |
1426 | ! |
betaZ[2] <- exp(theta[2]) |
1427 |
# Conditional precision for biomarker. |
|
1428 |
# Code for `precW` and `rho` will be added by |
|
1429 |
# `h_model_dual_endpoint_sigma2W()`, `h_model_dual_endpoint_rho()` helpers, below. |
|
1430 | ! |
condPrecW <- precW / (1 - pow(rho, 2)) |
1431 |
} |
|
1432 | 201x |
modelspecs_prior <- list( |
1433 | 201x |
betaZ_mean = beta_z_params@mean, |
1434 | 201x |
betaZ_prec = beta_z_params@prec |
1435 |
) |
|
1436 | ||
1437 | 201x |
comp <- list( |
1438 | 201x |
priormodel = priormodel, |
1439 | 201x |
modelspecs = modelspecs_prior, |
1440 | 201x |
init = NULL, |
1441 | 201x |
sample = "betaZ" |
1442 |
) |
|
1443 | ||
1444 |
# Update model components with regard to biomarker regression variance. |
|
1445 | 201x |
comp <- h_model_dual_endpoint_sigma2W( |
1446 | 201x |
use_fixed["sigma2W"], |
1447 | 201x |
sigma2W = sigma2W, |
1448 | 201x |
comp = comp |
1449 |
) |
|
1450 | ||
1451 |
# Update model components with regard to DLT and biomarker correlation. |
|
1452 | 201x |
comp <- h_model_dual_endpoint_rho( |
1453 | 201x |
use_fixed["rho"], |
1454 | 201x |
rho = rho, |
1455 | 201x |
comp = comp |
1456 |
) |
|
1457 | ||
1458 | 201x |
.DualEndpoint( |
1459 | 201x |
betaZ_params = beta_z_params, |
1460 | 201x |
ref_dose = positive_number(ref_dose), |
1461 | 201x |
use_log_dose = use_log_dose, |
1462 | 201x |
sigma2W = sigma2W, |
1463 | 201x |
rho = rho, |
1464 | 201x |
use_fixed = use_fixed, |
1465 | 201x |
datamodel = datamodel, |
1466 | 201x |
priormodel = comp$priormodel, |
1467 | 201x |
modelspecs = function(from_prior) { |
1468 | 59x |
if (!from_prior) { |
1469 | 34x |
comp$modelspecs$ref_dose <- ref_dose |
1470 | 34x |
comp$modelspecs$use_log_dose <- use_log_dose |
1471 |
} |
|
1472 | 59x |
comp$modelspecs |
1473 |
}, |
|
1474 | 201x |
init = function(y) { |
1475 | 49x |
c(comp$init, list(z = ifelse(y == 0, -1, 1), theta = c(0, 1))) |
1476 |
}, |
|
1477 | 201x |
datanames = c("nObs", "w", "x", "xLevel", "y"), |
1478 | 201x |
sample = comp$sample |
1479 |
) |
|
1480 |
} |
|
1481 | ||
1482 |
## default constructor ---- |
|
1483 | ||
1484 |
#' @rdname DualEndpoint-class |
|
1485 |
#' @note Typically, end users will not use the `.DefaultDualEndpoint()` function. |
|
1486 |
#' @export |
|
1487 |
.DefaultDualEndpoint <- function() { |
|
1488 | 4x |
stop(paste0("Class DualEndpoint cannot be instantiated directly. Please use one of its subclasses instead.")) |
1489 |
} |
|
1490 | ||
1491 |
# DualEndpointRW ---- |
|
1492 | ||
1493 |
## class ---- |
|
1494 | ||
1495 |
#' `DualEndpointRW` |
|
1496 |
#' |
|
1497 |
#' @description `r lifecycle::badge("experimental")` |
|
1498 |
#' |
|
1499 |
#' [`DualEndpointRW`] is the class for the dual endpoint model with random walk |
|
1500 |
#' prior for biomarker. |
|
1501 |
#' |
|
1502 |
#' |
|
1503 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
1504 |
#' relationship \eqn{f(x)} is modelled by a non-parametric random walk of first |
|
1505 |
#' or second order. That means, for the first order random walk we assume |
|
1506 |
#' \deqn{betaW_i - betaW_i-1 ~ Normal(0, (x_i - x_i-1) * sigma2betaW),} |
|
1507 |
#' where \eqn{betaW_i = f(x_i)} is the biomarker mean at the \eqn{i}-th dose |
|
1508 |
#' gridpoint \eqn{x_i}. |
|
1509 |
#' For the second order random walk, the second-order differences instead of |
|
1510 |
#' the first-order differences of the biomarker means follow the normal distribution |
|
1511 |
#' with \eqn{0} mean and \eqn{2 * (x_i - x_i-2) * sigma2betaW} variance. |
|
1512 |
#' |
|
1513 |
#' The variance parameter \eqn{sigma2betaW} is important because it steers the |
|
1514 |
#' smoothness of the function \eqn{f(x)}, i.e.: if it is large, then \eqn{f(x)} |
|
1515 |
#' will be very wiggly; if it is small, then \eqn{f(x)} will be smooth. |
|
1516 |
#' This parameter can either be a fixed value or assigned an inverse gamma prior |
|
1517 |
#' distribution. |
|
1518 |
#' |
|
1519 |
#' @note Non-equidistant dose grids can be used now, because the difference |
|
1520 |
#' \eqn{x_i - x_i-1} is included in the modelling assumption above. |
|
1521 |
#' Please note that due to impropriety of the random walk prior distributions, |
|
1522 |
#' it is not possible to produce MCMC samples with empty data objects (i.e., |
|
1523 |
#' sample from the prior). This is not a bug, but a theoretical feature of this |
|
1524 |
#' model. |
|
1525 |
#' |
|
1526 |
#' @slot sigma2betaW (`numeric`)\cr the prior variance factor of the random walk |
|
1527 |
#' prior for the biomarker model. Either a fixed value or Inverse-Gamma distribution |
|
1528 |
#' parameters, i.e. vector with two elements named `a` and `b`. |
|
1529 |
#' @slot rw1 (`flag`)\cr for specifying the random walk prior on the biomarker |
|
1530 |
#' level. When `TRUE`, random walk of first order is used. Otherwise, the |
|
1531 |
#' random walk of second order is used. |
|
1532 |
#' |
|
1533 |
#' @seealso [`DualEndpoint`], [`DualEndpointBeta`], [`DualEndpointEmax`]. |
|
1534 |
#' |
|
1535 |
#' @aliases DualEndpointRW |
|
1536 |
#' @export |
|
1537 |
#' |
|
1538 |
.DualEndpointRW <- setClass( |
|
1539 |
Class = "DualEndpointRW", |
|
1540 |
slots = c( |
|
1541 |
sigma2betaW = "numeric", |
|
1542 |
rw1 = "logical" |
|
1543 |
), |
|
1544 |
prototype = prototype( |
|
1545 |
sigma2betaW = 1, |
|
1546 |
rw1 = TRUE, |
|
1547 |
use_fixed = c( |
|
1548 |
sigma2W = TRUE, |
|
1549 |
rho = TRUE, |
|
1550 |
sigma2betaW = TRUE |
|
1551 |
) |
|
1552 |
), |
|
1553 |
contains = "DualEndpoint", |
|
1554 |
validity = v_model_dual_endpoint_rw |
|
1555 |
) |
|
1556 | ||
1557 |
## constructor ---- |
|
1558 | ||
1559 |
#' @rdname DualEndpointRW-class |
|
1560 |
#' |
|
1561 |
#' @param sigma2betaW (`numeric`)\cr the prior variance factor of the random walk |
|
1562 |
#' prior for the biomarker model. Either a fixed value or Inverse-Gamma distribution |
|
1563 |
#' parameters, i.e. vector with two elements named `a` and `b`. |
|
1564 |
#' @param rw1 (`flag`)\cr for specifying the random walk prior on the biomarker |
|
1565 |
#' level. When `TRUE`, random walk of first order is used. Otherwise, the |
|
1566 |
#' random walk of second order is used. |
|
1567 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
1568 |
#' |
|
1569 |
#' @export |
|
1570 |
#' @example examples/Model-class-DualEndpointRW.R |
|
1571 |
#' |
|
1572 |
DualEndpointRW <- function(sigma2betaW, |
|
1573 |
rw1 = TRUE, |
|
1574 |
...) { |
|
1575 | 52x |
assert_numeric(sigma2betaW, min.len = 1, max.len = 2) |
1576 | 52x |
assert_flag(rw1) |
1577 | ||
1578 | 52x |
start <- DualEndpoint(...) |
1579 | 52x |
start@use_fixed["sigma2betaW"] <- length(sigma2betaW) == 1L |
1580 | ||
1581 | 52x |
priormodel <- if (rw1) { |
1582 | 43x |
function() { |
1583 |
# The 1st order differences. |
|
1584 |
# Essentially dflat(), which is not available in JAGS. |
|
1585 | ! |
betaW[1] ~ dnorm(0, 0.000001) |
1586 | ! |
for (i in 2:nGrid) { |
1587 | ! |
delta[i - 1] ~ dnorm(0, precBetaW / (doseGrid[i] - doseGrid[i - 1])) |
1588 | ! |
betaW[i] <- betaW[i - 1] + delta[i - 1] |
1589 |
} |
|
1590 |
} |
|
1591 |
} else { |
|
1592 | 9x |
function() { |
1593 |
# The 2nd order differences. |
|
1594 | ! |
delta[1] ~ dnorm(0, 0.000001) |
1595 | ! |
betaW[1] ~ dnorm(0, 0.000001) |
1596 | ! |
betaW[2] <- betaW[1] + delta[1] |
1597 | ! |
for (i in 3:nGrid) { |
1598 |
# delta2: differences of the differences of betaW follow normal dist. |
|
1599 | ! |
delta2[i - 2] ~ dnorm(0, 2 * precBetaW / (doseGrid[i] - doseGrid[i - 2])) |
1600 | ! |
delta[i - 1] <- delta[i - 2] + delta2[i - 2] |
1601 | ! |
betaW[i] <- betaW[i - 1] + delta[i - 1] |
1602 |
} |
|
1603 |
} |
|
1604 |
} |
|
1605 | 52x |
start@priormodel <- h_jags_join_models(start@priormodel, priormodel) |
1606 | 52x |
start@datanames_prior <- c("nGrid", "doseGrid") |
1607 | 52x |
start@sample <- c(start@sample, "betaW", "delta") |
1608 | ||
1609 |
# Update model components with regard to biomarker regression variance. |
|
1610 | 52x |
start <- h_model_dual_endpoint_sigma2betaW( |
1611 | 52x |
start@use_fixed["sigma2betaW"], |
1612 | 52x |
sigma2betaW = sigma2betaW, |
1613 | 52x |
de = start |
1614 |
) |
|
1615 | ||
1616 | 52x |
.DualEndpointRW( |
1617 | 52x |
start, |
1618 | 52x |
sigma2betaW = sigma2betaW, |
1619 | 52x |
rw1 = rw1 |
1620 |
) |
|
1621 |
} |
|
1622 | ||
1623 |
## default constructor ---- |
|
1624 | ||
1625 |
#' @rdname DualEndpointRW-class |
|
1626 |
#' @note Typically, end users will not use the `.DefaultDualEndpointRW()` function. |
|
1627 |
#' @export |
|
1628 |
.DefaultDualEndpointRW <- function() { |
|
1629 | 7x |
DualEndpointRW( |
1630 | 7x |
mean = c(0, 1), |
1631 | 7x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
1632 | 7x |
sigma2W = c(a = 0.1, b = 0.1), |
1633 | 7x |
rho = c(a = 1, b = 1), |
1634 | 7x |
sigma2betaW = 0.01, |
1635 | 7x |
rw1 = TRUE |
1636 |
) |
|
1637 |
} |
|
1638 | ||
1639 |
# DualEndpointBeta ---- |
|
1640 | ||
1641 |
## class ---- |
|
1642 | ||
1643 |
#' `DualEndpointBeta` |
|
1644 |
#' |
|
1645 |
#' @description `r lifecycle::badge("experimental")` |
|
1646 |
#' |
|
1647 |
#' [`DualEndpointBeta`] is the class for the dual endpoint model with beta |
|
1648 |
#' function for dose-biomarker relationship. |
|
1649 |
#' |
|
1650 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
1651 |
#' relationship \eqn{f(x)} is modelled by a parametric, rescaled beta density |
|
1652 |
#' function: |
|
1653 |
#' \deqn{f(x) = E0 + (Emax - E0) * Beta(delta1, delta2) * (x/x*)^{delta1} * (1 - x/x*)^{delta2},} |
|
1654 |
#' where \eqn{x*} is the maximum dose (end of the dose range to be considered), |
|
1655 |
#' \eqn{delta1} and \eqn{delta2} are the two beta function parameters, and |
|
1656 |
#' \eqn{E0}, \eqn{Emax} are the minimum and maximum levels, respectively. |
|
1657 |
#' For ease of interpretation, we use the parametrization based on \eqn{delta1} |
|
1658 |
#' and the mode, where |
|
1659 |
#' \deqn{mode = delta1 / (delta1 + delta2),} |
|
1660 |
#' so that multiplying this by \eqn{x*} gives the mode on the dose grid. |
|
1661 |
#' |
|
1662 |
#' All parameters can currently be assigned uniform distributions or be fixed |
|
1663 |
#' in advance. Note that \code{E0} and \code{Emax} can have negative values or |
|
1664 |
#' uniform distributions reaching into negative range, while \code{delta1} and |
|
1665 |
#' \code{mode} must be positive or have uniform distributions in the positive |
|
1666 |
#' range. |
|
1667 |
#' |
|
1668 |
#' @slot E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
1669 |
#' parameters. |
|
1670 |
#' @slot Emax (`numeric`)\cr either a fixed number or the two uniform |
|
1671 |
#' distribution parameters. |
|
1672 |
#' @slot delta1 (`numeric`)\cr either a fixed positive number or the two |
|
1673 |
#' parameters of the uniform distribution, that can take only positive values. |
|
1674 |
#' @slot mode (`numeric`)\cr either a fixed positive number or the two |
|
1675 |
#' parameters of the uniform distribution, that can take only positive values. |
|
1676 |
#' @slot ref_dose_beta (`positive_number`)\cr the reference dose \eqn{x*} (note |
|
1677 |
#' that this is different from the `ref_dose` in the inherited [`DualEndpoint`] |
|
1678 |
#' model). |
|
1679 |
#' |
|
1680 |
#' @seealso [`DualEndpoint`], [`DualEndpointRW`], [`DualEndpointEmax`]. |
|
1681 |
#' |
|
1682 |
#' @aliases DualEndpointBeta |
|
1683 |
#' @export |
|
1684 |
#' |
|
1685 |
.DualEndpointBeta <- setClass( |
|
1686 |
Class = "DualEndpointBeta", |
|
1687 |
slots = c( |
|
1688 |
E0 = "numeric", |
|
1689 |
Emax = "numeric", |
|
1690 |
delta1 = "numeric", |
|
1691 |
mode = "numeric", |
|
1692 |
ref_dose_beta = "positive_number" |
|
1693 |
), |
|
1694 |
prototype = prototype( |
|
1695 |
E0 = c(0, 100), |
|
1696 |
Emax = c(0, 500), |
|
1697 |
delta1 = c(0, 5), |
|
1698 |
mode = c(1, 15), |
|
1699 |
ref_dose_beta = positive_number(1), |
|
1700 |
use_fixed = c( |
|
1701 |
sigma2W = TRUE, |
|
1702 |
rho = TRUE, |
|
1703 |
E0 = FALSE, |
|
1704 |
Emax = FALSE, |
|
1705 |
delta1 = FALSE, |
|
1706 |
mode = FALSE |
|
1707 |
) |
|
1708 |
), |
|
1709 |
contains = "DualEndpoint", |
|
1710 |
validity = v_model_dual_endpoint_beta |
|
1711 |
) |
|
1712 | ||
1713 |
## constructor ---- |
|
1714 | ||
1715 |
#' @rdname DualEndpointBeta-class |
|
1716 |
#' |
|
1717 |
#' @param E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
1718 |
#' parameters. |
|
1719 |
#' @param Emax (`numeric`)\cr either a fixed number or the two uniform distribution |
|
1720 |
#' parameters. |
|
1721 |
#' @param delta1 (`numeric`)\cr either a fixed positive number or the two parameters |
|
1722 |
#' of the uniform distribution, that can take only positive values. |
|
1723 |
#' @param mode (`numeric`)\cr either a fixed positive number or the two parameters |
|
1724 |
#' of the uniform distribution, that can take only positive values. |
|
1725 |
#' @param ref_dose_beta (`number`)\cr the reference dose \eqn{x*} (strictly |
|
1726 |
#' positive number). Note that this is different from the `ref_dose` in the |
|
1727 |
#' inherited [`DualEndpoint`] model). |
|
1728 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
1729 |
#' |
|
1730 |
#' @export |
|
1731 |
#' @example examples/Model-class-DualEndpointBeta.R |
|
1732 |
#' |
|
1733 |
DualEndpointBeta <- function(E0, |
|
1734 |
Emax, |
|
1735 |
delta1, |
|
1736 |
mode, |
|
1737 |
ref_dose_beta = 1, |
|
1738 |
...) { |
|
1739 | 26x |
assert_numeric(E0, min.len = 1, max.len = 2) |
1740 | 26x |
assert_numeric(Emax, min.len = 1, max.len = 2) |
1741 | 26x |
assert_numeric(delta1, min.len = 1, max.len = 2) |
1742 | 26x |
assert_numeric(mode, min.len = 1, max.len = 2) |
1743 | 26x |
assert_number(ref_dose_beta) |
1744 | ||
1745 | 26x |
start <- DualEndpoint(...) |
1746 | ||
1747 | 26x |
ms <- start@modelspecs |
1748 | 26x |
start@modelspecs <- function(from_prior) { |
1749 | 8x |
c(list(ref_dose_beta = ref_dose_beta), ms(from_prior)) |
1750 |
} |
|
1751 | 26x |
start@datanames_prior <- c("nGrid", "doseGrid") |
1752 | 26x |
start@sample <- c(start@sample, "betaW") |
1753 | ||
1754 | 26x |
start <- h_model_dual_endpoint_beta( |
1755 | 26x |
param = E0, |
1756 | 26x |
param_name = "E0", |
1757 | 26x |
priormodel = function() { |
1758 | ! |
E0 ~ dunif(E0_low, E0_high) |
1759 |
}, |
|
1760 | 26x |
de = start |
1761 |
) |
|
1762 | ||
1763 | 26x |
start <- h_model_dual_endpoint_beta( |
1764 | 26x |
param = Emax, |
1765 | 26x |
param_name = "Emax", |
1766 | 26x |
priormodel = function() { |
1767 | ! |
Emax ~ dunif(Emax_low, Emax_high) |
1768 |
}, |
|
1769 | 26x |
de = start |
1770 |
) |
|
1771 | ||
1772 | 26x |
start <- h_model_dual_endpoint_beta( |
1773 | 26x |
param = delta1, |
1774 | 26x |
param_name = "delta1", |
1775 | 26x |
priormodel = function() { |
1776 | ! |
delta1 ~ dunif(delta1_low, delta1_high) |
1777 |
}, |
|
1778 | 26x |
de = start |
1779 |
) |
|
1780 | ||
1781 | 26x |
start <- h_model_dual_endpoint_beta( |
1782 | 26x |
param = mode, |
1783 | 26x |
param_name = "mode", |
1784 | 26x |
priormodel = function() { |
1785 | ! |
mode ~ dunif(mode_low, mode_high) |
1786 |
}, |
|
1787 | 26x |
de = start |
1788 |
) |
|
1789 | ||
1790 | 26x |
start@priormodel <- h_jags_join_models( |
1791 | 26x |
start@priormodel, |
1792 | 26x |
function() { |
1793 |
# delta2 <- delta1 * (1 - (mode/ref_dose_beta)) / (mode/ref_dose_beta) # nolint |
|
1794 | ! |
delta2 <- delta1 * (ref_dose_beta / mode - 1) |
1795 |
# betafun <- (delta1 + delta2)^(delta1 + delta2) * delta1^(- delta1) * delta2^(- delta2) # nolint |
|
1796 | ! |
betafun <- (1 + delta2 / delta1)^delta1 * (delta1 / delta2 + 1)^delta2 |
1797 | ! |
for (i in 1:nGrid) { |
1798 | ! |
stand_dose_beta[i] <- doseGrid[i] / ref_dose_beta |
1799 | ! |
betaW[i] <- E0 + (Emax - E0) * betafun * stand_dose_beta[i]^delta1 * (1 - stand_dose_beta[i])^delta2 |
1800 |
} |
|
1801 |
} |
|
1802 |
) |
|
1803 | ||
1804 | 26x |
.DualEndpointBeta( |
1805 | 26x |
start, |
1806 | 26x |
E0 = E0, |
1807 | 26x |
Emax = Emax, |
1808 | 26x |
delta1 = delta1, |
1809 | 26x |
mode = mode, |
1810 | 26x |
ref_dose_beta = positive_number(ref_dose_beta) |
1811 |
) |
|
1812 |
} |
|
1813 | ||
1814 |
## default constructor ---- |
|
1815 | ||
1816 |
#' @rdname DualEndpointBeta-class |
|
1817 |
#' @note Typically, end users will not use the `.DefaultDualEndpointBeta()` function. |
|
1818 |
#' @export |
|
1819 |
.DefaultDualEndpointBeta <- function() { |
|
1820 | 7x |
DualEndpointBeta( |
1821 | 7x |
mean = c(0, 1), |
1822 | 7x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
1823 | 7x |
ref_dose = 10, |
1824 | 7x |
use_log_dose = TRUE, |
1825 | 7x |
sigma2W = c(a = 0.1, b = 0.1), |
1826 | 7x |
rho = c(a = 1, b = 1), |
1827 | 7x |
E0 = c(0, 100), |
1828 | 7x |
Emax = c(0, 500), |
1829 | 7x |
delta1 = c(0, 5), |
1830 | 7x |
mode = c(1, 15), |
1831 | 7x |
ref_dose_beta = 1000 |
1832 |
) |
|
1833 |
} |
|
1834 | ||
1835 |
# DualEndpointEmax ---- |
|
1836 | ||
1837 |
## class ---- |
|
1838 | ||
1839 |
#' `DualEndpointEmax` |
|
1840 |
#' |
|
1841 |
#' @description `r lifecycle::badge("experimental")` |
|
1842 |
#' |
|
1843 |
#' [`DualEndpointEmax`] is the class for the dual endpoint model with `Emax` |
|
1844 |
#' function for dose-biomarker relationship. |
|
1845 |
#' |
|
1846 |
#' @details This class extends the [`DualEndpoint`] class so that the dose-biomarker |
|
1847 |
#' relationship \eqn{f(x)} is modelled by a parametric `Emax` function: |
|
1848 |
#' \deqn{f(x) = E0 + [(Emax - E0) * (x/x*)]/[ED50 + (x/x*)],} |
|
1849 |
#' where \eqn{x*} is a reference dose, \eqn{E0} and \eqn{Emax} are the minimum |
|
1850 |
#' and maximum levels for the biomarker, and \eqn{ED50} is the dose achieving |
|
1851 |
#' half of the maximum effect \eqn{0.5 * Emax}. |
|
1852 |
#' All parameters can currently be assigned uniform distributions or be fixed. |
|
1853 |
#' |
|
1854 |
#' @slot E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
1855 |
#' parameters. |
|
1856 |
#' @slot Emax (`numeric`)\cr either a fixed number or the two uniform |
|
1857 |
#' distribution parameters. |
|
1858 |
#' @slot ED50 (`numeric`)\cr either a fixed number or the two uniform |
|
1859 |
#' distribution parameters. |
|
1860 |
#' @slot ref_dose_emax (`positive_number`)\cr the reference dose \eqn{x*} (note |
|
1861 |
#' that this is different from the `ref_dose` in the inherited [`DualEndpoint`] |
|
1862 |
#' model). |
|
1863 |
#' |
|
1864 |
#' @seealso [`DualEndpoint`], [`DualEndpointRW`], [`DualEndpointBeta`]. |
|
1865 |
#' |
|
1866 |
#' @aliases DualEndpointEmax |
|
1867 |
#' @export |
|
1868 |
#' |
|
1869 |
.DualEndpointEmax <- setClass( |
|
1870 |
Class = "DualEndpointEmax", |
|
1871 |
slots = c( |
|
1872 |
E0 = "numeric", |
|
1873 |
Emax = "numeric", |
|
1874 |
ED50 = "numeric", |
|
1875 |
ref_dose_emax = "numeric" |
|
1876 |
), |
|
1877 |
prototype = prototype( |
|
1878 |
E0 = c(0, 100), |
|
1879 |
Emax = c(0, 500), |
|
1880 |
ED50 = c(0, 500), |
|
1881 |
ref_dose_emax = positive_number(1), |
|
1882 |
use_fixed = c( |
|
1883 |
sigma2W = TRUE, |
|
1884 |
rho = TRUE, |
|
1885 |
E0 = FALSE, |
|
1886 |
Emax = FALSE, |
|
1887 |
ED50 = FALSE |
|
1888 |
) |
|
1889 |
), |
|
1890 |
contains = "DualEndpoint", |
|
1891 |
validity = v_model_dual_endpoint_emax |
|
1892 |
) |
|
1893 | ||
1894 |
## constructor ---- |
|
1895 | ||
1896 |
#' @rdname DualEndpointEmax-class |
|
1897 |
#' |
|
1898 |
#' @param E0 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
1899 |
#' parameters. |
|
1900 |
#' @param Emax (`numeric`)\cr either a fixed number or the two uniform distribution |
|
1901 |
#' parameters. |
|
1902 |
#' @param ED50 (`numeric`)\cr either a fixed number or the two uniform distribution |
|
1903 |
#' parameters. |
|
1904 |
#' @param ref_dose_emax (`number`)\cr the reference dose \eqn{x*} (strictly |
|
1905 |
#' positive number). Note that this is different from the `ref_dose` in the |
|
1906 |
#' inherited [`DualEndpoint`] model). |
|
1907 |
#' @param ... parameters passed to [DualEndpoint()]. |
|
1908 |
#' |
|
1909 |
#' @export |
|
1910 |
#' @example examples/Model-class-DualEndpointEmax.R |
|
1911 |
#' |
|
1912 |
DualEndpointEmax <- function(E0, |
|
1913 |
Emax, |
|
1914 |
ED50, |
|
1915 |
ref_dose_emax = 1, |
|
1916 |
...) { |
|
1917 | 23x |
assert_numeric(E0, min.len = 1, max.len = 2) |
1918 | 23x |
assert_numeric(Emax, min.len = 1, max.len = 2) |
1919 | 23x |
assert_numeric(ED50, min.len = 1, max.len = 2) |
1920 | 23x |
assert_number(ref_dose_emax) |
1921 | ||
1922 | 23x |
start <- DualEndpoint(...) |
1923 | ||
1924 | 23x |
start@sample <- c(start@sample, "betaW") |
1925 | 23x |
start@datanames_prior <- c("nGrid", "doseGrid") |
1926 | 23x |
ms <- start@modelspecs |
1927 | 23x |
start@modelspecs <- function(from_prior) { |
1928 | 8x |
c(list(ref_dose_emax = ref_dose_emax), ms(from_prior)) |
1929 |
} |
|
1930 | ||
1931 | 23x |
start <- h_model_dual_endpoint_beta( |
1932 | 23x |
param = E0, |
1933 | 23x |
param_name = "E0", |
1934 | 23x |
priormodel = function() { |
1935 | ! |
E0 ~ dunif(E0_low, E0_high) |
1936 |
}, |
|
1937 | 23x |
de = start |
1938 |
) |
|
1939 | ||
1940 | 23x |
start <- h_model_dual_endpoint_beta( |
1941 | 23x |
param = Emax, |
1942 | 23x |
param_name = "Emax", |
1943 | 23x |
priormodel = function() { |
1944 | ! |
Emax ~ dunif(Emax_low, Emax_high) |
1945 |
}, |
|
1946 | 23x |
de = start |
1947 |
) |
|
1948 | ||
1949 | 23x |
start <- h_model_dual_endpoint_beta( |
1950 | 23x |
param = ED50, |
1951 | 23x |
param_name = "ED50", |
1952 | 23x |
priormodel = function() { |
1953 | ! |
ED50 ~ dunif(ED50_low, ED50_high) |
1954 |
}, |
|
1955 | 23x |
de = start |
1956 |
) |
|
1957 | ||
1958 | 23x |
start@priormodel <- h_jags_join_models( |
1959 | 23x |
start@priormodel, |
1960 | 23x |
function() { |
1961 | ! |
for (i in 1:nGrid) { |
1962 | ! |
stand_dose_emax[i] <- doseGrid[i] / ref_dose_emax |
1963 | ! |
betaW[i] <- E0 + (Emax - E0) * stand_dose_emax[i] / (ED50 + stand_dose_emax[i]) |
1964 |
} |
|
1965 |
} |
|
1966 |
) |
|
1967 | ||
1968 | 23x |
.DualEndpointEmax( |
1969 | 23x |
start, |
1970 | 23x |
E0 = E0, |
1971 | 23x |
Emax = Emax, |
1972 | 23x |
ED50 = ED50, |
1973 | 23x |
ref_dose_emax = positive_number(ref_dose_emax) |
1974 |
) |
|
1975 |
} |
|
1976 | ||
1977 |
## default constructor ---- |
|
1978 | ||
1979 |
#' @rdname DualEndpointEmax-class |
|
1980 |
#' @note Typically, end users will not use the `.DefaultDualEndpointEmax()` function. |
|
1981 |
#' @export |
|
1982 |
.DefaultDualEndpointEmax <- function() { |
|
1983 | 7x |
DualEndpointEmax( |
1984 | 7x |
mean = c(0, 1), |
1985 | 7x |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
1986 | 7x |
sigma2W = c(a = 0.1, b = 0.1), |
1987 | 7x |
rho = c(a = 1, b = 1), |
1988 | 7x |
E0 = c(0, 100), |
1989 | 7x |
Emax = c(0, 500), |
1990 | 7x |
ED50 = c(10, 200), |
1991 | 7x |
ref_dose_emax = 1000 |
1992 |
) |
|
1993 |
} |
|
1994 | ||
1995 |
# ModelPseudo ---- |
|
1996 | ||
1997 |
## class ---- |
|
1998 | ||
1999 |
#' `ModelPseudo` |
|
2000 |
#' |
|
2001 |
#' @description `r lifecycle::badge("stable")` |
|
2002 |
#' |
|
2003 |
#' [`ModelPseudo`] is the parent class for models that express their prior in |
|
2004 |
#' the form of pseudo data (as if there is some data before the trial starts). |
|
2005 |
#' |
|
2006 |
#' @seealso [`GeneralModel`]. |
|
2007 |
#' |
|
2008 |
#' @aliases ModelPseudo |
|
2009 |
#' @export |
|
2010 |
#' |
|
2011 |
.ModelPseudo <- setClass( |
|
2012 |
Class = "ModelPseudo", |
|
2013 |
contains = "CrmPackClass" |
|
2014 |
) |
|
2015 | ||
2016 |
## default constructor ---- |
|
2017 | ||
2018 |
#' @rdname ModelPseudo-class |
|
2019 |
#' @note Typically, end users will not use the `.DefaultModelPseudo()` function. |
|
2020 |
#' @export |
|
2021 |
.DefaultModelPseudo <- function() { |
|
2022 | 1x |
stop(paste0("Class ModelPseudo should not be instantiated directly. Please use one of its subclasses instead.")) |
2023 |
} |
|
2024 | ||
2025 |
# ModelTox ---- |
|
2026 | ||
2027 |
## class ---- |
|
2028 | ||
2029 |
#' `ModelTox` |
|
2030 |
#' |
|
2031 |
#' @description `r lifecycle::badge("stable")` |
|
2032 |
#' |
|
2033 |
#' [`ModelTox`] is the parent class for DLE (dose-limiting events) models using |
|
2034 |
#' pseudo data prior. It is dedicated for DLE models or toxicity models that |
|
2035 |
#' have their prior specified in the form of pseudo data (as if there is some |
|
2036 |
#' data before the trial starts). |
|
2037 |
#' |
|
2038 |
#' The `data` must obey the convention of the [`Data`] class. This refers to any |
|
2039 |
#' observed DLE responses (`y` in [`Data`]), the dose levels (`x` in [`Data`]) |
|
2040 |
#' at which these responses are observed, all dose levels considered in the |
|
2041 |
#' study (`doseGrid` in [`Data`]), and finally other specifications in [`Data`] |
|
2042 |
#' class that can be used to generate prior or posterior modal estimates or |
|
2043 |
#' samples estimates for model parameter(s). |
|
2044 |
#' If no responses are observed, at least `doseGrid` has to be specified |
|
2045 |
#' in `data` for which prior modal estimates or samples can be obtained for |
|
2046 |
#' model parameters based on the specified pseudo data. |
|
2047 |
#' |
|
2048 |
#' @slot data (`Data`)\cr observed data that is used to obtain model parameters |
|
2049 |
#' estimates or samples (see details above). |
|
2050 |
#' |
|
2051 |
#' @seealso [`ModelEff`]. |
|
2052 |
#' |
|
2053 |
#' @aliases ModelTox |
|
2054 |
#' @export |
|
2055 |
#' |
|
2056 |
.ModelTox <- setClass( |
|
2057 |
Class = "ModelTox", |
|
2058 |
slots = c( |
|
2059 |
data = "Data" |
|
2060 |
), |
|
2061 |
contains = "ModelPseudo" |
|
2062 |
) |
|
2063 | ||
2064 |
## default constructor ---- |
|
2065 | ||
2066 |
#' @rdname ModelTox-class |
|
2067 |
#' @note Typically, end users will not use the `.DefaultModelTox()` function. |
|
2068 |
#' @export |
|
2069 |
.DefaultModelTox <- function() { |
|
2070 | 1x |
stop(paste0("Class ModelTox should not be instantiated directly. Please use one of its subclasses instead.")) |
2071 |
} |
|
2072 | ||
2073 |
# ModelEff ---- |
|
2074 | ||
2075 |
## class ---- |
|
2076 | ||
2077 |
#' `ModelEff` |
|
2078 |
#' |
|
2079 |
#' @description `r lifecycle::badge("stable")` |
|
2080 |
#' |
|
2081 |
#' [`ModelEff`] is the parent class for efficacy models using pseudo data prior. |
|
2082 |
#' It is dedicated all efficacy models that have their prior specified in the |
|
2083 |
#' form of pseudo data (as if there is some data before the trial starts). |
|
2084 |
#' |
|
2085 |
#' The `data` must obey the convention of the [`DataDual`] class. This refers to |
|
2086 |
#' any observed efficacy/biomarker responses (`w` in [`DataDual`]), the dose |
|
2087 |
#' levels at which these responses are observed (`x` in [`DataDual`]), all dose |
|
2088 |
#' levels considered in the study (`doseGrid` in [`DataDual`]), and finally |
|
2089 |
#' other specifications in [`DataDual`] class that can be used to generate prior |
|
2090 |
#' or posterior modal estimates or samples estimates for model parameter(s). |
|
2091 |
#' If no responses are observed, at least `doseGrid` has to be specified |
|
2092 |
#' in `data` for which prior modal estimates or samples can be obtained for |
|
2093 |
#' model parameters based on the specified pseudo data. |
|
2094 |
#' |
|
2095 |
#' @slot data (`DataDual`)\cr observed data that is used to obtain model |
|
2096 |
#' parameters estimates or samples (see details above). |
|
2097 |
#' |
|
2098 |
#' @seealso [`ModelTox`]. |
|
2099 |
#' |
|
2100 |
#' @aliases ModelEff |
|
2101 |
#' @export |
|
2102 |
#' |
|
2103 |
.ModelEff <- setClass( |
|
2104 |
Class = "ModelEff", |
|
2105 |
slots = c( |
|
2106 |
data = "DataDual" |
|
2107 |
), |
|
2108 |
contains = "ModelPseudo" |
|
2109 |
) |
|
2110 | ||
2111 |
## default constructor ---- |
|
2112 | ||
2113 |
#' @rdname ModelEff-class |
|
2114 |
#' @note Typically, end users will not use the `.DefaultModelEff()` function. |
|
2115 |
#' @export |
|
2116 |
.DefaultModelEff <- function() { |
|
2117 | 1x |
stop(paste0("Class ModelEff should not be instantiated directly. Please use one of its subclasses instead.")) |
2118 |
} |
|
2119 | ||
2120 |
# LogisticIndepBeta ---- |
|
2121 | ||
2122 |
## class ---- |
|
2123 | ||
2124 |
#' `LogisticIndepBeta` |
|
2125 |
#' |
|
2126 |
#' @description `r lifecycle::badge("stable")` |
|
2127 |
#' |
|
2128 |
#' [`LogisticIndepBeta`] is the class for the two-parameters logistic regression |
|
2129 |
#' dose-limiting events (DLE) model with prior expressed in form of pseudo data. |
|
2130 |
#' This model describes the relationship between the binary DLE responses |
|
2131 |
#' and the dose levels. More specifically, it represents the relationship of the |
|
2132 |
#' probabilities of the occurrence of a DLE for corresponding dose levels in log |
|
2133 |
#' scale. This model is specified as |
|
2134 |
#' \deqn{p(x) = exp(phi1 + phi2 * log(x)) / (1 + exp(phi1 + phi2 * log(x)))} |
|
2135 |
#' where \eqn{p(x)} is the probability of the occurrence of a DLE at dose \eqn{x}. |
|
2136 |
#' The two parameters of this model are the intercept \eqn{phi1} and the slope |
|
2137 |
#' \eqn{phi2}. The `LogisticIndepBeta` inherits all slots from [`ModelTox`] class. |
|
2138 |
#' |
|
2139 |
#' In the context of pseudo data, the following three arguments are used, |
|
2140 |
#' `binDLE`, `DLEdose` and `DLEweights`. The `DLEdose` represents fixed dose |
|
2141 |
#' levels at which the pseudo DLE responses `binDLE` are observed. `DLEweights` |
|
2142 |
#' represents total number of subjects treated per each dose level in `DLEdose`. |
|
2143 |
#' The `binDLE` represents the number of subjects observed with DLE per each |
|
2144 |
#' dose level in `DLEdose`. Hence, all these three vectors must be of the same |
|
2145 |
#' length and the order of the elements in any of the vectors `binDLE`, |
|
2146 |
#' `DLEdose` and `DLEweights` must be kept, so that an element of a given vector |
|
2147 |
#' corresponds to the elements of the remaining two vectors (see the example for |
|
2148 |
#' more insight). |
|
2149 |
#' Finally, since at least two DLE pseudo responses are needed to |
|
2150 |
#' obtain prior modal estimates (same as the maximum likelihood estimates) for |
|
2151 |
#' the model parameters, the `binDLE`, `DLEdose` and `DLEweights` must all be |
|
2152 |
#' vectors of at least length 2. |
|
2153 |
#' |
|
2154 |
#' @details The pseudo data can be interpreted as if we obtain some observations |
|
2155 |
#' before the trial starts. It can be used to express our prior, i.e. the |
|
2156 |
#' initial beliefs for the model parameters. The pseudo data is expressed in |
|
2157 |
#' the following way. First, fix at least two dose levels, then ask for experts' |
|
2158 |
#' opinion on how many subjects are to be treated at each of these dose levels |
|
2159 |
#' and on the number of subjects observed with a DLE. At each dose level, the |
|
2160 |
#' number of subjects observed with a DLE, divided by the total number of |
|
2161 |
#' subjects treated, is the probability of the occurrence of a DLE at that |
|
2162 |
#' particular dose level. The probabilities of the occurrence of a DLE based |
|
2163 |
#' on this pseudo data are independent and they follow Beta distributions. |
|
2164 |
#' Therefore, the joint prior probability density function of all these |
|
2165 |
#' probabilities can be obtained. Hence, by a change of variable, the joint |
|
2166 |
#' prior probability density function of the two parameters in this model can |
|
2167 |
#' also be obtained. In addition, a conjugate joint prior density function of |
|
2168 |
#' the two parameters in the model is used. For details about the form of all |
|
2169 |
#' these joint prior and posterior probability density functions, please refer |
|
2170 |
#' to Whitehead and Willamson (1998). |
|
2171 |
#' |
|
2172 |
#' @slot binDLE (`numeric`)\cr a vector of total numbers of DLE responses. |
|
2173 |
#' It must be at least of length 2 and the order of its elements must |
|
2174 |
#' correspond to values specified in `DLEdose` and `DLEweights`. |
|
2175 |
#' @slot DLEdose (`numeric`)\cr a vector of the dose levels corresponding to |
|
2176 |
#' It must be at least of length 2 and the order of its elements must |
|
2177 |
#' correspond to values specified in `binDLE` and `DLEweights`. |
|
2178 |
#' @slot DLEweights (`integer`)\cr total number of subjects treated at each of |
|
2179 |
#' the pseudo dose level `DLEdose`. |
|
2180 |
#' It must be at least of length 2 and the order of its elements must |
|
2181 |
#' correspond to values specified in `binDLE` and `DLEdose`. |
|
2182 |
#' @slot phi1 (`number`)\cr the intercept of the model. This slot is used in |
|
2183 |
#' output to display the resulting prior or posterior modal estimate of the |
|
2184 |
#' intercept obtained based on the pseudo data and (if any) observed data/responses. |
|
2185 |
#' @slot phi2 (`number`)\cr the slope of the model. This slot is used in output |
|
2186 |
#' to display the resulting prior or posterior modal estimate of the slope |
|
2187 |
#' obtained based on the pseudo data and (if any) the observed data/responses. |
|
2188 |
#' @slot Pcov (`matrix`)\cr refers to the 2x2 covariance matrix of the intercept |
|
2189 |
#' (\eqn{phi1}) and the slope parameters (\eqn{phi2}) of the model. |
|
2190 |
#' This is used in output to display the resulting prior and posterior |
|
2191 |
#' covariance matrix of \eqn{phi1} and \eqn{phi2} obtained, based on the |
|
2192 |
#' pseudo data and (if any) the observed data and responses. This slot is |
|
2193 |
#' needed for internal purposes. |
|
2194 |
#' |
|
2195 |
#' @aliases LogisticIndepBeta |
|
2196 |
#' @export |
|
2197 |
#' |
|
2198 |
.LogisticIndepBeta <- setClass( |
|
2199 |
Class = "LogisticIndepBeta", |
|
2200 |
slots = c( |
|
2201 |
binDLE = "numeric", |
|
2202 |
DLEdose = "numeric", |
|
2203 |
DLEweights = "integer", |
|
2204 |
phi1 = "numeric", |
|
2205 |
phi2 = "numeric", |
|
2206 |
Pcov = "matrix" |
|
2207 |
), |
|
2208 |
prototype = prototype( |
|
2209 |
binDLE = c(0, 0), |
|
2210 |
DLEdose = c(1, 1), |
|
2211 |
DLEweights = c(1L, 1L) |
|
2212 |
), |
|
2213 |
contains = "ModelTox", |
|
2214 |
validity = v_model_logistic_indep_beta |
|
2215 |
) |
|
2216 | ||
2217 |
## constructor ---- |
|
2218 | ||
2219 |
#' @rdname LogisticIndepBeta-class |
|
2220 |
#' |
|
2221 |
#' @param binDLE (`numeric`)\cr the number of subjects observed with a DLE, the |
|
2222 |
#' pseudo DLE responses, depending on dose levels `DLEdose`. |
|
2223 |
#' Elements of `binDLE` must correspond to the elements of `DLEdose` and |
|
2224 |
#' `DLEweights`. |
|
2225 |
#' @param DLEdose (`numeric`)\cr dose levels for the pseudo DLE responses. |
|
2226 |
#' Elements of `DLEdose` must correspond to the elements of `binDLE` and |
|
2227 |
#' `DLEweights`. |
|
2228 |
#' @param DLEweights (`numeric`)\cr the total number of subjects treated at each |
|
2229 |
#' of the dose levels `DLEdose`, pseudo weights. |
|
2230 |
#' Elements of `DLEweights` must correspond to the elements of `binDLE` and |
|
2231 |
#' `DLEdose`. |
|
2232 |
#' @param data (`Data`)\cr the input data to update estimates of the model |
|
2233 |
#' parameters. |
|
2234 |
#' |
|
2235 |
#' @export |
|
2236 |
#' @example examples/Model-class-LogisticIndepBeta.R |
|
2237 |
#' |
|
2238 |
LogisticIndepBeta <- function(binDLE, |
|
2239 |
DLEdose, |
|
2240 |
DLEweights, |
|
2241 |
data) { |
|
2242 | 199x |
assert_numeric(binDLE) |
2243 | 199x |
assert_numeric(DLEdose) |
2244 | 199x |
assert_integerish(DLEweights, lower = 0, any.missing = FALSE) |
2245 | 199x |
assert_class(data, "Data") |
2246 | ||
2247 |
# Combine pseudo and observed data. It can also happen that data@nObs == 0. |
|
2248 | 199x |
y <- c(binDLE, data@y) |
2249 | 199x |
x <- c(DLEdose, data@x) |
2250 | 199x |
w <- c(DLEweights, rep(1, data@nObs)) |
2251 | ||
2252 | 199x |
fit_dle <- suppressWarnings( |
2253 | 199x |
glm(y / w ~ log(x), family = binomial(link = "logit"), weights = w) |
2254 |
) |
|
2255 | 199x |
phi1 <- coef(fit_dle)[["(Intercept)"]] |
2256 | 199x |
phi2 <- coef(fit_dle)[["log(x)"]] |
2257 | 199x |
Pcov <- vcov(fit_dle) |
2258 | ||
2259 | 199x |
.LogisticIndepBeta( |
2260 | 199x |
binDLE = binDLE, |
2261 | 199x |
DLEdose = DLEdose, |
2262 | 199x |
DLEweights = as.integer(DLEweights), |
2263 | 199x |
phi1 = phi1, |
2264 | 199x |
phi2 = phi2, |
2265 | 199x |
Pcov = Pcov, |
2266 | 199x |
data = data |
2267 |
) |
|
2268 |
} |
|
2269 | ||
2270 |
## default constructor ---- |
|
2271 | ||
2272 |
#' @rdname LogisticIndepBeta-class |
|
2273 |
#' @note Typically, end users will not use the `.DefaultLogisticIndepBeta()` function. |
|
2274 |
#' @export |
|
2275 |
.DefaultLogisticIndepBeta <- function() { |
|
2276 | 5x |
my_model <- LogisticIndepBeta( |
2277 | 5x |
binDLE = c(1.05, 1.8), |
2278 | 5x |
DLEweights = c(3L, 3L), |
2279 | 5x |
DLEdose = c(25, 300), |
2280 | 5x |
data = Data(doseGrid = seq(25, 300, 25)) |
2281 |
) |
|
2282 |
} |
|
2283 | ||
2284 | ||
2285 |
# Effloglog ---- |
|
2286 | ||
2287 |
## class ---- |
|
2288 | ||
2289 |
#' `Effloglog` |
|
2290 |
#' |
|
2291 |
#' @description `r lifecycle::badge("stable")` |
|
2292 |
#' |
|
2293 |
#' [`Effloglog`] is the class for the linear log-log efficacy model using pseudo |
|
2294 |
#' data prior. It describes the relationship between continuous efficacy |
|
2295 |
#' responses and corresponding dose levels in log-log scale. This efficacy |
|
2296 |
#' log-log model is given as |
|
2297 |
#' \deqn{y_i = theta1 + theta2 * log(log(x_i)) + epsilon_i,} |
|
2298 |
#' where \eqn{y_i} is the efficacy response for subject \eqn{i}, \eqn{x_i} is |
|
2299 |
#' the dose level treated for subject \eqn{i} and \eqn{epsilon_i} is the random |
|
2300 |
#' error term of efficacy model at subject \eqn{i}. The error term |
|
2301 |
#' \eqn{epsilon_i} is a random variable that follows normal distribution with |
|
2302 |
#' mean \eqn{0} and variance \eqn{nu^{-1}}, which is assumed to be the |
|
2303 |
#' same for all subjects. |
|
2304 |
#' There are three parameters in this model, the intercept \eqn{theta1}, the |
|
2305 |
#' slope \eqn{theta2} and the precision \eqn{nu} of the efficacy responses, also |
|
2306 |
#' known as the inverse of the variance of the pseudo efficacy responses. It can |
|
2307 |
#' be a fixed constant or having a gamma distribution. Therefore, a single scalar |
|
2308 |
#' value or a vector with two positive numbers values must be specified for `nu` |
|
2309 |
#' slot. If there are some observed efficacy responses available, in the output, |
|
2310 |
#' `nu` will display the updated value of the precision or the updated values |
|
2311 |
#' for the parameters of the gamma distribution. |
|
2312 |
#' The `Effloglog` inherits all slots from [`ModelEff`] class. |
|
2313 |
#' |
|
2314 |
#' @details The prior of this model is specified in form of pseudo data. First, |
|
2315 |
#' at least two dose levels are fixed. Then, using e.g. experts' opinion, the |
|
2316 |
#' efficacy values that correspond to these dose levels can be obtained, |
|
2317 |
#' The `eff` and `eff_dose` arguments represent the prior in form of the pseudo |
|
2318 |
#' data. The `eff` represents the pseudo efficacy values. The `eff_dose` |
|
2319 |
#' represents the dose levels at which these pseudo efficacy values are |
|
2320 |
#' observed. Hence, the positions of the elements specified in `eff` and |
|
2321 |
#' `eff_dose` must correspond to each other between these vectors. |
|
2322 |
#' Since at least 2 pseudo efficacy values are needed to obtain modal |
|
2323 |
#' estimates of the intercept and slope parameters, both `eff` and `eff_dose` |
|
2324 |
#' must be vectors of length at least 2. |
|
2325 |
#' |
|
2326 |
#' The joint prior distribution of the intercept \eqn{theta1} and the slope |
|
2327 |
#' \eqn{theta2} of this model follows bivariate normal distribution with mean |
|
2328 |
#' \eqn{mu} and covariance matrix \eqn{(nu * Q)^{-1}}. |
|
2329 |
#' The mean \eqn{mu} is a \eqn{2 x 1} column vector that contains the prior |
|
2330 |
#' modal estimates of the intercept and the slope. |
|
2331 |
#' Scalar \eqn{nu} is the precision of the pseudo efficacy responses and |
|
2332 |
#' \eqn{Q} is the prior or posterior (given that observed, no DLT data is |
|
2333 |
#' available) precision matrix. |
|
2334 |
#' It is specified as \eqn{Q = X0^T * X0 + X^T * X}, where \eqn{X0} is a |
|
2335 |
#' design matrix that is based on pseudo dose levels only, and \eqn{X} is a |
|
2336 |
#' design matrix that is based on dose levels corresponding to the no DLT |
|
2337 |
#' efficacy responses observed only (if any). |
|
2338 |
#' Hence, the \eqn{X0} (or \eqn{X}) will be of size \eqn{r x 2}, if |
|
2339 |
#' there are \eqn{r >= 2} pseudo efficacy responses specified (or |
|
2340 |
#' if there are \eqn{r} no DLT efficacy responses observed in the `data`). |
|
2341 |
#' |
|
2342 |
#' @slot eff (`numeric`)\cr the pseudo efficacy responses. Each element here |
|
2343 |
#' must represent responses treated based on one subject. |
|
2344 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
2345 |
#' correspond to values specified in `eff_dose`. |
|
2346 |
#' @slot eff_dose (`numeric`)\cr the pseudo efficacy dose levels at which the |
|
2347 |
#' pseudo efficacy responses are observed. |
|
2348 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
2349 |
#' correspond to values specified in `eff`. |
|
2350 |
#' @slot nu (`numeric`)\cr parameter of the prior precision of pseudo efficacy |
|
2351 |
#' responses. This is either a fixed value or a named vector with two positive |
|
2352 |
#' numbers, the shape (`a`), and the rate (`b`) parameters for the gamma |
|
2353 |
#' distribution. |
|
2354 |
#' @slot use_fixed (`flag`)\cr indicates whether `nu` specified is a fixed value |
|
2355 |
#' or a vector with two parameters for gamma distribution. This slot is for |
|
2356 |
#' internal purposes only and must not be used by the user. |
|
2357 |
#' @slot theta1 (`number`)\cr the intercept in this efficacy log-log model. This |
|
2358 |
#' slot is used in output to display the resulting prior or posterior modal |
|
2359 |
#' estimates obtained based on the pseudo and observed (if any) data. |
|
2360 |
#' @slot theta2 (`number`)\cr the slope in this efficacy log-log model. This |
|
2361 |
#' slot is used in output to display the resulting prior or posterior modal |
|
2362 |
#' estimates obtained based on the pseudo and observed (if any) data. |
|
2363 |
#' @slot Pcov (`matrix`)\cr refers to the \eqn{2 x 2} covariance matrix of the |
|
2364 |
#' estimators of the intercept \eqn{theta1} and the slope \eqn{theta2} |
|
2365 |
#' parameters in this model. |
|
2366 |
#' This is used in output to display the resulting prior and posterior |
|
2367 |
#' covariance matrix of \eqn{theta1} and \eqn{theta2} obtained, based on the |
|
2368 |
#' pseudo and observed (if any) data. This slot is needed for internal purposes. |
|
2369 |
#' @slot X (`matrix`)\cr is the design matrix that is based on either the pseudo |
|
2370 |
#' dose levels or observed dose levels (without DLT). This is used |
|
2371 |
#' in the output to display the design matrix for the pseudo or the observed |
|
2372 |
#' efficacy responses. |
|
2373 |
#' @slot Y (`numeric`)\cr is a vector that either contains the pseudo efficacy |
|
2374 |
#' responses or observed efficacy responses (without DLT). |
|
2375 |
#' @slot mu (`numeric`)\cr a vector of the prior or the posterior modal estimates |
|
2376 |
#' of the intercept (\eqn{theta1}) and the slope (\eqn{theta2}). |
|
2377 |
#' This slot is used in output to display as the mean of the prior or posterior |
|
2378 |
#' bivariate normal distribution for \eqn{theta1} and \eqn{theta2}. |
|
2379 |
#' @slot Q (`matrix`)\cr is the prior or posterior (given that observed, no DLT |
|
2380 |
#' data is available) precision matrix. It is specified as |
|
2381 |
#' \eqn{Q = X0^T * X0 + X^T * X}, where \eqn{X0} is a design matrix that is |
|
2382 |
#' based on pseudo dose levels only, and \eqn{X} is a design matrix that is |
|
2383 |
#' based on dose levels corresponding to the observed, no DLT efficacy values |
|
2384 |
#' only (if any). |
|
2385 |
#' @slot const (`number`)\cr a non-negative number (default to 0), leading to the |
|
2386 |
#' model form described above. In general, the model has the form |
|
2387 |
#' \eqn{y_i = theta1 + theta2 * log(log(x_i + const)) + epsilon_i}, such that |
|
2388 |
#' dose levels greater than \eqn{1 - const} can be considered as described in |
|
2389 |
#' Yeung et al. (2015). |
|
2390 |
#' |
|
2391 |
#' @aliases Effloglog |
|
2392 |
#' @export |
|
2393 |
#' |
|
2394 |
.Effloglog <- setClass( |
|
2395 |
Class = "Effloglog", |
|
2396 |
slots = c( |
|
2397 |
eff = "numeric", |
|
2398 |
eff_dose = "numeric", |
|
2399 |
nu = "numeric", |
|
2400 |
use_fixed = "logical", |
|
2401 |
theta1 = "numeric", |
|
2402 |
theta2 = "numeric", |
|
2403 |
Pcov = "matrix", |
|
2404 |
X = "matrix", |
|
2405 |
Y = "numeric", |
|
2406 |
mu = "numeric", |
|
2407 |
Q = "matrix", |
|
2408 |
const = "numeric" |
|
2409 |
), |
|
2410 |
prototype = prototype( |
|
2411 |
eff = c(0, 0), |
|
2412 |
eff_dose = c(1, 1), |
|
2413 |
nu = 1 / 0.025, |
|
2414 |
use_fixed = TRUE, |
|
2415 |
const = 0 |
|
2416 |
), |
|
2417 |
contains = "ModelEff", |
|
2418 |
validity = v_model_eff_log_log |
|
2419 |
) |
|
2420 | ||
2421 |
## constructor ---- |
|
2422 | ||
2423 |
#' @rdname Effloglog-class |
|
2424 |
#' |
|
2425 |
#' @param eff (`numeric`)\cr the pseudo efficacy responses. |
|
2426 |
#' Elements of `eff` must correspond to the elements of `eff_dose`. |
|
2427 |
#' @param eff_dose (`numeric`)\cr dose levels that correspond to pseudo efficacy |
|
2428 |
#' responses in `eff`. |
|
2429 |
#' @param nu (`numeric`)\cr the precision (inverse of the variance) of the |
|
2430 |
#' efficacy responses. This is either a fixed value or a named vector with two |
|
2431 |
#' positive numbers, the shape (`a`), and the rate (`b`) parameters for the |
|
2432 |
#' gamma distribution. |
|
2433 |
#' @param data (`DataDual`)\cr observed data to update estimates of the model |
|
2434 |
#' parameters. |
|
2435 |
#' @param const (`number`)\cr the constant value added to the dose level when |
|
2436 |
#' the dose level value is less than or equal to 1 and a special form of the |
|
2437 |
#' linear log-log has to applied (Yeung et al. (2015).). |
|
2438 |
#' |
|
2439 |
#' @export |
|
2440 |
#' @example examples/Model-class-Effloglog.R |
|
2441 |
#' |
|
2442 |
Effloglog <- function(eff, |
|
2443 |
eff_dose, |
|
2444 |
nu, |
|
2445 |
data, |
|
2446 |
const = 0) { |
|
2447 | 141x |
assert_numeric(eff) |
2448 | 141x |
assert_numeric(eff_dose, len = length(eff)) |
2449 | 141x |
assert_numeric(nu, min.len = 1, max.len = 2) |
2450 | 141x |
assert_class(data, "Data") |
2451 | 141x |
assert_number(const, finite = TRUE) |
2452 | ||
2453 | 141x |
use_fixed <- length(nu) == 1L |
2454 | ||
2455 | 141x |
eff_dose <- eff_dose + const |
2456 |
# Get observed efficacy data without DLT (if any). |
|
2457 | 141x |
eff_obsrv_w_x <- getEff(data, no_dlt = TRUE) |
2458 | 141x |
eff_obsrv <- eff_obsrv_w_x$w_no_dlt |
2459 | 141x |
eff_obsrv_dose <- eff_obsrv_w_x$x_no_dlt + const |
2460 | ||
2461 |
# Fit pseudo and observed (if any) efficacy. |
|
2462 | 141x |
w <- c(eff, eff_obsrv) |
2463 | 141x |
x <- c(eff_dose, eff_obsrv_dose) |
2464 | 141x |
fit_eff <- suppressWarnings(lm(w ~ log(log(x)))) |
2465 | 141x |
X <- model.matrix(fit_eff) |
2466 | 141x |
Y <- w |
2467 | 141x |
mu <- coef(fit_eff) # This is [theta1, theta2]^T est. |
2468 | 141x |
Q <- crossprod(X) |
2469 | 141x |
Pcov <- vcov(fit_eff) |
2470 | ||
2471 | 141x |
nobs_no_dlt <- length(eff_obsrv) |
2472 | 141x |
if (nobs_no_dlt > 0L) { # Observed data available. |
2473 |
# Set X, Y to observed data only. |
|
2474 | 99x |
X <- model.matrix(fit_eff)[-seq_along(eff), ] |
2475 | 99x |
Y <- eff_obsrv |
2476 | ||
2477 | 99x |
fit_eff0 <- lm(eff ~ log(log(eff_dose))) # Pseudo only. |
2478 | 99x |
X0 <- model.matrix(fit_eff0) |
2479 | 99x |
mu0 <- coef(fit_eff0) |
2480 | 99x |
Q0 <- crossprod(X0) |
2481 |
# Note that mu = (Q0 + X^T * X)^{-1} * (Q0 * mu0 + X^T * X * (X^T * X)^{-1} X^T * Y), |
|
2482 |
# given that (X^T * X) is invertible and X, Y, mu0, Q0, are specified in this else block. |
|
2483 | 99x |
if (!use_fixed) { |
2484 | 99x |
nu["a"] <- nu["a"] + (nobs_no_dlt) / 2 |
2485 | 99x |
nu["b"] <- nu["b"] + (crossprod(Y) + t(mu0) %*% Q0 %*% mu0 - t(mu) %*% Q %*% mu) / 2 |
2486 |
} |
|
2487 |
} |
|
2488 | ||
2489 | 141x |
.Effloglog( |
2490 | 141x |
eff = eff, |
2491 | 141x |
eff_dose = eff_dose, |
2492 | 141x |
nu = nu, |
2493 | 141x |
use_fixed = use_fixed, |
2494 | 141x |
theta1 = mu[["(Intercept)"]], |
2495 | 141x |
theta2 = mu[["log(log(x))"]], |
2496 | 141x |
Pcov = Pcov, |
2497 | 141x |
X = X, |
2498 | 141x |
Y = Y, |
2499 | 141x |
mu = as.vector(mu), |
2500 | 141x |
Q = Q, |
2501 | 141x |
const = const, |
2502 | 141x |
data = data |
2503 |
) |
|
2504 |
} |
|
2505 | ||
2506 |
## default constructor ---- |
|
2507 | ||
2508 |
#' @rdname Effloglog-class |
|
2509 |
#' @note Typically, end users will not use the `.DefaultEffloglog()` function. |
|
2510 |
#' @export |
|
2511 |
.DefaultEffloglog <- function() { |
|
2512 | 5x |
emptydata <- DataDual(doseGrid = seq(25, 300, 25), placebo = FALSE) |
2513 | ||
2514 | 5x |
my_data <- DataDual( |
2515 | 5x |
x = c(25, 50, 50, 75, 100, 100, 225, 300), |
2516 | 5x |
y = c(0, 0, 0, 0, 1, 1, 1, 1), |
2517 | 5x |
w = c(0.31, 0.42, 0.59, 0.45, 0.6, 0.7, 0.6, 0.52), |
2518 | 5x |
doseGrid = emptydata@doseGrid, |
2519 | 5x |
ID = 1L:8L, |
2520 | 5x |
cohort = as.integer(c(1, 2, 2, 3, 4, 4, 5, 6)) |
2521 |
) |
|
2522 | ||
2523 | 5x |
Effloglog( |
2524 | 5x |
eff = c(1.223, 2.513), |
2525 | 5x |
eff_dose = c(25, 300), |
2526 | 5x |
nu = c(a = 1, b = 0.025), |
2527 | 5x |
data = my_data |
2528 |
) |
|
2529 |
} |
|
2530 | ||
2531 |
# EffFlexi ---- |
|
2532 | ||
2533 |
## class ---- |
|
2534 | ||
2535 |
#' `EffFlexi` |
|
2536 |
#' |
|
2537 |
#' @description `r lifecycle::badge("stable")` |
|
2538 |
#' |
|
2539 |
#' [`EffFlexi`] is the class for the efficacy model in flexible form of prior |
|
2540 |
#' expressed in form of pseudo data. In this class, a flexible form is used to |
|
2541 |
#' describe the relationship between the efficacy responses and the dose levels |
|
2542 |
#' and it is specified as |
|
2543 |
#' \deqn{(W | betaW, sigma2W) ~ Normal(X * betaW, sigma2W * I),} |
|
2544 |
#' where \eqn{W} is a vector of the efficacy responses, \eqn{betaW} is a column |
|
2545 |
#' vector of the mean efficacy responses for all dose levels, and \eqn{X} is |
|
2546 |
#' the design matrix with entries \eqn{I_i,j} that are equal to 1 if subject |
|
2547 |
#' \eqn{i} is allocated to dose \eqn{j}, and \eqn{0} otherwise. The \eqn{sigma2W} |
|
2548 |
#' is the variance of the efficacy responses which can be either a fixed number |
|
2549 |
#' or a number from an inverse gamma distribution. |
|
2550 |
#' This flexible form aims to capture different shapes of the dose-efficacy |
|
2551 |
#' curve. In addition, the first (RW1) or second order (RW2) random walk model |
|
2552 |
#' can be used for smoothing data. That is the random walk model is used to model |
|
2553 |
#' the first or the second order differences of the mean efficacy responses to |
|
2554 |
#' its neighboring dose levels of their mean efficacy responses. |
|
2555 |
#' |
|
2556 |
#' The RW1 model is given as |
|
2557 |
#' \deqn{betaW_j - betaW_j-1) ~ Normal(0, sigma2betaW),} |
|
2558 |
#' and for RW2 as |
|
2559 |
#' \deqn{betaW_j-2 - 2 * betaW_j-1 + beta_j ~ Normal(0, sigma2betaW),} |
|
2560 |
#' where \eqn{betaW_j} is the vector of mean efficacy responses at dose j, and |
|
2561 |
#' the \eqn{sigma2betaW} is the prior variance which can be either a fixed |
|
2562 |
#' number or a number from an inverse gamma distribution. |
|
2563 |
#' |
|
2564 |
#' The `eff` and `eff_dose` are the pseudo efficacy responses and dose levels at |
|
2565 |
#' which these pseudo efficacy responses are observed. Both, `eff` and `eff_dose` |
|
2566 |
#' must be vectors of length at least 2. The positions of the elements specified |
|
2567 |
#' in `eff` and `eff_dose` must correspond to each other between these vectors. |
|
2568 |
#' |
|
2569 |
#' @details This model will output the updated value or the updated values of the |
|
2570 |
#' parameters of the inverse gamma distributions for \eqn{sigma2W} and |
|
2571 |
#' \eqn{sigma2betaW}. The `EffFlexi` inherits all slots from [`ModelEff`] class. |
|
2572 |
#' |
|
2573 |
#' @slot eff (`numeric`)\cr the pseudo efficacy responses. Each element here |
|
2574 |
#' must represent responses treated based on one subject. |
|
2575 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
2576 |
#' correspond to values specified in `eff_dose`. |
|
2577 |
#' @slot eff_dose (`numeric`)\cr the pseudo efficacy dose levels at which the |
|
2578 |
#' pseudo efficacy responses are observed. |
|
2579 |
#' It must be a vector of length at least 2 and the order of its elements must |
|
2580 |
#' correspond to values specified in `eff`. |
|
2581 |
#' @slot sigma2W (`numeric`)\cr the prior variance of the flexible efficacy form. |
|
2582 |
#' This is either a fixed value or a named vector with two positive numbers, |
|
2583 |
#' the shape (`a`), and the rate (`b`) parameters for the gamma distribution. |
|
2584 |
#' @slot sigma2betaW (`numeric`)\cr the prior variance of the random walk model |
|
2585 |
#' for the mean efficacy responses. This is either a fixed value or a named |
|
2586 |
#' vector with two positive numbers, the shape (`a`), and the rate (`b`) |
|
2587 |
#' parameters for the gamma distribution. |
|
2588 |
#' @slot use_fixed (`logical`)\cr indicates whether a fixed value for |
|
2589 |
#' `sigma2W` and `sigma2betaW` (for each parameter separately) is used or not. |
|
2590 |
#' This slot is needed for internal purposes and must not be touched by the user. |
|
2591 |
#' @slot rw1 (`flag`)\cr used for smoothing data for this efficacy model. If it |
|
2592 |
#' is `TRUE`, the first-order random walk model is used for the mean efficacy |
|
2593 |
#' responses. Otherwise, the random walk of second order is used. |
|
2594 |
#' @slot X (`matrix`)\cr the design matrix for the efficacy responses. It is |
|
2595 |
#' based on both the pseudo and the observed efficacy responses. |
|
2596 |
#' @slot RW (`matrix`)\cr the difference matrix for the random walk model. This |
|
2597 |
#' slot is needed for internal purposes and must not be used by the user. |
|
2598 |
#' @slot RW_rank (`integer`)\cr is the rank of the difference matrix. This |
|
2599 |
#' slot is needed for internal purposes and must not be used by the user. |
|
2600 |
#' |
|
2601 |
#' @aliases EffFlexi |
|
2602 |
#' @export |
|
2603 |
#' |
|
2604 |
.EffFlexi <- setClass( |
|
2605 |
Class = "EffFlexi", |
|
2606 |
slots = c( |
|
2607 |
eff = "numeric", |
|
2608 |
eff_dose = "numeric", |
|
2609 |
sigma2W = "numeric", |
|
2610 |
sigma2betaW = "numeric", |
|
2611 |
use_fixed = "logical", |
|
2612 |
rw1 = "logical", |
|
2613 |
X = "matrix", |
|
2614 |
RW = "matrix", |
|
2615 |
RW_rank = "integer" |
|
2616 |
), |
|
2617 |
prototype = prototype( |
|
2618 |
eff = c(0, 0), |
|
2619 |
eff_dose = c(1, 1), |
|
2620 |
sigma2W = 0.025, |
|
2621 |
sigma2betaW = 1, |
|
2622 |
rw1 = TRUE, |
|
2623 |
use_fixed = c(sigma2W = TRUE, sigma2betaW = TRUE) |
|
2624 |
), |
|
2625 |
contains = "ModelEff", |
|
2626 |
validity = v_model_eff_flexi |
|
2627 |
) |
|
2628 | ||
2629 |
## constructor ---- |
|
2630 | ||
2631 |
#' @rdname EffFlexi-class |
|
2632 |
#' |
|
2633 |
#' @param eff (`numeric`)\cr the pseudo efficacy responses. |
|
2634 |
#' Elements of `eff` must correspond to the elements of `eff_dose`. |
|
2635 |
#' @param eff_dose (`numeric`)\cr dose levels that correspond to pseudo efficacy |
|
2636 |
#' responses in `eff`. |
|
2637 |
#' @param sigma2W (`numeric`)\cr the prior variance of the efficacy responses. |
|
2638 |
#' This is either a fixed value or a named vector with two positive numbers, |
|
2639 |
#' the shape (`a`), and the rate (`b`) parameters for the inverse gamma |
|
2640 |
#' distribution. |
|
2641 |
#' @param sigma2betaW (`numeric`)\cr the prior variance of the random walk model |
|
2642 |
#' used for smoothing. This is either a fixed value or a named vector with two |
|
2643 |
#' positive numbers, the shape (`a`), and the rate (`b`) parameters for the |
|
2644 |
#' inverse gamma distribution. |
|
2645 |
#' @param rw1 (`flag`)\cr used for smoothing data for this efficacy model. If it |
|
2646 |
#' is `TRUE`, the first-order random walk model is used for the mean efficacy |
|
2647 |
#' responses. Otherwise, the random walk of second order is used. |
|
2648 |
#' @param data (`DataDual`)\cr observed data to update estimates of the model |
|
2649 |
#' parameters. |
|
2650 |
#' |
|
2651 |
#' @export |
|
2652 |
#' @example examples/Model-class-EffFlexi.R |
|
2653 |
#' |
|
2654 |
EffFlexi <- function(eff, |
|
2655 |
eff_dose, |
|
2656 |
sigma2W, |
|
2657 |
sigma2betaW, |
|
2658 |
rw1 = TRUE, |
|
2659 |
data) { |
|
2660 | 58x |
assert_numeric(eff) |
2661 | 58x |
assert_numeric(eff_dose) |
2662 | 58x |
assert_numeric(sigma2W, min.len = 1, max.len = 2) |
2663 | 58x |
assert_numeric(sigma2betaW, min.len = 1, max.len = 2) |
2664 | 58x |
assert_flag(rw1) |
2665 | 58x |
assert_class(data, "DataDual") |
2666 | ||
2667 | 58x |
use_fixed <- c( |
2668 | 58x |
sigma2W = test_number(sigma2W), |
2669 | 58x |
sigma2betaW = test_number(sigma2betaW) |
2670 |
) |
|
2671 | ||
2672 | 58x |
x <- c(eff_dose, getEff(data, no_dlt = TRUE)$x_no_dlt) |
2673 | 58x |
x_level <- match_within_tolerance(x, data@doseGrid) |
2674 | 58x |
X <- model.matrix(~ -1L + factor(x_level, levels = seq_len(data@nGrid))) |
2675 | 58x |
X <- matrix(as.integer(X), ncol = ncol(X)) # To remove some obsolete attributes. |
2676 | ||
2677 |
# Set up the random walk penalty matrix and its rank. |
|
2678 |
# D1: difference matrix of order 1. |
|
2679 | 58x |
D1 <- cbind(0, diag(data@nGrid - 1)) - cbind(diag(data@nGrid - 1), 0) |
2680 | 58x |
if (rw1) { # the rank-deficient prior precision for the RW1 prior. |
2681 | 37x |
RW <- crossprod(D1) |
2682 | 37x |
RW_rank <- data@nGrid - 1L # rank = dimension - 1. # nolintr |
2683 |
} else { # Second-order difference. |
|
2684 | 21x |
D2 <- D1[-1, -1] %*% D1 |
2685 | 21x |
RW <- crossprod(D2) |
2686 | 21x |
RW_rank <- data@nGrid - 2L # nolintr |
2687 |
} |
|
2688 | ||
2689 | 58x |
.EffFlexi( |
2690 | 58x |
eff = eff, |
2691 | 58x |
eff_dose = eff_dose, |
2692 | 58x |
sigma2W = sigma2W, |
2693 | 58x |
sigma2betaW = sigma2betaW, |
2694 | 58x |
use_fixed = use_fixed, |
2695 | 58x |
rw1 = rw1, |
2696 | 58x |
X = X, |
2697 | 58x |
RW = RW, |
2698 | 58x |
RW_rank = RW_rank, |
2699 | 58x |
data = data |
2700 |
) |
|
2701 |
} |
|
2702 | ||
2703 |
## default constructor ---- |
|
2704 | ||
2705 |
#' @rdname EffFlexi-class |
|
2706 |
#' @note Typically, end users will not use the `.DefaultEffFlexi()` function. |
|
2707 |
#' @export |
|
2708 |
.DefaultEffFlexi <- function() { |
|
2709 | 5x |
empty_data <- DataDual(doseGrid = seq(25, 300, 25)) |
2710 | 5x |
EffFlexi( |
2711 | 5x |
eff = c(1.223, 2.513), |
2712 | 5x |
eff_dose = c(25, 300), |
2713 | 5x |
sigma2W = c(a = 0.1, b = 0.1), |
2714 | 5x |
sigma2betaW = c(a = 20, b = 50), |
2715 | 5x |
rw1 = FALSE, |
2716 | 5x |
data = empty_data |
2717 |
) |
|
2718 | ||
2719 | 5x |
data <- DataDual( |
2720 | 5x |
x = c(25, 50, 50, 75, 100, 100, 225, 300), |
2721 | 5x |
y = c(0, 0, 0, 0, 1, 1, 1, 1), |
2722 | 5x |
w = c(0.31, 0.42, 0.59, 0.45, 0.6, 0.7, 0.6, 0.52), |
2723 | 5x |
doseGrid = empty_data@doseGrid, |
2724 | 5x |
ID = 1L:8L, |
2725 | 5x |
cohort = as.integer(c(1, 2, 2, 3, 4, 4, 5, 6)) |
2726 |
) |
|
2727 | ||
2728 | 5x |
EffFlexi( |
2729 | 5x |
eff = c(1.223, 2.513), |
2730 | 5x |
eff_dose = c(25, 300), |
2731 | 5x |
sigma2W = c(a = 0.1, b = 0.1), |
2732 | 5x |
sigma2betaW = c(a = 20, b = 50), |
2733 | 5x |
rw1 = FALSE, |
2734 | 5x |
data = data |
2735 |
) |
|
2736 |
} |
|
2737 | ||
2738 |
# DALogisticLogNormal ---- |
|
2739 | ||
2740 |
## class ---- |
|
2741 | ||
2742 |
#' `DALogisticLogNormal` |
|
2743 |
#' |
|
2744 |
#' @description `r lifecycle::badge("stable")` |
|
2745 |
#' |
|
2746 |
#' [`DALogisticLogNormal`] is the class for the logistic model with bivariate |
|
2747 |
#' (log) normal prior and data augmentation. This class inherits from the |
|
2748 |
#' [`LogisticLogNormal`] class. |
|
2749 |
#' |
|
2750 |
#' @note We still need to include here formula for the lambda prior. |
|
2751 |
#' |
|
2752 |
#' @slot npiece (`number`)\cr the number of pieces in the `PEM`. |
|
2753 |
#' @slot l (`numeric`)\cr a vector used in the lambda prior. |
|
2754 |
#' @slot c_par (`numeric`)\cr a parameter used in the lambda prior; according to |
|
2755 |
#' Liu's paper, `c_par = 2` is recommended. |
|
2756 |
#' @slot cond_pem (`flag`)\cr is a conditional piecewise-exponential model used? |
|
2757 |
#' (default). Otherwise an unconditional model is used. |
|
2758 |
#' |
|
2759 |
#' @seealso [`ModelLogNormal`], [`LogisticNormal`], [`LogisticLogNormal`]. |
|
2760 |
#' |
|
2761 |
#' @aliases DALogisticLogNormal |
|
2762 |
#' @export |
|
2763 |
#' |
|
2764 |
.DALogisticLogNormal <- setClass( |
|
2765 |
Class = "DALogisticLogNormal", |
|
2766 |
slots = c( |
|
2767 |
npiece = "integer", |
|
2768 |
l = "numeric", |
|
2769 |
c_par = "numeric", |
|
2770 |
cond_pem = "logical" |
|
2771 |
), |
|
2772 |
prototype = prototype( |
|
2773 |
npiece = 3L, |
|
2774 |
l = 0.5, |
|
2775 |
c_par = 2, |
|
2776 |
cond_pem = TRUE |
|
2777 |
), |
|
2778 |
contains = "LogisticLogNormal", |
|
2779 |
validity = v_model_da_logistic_log_normal |
|
2780 |
) |
|
2781 | ||
2782 |
## constructor ---- |
|
2783 | ||
2784 |
#' @rdname DALogisticLogNormal-class |
|
2785 |
#' |
|
2786 |
#' @param npiece (`number`)\cr the number of pieces in the `PEM`. |
|
2787 |
#' @param l (`numeric`)\cr a vector used in the lambda prior. |
|
2788 |
#' @param c_par (`numeric`)\cr a parameter used in the lambda prior; according to |
|
2789 |
#' Liu's paper, `c_par = 2` is recommended. |
|
2790 |
#' @param cond_pem (`flag`)\cr is a conditional piecewise-exponential model used? |
|
2791 |
#' (default). Otherwise an unconditional model is used. |
|
2792 |
#' @inheritDotParams LogisticLogNormal |
|
2793 |
#' |
|
2794 |
#' @export |
|
2795 |
#' @example examples/Model-class-DALogisticLogNormal.R |
|
2796 |
#' |
|
2797 |
DALogisticLogNormal <- function(npiece = 3, |
|
2798 |
l, |
|
2799 |
c_par = 2, |
|
2800 |
cond_pem = TRUE, |
|
2801 |
...) { |
|
2802 | 29x |
assert_flag(cond_pem) |
2803 | ||
2804 | 29x |
start <- LogisticLogNormal(...) |
2805 | ||
2806 | 29x |
datamodel <- function() { |
2807 | ! |
for (i in 1:nObs) { |
2808 |
# Part I: describe the logistic model of DLTs vs dose. |
|
2809 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
2810 | ||
2811 |
# Part II: describe the piecewise exponential. |
|
2812 |
# Notice that: |
|
2813 |
# when y=1 -> DLT=1 and u=<T; |
|
2814 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
2815 |
# when y=0 & T>t (u<T) -> DLT=NA/missing; |
|
2816 |
# when indx=0 -> censored, i.e u<T and event=0; |
|
2817 |
# when indx=1 -> not censored, i.e. u>=T or event=1; |
|
2818 | ! |
indx[i] <- 1 - step(Tmax - u[i] - eps) * (1 - y[i]) |
2819 | ||
2820 | ! |
for (j in 1:npiece) { |
2821 |
# When not censored, i.e DLT!=NA & t[i]=u[i]; |
|
2822 |
# if t[i]<h[j], d[i,j]=0; |
|
2823 |
# if h[j]<t[i]=<h[j+1], d[i,j]=1 |
|
2824 |
# if h[j+1]<t[i], d[i,j]=0 |
|
2825 |
# When censored t[i]>u[i] -> d[i,j]=0 |
|
2826 | ! |
d[i, j] <- y[i] * step(u[i] - h[j] - eps) * step(h[j + 1] - u[i]) |
2827 | ||
2828 |
# DLT free survival(time) for patient i in interval I(j); |
|
2829 |
# if t[i]<h[j], s[i,j]=0; |
|
2830 |
# if h[j]<t[i]<=h[j+1], s[i,j]=t[i]-h[j] |
|
2831 |
# if h[j+1]<=t[i], s[i,j]=h[j+1]-h[j] |
|
2832 | ! |
s[i, j] <- min(u[i] - h[j], h[j + 1] - h[j]) * step(u[i] - h[j]) |
2833 | ||
2834 |
# piecewise exponential hazard rate lambda[j]; |
|
2835 | ! |
mu_u[i, j] <- lambda[j] * s[i, j] |
2836 | ! |
mu[i, j] <- d[i, j] * log(lambda[j]) - y[i] * mu_u[i, j] |
2837 |
} |
|
2838 | ||
2839 |
# The likelihood function. |
|
2840 | ! |
L_obs[i] <- exp(sum(mu[i, ])) * pow(p[i] / A, y[i]) * pow(1 - p[i], 1 - y[i]) # Not censored. # nolintr |
2841 | ! |
L_cnsr[i] <- 1 - p[i] * (1 - exp(-sum(mu_u[i, ]))) / A # Censored. # nolintr |
2842 | ! |
L[i] <- pow(L_obs[i], indx[i]) * pow(L_cnsr[i], 1 - indx[i]) |
2843 | ||
2844 |
# Apply zero trick in JAGS. |
|
2845 | ! |
phi[i] <- -log(L[i]) + cadj |
2846 | ! |
zeros[i] ~ dpois(phi[i]) |
2847 |
} |
|
2848 |
} |
|
2849 | ||
2850 | 29x |
priormodel <- h_jags_join_models( |
2851 | 29x |
start@priormodel, |
2852 | 29x |
function() { |
2853 | ! |
g_beta <- 1 / c_par |
2854 | ! |
for (j in 1:npiece) { |
2855 | ! |
g_alpha[j] <- l[j] / c_par |
2856 | ! |
lambda[j] ~ dgamma(g_alpha[j], g_beta) |
2857 | ! |
mu_T[j] <- lambda[j] * (h[j + 1] - h[j]) # nolintr |
2858 |
} |
|
2859 |
# If cond = 1, then conditional PEM is used and A is defined as |
|
2860 |
# the probability to have DLT, i.e. t<T, otherwise |
|
2861 |
# cond = 0 and A is just 1 (so no impact in likelihood). |
|
2862 | ! |
A <- cond * (1 - exp(-sum(mu_T))) + (1 - cond) |
2863 |
} |
|
2864 |
) |
|
2865 | ||
2866 | 29x |
modelspecs <- function(nObs, Tmax, from_prior) { |
2867 | 20x |
ms <- list( |
2868 | 20x |
prec = start@params@prec, |
2869 | 20x |
mean = start@params@mean, |
2870 | 20x |
npiece = npiece, |
2871 | 20x |
l = l, |
2872 | 20x |
c_par = c_par, |
2873 | 20x |
h = seq(from = 0L, to = Tmax, length = npiece + 1), |
2874 | 20x |
cond = as.integer(cond_pem) |
2875 |
) |
|
2876 | 20x |
if (!from_prior) { |
2877 | 19x |
ms <- c(list(ref_dose = start@ref_dose, zeros = rep(0, nObs), eps = 1e-10, cadj = 1e10), ms) |
2878 |
} |
|
2879 | 20x |
ms |
2880 |
} |
|
2881 | ||
2882 | 29x |
assert_integerish(npiece, lower = 1) |
2883 | ||
2884 | 29x |
.DALogisticLogNormal( |
2885 | 29x |
start, |
2886 | 29x |
npiece = as.integer(npiece), |
2887 | 29x |
l = l, |
2888 | 29x |
c_par = c_par, |
2889 | 29x |
cond_pem = cond_pem, |
2890 | 29x |
datamodel = datamodel, |
2891 | 29x |
priormodel = priormodel, |
2892 | 29x |
modelspecs = modelspecs, |
2893 | 29x |
datanames = c("nObs", "y", "x", "u", "Tmax"), |
2894 | 29x |
sample = c("alpha0", "alpha1", "lambda") |
2895 |
) |
|
2896 |
} |
|
2897 | ||
2898 |
## default constructor ---- |
|
2899 | ||
2900 |
#' @rdname DALogisticLogNormal-class |
|
2901 |
#' @note Typically, end users will not use the `.DefaultDALogisticLogNormal()` function. |
|
2902 |
#' @export |
|
2903 |
.DefaultDALogisticLogNormal <- function() { |
|
2904 | 7x |
npiece <- 10 |
2905 | 7x |
Tmax <- 60 |
2906 | ||
2907 | 7x |
lambda_prior <- function(k) { |
2908 | 7x |
npiece / (Tmax * (npiece - k + 0.5)) |
2909 |
} |
|
2910 | ||
2911 | 7x |
DALogisticLogNormal( |
2912 | 7x |
mean = c(-0.85, 1), |
2913 | 7x |
cov = matrix(c(1, -0.5, -0.5, 1), nrow = 2), |
2914 | 7x |
ref_dose = 56, |
2915 | 7x |
npiece = npiece, |
2916 | 7x |
l = as.numeric(t(apply(as.matrix(c(1:npiece), 1, npiece), 2, lambda_prior))), |
2917 | 7x |
c_par = 2 |
2918 |
) |
|
2919 |
} |
|
2920 | ||
2921 |
# TITELogisticLogNormal ---- |
|
2922 | ||
2923 |
## class ---- |
|
2924 | ||
2925 |
#' `TITELogisticLogNormal` |
|
2926 |
#' |
|
2927 |
#' @description `r lifecycle::badge("stable")` |
|
2928 |
#' |
|
2929 |
#' [`TITELogisticLogNormal`] is the class for TITE-CRM based on a logistic |
|
2930 |
#' regression model using a bivariate normal prior on the intercept and log |
|
2931 |
#' slope parameters. |
|
2932 |
#' |
|
2933 |
#' This class inherits from the [`LogisticLogNormal`]. |
|
2934 |
#' |
|
2935 |
#' @slot weight_method (`string`)\cr the weight function method: either linear |
|
2936 |
#' or adaptive. This was used in Liu, Yin and Yuan's paper. |
|
2937 |
#' |
|
2938 |
#' @seealso [`DALogisticLogNormal`]. |
|
2939 |
#' |
|
2940 |
#' @aliases TITELogisticLogNormal |
|
2941 |
#' @export |
|
2942 |
#' |
|
2943 |
.TITELogisticLogNormal <- setClass( |
|
2944 |
Class = "TITELogisticLogNormal", |
|
2945 |
slots = c(weight_method = "character"), |
|
2946 |
prototype = prototype(weight_method = "linear"), |
|
2947 |
contains = "LogisticLogNormal", |
|
2948 |
validity = v_model_tite_logistic_log_normal |
|
2949 |
) |
|
2950 | ||
2951 |
## constructor ---- |
|
2952 | ||
2953 |
#' @rdname TITELogisticLogNormal-class |
|
2954 |
#' |
|
2955 |
#' @param weight_method (`string`)\cr the weight function method: either linear |
|
2956 |
#' or adaptive. This was used in Liu, Yin and Yuan's paper. |
|
2957 |
#' @inheritDotParams LogisticLogNormal |
|
2958 |
#' |
|
2959 |
#' @export |
|
2960 |
#' @example examples/Model-class-TITELogisticLogNormal.R |
|
2961 |
#' |
|
2962 |
TITELogisticLogNormal <- function(weight_method = "linear", |
|
2963 |
...) { |
|
2964 | 18x |
assert_character(weight_method, min.len = 1L, max.len = 2L, any.missing = FALSE) |
2965 | ||
2966 | 18x |
start <- LogisticLogNormal(...) |
2967 | ||
2968 | 18x |
datamodel <- function() { |
2969 | ! |
for (i in 1:nObs) { |
2970 | ! |
logit(p[i]) <- alpha0 + alpha1 * log(x[i] / ref_dose) |
2971 | ||
2972 |
# The piecewise exponential likelihood. Notice that: |
|
2973 |
# when y=1 -> DLT=1 and u=<T; |
|
2974 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
2975 |
# when y=0 & T>t (u<T) -> DLT=NA/missing; |
|
2976 |
# when indx=0 -> censored, i.e u<T and event=0; |
|
2977 |
# when indx=1 -> not censored, i.e. u>=T or event=1; |
|
2978 | ! |
L[i] <- pow(p[i], y[i]) * pow((1 - w[i] * p[i]), (1 - y[i])) |
2979 | ||
2980 |
# Apply zero trick in JAGS. |
|
2981 | ! |
phi[i] <- -log(L[i]) + cadj |
2982 | ! |
zeros[i] ~ dpois(phi[i]) |
2983 |
} |
|
2984 |
} |
|
2985 | ||
2986 | 18x |
modelspecs <- function(nObs, u, Tmax, y, from_prior) { |
2987 | 6x |
ms <- list(prec = start@params@prec, mean = start@params@mean) |
2988 |
# Calculate weights `w` based on the input data. |
|
2989 | 6x |
if (!from_prior && nObs > 0L) { |
2990 | 4x |
if (weight_method == "linear") { |
2991 | 2x |
w <- u / Tmax |
2992 | 2x |
} else if (weight_method == "adaptive") { |
2993 | 2x |
nDLT <- sum(y) |
2994 | 2x |
if (nDLT > 0) { |
2995 | 2x |
u_dlt <- sort(u[y == 1]) |
2996 | 2x |
w <- sapply(u, function(u_i) { |
2997 | 20x |
m <- sum(u_i >= u_dlt) |
2998 | 20x |
w_i <- if (m == 0) { |
2999 | 9x |
u_i / u_dlt[1] |
3000 | 20x |
} else if (m < nDLT) { |
3001 | 5x |
m + (u_i - u_dlt[m]) / (u_dlt[m + 1] - u_dlt[m]) |
3002 | 20x |
} else { # m == nDLT. nolintr |
3003 | 6x |
m + (u_i - u_dlt[m]) / (Tmax + 0.00000001 - u_dlt[m]) |
3004 |
} |
|
3005 | 20x |
w_i / (nDLT + 1) |
3006 |
}) |
|
3007 |
} else { |
|
3008 | ! |
w <- u / Tmax |
3009 |
} |
|
3010 |
} |
|
3011 | 4x |
w[y == 1] <- 1 |
3012 | 4x |
w[u == Tmax] <- 1 |
3013 | ||
3014 | 4x |
ms <- c(list(ref_dose = start@ref_dose, zeros = rep(0, nObs), cadj = 1e10, w = w), ms) |
3015 |
} |
|
3016 | 6x |
ms |
3017 |
} |
|
3018 | ||
3019 | 18x |
.TITELogisticLogNormal( |
3020 | 18x |
start, |
3021 | 18x |
weight_method = weight_method, |
3022 | 18x |
datamodel = datamodel, |
3023 | 18x |
modelspecs = modelspecs, |
3024 | 18x |
datanames = c("nObs", "y", "x") |
3025 |
) |
|
3026 |
} |
|
3027 | ||
3028 |
## default constructor ---- |
|
3029 | ||
3030 |
#' @rdname TITELogisticLogNormal-class |
|
3031 |
#' @note Typically, end users will not use the `.DefaultTITELogisticLogNormal()` function. |
|
3032 |
#' @export |
|
3033 |
.DefaultTITELogisticLogNormal <- function() { |
|
3034 | 7x |
TITELogisticLogNormal( |
3035 | 7x |
mean = c(0, 1), |
3036 | 7x |
cov = diag(2), |
3037 | 7x |
ref_dose = 1, |
3038 | 7x |
weight_method = "linear" |
3039 |
) |
|
3040 |
} |
|
3041 | ||
3042 |
# OneParLogNormalPrior ---- |
|
3043 | ||
3044 |
## class ---- |
|
3045 | ||
3046 |
#' `OneParLogNormalPrior` |
|
3047 |
#' |
|
3048 |
#' @description `r lifecycle::badge("stable")` |
|
3049 |
#' |
|
3050 |
#' [`OneParLogNormalPrior`] is the class for a standard CRM with a normal prior on |
|
3051 |
#' the log power parameter for the skeleton prior probabilities. |
|
3052 |
#' |
|
3053 |
#' @slot skel_fun (`function`)\cr function to calculate the prior DLT probabilities. |
|
3054 |
#' @slot skel_fun_inv (`function`)\cr inverse function of `skel_fun`. |
|
3055 |
#' @slot skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
3056 |
#' of unique and sorted probability values between 0 and 1. |
|
3057 |
#' @slot sigma2 (`number`)\cr prior variance of log power parameter alpha. |
|
3058 |
#' |
|
3059 |
#' @seealso [`ModelLogNormal`]. |
|
3060 |
#' |
|
3061 |
#' @aliases OneParLogNormalPrior |
|
3062 |
#' @export |
|
3063 |
#' |
|
3064 |
.OneParLogNormalPrior <- setClass( |
|
3065 |
Class = "OneParLogNormalPrior", |
|
3066 |
slots = c( |
|
3067 |
skel_fun = "function", |
|
3068 |
skel_fun_inv = "function", |
|
3069 |
skel_probs = "numeric", |
|
3070 |
sigma2 = "numeric" |
|
3071 |
), |
|
3072 |
contains = "GeneralModel", |
|
3073 |
validity = v_model_one_par_exp_normal_prior |
|
3074 |
) |
|
3075 | ||
3076 |
## constructor ---- |
|
3077 | ||
3078 |
#' @rdname OneParLogNormalPrior-class |
|
3079 |
#' |
|
3080 |
#' @param skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
3081 |
#' of unique and sorted probability values between 0 and 1. |
|
3082 |
#' @param dose_grid (`numeric`)\cr dose grid. It must be must be a sorted vector |
|
3083 |
#' of the same length as `skel_probs`. |
|
3084 |
#' @param sigma2 (`number`)\cr prior variance of log power parameter alpha. |
|
3085 |
#' |
|
3086 |
#' @export |
|
3087 |
#' @example examples/Model-class-OneParLogNormalPrior.R |
|
3088 |
#' |
|
3089 |
OneParLogNormalPrior <- function(skel_probs, |
|
3090 |
dose_grid, |
|
3091 |
sigma2) { |
|
3092 | 44x |
assert_probabilities(skel_probs, unique = TRUE, sorted = TRUE) # So that skel_fun_inv exists. |
3093 | 41x |
assert_numeric(dose_grid, len = length(skel_probs), any.missing = FALSE, unique = TRUE, sorted = TRUE) |
3094 | ||
3095 | 38x |
skel_fun <- approxfun(x = dose_grid, y = skel_probs, rule = 2) |
3096 | 38x |
skel_fun_inv <- approxfun(x = skel_probs, y = dose_grid, rule = 2) |
3097 | ||
3098 | 38x |
.OneParLogNormalPrior( |
3099 | 38x |
skel_fun = skel_fun, |
3100 | 38x |
skel_fun_inv = skel_fun_inv, |
3101 | 38x |
skel_probs = skel_probs, |
3102 | 38x |
sigma2 = sigma2, |
3103 | 38x |
datamodel = function() { |
3104 | ! |
for (i in 1:nObs) { |
3105 | ! |
p[i] <- skel_probs[xLevel[i]]^exp(alpha) |
3106 | ! |
y[i] ~ dbern(p[i]) |
3107 |
} |
|
3108 |
}, |
|
3109 | 38x |
priormodel = function() { |
3110 | ! |
alpha ~ dnorm(0, 1 / sigma2) |
3111 |
}, |
|
3112 | 38x |
modelspecs = function(from_prior) { |
3113 | 5x |
ms <- list(sigma2 = sigma2) |
3114 | 5x |
if (!from_prior) { |
3115 | 3x |
ms$skel_probs <- skel_probs |
3116 |
} |
|
3117 | 5x |
ms |
3118 |
}, |
|
3119 | 38x |
init = function() { |
3120 | 7x |
list(alpha = 1) |
3121 |
}, |
|
3122 | 38x |
datanames = c("nObs", "y", "xLevel"), |
3123 | 38x |
sample = "alpha" |
3124 |
) |
|
3125 |
} |
|
3126 | ||
3127 |
## default constructor ---- |
|
3128 | ||
3129 |
#' @rdname OneParLogNormalPrior-class |
|
3130 |
#' @return an instance of the `OneParLogNormalPrior` class |
|
3131 |
#' @export |
|
3132 |
.DefaultOneParLogNormalPrior <- function() { |
|
3133 | 6x |
OneParLogNormalPrior( |
3134 | 6x |
skel_probs = seq(from = 0.1, to = 0.9, length = 5), |
3135 | 6x |
dose_grid = 1:5, |
3136 | 6x |
sigma2 = 2 |
3137 |
) |
|
3138 |
} |
|
3139 | ||
3140 |
# OneParExpPrior ---- |
|
3141 | ||
3142 |
## class ---- |
|
3143 | ||
3144 |
#' `OneParExpPrior` |
|
3145 |
#' |
|
3146 |
#' @description `r lifecycle::badge("experimental")` |
|
3147 |
#' |
|
3148 |
#' [`OneParExpPrior`] is the class for a standard CRM with an exponential prior |
|
3149 |
#' on the power parameter for the skeleton prior probabilities. It is an |
|
3150 |
#' implementation of a version of the one-parameter CRM (O’Quigley et al. 1990). |
|
3151 |
#' |
|
3152 |
#' @note Typically, end users will not use the `.DefaultOneparExpPrior()` function. |
|
3153 |
#' |
|
3154 |
#' @slot skel_fun (`function`)\cr function to calculate the prior DLT probabilities. |
|
3155 |
#' @slot skel_fun_inv (`function`)\cr inverse function of `skel_fun`. |
|
3156 |
#' @slot skel_probs (`numeric`)\cr skeleton prior probabilities. This is a vector |
|
3157 |
#' of unique and sorted probability values between 0 and 1. |
|
3158 |
#' @slot lambda (`number`)\cr rate parameter of prior exponential distribution |
|
3159 |
#' for theta. |
|
3160 |
#' |
|
3161 |
#' @aliases OneParExpPrior |
|
3162 |
#' @export |
|
3163 |
#' |
|
3164 |
.OneParExpPrior <- setClass( |
|
3165 |
Class = "OneParExpPrior", |
|
3166 |
slots = c( |
|
3167 |
skel_fun = "function", |
|
3168 |
skel_fun_inv = "function", |
|
3169 |
skel_probs = "numeric", |
|
3170 |
lambda = "numeric" |
|
3171 |
), |
|
3172 |
contains = "GeneralModel", |
|
3173 |
validity = v_model_one_par_exp_prior |
|
3174 |
) |
|
3175 | ||
3176 |
## constructor ---- |
|
3177 | ||
3178 |
#' @rdname OneParExpPrior-class |
|
3179 |
#' |
|
3180 |
#' @param skel_probs see slot definition. |
|
3181 |
#' @param dose_grid (`numeric`)\cr dose grid. It must be must be a sorted vector |
|
3182 |
#' of the same length as `skel_probs`. |
|
3183 |
#' @param lambda see slot definition. |
|
3184 |
#' |
|
3185 |
#' @export |
|
3186 |
#' @example examples/Model-class-OneParExpPrior.R |
|
3187 |
#' |
|
3188 |
OneParExpPrior <- function(skel_probs, |
|
3189 |
dose_grid, |
|
3190 |
lambda) { |
|
3191 | 30x |
assert_probabilities(skel_probs, unique = TRUE, sorted = TRUE) # So that skel_fun_inv exists. |
3192 | 27x |
assert_numeric(dose_grid, len = length(skel_probs), any.missing = FALSE, unique = TRUE, sorted = TRUE) |
3193 | ||
3194 | 24x |
skel_fun <- approxfun(x = dose_grid, y = skel_probs, rule = 2) |
3195 | 24x |
skel_fun_inv <- approxfun(x = skel_probs, y = dose_grid, rule = 2) |
3196 | ||
3197 | 24x |
.OneParExpPrior( |
3198 | 24x |
skel_fun = skel_fun, |
3199 | 24x |
skel_fun_inv = skel_fun_inv, |
3200 | 24x |
skel_probs = skel_probs, |
3201 | 24x |
lambda = lambda, |
3202 | 24x |
datamodel = function() { |
3203 | ! |
for (i in 1:nObs) { |
3204 | ! |
p[i] <- skel_probs[xLevel[i]]^theta |
3205 | ! |
y[i] ~ dbern(p[i]) |
3206 |
} |
|
3207 |
}, |
|
3208 | 24x |
priormodel = function() { |
3209 | ! |
theta ~ dexp(lambda) |
3210 |
}, |
|
3211 | 24x |
modelspecs = function(from_prior) { |
3212 | 2x |
ms <- list(lambda = lambda) |
3213 | 2x |
if (!from_prior) { |
3214 | 1x |
ms$skel_probs <- skel_probs |
3215 |
} |
|
3216 | 2x |
ms |
3217 |
}, |
|
3218 | 24x |
init = function() { |
3219 | 2x |
list(theta = 1) |
3220 |
}, |
|
3221 | 24x |
datanames = c("nObs", "y", "xLevel"), |
3222 | 24x |
sample = "theta" |
3223 |
) |
|
3224 |
} |
|
3225 | ||
3226 |
## default constructor ---- |
|
3227 | ||
3228 |
#' @rdname OneParExpPrior-class |
|
3229 |
#' @note Typically, end users will not use the `.DefaultOneParLogNormalPrior()` function. |
|
3230 |
#' @export |
|
3231 |
.DefaultOneParExpPrior <- function() { |
|
3232 | 6x |
OneParExpPrior( |
3233 | 6x |
skel_probs = c(0.1, 0.3, 0.5, 0.7, 0.9), |
3234 | 6x |
dose_grid = 1:5, |
3235 | 6x |
lambda = 2 |
3236 |
) |
|
3237 |
} |
|
3238 | ||
3239 |
# FractionalCRM ---- |
|
3240 | ||
3241 |
## class ---- |
|
3242 | ||
3243 |
#' `FractionalCRM` |
|
3244 |
#' |
|
3245 |
#' @description `r lifecycle::badge("stable")` |
|
3246 |
#' |
|
3247 |
#' [`FractionalCRM`] is the class for a fractional CRM model based on a one |
|
3248 |
#' parameter CRM (with normal prior on the log-power parameter) as well as |
|
3249 |
#' Kaplan-Meier based estimation of the conditional probability to experience a |
|
3250 |
#' DLT for non-complete observations. |
|
3251 |
#' |
|
3252 |
#' This fractional CRM model follows the paper and code by Guosheng Yin et al. |
|
3253 |
#' |
|
3254 |
#' @seealso [`TITELogisticLogNormal`]. |
|
3255 |
#' |
|
3256 |
#' @aliases FractionalCRM |
|
3257 |
#' @export |
|
3258 |
#' |
|
3259 |
.FractionalCRM <- setClass( |
|
3260 |
Class = "FractionalCRM", |
|
3261 |
contains = "OneParLogNormalPrior" |
|
3262 |
) |
|
3263 | ||
3264 |
## constructor ---- |
|
3265 | ||
3266 |
#' @rdname FractionalCRM-class |
|
3267 |
#' |
|
3268 |
#' @inheritDotParams OneParLogNormalPrior |
|
3269 |
#' |
|
3270 |
#' @export |
|
3271 |
#' @example examples/Model-class-FractionalCRM.R |
|
3272 |
#' |
|
3273 |
FractionalCRM <- function(...) { |
|
3274 | 12x |
start <- OneParLogNormalPrior(...) |
3275 | ||
3276 |
# This is adapted from the TITELogisticLogNormal class. |
|
3277 | 12x |
datamodel <- function() { |
3278 | ! |
for (i in 1:nObs) { |
3279 | ! |
p[i] <- skel_probs[xLevel[i]]^exp(alpha) |
3280 | ||
3281 |
# The piecewise exponential likelihood. Notice that: |
|
3282 |
# when y=1 -> DLT=1 and u=<T; |
|
3283 |
# when y=0 & T<t (u=T) -> DLT=0; |
|
3284 |
# when y=0 & T>t (u<T) -> DLT=NA/missing. |
|
3285 |
# Therefore, `yhat` is used instead of `y` for the likelihood f. (see `modelspecs`). |
|
3286 | ! |
L[i] <- pow(p[i], yhat[i]) * pow((1 - p[i]), (1 - yhat[i])) |
3287 | ||
3288 |
# Apply zero trick in JAGS. |
|
3289 | ! |
phi[i] <- -log(L[i]) + cadj |
3290 | ! |
zeros[i] ~ dpois(phi[i]) |
3291 |
} |
|
3292 |
} |
|
3293 | ||
3294 | 12x |
modelspecs <- function(nObs, u, Tmax, y, from_prior) { |
3295 | 2x |
ms <- list(sigma2 = start@sigma2) |
3296 | 2x |
if (!from_prior) { |
3297 |
# Calculate fractional contribution `yhat` |
|
3298 |
# based on the input data using the Kaplan-Meier method. |
|
3299 | 1x |
yhat <- if (nObs > 0) { |
3300 | 1x |
km <- survival::survfit(survival::Surv(u, y) ~ 1) |
3301 | 1x |
s_tau <- tail(km$surv[km$time <= Tmax], 1) # Survival probability = S(Tmax). |
3302 | 1x |
ifelse( |
3303 | 1x |
u < Tmax & y == 0L, # Within the assessment window and so far no DLT. |
3304 | 1x |
yes = 1 - s_tau / sapply(u, function(u_i) tail(km$surv[km$time <= u_i], 1)), |
3305 | 1x |
no = y |
3306 |
) |
|
3307 |
} else { |
|
3308 | ! |
1L |
3309 |
} |
|
3310 | 1x |
ms <- c( |
3311 | 1x |
list(skel_probs = start@skel_probs, zeros = rep(0, nObs), cadj = 1e10, yhat = yhat), |
3312 | 1x |
ms |
3313 |
) |
|
3314 |
} |
|
3315 | 2x |
ms |
3316 |
} |
|
3317 | ||
3318 | 12x |
.FractionalCRM( |
3319 | 12x |
start, |
3320 | 12x |
datamodel = datamodel, |
3321 | 12x |
modelspecs = modelspecs, |
3322 | 12x |
datanames = c("nObs", "xLevel") |
3323 |
) |
|
3324 |
} |
|
3325 | ||
3326 |
## default constructor ---- |
|
3327 | ||
3328 |
#' @rdname FractionalCRM-class |
|
3329 |
#' @note Typically, end users will not use the `.DefaultTITELogisticLogNormal()` function. |
|
3330 |
#' @export |
|
3331 |
.DefaultFractionalCRM <- function() { |
|
3332 | 7x |
FractionalCRM( |
3333 | 7x |
skel_probs = c(0.1, 0.2, 0.3, 0.4), |
3334 | 7x |
dose_grid = c(10, 30, 50, 100), |
3335 | 7x |
sigma2 = 2 |
3336 |
) |
|
3337 |
} |
|
3338 | ||
3339 |
## class ---- |
|
3340 | ||
3341 |
#' `LogisticLogNormalOrdinal` |
|
3342 |
#' |
|
3343 |
#' @description `r lifecycle::badge("experimental")` |
|
3344 |
#' |
|
3345 |
#' [`LogisticLogNormalOrdinal`] is the class for a logistic lognormal CRM model |
|
3346 |
#' using an ordinal toxicity scale. |
|
3347 |
#' |
|
3348 |
#' @aliases LogisticLogNormalOrdinal |
|
3349 |
#' @export |
|
3350 |
.LogisticLogNormalOrdinal <- setClass( |
|
3351 |
Class = "LogisticLogNormalOrdinal", |
|
3352 |
contains = "ModelLogNormal", |
|
3353 |
validity = v_logisticlognormalordinal |
|
3354 |
) |
|
3355 | ||
3356 |
## constructor ---- |
|
3357 | ||
3358 |
#' @rdname LogisticLogNormalOrdinal-class |
|
3359 |
#' @inheritParams ModelLogNormal |
|
3360 |
#' @export |
|
3361 |
#' @example examples/Model-class-LogisticLogNormalOrdinal.R |
|
3362 |
LogisticLogNormalOrdinal <- function(mean, cov, ref_dose) { |
|
3363 | 31x |
params <- ModelParamsNormal(mean, cov) |
3364 | 31x |
.LogisticLogNormalOrdinal( |
3365 | 31x |
params = params, |
3366 | 31x |
ref_dose = positive_number(ref_dose), |
3367 | 31x |
priormodel = function() { |
3368 | ! |
alpha[1] ~ dnorm(mean[1], prec[1, 1]) |
3369 | ! |
for (i in 2:(k - 1)) { |
3370 | ! |
alpha[i] ~ dnorm(mean[i], prec[i, i]) %_% T(, alpha[i - 1]) |
3371 |
} |
|
3372 | ! |
gamma ~ dnorm(mean[k], prec[k, k]) |
3373 | ! |
beta <- exp(gamma) |
3374 |
}, |
|
3375 | 31x |
datamodel = function() { |
3376 | ! |
for (i in 1:nObs) { |
3377 | ! |
xhat[i] <- log(x[i] / ref_dose) |
3378 | ! |
for (j in 1:(k - 1)) { |
3379 | ! |
z[i, j] <- alpha[j] + beta * xhat[i] |
3380 | ! |
p[i, j] <- exp(z[i, j]) / (1 + exp(z[i, j])) |
3381 | ! |
tox[i, j] ~ dbern(p[i, j]) |
3382 |
} |
|
3383 |
} |
|
3384 |
}, |
|
3385 | 31x |
modelspecs = function(y, from_prior) { |
3386 | 15x |
ms <- list( |
3387 | 15x |
mean = params@mean, |
3388 | 15x |
prec = params@prec, |
3389 | 15x |
k = length(mean), |
3390 | 15x |
tox = array(dim = c(length(y), length(mean) - 1)) |
3391 |
) |
|
3392 | 15x |
if (!from_prior) { |
3393 | 14x |
for (i in seq_along(y)) { |
3394 | 134x |
for (j in 1:(ms$k - 1)) { |
3395 | 268x |
ms$tox[i, j] <- y[i] >= j |
3396 |
} |
|
3397 |
} |
|
3398 | 14x |
ms$ref_dose <- ref_dose |
3399 |
} |
|
3400 | 15x |
ms |
3401 |
}, |
|
3402 | 31x |
init = function() { |
3403 | 15x |
list(alpha = sapply(1:(length(mean) - 1), function(x) -(x + 1)), gamma = 1) |
3404 |
}, |
|
3405 | 31x |
datanames = c("nObs", "y", "x"), |
3406 |
# Need to provide JAGS column names here |
|
3407 | 31x |
sample = c(paste0("alpha[", 1:(length(mean) - 1), "]"), "beta") |
3408 |
) |
|
3409 |
} |
|
3410 | ||
3411 |
## default constructor ---- |
|
3412 | ||
3413 |
#' @rdname LogisticLogNormalOrdinal-class |
|
3414 |
#' @note Typically, end users will not use the `.DefaultLogisticLogNormalOrdinal()` function. |
|
3415 |
#' @export |
|
3416 |
.DefaultLogisticLogNormalOrdinal <- function() { |
|
3417 | 23x |
LogisticLogNormalOrdinal( |
3418 | 23x |
mean = c(-3, -4, 1), |
3419 | 23x |
cov = diag(c(3, 4, 1)), |
3420 | 23x |
ref_dose = 50 |
3421 |
) |
|
3422 |
} |
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 |
# nolint start |
|
11 | ||
12 |
## ============================================================ |
|
13 | ||
14 |
##' Simulate outcomes from a CRM design |
|
15 |
##' |
|
16 |
##' @param object the \code{\linkS4class{Design}} object we want to simulate |
|
17 |
##' data from |
|
18 |
##' @param nsim the number of simulations (default: 1) |
|
19 |
##' @param seed see \code{\link{set_seed}} |
|
20 |
##' @param truth a function which takes as input a dose (vector) and returns the |
|
21 |
##' true probability (vector) for toxicity. Additional arguments can be supplied |
|
22 |
##' in \code{args}. |
|
23 |
##' @param args data frame with arguments for the \code{truth} function. The |
|
24 |
##' column names correspond to the argument names, the rows to the values of the |
|
25 |
##' arguments. The rows are appropriately recycled in the \code{nsim} |
|
26 |
##' simulations. In order to produce outcomes from the posterior predictive |
|
27 |
##' distribution, e.g, pass an \code{object} that contains the data observed so |
|
28 |
##' far, \code{truth} contains the \code{prob} function from the model in |
|
29 |
##' \code{object}, and \code{args} contains posterior samples from the model. |
|
30 |
##' @param firstSeparate enroll the first patient separately from the rest of |
|
31 |
##' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
32 |
##' in this patient. |
|
33 |
##' @param mcmcOptions object of class \code{\linkS4class{McmcOptions}}, |
|
34 |
##' giving the MCMC options for each evaluation in the trial. By default, |
|
35 |
##' the standard options are used |
|
36 |
##' @param parallel should the simulation runs be parallelized across the |
|
37 |
##' clusters of the computer? (not default) |
|
38 |
##' @param nCores how many cores should be used for parallel computing? |
|
39 |
##' Defaults to the number of cores on the machine, maximum 5. |
|
40 |
##' @param \dots not used |
|
41 |
##' @param derive a named list of functions which derives statistics, based on the |
|
42 |
##' vector of posterior MTD samples. Each list element must therefore accept |
|
43 |
##' one and only one argument, which is a numeric vector, and return a number. |
|
44 |
##' |
|
45 |
##' @return an object of class \code{\linkS4class{Simulations}} |
|
46 |
##' |
|
47 |
##' @example examples/design-method-simulate-Design.R |
|
48 |
##' @export |
|
49 |
##' @importFrom parallel detectCores |
|
50 |
##' @keywords methods |
|
51 |
setMethod("simulate", |
|
52 |
signature = |
|
53 |
signature( |
|
54 |
object = "Design", |
|
55 |
nsim = "ANY", |
|
56 |
seed = "ANY" |
|
57 |
), |
|
58 |
def = |
|
59 |
function(object, nsim = 1L, seed = NULL, |
|
60 |
truth, args = NULL, firstSeparate = FALSE, |
|
61 |
mcmcOptions = McmcOptions(), |
|
62 |
parallel = FALSE, nCores = |
|
63 |
min(parallel::detectCores(), 5), derive = list(), |
|
64 |
...) { |
|
65 |
## checks and extracts |
|
66 | 6x |
assert_function(truth) |
67 | 6x |
assert_flag(firstSeparate) |
68 | 6x |
assert_count(nsim, positive = TRUE) |
69 | 6x |
assert_flag(parallel) |
70 | 6x |
assert_count(nCores, positive = TRUE) |
71 | ||
72 | 6x |
args <- as.data.frame(args) |
73 | 6x |
nArgs <- max(nrow(args), 1L) |
74 | ||
75 |
## seed handling |
|
76 | 6x |
RNGstate <- set_seed(seed) |
77 | ||
78 |
## from this, |
|
79 |
## generate the individual seeds for the simulation runs |
|
80 | 6x |
simSeeds <- sample.int(n = 2147483647, size = as.integer(nsim)) |
81 | ||
82 |
## the function to produce the run a single simulation |
|
83 |
## with index "iterSim" |
|
84 | 6x |
runSim <- function(iterSim) { |
85 |
## set the seed for this run |
|
86 | 18x |
set.seed(simSeeds[iterSim]) |
87 | ||
88 |
## what is now the argument for the truth? |
|
89 |
## (appropriately recycled) |
|
90 | 18x |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
91 | ||
92 |
## start the simulated data with the provided one |
|
93 | 18x |
thisData <- object@data |
94 | ||
95 |
# In case there are placebo |
|
96 | 18x |
if (thisData@placebo) { |
97 |
## what is the probability for tox. at placebo? |
|
98 | 1x |
thisProb.PL <- h_this_truth( |
99 | 1x |
object@data@doseGrid[1], |
100 | 1x |
thisArgs, |
101 | 1x |
truth |
102 |
) |
|
103 |
} |
|
104 | ||
105 |
## shall we stop the trial? |
|
106 |
## First, we want to continue with the starting dose. |
|
107 |
## This variable is updated after each cohort in the loop. |
|
108 | 18x |
stopit <- FALSE |
109 | ||
110 |
## what is the next dose to be used? |
|
111 |
## initialize with starting dose |
|
112 | 18x |
thisDose <- object@startingDose |
113 | ||
114 |
## inside this loop we simulate the whole trial, until stopping |
|
115 | 18x |
while (!stopit) { |
116 |
## what is the probability for tox. at this dose? |
|
117 | 78x |
thisProb <- h_this_truth( |
118 | 78x |
thisDose, |
119 | 78x |
thisArgs, |
120 | 78x |
truth |
121 |
) |
|
122 | ||
123 |
## what is the cohort size at this dose? |
|
124 | 78x |
thisSize <- size(object@cohort_size, |
125 | 78x |
dose = thisDose, |
126 | 78x |
data = thisData |
127 |
) |
|
128 | ||
129 |
## In case there are placebo |
|
130 | 78x |
if (thisData@placebo) { |
131 | 5x |
thisSize.PL <- size(object@pl_cohort_size, |
132 | 5x |
dose = thisDose, |
133 | 5x |
data = thisData |
134 |
) |
|
135 |
} |
|
136 | ||
137 | 78x |
thisData <- h_determine_dlts( |
138 | 78x |
data = thisData, |
139 | 78x |
dose = thisDose, |
140 | 78x |
prob = thisProb, |
141 | 78x |
prob_placebo = thisProb.PL, |
142 | 78x |
cohort_size = thisSize, |
143 | 78x |
cohort_size_placebo = thisSize.PL, |
144 | 78x |
dose_grid = object@data@doseGrid[1], |
145 | 78x |
first_separate = firstSeparate |
146 |
) |
|
147 | ||
148 |
## what is the dose limit? |
|
149 | 78x |
doselimit <- maxDose(object@increments, |
150 | 78x |
data = thisData |
151 |
) |
|
152 | ||
153 |
## generate samples from the model |
|
154 | 78x |
thisSamples <- mcmc( |
155 | 78x |
data = thisData, |
156 | 78x |
model = object@model, |
157 | 78x |
options = mcmcOptions |
158 |
) |
|
159 | ||
160 |
## => what is the next best dose? |
|
161 | 78x |
thisDose <- nextBest(object@nextBest, |
162 | 78x |
doselimit = doselimit, |
163 | 78x |
samples = thisSamples, |
164 | 78x |
model = object@model, |
165 | 78x |
data = thisData |
166 | 78x |
)$value |
167 | ||
168 | ||
169 |
## evaluate stopping rules |
|
170 | 78x |
stopit <- stopTrial(object@stopping, |
171 | 78x |
dose = thisDose, |
172 | 78x |
samples = thisSamples, |
173 | 78x |
model = object@model, |
174 | 78x |
data = thisData |
175 |
) |
|
176 | ||
177 | 78x |
stopit_results <- h_unpack_stopit(stopit) |
178 |
} |
|
179 | ||
180 |
## get the fit |
|
181 | 18x |
thisFit <- fit( |
182 | 18x |
object = thisSamples, |
183 | 18x |
model = object@model, |
184 | 18x |
data = thisData |
185 |
) |
|
186 | ||
187 |
# Get the MTD estimate from the samples. |
|
188 | ||
189 | 18x |
target_dose_samples <- dose( |
190 | 18x |
mean(object@nextBest@target), |
191 | 18x |
model = object@model, |
192 | 18x |
samples = thisSamples |
193 |
) |
|
194 | ||
195 |
# Create a function for additional statistical summary. |
|
196 | ||
197 | 18x |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
198 | ||
199 |
## return the results |
|
200 | 18x |
thisResult <- |
201 | 18x |
list( |
202 | 18x |
data = thisData, |
203 | 18x |
dose = thisDose, |
204 | 18x |
fit = |
205 | 18x |
subset(thisFit, |
206 | 18x |
select = c(middle, lower, upper) |
207 |
), |
|
208 | 18x |
stop = |
209 | 18x |
attr( |
210 | 18x |
stopit, |
211 | 18x |
"message" |
212 |
), |
|
213 | 18x |
report_results = stopit_results, |
214 | 18x |
additional_stats = additional_stats |
215 |
) |
|
216 | 18x |
return(thisResult) |
217 |
} |
|
218 | ||
219 | 6x |
resultList <- get_result_list( |
220 | 6x |
fun = runSim, |
221 | 6x |
nsim = nsim, |
222 | 6x |
vars = |
223 | 6x |
c( |
224 | 6x |
"simSeeds", |
225 | 6x |
"args", |
226 | 6x |
"nArgs", |
227 | 6x |
"firstSeparate", |
228 | 6x |
"truth", |
229 | 6x |
"object", |
230 | 6x |
"mcmcOptions" |
231 |
), |
|
232 | 6x |
parallel = parallel, |
233 | 6x |
n_cores = nCores |
234 |
) |
|
235 | ||
236 |
# format simulation output |
|
237 | 6x |
simulations_output <- h_simulations_output_format(resultList) |
238 | ||
239 |
## return the results in the Simulations class object |
|
240 | 6x |
ret <- Simulations( |
241 | 6x |
data = simulations_output$dataList, |
242 | 6x |
doses = simulations_output$recommendedDoses, |
243 | 6x |
fit = simulations_output$fitList, |
244 | 6x |
stop_report = simulations_output$stop_matrix, |
245 | 6x |
stop_reasons = simulations_output$stopReasons, |
246 | 6x |
additional_stats = simulations_output$additional_stats, |
247 | 6x |
seed = RNGstate |
248 |
) |
|
249 | ||
250 | 6x |
return(ret) |
251 |
} |
|
252 |
) |
|
253 | ||
254 | ||
255 | ||
256 | ||
257 |
##' Simulate outcomes from a rule-based design |
|
258 |
##' |
|
259 |
##' @param object the \code{\linkS4class{RuleDesign}} object we want to simulate |
|
260 |
##' data from |
|
261 |
##' @param nsim the number of simulations (default: 1) |
|
262 |
##' @param seed see \code{\link{set_seed}} |
|
263 |
##' @param truth a function which takes as input a dose (vector) and returns the |
|
264 |
##' true probability (vector) for toxicity. Additional arguments can be supplied |
|
265 |
##' in \code{args}. |
|
266 |
##' @param args data frame with arguments for the \code{truth} function. The |
|
267 |
##' column names correspond to the argument names, the rows to the values of the |
|
268 |
##' arguments. The rows are appropriately recycled in the \code{nsim} |
|
269 |
##' simulations. |
|
270 |
##' @param parallel should the simulation runs be parallelized across the |
|
271 |
##' clusters of the computer? (not default) |
|
272 |
##' @param nCores how many cores should be used for parallel computing? |
|
273 |
##' Defaults to the number of cores on the machine, maximum 5. |
|
274 |
##' @param \dots not used |
|
275 |
##' |
|
276 |
##' @return an object of class \code{\linkS4class{GeneralSimulations}} |
|
277 |
##' |
|
278 |
##' @example examples/design-method-simulate-RuleDesign.R |
|
279 |
##' @export |
|
280 |
##' @keywords methods |
|
281 |
setMethod("simulate", |
|
282 |
signature = |
|
283 |
signature( |
|
284 |
object = "RuleDesign", |
|
285 |
nsim = "ANY", |
|
286 |
seed = "ANY" |
|
287 |
), |
|
288 |
def = |
|
289 |
function(object, nsim = 1L, seed = NULL, |
|
290 |
truth, args = NULL, |
|
291 |
parallel = FALSE, |
|
292 |
nCores = |
|
293 |
min(parallel::detectCores(), 5L), |
|
294 |
...) { |
|
295 |
## checks and extracts |
|
296 | 1x |
assert_function(truth) |
297 | 1x |
assert_count(nsim, positive = TRUE) |
298 | 1x |
assert_flag(parallel) |
299 | 1x |
assert_count(nCores, positive = TRUE) |
300 | ||
301 | 1x |
args <- as.data.frame(args) |
302 | 1x |
nArgs <- max(nrow(args), 1L) |
303 | ||
304 |
## seed handling |
|
305 | 1x |
RNGstate <- set_seed(seed) |
306 | ||
307 |
## from this, |
|
308 |
## generate the individual seeds for the simulation runs |
|
309 | 1x |
simSeeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
310 | ||
311 |
## the function to produce the run a single simulation |
|
312 |
## with index "iterSim" |
|
313 | 1x |
runSim <- function(iterSim) { |
314 |
## set the seed for this run |
|
315 | 1x |
set.seed(simSeeds[iterSim]) |
316 | ||
317 |
## what is now the argument for the truth? |
|
318 |
## (appropriately recycled) |
|
319 | 1x |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
320 | ||
321 |
## so this truth is... |
|
322 | 1x |
thisTruth <- function(dose) { |
323 | 12x |
do.call( |
324 | 12x |
truth, |
325 |
## First argument: the dose |
|
326 | 12x |
c( |
327 | 12x |
dose, |
328 |
## Following arguments |
|
329 | 12x |
thisArgs |
330 |
) |
|
331 |
) |
|
332 |
} |
|
333 | ||
334 |
## start the simulated data with the provided one |
|
335 | 1x |
thisData <- object@data |
336 | ||
337 |
## shall we stop the trial? |
|
338 |
## First, we want to continue with the starting dose. |
|
339 |
## This variable is updated after each cohort in the loop. |
|
340 | 1x |
stopit <- FALSE |
341 | ||
342 |
## what is the next dose to be used? |
|
343 |
## initialize with starting dose |
|
344 | 1x |
thisDose <- object@startingDose |
345 | ||
346 |
## inside this loop we simulate the whole trial, until stopping |
|
347 | 1x |
while (!stopit) { |
348 |
## what is the probability for tox. at this dose? |
|
349 | 12x |
thisProb <- thisTruth(thisDose) |
350 | ||
351 |
## what is the cohort size at this dose? |
|
352 | 12x |
thisSize <- size(object@cohort_size, |
353 | 12x |
dose = thisDose, |
354 | 12x |
data = thisData |
355 |
) |
|
356 | ||
357 |
## simulate DLTs |
|
358 | 12x |
thisDLTs <- rbinom( |
359 | 12x |
n = thisSize, |
360 | 12x |
size = 1L, |
361 | 12x |
prob = thisProb |
362 |
) |
|
363 | ||
364 |
## update the data with this cohort |
|
365 | 12x |
thisData <- update( |
366 | 12x |
object = thisData, |
367 | 12x |
x = thisDose, |
368 | 12x |
y = thisDLTs |
369 |
) |
|
370 | ||
371 |
## evaluate the rule |
|
372 | 12x |
thisOutcome <- nextBest(object@nextBest, |
373 | 12x |
data = thisData |
374 |
) |
|
375 | ||
376 | 12x |
thisDose <- thisOutcome$value |
377 | 12x |
stopit <- thisOutcome$stopHere |
378 |
} |
|
379 | ||
380 |
## return the results |
|
381 | 1x |
thisResult <- |
382 | 1x |
list( |
383 | 1x |
data = thisData, |
384 | 1x |
dose = thisDose |
385 |
) |
|
386 | ||
387 | 1x |
return(thisResult) |
388 |
} |
|
389 | ||
390 | 1x |
resultList <- get_result_list( |
391 | 1x |
fun = runSim, |
392 | 1x |
nsim = nsim, |
393 | 1x |
vars = |
394 | 1x |
c( |
395 | 1x |
"simSeeds", |
396 | 1x |
"args", |
397 | 1x |
"nArgs", |
398 | 1x |
"truth", |
399 | 1x |
"object" |
400 |
), |
|
401 | 1x |
parallel = parallel, |
402 | 1x |
n_cores = nCores |
403 |
) |
|
404 | ||
405 |
## put everything in the GeneralSimulations format: |
|
406 | ||
407 |
## setup the list for the simulated data objects |
|
408 | 1x |
dataList <- lapply(resultList, "[[", "data") |
409 | ||
410 |
## the vector of the final dose recommendations |
|
411 | 1x |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "dose")) |
412 | ||
413 |
## return the results in the GeneralSimulations class object |
|
414 | 1x |
ret <- GeneralSimulations( |
415 | 1x |
data = dataList, |
416 | 1x |
doses = recommendedDoses, |
417 | 1x |
seed = RNGstate |
418 |
) |
|
419 | ||
420 | 1x |
return(ret) |
421 |
} |
|
422 |
) |
|
423 | ||
424 | ||
425 |
##' Simulate outcomes from a dual-endpoint design |
|
426 |
##' |
|
427 |
##' @param object the \code{\linkS4class{DualDesign}} object we want to simulate |
|
428 |
##' data from |
|
429 |
##' @param nsim the number of simulations (default: 1) |
|
430 |
##' @param seed see \code{\link{set_seed}} |
|
431 |
##' @param trueTox a function which takes as input a dose (vector) and returns the |
|
432 |
##' true probability (vector) for toxicity. Additional arguments can be supplied |
|
433 |
##' in \code{args}. |
|
434 |
##' @param trueBiomarker a function which takes as input a dose (vector) and |
|
435 |
##' returns the true biomarker level (vector). Additional arguments can be |
|
436 |
##' supplied in \code{args}. |
|
437 |
##' @param args data frame with arguments for the \code{trueTox} and |
|
438 |
##' \code{trueBiomarker} function. The column names correspond to the argument |
|
439 |
##' names, the rows to the values of the arguments. The rows are appropriately |
|
440 |
##' recycled in the \code{nsim} simulations. |
|
441 |
##' @param sigma2W variance for the biomarker measurements |
|
442 |
##' @param rho correlation between toxicity and biomarker measurements (default: |
|
443 |
##' 0) |
|
444 |
##' @param firstSeparate enroll the first patient separately from the rest of |
|
445 |
##' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
446 |
##' in this patient. |
|
447 |
##' @param mcmcOptions object of class \code{\linkS4class{McmcOptions}}, |
|
448 |
##' giving the MCMC options for each evaluation in the trial. By default, |
|
449 |
##' the standard options are used |
|
450 |
##' @param parallel should the simulation runs be parallelized across the |
|
451 |
##' clusters of the computer? (not default) |
|
452 |
##' @param nCores how many cores should be used for parallel computing? |
|
453 |
##' Defaults to the number of cores on the machine, maximum 5. |
|
454 |
##' @param \dots not used |
|
455 |
##' @param derive a named list of functions which derives statistics, based on the |
|
456 |
##' vector of posterior MTD samples. Each list element must therefore accept |
|
457 |
##' one and only one argument, which is a numeric vector, and return a number. |
|
458 |
##' |
|
459 |
##' @return an object of class \code{\linkS4class{DualSimulations}} |
|
460 |
##' |
|
461 |
##' @example examples/design-method-simulate-DualDesign.R |
|
462 |
##' @importFrom mvtnorm rmvnorm |
|
463 |
##' @export |
|
464 |
##' @keywords methods |
|
465 |
setMethod("simulate", |
|
466 |
signature = |
|
467 |
signature(object = "DualDesign"), |
|
468 |
def = |
|
469 |
function(object, nsim = 1L, seed = NULL, |
|
470 |
trueTox, trueBiomarker, args = NULL, |
|
471 |
sigma2W, rho = 0, |
|
472 |
firstSeparate = FALSE, |
|
473 |
mcmcOptions = McmcOptions(), |
|
474 |
parallel = FALSE, |
|
475 |
nCores = |
|
476 |
min(parallel::detectCores(), 5), derive = list(), |
|
477 |
...) { |
|
478 |
## checks and extracts |
|
479 | 1x |
assert_function(trueTox) |
480 | 1x |
assert_function(trueBiomarker) |
481 | 1x |
assert_number(sigma2W, lower = 0) |
482 | 1x |
assert_number(rho, lower = -1, upper = 1) |
483 | 1x |
assert_flag(firstSeparate) |
484 | 1x |
assert_count(nsim, positive = TRUE) |
485 | 1x |
assert_flag(parallel) |
486 | 1x |
assert_count(nCores, positive = TRUE) |
487 | ||
488 | 1x |
args <- as.data.frame(args) |
489 | 1x |
nArgs <- max(nrow(args), 1L) |
490 | ||
491 |
## get names of arguments (excluding the first one which is the dose) |
|
492 | 1x |
trueToxArgnames <- names(formals(trueTox))[-1] |
493 | 1x |
trueBiomarkerArgnames <- names(formals(trueBiomarker))[-1] |
494 | ||
495 |
## this is the covariance matrix we assume: |
|
496 | 1x |
trueCov <- matrix( |
497 | 1x |
c( |
498 | 1x |
sigma2W, sqrt(sigma2W) * rho, |
499 | 1x |
sqrt(sigma2W) * rho, 1 |
500 |
), |
|
501 | 1x |
nrow = 2, byrow = TRUE |
502 |
) |
|
503 | ||
504 |
## seed handling |
|
505 | 1x |
RNGstate <- set_seed(seed) |
506 | ||
507 |
## from this, |
|
508 |
## generate the individual seeds for the simulation runs |
|
509 | 1x |
simSeeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
510 | ||
511 |
## the function to produce the run a single simulation |
|
512 |
## with index "iterSim" |
|
513 | 1x |
runSim <- function(iterSim) { |
514 |
## set the seed for this run |
|
515 | 1x |
set.seed(simSeeds[iterSim]) |
516 | ||
517 |
## what is now the argument for the true functions? |
|
518 |
## (appropriately recycled) |
|
519 | 1x |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
520 | ||
521 |
## so the true tox function is: |
|
522 | 1x |
thisTrueTox <- function(dose) { |
523 | 4x |
do.call( |
524 | 4x |
trueTox, |
525 |
## First argument: the dose |
|
526 | 4x |
c( |
527 | 4x |
dose, |
528 |
## Following arguments: take only those that |
|
529 |
## are required by the tox function |
|
530 | 4x |
as.list(thisArgs)[trueToxArgnames] |
531 |
) |
|
532 |
) |
|
533 |
} |
|
534 | ||
535 |
## and the true biomarker function is: |
|
536 | 1x |
thisTrueBiomarker <- function(dose) { |
537 | 4x |
do.call( |
538 | 4x |
trueBiomarker, |
539 |
## First argument: the dose |
|
540 | 4x |
c( |
541 | 4x |
dose, |
542 |
## Following arguments: take only those that |
|
543 |
## are required by the biomarker function |
|
544 | 4x |
as.list(thisArgs)[trueBiomarkerArgnames] |
545 |
) |
|
546 |
) |
|
547 |
} |
|
548 | ||
549 |
## start the simulated data with the provided one |
|
550 | 1x |
thisData <- object@data |
551 | ||
552 |
## shall we stop the trial? |
|
553 |
## First, we want to continue with the starting dose. |
|
554 |
## This variable is updated after each cohort in the loop. |
|
555 | 1x |
stopit <- FALSE |
556 | ||
557 |
## what is the next dose to be used? |
|
558 |
## initialize with starting dose |
|
559 | 1x |
thisDose <- object@startingDose |
560 | ||
561 | 1x |
if (thisData@placebo) { |
562 |
## what is the probability for tox. at placebo? |
|
563 | ! |
thisProb.PL <- thisTrueTox(object@data@doseGrid[1]) |
564 | ! |
thisMeanZ.PL <- qlogis(thisProb.PL) |
565 | ||
566 |
## what is the biomarker mean at placebo? |
|
567 | ! |
thisMeanBiomarker.PL <- thisTrueBiomarker(object@data@doseGrid[1]) |
568 |
} |
|
569 | ||
570 |
# In case there are placebo, extract true Toxicity and Efficacy for placebo |
|
571 | ||
572 |
## inside this loop we simulate the whole trial, until stopping |
|
573 | 1x |
while (!stopit) { |
574 |
## what is the probability for tox. at this dose? |
|
575 | 4x |
thisProb <- thisTrueTox(thisDose) |
576 |
## and the transformation to the z scale is: |
|
577 | 4x |
thisMeanZ <- qlogis(thisProb) |
578 | ||
579 |
## what is the biomarker mean at this dose? |
|
580 | 4x |
thisMeanBiomarker <- thisTrueBiomarker(thisDose) |
581 | ||
582 |
## what is the cohort size at this dose? |
|
583 | 4x |
thisSize <- size(object@cohort_size, |
584 | 4x |
dose = thisDose, |
585 | 4x |
data = thisData |
586 |
) |
|
587 | ||
588 |
## In case there are placebo |
|
589 |
## what is the cohort size at this dose for Placebo? |
|
590 | 4x |
if (thisData@placebo) { |
591 | ! |
thisSize.PL <- size(object@pl_cohort_size, |
592 | ! |
dose = thisDose, |
593 | ! |
data = thisData |
594 |
) |
|
595 |
} |
|
596 | ||
597 |
## simulate tox and biomarker response: depends on whether we |
|
598 |
## separate the first patient or not. |
|
599 | 4x |
tmp <- |
600 | 4x |
if (firstSeparate && (thisSize > 1L)) { |
601 |
## dose the first patient |
|
602 | ! |
tmpStart <- mvtnorm::rmvnorm( |
603 | ! |
n = 1, |
604 | ! |
mean = |
605 | ! |
c( |
606 | ! |
thisMeanBiomarker, |
607 | ! |
thisMeanZ |
608 |
), |
|
609 | ! |
sigma = trueCov |
610 |
) |
|
611 | ||
612 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
613 | ! |
tmpStart.PL <- mvtnorm::rmvnorm( |
614 | ! |
n = 1, |
615 | ! |
mean = |
616 | ! |
c( |
617 | ! |
thisMeanBiomarker.PL, |
618 | ! |
thisMeanZ.PL |
619 |
), |
|
620 | ! |
sigma = trueCov |
621 |
) |
|
622 |
} |
|
623 | ||
624 | ||
625 |
## if there is no DLT: |
|
626 | ! |
if (tmpStart[, 2] < 0) { |
627 |
## enroll the remaining patients |
|
628 | ! |
tmpStart <- |
629 | ! |
rbind( |
630 | ! |
tmpStart, |
631 | ! |
mvtnorm::rmvnorm( |
632 | ! |
n = thisSize - 1, |
633 | ! |
mean = |
634 | ! |
c( |
635 | ! |
thisMeanBiomarker, |
636 | ! |
thisMeanZ |
637 |
), |
|
638 | ! |
sigma = trueCov |
639 |
) |
|
640 |
) |
|
641 | ||
642 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
643 | ! |
tmpStart.PL <- |
644 | ! |
rbind( |
645 | ! |
tmpStart.PL, |
646 | ! |
mvtnorm::rmvnorm( |
647 | ! |
n = thisSize.PL, |
648 | ! |
mean = |
649 | ! |
c( |
650 | ! |
thisMeanBiomarker.PL, |
651 | ! |
thisMeanZ.PL |
652 |
), |
|
653 | ! |
sigma = trueCov |
654 |
) |
|
655 |
) |
|
656 |
} |
|
657 |
} |
|
658 | ||
659 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
660 | ! |
list(tmpStart = tmpStart, tmpStart.PL = tmpStart.PL) |
661 |
} else { |
|
662 | ! |
list(tmpStart = tmpStart) |
663 |
} |
|
664 |
} else { |
|
665 |
## we can directly dose all patients |
|
666 | 4x |
tmpStart <- mvtnorm::rmvnorm( |
667 | 4x |
n = thisSize, |
668 | 4x |
mean = |
669 | 4x |
c( |
670 | 4x |
thisMeanBiomarker, |
671 | 4x |
thisMeanZ |
672 |
), |
|
673 | 4x |
sigma = trueCov |
674 |
) |
|
675 | ||
676 | 4x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
677 | ! |
tmpStart.PL <- mvtnorm::rmvnorm( |
678 | ! |
n = thisSize.PL, |
679 | ! |
mean = |
680 | ! |
c( |
681 | ! |
thisMeanBiomarker.PL, |
682 | ! |
thisMeanZ.PL |
683 |
), |
|
684 | ! |
sigma = trueCov |
685 |
) |
|
686 |
} |
|
687 | ||
688 | 4x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
689 | ! |
list(tmpStart = tmpStart, tmpStart.PL = tmpStart.PL) |
690 |
} else { |
|
691 | 4x |
list(tmpStart = tmpStart) |
692 |
} |
|
693 |
} |
|
694 | ||
695 |
## extract biomarker and DLT samples |
|
696 | 4x |
thisBiomarkers <- tmp$tmpStart[, 1] |
697 | 4x |
thisDLTs <- as.integer(tmp$tmpStart[, 2] > 0) |
698 | ||
699 |
# in case there are placebo |
|
700 | 4x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
701 | ! |
thisBiomarkers.PL <- tmp$tmpStart.PL[, 1] |
702 | ! |
thisDLTs.PL <- as.integer(tmp$tmpStart.PL[, 2] > 0) |
703 | ||
704 |
## update the data first with placebo... |
|
705 | ! |
thisData <- update( |
706 | ! |
object = thisData, |
707 | ! |
x = object@data@doseGrid[1], |
708 | ! |
y = thisDLTs.PL, |
709 | ! |
w = thisBiomarkers.PL, |
710 | ! |
check = FALSE |
711 |
) |
|
712 | ||
713 |
### ... and then with active dose |
|
714 | ! |
thisData <- update( |
715 | ! |
object = thisData, |
716 | ! |
x = thisDose, |
717 | ! |
y = thisDLTs, |
718 | ! |
w = thisBiomarkers, |
719 | ! |
new_cohort = FALSE |
720 |
) |
|
721 |
} else { |
|
722 | 4x |
thisData <- update( |
723 | 4x |
object = thisData, |
724 | 4x |
x = thisDose, |
725 | 4x |
y = thisDLTs, |
726 | 4x |
w = thisBiomarkers |
727 |
) |
|
728 |
} |
|
729 | ||
730 | ||
731 |
## what is the dose limit? |
|
732 | 4x |
doselimit <- maxDose(object@increments, |
733 | 4x |
data = thisData |
734 |
) |
|
735 | ||
736 |
## generate samples from the model |
|
737 | 4x |
thisSamples <- mcmc( |
738 | 4x |
data = thisData, |
739 | 4x |
model = object@model, |
740 | 4x |
options = mcmcOptions |
741 |
) |
|
742 | ||
743 |
## => what is the next best dose? |
|
744 | 4x |
thisDose <- nextBest(object@nextBest, |
745 | 4x |
doselimit = doselimit, |
746 | 4x |
samples = thisSamples, |
747 | 4x |
model = object@model, |
748 | 4x |
data = thisData |
749 | 4x |
)$value |
750 | ||
751 |
## evaluate stopping rules |
|
752 | 4x |
stopit <- stopTrial(object@stopping, |
753 | 4x |
dose = thisDose, |
754 | 4x |
samples = thisSamples, |
755 | 4x |
model = object@model, |
756 | 4x |
data = thisData |
757 |
) |
|
758 | 4x |
stopit_results <- h_unpack_stopit(stopit) |
759 |
} |
|
760 | ||
761 |
## get the fit |
|
762 | 1x |
thisFit <- fit( |
763 | 1x |
object = thisSamples, |
764 | 1x |
model = object@model, |
765 | 1x |
data = thisData |
766 |
) |
|
767 | ||
768 |
# Get the MTD estimate from the samples. |
|
769 | ||
770 | 1x |
target_dose_samples <- dose( |
771 | 1x |
mean(object@nextBest@target), |
772 | 1x |
model = object@model, |
773 | 1x |
samples = thisSamples |
774 |
) |
|
775 | ||
776 |
# Create a function for additional statistical summary. |
|
777 | ||
778 | 1x |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
779 | ||
780 | ||
781 |
## return the results |
|
782 | 1x |
thisResult <- |
783 | 1x |
list( |
784 | 1x |
data = thisData, |
785 | 1x |
dose = thisDose, |
786 | 1x |
fitTox = |
787 | 1x |
subset(thisFit, |
788 | 1x |
select = |
789 | 1x |
c(middle, lower, upper) |
790 |
), |
|
791 | 1x |
fit_biomarker = |
792 | 1x |
subset(thisFit, |
793 | 1x |
select = |
794 | 1x |
c( |
795 | 1x |
middleBiomarker, lowerBiomarker, |
796 | 1x |
upperBiomarker |
797 |
) |
|
798 |
), |
|
799 | 1x |
rho_est = median(thisSamples@data$rho), |
800 | 1x |
sigma2w_est = median(1 / thisSamples@data$precW), |
801 | 1x |
stop = |
802 | 1x |
attr( |
803 | 1x |
stopit, |
804 | 1x |
"message" |
805 |
), |
|
806 | 1x |
additional_stats = additional_stats, |
807 | 1x |
report_results = stopit_results |
808 |
) |
|
809 | ||
810 | 1x |
return(thisResult) |
811 |
} |
|
812 | ||
813 | 1x |
resultList <- get_result_list( |
814 | 1x |
fun = runSim, |
815 | 1x |
nsim = nsim, |
816 | 1x |
vars = |
817 | 1x |
c( |
818 | 1x |
"simSeeds", |
819 | 1x |
"args", |
820 | 1x |
"nArgs", |
821 | 1x |
"firstSeparate", |
822 | 1x |
"trueTox", |
823 | 1x |
"trueBiomarker", |
824 | 1x |
"trueCov", |
825 | 1x |
"object", |
826 | 1x |
"mcmcOptions" |
827 |
), |
|
828 | 1x |
parallel = parallel, |
829 | 1x |
n_cores = nCores |
830 |
) |
|
831 | ||
832 |
## put everything in the Simulations format: |
|
833 | ||
834 |
## setup the list for the simulated data objects |
|
835 | 1x |
dataList <- lapply(resultList, "[[", "data") |
836 | ||
837 |
## the vector of the final dose recommendations |
|
838 | 1x |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "dose")) |
839 | ||
840 |
## vector of rho estimates |
|
841 | 1x |
rhoEstimates <- as.numeric(sapply(resultList, "[[", "rho_est")) |
842 | ||
843 |
## vector of sigma2W estimates |
|
844 | 1x |
sigma2Westimates <- as.numeric(sapply(resultList, "[[", "sigma2w_est")) |
845 | ||
846 |
## setup the list for the final tox fits |
|
847 | 1x |
fitToxList <- lapply(resultList, "[[", "fitTox") |
848 | ||
849 |
## setup the list for the final biomarker fits |
|
850 | 1x |
fitBiomarkerList <- lapply(resultList, "[[", "fit_biomarker") |
851 | ||
852 |
## the reasons for stopping |
|
853 | 1x |
stopReasons <- lapply(resultList, "[[", "stop") |
854 | ||
855 |
# individual stopping rule results as matrix, labels as column names |
|
856 | 1x |
stop_results <- lapply(resultList, "[[", "report_results") |
857 | 1x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
858 | ||
859 |
## For dual simulations summary of additional statistics. |
|
860 | 1x |
additional_stats <- lapply(resultList, "[[", "additional_stats") |
861 | ||
862 |
## return the results in the DualSimulations class object |
|
863 | 1x |
ret <- DualSimulations( |
864 | 1x |
data = dataList, |
865 | 1x |
doses = recommendedDoses, |
866 | 1x |
rho_est = rhoEstimates, |
867 | 1x |
sigma2w_est = sigma2Westimates, |
868 | 1x |
fit = fitToxList, |
869 | 1x |
fit_biomarker = fitBiomarkerList, |
870 | 1x |
stop_report = stop_report, |
871 | 1x |
stop_reasons = stopReasons, |
872 | 1x |
additional_stats = additional_stats, |
873 | 1x |
seed = RNGstate |
874 |
) |
|
875 | ||
876 | 1x |
return(ret) |
877 |
} |
|
878 |
) |
|
879 | ||
880 | ||
881 |
## ============================================================ |
|
882 | ||
883 |
##' Obtain hypothetical trial course table for a design |
|
884 |
##' |
|
885 |
##' This generic function takes a design and generates a dataframe |
|
886 |
##' showing the beginning of several hypothetical trial courses under |
|
887 |
##' the design. This means, from the generated dataframe one can read off: |
|
888 |
##' - how many cohorts are required in the optimal case (no DLTs observed) in |
|
889 |
##' order to reach the highest dose of the specified dose grid (or until |
|
890 |
##' the stopping rule is fulfilled) |
|
891 |
##' - assuming no DLTs are observed until a certain dose level, what the next |
|
892 |
##' recommended dose is for all possible number of DLTs observed |
|
893 |
##' - the actual relative increments that will be used in these cases |
|
894 |
##' - whether the trial would stop at a certain cohort |
|
895 |
##' Examining the "single trial" behavior of a dose escalation design is |
|
896 |
##' the first important step in evaluating a design, and cannot be replaced by |
|
897 |
##' studying solely the operating characteristics in "many trials". The cohort |
|
898 |
##' sizes are also taken from the design, assuming no DLTs occur until the dose |
|
899 |
##' listed. |
|
900 |
##' |
|
901 |
##' @param object the design (\code{\linkS4class{Design}} or |
|
902 |
##' \code{\linkS4class{RuleDesign}} object) we want to examine |
|
903 |
##' @param \dots additional arguments (see methods) |
|
904 |
##' @param maxNoIncrement maximum number of contiguous next doses at 0 |
|
905 |
##' DLTs that are the same as before, i.e. no increment (default to 100) |
|
906 |
##' |
|
907 |
##' @return The data frame |
|
908 |
##' |
|
909 |
##' @export |
|
910 |
##' @keywords methods regression |
|
911 |
setGeneric("examine", |
|
912 |
def = |
|
913 |
function(object, ..., maxNoIncrement = 100L) { |
|
914 |
## check maxNoIncrement argument |
|
915 | 4x |
assert_count(maxNoIncrement, positive = TRUE) |
916 | ||
917 |
## there should be no default method, |
|
918 |
## therefore just forward to next method! |
|
919 | 4x |
standardGeneric("examine") |
920 |
}, |
|
921 |
valueClass = "data.frame" |
|
922 |
) |
|
923 | ||
924 | ||
925 |
##' @describeIn examine Examine a model-based CRM |
|
926 |
##' |
|
927 |
##' @param mcmcOptions object of class \code{\linkS4class{McmcOptions}}, |
|
928 |
##' giving the MCMC options for each evaluation in the trial. By default, |
|
929 |
##' the standard options are used |
|
930 |
##' |
|
931 |
##' @example examples/design-method-examine-Design.R |
|
932 |
setMethod("examine", |
|
933 |
signature = |
|
934 |
signature(object = "Design"), |
|
935 |
def = |
|
936 |
function(object, |
|
937 |
mcmcOptions = McmcOptions(), |
|
938 |
..., |
|
939 |
maxNoIncrement) { |
|
940 |
## start with the empty table |
|
941 | 2x |
ret <- data.frame( |
942 | 2x |
dose = numeric(), |
943 | 2x |
DLTs = integer(), |
944 | 2x |
nextDose = numeric(), |
945 | 2x |
stop = logical(), |
946 | 2x |
increment = integer() |
947 |
) |
|
948 | ||
949 |
## start the base data with the provided one |
|
950 | 2x |
baseData <- object@data |
951 | ||
952 |
## are we finished and can stop? |
|
953 | 2x |
stopit <- FALSE |
954 | ||
955 |
## counter how many contiguous doses at 0 DLTs with |
|
956 |
## no increment |
|
957 | 2x |
noIncrementCounter <- 0L |
958 | ||
959 |
## what is the next dose to be used? |
|
960 |
## initialize with starting dose |
|
961 | 2x |
thisDose <- object@startingDose |
962 | ||
963 |
## inside this loop we continue filling up the table, until |
|
964 |
## stopping |
|
965 | 2x |
while (!stopit) { |
966 |
## what is the cohort size at this dose? |
|
967 | 12x |
thisSize <- size(object@cohort_size, |
968 | 12x |
dose = thisDose, |
969 | 12x |
data = baseData |
970 |
) |
|
971 | ||
972 | 12x |
if (baseData@placebo) { |
973 | 5x |
thisSize.PL <- size(object@pl_cohort_size, |
974 | 5x |
dose = thisDose, |
975 | 5x |
data = baseData |
976 |
) |
|
977 |
} |
|
978 | ||
979 |
## for all possible number of DLTs: |
|
980 | 12x |
for (numDLTs in 0:thisSize) |
981 |
{ |
|
982 |
## update data with corresponding DLT vector |
|
983 | 48x |
if (baseData@placebo && (thisSize.PL > 0L)) { |
984 | 20x |
thisData <- update( |
985 | 20x |
object = baseData, |
986 | 20x |
x = baseData@doseGrid[1], |
987 | 20x |
y = rep(0, thisSize.PL), |
988 | 20x |
check = FALSE |
989 |
) |
|
990 | ||
991 | 20x |
thisData <- |
992 | 20x |
update( |
993 | 20x |
object = thisData, |
994 | 20x |
x = thisDose, |
995 | 20x |
y = |
996 | 20x |
rep( |
997 | 20x |
x = c(0, 1), |
998 | 20x |
times = |
999 | 20x |
c( |
1000 | 20x |
thisSize - numDLTs, |
1001 | 20x |
numDLTs |
1002 |
) |
|
1003 |
), |
|
1004 | 20x |
new_cohort = FALSE |
1005 |
) |
|
1006 |
} else { |
|
1007 | 28x |
thisData <- |
1008 | 28x |
update( |
1009 | 28x |
object = baseData, |
1010 | 28x |
x = thisDose, |
1011 | 28x |
y = |
1012 | 28x |
rep( |
1013 | 28x |
x = c(0, 1), |
1014 | 28x |
times = |
1015 | 28x |
c( |
1016 | 28x |
thisSize - numDLTs, |
1017 | 28x |
numDLTs |
1018 |
) |
|
1019 |
) |
|
1020 |
) |
|
1021 |
} |
|
1022 | ||
1023 |
## what is the dose limit? |
|
1024 | 48x |
doselimit <- maxDose(object@increments, |
1025 | 48x |
data = thisData |
1026 |
) |
|
1027 | ||
1028 |
## generate samples from the model |
|
1029 | 48x |
thisSamples <- mcmc( |
1030 | 48x |
data = thisData, |
1031 | 48x |
model = object@model, |
1032 | 48x |
options = mcmcOptions |
1033 |
) |
|
1034 | ||
1035 |
## => what is the next best dose? |
|
1036 | 48x |
nextDose <- nextBest(object@nextBest, |
1037 | 48x |
doselimit = doselimit, |
1038 | 48x |
samples = thisSamples, |
1039 | 48x |
model = object@model, |
1040 | 48x |
data = thisData |
1041 | 48x |
)$value |
1042 | ||
1043 |
## compute relative increment in percent |
|
1044 | 48x |
thisIncrement <- |
1045 | 48x |
round((nextDose - thisDose) / thisDose * 100) |
1046 | ||
1047 |
## evaluate stopping rules |
|
1048 | 48x |
stopThisTrial <- stopTrial(object@stopping, |
1049 | 48x |
dose = nextDose, |
1050 | 48x |
samples = thisSamples, |
1051 | 48x |
model = object@model, |
1052 | 48x |
data = thisData |
1053 |
) |
|
1054 | ||
1055 |
## append information to the data frame |
|
1056 | 48x |
ret <- rbind( |
1057 | 48x |
ret, |
1058 | 48x |
list( |
1059 | 48x |
dose = thisDose, |
1060 | 48x |
DLTs = numDLTs, |
1061 | 48x |
nextDose = nextDose, |
1062 | 48x |
stop = stopThisTrial, |
1063 | 48x |
increment = as.integer(thisIncrement) |
1064 |
) |
|
1065 |
) |
|
1066 |
} |
|
1067 | ||
1068 |
## change base data |
|
1069 | 12x |
if (baseData@placebo && (thisSize.PL > 0L)) { |
1070 | 5x |
baseData <- |
1071 | 5x |
update( |
1072 | 5x |
object = baseData, |
1073 | 5x |
x = baseData@doseGrid[1], |
1074 | 5x |
y = rep(0, thisSize.PL), |
1075 | 5x |
check = FALSE |
1076 |
) |
|
1077 | ||
1078 | 5x |
baseData <- |
1079 | 5x |
update( |
1080 | 5x |
object = baseData, |
1081 | 5x |
x = thisDose, |
1082 | 5x |
y = rep(0, thisSize), |
1083 | 5x |
new_cohort = FALSE |
1084 |
) |
|
1085 |
} else { |
|
1086 | 7x |
baseData <- |
1087 | 7x |
update( |
1088 | 7x |
object = baseData, |
1089 | 7x |
x = thisDose, |
1090 | 7x |
y = rep(0, thisSize) |
1091 |
) |
|
1092 |
} |
|
1093 | ||
1094 |
## what are the results if 0 DLTs? |
|
1095 | 12x |
resultsNoDLTs <- subset( |
1096 | 12x |
tail(ret, thisSize + 1), |
1097 | 12x |
dose == thisDose & DLTs == 0 |
1098 |
) |
|
1099 | ||
1100 |
## what is the new dose then accordingly? |
|
1101 | 12x |
newDose <- as.numeric(resultsNoDLTs$nextDose) |
1102 | ||
1103 |
## what is the difference to the previous dose? |
|
1104 | 12x |
doseDiff <- newDose - thisDose |
1105 | ||
1106 |
## update the counter for no increments of the dose |
|
1107 | 12x |
if (doseDiff == 0) { |
1108 | 10x |
noIncrementCounter <- noIncrementCounter + 1L |
1109 |
} else { |
|
1110 | 2x |
noIncrementCounter <- 0L |
1111 |
} |
|
1112 | ||
1113 |
## would stopping rule be fulfilled already? |
|
1114 | 12x |
stopAlready <- resultsNoDLTs$stop |
1115 | ||
1116 |
## update dose |
|
1117 | 12x |
thisDose <- newDose |
1118 | ||
1119 |
## too many times no increment? |
|
1120 | 12x |
stopNoIncrement <- (noIncrementCounter >= maxNoIncrement) |
1121 | 12x |
if (stopNoIncrement) { |
1122 | ! |
warning(paste( |
1123 | ! |
"Stopping because", |
1124 | ! |
noIncrementCounter, |
1125 | ! |
"times no increment vs. previous dose" |
1126 |
)) |
|
1127 |
} |
|
1128 | ||
1129 |
## check if we can stop: |
|
1130 |
## either when we have reached the highest dose in the |
|
1131 |
## next cohort, or when the stopping rule is already |
|
1132 |
## fulfilled, or when too many times no increment |
|
1133 | 12x |
stopit <- (thisDose >= max(object@data@doseGrid)) || |
1134 | 12x |
stopAlready || stopNoIncrement |
1135 |
} |
|
1136 | ||
1137 | 2x |
return(ret) |
1138 |
} |
|
1139 |
) |
|
1140 | ||
1141 | ||
1142 | ||
1143 |
##' @describeIn examine Examine a rule-based design |
|
1144 |
##' @example examples/design-method-examine-RuleDesign.R |
|
1145 |
setMethod("examine", |
|
1146 |
signature = |
|
1147 |
signature(object = "RuleDesign"), |
|
1148 |
def = |
|
1149 |
function(object, |
|
1150 |
..., |
|
1151 |
maxNoIncrement) { |
|
1152 |
## start with the empty table |
|
1153 | 1x |
ret <- data.frame( |
1154 | 1x |
dose = numeric(), |
1155 | 1x |
DLTs = integer(), |
1156 | 1x |
nextDose = numeric(), |
1157 | 1x |
stop = logical(), |
1158 | 1x |
increment = integer() |
1159 |
) |
|
1160 | ||
1161 |
## start the base data with the provided one |
|
1162 | 1x |
baseData <- object@data |
1163 | ||
1164 |
## are we finished and can stop? |
|
1165 | 1x |
stopit <- FALSE |
1166 | ||
1167 |
## counter how many contiguous doses at 0 DLTs with |
|
1168 |
## no increment |
|
1169 | 1x |
noIncrementCounter <- 0L |
1170 | ||
1171 |
## what is the next dose to be used? |
|
1172 |
## initialize with starting dose |
|
1173 | 1x |
thisDose <- object@startingDose |
1174 | ||
1175 |
## inside this loop we continue filling up the table, until |
|
1176 |
## stopping |
|
1177 | 1x |
while (!stopit) { |
1178 |
## what is the cohort size at this dose? |
|
1179 | 10x |
thisSize <- size(object@cohort_size, |
1180 | 10x |
dose = thisDose, |
1181 | 10x |
data = baseData |
1182 |
) |
|
1183 | ||
1184 |
## for all possible number of DLTs: |
|
1185 | 10x |
for (numDLTs in 0:thisSize) |
1186 |
{ |
|
1187 |
## update data with corresponding DLT vector |
|
1188 | 40x |
thisData <- |
1189 | 40x |
update( |
1190 | 40x |
object = baseData, |
1191 | 40x |
x = thisDose, |
1192 | 40x |
y = |
1193 | 40x |
rep( |
1194 | 40x |
x = c(0, 1), |
1195 | 40x |
times = |
1196 | 40x |
c( |
1197 | 40x |
thisSize - numDLTs, |
1198 | 40x |
numDLTs |
1199 |
) |
|
1200 |
) |
|
1201 |
) |
|
1202 | ||
1203 |
## evaluate the rule |
|
1204 | 40x |
thisOutcome <- nextBest(object@nextBest, |
1205 | 40x |
data = thisData |
1206 |
) |
|
1207 | ||
1208 |
## next dose |
|
1209 | 40x |
nextDose <- thisOutcome$value |
1210 | ||
1211 |
## do we stop here? |
|
1212 | 40x |
stopThisTrial <- thisOutcome$stopHere |
1213 | ||
1214 |
## compute relative increment in percent |
|
1215 | 40x |
thisIncrement <- |
1216 | 40x |
round((nextDose - thisDose) / thisDose * 100) |
1217 | ||
1218 |
## append information to the data frame |
|
1219 | 40x |
ret <- rbind( |
1220 | 40x |
ret, |
1221 | 40x |
list( |
1222 | 40x |
dose = thisDose, |
1223 | 40x |
DLTs = numDLTs, |
1224 | 40x |
nextDose = nextDose, |
1225 | 40x |
stop = stopThisTrial, |
1226 | 40x |
increment = as.integer(thisIncrement) |
1227 |
) |
|
1228 |
) |
|
1229 |
} |
|
1230 | ||
1231 |
## change base data |
|
1232 | 10x |
baseData <- |
1233 | 10x |
update( |
1234 | 10x |
object = baseData, |
1235 | 10x |
x = thisDose, |
1236 | 10x |
y = rep(0, thisSize) |
1237 |
) |
|
1238 | ||
1239 |
## what are the results if 0 DLTs? |
|
1240 | 10x |
resultsNoDLTs <- subset( |
1241 | 10x |
tail(ret, thisSize + 1), |
1242 | 10x |
dose == thisDose & DLTs == 0 |
1243 |
) |
|
1244 | ||
1245 |
## what is the new dose then accordingly? |
|
1246 | 10x |
newDose <- as.numeric(resultsNoDLTs$nextDose) |
1247 | ||
1248 |
## what is the difference to the previous dose? |
|
1249 | 10x |
doseDiff <- newDose - thisDose |
1250 | ||
1251 |
## update the counter for no increments of the dose |
|
1252 | 10x |
if (doseDiff == 0) { |
1253 | ! |
noIncrementCounter <- noIncrementCounter + 1L |
1254 |
} else { |
|
1255 | 10x |
noIncrementCounter <- 0L |
1256 |
} |
|
1257 | ||
1258 |
## would stopping rule be fulfilled already? |
|
1259 | 10x |
stopAlready <- resultsNoDLTs$stop |
1260 | ||
1261 |
## update dose |
|
1262 | 10x |
thisDose <- newDose |
1263 | ||
1264 |
## too many times no increment? |
|
1265 | 10x |
stopNoIncrement <- (noIncrementCounter >= maxNoIncrement) |
1266 | 10x |
if (stopNoIncrement) { |
1267 | ! |
warning(paste( |
1268 | ! |
"Stopping because", |
1269 | ! |
noIncrementCounter, |
1270 | ! |
"times no increment vs. previous dose" |
1271 |
)) |
|
1272 |
} |
|
1273 | ||
1274 |
## check if we can stop: |
|
1275 |
## either when we have reached the highest dose in the |
|
1276 |
## next cohort, or when the stopping rule is already |
|
1277 |
## fulfilled, or when too many times no increment |
|
1278 | 10x |
stopit <- (thisDose >= max(object@data@doseGrid)) || |
1279 | 10x |
stopAlready || stopNoIncrement |
1280 |
} |
|
1281 | ||
1282 | 1x |
return(ret) |
1283 |
} |
|
1284 |
) |
|
1285 | ||
1286 |
##' @describeIn examine Examine a model-based CRM |
|
1287 |
##' |
|
1288 |
##' @param mcmcOptions object of class \code{\linkS4class{McmcOptions}}, |
|
1289 |
##' giving the MCMC options for each evaluation in the trial. By default, |
|
1290 |
##' the standard options are used |
|
1291 |
##' |
|
1292 |
##' @example examples/design-method-examine-DADesign.R |
|
1293 |
setMethod("examine", |
|
1294 |
signature = |
|
1295 |
signature(object = "DADesign"), |
|
1296 |
def = |
|
1297 |
function(object, mcmcOptions = McmcOptions(), ..., |
|
1298 |
maxNoIncrement) { |
|
1299 |
# A function to return follow up fulfull yes (TRUE) vs no (FALSE); |
|
1300 | 1x |
ready_to_open <- function(day, window, thisSurv) { |
1301 | 180x |
size <- length(thisSurv) |
1302 |
# the date the patient starts; |
|
1303 | 180x |
start_time <- apply(rbind(thisSurv[-size], window$patientGap[-1]), 2, min) |
1304 |
# the relative time for each patient on the specified "date"; |
|
1305 | 180x |
individule_check <- day - cumsum(c(0, start_time)) |
1306 |
# the minial number should be 0; |
|
1307 | 180x |
individule_check[individule_check < 0] <- 0 |
1308 | 180x |
follow_up <- apply(rbind(thisSurv, individule_check), 2, min) |
1309 | 180x |
return(all((follow_up - apply(rbind(window$patientFollow, thisSurv), 2, min)) >= 0) & (max(follow_up) >= min(window$patientFollowMin, max(thisSurv)))) |
1310 |
} |
|
1311 | ||
1312 |
## assume we have surfficient patients, i.e. patient can be immediately enrolled |
|
1313 |
## once the trial accumulation is open. This function will tell you when to open |
|
1314 |
## the next cohort; |
|
1315 |
# this function applys to all trials; |
|
1316 | 1x |
nextOpen <- function(window, thisSurv) { |
1317 | 3x |
size <- length(thisSurv) |
1318 | ||
1319 | 3x |
window$patientGap <- window$patientGap[1:size] ## if length(window$pt)>length(thisSurv), assume the first length(thisSurv) patients were enrolled; |
1320 |
## if the DLT happens before the end of DLT window, then the next |
|
1321 |
## cohort/enrollment of the next patient would happened earlier; |
|
1322 | 3x |
start_time <- apply(rbind(thisSurv[-size], window$patientGap[-1]), 2, min) |
1323 |
# duration of the cohort (all DLT windows finished); |
|
1324 | 3x |
maxT <- max(thisSurv + cumsum(c(0, start_time))) |
1325 | ||
1326 | 3x |
meetrequire <- sapply(1:maxT, function(i) { |
1327 | 180x |
ready_to_open(i, window, thisSurv) |
1328 |
}) |
|
1329 | 3x |
if (sum(meetrequire) > 0) { |
1330 |
# the earliest time that the require is met; |
|
1331 | 3x |
time <- min(c(1:maxT)[meetrequire]) |
1332 |
} else { |
|
1333 | ! |
time <- maxT |
1334 |
} |
|
1335 | ||
1336 | 3x |
return(time) |
1337 |
} |
|
1338 | ||
1339 |
## start with the empty table |
|
1340 | 1x |
ret <- data.frame( |
1341 | 1x |
DLTsearly_1 = integer(), ## JZ: add a cohort index; |
1342 | 1x |
dose = numeric(), |
1343 | 1x |
DLTs = integer(), |
1344 | 1x |
nextDose = numeric(), |
1345 | 1x |
stop = logical(), |
1346 | 1x |
increment = integer() |
1347 |
) |
|
1348 | ||
1349 |
## start the base data with the provided one |
|
1350 | 1x |
baseData <- object@data |
1351 | ||
1352 |
## are we finished and can stop? |
|
1353 | 1x |
stopit <- FALSE |
1354 | ||
1355 |
## what is the next dose to be used? |
|
1356 |
## initialize with starting dose |
|
1357 | 1x |
thisDose <- object@startingDose |
1358 | ||
1359 |
## initial {fact} variables; |
|
1360 | 1x |
factDLTs <- baseData@y |
1361 | 1x |
factSurv <- baseData@u |
1362 | 1x |
factT0 <- baseData@t0 |
1363 | ||
1364 |
## Initiate "trialtime" which is zero. This is the global time for studies; |
|
1365 | 1x |
trialtime <- 0 |
1366 | ||
1367 |
## when the current cohort open? |
|
1368 | 1x |
pretime <- 0 |
1369 | ||
1370 |
## the duration of DLT window |
|
1371 | 1x |
Tmax <- baseData@Tmax |
1372 | ||
1373 |
## number of patients with un-completed DLT window; |
|
1374 |
## assume no patient is under DLT observation period at the beginning; |
|
1375 | 1x |
preSize <- 0 |
1376 | ||
1377 |
## inside this loop we continue filling up the table, until |
|
1378 |
## stopping |
|
1379 | 1x |
while (!stopit) { |
1380 |
## what is the cohort size at this dose? |
|
1381 | 3x |
thisSize <- size(object@cohort_size, |
1382 | 3x |
dose = thisDose, |
1383 | 3x |
data = baseData |
1384 |
) |
|
1385 | ||
1386 |
## what's the safetywindow |
|
1387 | 3x |
thisSafetywindow <- windowLength(object@safetyWindow, thisSize) |
1388 | ||
1389 | ||
1390 |
# initial parameters |
|
1391 | 3x |
thisT0 <- trialtime + cumsum(thisSafetywindow$patientGap) |
1392 | ||
1393 | 3x |
factDLTs <- c(factDLTs, rep(0, thisSize)) |
1394 | ||
1395 | 3x |
factSurv <- c(factSurv, rep(Tmax, thisSize)) |
1396 | ||
1397 | 3x |
factT0 <- c(factT0, thisT0) |
1398 | ||
1399 |
## The time that the next cohort open |
|
1400 | 3x |
trialtime <- trialtime + nextOpen( |
1401 | 3x |
window = thisSafetywindow, |
1402 | 3x |
thisSurv = rep(Tmax, thisSize) |
1403 |
) |
|
1404 | ||
1405 |
## In the DA-CRM, we should count the number of patients who is still within the DLT window; |
|
1406 |
## Thus the loop for numDLTs should be 0:nFollow; |
|
1407 | 3x |
nFollow <- thisSize + preSize |
1408 | ||
1409 |
## Identify the censored patients; |
|
1410 |
## "thiscensored" will be used in the cases that numDLTs>0; |
|
1411 | 3x |
npt <- length(baseData@x) # total number of patients |
1412 | ||
1413 | 3x |
thiscensored <- c(c(1:npt)[(trialtime - baseData@t0) < baseData@Tmax & baseData@y == 0], (npt + 1):(npt + thisSize)) |
1414 | ||
1415 | ||
1416 | ||
1417 |
## for all possible number of DLTs: |
|
1418 | 3x |
for (numDLTs in 0:nFollow) |
1419 |
{ |
|
1420 |
## If numDLTs>0, two extreme cases will be examinated; |
|
1421 |
## (1) DLTs occur on patients with the longer follow ups; |
|
1422 |
## (2) DLTs occur on patients with the shorter follow ups; |
|
1423 | ||
1424 | ||
1425 | ||
1426 | ||
1427 | ||
1428 | ||
1429 | 9x |
if (numDLTs == 0) { |
1430 | 3x |
baseData <- update( |
1431 | 3x |
object = baseData, |
1432 | 3x |
y = factDLTs, #### the x will be constantly updated according to u |
1433 | 3x |
u = factSurv, |
1434 | 3x |
t0 = factT0, |
1435 | 3x |
x = thisDose, |
1436 | 3x |
trialtime = trialtime |
1437 | 3x |
) #### the u will be constantly updated |
1438 | ||
1439 | ||
1440 | ||
1441 |
## what is the dose limit? |
|
1442 | 3x |
doselimit <- maxDose(object@increments, |
1443 | 3x |
data = baseData |
1444 |
) |
|
1445 | ||
1446 |
## generate samples from the model |
|
1447 | 3x |
thisSamples <- mcmc( |
1448 | 3x |
data = baseData, |
1449 | 3x |
model = object@model, |
1450 | 3x |
options = mcmcOptions |
1451 |
) |
|
1452 | ||
1453 |
## => what is the next best dose? |
|
1454 | 3x |
nextDose <- nextBest(object@nextBest, |
1455 | 3x |
doselimit = doselimit, |
1456 | 3x |
samples = thisSamples, |
1457 | 3x |
model = object@model, |
1458 | 3x |
data = baseData |
1459 | 3x |
)$value |
1460 | ||
1461 |
# ##remove savePlot |
|
1462 |
# |
|
1463 |
# savePlot(plot(baseData),name=paste("Dose",thisDose,0,"DLT",nextDose,sep="_")) |
|
1464 |
# |
|
1465 |
## compute relative increment in percent |
|
1466 | 3x |
thisIncrement <- |
1467 | 3x |
round((nextDose - thisDose) / thisDose * 100) |
1468 | ||
1469 |
## evaluate stopping rules |
|
1470 | 3x |
stopThisTrial <- stopTrial(object@stopping, |
1471 | 3x |
dose = nextDose, |
1472 | 3x |
samples = thisSamples, |
1473 | 3x |
model = object@model, |
1474 | 3x |
data = baseData |
1475 |
) |
|
1476 | ||
1477 |
## append information to the data frame |
|
1478 | 3x |
ret <- rbind( |
1479 | 3x |
ret, |
1480 | 3x |
list( |
1481 | 3x |
DLTsearly_1 = 0, |
1482 | 3x |
dose = thisDose, |
1483 | 3x |
DLTs = numDLTs, |
1484 | 3x |
nextDose = nextDose, |
1485 | 3x |
stop = stopThisTrial, |
1486 | 3x |
increment = as.integer(thisIncrement) |
1487 |
) |
|
1488 |
) |
|
1489 |
### comment here to show only no DLTs; |
|
1490 |
# } |
|
1491 |
} else { |
|
1492 | 6x |
for (DLTsearly in 1:numDLTs) { |
1493 |
# Update current {fact} variables |
|
1494 | 10x |
thisDLTs <- factDLTs |
1495 | 10x |
thisSurv <- factSurv |
1496 | ||
1497 | 10x |
if (DLTsearly == 1) { |
1498 |
# scenario 1: The patients with longest follow up have DLTs |
|
1499 | ||
1500 | 6x |
thisDLTs[thiscensored][1:numDLTs] <- rep(1, rep(numDLTs)) |
1501 | ||
1502 | 6x |
thisSurv[thiscensored][1:numDLTs] <- apply(rbind(rep(Tmax, numDLTs), c(trialtime - factT0[thiscensored][1:numDLTs])), 2, min) |
1503 | ||
1504 | ||
1505 | 6x |
thisData <- update( |
1506 | 6x |
object = baseData, |
1507 | 6x |
y = thisDLTs, #### the y will be updated according to u |
1508 | 6x |
u = thisSurv, |
1509 | 6x |
t0 = factT0, |
1510 | 6x |
x = thisDose, |
1511 | 6x |
trialtime = trialtime |
1512 | 6x |
) #### the u will be updated |
1513 |
} else { |
|
1514 |
# scenario 2: The patients with shortest follow up have DLTs |
|
1515 | ||
1516 | 4x |
thisDLTs[rev(thiscensored)][1:numDLTs] <- rep(1, rep(numDLTs)) |
1517 | ||
1518 | 4x |
thisSurv[rev(thiscensored)][1:numDLTs] <- c(apply(rbind(rep(1, numDLTs), pretime + 1 - factT0[rev(thiscensored)][1:numDLTs]), 2, max)) |
1519 | ||
1520 | 4x |
if (numDLTs >= thisSize) { |
1521 | 4x |
thistime <- 1 + max(thisT0) |
1522 |
} else { |
|
1523 | ! |
thistime <- trialtime |
1524 |
} |
|
1525 | ||
1526 | 4x |
thisData <- update( |
1527 | 4x |
object = baseData, |
1528 | 4x |
y = thisDLTs, #### the y will be updated according to u |
1529 | 4x |
u = thisSurv, |
1530 | 4x |
t0 = factT0, |
1531 | 4x |
x = thisDose, |
1532 | 4x |
trialtime = thistime |
1533 | 4x |
) #### the u will be updated |
1534 |
} |
|
1535 | ||
1536 | ||
1537 |
## what is the dose limit? |
|
1538 | 10x |
doselimit <- maxDose(object@increments, |
1539 | 10x |
data = thisData |
1540 |
) |
|
1541 | ||
1542 |
## generate samples from the model |
|
1543 | 10x |
thisSamples <- mcmc( |
1544 | 10x |
data = thisData, |
1545 | 10x |
model = object@model, |
1546 | 10x |
options = mcmcOptions |
1547 |
) |
|
1548 | ||
1549 |
## => what is the next best dose? |
|
1550 | 10x |
nextDose <- nextBest(object@nextBest, |
1551 | 10x |
doselimit = doselimit, |
1552 | 10x |
samples = thisSamples, |
1553 | 10x |
model = object@model, |
1554 | 10x |
data = thisData |
1555 | 10x |
)$value |
1556 | ||
1557 |
# ##remove savePlot |
|
1558 |
# savePlot(plot(thisData),name=paste("Dose",thisDose,numDLTs,"DLT",DLTsearly,nextDose,sep="_")) |
|
1559 |
# |
|
1560 |
## compute relative increment in percent |
|
1561 | 10x |
thisIncrement <- |
1562 | 10x |
round((nextDose - thisDose) / thisDose * 100) |
1563 | ||
1564 |
## evaluate stopping rules |
|
1565 | 10x |
stopThisTrial <- stopTrial(object@stopping, |
1566 | 10x |
dose = nextDose, |
1567 | 10x |
samples = thisSamples, |
1568 | 10x |
model = object@model, |
1569 | 10x |
data = thisData |
1570 |
) |
|
1571 | ||
1572 |
## append information to the data frame |
|
1573 | 10x |
ret <- rbind( |
1574 | 10x |
ret, |
1575 | 10x |
list( |
1576 | 10x |
DLTsearly_1 = DLTsearly, |
1577 | 10x |
dose = thisDose, |
1578 | 10x |
DLTs = numDLTs, |
1579 | 10x |
nextDose = nextDose, |
1580 | 10x |
stop = stopThisTrial, |
1581 | 10x |
increment = as.integer(thisIncrement) |
1582 |
) |
|
1583 |
) |
|
1584 |
} |
|
1585 |
} |
|
1586 |
} |
|
1587 | ||
1588 |
## update pretime |
|
1589 | 3x |
pretime <- trialtime |
1590 | ||
1591 |
## what are the results if 0 DLTs? |
|
1592 | 3x |
resultsNoDLTs <- subset( |
1593 | 3x |
ret, |
1594 | 3x |
dose == thisDose & DLTs == 0 |
1595 |
) |
|
1596 | ||
1597 |
## what is the new dose according to table? |
|
1598 | 3x |
newDose <- as.numeric(resultsNoDLTs$nextDose) |
1599 | ||
1600 |
## what is the difference to the previous dose? |
|
1601 | 3x |
doseDiff <- newDose - thisDose |
1602 | ||
1603 |
## would stopping rule be fulfilled already? |
|
1604 | 3x |
stopAlready <- any(resultsNoDLTs$stop) |
1605 | ||
1606 |
## update dose |
|
1607 | 3x |
thisDose <- max(newDose) |
1608 | ||
1609 |
## number of patients with un-completed DLT window; |
|
1610 | 3x |
preSize <- sum(baseData@u[baseData@y == 0] < baseData@Tmax) |
1611 | ||
1612 |
## update the counter for no increments of the dose |
|
1613 | 3x |
if (all(doseDiff == 0)) { |
1614 | 2x |
noIncrementCounter <- noIncrementCounter + 1L |
1615 |
} else { |
|
1616 | 1x |
noIncrementCounter <- 0L |
1617 |
} |
|
1618 | ||
1619 |
## too many times no increment? |
|
1620 | 3x |
stopNoIncrement <- (noIncrementCounter >= maxNoIncrement) |
1621 | 3x |
if (stopNoIncrement) { |
1622 | 1x |
warning(paste( |
1623 | 1x |
"Stopping because", |
1624 | 1x |
noIncrementCounter, |
1625 | 1x |
"times no increment vs. previous dose" |
1626 |
)) |
|
1627 |
} |
|
1628 | ||
1629 |
## check if we can stop: |
|
1630 |
## either when we have reached the highest dose in the |
|
1631 |
## next cohort, or when the stopping rule is already |
|
1632 |
## fulfilled, or when too many times no increment |
|
1633 | 3x |
stopit <- (thisDose >= max(object@data@doseGrid)) || |
1634 | 3x |
stopAlready || stopNoIncrement |
1635 |
} |
|
1636 | ||
1637 | 1x |
return(ret) |
1638 |
} |
|
1639 |
) |
|
1640 | ||
1641 |
## =================================================================================== |
|
1642 |
## ---------------------------------------------------------------------------------------- |
|
1643 |
## Simulate design using DLE responses only with DLE samples (pseudo DLE model) |
|
1644 |
## ------------------------------------------------------------------------------------ |
|
1645 |
##' This is a methods to simulate dose escalation procedure only using the DLE responses. |
|
1646 |
##' This is a method based on the \code{\linkS4class{TDsamplesDesign}} where model used are of |
|
1647 |
##' \code{\linkS4class{ModelTox}} class object DLE samples are also used |
|
1648 |
##' |
|
1649 |
##' @param object the \code{\linkS4class{TDsamplesDesign}} object we want to simulate the data from |
|
1650 |
##' @param nsim the number of simulations (default :1) |
|
1651 |
##' @param seed see \code{\link{set_seed}} |
|
1652 |
##' @param truth a function which takes as input a dose (vector) and returns the true probability |
|
1653 |
##' (vector) of the occurrence of a DLE. Additional arguments can be supplied in \code{args}. |
|
1654 |
##' @param args data frame with arguments for the \code{truth} function. The |
|
1655 |
##' column names correspond to the argument names, the rows to the values of the |
|
1656 |
##' arguments. The rows are appropriately recycled in the \code{nsim} |
|
1657 |
##' simulations. In order to produce outcomes from the posterior predictive |
|
1658 |
##' distribution, e.g, pass an \code{object} that contains the data observed so |
|
1659 |
##' far, \code{truth} contains the \code{prob} function from the model in |
|
1660 |
##' \code{object}, and \code{args} contains posterior samples from the model. |
|
1661 |
##' @param firstSeparate enroll the first patient separately from the rest of |
|
1662 |
##' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
1663 |
##' in this patient. |
|
1664 |
##' @param mcmcOptions object of class \code{\linkS4class{McmcOptions}}, |
|
1665 |
##' giving the MCMC options for each evaluation in the trial. By default, |
|
1666 |
##' the standard options are used |
|
1667 |
##' @param parallel should the simulation runs be parallelized across the |
|
1668 |
##' clusters of the computer? (not default) |
|
1669 |
##' @param nCores how many cores should be used for parallel computing? |
|
1670 |
##' Defaults to the number of cores on the machine, maximum 5. |
|
1671 |
##' @param \dots not used |
|
1672 |
##' |
|
1673 |
##' @example examples/design-method-simulateTDsamplesDesign.R |
|
1674 |
##' |
|
1675 |
##' @return an object of class \code{\linkS4class{PseudoSimulations}} |
|
1676 |
##' |
|
1677 |
##' @export |
|
1678 |
##' @keywords methods |
|
1679 |
setMethod("simulate", |
|
1680 |
signature = |
|
1681 |
signature( |
|
1682 |
object = "TDsamplesDesign", |
|
1683 |
nsim = "ANY", |
|
1684 |
seed = "ANY" |
|
1685 |
), |
|
1686 |
def = |
|
1687 |
function(object, nsim = 1L, seed = NULL, |
|
1688 |
truth, args = NULL, firstSeparate = FALSE, |
|
1689 |
mcmcOptions = McmcOptions(), |
|
1690 |
parallel = FALSE, nCores = |
|
1691 |
min(parallel::detectCores(), 5L), |
|
1692 |
...) { |
|
1693 |
## checks and extracts |
|
1694 | 1x |
assert_function(truth) |
1695 | 1x |
assert_flag(firstSeparate) |
1696 | 1x |
assert_count(nsim, positive = TRUE) |
1697 | 1x |
assert_flag(parallel) |
1698 | 1x |
assert_count(nCores, positive = TRUE) |
1699 | ||
1700 | 1x |
args <- as.data.frame(args) |
1701 | 1x |
nArgs <- max(nrow(args), 1L) |
1702 | ||
1703 | ||
1704 |
## seed handling |
|
1705 | 1x |
RNGstate <- set_seed(seed) |
1706 | ||
1707 |
## from this, |
|
1708 |
## generate the individual seeds for the simulation runs |
|
1709 | 1x |
simSeeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
1710 | ||
1711 |
## the function to produce the run a single simulation |
|
1712 |
## with index "iterSim" |
|
1713 | 1x |
runSim <- function(iterSim) { |
1714 |
## set the seed for this run |
|
1715 | 1x |
set.seed(simSeeds[iterSim]) |
1716 | ||
1717 |
## what is now the argument for the truth? |
|
1718 |
## (appropriately recycled) |
|
1719 | 1x |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
1720 | ||
1721 |
## so this truth is... |
|
1722 | 1x |
thisTruth <- function(dose) { |
1723 | 12x |
do.call( |
1724 | 12x |
truth, |
1725 |
## First argument: the dose |
|
1726 | 12x |
c( |
1727 | 12x |
dose, |
1728 |
## Following arguments |
|
1729 | 12x |
thisArgs |
1730 |
) |
|
1731 |
) |
|
1732 |
} |
|
1733 | ||
1734 |
## start the simulated data with the provided one |
|
1735 | 1x |
thisData <- object@data |
1736 | ||
1737 |
# In case there are placebo |
|
1738 | 1x |
if (thisData@placebo) { |
1739 |
## what is the probability for tox. at placebo? |
|
1740 | ! |
thisProb.PL <- thisTruth(object@data@doseGrid[1]) |
1741 |
} |
|
1742 | ||
1743 | ||
1744 |
## shall we stop the trial? |
|
1745 |
## First, we want to continue with the starting dose. |
|
1746 |
## This variable is updated after each cohort in the loop. |
|
1747 | 1x |
stopit <- FALSE |
1748 | ||
1749 |
## what is the next dose to be used? |
|
1750 |
## initialize with starting dose |
|
1751 | 1x |
thisDose <- object@startingDose |
1752 | ||
1753 |
## inside this loop we simulate the whole trial, until stopping |
|
1754 | 1x |
while (!stopit) { |
1755 |
## what is the probability for tox. at this dose? |
|
1756 | 12x |
thisProb <- thisTruth(thisDose) |
1757 | ||
1758 | ||
1759 |
## what is the cohort size at this dose? |
|
1760 | 12x |
thisSize <- size(object@cohort_size, |
1761 | 12x |
dose = thisDose, |
1762 | 12x |
data = thisData |
1763 |
) |
|
1764 | ||
1765 |
## In case there are placebo |
|
1766 | 12x |
if (thisData@placebo) { |
1767 | ! |
thisSize.PL <- size(object@pl_cohort_size, |
1768 | ! |
dose = thisDose, |
1769 | ! |
data = thisData |
1770 |
) |
|
1771 |
} |
|
1772 | ||
1773 | ||
1774 | ||
1775 |
## simulate DLTs: depends on whether we |
|
1776 |
## separate the first patient or not. |
|
1777 | 12x |
if (firstSeparate && (thisSize > 1L)) { |
1778 |
## dose the first patient |
|
1779 | ! |
thisDLTs <- rbinom( |
1780 | ! |
n = 1L, |
1781 | ! |
size = 1L, |
1782 | ! |
prob = thisProb |
1783 |
) |
|
1784 | ||
1785 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
1786 | ! |
thisDLTs.PL <- rbinom( |
1787 | ! |
n = 1L, |
1788 | ! |
size = 1L, |
1789 | ! |
prob = thisProb.PL |
1790 |
) |
|
1791 |
} |
|
1792 | ||
1793 |
## if there is no DLT: |
|
1794 | ! |
if (thisDLTs == 0) { |
1795 |
## enroll the remaining patients |
|
1796 | ! |
thisDLTs <- c( |
1797 | ! |
thisDLTs, |
1798 | ! |
rbinom( |
1799 | ! |
n = thisSize - 1L, |
1800 | ! |
size = 1L, |
1801 | ! |
prob = thisProb |
1802 |
) |
|
1803 |
) |
|
1804 | ||
1805 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
1806 | ! |
thisDLTs.PL <- c( |
1807 | ! |
thisDLTs.PL, |
1808 | ! |
rbinom( |
1809 | ! |
n = thisSize.PL, |
1810 | ! |
size = 1L, |
1811 | ! |
prob = thisProb.PL |
1812 |
) |
|
1813 |
) |
|
1814 |
} |
|
1815 |
} |
|
1816 |
} else { |
|
1817 |
## we can directly dose all patients |
|
1818 | 12x |
thisDLTs <- rbinom( |
1819 | 12x |
n = thisSize, |
1820 | 12x |
size = 1L, |
1821 | 12x |
prob = thisProb |
1822 |
) |
|
1823 | 12x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
1824 | ! |
thisDLTs.PL <- rbinom( |
1825 | ! |
n = thisSize.PL, |
1826 | ! |
size = 1L, |
1827 | ! |
prob = thisProb.PL |
1828 |
) |
|
1829 |
} |
|
1830 |
} |
|
1831 | ||
1832 |
## update the data with this placebo (if any) cohort and then with active dose |
|
1833 | 12x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
1834 | ! |
thisData <- update( |
1835 | ! |
object = thisData, |
1836 | ! |
x = object@data@doseGrid[1], |
1837 | ! |
y = thisDLTs.PL |
1838 |
) |
|
1839 | ||
1840 |
## update the data with active dose |
|
1841 | ! |
thisData <- update( |
1842 | ! |
object = thisData, |
1843 | ! |
x = thisDose, |
1844 | ! |
y = thisDLTs, |
1845 | ! |
new_cohort = FALSE |
1846 |
) |
|
1847 |
} else { |
|
1848 |
## update the data with this cohort |
|
1849 | 12x |
thisData <- update( |
1850 | 12x |
object = thisData, |
1851 | 12x |
x = thisDose, |
1852 | 12x |
y = thisDLTs |
1853 |
) |
|
1854 |
} |
|
1855 | ||
1856 |
## Update the model with thisData |
|
1857 | 12x |
thisModel <- update(object@model, |
1858 | 12x |
data = thisData |
1859 |
) |
|
1860 | ||
1861 |
## what is the dose limit? |
|
1862 | 12x |
doselimit <- maxDose(object@increments, |
1863 | 12x |
data = thisData |
1864 |
) |
|
1865 | ||
1866 |
## generate samples from the model |
|
1867 | 12x |
thisSamples <- mcmc( |
1868 | 12x |
data = thisData, |
1869 | 12x |
model = thisModel, |
1870 | 12x |
options = mcmcOptions |
1871 |
) |
|
1872 | ||
1873 |
## => what is the next best dose? |
|
1874 | ||
1875 | 12x |
next_bd <- nextBest(object@nextBest, |
1876 | 12x |
doselimit = doselimit, |
1877 | 12x |
samples = thisSamples, |
1878 | 12x |
model = thisModel, |
1879 | 12x |
data = thisData, |
1880 | 12x |
in_sim = TRUE |
1881 |
) |
|
1882 | ||
1883 | 12x |
thisDose <- next_bd$next_dose_drt |
1884 | 12x |
thisTDtargetDuringTrial <- next_bd$dose_target_drt |
1885 | 12x |
thisTDtargetEndOfTrial <- next_bd$dose_target_eot |
1886 | 12x |
thisTDtargetEndOfTrialatdoseGrid <- next_bd$next_dose_eot |
1887 | 12x |
thisCITDEOT <- list(lower = next_bd$ci_dose_target_eot[1], upper = next_bd$ci_dose_target_eot[2]) |
1888 | 12x |
thisratioTDEOT <- next_bd$ci_ratio_dose_target_eot |
1889 | ||
1890 |
## evaluate stopping rules |
|
1891 | 12x |
stopit <- stopTrial(object@stopping, |
1892 | 12x |
dose = thisDose, |
1893 | 12x |
samples = thisSamples, |
1894 | 12x |
model = thisModel, |
1895 | 12x |
data = thisData |
1896 |
) |
|
1897 | 12x |
stopit_results <- h_unpack_stopit(stopit) |
1898 |
} |
|
1899 | ||
1900 |
## get the fit |
|
1901 | 1x |
thisFit <- fit( |
1902 | 1x |
object = thisSamples, |
1903 | 1x |
model = thisModel, |
1904 | 1x |
data = thisData |
1905 |
) |
|
1906 | ||
1907 | ||
1908 |
## return the results |
|
1909 | 1x |
thisResult <- |
1910 | 1x |
list( |
1911 | 1x |
data = thisData, |
1912 | 1x |
dose = thisDose, |
1913 | 1x |
TDtargetDuringTrial = thisTDtargetDuringTrial, |
1914 | 1x |
TDtargetEndOfTrial = thisTDtargetEndOfTrial, |
1915 | 1x |
TDtargetEndOfTrialatdoseGrid = thisTDtargetEndOfTrialatdoseGrid, |
1916 | 1x |
TDtargetDuringTrialatdoseGrid = thisDose, |
1917 | 1x |
CITDEOT = thisCITDEOT, |
1918 | 1x |
ratioTDEOT = thisratioTDEOT, |
1919 | 1x |
fit = |
1920 | 1x |
subset(thisFit, |
1921 | 1x |
select = c(middle, lower, upper) |
1922 |
), |
|
1923 | 1x |
stop = |
1924 | 1x |
attr( |
1925 | 1x |
stopit, |
1926 | 1x |
"message" |
1927 |
), |
|
1928 | 1x |
report_results = stopit_results |
1929 |
) |
|
1930 | 1x |
return(thisResult) |
1931 |
} |
|
1932 | ||
1933 | 1x |
resultList <- get_result_list( |
1934 | 1x |
fun = runSim, |
1935 | 1x |
nsim = nsim, |
1936 | 1x |
vars = |
1937 | 1x |
c( |
1938 | 1x |
"simSeeds", |
1939 | 1x |
"args", |
1940 | 1x |
"nArgs", |
1941 | 1x |
"firstSeparate", |
1942 | 1x |
"truth", |
1943 | 1x |
"object", |
1944 | 1x |
"mcmcOptions" |
1945 |
), |
|
1946 | 1x |
parallel = parallel, |
1947 | 1x |
n_cores = nCores |
1948 |
) |
|
1949 | ||
1950 |
## put everything in the Simulations format: |
|
1951 | ||
1952 |
## setup the list for the simulated data objects |
|
1953 | 1x |
dataList <- lapply(resultList, "[[", "data") |
1954 | ||
1955 |
## set up list for the final TD during Trial Estimate |
|
1956 | 1x |
TDtargetDuringTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrial")) |
1957 | ||
1958 |
## set up list for the final TD End of Trial Estimate |
|
1959 | 1x |
TDtargetEndOfTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrial")) |
1960 | ||
1961 |
## set up list for the final TD during Trial estimate at dose Grid |
|
1962 | 1x |
TDtargetDuringTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrialatdoseGrid")) |
1963 | ||
1964 |
## set up list for the final TD End Of Trial estimate at dose Grid |
|
1965 | 1x |
TDtargetEndOfTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrialatdoseGrid")) |
1966 | ||
1967 | ||
1968 |
## the vector of the final dose recommendations |
|
1969 | 1x |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrialatdoseGrid")) |
1970 | ||
1971 |
## Set up the list for the final 95% CI obtained |
|
1972 | 1x |
CIList <- lapply(resultList, "[[", "CITDEOT") |
1973 | ||
1974 |
## Set up the list for the final ratios obtained |
|
1975 | 1x |
ratioList <- as.numeric(sapply(resultList, "[[", "ratioTDEOT")) |
1976 | ||
1977 |
## Set up the list for the final TDEOT 95% CI obtained |
|
1978 | 1x |
CITDEOTList <- lapply(resultList, "[[", "CITDEOT") |
1979 | ||
1980 |
## Set up the list for the final TDEOT ratios obtained |
|
1981 | 1x |
ratioTDEOTList <- as.numeric(sapply(resultList, "[[", "ratioTDEOT")) |
1982 | ||
1983 |
## setup the list for the final fits |
|
1984 | 1x |
fitList <- lapply(resultList, "[[", "fit") |
1985 | ||
1986 |
## the reasons for stopping |
|
1987 | 1x |
stopReasons <- lapply(resultList, "[[", "stop") |
1988 | ||
1989 |
# individual stopping rule results as matrix, labels as column names |
|
1990 | 1x |
stop_results <- lapply(resultList, "[[", "report_results") |
1991 | 1x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
1992 | ||
1993 | ||
1994 |
## return the results in the Simulations class object |
|
1995 | 1x |
ret <- PseudoSimulations( |
1996 | 1x |
data = dataList, |
1997 | 1x |
doses = recommendedDoses, |
1998 | 1x |
fit = fitList, |
1999 | 1x |
final_td_target_during_trial_estimates = TDtargetDuringTrialList, |
2000 | 1x |
final_td_target_end_of_trial_estimates = TDtargetEndOfTrialList, |
2001 | 1x |
final_td_target_during_trial_at_dose_grid = TDtargetDuringTrialDoseGridList, |
2002 | 1x |
final_td_target_end_of_trial_at_dose_grid = TDtargetEndOfTrialDoseGridList, |
2003 | 1x |
final_cis = CIList, |
2004 | 1x |
final_ratios = ratioList, |
2005 | 1x |
final_tdeot_cis = CITDEOTList, |
2006 | 1x |
final_tdeot_ratios = ratioTDEOTList, |
2007 | 1x |
stop_reasons = stopReasons, |
2008 | 1x |
stop_report = stop_report, |
2009 | 1x |
seed = RNGstate |
2010 |
) |
|
2011 | ||
2012 | 1x |
return(ret) |
2013 |
} |
|
2014 |
) |
|
2015 |
## ------------------------------------------------------------------------------------- |
|
2016 |
## Simulate design using DLE responses only without samples (pseudo DLE model) |
|
2017 |
## -------------------------------------------------------------------------------- |
|
2018 |
### |
|
2019 |
##' This is a methods to simulate dose escalation procedure only using the DLE responses. |
|
2020 |
##' This is a method based on the \code{\linkS4class{TDDesign}} where model used are of |
|
2021 |
##' \code{\linkS4class{ModelTox}} class object and no samples are involved. |
|
2022 |
##' |
|
2023 |
##' @param object the \code{\linkS4class{TDDesign}} object we want to simulate the data from |
|
2024 |
##' @param nsim the number of simulations (default :1) |
|
2025 |
##' @param seed see \code{\link{set_seed}} |
|
2026 |
##' @param truth a function which takes as input a dose (vector) and returns the true probability |
|
2027 |
##' (vector) of the occurrence of a DLE. Additional arguments can be supplied in \code{args}. |
|
2028 |
##' @param args data frame with arguments for the \code{truth} function. The |
|
2029 |
##' column names correspond to the argument names, the rows to the values of the |
|
2030 |
##' arguments. The rows are appropriately recycled in the \code{nsim} |
|
2031 |
##' simulations. In order to produce outcomes from the posterior predictive |
|
2032 |
##' distribution, e.g, pass an \code{object} that contains the data observed so |
|
2033 |
##' far, \code{truth} contains the \code{prob} function from the model in |
|
2034 |
##' \code{object}, and \code{args} contains posterior samples from the model. |
|
2035 |
##' @param firstSeparate enroll the first patient separately from the rest of |
|
2036 |
##' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
2037 |
##' in this patient. |
|
2038 |
##' @param parallel should the simulation runs be parallelized across the |
|
2039 |
##' clusters of the computer? (not default) |
|
2040 |
##' @param nCores how many cores should be used for parallel computing? |
|
2041 |
##' Defaults to the number of cores on the machine, maximum 5. |
|
2042 |
##' @param \dots not used |
|
2043 |
##' |
|
2044 |
##' @example examples/design-method-simulateTDDesign.R |
|
2045 |
##' |
|
2046 |
##' @return an object of class \code{\linkS4class{PseudoSimulations}} |
|
2047 |
##' |
|
2048 |
##' @export |
|
2049 |
##' @keywords methods |
|
2050 |
setMethod("simulate", |
|
2051 |
signature = |
|
2052 |
signature( |
|
2053 |
object = "TDDesign", |
|
2054 |
nsim = "ANY", |
|
2055 |
seed = "ANY" |
|
2056 |
), |
|
2057 |
def = |
|
2058 |
function(object, nsim = 1L, seed = NULL, |
|
2059 |
truth, args = NULL, firstSeparate = FALSE, |
|
2060 |
parallel = FALSE, nCores = |
|
2061 |
min(parallel::detectCores(), 5L), |
|
2062 |
...) { |
|
2063 |
## checks and extracts |
|
2064 | 1x |
assert_function(truth) |
2065 | 1x |
assert_flag(firstSeparate) |
2066 | 1x |
assert_count(nsim, positive = TRUE) |
2067 | 1x |
assert_flag(parallel) |
2068 | 1x |
assert_count(nCores, positive = TRUE) |
2069 | ||
2070 | 1x |
args <- as.data.frame(args) |
2071 | 1x |
nArgs <- max(nrow(args), 1L) |
2072 | ||
2073 |
## seed handling |
|
2074 | 1x |
RNGstate <- set_seed(seed) |
2075 | ||
2076 |
## from this, |
|
2077 |
## generate the individual seeds for the simulation runs |
|
2078 | 1x |
simSeeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
2079 | ||
2080 |
## the function to produce the run a single simulation |
|
2081 |
## with index "iterSim" |
|
2082 | 1x |
runSim <- function(iterSim) { |
2083 |
## set the seed for this run |
|
2084 | 1x |
set.seed(simSeeds[iterSim]) |
2085 | ||
2086 |
## what is now the argument for the truth? |
|
2087 |
## (appropriately recycled) |
|
2088 | 1x |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
2089 | ||
2090 |
## so this truth is... |
|
2091 | 1x |
thisTruth <- function(dose) { |
2092 | 12x |
do.call( |
2093 | 12x |
truth, |
2094 |
## First argument: the dose |
|
2095 | 12x |
c( |
2096 | 12x |
dose, |
2097 |
## Following arguments |
|
2098 | 12x |
thisArgs |
2099 |
) |
|
2100 |
) |
|
2101 |
} |
|
2102 | ||
2103 |
## start the simulated data with the provided one |
|
2104 | 1x |
thisData <- object@data |
2105 | ||
2106 |
# In case there are placebo |
|
2107 | 1x |
if (thisData@placebo) { |
2108 |
## what is the probability for tox. at placebo? |
|
2109 | ! |
thisProb.PL <- thisTruth(object@data@doseGrid[1]) |
2110 |
} |
|
2111 | ||
2112 |
## shall we stop the trial? |
|
2113 |
## First, we want to continue with the starting dose. |
|
2114 |
## This variable is updated after each cohort in the loop. |
|
2115 | 1x |
stopit <- FALSE |
2116 | ||
2117 |
## what is the next dose to be used? |
|
2118 |
## initialize with starting dose |
|
2119 | 1x |
thisDose <- object@startingDose |
2120 | ||
2121 |
## inside this loop we simulate the whole trial, until stopping |
|
2122 | 1x |
while (!stopit) { |
2123 |
## what is the probability for tox. at this dose? |
|
2124 | 12x |
thisProb <- thisTruth(thisDose) |
2125 | ||
2126 |
## what is the cohort size at this dose? |
|
2127 | 12x |
thisSize <- size(object@cohort_size, |
2128 | 12x |
dose = thisDose, |
2129 | 12x |
data = thisData |
2130 |
) |
|
2131 | ||
2132 |
## In case there are placebo |
|
2133 | 12x |
if (thisData@placebo) { |
2134 | ! |
thisSize.PL <- size(object@pl_cohort_size, |
2135 | ! |
dose = thisDose, |
2136 | ! |
data = thisData |
2137 |
) |
|
2138 |
} |
|
2139 | ||
2140 |
## simulate DLTs: depends on whether we |
|
2141 |
## separate the first patient or not. |
|
2142 | 12x |
if (firstSeparate && (thisSize > 1L)) { |
2143 |
## dose the first patient |
|
2144 | ! |
thisDLTs <- rbinom( |
2145 | ! |
n = 1L, |
2146 | ! |
size = 1L, |
2147 | ! |
prob = thisProb |
2148 |
) |
|
2149 | ||
2150 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2151 | ! |
thisDLTs.PL <- rbinom( |
2152 | ! |
n = 1L, |
2153 | ! |
size = 1L, |
2154 | ! |
prob = thisProb.PL |
2155 |
) |
|
2156 |
} |
|
2157 | ||
2158 |
## if there is no DLT: |
|
2159 | ! |
if (thisDLTs == 0) { |
2160 |
## enroll the remaining patients |
|
2161 | ! |
thisDLTs <- c( |
2162 | ! |
thisDLTs, |
2163 | ! |
rbinom( |
2164 | ! |
n = thisSize - 1L, |
2165 | ! |
size = 1L, |
2166 | ! |
prob = thisProb |
2167 |
) |
|
2168 |
) |
|
2169 | ||
2170 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2171 | ! |
thisDLTs.PL <- c( |
2172 | ! |
thisDLTs.PL, |
2173 | ! |
rbinom( |
2174 | ! |
n = thisSize.PL, |
2175 | ! |
size = 1L, |
2176 | ! |
prob = thisProb.PL |
2177 |
) |
|
2178 |
) |
|
2179 |
} |
|
2180 |
} |
|
2181 |
} else { |
|
2182 |
## we can directly dose all patients |
|
2183 | 12x |
thisDLTs <- rbinom( |
2184 | 12x |
n = thisSize, |
2185 | 12x |
size = 1L, |
2186 | 12x |
prob = thisProb |
2187 |
) |
|
2188 | ||
2189 | 12x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2190 | ! |
thisDLTs.PL <- rbinom( |
2191 | ! |
n = thisSize.PL, |
2192 | ! |
size = 1L, |
2193 | ! |
prob = thisProb.PL |
2194 |
) |
|
2195 |
} |
|
2196 |
} |
|
2197 | ||
2198 |
## update the data with this placebo (if any) cohort and then with active dose |
|
2199 | 12x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2200 | ! |
thisData <- update( |
2201 | ! |
object = thisData, |
2202 | ! |
x = object@data@doseGrid[1], |
2203 | ! |
y = thisDLTs.PL |
2204 |
) |
|
2205 | ||
2206 |
## update the data with active dose |
|
2207 | ! |
thisData <- update( |
2208 | ! |
object = thisData, |
2209 | ! |
x = thisDose, |
2210 | ! |
y = thisDLTs, |
2211 | ! |
new_cohort = FALSE |
2212 |
) |
|
2213 |
} else { |
|
2214 |
## update the data with this cohort |
|
2215 | 12x |
thisData <- update( |
2216 | 12x |
object = thisData, |
2217 | 12x |
x = thisDose, |
2218 | 12x |
y = thisDLTs |
2219 |
) |
|
2220 |
} |
|
2221 | ||
2222 |
## Update model estimates with thisData |
|
2223 | 12x |
thisModel <- update(object@model, |
2224 | 12x |
data = thisData |
2225 |
) |
|
2226 | ||
2227 |
## what is the dose limit? |
|
2228 | 12x |
doselimit <- maxDose(object@increments, data = thisData) |
2229 | ||
2230 | ||
2231 |
## => what is the next best dose? |
|
2232 | 12x |
next_bd <- nextBest(object@nextBest, |
2233 | 12x |
doselimit = doselimit, |
2234 | 12x |
model = thisModel, |
2235 | 12x |
data = thisData, |
2236 | 12x |
in_sim = TRUE |
2237 |
) |
|
2238 | ||
2239 | 12x |
thisDose <- next_bd$next_dose_drt |
2240 | 12x |
thisTDtargetDuringTrial <- next_bd$dose_target_drt |
2241 | 12x |
thisTDtargetEndOfTrial <- next_bd$dose_target_eot |
2242 | 12x |
thisTDtargetEndOfTrialatdoseGrid <- next_bd$next_dose_eot |
2243 | 12x |
thisCITDEOT <- list(lower = next_bd$ci_dose_target_eot[1], upper = next_bd$ci_dose_target_eot[2]) |
2244 | 12x |
thisratioTDEOT <- next_bd$ci_ratio_dose_target_eot |
2245 | ||
2246 |
## evaluate stopping rules |
|
2247 | 12x |
stopit <- stopTrial(object@stopping, |
2248 | 12x |
dose = thisDose, |
2249 | 12x |
model = thisModel, |
2250 | 12x |
data = thisData |
2251 |
) |
|
2252 | 12x |
stopit_results <- h_unpack_stopit(stopit) |
2253 |
} |
|
2254 |
## get the fit |
|
2255 | 1x |
prob_fun <- probFunction(thisModel, phi1 = thisModel@phi1, phi2 = thisModel@phi2) |
2256 | 1x |
thisFit <- list( |
2257 | 1x |
phi1 = thisModel@phi1, |
2258 | 1x |
phi2 = thisModel@phi2, |
2259 | 1x |
probDLE = prob_fun(object@data@doseGrid) |
2260 |
) |
|
2261 | ||
2262 | ||
2263 | ||
2264 |
## return the results |
|
2265 | 1x |
thisResult <- |
2266 | 1x |
list( |
2267 | 1x |
data = thisData, |
2268 | 1x |
dose = thisDose, |
2269 | 1x |
TDtargetDuringTrial = thisTDtargetDuringTrial, |
2270 | 1x |
TDtargetEndOfTrial = thisTDtargetEndOfTrial, |
2271 | 1x |
TDtargetEndOfTrialatdoseGrid = thisTDtargetEndOfTrialatdoseGrid, |
2272 | 1x |
TDtargetDuringTrialatdoseGrid = thisDose, |
2273 | 1x |
CITDEOT = thisCITDEOT, |
2274 | 1x |
ratioTDEOT = thisratioTDEOT, |
2275 | 1x |
fit = thisFit, |
2276 | 1x |
stop = |
2277 | 1x |
attr( |
2278 | 1x |
stopit, |
2279 | 1x |
"message" |
2280 |
), |
|
2281 | 1x |
report_results = stopit_results |
2282 |
) |
|
2283 | 1x |
return(thisResult) |
2284 |
} |
|
2285 | ||
2286 | ||
2287 | 1x |
resultList <- get_result_list( |
2288 | 1x |
fun = runSim, |
2289 | 1x |
nsim = nsim, |
2290 | 1x |
vars = |
2291 | 1x |
c( |
2292 | 1x |
"simSeeds", |
2293 | 1x |
"args", |
2294 | 1x |
"nArgs", |
2295 | 1x |
"firstSeparate", |
2296 | 1x |
"truth", |
2297 | 1x |
"object" |
2298 |
), |
|
2299 | 1x |
parallel = parallel, |
2300 | 1x |
n_cores = nCores |
2301 |
) |
|
2302 | ||
2303 |
## put everything in the Simulations format: |
|
2304 | ||
2305 |
## setup the list for the simulated data objects |
|
2306 | 1x |
dataList <- lapply(resultList, "[[", "data") |
2307 | ||
2308 | ||
2309 |
## set up list for the final TD during Trial Estimate |
|
2310 | 1x |
TDtargetDuringTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrial")) |
2311 | ||
2312 |
## set up list for the final TD End of Trial Estimate |
|
2313 | 1x |
TDtargetEndOfTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrial")) |
2314 | ||
2315 |
## set up list for the final TD during Trial estimate at dose Grid |
|
2316 | 1x |
TDtargetDuringTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrialatdoseGrid")) |
2317 | ||
2318 |
## set up list for the final TD End Of Trial estimate at dose Grid |
|
2319 | 1x |
TDtargetEndOfTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrialatdoseGrid")) |
2320 | ||
2321 | ||
2322 |
## the vector of the final dose recommendations |
|
2323 | 1x |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrialatdoseGrid")) |
2324 | ||
2325 |
## Set up the list for the final 95% CI obtained |
|
2326 | 1x |
CIList <- lapply(resultList, "[[", "CITDEOT") |
2327 | ||
2328 |
## Set up the list for the final ratios obtained |
|
2329 | 1x |
ratioList <- as.numeric(sapply(resultList, "[[", "ratioTDEOT")) |
2330 | ||
2331 |
## Set up the list for the final TDEOT 95% CI obtained |
|
2332 | 1x |
CITDEOTList <- lapply(resultList, "[[", "CITDEOT") |
2333 | ||
2334 |
## Set up the list for the final TDEOT ratios obtained |
|
2335 | 1x |
ratioTDEOTList <- as.numeric(sapply(resultList, "[[", "ratioTDEOT")) |
2336 |
## set up the list for the final fits |
|
2337 | ||
2338 | 1x |
fitList <- lapply(resultList, "[[", "fit") |
2339 | ||
2340 |
## the reasons for stopping |
|
2341 | 1x |
stopReasons <- lapply(resultList, "[[", "stop") |
2342 | ||
2343 |
# individual stopping rule results as matrix, labels as column names |
|
2344 | 1x |
stop_results <- lapply(resultList, "[[", "report_results") |
2345 | 1x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
2346 | ||
2347 | ||
2348 |
## return the results in the Simulations class object |
|
2349 | 1x |
ret <- PseudoSimulations( |
2350 | 1x |
data = dataList, |
2351 | 1x |
doses = recommendedDoses, |
2352 | 1x |
fit = fitList, |
2353 | 1x |
final_td_target_during_trial_estimates = TDtargetDuringTrialList, |
2354 | 1x |
final_td_target_end_of_trial_estimates = TDtargetEndOfTrialList, |
2355 | 1x |
final_td_target_during_trial_at_dose_grid = TDtargetDuringTrialDoseGridList, |
2356 | 1x |
final_td_target_end_of_trial_at_dose_grid = TDtargetEndOfTrialDoseGridList, |
2357 | 1x |
final_cis = CIList, |
2358 | 1x |
final_ratios = ratioList, |
2359 | 1x |
final_tdeot_cis = CITDEOTList, |
2360 | 1x |
final_tdeot_ratios = ratioTDEOTList, |
2361 | 1x |
stop_reasons = stopReasons, |
2362 | 1x |
stop_report = stop_report, |
2363 | 1x |
seed = RNGstate |
2364 |
) |
|
2365 | ||
2366 | 1x |
return(ret) |
2367 |
} |
|
2368 |
) |
|
2369 |
## ----------------------------------------------------------------------------------------------- |
|
2370 |
## Simulate design using DLE and efficacy responses without DLE and efficacy samples (pseudo models) |
|
2371 |
## -------------------------------------------------------------------------------------------- |
|
2372 |
### |
|
2373 |
##' This is a methods to simulate dose escalation procedure using both DLE and efficacy responses. |
|
2374 |
##' This is a method based on the \code{\linkS4class{DualResponsesDesign}} where DLEmodel used are of |
|
2375 |
##' \code{\linkS4class{ModelTox}} class object and efficacy model used are of \code{\linkS4class{ModelEff}} |
|
2376 |
##' class object. In addition, no DLE and efficacy samples are involved or generated in the simulation |
|
2377 |
##' process |
|
2378 |
##' |
|
2379 |
##' @param object the \code{\linkS4class{DualResponsesDesign}} object we want to simulate the data from |
|
2380 |
##' @param nsim the number of simulations (default :1) |
|
2381 |
##' @param seed see \code{\link{set_seed}} |
|
2382 |
##' @param trueDLE a function which takes as input a dose (vector) and returns the true probability |
|
2383 |
##' (vector) of the occurrence of a DLE. Additional arguments can be supplied in \code{args}. |
|
2384 |
##' @param trueEff a function which takes as input a dose (vector) and returns the expected efficacy |
|
2385 |
##' responses (vector). Additional arguments can be supplied in \code{args}. |
|
2386 |
##' @param trueNu the precision, the inverse of the variance of the efficacy responses |
|
2387 |
##' @param args data frame with arguments for the \code{trueDLE} and |
|
2388 |
##' \code{trueEff} function. The column names correspond to the argument |
|
2389 |
##' names, the rows to the values of the arguments. The rows are appropriately |
|
2390 |
##' recycled in the \code{nsim} simulations. |
|
2391 |
##' @param firstSeparate enroll the first patient separately from the rest of |
|
2392 |
##' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
2393 |
##' in this patient. |
|
2394 |
##' @param parallel should the simulation runs be parallelized across the |
|
2395 |
##' clusters of the computer? (not default) |
|
2396 |
##' @param nCores how many cores should be used for parallel computing? |
|
2397 |
##' Defaults to the number of cores on the machine, maximum 5. |
|
2398 |
##' @param \dots not used |
|
2399 |
##' |
|
2400 |
##' @example examples/design-method-simulateDualResponsesDesign.R |
|
2401 |
##' |
|
2402 |
##' @return an object of class \code{\linkS4class{PseudoDualSimulations}} |
|
2403 |
##' |
|
2404 |
##' @export |
|
2405 |
##' @keywords methods |
|
2406 | ||
2407 |
setMethod("simulate", |
|
2408 |
signature = |
|
2409 |
signature( |
|
2410 |
object = "DualResponsesDesign", |
|
2411 |
nsim = "ANY", |
|
2412 |
seed = "ANY" |
|
2413 |
), |
|
2414 |
def = |
|
2415 |
function(object, nsim = 1L, seed = NULL, |
|
2416 |
trueDLE, trueEff, trueNu, |
|
2417 |
args = NULL, firstSeparate = FALSE, |
|
2418 |
parallel = FALSE, nCores = |
|
2419 |
min(parallel::detectCores(), 5L), |
|
2420 |
...) { |
|
2421 |
## checks and extracts |
|
2422 | 1x |
assert_function(trueDLE) |
2423 | 1x |
assert_function(trueEff) |
2424 | 1x |
assert_true(trueNu > 0) |
2425 | 1x |
assert_flag(firstSeparate) |
2426 | 1x |
assert_count(nsim, positive = TRUE) |
2427 | 1x |
assert_flag(parallel) |
2428 | 1x |
assert_count(nCores, positive = TRUE) |
2429 | ||
2430 | 1x |
args <- as.data.frame(args) |
2431 | 1x |
nArgs <- max(nrow(args), 1L) |
2432 | ||
2433 |
## get names of arguments (excluding the first one which is the dose) |
|
2434 | 1x |
trueDLEArgnames <- names(formals(trueDLE))[-1] |
2435 | 1x |
trueEffArgnames <- names(formals(trueEff))[-1] |
2436 | ||
2437 | ||
2438 | ||
2439 |
## seed handling |
|
2440 | 1x |
RNGstate <- set_seed(seed) |
2441 | ||
2442 |
## from this, |
|
2443 |
## generate the individual seeds for the simulation runs |
|
2444 | 1x |
simSeeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
2445 | ||
2446 |
## the function to produce the run a single simulation |
|
2447 |
## with index "iterSim" |
|
2448 | 1x |
runSim <- function(iterSim) { |
2449 |
## set the seed for this run |
|
2450 | 1x |
set.seed(simSeeds[iterSim]) |
2451 | ||
2452 |
## what is now the argument for the truth? |
|
2453 |
## (appropriately recycled) |
|
2454 | 1x |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
2455 | ||
2456 |
## so this truth DLE function is... |
|
2457 | 1x |
thisTruthDLE <- function(dose) { |
2458 | 12x |
do.call( |
2459 | 12x |
trueDLE, |
2460 |
## First argument: the dose |
|
2461 | 12x |
c( |
2462 | 12x |
dose, |
2463 |
## Following arguments: take only those that |
|
2464 |
## are required by the DLE function |
|
2465 | 12x |
as.list(thisArgs)[trueDLEArgnames] |
2466 |
) |
|
2467 |
) |
|
2468 |
} |
|
2469 | ||
2470 |
## and the truth Eff function is: |
|
2471 | 1x |
thisTruthEff <- function(dose) { |
2472 | 12x |
do.call( |
2473 | 12x |
trueEff, |
2474 |
## First argument: the dose |
|
2475 | 12x |
c( |
2476 | 12x |
dose, |
2477 |
## Following arguments: take only those that |
|
2478 |
## are required by the Eff function |
|
2479 | 12x |
as.list(thisArgs)[trueEffArgnames] |
2480 |
) |
|
2481 |
) |
|
2482 |
} |
|
2483 | ||
2484 |
## start the simulated data with the provided one |
|
2485 | 1x |
thisData <- object@data |
2486 | ||
2487 |
## find true sigma2 to generate responses |
|
2488 | ||
2489 | 1x |
trueSigma2 <- 1 / trueNu |
2490 | ||
2491 |
## start the simulated data with the provided one |
|
2492 | 1x |
thisData <- object@data |
2493 | ||
2494 | 1x |
if (thisData@placebo) { |
2495 |
## what is the probability for tox. at placebo? |
|
2496 | ! |
thisProb.PL <- thisTruthDLE(object@data@doseGrid[1]) |
2497 | ||
2498 |
## what is the mean efficacy at placebo? |
|
2499 | ! |
thisMeanEff.PL <- thisTruthEff(object@data@doseGrid[1]) |
2500 |
} |
|
2501 | ||
2502 |
## shall we stop the trial? |
|
2503 |
## First, we want to continue with the starting dose. |
|
2504 |
## This variable is updated after each cohort in the loop. |
|
2505 | 1x |
stopit <- FALSE |
2506 | ||
2507 |
## what is the next dose to be used? |
|
2508 |
## initialize with starting dose |
|
2509 | 1x |
thisDose <- object@startingDose |
2510 | ||
2511 |
## inside this loop we simulate the whole trial, until stopping |
|
2512 | 1x |
while (!stopit) { |
2513 |
## what is the probability for tox. at this dose? |
|
2514 | 12x |
thisDLEProb <- thisTruthDLE(thisDose) |
2515 | 12x |
thisMeanEff <- thisTruthEff(thisDose) |
2516 | ||
2517 |
## what is the cohort size at this dose? |
|
2518 | 12x |
thisSize <- size(object@cohort_size, |
2519 | 12x |
dose = thisDose, |
2520 | 12x |
data = thisData |
2521 |
) |
|
2522 | ||
2523 | ||
2524 |
## In case there are placebo |
|
2525 |
## what is the cohort size at this dose for Placebo? |
|
2526 | 12x |
if (thisData@placebo) { |
2527 | ! |
thisSize.PL <- size(object@pl_cohort_size, |
2528 | ! |
dose = thisDose, |
2529 | ! |
data = thisData |
2530 |
) |
|
2531 |
} |
|
2532 | ||
2533 | ||
2534 |
## simulate DLTs: depends on whether we |
|
2535 |
## separate the first patient or not. |
|
2536 | 12x |
if (firstSeparate && (thisSize > 1L)) { |
2537 |
## dose the first patient |
|
2538 | ! |
thisDLTs <- rbinom( |
2539 | ! |
n = 1L, |
2540 | ! |
size = 1L, |
2541 | ! |
prob = thisDLEProb |
2542 |
) |
|
2543 | ||
2544 | ||
2545 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2546 | ! |
thisDLTs.PL <- rbinom( |
2547 | ! |
n = 1L, |
2548 | ! |
size = 1L, |
2549 | ! |
prob = thisProb.PL |
2550 |
) |
|
2551 |
} |
|
2552 | ||
2553 | ! |
thisEff <- rnorm( |
2554 | ! |
n = 1L, |
2555 | ! |
mean = thisMeanEff, |
2556 | ! |
sd = sqrt(trueSigma2) |
2557 |
) |
|
2558 | ||
2559 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2560 | ! |
thisEff.PL <- rnorm( |
2561 | ! |
n = 1L, |
2562 | ! |
mean = thisMeanEff.PL, |
2563 | ! |
sd = sqrt(trueSigma2) |
2564 |
) |
|
2565 |
} |
|
2566 | ||
2567 |
## if there is no DLT: |
|
2568 | ! |
if (thisDLTs == 0) { |
2569 |
## enroll the remaining patients |
|
2570 | ! |
thisDLTs <- c( |
2571 | ! |
thisDLTs, |
2572 | ! |
rbinom( |
2573 | ! |
n = thisSize - 1L, |
2574 | ! |
size = 1L, |
2575 | ! |
prob = thisDLEProb |
2576 |
) |
|
2577 |
) |
|
2578 | ||
2579 | ! |
thisEff <- c( |
2580 | ! |
thisEff, |
2581 | ! |
rnorm( |
2582 | ! |
n = thisSize - 1L, |
2583 | ! |
mean = thisMeanEff, |
2584 | ! |
sd = sqrt(trueSigma2) |
2585 |
) |
|
2586 |
) |
|
2587 | ||
2588 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2589 | ! |
thisDLTs.PL <- c( |
2590 | ! |
thisDLTs.PL, |
2591 | ! |
rbinom( |
2592 | ! |
n = thisSize.PL, |
2593 | ! |
size = 1L, |
2594 | ! |
prob = thisProb.PL |
2595 |
) |
|
2596 |
) |
|
2597 | ! |
thisEff.PL <- c( |
2598 | ! |
thisMeanEff.PL, |
2599 | ! |
rnorm( |
2600 | ! |
n = thisSize.PL, |
2601 | ! |
mean = thisMeanEff.PL, |
2602 | ! |
sd = sqrt(trueSigma2) |
2603 |
) |
|
2604 |
) |
|
2605 |
} |
|
2606 |
} |
|
2607 |
} else { |
|
2608 |
## we can directly dose all patients |
|
2609 | 12x |
thisDLTs <- rbinom( |
2610 | 12x |
n = thisSize, |
2611 | 12x |
size = 1L, |
2612 | 12x |
prob = thisDLEProb |
2613 |
) |
|
2614 | 12x |
thisEff <- rnorm( |
2615 | 12x |
n = thisSize, |
2616 | 12x |
mean = thisMeanEff, |
2617 | 12x |
sd = sqrt(trueSigma2) |
2618 |
) |
|
2619 | ||
2620 | 12x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2621 | ! |
thisDLTs.PL <- rbinom( |
2622 | ! |
n = thisSize.PL, |
2623 | ! |
size = 1L, |
2624 | ! |
prob = thisProb.PL |
2625 |
) |
|
2626 | ! |
thisEff.PL <- rnorm( |
2627 | ! |
n = thisSize.PL, |
2628 | ! |
mean = thisMeanEff.PL, |
2629 | ! |
sd = sqrt(trueSigma2) |
2630 |
) |
|
2631 |
} |
|
2632 |
} |
|
2633 | ||
2634 |
## update the data with this placebo (if any) cohort and then with active dose |
|
2635 | 12x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
2636 | ! |
thisData <- update( |
2637 | ! |
object = thisData, |
2638 | ! |
x = object@data@doseGrid[1], |
2639 | ! |
y = thisDLTs.PL, |
2640 | ! |
w = thisEff.PL, |
2641 | ! |
check = FALSE |
2642 |
) |
|
2643 | ||
2644 |
## update the data with active dose |
|
2645 | ! |
thisData <- update( |
2646 | ! |
object = thisData, |
2647 | ! |
x = thisDose, |
2648 | ! |
y = thisDLTs, |
2649 | ! |
w = thisEff, |
2650 | ! |
new_cohort = FALSE |
2651 |
) |
|
2652 |
} else { |
|
2653 |
## update the data with this cohort |
|
2654 | 12x |
thisData <- update( |
2655 | 12x |
object = thisData, |
2656 | 12x |
x = thisDose, |
2657 | 12x |
y = thisDLTs, |
2658 | 12x |
w = thisEff |
2659 |
) |
|
2660 |
} |
|
2661 |
## Update model estimate in DLE and Eff models |
|
2662 | 12x |
thisDLEModel <- update( |
2663 | 12x |
object = object@model, |
2664 | 12x |
data = thisData |
2665 |
) |
|
2666 | ||
2667 | 12x |
thisEffModel <- update( |
2668 | 12x |
object = object@eff_model, |
2669 | 12x |
data = thisData |
2670 |
) |
|
2671 | ||
2672 | 12x |
thisNu <- thisEffModel@nu |
2673 | ||
2674 | ||
2675 | 12x |
thisSigma2 <- if (thisEffModel@use_fixed) { |
2676 | ! |
1 / thisNu |
2677 |
} else { |
|
2678 | 12x |
1 / (as.numeric(thisNu["a"] / thisNu["b"])) |
2679 |
} |
|
2680 | ||
2681 | ||
2682 |
## what is the dose limit? |
|
2683 | 12x |
doselimit <- maxDose(object@increments, data = thisData) |
2684 | ||
2685 | ||
2686 | ||
2687 |
## => what is the next best dose? |
|
2688 | 12x |
next_bd <- nextBest(object@nextBest, |
2689 | 12x |
doselimit = doselimit, |
2690 | 12x |
model = thisDLEModel, |
2691 | 12x |
data = thisData, |
2692 | 12x |
model_eff = thisEffModel, |
2693 | 12x |
in_sim = TRUE |
2694 |
) |
|
2695 | ||
2696 | 12x |
thisDose <- next_bd$next_dose |
2697 | 12x |
thisTDtargetDuringTrial <- next_bd$dose_target_drt |
2698 | 12x |
thisTDtargetDuringTrialAtDoseGrid <- next_bd$next_dose_drt |
2699 | 12x |
thisTDtargetEndOfTrial <- next_bd$dose_target_eot |
2700 | 12x |
thisTDtargetEndOfTrialAtDoseGrid <- next_bd$next_dose_eot |
2701 | 12x |
thisGstar <- next_bd$dose_max_gain |
2702 | 12x |
thisGstarAtDoseGrid <- next_bd$next_dose_max_gain |
2703 | ||
2704 | 12x |
Recommend <- min(thisTDtargetEndOfTrialAtDoseGrid, thisGstarAtDoseGrid) |
2705 | ||
2706 |
## Find the 95 % CI and its ratio (upper to the lower of this 95% CI of each of the estimates) |
|
2707 | 12x |
thisCITDEOT <- list(lower = next_bd$ci_dose_target_eot[1], upper = next_bd$ci_dose_target_eot[2]) |
2708 | 12x |
thisratioTDEOT <- next_bd$ci_ratio_dose_target_eot |
2709 | ||
2710 | 12x |
thisCIGstar <- list(lower = next_bd$ci_dose_max_gain[1], upper = next_bd$ci_dose_max_gain[2]) |
2711 | 12x |
thisratioGstar <- next_bd$ci_ratio_dose_max_gain |
2712 | ||
2713 |
## Find the optimal dose |
|
2714 | 12x |
OptimalDose <- min(thisGstar, thisTDtargetEndOfTrial) |
2715 | ||
2716 | 12x |
if (OptimalDose == thisGstar) { |
2717 | ! |
thisratio <- thisratioGstar |
2718 | ! |
thisCI <- thisCIGstar |
2719 |
} else { |
|
2720 | 12x |
thisratio <- thisratioTDEOT |
2721 | 12x |
thisCI <- thisCITDEOT |
2722 |
} |
|
2723 | ||
2724 |
## evaluate stopping rules |
|
2725 | 12x |
stopit <- stopTrial(object@stopping, |
2726 | 12x |
dose = thisDose, |
2727 | 12x |
model = thisDLEModel, |
2728 | 12x |
data = thisData, |
2729 | 12x |
Effmodel = thisEffModel |
2730 |
) |
|
2731 | 12x |
stopit_results <- h_unpack_stopit(stopit) |
2732 |
} |
|
2733 | ||
2734 |
## get the fits |
|
2735 | 1x |
prob_fun <- probFunction(thisDLEModel, phi1 = thisDLEModel@phi1, phi2 = thisDLEModel@phi2) |
2736 | 1x |
thisDLEFit <- list( |
2737 | 1x |
phi1 = thisDLEModel@phi1, |
2738 | 1x |
phi2 = thisDLEModel@phi2, |
2739 | 1x |
probDLE = prob_fun(object@data@doseGrid) |
2740 |
) |
|
2741 | ||
2742 | 1x |
eff_fun <- efficacyFunction(thisEffModel, theta1 = thisEffModel@theta1, theta2 = thisEffModel@theta2) |
2743 | 1x |
thisEffFit <- list( |
2744 | 1x |
theta1 = thisEffModel@theta1, |
2745 | 1x |
theta2 = thisEffModel@theta2, |
2746 | 1x |
ExpEff = eff_fun(object@data@doseGrid) |
2747 |
) |
|
2748 | ||
2749 | ||
2750 |
## return the results |
|
2751 | 1x |
thisResult <- list( |
2752 | 1x |
data = thisData, |
2753 | 1x |
dose = thisDose, |
2754 | 1x |
TDtargetDuringTrial = thisTDtargetDuringTrial, |
2755 | 1x |
TDtargetDuringTrialAtDoseGrid = thisTDtargetDuringTrialAtDoseGrid, |
2756 | 1x |
TDtargetEndOfTrial = thisTDtargetEndOfTrial, |
2757 | 1x |
TDtargetEndOfTrialAtDoseGrid = thisTDtargetEndOfTrialAtDoseGrid, |
2758 | 1x |
Gstar = thisGstar, |
2759 | 1x |
GstarAtDoseGrid = thisGstarAtDoseGrid, |
2760 | 1x |
Recommend = Recommend, |
2761 | 1x |
OptimalDose = OptimalDose, |
2762 | 1x |
OptimalDoseAtDoseGrid = Recommend, |
2763 | 1x |
ratio = thisratio, |
2764 | 1x |
CI = thisCI, |
2765 | 1x |
ratioGstar = thisratioGstar, |
2766 | 1x |
CIGstar = thisCIGstar, |
2767 | 1x |
ratioTDEOT = thisratioTDEOT, |
2768 | 1x |
CITDEOT = thisCITDEOT, |
2769 | 1x |
fitDLE = thisDLEFit, |
2770 | 1x |
fitEff = thisEffFit, |
2771 | 1x |
sigma2est = thisSigma2, |
2772 | 1x |
stop = attr( |
2773 | 1x |
stopit, |
2774 | 1x |
"message" |
2775 |
), |
|
2776 | 1x |
report_results = stopit_results |
2777 |
) |
|
2778 | ||
2779 | 1x |
return(thisResult) |
2780 |
} |
|
2781 | ||
2782 | ||
2783 | 1x |
resultList <- get_result_list( |
2784 | 1x |
fun = runSim, |
2785 | 1x |
nsim = nsim, |
2786 | 1x |
vars = |
2787 | 1x |
c( |
2788 | 1x |
"simSeeds", |
2789 | 1x |
"args", |
2790 | 1x |
"nArgs", |
2791 | 1x |
"firstSeparate", |
2792 | 1x |
"trueDLE", |
2793 | 1x |
"trueEff", |
2794 | 1x |
"trueNu", |
2795 | 1x |
"object" |
2796 |
), |
|
2797 | 1x |
parallel = parallel, |
2798 | 1x |
n_cores = nCores |
2799 |
) |
|
2800 | ||
2801 | ||
2802 |
## put everything in the Simulations format: |
|
2803 | ||
2804 |
## setup the list for the simulated data objects |
|
2805 | 1x |
dataList <- lapply(resultList, "[[", "data") |
2806 | ||
2807 |
## the vector of the final dose recommendations |
|
2808 | 1x |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "Recommend")) |
2809 | ||
2810 | ||
2811 |
## set up list for the final TD during Trial Estimate |
|
2812 | 1x |
TDtargetDuringTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrial")) |
2813 | ||
2814 |
## set up list for the final TD End of Trial Estimate |
|
2815 | 1x |
TDtargetEndOfTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrial")) |
2816 | ||
2817 |
## set up list for the final TD during Trial estimate at dose Grid |
|
2818 | 1x |
TDtargetDuringTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrialAtDoseGrid")) |
2819 | ||
2820 |
## set up list for the final TD End Of Trial estimate at dose Grid |
|
2821 | 1x |
TDtargetEndOfTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrialAtDoseGrid")) |
2822 | ||
2823 |
## set up list for the final Gstar estimates |
|
2824 | 1x |
GstarList <- as.numeric(sapply(resultList, "[[", "Gstar")) |
2825 | ||
2826 |
## set up list for the final Gstar estimates at dose grid |
|
2827 | 1x |
GstarAtDoseGridList <- as.numeric(sapply(resultList, "[[", "GstarAtDoseGrid")) |
2828 | ||
2829 |
## set up list for final optimal dose estimates |
|
2830 | 1x |
OptimalDoseList <- as.numeric(sapply(resultList, "[[", "OptimalDose")) |
2831 | ||
2832 |
## set up list for final optimal dose estimates at dose Grid |
|
2833 | 1x |
OptimalDoseAtDoseGridList <- as.numeric(sapply(resultList, "[[", "Recommend")) |
2834 | ||
2835 |
## Set up the list for the final 95% CI obtained |
|
2836 | 1x |
CIList <- lapply(resultList, "[[", "CI") |
2837 | ||
2838 |
## Set up the list for the final ratios obtained |
|
2839 | 1x |
ratioList <- as.numeric(sapply(resultList, "[[", "ratio")) |
2840 | ||
2841 |
## Set up the list for the final 95% CI of the TDtarget End Of Trial obtained |
|
2842 | 1x |
CITDEOTList <- lapply(resultList, "[[", "CITDEOT") |
2843 | ||
2844 |
## Set up the list for the final ratios of the TDtarget End Of Trial obtained |
|
2845 | 1x |
ratioTDEOTList <- as.numeric(sapply(resultList, "[[", "ratioTDEOT")) |
2846 | ||
2847 |
## Set up the list for the final 95% CI of the Gstar obtained |
|
2848 | 1x |
CIGstarList <- lapply(resultList, "[[", "CIGstar") |
2849 | ||
2850 |
## Set up the list for the final ratios of the Gstar obtained |
|
2851 | 1x |
ratioGstarList <- as.numeric(sapply(resultList, "[[", "ratioGstar")) |
2852 | ||
2853 | ||
2854 | ||
2855 |
## set up the list for the final fits |
|
2856 | 1x |
fitDLEList <- lapply(resultList, "[[", "fitDLE") |
2857 | 1x |
fitEffList <- lapply(resultList, "[[", "fitEff") |
2858 | ||
2859 | ||
2860 |
## the vector of the sigma2 |
|
2861 | 1x |
sigma2Estimates <- as.numeric(sapply(resultList, "[[", "sigma2est")) |
2862 | ||
2863 |
## the reasons for stopping |
|
2864 | 1x |
stopReasons <- lapply(resultList, "[[", "stop") |
2865 | ||
2866 |
# individual stopping rule results as matrix, labels as column names |
|
2867 | 1x |
stop_results <- lapply(resultList, "[[", "report_results") |
2868 | 1x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
2869 | ||
2870 | ||
2871 |
## return the results in the Simulations class object |
|
2872 | 1x |
ret <- PseudoDualSimulations( |
2873 | 1x |
data = dataList, |
2874 | 1x |
doses = recommendedDoses, |
2875 | 1x |
final_td_target_during_trial_estimates = TDtargetDuringTrialList, |
2876 | 1x |
final_td_target_end_of_trial_estimates = TDtargetEndOfTrialList, |
2877 | 1x |
final_td_target_during_trial_at_dose_grid = TDtargetDuringTrialDoseGridList, |
2878 | 1x |
final_td_target_end_of_trial_at_dose_grid = TDtargetEndOfTrialDoseGridList, |
2879 | 1x |
final_cis = CIList, |
2880 | 1x |
final_ratios = ratioList, |
2881 | 1x |
final_gstar_estimates = GstarList, |
2882 | 1x |
final_gstar_at_dose_grid = GstarAtDoseGridList, |
2883 | 1x |
final_gstar_cis = CIGstarList, |
2884 | 1x |
final_gstar_ratios = ratioGstarList, |
2885 | 1x |
final_tdeot_cis = CITDEOTList, |
2886 | 1x |
final_tdeot_ratios = ratioTDEOTList, |
2887 | 1x |
final_optimal_dose = OptimalDoseList, |
2888 | 1x |
final_optimal_dose_at_dose_grid = OptimalDoseAtDoseGridList, |
2889 | 1x |
fit = fitDLEList, |
2890 | 1x |
fit_eff = fitEffList, |
2891 | 1x |
sigma2_est = sigma2Estimates, |
2892 | 1x |
stop_reasons = stopReasons, |
2893 | 1x |
stop_report = stop_report, |
2894 | 1x |
seed = RNGstate |
2895 |
) |
|
2896 | 1x |
return(ret) |
2897 |
} |
|
2898 |
) |
|
2899 | ||
2900 |
## ========================================================================= |
|
2901 |
## ----------------------------------------------------------------------------------------------- |
|
2902 |
## Simulate design using DLE and efficacy responses with DLE and efficacy samples (pseudo models) |
|
2903 |
## -------------------------------------------------------------------------------------------- |
|
2904 |
### |
|
2905 |
##' This is a methods to simulate dose escalation procedure using both DLE and efficacy responses. |
|
2906 |
##' This is a method based on the \code{\linkS4class{DualResponsesSamplesDesign}} where DLEmodel |
|
2907 |
##' used are of |
|
2908 |
##' \code{\linkS4class{ModelTox}} class object and efficacy model used are of |
|
2909 |
##' \code{\linkS4class{ModelEff}} |
|
2910 |
##' class object (special case is \code{\linkS4class{EffFlexi}} class model object). |
|
2911 |
##' In addition, DLE and efficacy samples are involved or generated in the simulation |
|
2912 |
##' process |
|
2913 |
##' |
|
2914 |
##' @param object the \code{\linkS4class{DualResponsesSamplesDesign}} object we want to |
|
2915 |
##' simulate the data from |
|
2916 |
##' @param nsim the number of simulations (default :1) |
|
2917 |
##' @param seed see \code{\link{set_seed}} |
|
2918 |
##' @param trueDLE a function which takes as input a dose (vector) and returns the true probability |
|
2919 |
##' (vector) of the occurrence of a DLE. Additional arguments can be supplied in \code{args}. |
|
2920 |
##' @param trueEff a function which takes as input a dose (vector) and returns the expected |
|
2921 |
##' efficacy responses (vector). Additional arguments can be supplied in \code{args}. |
|
2922 |
##' @param trueNu (not with code{\linkS4class{EffFlexi}}) the precision, the inverse of the |
|
2923 |
##' variance of the efficacy responses |
|
2924 |
##' @param trueSigma2 (only with code{\linkS4class{EffFlexi}}) the true variance of the efficacy |
|
2925 |
##' responses which must be a single positive scalar. |
|
2926 |
##' @param trueSigma2betaW (only with code{\linkS4class{EffFlexi}}) the true variance for the |
|
2927 |
##' random walk model used for smoothing. This must be a single positive scalar. |
|
2928 |
##' @param args data frame with arguments for the \code{trueDLE} and |
|
2929 |
##' \code{trueEff} function. The column names correspond to the argument |
|
2930 |
##' names, the rows to the values of the arguments. The rows are appropriately |
|
2931 |
##' recycled in the \code{nsim} simulations. |
|
2932 |
##' @param firstSeparate enroll the first patient separately from the rest of |
|
2933 |
##' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
2934 |
##' in this patient. |
|
2935 |
##' @param mcmcOptions object of class \code{\linkS4class{McmcOptions}}, |
|
2936 |
##' giving the MCMC options for each evaluation in the trial. By default, |
|
2937 |
##' the standard options are used |
|
2938 |
##' @param parallel should the simulation runs be parallelized across the |
|
2939 |
##' clusters of the computer? (not default) |
|
2940 |
##' @param nCores how many cores should be used for parallel computing? |
|
2941 |
##' Defaults to the number of cores on the machine, maximum 5. |
|
2942 |
##' |
|
2943 |
##' @param ... not used. |
|
2944 |
##' |
|
2945 |
##' @example examples/design-method-simulateDualResponsesSamplesDesign.R |
|
2946 |
##' |
|
2947 |
##' @return an object of class \code{\linkS4class{PseudoDualSimulations}} or |
|
2948 |
##' \code{\linkS4class{PseudoDualFlexiSimulations}} |
|
2949 |
##' |
|
2950 |
##' @export |
|
2951 |
##' @keywords methods |
|
2952 |
setMethod("simulate", |
|
2953 |
signature = |
|
2954 |
signature( |
|
2955 |
object = "DualResponsesSamplesDesign", |
|
2956 |
nsim = "ANY", |
|
2957 |
seed = "ANY" |
|
2958 |
), |
|
2959 |
def = |
|
2960 |
function(object, nsim = 1L, seed = NULL, |
|
2961 |
trueDLE, trueEff, trueNu = NULL, |
|
2962 |
trueSigma2 = NULL, trueSigma2betaW = NULL, |
|
2963 |
args = NULL, firstSeparate = FALSE, |
|
2964 |
mcmcOptions = McmcOptions(), |
|
2965 |
parallel = FALSE, nCores = |
|
2966 |
min(parallel::detectCores(), 5L), |
|
2967 |
...) { |
|
2968 |
## common checks and extracts |
|
2969 | 1x |
assert_function(trueDLE) |
2970 | 1x |
assert_flag(firstSeparate) |
2971 | 1x |
assert_count(nsim, positive = TRUE) |
2972 | 1x |
assert_flag(parallel) |
2973 | 1x |
assert_count(nCores, positive = TRUE) |
2974 | ||
2975 |
## check if special case applies |
|
2976 | 1x |
isFlexi <- is(object@eff_model, "EffFlexi") |
2977 | ||
2978 |
## conditional code from here on: |
|
2979 | 1x |
if (isFlexi) { |
2980 |
## special checks and extracts |
|
2981 | ! |
stopifnot( |
2982 | ! |
trueSigma2 > 0, |
2983 | ! |
trueSigma2betaW > 0, |
2984 | ! |
is.numeric(trueEff), |
2985 | ! |
length(trueEff) == length(object@data@doseGrid) |
2986 |
) |
|
2987 | ||
2988 | ! |
args <- as.data.frame(args) |
2989 | ! |
nArgs <- max(nrow(args), 1L) |
2990 | ||
2991 |
## get names of arguments (excluding the first one which is the dose) |
|
2992 | ! |
trueDLEArgnames <- names(formals(trueDLE))[-1] |
2993 | ||
2994 |
## seed handling |
|
2995 | ! |
RNGstate <- set_seed(seed) |
2996 | ||
2997 |
## from this, |
|
2998 |
## generate the individual seeds for the simulation runs |
|
2999 | ! |
simSeeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
3000 | ||
3001 |
## the function to produce the run a single simulation |
|
3002 |
## with index "iterSim" |
|
3003 | ! |
runSim <- function(iterSim) { |
3004 |
## set the seed for this run |
|
3005 | ! |
set.seed(simSeeds[iterSim]) |
3006 | ||
3007 |
## what is now the argument for the truth? |
|
3008 |
## (appropriately recycled) |
|
3009 | ! |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
3010 | ||
3011 |
## so this truth is... |
|
3012 | ! |
thisTruthDLE <- function(dose) { |
3013 | ! |
do.call( |
3014 | ! |
trueDLE, |
3015 |
## First argument: the dose |
|
3016 | ! |
c( |
3017 | ! |
dose, |
3018 |
## Following arguments |
|
3019 | ! |
thisArgs |
3020 |
) |
|
3021 |
) |
|
3022 |
} |
|
3023 | ||
3024 |
## get the true Eff |
|
3025 | ! |
thisTruthEff <- trueEff |
3026 | ||
3027 |
## start the simulated data with the provided one |
|
3028 | ! |
thisData <- object@data |
3029 | ||
3030 |
## shall we stop the trial? |
|
3031 |
## First, we want to continue with the starting dose. |
|
3032 |
## This variable is updated after each cohort in the loop. |
|
3033 | ! |
stopit <- FALSE |
3034 | ||
3035 |
## what is the next dose to be used? |
|
3036 |
## initialize with starting dose |
|
3037 | ! |
thisDose <- object@startingDose |
3038 | ||
3039 |
## Start with specified sigma2 and sigma2betaW |
|
3040 | ! |
thisSigma2 <- trueSigma2 |
3041 | ! |
thisSigma2betaW <- trueSigma2betaW |
3042 | ||
3043 | ||
3044 |
## inside this loop we simulate the whole trial, until stopping |
|
3045 | ! |
while (!stopit) { |
3046 |
## what is the probability for tox. at this dose? |
|
3047 | ! |
thisDLEProb <- thisTruthDLE(thisDose) |
3048 | ! |
thisDoseIndex <- which(thisDose == thisData@doseGrid) |
3049 | ! |
thisMeanEff <- thisTruthEff[thisDoseIndex] |
3050 | ||
3051 | ||
3052 | ||
3053 |
## what is the cohort size at this dose? |
|
3054 | ! |
thisSize <- size(object@cohort_size, |
3055 | ! |
dose = thisDose, |
3056 | ! |
data = thisData |
3057 |
) |
|
3058 | ||
3059 | ! |
if (thisData@placebo) { |
3060 | ! |
thisSize.PL <- size(object@pl_cohort_size, |
3061 | ! |
dose = thisDose, |
3062 | ! |
data = thisData |
3063 |
) |
|
3064 |
} |
|
3065 | ||
3066 |
## simulate DLTs: depends on whether we |
|
3067 |
## separate the first patient or not. |
|
3068 | ! |
if (firstSeparate && (thisSize > 1L)) { |
3069 |
## dose the first patient |
|
3070 | ! |
thisDLTs <- rbinom( |
3071 | ! |
n = 1L, |
3072 | ! |
size = 1L, |
3073 | ! |
prob = thisDLEProb |
3074 |
) |
|
3075 | ||
3076 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3077 | ! |
thisDLTs.PL <- rbinom( |
3078 | ! |
n = 1L, |
3079 | ! |
size = 1L, |
3080 | ! |
prob = thisProb.PL |
3081 |
) |
|
3082 |
} |
|
3083 | ||
3084 | ! |
thisEff <- rnorm( |
3085 | ! |
n = 1L, |
3086 | ! |
mean = thisMeanEff, |
3087 | ! |
sd = sqrt(trueSigma2) |
3088 |
) |
|
3089 | ||
3090 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3091 | ! |
thisEff.PL <- rnorm( |
3092 | ! |
n = 1L, |
3093 | ! |
mean = thisMeanEff.PL, |
3094 | ! |
sd = sqrt(trueSigma2) |
3095 |
) |
|
3096 |
} |
|
3097 | ||
3098 |
## if there is no DLT: |
|
3099 | ! |
if (thisDLTs == 0) { |
3100 |
## enroll the remaining patients |
|
3101 | ! |
thisDLTs <- c( |
3102 | ! |
thisDLTs, |
3103 | ! |
rbinom( |
3104 | ! |
n = thisSize - 1L, |
3105 | ! |
size = 1L, |
3106 | ! |
prob = thisDLEProb |
3107 |
) |
|
3108 |
) |
|
3109 | ! |
thisEff <- c( |
3110 | ! |
thisEff, |
3111 | ! |
rnorm( |
3112 | ! |
n = thisSize - 1L, |
3113 | ! |
mean = thisMeanEff, |
3114 | ! |
sd = sqrt(trueSigma2) |
3115 |
) |
|
3116 |
) |
|
3117 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3118 | ! |
thisDLTs.PL <- c( |
3119 | ! |
thisDLTs.PL, |
3120 | ! |
rbinom( |
3121 | ! |
n = thisSize.PL, |
3122 | ! |
size = 1L, |
3123 | ! |
prob = thisProb.PL |
3124 |
) |
|
3125 |
) |
|
3126 | ! |
thisEff.PL <- c( |
3127 | ! |
thisMeanEff.PL, |
3128 | ! |
rnorm( |
3129 | ! |
n = thisSize.PL, |
3130 | ! |
mean = thisMeanEff.PL, |
3131 | ! |
sd = sqrt(trueSigma2) |
3132 |
) |
|
3133 |
) |
|
3134 |
} |
|
3135 |
} |
|
3136 |
} else { |
|
3137 |
## we can directly dose all patients |
|
3138 | ! |
thisDLTs <- rbinom( |
3139 | ! |
n = thisSize, |
3140 | ! |
size = 1L, |
3141 | ! |
prob = thisDLEProb |
3142 |
) |
|
3143 | ||
3144 | ! |
thisEff <- rnorm( |
3145 | ! |
n = thisSize, |
3146 | ! |
mean = thisMeanEff, |
3147 | ! |
sd = sqrt(trueSigma2) |
3148 |
) |
|
3149 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3150 | ! |
thisDLTs.PL <- rbinom( |
3151 | ! |
n = thisSize.PL, |
3152 | ! |
size = 1L, |
3153 | ! |
prob = thisProb.PL |
3154 |
) |
|
3155 | ! |
thisEff.PL <- rnorm( |
3156 | ! |
n = thisSize.PL, |
3157 | ! |
mean = thisMeanEff.PL, |
3158 | ! |
sd = sqrt(trueSigma2) |
3159 |
) |
|
3160 |
} |
|
3161 |
} |
|
3162 | ||
3163 |
## update the data with this placebo (if any) cohort and then with active dose |
|
3164 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3165 | ! |
thisData <- update( |
3166 | ! |
object = thisData, |
3167 | ! |
x = object@data@doseGrid[1], |
3168 | ! |
y = thisDLTs.PL, |
3169 | ! |
w = thisEff.PL, |
3170 | ! |
check = FALSE |
3171 |
) |
|
3172 | ||
3173 |
## update the data with active dose |
|
3174 | ! |
thisData <- update( |
3175 | ! |
object = thisData, |
3176 | ! |
x = thisDose, |
3177 | ! |
y = thisDLTs, |
3178 | ! |
w = thisEff, |
3179 | ! |
new_cohort = FALSE |
3180 |
) |
|
3181 |
} else { |
|
3182 |
## update the data with this cohort |
|
3183 | ! |
thisData <- update( |
3184 | ! |
object = thisData, |
3185 | ! |
x = thisDose, |
3186 | ! |
y = thisDLTs, |
3187 | ! |
w = thisEff |
3188 |
) |
|
3189 |
} |
|
3190 | ||
3191 |
## Update model estimate in DLE model |
|
3192 | ! |
thisDLEModel <- update( |
3193 | ! |
object = object@model, |
3194 | ! |
data = thisData |
3195 |
) |
|
3196 | ||
3197 | ! |
thisEffModel <- update( |
3198 | ! |
object = object@eff_model, |
3199 | ! |
data = thisData |
3200 |
) |
|
3201 | ||
3202 | ||
3203 |
## what is the dose limit? |
|
3204 | ! |
doselimit <- maxDose(object@increments, |
3205 | ! |
data = thisData |
3206 |
) |
|
3207 | ||
3208 |
## generate DLE and Eff samples from the DLE and Eff model |
|
3209 | ! |
thisDLEsamples <- mcmc( |
3210 | ! |
data = thisData, |
3211 | ! |
model = thisDLEModel, |
3212 | ! |
options = mcmcOptions |
3213 |
) |
|
3214 | ||
3215 | ! |
thisEffsamples <- mcmc( |
3216 | ! |
data = thisData, |
3217 | ! |
model = thisEffModel, |
3218 | ! |
options = mcmcOptions |
3219 |
) |
|
3220 | ||
3221 | ! |
thisSigma2 <- mean(thisEffsamples@data$sigma2W) |
3222 | ||
3223 | ! |
thisSigma2betaW <- mean(thisEffsamples@data$sigma2betaW) |
3224 | ||
3225 |
## => what is the next best dose? |
|
3226 | ||
3227 | ! |
next_bd <- nextBest(object@nextBest, |
3228 | ! |
doselimit = doselimit, |
3229 | ! |
samples = thisDLEsamples, |
3230 | ! |
model = thisDLEModel, |
3231 | ! |
model_eff = thisEffModel, |
3232 | ! |
samples_eff = thisEffsamples, |
3233 | ! |
data = thisData, |
3234 | ! |
in_sim = TRUE |
3235 |
) |
|
3236 | ||
3237 | ! |
thisDose <- next_bd$next_dose |
3238 | ! |
thisTDtargetDuringTrial <- next_bd$dose_target_drt |
3239 | ! |
thisTDtargetDuringTrialAtDoseGrid <- next_bd$next_dose_drt |
3240 | ! |
thisTDtargetEndOfTrial <- next_bd$dose_target_eot |
3241 | ! |
thisTDtargetEndOfTrialAtDoseGrid <- next_bd$next_dose_eot |
3242 | ! |
thisGstar <- next_bd$dose_max_gain |
3243 | ! |
thisGstarAtDoseGrid <- next_bd$next_dose_max_gain |
3244 | ||
3245 | ! |
Recommend <- min(thisTDtargetEndOfTrialAtDoseGrid, thisGstarAtDoseGrid) |
3246 | ||
3247 |
## Find the 95 % CI and its ratio (upper to the lower of this 95% CI of each of the estimates) |
|
3248 | ! |
thisCITDEOT <- list(lower = next_bd$ci_dose_target_eot[1], upper = next_bd$ci_dose_target_eot[2]) |
3249 | ! |
thisratioTDEOT <- next_bd$ci_ratio_dose_target_eot |
3250 | ||
3251 | ! |
thisCIGstar <- list(lower = next_bd$ci_dose_max_gain[1], upper = next_bd$ci_dose_max_gain[2]) |
3252 | ! |
thisratioGstar <- next_bd$ci_ratio_dose_max_gain |
3253 | ||
3254 |
## Find the optimal dose |
|
3255 | ! |
OptimalDose <- min(thisGstar, thisTDtargetEndOfTrial) |
3256 | ||
3257 | ! |
if (OptimalDose == thisGstar) { |
3258 | ! |
thisratio <- thisratioGstar |
3259 | ! |
thisCI <- thisCIGstar |
3260 |
} else { |
|
3261 | ! |
thisratio <- thisratioTDEOT |
3262 | ! |
thisCI <- thisCITDEOT |
3263 |
} |
|
3264 | ||
3265 |
## evaluate stopping rules |
|
3266 | ! |
stopit <- stopTrial(object@stopping, |
3267 | ! |
dose = thisDose, |
3268 | ! |
samples = thisDLEsamples, |
3269 | ! |
model = thisDLEModel, |
3270 | ! |
data = thisData, |
3271 | ! |
TDderive = object@nextBest@derive, |
3272 | ! |
Effmodel = thisEffModel, |
3273 | ! |
Effsamples = thisEffsamples, |
3274 | ! |
Gstarderive = object@nextBest@mg_derive |
3275 |
) |
|
3276 | ! |
stopit_results <- h_unpack_stopit(stopit) |
3277 |
} |
|
3278 | ||
3279 |
## get the fits |
|
3280 | ||
3281 | ! |
thisDLEFit <- fit( |
3282 | ! |
object = thisDLEsamples, |
3283 | ! |
model = thisDLEModel, |
3284 | ! |
data = thisData |
3285 |
) |
|
3286 | ||
3287 | ! |
thisEffFit <- fit( |
3288 | ! |
object = thisEffsamples, |
3289 | ! |
model = thisEffModel, |
3290 | ! |
data = thisData |
3291 |
) |
|
3292 | ||
3293 | ||
3294 |
## return the results |
|
3295 | ! |
thisResult <- |
3296 | ! |
list( |
3297 | ! |
data = thisData, |
3298 | ! |
dose = thisDose, |
3299 | ! |
TDtargetDuringTrial = thisTDtargetDuringTrial, |
3300 | ! |
TDtargetDuringTrialAtDoseGrid = thisTDtargetDuringTrialAtDoseGrid, |
3301 | ! |
TDtargetEndOfTrial = thisTDtargetEndOfTrial, |
3302 | ! |
TDtargetEndOfTrialAtDoseGrid = thisTDtargetEndOfTrialAtDoseGrid, |
3303 | ! |
Gstar = thisGstar, |
3304 | ! |
GstarAtDoseGrid = thisGstarAtDoseGrid, |
3305 | ! |
Recommend = Recommend, |
3306 | ! |
OptimalDose = OptimalDose, |
3307 | ! |
OptimalDoseAtDoseGrid = Recommend, |
3308 | ! |
ratio = thisratio, |
3309 | ! |
CI = thisCI, |
3310 | ! |
ratioGstar = thisratioGstar, |
3311 | ! |
CIGstar = thisCIGstar, |
3312 | ! |
ratioTDEOT = thisratioTDEOT, |
3313 | ! |
CITDEOT = thisCITDEOT, |
3314 | ! |
fitDLE = subset(thisDLEFit, |
3315 | ! |
select = |
3316 | ! |
c(middle, lower, upper) |
3317 |
), |
|
3318 | ! |
fitEff = subset(thisEffFit, |
3319 | ! |
select = |
3320 | ! |
c(middle, lower, upper) |
3321 |
), |
|
3322 | ! |
sigma2est = thisSigma2, |
3323 | ! |
sigma2betaWest = thisSigma2betaW, |
3324 | ! |
stop = |
3325 | ! |
attr( |
3326 | ! |
stopit, |
3327 | ! |
"message" |
3328 |
), |
|
3329 | ! |
report_results = stopit_results |
3330 |
) |
|
3331 | ||
3332 | ! |
return(thisResult) |
3333 |
} |
|
3334 | ||
3335 | ! |
resultList <- get_result_list( |
3336 | ! |
fun = runSim, |
3337 | ! |
nsim = nsim, |
3338 | ! |
vars = |
3339 | ! |
c( |
3340 | ! |
"simSeeds", |
3341 | ! |
"args", |
3342 | ! |
"nArgs", |
3343 | ! |
"firstSeparate", |
3344 | ! |
"trueDLE", |
3345 | ! |
"trueEff", |
3346 | ! |
"trueSigma2", |
3347 | ! |
"trueSigma2betaW", |
3348 | ! |
"object", |
3349 | ! |
"mcmcOptions" |
3350 |
), |
|
3351 | ! |
parallel = parallel, |
3352 | ! |
n_cores = nCores |
3353 |
) |
|
3354 | ||
3355 |
## put everything in the Simulations format: |
|
3356 | ||
3357 |
## setup the list for the simulated data objects |
|
3358 | ! |
dataList <- lapply(resultList, "[[", "data") |
3359 | ||
3360 |
## the vector of the final dose recommendations |
|
3361 | ! |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "Recommend")) |
3362 | ||
3363 | ||
3364 |
## set up the list for the final fits for both DLE and efficacy |
|
3365 | ! |
fitDLEList <- lapply(resultList, "[[", "fitDLE") |
3366 | ! |
fitEffList <- lapply(resultList, "[[", "fitEff") |
3367 | ||
3368 |
## the vector of sigma2 estimates |
|
3369 | ! |
sigma2Estimates <- as.numeric(sapply(resultList, "[[", "sigma2est")) |
3370 | ||
3371 |
## the vector of sigma2betaW estimates |
|
3372 | ! |
sigma2betaWEstimates <- as.numeric(sapply(resultList, "[[", "sigma2betaWest")) |
3373 | ||
3374 |
## set up list for the final TD during Trial Estimate |
|
3375 | ! |
TDtargetDuringTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrial")) |
3376 | ||
3377 |
## set up list for the final TD End of Trial Estimate |
|
3378 | ! |
TDtargetEndOfTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrial")) |
3379 | ||
3380 |
## set up list for the final TD during Trial estimate at dose Grid |
|
3381 | ! |
TDtargetDuringTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrialAtDoseGrid")) |
3382 | ||
3383 |
## set up list for the final TD End Of Trial estimate at dose Grid |
|
3384 | ! |
TDtargetEndOfTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrialAtDoseGrid")) |
3385 | ||
3386 |
## set up list for the final Gstar estimates |
|
3387 | ! |
GstarList <- as.numeric(sapply(resultList, "[[", "Gstar")) |
3388 | ||
3389 |
## set up list for the final Gstar estimates at dose grid |
|
3390 | ! |
GstarAtDoseGridList <- as.numeric(sapply(resultList, "[[", "GstarAtDoseGrid")) |
3391 | ||
3392 |
## set up list for final optimal dose estimates |
|
3393 | ! |
OptimalDoseList <- as.numeric(sapply(resultList, "[[", "OptimalDose")) |
3394 | ||
3395 |
## set up list for final optimal dose estimates at dose Grid |
|
3396 | ! |
OptimalDoseAtDoseGridList <- as.numeric(sapply(resultList, "[[", "Recommend")) |
3397 | ||
3398 |
## Set up the list for the final 95% CI obtained |
|
3399 | ! |
CIList <- lapply(resultList, "[[", "CI") |
3400 | ||
3401 |
## Set up the list for the final ratios obtained |
|
3402 | ! |
ratioList <- as.numeric(sapply(resultList, "[[", "ratio")) |
3403 | ||
3404 |
## Set up the list for the final 95% CI of the TDtarget End Of Trial obtained |
|
3405 | ! |
CITDEOTList <- lapply(resultList, "[[", "CITDEOT") |
3406 | ||
3407 |
## Set up the list for the final ratios of the TDtarget End Of Trial obtained |
|
3408 | ! |
ratioTDEOTList <- as.numeric(sapply(resultList, "[[", "ratioTDEOT")) |
3409 | ||
3410 |
## Set up the list for the final 95% CI of the Gstar obtained |
|
3411 | ! |
CIGstarList <- lapply(resultList, "[[", "CIGstar") |
3412 | ||
3413 |
## Set up the list for the final ratios of the Gstar obtained |
|
3414 | ! |
ratioGstarList <- as.numeric(sapply(resultList, "[[", "ratioGstar")) |
3415 | ||
3416 |
## the reasons for stopping |
|
3417 | ! |
stopReasons <- lapply(resultList, "[[", "stop") |
3418 | ||
3419 |
# individual stopping rule results as matrix, labels as column names |
|
3420 | ! |
stop_results <- lapply(resultList, "[[", "report_results") |
3421 | ! |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
3422 | ||
3423 | ||
3424 |
## return the results in the Simulations class object |
|
3425 | ! |
ret <- PseudoDualFlexiSimulations( |
3426 | ! |
data = dataList, |
3427 | ! |
doses = recommendedDoses, |
3428 | ! |
final_td_target_during_trial_estimates = TDtargetDuringTrialList, |
3429 | ! |
final_td_target_end_of_trial_estimates = TDtargetEndOfTrialList, |
3430 | ! |
final_td_target_during_trial_at_dose_grid = TDtargetDuringTrialDoseGridList, |
3431 | ! |
final_td_target_end_of_trial_at_dose_grid = TDtargetEndOfTrialDoseGridList, |
3432 | ! |
final_cis = CIList, |
3433 | ! |
final_ratios = ratioList, |
3434 | ! |
final_gstar_estimates = GstarList, |
3435 | ! |
final_gstar_at_dose_grid = GstarAtDoseGridList, |
3436 | ! |
final_gstar_cis = CIGstarList, |
3437 | ! |
final_gstar_ratios = ratioGstarList, |
3438 | ! |
final_tdeot_cis = CITDEOTList, |
3439 | ! |
final_tdeot_ratios = ratioTDEOTList, |
3440 | ! |
final_optimal_dose = OptimalDoseList, |
3441 | ! |
final_optimal_dose_at_dose_grid = OptimalDoseAtDoseGridList, |
3442 | ! |
fit = fitDLEList, |
3443 | ! |
fit_eff = fitEffList, |
3444 | ! |
sigma2_est = sigma2Estimates, |
3445 | ! |
sigma2betaWest = sigma2betaWEstimates, |
3446 | ! |
stop_reasons = stopReasons, |
3447 | ! |
stop_report = stop_report, |
3448 | ! |
seed = RNGstate |
3449 |
) |
|
3450 | ||
3451 | ! |
return(ret) |
3452 |
} else { |
|
3453 | 1x |
stopifnot( |
3454 | 1x |
trueNu > 0, |
3455 | 1x |
is.function(trueEff) |
3456 |
) |
|
3457 | ||
3458 | ||
3459 | 1x |
args <- as.data.frame(args) |
3460 | 1x |
nArgs <- max(nrow(args), 1L) |
3461 | ||
3462 |
## get names of arguments (excluding the first one which is the dose) |
|
3463 | 1x |
trueDLEArgnames <- names(formals(trueDLE))[-1] |
3464 | 1x |
trueEffArgnames <- names(formals(trueEff))[-1] |
3465 | ||
3466 | ||
3467 | ||
3468 |
## seed handling |
|
3469 | 1x |
RNGstate <- set_seed(seed) |
3470 | ||
3471 |
## from this, |
|
3472 |
## generate the individual seeds for the simulation runs |
|
3473 | 1x |
simSeeds <- sample(x = seq_len(1e5), size = nsim) |
3474 | ||
3475 |
## the function to produce the run a single simulation |
|
3476 |
## with index "iterSim" |
|
3477 | 1x |
runSim <- function(iterSim) { |
3478 |
## set the seed for this run |
|
3479 | 1x |
set.seed(simSeeds[iterSim]) |
3480 | ||
3481 |
## what is now the argument for the truth? |
|
3482 |
## (appropriately recycled) |
|
3483 | 1x |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
3484 | ||
3485 |
## so this truth DLE function is... |
|
3486 | 1x |
thisTruthDLE <- function(dose) { |
3487 | 4x |
do.call( |
3488 | 4x |
trueDLE, |
3489 |
## First argument: the dose |
|
3490 | 4x |
c( |
3491 | 4x |
dose, |
3492 |
## Following arguments: take only those that |
|
3493 |
## are required by the DLE function |
|
3494 | 4x |
as.list(thisArgs)[trueDLEArgnames] |
3495 |
) |
|
3496 |
) |
|
3497 |
} |
|
3498 | ||
3499 |
## and the truth Eff function is: |
|
3500 | 1x |
thisTruthEff <- function(dose) { |
3501 | 4x |
do.call( |
3502 | 4x |
trueEff, |
3503 |
## First argument: the dose |
|
3504 | 4x |
c( |
3505 | 4x |
dose, |
3506 |
## Following arguments: take only those that |
|
3507 |
## are required by the Eff function |
|
3508 | 4x |
as.list(thisArgs)[trueEffArgnames] |
3509 |
) |
|
3510 |
) |
|
3511 |
} |
|
3512 | ||
3513 |
## find true sigma2 to generate responses |
|
3514 | ||
3515 | 1x |
trueSigma2 <- 1 / trueNu |
3516 | ||
3517 |
## start the simulated data with the provided one |
|
3518 | 1x |
thisData <- object@data |
3519 | ||
3520 | ||
3521 |
## shall we stop the trial? |
|
3522 |
## First, we want to continue with the starting dose. |
|
3523 |
## This variable is updated after each cohort in the loop. |
|
3524 | 1x |
stopit <- FALSE |
3525 | ||
3526 |
## what is the next dose to be used? |
|
3527 |
## initialize with starting dose |
|
3528 | 1x |
thisDose <- object@startingDose |
3529 | ||
3530 |
## inside this loop we simulate the whole trial, until stopping |
|
3531 | 1x |
while (!stopit) { |
3532 |
## what is the probability for tox. at this dose? |
|
3533 | 4x |
thisDLEProb <- thisTruthDLE(thisDose) |
3534 | 4x |
thisMeanEff <- thisTruthEff(thisDose) |
3535 | ||
3536 |
## what is the cohort size at this dose? |
|
3537 | 4x |
thisSize <- size(object@cohort_size, |
3538 | 4x |
dose = thisDose, |
3539 | 4x |
data = thisData |
3540 |
) |
|
3541 | ||
3542 |
## simulate DLTs: depends on whether we |
|
3543 |
## separate the first patient or not. |
|
3544 | 4x |
if (firstSeparate && (thisSize > 1L)) { |
3545 |
## dose the first patient |
|
3546 | ! |
thisDLTs <- rbinom( |
3547 | ! |
n = 1L, |
3548 | ! |
size = 1L, |
3549 | ! |
prob = thisDLEProb |
3550 |
) |
|
3551 | ||
3552 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3553 | ! |
thisDLTs.PL <- rbinom( |
3554 | ! |
n = 1L, |
3555 | ! |
size = 1L, |
3556 | ! |
prob = thisProb.PL |
3557 |
) |
|
3558 |
} |
|
3559 | ||
3560 | ! |
thisEff <- rnorm( |
3561 | ! |
n = 1L, |
3562 | ! |
mean = thisMeanEff, |
3563 | ! |
sd = sqrt(trueSigma2) |
3564 |
) |
|
3565 | ||
3566 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3567 | ! |
thisEff.PL <- rnorm( |
3568 | ! |
n = 1L, |
3569 | ! |
mean = thisMeanEff.PL, |
3570 | ! |
sd = sqrt(trueSigma2) |
3571 |
) |
|
3572 |
} |
|
3573 | ||
3574 |
## if there is no DLT: |
|
3575 | ! |
if (thisDLTs == 0) { |
3576 |
## enroll the remaining patients |
|
3577 | ! |
thisDLTs <- c( |
3578 | ! |
thisDLTs, |
3579 | ! |
rbinom( |
3580 | ! |
n = thisSize - 1L, |
3581 | ! |
size = 1L, |
3582 | ! |
prob = thisDLEProb |
3583 |
) |
|
3584 |
) |
|
3585 | ! |
thisEff <- c( |
3586 | ! |
thisEff, |
3587 | ! |
rnorm( |
3588 | ! |
n = thisSize - 1L, |
3589 | ! |
mean = thisMeanEff, |
3590 | ! |
sd = sqrt(trueSigma2) |
3591 |
) |
|
3592 |
) |
|
3593 | ||
3594 | ||
3595 | ! |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3596 | ! |
thisDLTs.PL <- c( |
3597 | ! |
thisDLTs.PL, |
3598 | ! |
rbinom( |
3599 | ! |
n = thisSize.PL, |
3600 | ! |
size = 1L, |
3601 | ! |
prob = thisProb.PL |
3602 |
) |
|
3603 |
) |
|
3604 | ! |
thisEff.PL <- c( |
3605 | ! |
thisMeanEff.PL, |
3606 | ! |
rnorm( |
3607 | ! |
n = thisSize.PL, |
3608 | ! |
mean = thisMeanEff.PL, |
3609 | ! |
sd = sqrt(trueSigma2) |
3610 |
) |
|
3611 |
) |
|
3612 |
} |
|
3613 |
} |
|
3614 |
} else { |
|
3615 |
## we can directly dose all patients |
|
3616 | 4x |
thisDLTs <- rbinom( |
3617 | 4x |
n = thisSize, |
3618 | 4x |
size = 1L, |
3619 | 4x |
prob = thisDLEProb |
3620 |
) |
|
3621 | 4x |
thisEff <- rnorm( |
3622 | 4x |
n = thisSize, |
3623 | 4x |
mean = thisMeanEff, |
3624 | 4x |
sd = sqrt(trueSigma2) |
3625 |
) |
|
3626 | ||
3627 | 4x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3628 | ! |
thisDLTs.PL <- rbinom( |
3629 | ! |
n = thisSize.PL, |
3630 | ! |
size = 1L, |
3631 | ! |
prob = thisProb.PL |
3632 |
) |
|
3633 | ! |
thisEff.PL <- rnorm( |
3634 | ! |
n = thisSize.PL, |
3635 | ! |
mean = thisMeanEff.PL, |
3636 | ! |
sd = sqrt(trueSigma2) |
3637 |
) |
|
3638 |
} |
|
3639 |
} |
|
3640 | ||
3641 | ||
3642 | ||
3643 |
## update the data with this placebo (if any) cohort and then with active dose |
|
3644 | 4x |
if (thisData@placebo && (thisSize.PL > 0L)) { |
3645 | ! |
thisData <- update( |
3646 | ! |
object = thisData, |
3647 | ! |
x = object@data@doseGrid[1], |
3648 | ! |
y = thisDLTs.PL, |
3649 | ! |
w = thisEff.PL, |
3650 | ! |
check = FALSE |
3651 |
) |
|
3652 | ||
3653 |
## update the data with active dose |
|
3654 | ! |
thisData <- update( |
3655 | ! |
object = thisData, |
3656 | ! |
x = thisDose, |
3657 | ! |
y = thisDLTs, |
3658 | ! |
w = thisEff, |
3659 | ! |
new_cohort = FALSE |
3660 |
) |
|
3661 |
} else { |
|
3662 |
## update the data with this cohort |
|
3663 | 4x |
thisData <- update( |
3664 | 4x |
object = thisData, |
3665 | 4x |
x = thisDose, |
3666 | 4x |
y = thisDLTs, |
3667 | 4x |
w = thisEff |
3668 |
) |
|
3669 |
} |
|
3670 | ||
3671 | ||
3672 |
## Update model estimate in DLE and Eff models |
|
3673 | 4x |
thisDLEModel <- update( |
3674 | 4x |
object = object@model, |
3675 | 4x |
data = thisData |
3676 |
) |
|
3677 | ||
3678 | 4x |
thisEffModel <- update( |
3679 | 4x |
object = object@eff_model, |
3680 | 4x |
data = thisData |
3681 |
) |
|
3682 | ||
3683 | 4x |
thisNu <- thisEffModel@nu |
3684 | ||
3685 | 4x |
thisDLEsamples <- mcmc( |
3686 | 4x |
data = thisData, |
3687 | 4x |
model = thisDLEModel, |
3688 | 4x |
options = mcmcOptions |
3689 |
) |
|
3690 | ||
3691 | 4x |
thisEffsamples <- mcmc( |
3692 | 4x |
data = thisData, |
3693 | 4x |
model = thisEffModel, |
3694 | 4x |
options = mcmcOptions |
3695 |
) |
|
3696 | ||
3697 | 4x |
thisSigma2 <- if (thisEffModel@use_fixed) { |
3698 | ! |
1 / thisNu |
3699 |
} else { |
|
3700 | 4x |
1 / (as.numeric(thisNu["a"] / thisNu["b"])) |
3701 |
} |
|
3702 | ||
3703 |
## what is the dose limit? |
|
3704 | 4x |
doselimit <- maxDose(object@increments, data = thisData) |
3705 | ||
3706 | ||
3707 | ||
3708 |
## => what is the next best dose? |
|
3709 | 4x |
next_bd <- nextBest(object@nextBest, |
3710 | 4x |
doselimit = doselimit, |
3711 | 4x |
samples = thisDLEsamples, |
3712 | 4x |
model = thisDLEModel, |
3713 | 4x |
data = thisData, |
3714 | 4x |
model_eff = thisEffModel, |
3715 | 4x |
samples_eff = thisEffsamples, |
3716 | 4x |
in_sim = TRUE |
3717 |
) |
|
3718 | ||
3719 | 4x |
thisDose <- next_bd$next_dose |
3720 | 4x |
thisTDtargetDuringTrial <- next_bd$dose_target_drt |
3721 | 4x |
thisTDtargetDuringTrialAtDoseGrid <- next_bd$next_dose_drt |
3722 | 4x |
thisTDtargetEndOfTrial <- next_bd$dose_target_eot |
3723 | 4x |
thisTDtargetEndOfTrialAtDoseGrid <- next_bd$next_dose_eot |
3724 | 4x |
thisGstar <- next_bd$dose_max_gain |
3725 | 4x |
thisGstarAtDoseGrid <- next_bd$next_dose_max_gain |
3726 | ||
3727 | 4x |
Recommend <- min(thisTDtargetEndOfTrialAtDoseGrid, thisGstarAtDoseGrid) |
3728 | ||
3729 |
## Find the 95 % CI and its ratio (upper to the lower of this 95% CI of each of the estimates) |
|
3730 | 4x |
thisCITDEOT <- list(lower = next_bd$ci_dose_target_eot[1], upper = next_bd$ci_dose_target_eot[2]) |
3731 | 4x |
thisratioTDEOT <- next_bd$ci_ratio_dose_target_eot |
3732 | ||
3733 | 4x |
thisCIGstar <- list(lower = next_bd$ci_dose_max_gain[1], upper = next_bd$ci_dose_max_gain[2]) |
3734 | 4x |
thisratioGstar <- next_bd$ci_ratio_dose_max_gain |
3735 | ||
3736 |
## Find the optimal dose |
|
3737 | 4x |
OptimalDose <- min(thisGstar, thisTDtargetEndOfTrial) |
3738 | ||
3739 | 4x |
if (OptimalDose == thisGstar) { |
3740 | ! |
thisratio <- thisratioGstar |
3741 | ! |
thisCI <- thisCIGstar |
3742 |
} else { |
|
3743 | 4x |
thisratio <- thisratioTDEOT |
3744 | 4x |
thisCI <- thisCITDEOT |
3745 |
} |
|
3746 | ||
3747 | ||
3748 |
## evaluate stopping rules |
|
3749 | 4x |
stopit <- stopTrial(object@stopping, |
3750 | 4x |
dose = thisDose, |
3751 | 4x |
samples = thisDLEsamples, |
3752 | 4x |
model = thisDLEModel, |
3753 | 4x |
data = thisData, |
3754 | 4x |
TDderive = object@nextBest@derive, |
3755 | 4x |
Effmodel = thisEffModel, |
3756 | 4x |
Effsamples = thisEffsamples, |
3757 | 4x |
Gstarderive = object@nextBest@mg_derive |
3758 |
) |
|
3759 | 4x |
stopit_results <- h_unpack_stopit(stopit) |
3760 |
} |
|
3761 |
## get the fit |
|
3762 | 1x |
thisDLEFit <- fit( |
3763 | 1x |
object = thisDLEsamples, |
3764 | 1x |
model = thisDLEModel, |
3765 | 1x |
data = thisData |
3766 |
) |
|
3767 | ||
3768 | 1x |
thisEffFit <- fit( |
3769 | 1x |
object = thisEffsamples, |
3770 | 1x |
model = thisEffModel, |
3771 | 1x |
data = thisData |
3772 |
) |
|
3773 | ||
3774 | ||
3775 |
## return the results |
|
3776 | 1x |
thisResult <- list( |
3777 | 1x |
data = thisData, |
3778 | 1x |
dose = thisDose, |
3779 | 1x |
TDtargetDuringTrial = thisTDtargetDuringTrial, |
3780 | 1x |
TDtargetDuringTrialAtDoseGrid = thisTDtargetDuringTrialAtDoseGrid, |
3781 | 1x |
TDtargetEndOfTrial = thisTDtargetEndOfTrial, |
3782 | 1x |
TDtargetEndOfTrialAtDoseGrid = thisTDtargetEndOfTrialAtDoseGrid, |
3783 | 1x |
Gstar = thisGstar, |
3784 | 1x |
GstarAtDoseGrid = thisGstarAtDoseGrid, |
3785 | 1x |
Recommend = Recommend, |
3786 | 1x |
OptimalDose = OptimalDose, |
3787 | 1x |
OptimalDoseAtDoseGrid = Recommend, |
3788 | 1x |
ratio = thisratio, |
3789 | 1x |
CI = thisCI, |
3790 | 1x |
ratioGstar = thisratioGstar, |
3791 | 1x |
CIGstar = thisCIGstar, |
3792 | 1x |
ratioTDEOT = thisratioTDEOT, |
3793 | 1x |
CITDEOT = thisCITDEOT, |
3794 | 1x |
fitDLE = subset(thisDLEFit, |
3795 | 1x |
select = |
3796 | 1x |
c(middle, lower, upper) |
3797 |
), |
|
3798 | 1x |
fitEff = subset(thisEffFit, |
3799 | 1x |
select = |
3800 | 1x |
c(middle, lower, upper) |
3801 |
), |
|
3802 | 1x |
sigma2est = thisSigma2, |
3803 | 1x |
stop = attr( |
3804 | 1x |
stopit, |
3805 | 1x |
"message" |
3806 |
), |
|
3807 | 1x |
report_results = stopit_results |
3808 |
) |
|
3809 | ||
3810 | 1x |
return(thisResult) |
3811 |
} |
|
3812 | ||
3813 | ||
3814 | 1x |
resultList <- get_result_list( |
3815 | 1x |
fun = runSim, |
3816 | 1x |
nsim = nsim, |
3817 | 1x |
vars = |
3818 | 1x |
c( |
3819 | 1x |
"simSeeds", |
3820 | 1x |
"args", |
3821 | 1x |
"nArgs", |
3822 | 1x |
"firstSeparate", |
3823 | 1x |
"trueDLE", |
3824 | 1x |
"trueEff", |
3825 | 1x |
"trueNu", |
3826 | 1x |
"object" |
3827 |
), |
|
3828 | 1x |
parallel = parallel, |
3829 | 1x |
n_cores = nCores |
3830 |
) |
|
3831 | ||
3832 | ||
3833 |
## put everything in the Simulations format: |
|
3834 | ||
3835 |
## setup the list for the simulated data objects |
|
3836 | 1x |
dataList <- lapply(resultList, "[[", "data") |
3837 | ||
3838 |
## the vector of the final dose recommendations |
|
3839 | 1x |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "Recommend")) |
3840 | ||
3841 |
## set up list for the final TD during Trial Estimate |
|
3842 | 1x |
TDtargetDuringTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrial")) |
3843 | ||
3844 |
## set up list for the final TD End of Trial Estimate |
|
3845 | 1x |
TDtargetEndOfTrialList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrial")) |
3846 | ||
3847 |
## set up list for the final TD during Trial estimate at dose Grid |
|
3848 | 1x |
TDtargetDuringTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetDuringTrialAtDoseGrid")) |
3849 | ||
3850 |
## set up list for the final TD End Of Trial estimate at dose Grid |
|
3851 | 1x |
TDtargetEndOfTrialDoseGridList <- as.numeric(sapply(resultList, "[[", "TDtargetEndOfTrialAtDoseGrid")) |
3852 | ||
3853 |
## set up list for the final Gstar estimates |
|
3854 | 1x |
GstarList <- as.numeric(sapply(resultList, "[[", "Gstar")) |
3855 | ||
3856 |
## set up list for the final Gstar estimates at dose grid |
|
3857 | 1x |
GstarAtDoseGridList <- as.numeric(sapply(resultList, "[[", "GstarAtDoseGrid")) |
3858 | ||
3859 |
## set up list for final optimal dose estimates |
|
3860 | 1x |
OptimalDoseList <- as.numeric(sapply(resultList, "[[", "OptimalDose")) |
3861 | ||
3862 |
## set up list for final optimal dose estimates at dose Grid |
|
3863 | 1x |
OptimalDoseAtDoseGridList <- as.numeric(sapply(resultList, "[[", "Recommend")) |
3864 | ||
3865 |
## Set up the list for the final 95% CI obtained |
|
3866 | 1x |
CIList <- lapply(resultList, "[[", "CI") |
3867 | ||
3868 |
## Set up the list for the final ratios obtained |
|
3869 | 1x |
ratioList <- as.numeric(sapply(resultList, "[[", "ratio")) |
3870 | ||
3871 |
## Set up the list for the final 95% CI of the TDtarget End Of Trial obtained |
|
3872 | 1x |
CITDEOTList <- lapply(resultList, "[[", "CITDEOT") |
3873 | ||
3874 |
## Set up the list for the final ratios of the TDtarget End Of Trial obtained |
|
3875 | 1x |
ratioTDEOTList <- as.numeric(sapply(resultList, "[[", "ratioTDEOT")) |
3876 | ||
3877 |
## Set up the list for the final 95% CI of the Gstar obtained |
|
3878 | 1x |
CIGstarList <- lapply(resultList, "[[", "CIGstar") |
3879 | ||
3880 |
## Set up the list for the final ratios of the Gstar obtained |
|
3881 | 1x |
ratioGstarList <- as.numeric(sapply(resultList, "[[", "ratioGstar")) |
3882 | ||
3883 |
## set up the list for the final fits for both DLE and efficacy |
|
3884 | 1x |
fitDLEList <- lapply(resultList, "[[", "fitDLE") |
3885 | 1x |
fitEffList <- lapply(resultList, "[[", "fitEff") |
3886 |
## the vector of the sigma2 |
|
3887 | 1x |
sigma2Estimates <- as.numeric(sapply(resultList, "[[", "sigma2est")) |
3888 | ||
3889 |
## the reasons for stopping |
|
3890 | 1x |
stopReasons <- lapply(resultList, "[[", "stop") |
3891 | ||
3892 |
# individual stopping rule results as matrix, labels as column names |
|
3893 | 1x |
stop_results <- lapply(resultList, "[[", "report_results") |
3894 | 1x |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
3895 | ||
3896 | ||
3897 |
## return the results in the Simulations class object |
|
3898 | 1x |
ret <- PseudoDualSimulations( |
3899 | 1x |
data = dataList, |
3900 | 1x |
doses = recommendedDoses, |
3901 | 1x |
final_td_target_during_trial_estimates = TDtargetDuringTrialList, |
3902 | 1x |
final_td_target_end_of_trial_estimates = TDtargetEndOfTrialList, |
3903 | 1x |
final_td_target_during_trial_at_dose_grid = TDtargetDuringTrialDoseGridList, |
3904 | 1x |
final_td_target_end_of_trial_at_dose_grid = TDtargetEndOfTrialDoseGridList, |
3905 | 1x |
final_cis = CIList, |
3906 | 1x |
final_ratios = ratioList, |
3907 | 1x |
final_gstar_estimates = GstarList, |
3908 | 1x |
final_gstar_at_dose_grid = GstarAtDoseGridList, |
3909 | 1x |
final_gstar_cis = CIGstarList, |
3910 | 1x |
final_gstar_ratios = ratioGstarList, |
3911 | 1x |
final_tdeot_cis = CITDEOTList, |
3912 | 1x |
final_tdeot_ratios = ratioTDEOTList, |
3913 | 1x |
final_optimal_dose = OptimalDoseList, |
3914 | 1x |
final_optimal_dose_at_dose_grid = OptimalDoseAtDoseGridList, |
3915 | 1x |
fit = fitDLEList, |
3916 | 1x |
fit_eff = fitEffList, |
3917 | 1x |
sigma2_est = sigma2Estimates, |
3918 | 1x |
stop_reasons = stopReasons, |
3919 | 1x |
stop_report = stop_report, |
3920 | 1x |
seed = RNGstate |
3921 |
) |
|
3922 | 1x |
return(ret) |
3923 |
} |
|
3924 |
} |
|
3925 |
) |
|
3926 | ||
3927 |
## -------------------------------------------------------------------------- |
|
3928 | ||
3929 |
##' Simulate outcomes from a time-to-DLT augmented CRM design (`DADesign`) |
|
3930 |
##' |
|
3931 |
##' @param object the \code{\linkS4class{DADesign}} object we want to simulate |
|
3932 |
##' data from |
|
3933 |
##' @param nsim the number of simulations (default: 1) |
|
3934 |
##' @param seed see \code{\link{set_seed}} |
|
3935 |
##' @param truthTox a function which takes as input a dose (vector) and returns the |
|
3936 |
##' true probability (vector) for toxicity and the time DLT occurs. Additional |
|
3937 |
##' arguments can be supplied in \code{args}. |
|
3938 |
##' @param truthSurv a CDF which takes as input a time (vector) and returns |
|
3939 |
##' the true cumulative probability (vector) that the DLT would occur conditioning on the patient |
|
3940 |
##' has DLTs. |
|
3941 |
##' @param trueTmax (`number` or `NULL`)\cr the true maximum time at which DLTs can occur. Note that this must be larger thank `Tmax` from the `object`'s base data, which is the length of the DLT window, i.e. until which time DLTs are officially declared as such and used in the trial. |
|
3942 |
##' @param args data frame with arguments for the \code{truth} function. The |
|
3943 |
##' column names correspond to the argument names, the rows to the values of the |
|
3944 |
##' arguments. The rows are appropriately recycled in the \code{nsim} |
|
3945 |
##' simulations. In order to produce outcomes from the posterior predictive |
|
3946 |
##' distribution, e.g, pass an \code{object} that contains the data observed so |
|
3947 |
##' far, \code{truth} contains the \code{prob} function from the model in |
|
3948 |
##' \code{object}, and \code{args} contains posterior samples from the model. |
|
3949 |
##' @param firstSeparate enroll the first patient separately from the rest of |
|
3950 |
##' the cohort? (not default) If yes, the cohort will be closed if a DLT occurs |
|
3951 |
##' in this patient. |
|
3952 |
##' @param deescalate deescalation when a DLT occurs in cohorts with lower dose |
|
3953 |
##' level. |
|
3954 |
##' @param mcmcOptions object of class \code{\linkS4class{McmcOptions}}, |
|
3955 |
##' giving the MCMC options for each evaluation in the trial. By default, |
|
3956 |
##' the standard options are used. |
|
3957 |
##' @param DA document or rename this parameter to make it more meaningful |
|
3958 |
##' @param parallel should the simulation runs be parallelized across the |
|
3959 |
##' clusters of the computer? (not default) |
|
3960 |
##' @param nCores how many cores should be used for parallel computing? |
|
3961 |
##' Defaults to the number of cores on the machine (maximum 5) |
|
3962 |
##' @param \dots not used |
|
3963 |
##' @param derive a named list of functions which derives statistics, based on the |
|
3964 |
##' vector of posterior MTD samples. Each list element must therefore accept |
|
3965 |
##' one and only one argument, which is a numeric vector, and return a number. |
|
3966 |
##' |
|
3967 |
##' @return an object of class \code{\linkS4class{Simulations}} |
|
3968 |
##' |
|
3969 |
##' @example examples/design-method-simulate-DADesign.R |
|
3970 |
##' @export |
|
3971 |
##' @keywords methods |
|
3972 |
setMethod("simulate", |
|
3973 |
signature = |
|
3974 |
signature( |
|
3975 |
object = "DADesign", |
|
3976 |
nsim = "ANY", |
|
3977 |
seed = "ANY" |
|
3978 |
), |
|
3979 |
def = |
|
3980 |
function(object, nsim = 1L, seed = NULL, |
|
3981 |
truthTox, truthSurv, trueTmax = NULL, args = NULL, firstSeparate = FALSE, |
|
3982 |
deescalate = TRUE, |
|
3983 |
mcmcOptions = McmcOptions(), |
|
3984 |
DA = TRUE, |
|
3985 |
parallel = FALSE, nCores = min(parallel::detectCores(), 5), |
|
3986 |
derive = list(), |
|
3987 |
...) { |
|
3988 |
## checks and extracts |
|
3989 | ! |
assert_function(truthTox) |
3990 | ! |
assert_function(truthSurv) |
3991 | ! |
assert_flag(firstSeparate) |
3992 | ! |
assert_count(nsim, positive = TRUE) |
3993 | ! |
assert_flag(parallel) |
3994 | ! |
assert_count(nCores, positive = TRUE) |
3995 | ||
3996 | ! |
args <- as.data.frame(args) |
3997 | ! |
nArgs <- max(nrow(args), 1L) |
3998 | ||
3999 |
## seed handling |
|
4000 | ! |
RNGstate <- set_seed(seed) |
4001 | ||
4002 |
## from this, |
|
4003 |
## generate the individual seeds for the simulation runs |
|
4004 | ! |
simSeeds <- sample(x = seq_len(1e5), size = as.integer(nsim)) |
4005 | ||
4006 |
## Define functions which are useful in DLT Surv generation |
|
4007 | ! |
inverse <- function(f, lower = -100, upper = 100) { |
4008 | ! |
function(y) { |
4009 | ! |
uniroot((function(x) f(x) - y), |
4010 | ! |
lower = lower, upper = upper |
4011 | ! |
)[1]$root |
4012 |
} |
|
4013 |
} |
|
4014 | ||
4015 |
## The DLT window length |
|
4016 | ! |
thisData <- object@data |
4017 | ! |
Tmax <- thisData@Tmax |
4018 | ||
4019 | ! |
if (is.null(trueTmax)) { |
4020 | ! |
trueTmax <- Tmax |
4021 | ! |
} else if (trueTmax < Tmax) { |
4022 | ! |
warning("trueTmax < Tmax! trueTmax is set to Tmax") |
4023 | ! |
trueTmax <- Tmax |
4024 |
} |
|
4025 | ||
4026 |
## Calculate the inverse function of Surv to DLT CDF |
|
4027 | ! |
itruthSurv <- inverse(truthSurv, 0, trueTmax) |
4028 | ||
4029 |
## generate random variable of Surv to DLT data; return Tmax when no |
|
4030 |
## DLT |
|
4031 | ! |
rtruthSurv <- function(DLT, Tmax_, itruthSurv = itruthSurv) { |
4032 | ! |
u_i <- rep(-100, length(DLT)) # remember to check this |
4033 | ||
4034 | ! |
if (sum(DLT == 0) > 0) { |
4035 | ! |
u_i[DLT == 0] <- Tmax_ |
4036 |
} |
|
4037 | ||
4038 | ! |
if (sum(DLT == 1) > 0) { |
4039 | ! |
u_i[DLT == 1] <- unlist(lapply(runif(sum(DLT == 1), 0, 1), itruthSurv)) |
4040 |
} |
|
4041 | ||
4042 |
# make sure that results are always positive, otherwise we get |
|
4043 |
# problems below. |
|
4044 | ! |
u_i[u_i == 0] <- 0.5 |
4045 | ! |
return(u_i) |
4046 |
} |
|
4047 | ||
4048 |
# A function to return follow up fulfull yes (TRUE) vs no (FALSE); |
|
4049 | ! |
ready_to_open <- function(day, window, thisSurv) { |
4050 | ! |
size <- length(thisSurv) |
4051 |
# the date the patient starts; |
|
4052 | ! |
start_time <- apply(rbind(thisSurv[-size], window$patientGap[-1]), 2, min) |
4053 |
# the relative time for each patient on the specified "date"; |
|
4054 | ! |
individule_check <- day - cumsum(c(0, start_time)) |
4055 |
# the minial number should be 0; |
|
4056 | ! |
individule_check[individule_check < 0] <- 0 |
4057 | ! |
follow_up <- apply(rbind(thisSurv, individule_check), 2, min) |
4058 | ! |
return(all((follow_up - apply(rbind(window$patientFollow, thisSurv), 2, min)) >= 0) & (max(follow_up) >= min(window$patientFollowMin, max(thisSurv)))) |
4059 |
} |
|
4060 | ||
4061 |
## assume we have surfficient patients, i.e. patient can be immediately enrolled |
|
4062 |
## once the trial accumulation is open. This function will tell you when to open |
|
4063 |
## the next cohort; |
|
4064 |
# this function applys to all trials; |
|
4065 | ! |
nextOpen <- function(window, thisSurv) { |
4066 | ! |
size <- length(thisSurv) |
4067 | ||
4068 | ! |
window$patientGap <- window$patientGap[1:size] ## if length(window$pt)>length(thisSurv), assume the first length(thisSurv) patients were enrolled; |
4069 |
## if the DLT happens before the end of DLT window, then the next |
|
4070 |
## cohort/enrollment of the next patient would happened earlier; |
|
4071 | ! |
start_time <- apply(rbind(thisSurv[-size], window$patientGap[-1]), 2, min) |
4072 |
# duration of the cohort (all DLT windows finished); |
|
4073 | ! |
maxT <- max(thisSurv + cumsum(c(0, start_time))) |
4074 | ||
4075 | ! |
meetrequire <- sapply(1:maxT, function(i) { |
4076 | ! |
ready_to_open(i, window, thisSurv) |
4077 |
}) |
|
4078 | ! |
if (sum(meetrequire) > 0) { |
4079 |
# the earliest time that the require is met; |
|
4080 | ! |
time <- min(c(1:maxT)[meetrequire]) |
4081 |
} else { |
|
4082 | ! |
time <- maxT |
4083 |
} |
|
4084 | ||
4085 | ! |
return(time) |
4086 |
} |
|
4087 | ||
4088 |
## the function to produce the run a single simulation |
|
4089 |
## with index "iterSim" |
|
4090 | ! |
runSim <- function(iterSim) { |
4091 |
## set the seed for this run |
|
4092 | ! |
set.seed(simSeeds[iterSim]) |
4093 | ||
4094 |
# check<<-simSeeds[iterSim] |
|
4095 |
## what is now the argument for the truth? |
|
4096 |
## (appropriately recycled) |
|
4097 | ! |
thisArgs <- args[(iterSim - 1) %% nArgs + 1, , drop = FALSE] |
4098 | ||
4099 |
## so this truth is... |
|
4100 | ! |
thisTruth <- function(dose) { |
4101 | ! |
do.call( |
4102 | ! |
truthTox, |
4103 |
## First argument: the dose |
|
4104 | ! |
c( |
4105 | ! |
dose, |
4106 |
## Following arguments |
|
4107 | ! |
thisArgs |
4108 |
) |
|
4109 |
) |
|
4110 |
} |
|
4111 | ||
4112 |
## start the simulated data with the provided one |
|
4113 | ! |
thisData <- object@data |
4114 | ||
4115 |
# In case there are placebo |
|
4116 | ! |
if (thisData@placebo) { |
4117 |
## what is the probability for tox. at placebo? |
|
4118 | ! |
thisProb.PL <- thisTruth(object@data@doseGrid[1]) |
4119 |
} |
|
4120 | ||
4121 |
## shall we stop the trial? |
|
4122 |
## First, we want to continue with the starting dose. |
|
4123 |
## This variable is updated after each cohort in the loop. |
|
4124 | ! |
stopit <- FALSE |
4125 | ||
4126 |
## the time clock for the study, set to 0 when the first simulated |
|
4127 |
## patient initially dosed; |
|
4128 | ! |
trialtime <- 0 |
4129 | ||
4130 |
# initiate the true DLT, true DLT Surv and the C1/D1 for each patients |
|
4131 | ! |
factDLTs <- thisData@y |
4132 | ! |
factSurv <- thisData@u |
4133 | ! |
factT0 <- thisData@t0 |
4134 | ||
4135 |
## what is the next dose to be used? |
|
4136 |
## initialize with starting dose |
|
4137 | ! |
thisDose <- object@startingDose |
4138 | ||
4139 |
## inside this loop we simulate the whole trial, until stopping |
|
4140 | ! |
while (!stopit) { |
4141 |
## what is the probability for tox. at this dose? |
|
4142 | ! |
thisProb <- thisTruth(thisDose) |
4143 | ||
4144 |
## what is the cohort size at this dose? |
|
4145 | ! |
thisSize <- size(object@cohort_size, |
4146 | ! |
dose = thisDose, |
4147 | ! |
data = thisData |
4148 |
) |
|
4149 | ||
4150 | ! |
thisSafetywindow <- windowLength(object@safetyWindow, thisSize) |
4151 |
# better: add a checkpoint in safetywindow--dim(safetywindow$pt)==thisSize; |
|
4152 | ||
4153 |
## In case there are placebo |
|
4154 | ! |
if (thisData@placebo) { |
4155 | ! |
thisSize.PL <- size(object@pl_cohort_size, |
4156 | ! |
dose = thisDose, |
4157 | ! |
data = thisData |
4158 |
) |
|
4159 |
} |
|
4160 | ||
4161 | ||
4162 |
## simulate DLTs: depends on whether we |
|
4163 |
## separate the first patient or not. |
|
4164 |
## amended on May 24: if any patient had DLT before the |
|
4165 |
## first patient finished a staggered window |
|
4166 |
## further enrollment will be stopped; |
|
4167 | ||
4168 | ! |
if (firstSeparate && (thisSize > 1L)) { |
4169 |
## dose the first patient |
|
4170 | ! |
thisDLTs <- rbinom( |
4171 | ! |
n = 1L, |
4172 | ! |
size = 1L, |
4173 | ! |
prob = thisProb |
4174 |
) |
|
4175 | ||
4176 | ! |
if (thisData@placebo) { |
4177 | ! |
thisDLTs.PL <- rbinom( |
4178 | ! |
n = 1L, |
4179 | ! |
size = 1L, |
4180 | ! |
prob = thisProb.PL |
4181 |
) |
|
4182 |
} |
|
4183 | ||
4184 | ! |
thisSurv <- ceiling(rtruthSurv(DLT = thisDLTs, Tmax_ = trueTmax, itruthSurv = itruthSurv)) |
4185 | ||
4186 | ! |
if (Tmax < trueTmax) { |
4187 | ! |
thisDLTs[thisDLTs == 1 & thisSurv > Tmax] <- 0 |
4188 | ||
4189 | ! |
thisSurv <- apply(rbind(thisSurv, rep(Tmax, length(thisSurv))), 2, min) |
4190 |
} |
|
4191 | ||
4192 | ! |
thisT0 <- trialtime |
4193 | ||
4194 |
## if there is no DLT during Safety window: |
|
4195 |
## and no DLTs of previous patients--> |
|
4196 | ||
4197 | ||
4198 |
# need to update the DataDA object |
|
4199 | ! |
tempData <- update( |
4200 | ! |
object = thisData, |
4201 | ! |
y = c(factDLTs, thisDLTs), #### the y will be updated according to u |
4202 | ! |
u = c(factSurv, thisSurv), |
4203 | ! |
t0 = c(factT0, thisT0), |
4204 | ! |
x = thisDose, |
4205 | ! |
trialtime = trialtime + thisSafetywindow$patientGap[2] |
4206 | ! |
) #### the u will be updated over time |
4207 | ||
4208 | ! |
temptime <- (tempData@u + tempData@t0)[tempData@y == 1 & tempData@x <= thisDose] |
4209 | ||
4210 | ||
4211 |
# identify number of DLTs occurs during the thisSafetywindow$pt[2] |
|
4212 |
# if(thisSurv>thisSafetywindow$pt[2]) |
|
4213 | ! |
if (sum(temptime > trialtime) == 0) { |
4214 |
## enroll the remaining patients |
|
4215 | ! |
thisDLTs <- c( |
4216 | ! |
thisDLTs, |
4217 | ! |
rbinom( |
4218 | ! |
n = thisSize - 1L, |
4219 | ! |
size = 1L, |
4220 | ! |
prob = thisProb |
4221 |
) |
|
4222 |
) |
|
4223 | ||
4224 | ! |
thisSurv <- c( |
4225 | ! |
thisSurv, |
4226 | ! |
ceiling(rtruthSurv(thisDLTs[-1], trueTmax, itruthSurv = itruthSurv)) |
4227 |
) |
|
4228 | ||
4229 | ! |
if (Tmax < trueTmax) { |
4230 | ! |
thisDLTs[thisDLTs == 1 & thisSurv > Tmax] <- 0 |
4231 | ||
4232 | ! |
thisSurv <- apply(rbind(thisSurv, rep(Tmax, length(thisSurv))), 2, min) |
4233 |
} |
|
4234 | ||
4235 |
# in case any DLT happens before the end of the safety window; |
|
4236 | ! |
real_window <- apply(rbind(thisSurv[-thisSize], thisSafetywindow$patientGap[-1]), 2, min) |
4237 | ||
4238 | ||
4239 | ! |
thisT0 <- trialtime + c(0, cumsum(real_window)) |
4240 | ||
4241 | ! |
if (thisData@placebo && (thisSize.PL > 1L)) { |
4242 | ! |
thisDLTs.PL <- c( |
4243 | ! |
thisDLTs.PL, |
4244 | ! |
rbinom( |
4245 | ! |
n = thisSize.PL - 1L, |
4246 | ! |
size = 1L, |
4247 | ! |
prob = thisProb.PL |
4248 |
) |
|
4249 |
) |
|
4250 |
} |
|
4251 |
} |
|
4252 | ||
4253 | ! |
rm(tempData) |
4254 | ! |
rm(temptime) |
4255 |
} else { |
|
4256 |
## we can directly dose all patients |
|
4257 | ! |
thisDLTs <- rbinom( |
4258 | ! |
n = thisSize, |
4259 | ! |
size = 1L, |
4260 | ! |
prob = thisProb |
4261 |
) |
|
4262 | ||
4263 | ! |
thisSurv <- ceiling(rtruthSurv(thisDLTs, trueTmax, itruthSurv = itruthSurv)) |
4264 |
## should return a vector with a same dimention as thisDLTs |
|
4265 | ! |
if (Tmax < trueTmax) { |
4266 | ! |
thisDLTs[thisDLTs == 1 & thisSurv > Tmax] <- 0 |
4267 | ||
4268 | ! |
thisSurv <- apply(rbind(thisSurv, rep(Tmax, length(thisSurv))), 2, min) |
4269 |
} |
|
4270 |
# in case any DLT happens before the end of the safety window; |
|
4271 | ! |
real_window <- apply(rbind(thisSurv[-thisSize], thisSafetywindow$patientGap[-1]), 2, min) |
4272 | ||
4273 | ! |
thisT0 <- trialtime + c(0, cumsum(real_window)) |
4274 |
## should return a vector with a same dimention as thisDLTs |
|
4275 | ||
4276 | ! |
if (thisData@placebo) { |
4277 | ! |
thisDLTs.PL <- rbinom( |
4278 | ! |
n = thisSize.PL, |
4279 | ! |
size = 1L, |
4280 | ! |
prob = thisProb.PL |
4281 |
) |
|
4282 |
} |
|
4283 |
} |
|
4284 | ||
4285 | ||
4286 |
## update the data with this placebo (if any) |
|
4287 |
## cohort and then with active dose |
|
4288 | ! |
if (thisData@placebo) { |
4289 | ! |
thisData <- update( |
4290 | ! |
object = thisData, |
4291 | ! |
x = object@data@doseGrid[1], |
4292 | ! |
y = thisDLTs.PL |
4293 |
) |
|
4294 | ||
4295 |
## update the data with active dose |
|
4296 | ! |
thisData <- update( |
4297 | ! |
object = thisData, |
4298 | ! |
x = thisDose, |
4299 | ! |
y = thisDLTs, |
4300 | ! |
new_cohort = FALSE |
4301 |
) |
|
4302 | ||
4303 |
## JZ: additional part for DADesign--when to start the next cohort |
|
4304 | ! |
trialtime <- trialtime + nextOpen( |
4305 | ! |
window = thisSafetywindow, |
4306 | ! |
thisSurv = thisSurv |
4307 |
) |
|
4308 |
} else { |
|
4309 |
## JZ: since the whole y and u column need update. |
|
4310 |
## factDLTs and factSuev get update and then calculate the y and u value in |
|
4311 |
## thisData object |
|
4312 |
# |
|
4313 |
# ## update the data with this cohort |
|
4314 |
# thisData <- update(object=thisData, |
|
4315 |
# x=thisDose, ####the x will be constantly updated according to u |
|
4316 |
# y=thisDLTs, |
|
4317 |
# u=thisSurv) ####the u will be constantly updated |
|
4318 | ||
4319 | ! |
factDLTs <- c(factDLTs, thisDLTs) |
4320 | ||
4321 | ! |
factSurv <- c(factSurv, thisSurv) # better: check the data type of factSurv and thisSurv; |
4322 | ||
4323 | ! |
factT0 <- c(factT0, thisT0) |
4324 | ||
4325 | ||
4326 | ! |
tempnext <- nextOpen( |
4327 | ! |
window = thisSafetywindow, |
4328 | ! |
thisSurv = thisSurv |
4329 |
) |
|
4330 | ||
4331 |
##### if there are DLTs, patients in the higher cohorts will be dosed a lower dose or discontinue. |
|
4332 | ! |
if (deescalate == TRUE) { |
4333 | ! |
newDLTid <- ((factSurv + factT0) > trialtime & (factSurv + factT0 - trialtime) <= tempnext & factDLTs == 1) |
4334 | ||
4335 | ! |
newDLTnum <- c(1:length(factDLTs))[newDLTid] |
4336 | ||
4337 | ! |
newDLTnum <- newDLTnum[newDLTnum <= (length(factDLTs) - length(thisDLTs))] |
4338 | ||
4339 |
# if(ifelse(sum(newDLTnum)==0,Inf,min(newDLTnum))<=(length(factDLTs)-length(thisDLTs))){ |
|
4340 | ! |
if (length(newDLTnum) > 0) { |
4341 | ! |
for (DLT_loop in newDLTnum) { |
4342 | ! |
newDLTtime <- (factSurv + factT0)[DLT_loop] |
4343 | ||
4344 |
# identify higher dose--impacted patients: |
|
4345 | ! |
deescalateID <- c(DLT_loop:length(factDLTs))[c(thisData@x, rep(thisDose, length(thisDLTs)))[DLT_loop:length(factDLTs)] > thisData@x[DLT_loop]] |
4346 | ||
4347 | ||
4348 |
## DLT will be observed once the followup time >= the time to DLT |
|
4349 | ! |
factDLTs[deescalateID] <- as.integer(factDLTs * (newDLTtime >= factT0 + factSurv))[deescalateID] |
4350 | ||
4351 |
## update DLT free survival time |
|
4352 | ! |
factSurv[deescalateID] <- apply(rbind(factSurv, newDLTtime - factT0), 2, min)[deescalateID] |
4353 |
} |
|
4354 | ||
4355 | ! |
tempnext <- min(tempnext, max((factSurv + factT0)[(length(factDLTs) - length(thisDLTs) + 1):length(factDLTs)]) - trialtime) |
4356 |
} |
|
4357 |
} |
|
4358 | ||
4359 | ||
4360 | ||
4361 | ||
4362 | ||
4363 | ||
4364 |
## JZ: future work: additional part for DADesign--when to start the next cohort |
|
4365 |
## nextOpen can be modified to incorporate different patient enrollment rate; |
|
4366 |
## currently assume we have sufficient patients; |
|
4367 |
## If there is a gap between cohorts for cohort manager meeting, it can be |
|
4368 |
## added to here; |
|
4369 | ||
4370 | ! |
trialtime <- trialtime + tempnext |
4371 | ||
4372 | ||
4373 | ||
4374 |
## Update thisData |
|
4375 |
## according to what can be observed by the time when the next cohort open; |
|
4376 | ||
4377 | ||
4378 | ! |
thisData <- update( |
4379 | ! |
object = thisData, |
4380 | ! |
y = factDLTs, #### the y will be updated according to u |
4381 | ! |
u = factSurv, |
4382 | ! |
t0 = factT0, |
4383 | ! |
x = thisDose, |
4384 | ! |
trialtime = trialtime |
4385 | ! |
) #### the u will be updated over time |
4386 | ||
4387 | ! |
try(if (length(thisData@x) != length(thisData@u) || length(thisData@u) != length(thisData@y)) { |
4388 | ! |
stop("x,y,u dimention error") |
4389 |
}) |
|
4390 |
} |
|
4391 | ||
4392 | ||
4393 |
# testthisdata<<-thisData |
|
4394 | ||
4395 |
## what is the dose limit? #JZ should |
|
4396 |
## still work for the DataDA object |
|
4397 | ! |
doselimit <- maxDose(object@increments, |
4398 | ! |
data = thisData |
4399 |
) |
|
4400 | ||
4401 | ||
4402 | ||
4403 |
## generate samples from the model |
|
4404 | ! |
if (DA == TRUE) { |
4405 | ! |
thisSamples <- mcmc( |
4406 | ! |
data = thisData, |
4407 | ! |
model = object@model, |
4408 | ! |
options = mcmcOptions |
4409 |
) |
|
4410 | ! |
} else if (DA == FALSE) { |
4411 | ! |
temp_model <- LogisticLogNormal( |
4412 | ! |
mean = object@model@params@mean, |
4413 | ! |
cov = object@model@params@cov, |
4414 | ! |
ref_dose = object@model@refDose |
4415 |
) |
|
4416 | ||
4417 | ! |
trunk_Data <- Data( |
4418 | ! |
x = thisData@x, y = thisData@y, |
4419 | ! |
doseGrid = thisData@doseGrid, |
4420 | ! |
cohort = thisData@cohort, |
4421 | ! |
ID = thisData@ID |
4422 |
) |
|
4423 | ||
4424 | ! |
thisSamples <- mcmc( |
4425 | ! |
data = trunk_Data, |
4426 | ! |
model = temp_model, |
4427 | ! |
options = mcmcOptions |
4428 |
) |
|
4429 |
} |
|
4430 | ||
4431 |
## => what is the next best dose? |
|
4432 | ! |
thisDose <- nextBest(object@nextBest, |
4433 | ! |
doselimit = doselimit, |
4434 | ! |
samples = thisSamples, |
4435 | ! |
model = object@model, |
4436 | ! |
data = thisData |
4437 | ! |
)$value |
4438 | ||
4439 |
## evaluate stopping rules |
|
4440 | ! |
stopit <- stopTrial(object@stopping, |
4441 | ! |
dose = thisDose, |
4442 | ! |
samples = thisSamples, |
4443 | ! |
model = object@model, |
4444 | ! |
data = thisData |
4445 |
) |
|
4446 | ! |
stopit_results <- h_unpack_stopit(stopit) |
4447 |
} |
|
4448 | ||
4449 |
## get the fit |
|
4450 | ! |
thisFit <- fit( |
4451 | ! |
object = thisSamples, |
4452 | ! |
model = object@model, |
4453 | ! |
data = thisData |
4454 |
) |
|
4455 | ||
4456 |
# Get the MTD estimate from the samples. |
|
4457 | ||
4458 | ! |
target_dose_samples <- dose( |
4459 | ! |
mean(object@nextBest@target), |
4460 | ! |
model = object@model, |
4461 | ! |
samples = thisSamples |
4462 |
) |
|
4463 | ||
4464 |
# Create a function for additional statistical summary. |
|
4465 | ! |
additional_stats <- lapply(derive, function(f) f(target_dose_samples)) |
4466 | ||
4467 |
## return the results |
|
4468 | ! |
thisResult <- |
4469 | ! |
list( |
4470 | ! |
data = thisData, |
4471 | ! |
dose = thisDose, |
4472 | ! |
duration = trialtime, |
4473 | ! |
fit = |
4474 | ! |
subset(thisFit, |
4475 | ! |
select = c(middle, lower, upper) |
4476 |
), |
|
4477 | ! |
stop = |
4478 | ! |
attr( |
4479 | ! |
stopit, |
4480 | ! |
"message" |
4481 |
), |
|
4482 | ! |
report_results = stopit_results, |
4483 | ! |
additional_stats = additional_stats |
4484 |
) |
|
4485 | ! |
return(thisResult) |
4486 |
} |
|
4487 | ||
4488 | ! |
resultList <- get_result_list( |
4489 | ! |
fun = runSim, ## remove |
4490 | ! |
nsim = nsim, |
4491 | ! |
vars = |
4492 | ! |
c( |
4493 | ! |
"simSeeds", |
4494 | ! |
"args", |
4495 | ! |
"nArgs", |
4496 | ! |
"firstSeparate", |
4497 | ! |
"truthTox", |
4498 | ! |
"truthSurv", |
4499 | ! |
"object", |
4500 | ! |
"mcmcOptions", |
4501 | ! |
"nextOpen", |
4502 | ! |
"ready_to_open" |
4503 |
), |
|
4504 | ! |
parallel = parallel, |
4505 | ! |
n_cores = nCores |
4506 |
) |
|
4507 | ||
4508 |
## put everything in the Simulations format: |
|
4509 | ||
4510 |
## setup the list for the simulated data objects |
|
4511 | ! |
dataList <- lapply(resultList, "[[", "data") |
4512 | ||
4513 | ||
4514 |
## the vector of the final dose recommendations |
|
4515 | ! |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "dose")) |
4516 | ||
4517 |
## the vector of the final trial duration; |
|
4518 | ! |
trialduration <- as.numeric(sapply(resultList, "[[", "duration")) |
4519 | ||
4520 |
## setup the list for the final fits |
|
4521 | ! |
fitList <- lapply(resultList, "[[", "fit") |
4522 | ||
4523 |
## the reasons for stopping |
|
4524 | ! |
stopReasons <- lapply(resultList, "[[", "stop") |
4525 | ||
4526 |
# individual stopping rule results as matrix, labels as column names |
|
4527 | ! |
stop_results <- lapply(resultList, "[[", "report_results") |
4528 | ! |
stop_report <- as.matrix(do.call(rbind, stop_results)) |
4529 | ||
4530 | ! |
additional_stats <- lapply(resultList, "[[", "additional_stats") |
4531 | ||
4532 |
## return the results in the Simulations class object |
|
4533 | ! |
ret <- DASimulations( |
4534 | ! |
data = dataList, |
4535 | ! |
doses = recommendedDoses, |
4536 | ! |
fit = fitList, |
4537 | ! |
trialduration = trialduration, |
4538 | ! |
stop_report = stop_report, |
4539 | ! |
stop_reasons = stopReasons, |
4540 | ! |
additional_stats = additional_stats, |
4541 | ! |
seed = RNGstate |
4542 |
) |
|
4543 | ||
4544 | ||
4545 | ! |
return(ret) |
4546 |
} |
|
4547 |
) |
|
4548 | ||
4549 |
## -------------------------------------------------------------------------- |
|
4550 |
# nolint end |
|
4551 | ||
4552 |
# simulate ---- |
|
4553 | ||
4554 |
## DesignGrouped ---- |
|
4555 | ||
4556 |
#' Simulate Method for the [`DesignGrouped`] Class |
|
4557 |
#' |
|
4558 |
#' @description `r lifecycle::badge("experimental")` |
|
4559 |
#' |
|
4560 |
#' A simulate method for [`DesignGrouped`] designs. |
|
4561 |
#' |
|
4562 |
#' @param object (`DesignGrouped`)\cr the design we want to simulate trials from. |
|
4563 |
#' @param nsim (`number`)\cr how many trials should be simulated. |
|
4564 |
#' @param seed (`RNGstate`)\cr generated with [set_seed()]. |
|
4565 |
#' @param truth (`function`)\cr a function which takes as input a dose (vector) and |
|
4566 |
#' returns the true probability (vector) for toxicity for the mono arm. |
|
4567 |
#' Additional arguments can be supplied in `args`. |
|
4568 |
#' @param combo_truth (`function`)\cr same as `truth` but for the combo arm. |
|
4569 |
#' @param args (`data.frame`)\cr optional `data.frame` with arguments that work |
|
4570 |
#' for both the `truth` and `combo_truth` functions. The column names correspond to |
|
4571 |
#' the argument names, the rows to the values of the arguments. The rows are |
|
4572 |
#' appropriately recycled in the `nsim` simulations. |
|
4573 |
#' @param firstSeparate (`flag`)\cr whether to enroll the first patient separately |
|
4574 |
#' from the rest of the cohort and close the cohort in case a DLT occurs in this |
|
4575 |
#' first patient. |
|
4576 |
#' @param mcmcOptions (`McmcOptions`)\cr MCMC options for each evaluation in the trial. |
|
4577 |
#' @param parallel (`flag`)\cr whether the simulation runs are parallelized across the |
|
4578 |
#' cores of the computer. |
|
4579 |
#' @param nCores (`number`)\cr how many cores should be used for parallel computing. |
|
4580 |
#' @param ... not used. |
|
4581 |
#' |
|
4582 |
#' @return A list of `mono` and `combo` simulation results as [`Simulations`] objects. |
|
4583 |
#' |
|
4584 |
#' @aliases simulate-DesignGrouped |
|
4585 |
#' @export |
|
4586 |
#' @example examples/Design-method-simulate-DesignGrouped.R |
|
4587 |
#' |
|
4588 |
setMethod( |
|
4589 |
"simulate", |
|
4590 |
signature = |
|
4591 |
signature( |
|
4592 |
object = "DesignGrouped", |
|
4593 |
nsim = "ANY", |
|
4594 |
seed = "ANY" |
|
4595 |
), |
|
4596 |
def = |
|
4597 |
function(object, |
|
4598 |
nsim = 1L, |
|
4599 |
seed = NULL, |
|
4600 |
truth, |
|
4601 |
combo_truth, |
|
4602 |
args = data.frame(), |
|
4603 |
firstSeparate = FALSE, |
|
4604 |
mcmcOptions = McmcOptions(), |
|
4605 |
parallel = FALSE, |
|
4606 |
nCores = min(parallelly::availableCores(), 5), |
|
4607 |
...) { |
|
4608 | 9x |
nsim <- as.integer(nsim) |
4609 | 9x |
assert_function(truth) |
4610 | 9x |
assert_function(combo_truth) |
4611 | 9x |
assert_data_frame(args) |
4612 | 9x |
assert_count(nsim, positive = TRUE) |
4613 | 9x |
assert_flag(firstSeparate) |
4614 | 9x |
assert_flag(parallel) |
4615 | 9x |
assert_count(nCores, positive = TRUE) |
4616 | ||
4617 | 9x |
n_args <- max(nrow(args), 1L) |
4618 | 9x |
rng_state <- set_seed(seed) |
4619 | 9x |
sim_seeds <- sample.int(n = 2147483647, size = nsim) |
4620 | ||
4621 | 9x |
run_sim <- function(iter_sim) { |
4622 | 16x |
set.seed(sim_seeds[iter_sim]) |
4623 | 16x |
current <- list(mono = list(), combo = list()) |
4624 |
# Define true toxicity functions. |
|
4625 | 16x |
current$args <- args[(iter_sim - 1) %% n_args + 1, , drop = FALSE] |
4626 | 16x |
current$mono$truth <- function(dose) do.call(truth, c(dose, current$args)) |
4627 | 16x |
current$combo$truth <- function(dose) do.call(combo_truth, c(dose, current$args)) |
4628 |
# Start the simulated data with the provided one. |
|
4629 | 16x |
current$mono$data <- object@mono@data |
4630 | 16x |
current$combo$data <- object@combo@data |
4631 |
# We are in the first cohort and continue for mono and combo. |
|
4632 | 16x |
current$first <- TRUE |
4633 | 16x |
current$mono$stop <- current$combo$stop <- FALSE |
4634 | ||
4635 |
# What are the next doses to be used? Initialize with starting doses. |
|
4636 | 16x |
if (object@same_dose_for_all || (!object@first_cohort_mono_only && object@same_dose_for_start)) { |
4637 | 6x |
current$mono$dose <- current$combo$dose <- min(object@mono@startingDose, object@combo@startingDose) |
4638 |
} else { |
|
4639 | 10x |
current$mono$dose <- object@mono@startingDose |
4640 | 10x |
current$combo$dose <- object@combo@startingDose |
4641 |
} |
|
4642 | ||
4643 |
# Inside this loop we simulate the whole trial, until stopping. |
|
4644 | 16x |
while (!(current$mono$stop && current$combo$stop)) { |
4645 | 71x |
if (!current$mono$stop) { |
4646 | 55x |
cohort_size_mono <- size( |
4647 | 55x |
object@mono@cohort_size, |
4648 | 55x |
dose = current$mono$dose, |
4649 | 55x |
data = current$mono$data |
4650 |
) |
|
4651 | 55x |
this_prob_mono <- current$mono$truth(current$mono$dose) |
4652 | 55x |
current$mono$data <- current$mono$data %>% |
4653 | 55x |
h_determine_dlts( |
4654 | 55x |
dose = current$mono$dose, |
4655 | 55x |
prob = this_prob_mono, |
4656 | 55x |
cohort_size = cohort_size_mono, |
4657 | 55x |
first_separate = firstSeparate |
4658 |
) |
|
4659 |
} |
|
4660 | 71x |
if (!current$combo$stop && (!current$first || !object@first_cohort_mono_only)) { |
4661 | 63x |
cohort_size_combo <- size( |
4662 | 63x |
object@combo@cohort_size, |
4663 | 63x |
dose = current$combo$dose, |
4664 | 63x |
data = current$combo$data |
4665 |
) |
|
4666 | 63x |
this_prob_combo <- current$combo$truth(current$combo$dose) |
4667 | 63x |
current$combo$data <- current$combo$data %>% |
4668 | 63x |
h_determine_dlts( |
4669 | 63x |
dose = current$combo$dose, |
4670 | 63x |
prob = this_prob_combo, |
4671 | 63x |
cohort_size = cohort_size_combo, |
4672 | 63x |
first_separate = firstSeparate |
4673 |
) |
|
4674 |
} |
|
4675 | ||
4676 | 71x |
current$grouped <- h_group_data(current$mono$data, current$combo$data) |
4677 | 71x |
current$samples <- mcmc(current$grouped, object@model, mcmcOptions) |
4678 | 71x |
if (!current$mono$stop) { |
4679 | 55x |
current$mono$limit <- maxDose(object@mono@increments, data = current$mono$data) |
4680 | 55x |
current$mono$dose <- object@mono@nextBest %>% |
4681 | 55x |
nextBest(current$mono$limit, current$samples, object@model, current$grouped, group = "mono") |
4682 | 55x |
current$mono$dose <- current$mono$dose$value |
4683 |
} |
|
4684 | 71x |
if (!current$combo$stop && (!current$first || !object@first_cohort_mono_only)) { |
4685 | 63x |
current$combo$limit <- if (is.na(current$mono$dose)) { |
4686 | ! |
0 |
4687 |
} else { |
|
4688 | 63x |
maxDose(object@combo@increments, current$combo$data) %>% |
4689 | 63x |
min(current$mono$dose, na.rm = TRUE) |
4690 |
} |
|
4691 | 63x |
current$combo$dose <- object@combo@nextBest %>% |
4692 | 63x |
nextBest(current$combo$limit, current$samples, object@model, current$grouped, group = "combo") |
4693 | 63x |
current$combo$dose <- current$combo$dose$value |
4694 | 63x |
current$combo$stop <- object@combo@stopping %>% |
4695 | 63x |
stopTrial(current$combo$dose, current$samples, object@model, current$combo$data, group = "combo") |
4696 | 63x |
current$combo$results <- h_unpack_stopit(current$combo$stop) |
4697 |
} |
|
4698 | 71x |
if (!current$mono$stop) { |
4699 | 55x |
current$mono$stop <- object@mono@stopping %>% |
4700 | 55x |
stopTrial( |
4701 | 55x |
current$mono$dose, current$samples, object@model, current$mono$data, |
4702 | 55x |
group = "mono", external = current$combo$stop |
4703 |
) |
|
4704 | 55x |
current$mono$results <- h_unpack_stopit(current$mono$stop) |
4705 |
} |
|
4706 | 71x |
if (object@same_dose_for_all && !current$mono$stop && !current$combo$stop) { |
4707 | 16x |
current$mono$dose <- current$combo$dose <- min(current$mono$dose, current$combo$dose) |
4708 |
} |
|
4709 | 71x |
if (current$first) { |
4710 | 16x |
current$first <- FALSE |
4711 | 16x |
if (object@first_cohort_mono_only && object@same_dose_for_start) { |
4712 | 2x |
current$mono$dose <- current$combo$dose <- min(current$mono$dose, current$combo$dose) |
4713 |
} |
|
4714 |
} |
|
4715 |
} |
|
4716 | 16x |
current$mono$fit <- fit(current$samples, object@model, current$grouped, group = "mono") |
4717 | 16x |
current$combo$fit <- fit(current$samples, object@model, current$grouped, group = "combo") |
4718 | 16x |
lapply( |
4719 | 16x |
X = current[c("mono", "combo")], FUN = with, |
4720 | 16x |
list( |
4721 | 16x |
data = data, dose = dose, fit = subset(fit, select = -dose), |
4722 | 16x |
stop = attr(stop, "message"), results = results |
4723 |
) |
|
4724 |
) |
|
4725 |
} |
|
4726 | 9x |
vars_needed <- c("simSeeds", "args", "nArgs", "truth", "combo_truth", "firstSeparate", "object", "mcmcOptions") |
4727 | ||
4728 | 9x |
result_list <- get_result_list(run_sim, nsim, vars_needed, parallel, nCores) |
4729 |
# Now we have a list with each element containing mono and combo. Reorder this a bit: |
|
4730 | 9x |
result_list <- list( |
4731 | 9x |
mono = lapply(result_list, "[[", "mono"), |
4732 | 9x |
combo = lapply(result_list, "[[", "combo") |
4733 |
) |
|
4734 |
# Put everything in a list with both mono and combo Simulations: |
|
4735 | 9x |
lapply(result_list, function(this_list) { |
4736 | 18x |
data_list <- lapply(this_list, "[[", "data") |
4737 | 18x |
recommended_doses <- as.numeric(sapply(this_list, "[[", "dose")) |
4738 | 18x |
fit_list <- lapply(this_list, "[[", "fit") |
4739 | 18x |
stop_reasons <- lapply(this_list, "[[", "stop") |
4740 | 18x |
report_results <- lapply(this_list, "[[", "results") |
4741 | 18x |
stop_report <- as.matrix(do.call(rbind, report_results)) |
4742 | 18x |
additional_stats <- lapply(this_list, "[[", "additional_stats") |
4743 | ||
4744 | ||
4745 | 18x |
Simulations( |
4746 | 18x |
data = data_list, |
4747 | 18x |
doses = recommended_doses, |
4748 | 18x |
fit = fit_list, |
4749 | 18x |
stop_reasons = stop_reasons, |
4750 | 18x |
stop_report = stop_report, |
4751 | 18x |
additional_stats = additional_stats, |
4752 | 18x |
seed = rng_state |
4753 |
) |
|
4754 |
}) |
|
4755 |
} |
|
4756 |
) |
|
4757 | ||
4758 |
# tidy ---- |
|
4759 | ||
4760 |
## tidy-DualDesign ---- |
|
4761 | ||
4762 |
#' @rdname tidy |
|
4763 |
#' @aliases tidy-DualDesign |
|
4764 |
#' @example examples/Design-method-tidyDualDesign.R |
|
4765 |
#' |
|
4766 |
#' @export |
|
4767 |
setMethod( |
|
4768 |
f = "tidy", |
|
4769 |
signature = signature(x = "DualDesign"), |
|
4770 |
definition = function(x, ...) { |
|
4771 |
# Some Design objects have complex attributes whose structure is not supported. |
|
4772 | 3x |
rv <- h_tidy_all_slots(x, attributes = FALSE) %>% h_tidy_class(x) |
4773 | 3x |
if (length(rv) == 1) { |
4774 | ! |
rv[[names(rv)[1]]] %>% h_tidy_class(x) |
4775 |
} else { |
|
4776 | 3x |
rv |
4777 |
} |
|
4778 |
} |
|
4779 |
) |
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 |
# 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 | 83x |
assert_flag(asis) |
85 |
# Because subsections use level + 1 and 6 is the lowest markdown header level |
|
86 | 65x |
assert_int(level, lower = 1, upper = 5) |
87 | 65x |
assert_character(title, any.missing = FALSE, len = 1L) |
88 | ||
89 | 65x |
slots_to_process <- setdiff(slotNames(x), ignore_slots) |
90 | ||
91 | 65x |
args <- list(...) |
92 | 65x |
units <- ifelse("units" %in% names(args), paste0(" ", args[["units"]]), "") |
93 | 65x |
section_labels <- h_prepare_section_labels( |
94 | 65x |
x, |
95 | 65x |
default_sections, |
96 | 65x |
user_sections |
97 |
) |
|
98 | 65x |
assert_subset(slots_to_process, names(section_labels)) |
99 | ||
100 | 65x |
rv <- paste0( |
101 | 65x |
h_markdown_header(title, level = level), |
102 | 65x |
paste0( |
103 | 65x |
lapply( |
104 | 65x |
slots_to_process, |
105 | 65x |
function(nm) { |
106 | 478x |
tmp <- switch(nm, |
107 | 478x |
starting_dose = knit_print( |
108 | 478x |
StartingDose(x@starting_dose), |
109 | 478x |
asis = FALSE, |
110 | 478x |
level = level + 1L, |
111 |
... |
|
112 |
), |
|
113 | 478x |
startingDose = knit_print( |
114 | 478x |
StartingDose(x@startingDose), |
115 | 478x |
asis = FALSE, |
116 | 478x |
level = level + 1L, |
117 |
... |
|
118 |
), |
|
119 | 478x |
pl_cohort_size = ifelse( |
120 | 478x |
identical(slot(x, "pl_cohort_size"), CohortSizeConst(0)), |
121 | 478x |
"Placebo will not be administered in the trial.\n\n", |
122 | 478x |
knit_print( |
123 | 478x |
slot(x, "pl_cohort_size"), |
124 | 478x |
asis = FALSE, |
125 | 478x |
level = level + 1L, |
126 |
... |
|
127 |
) |
|
128 |
), |
|
129 |
{ |
|
130 | 361x |
knit_print(slot(x, nm), asis = FALSE, level = level + 1L, ...) |
131 |
} |
|
132 |
) |
|
133 | 478x |
paste0(h_markdown_header(section_labels[nm], level + 1L), tmp) |
134 |
} |
|
135 |
), |
|
136 | 65x |
collapse = "\n\n" |
137 |
), |
|
138 | 65x |
"\n\n" |
139 |
) |
|
140 | ||
141 | 65x |
if (asis) { |
142 | 27x |
rv <- knitr::asis_output(rv) |
143 |
} |
|
144 | 65x |
rv |
145 |
} |
|
146 | ||
147 |
#' @description A Helper Function to create Markdown Headers |
|
148 |
#' |
|
149 |
#' @param text (`character`) the header text |
|
150 |
#' @param level (`positive_integer`) the level of the header. Must be between 1 and 6. |
|
151 |
#' @return the Markdown header string: a newline, `#` repeated `level` times, |
|
152 |
#' a space, `text` followed by two newlines. |
|
153 |
#' @keywords internal |
|
154 |
#' @noRd |
|
155 |
h_markdown_header <- function(text, level = 2L) { |
|
156 | 567x |
assert_character(text, any.missing = FALSE, len = 1L, min.chars = 2L) |
157 | 564x |
assert_int(level, lower = 1, upper = 6) |
158 | ||
159 | 561x |
paste0( |
160 | 561x |
"\n", |
161 | 561x |
stringr::str_dup("#", level), |
162 |
" ", |
|
163 | 561x |
text, |
164 | 561x |
"\n\n" |
165 |
) |
|
166 |
} |
|
167 | ||
168 |
#' Modify a Set of Default Slot Labels With Custom Custom Labels |
|
169 |
#' |
|
170 |
#' x (`S4`)\cr the S4 object for which slot labels are required |
|
171 |
#' default_labels (`character`)\cr a vector of slot labels whose names are a |
|
172 |
#' superset of the slot names of `x` |
|
173 |
#' user_labels (`character`)\cr a vector of slot labels whose names are a |
|
174 |
#' superset of the slot names of `x`. Can be `NA`, in which case no updates |
|
175 |
#' are made |
|
176 |
#' @returns `default_labels` updated according to `user_labels` |
|
177 |
#' @noRd |
|
178 |
#' @keywords internal |
|
179 |
h_prepare_section_labels <- function(x, default_labels, user_labels = NA) { |
|
180 | 71x |
assert_true(isS4(x)) |
181 | 71x |
assert_character(default_labels, any.missing = FALSE) |
182 | ||
183 | 71x |
if (!any(is.na(user_labels))) { |
184 | 9x |
assert_character(user_labels, any.missing = FALSE) |
185 | 9x |
assert_subset(names(user_labels), slotNames(x)) |
186 | ||
187 | 9x |
for (nm in names(user_labels)) { |
188 | 7x |
default_labels[nm] <- user_labels[nm] |
189 |
} |
|
190 |
} |
|
191 | 71x |
default_labels |
192 |
} |
|
193 | ||
194 |
# Methods ---- |
|
195 | ||
196 |
# StartingDose ---- |
|
197 | ||
198 |
#' @description `r lifecycle::badge("experimental")` |
|
199 |
#' @rdname knit_print |
|
200 |
#' @export |
|
201 |
#' @method knit_print StartingDose |
|
202 |
knit_print.StartingDose <- function(x, ..., asis = TRUE) { |
|
203 | 73x |
assert_flag(asis) |
204 | ||
205 | 71x |
args <- list(...) |
206 | 71x |
units <- ifelse("units" %in% names(args), paste0(" ", args[["units"]]), "") |
207 | 71x |
rv <- paste0( |
208 | 71x |
"The starting dose is ", |
209 | 71x |
paste0(x@starting_dose, units), |
210 | 71x |
".\n\n" |
211 |
) |
|
212 | ||
213 | 71x |
if (asis) { |
214 | 2x |
rv <- knitr::asis_output(rv) |
215 |
} |
|
216 | 71x |
rv |
217 |
} |
|
218 | ||
219 |
# RuleDesign ---- |
|
220 | ||
221 |
#' @description `r lifecycle::badge("experimental")` |
|
222 |
#' @inheritParams knit_print.StoppingTargetProb |
|
223 |
#' @param level (`positive_integer`) The level of the headings used to separate |
|
224 |
#' slots. Must be between 1 and 6. |
|
225 |
#' @param title (`character`) The text of the heading of the section describing |
|
226 |
#' the design |
|
227 |
#' @param sections (`character`) a named vector of length at least 4 defining |
|
228 |
#' the headings used to define the sections corresponding to the design's slots. |
|
229 |
#' The element names must match the Design's slot names. |
|
230 |
#' @rdname knit_print |
|
231 |
#' @export |
|
232 |
#' @method knit_print RuleDesign |
|
233 |
knit_print.RuleDesign <- function( |
|
234 |
x, |
|
235 |
..., |
|
236 |
level = 2L, |
|
237 |
title = "Design", |
|
238 |
sections = NA, |
|
239 |
asis = TRUE) { |
|
240 | 8x |
h_knit_print_design( |
241 | 8x |
x, |
242 |
..., |
|
243 | 8x |
level = 2L, |
244 | 8x |
title = "Design", |
245 | 8x |
default_sections = c( |
246 | 8x |
"nextBest" = "Dose recommendation", |
247 | 8x |
"cohort_size" = "Cohort size", |
248 | 8x |
"data" = "Observed data", |
249 | 8x |
"startingDose" = "Starting dose" |
250 |
), |
|
251 | 8x |
user_sections = sections, |
252 | 8x |
asis = asis |
253 |
) |
|
254 |
} |
|
255 | ||
256 |
# Design ---- |
|
257 | ||
258 |
#' @description `r lifecycle::badge("experimental")` |
|
259 |
#' @inheritParams knit_print.RuleDesign |
|
260 |
#' @rdname knit_print |
|
261 |
#' @export |
|
262 |
#' @method knit_print Design |
|
263 |
knit_print.Design <- function( |
|
264 |
x, |
|
265 |
..., |
|
266 |
level = 2L, |
|
267 |
title = "Design", |
|
268 |
sections = NA, |
|
269 |
asis = TRUE) { |
|
270 | 22x |
h_knit_print_design( |
271 | 22x |
x, |
272 |
..., |
|
273 | 22x |
level = 2L, |
274 | 22x |
title = "Design", |
275 | 22x |
default_sections = c( |
276 | 22x |
"nextBest" = "Dose recommendation", |
277 | 22x |
"cohort_size" = "Cohort size", |
278 | 22x |
"data" = "Observed data", |
279 | 22x |
"startingDose" = "Starting dose", |
280 | 22x |
"increments" = "Escalation rule", |
281 | 22x |
"stopping" = "Stopping rule", |
282 | 22x |
"model" = "Dose toxicity model", |
283 | 22x |
"pl_cohort_size" = "Use of placebo" |
284 |
), |
|
285 | 22x |
user_sections = sections, |
286 | 22x |
asis = asis |
287 |
) |
|
288 |
} |
|
289 | ||
290 |
# DualDesign ---- |
|
291 | ||
292 |
#' @description `r lifecycle::badge("experimental")` |
|
293 |
#' @inheritParams knit_print.RuleDesign |
|
294 |
#' @rdname knit_print |
|
295 |
#' @export |
|
296 |
#' @method knit_print DualDesign |
|
297 |
knit_print.DualDesign <- function( |
|
298 |
x, |
|
299 |
..., |
|
300 |
level = 2L, |
|
301 |
title = "Design", |
|
302 |
sections = NA, |
|
303 |
asis = TRUE) { |
|
304 | 8x |
assert_flag(asis) |
305 | 6x |
assert_character(title, len = 1, any.missing = FALSE) |
306 | 6x |
assert_integer(level, len = 1, lower = 1, upper = 6) |
307 | ||
308 | 6x |
args <- list(...) |
309 | 6x |
bLabel <- ifelse( |
310 | 6x |
"biomarker_label" %in% names(args), |
311 | 6x |
args[["biomarker_label"]], |
312 | 6x |
"biomarker" |
313 |
) |
|
314 | 6x |
tLabel <- ifelse( |
315 | 6x |
"tox_label" %in% names(args), |
316 | 6x |
args[["tox_label"]], |
317 | 6x |
"toxicity" |
318 |
) |
|
319 | ||
320 | 6x |
if (is.na(sections)) { |
321 | 6x |
sections <- c("model" = paste0("Dose-", tLabel, " and dose-", bLabel, " models")) |
322 |
} else { |
|
323 | ! |
if (!("model" %in% names(sections))) { |
324 | ! |
sections["model"] <- paste0("Dose-", tLabel, " and dose-", bLabel, " models") |
325 |
} |
|
326 |
} |
|
327 | ||
328 | 6x |
knit_print.Design( |
329 | 6x |
x, |
330 | 6x |
level = level, |
331 | 6x |
title = title, |
332 | 6x |
sections = sections, |
333 | 6x |
asis = asis, |
334 |
... |
|
335 |
) |
|
336 |
} |
|
337 | ||
338 |
# DADesign ---- |
|
339 | ||
340 |
#' @description `r lifecycle::badge("experimental")` |
|
341 |
#' @inheritParams knit_print.RuleDesign |
|
342 |
#' @rdname knit_print |
|
343 |
#' @export |
|
344 |
#' @method knit_print DADesign |
|
345 |
knit_print.DADesign <- function( |
|
346 |
x, |
|
347 |
..., |
|
348 |
level = 2L, |
|
349 |
title = "Design", |
|
350 |
sections = NA, |
|
351 |
asis = TRUE) { |
|
352 | 8x |
h_knit_print_design( |
353 | 8x |
x, |
354 |
..., |
|
355 | 8x |
level = 2L, |
356 | 8x |
title = "Design", |
357 | 8x |
default_sections = c( |
358 | 8x |
"nextBest" = "Dose recommendation", |
359 | 8x |
"cohort_size" = "Cohort size", |
360 | 8x |
"data" = "Observed data", |
361 | 8x |
"startingDose" = "Starting dose", |
362 | 8x |
"increments" = "Escalation rule", |
363 | 8x |
"stopping" = "Stopping rule", |
364 | 8x |
"model" = "Dose toxicity model", |
365 | 8x |
"pl_cohort_size" = "Use of placebo", |
366 | 8x |
"safetyWindow" = "Safety window" |
367 |
), |
|
368 | 8x |
user_sections = sections, |
369 | 8x |
asis = asis |
370 |
) |
|
371 |
} |
|
372 | ||
373 |
# TDDesign ---- |
|
374 | ||
375 |
#' @description `r lifecycle::badge("experimental")` |
|
376 |
#' @inheritParams knit_print.RuleDesign |
|
377 |
#' @rdname knit_print |
|
378 |
#' @export |
|
379 |
#' @method knit_print TDDesign |
|
380 |
knit_print.TDDesign <- function( |
|
381 |
x, |
|
382 |
..., |
|
383 |
level = 2L, |
|
384 |
title = "Design", |
|
385 |
sections = NA, |
|
386 |
asis = TRUE) { |
|
387 | 8x |
knit_print.Design( |
388 | 8x |
x, |
389 | 8x |
level = level, |
390 | 8x |
title = title, |
391 | 8x |
sections = sections, |
392 | 8x |
asis = asis, |
393 |
... |
|
394 |
) |
|
395 |
} |
|
396 | ||
397 |
# DualResponsesDesign ---- |
|
398 | ||
399 |
#' @description `r lifecycle::badge("experimental")` |
|
400 |
#' @inheritParams knit_print.RuleDesign |
|
401 |
#' @rdname knit_print |
|
402 |
#' @export |
|
403 |
#' @method knit_print DualResponsesDesign |
|
404 |
knit_print.DualResponsesDesign <- function( |
|
405 |
x, |
|
406 |
..., |
|
407 |
level = 2L, |
|
408 |
title = "Design", |
|
409 |
sections = NA, |
|
410 |
asis = TRUE) { |
|
411 |
knit_print.Design( |
|
412 |
x, |
|
413 |
level = level, |
|
414 |
title = title, |
|
415 |
sections = sections, |
|
416 |
asis = asis, |
|
417 |
... |
|
418 |
) |
|
419 |
} |
|
420 | ||
421 |
# DesignOrdinal ---- |
|
422 | ||
423 |
#' @description `r lifecycle::badge("experimental")` |
|
424 |
#' @inheritParams knit_print.RuleDesign |
|
425 |
#' @rdname knit_print |
|
426 |
#' @export |
|
427 |
#' @method knit_print DesignOrdinal |
|
428 |
knit_print.DesignOrdinal <- function( |
|
429 |
x, |
|
430 |
..., |
|
431 |
level = 2L, |
|
432 |
title = "Design", |
|
433 |
sections = NA, |
|
434 |
asis = TRUE) { |
|
435 | 6x |
h_knit_print_design( |
436 | 6x |
x, |
437 |
..., |
|
438 | 6x |
level = 2L, |
439 | 6x |
title = "Design", |
440 | 6x |
default_sections = c( |
441 | 6x |
"next_best" = "Dose recommendation", |
442 | 6x |
"cohort_size" = "Cohort size", |
443 | 6x |
"data" = "Observed data", |
444 | 6x |
"starting_dose" = "Starting dose", |
445 | 6x |
"increments" = "Escalation rule", |
446 | 6x |
"stopping" = "Stopping rule", |
447 | 6x |
"model" = "Dose toxicity model", |
448 | 6x |
"pl_cohort_size" = "Use of placebo" |
449 |
), |
|
450 | 6x |
user_sections = sections, |
451 | 6x |
asis = asis |
452 |
) |
|
453 |
} |
|
454 | ||
455 |
# DesignGrouped ---- |
|
456 | ||
457 |
# Needs special handling because of the empty models and nested rules in the |
|
458 |
# mono and combo slots and because of the many slots that are of built-in types |
|
459 |
# rather than being of crmPack-specific types |
|
460 | ||
461 |
#' @description `r lifecycle::badge("experimental")` |
|
462 |
#' @inheritParams knit_print.RuleDesign |
|
463 |
#' @rdname knit_print |
|
464 |
#' @export |
|
465 |
#' @method knit_print DesignGrouped |
|
466 |
knit_print.DesignGrouped <- function( |
|
467 |
x, |
|
468 |
..., |
|
469 |
level = 2L, |
|
470 |
title = "Design", |
|
471 |
sections = c( |
|
472 |
"model" = "Dose toxicity model", |
|
473 |
"mono" = "Monotherapy rules", |
|
474 |
"combo" = "Combination therapy rules", |
|
475 |
"other" = "Other details" |
|
476 |
), |
|
477 |
asis = TRUE) { |
|
478 | 6x |
assert_flag(asis) |
479 | 4x |
assert_character(title, len = 1, any.missing = FALSE) |
480 | 4x |
assert_integer(level, len = 1, lower = 1, upper = 6) |
481 | ||
482 | 4x |
rv <- paste0( |
483 | 4x |
h_markdown_header(sections["model"], level = level), |
484 | 4x |
knit_print(x@model, asis = FALSE, ...), |
485 | 4x |
h_markdown_header(sections["mono"], level = level), |
486 | 4x |
h_knit_print_design( |
487 | 4x |
x@mono, |
488 | 4x |
asis = FALSE, |
489 | 4x |
level = level + 1L, |
490 | 4x |
ignore_slots = c("model"), |
491 | 4x |
default_sections = c( |
492 | 4x |
"nextBest" = "Dose recommendation", |
493 | 4x |
"cohort_size" = "Cohort size", |
494 | 4x |
"data" = "Observed monotherapy data", |
495 | 4x |
"startingDose" = "Starting dose", |
496 | 4x |
"increments" = "Escalation rule", |
497 | 4x |
"stopping" = "Stopping rule", |
498 | 4x |
"pl_cohort_size" = "Use of placebo" |
499 |
), |
|
500 | 4x |
sections = sections[["mono"]], |
501 |
... |
|
502 |
), |
|
503 | 4x |
h_markdown_header(sections["combo"], level = level), |
504 | 4x |
h_knit_print_design( |
505 | 4x |
x@combo, |
506 | 4x |
asis = FALSE, |
507 | 4x |
level = level + 1L, |
508 | 4x |
ignore_slots = "model", |
509 | 4x |
default_sections = c( |
510 | 4x |
"nextBest" = "Dose recommendation", |
511 | 4x |
"cohort_size" = "Cohort size", |
512 | 4x |
"data" = "Observed combination therapy 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[["combo"]], |
519 |
... |
|
520 |
), |
|
521 | 4x |
h_markdown_header(sections["other"], level = level), |
522 | 4x |
ifelse( |
523 | 4x |
x@first_cohort_mono_only, |
524 | 4x |
paste0( |
525 | 4x |
"No combination dosing may occur until the results of at least one ", |
526 | 4x |
"monotherapy cohort are available.\n\n" |
527 |
), |
|
528 | 4x |
"Simultaneous combination and monotherapy dosing is permitted from the outset.\n\n" |
529 |
), |
|
530 | 4x |
ifelse( |
531 | 4x |
x@same_dose_for_start, |
532 | 4x |
paste0( |
533 | 4x |
"When monotherapy and combination therapy are used in the same cohort ", |
534 | 4x |
"for the first time, the same dose must be used for both regimens.\n\n" |
535 |
), |
|
536 | 4x |
paste0( |
537 | 4x |
"When monotherapy and combination therapy are used in the same cohort ", |
538 | 4x |
"for the first time, the use of a different dose in each regimen is permitted.\n\n" |
539 |
) |
|
540 |
), |
|
541 | 4x |
ifelse( |
542 | 4x |
x@same_dose_for_all, |
543 | 4x |
paste0( |
544 | 4x |
"Whenever monotherapy and combination therapy are used in the same cohort, ", |
545 | 4x |
"the same dose must be used for both regimens.\n\n" |
546 |
), |
|
547 | 4x |
paste0( |
548 | 4x |
"Whenever monotherapy and combination therapy are used in the same cohort, ", |
549 | 4x |
"the use of a different dose in each regimen is permitted.\n\n" |
550 |
) |
|
551 |
) |
|
552 |
) |
|
553 | ||
554 | 4x |
if (asis) { |
555 | 2x |
rv <- knitr::asis_output(rv) |
556 |
} |
|
557 | 4x |
rv |
558 |
} |
|
559 | ||
560 |
# TDsamplesDesign ---- |
|
561 | ||
562 |
#' @description `r lifecycle::badge("experimental")` |
|
563 |
#' @inheritParams knit_print.RuleDesign |
|
564 |
#' @rdname knit_print |
|
565 |
#' @export |
|
566 |
#' @method knit_print TDsamplesDesign |
|
567 |
knit_print.TDsamplesDesign <- function( |
|
568 |
x, |
|
569 |
..., |
|
570 |
level = 2L, |
|
571 |
title = "Design", |
|
572 |
sections = NA, |
|
573 |
asis = TRUE) { |
|
574 | 6x |
h_knit_print_design( |
575 | 6x |
x, |
576 |
..., |
|
577 | 6x |
level = level, |
578 | 6x |
title = title, |
579 | 6x |
default_sections = c( |
580 | 6x |
"nextBest" = "Dose recommendation", |
581 | 6x |
"cohort_size" = "Cohort size", |
582 | 6x |
"data" = "Observed data", |
583 | 6x |
"startingDose" = "Starting dose", |
584 | 6x |
"model" = "Dose toxicity model", |
585 | 6x |
"stopping" = "Stopping rule", |
586 | 6x |
"increments" = "Escalation rule", |
587 | 6x |
"pl_cohort_size" = "Use of placebo" |
588 |
), |
|
589 | 6x |
user_sections = sections, |
590 | 6x |
asis = asis |
591 |
) |
|
592 |
} |
|
593 | ||
594 |
# DualResponsesDesign ---- |
|
595 | ||
596 |
#' @description `r lifecycle::badge("experimental")` |
|
597 |
#' @inheritParams knit_print.RuleDesign |
|
598 |
#' @rdname knit_print |
|
599 |
#' @export |
|
600 |
#' @method knit_print DualResponsesDesign |
|
601 |
knit_print.DualResponsesDesign <- function( |
|
602 |
x, |
|
603 |
..., |
|
604 |
level = 2L, |
|
605 |
title = "Design", |
|
606 |
sections = NA, |
|
607 |
asis = TRUE) { |
|
608 | 8x |
h_knit_print_design( |
609 | 8x |
x, |
610 |
..., |
|
611 | 8x |
level = level, |
612 | 8x |
title = title, |
613 | 8x |
default_sections = c( |
614 | 8x |
"nextBest" = "Dose recommendation", |
615 | 8x |
"cohort_size" = "Cohort size", |
616 | 8x |
"data" = "Observed data", |
617 | 8x |
"startingDose" = "Starting dose", |
618 | 8x |
"increments" = "Escalation rule", |
619 | 8x |
"stopping" = "Stopping rule", |
620 | 8x |
"model" = "Dose-toxicity model", |
621 | 8x |
"eff_model" = "Dose-efficacy model", |
622 | 8x |
"pl_cohort_size" = "Use of placebo" |
623 |
), |
|
624 | 8x |
ignore_sections = c("model", "eff_model"), |
625 | 8x |
sections = sections, |
626 | 8x |
asis = asis |
627 |
) |
|
628 |
} |
|
629 | ||
630 |
# DualResponsesSamplesDesign ---- |
|
631 | ||
632 |
#' @description `r lifecycle::badge("experimental")` |
|
633 |
#' @inheritParams knit_print.RuleDesign |
|
634 |
#' @rdname knit_print |
|
635 |
#' @export |
|
636 |
#' @method knit_print DualResponsesSamplesDesign |
|
637 |
knit_print.DualResponsesSamplesDesign <- function( |
|
638 |
x, |
|
639 |
..., |
|
640 |
level = 2L, |
|
641 |
title = "Design", |
|
642 |
sections = NA, |
|
643 |
asis = TRUE) { |
|
644 | 8x |
h_knit_print_design( |
645 | 8x |
x, |
646 |
..., |
|
647 | 8x |
level = level, |
648 | 8x |
title = title, |
649 | 8x |
default_sections = c( |
650 | 8x |
"nextBest" = "Dose recommendation", |
651 | 8x |
"cohort_size" = "Cohort size", |
652 | 8x |
"data" = "Observed data", |
653 | 8x |
"startingDose" = "Starting dose", |
654 | 8x |
"increments" = "Escalation rule", |
655 | 8x |
"stopping" = "Stopping rule", |
656 | 8x |
"model" = "Dose-toxicity model", |
657 | 8x |
"eff_model" = "Dose-efficacy model", |
658 | 8x |
"pl_cohort_size" = "Use of placebo" |
659 |
), |
|
660 | 8x |
ignore_sections = c("model", "eff_model"), |
661 | 8x |
sections = sections, |
662 | 8x |
asis = asis |
663 |
) |
|
664 |
} |
|
665 | ||
666 |
# RuleDesignOrdinal ---- |
|
667 | ||
668 |
#' @description `r lifecycle::badge("experimental")` |
|
669 |
#' @inheritParams knit_print.RuleDesign |
|
670 |
#' @rdname knit_print |
|
671 |
#' @export |
|
672 |
#' @method knit_print RuleDesignOrdinal |
|
673 |
knit_print.RuleDesignOrdinal <- function( |
|
674 |
x, |
|
675 |
..., |
|
676 |
level = 2L, |
|
677 |
title = "Design", |
|
678 |
sections = NA, |
|
679 |
asis = TRUE) { |
|
680 | 9x |
h_knit_print_design( |
681 | 9x |
x, |
682 |
..., |
|
683 | 9x |
level = 2L, |
684 | 9x |
title = "Design", |
685 | 9x |
default_sections = c( |
686 | 9x |
"next_best" = "Dose recommendation", |
687 | 9x |
"cohort_size" = "Cohort size", |
688 | 9x |
"data" = "Observed data", |
689 | 9x |
"starting_dose" = "Starting dose" |
690 |
), |
|
691 | 9x |
user_sections = sections, |
692 | 9x |
asis = asis |
693 |
) |
|
694 |
} |
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 | 442x |
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, |
|
59 |
model, |
|
60 |
options, |
|
61 |
from_prior = data@nObs == 0L, |
|
62 |
...) { |
|
63 | 360x |
assert_flag(from_prior) |
64 | ||
65 | 360x |
model_fun <- if (from_prior) { |
66 | 38x |
model@priormodel |
67 |
} else { |
|
68 | 322x |
h_jags_join_models(model@datamodel, model@priormodel) |
69 |
} |
|
70 | ||
71 | 360x |
model_file <- h_jags_write_model(model_fun) |
72 | 360x |
model_inits <- h_jags_get_model_inits(model, data) |
73 | 360x |
model_data <- h_jags_get_data(model, data, from_prior) |
74 | ||
75 | 360x |
jags_model <- rjags::jags.model( |
76 | 360x |
file = model_file, |
77 | 360x |
data = model_data, |
78 | 360x |
inits = c( |
79 | 360x |
model_inits, |
80 | 360x |
.RNG.name = h_null_if_na(options@rng_kind), |
81 | 360x |
.RNG.seed = h_null_if_na(options@rng_seed) |
82 |
), |
|
83 | 360x |
quiet = !is_logging_enabled(), |
84 | 360x |
n.adapt = 0 # No adaptation. Important for reproducibility. |
85 |
) |
|
86 | 360x |
update(jags_model, n.iter = options@burnin, progress.bar = "none") |
87 | ||
88 |
# This is necessary as some outputs are written directly from the JAGS |
|
89 |
# compiled code to the outstream. |
|
90 | 360x |
log_trace("Running rjags::jags.samples") |
91 | 360x |
if (is_logging_enabled()) { |
92 | ! |
jags_samples <- rjags::jags.samples( |
93 | ! |
model = jags_model, |
94 | ! |
variable.names = model@sample, |
95 | ! |
n.iter = (options@iterations - options@burnin), |
96 | ! |
thin = options@step |
97 |
) |
|
98 |
} else { |
|
99 | 360x |
invisible( |
100 | 360x |
capture.output( |
101 | 360x |
jags_samples <- rjags::jags.samples( |
102 | 360x |
model = jags_model, |
103 | 360x |
variable.names = model@sample, |
104 | 360x |
n.iter = (options@iterations - options@burnin), |
105 | 360x |
thin = options@step, |
106 | 360x |
progress.bar = "none" |
107 |
) |
|
108 |
) |
|
109 |
) |
|
110 |
} |
|
111 | 360x |
log_trace("JAGS samples: ", jags_samples, capture = TRUE) |
112 | 360x |
samples <- lapply(jags_samples, h_jags_extract_samples) |
113 | ||
114 | 360x |
Samples(data = samples, options = options) |
115 |
} |
|
116 |
) |
|
117 | ||
118 |
# mcmc-GeneralData-DualEndpointRW ---- |
|
119 | ||
120 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
121 |
#' [`DualEndpointRW`] model, it is required that there are at least two (in |
|
122 |
#' case of random walk prior of the first order on the biomarker level) or |
|
123 |
#' three doses in the grid. |
|
124 |
#' |
|
125 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
126 |
#' when number of observations in `data` is `0`. For some models it might be |
|
127 |
#' necessary to specify it manually here though. |
|
128 |
#' |
|
129 |
#' @aliases mcmc-GeneralData-DualEndpointRW |
|
130 |
#' @example examples/mcmc-DualEndpointRW.R |
|
131 |
#' |
|
132 |
setMethod( |
|
133 |
f = "mcmc", |
|
134 |
signature = signature( |
|
135 |
data = "GeneralData", |
|
136 |
model = "DualEndpointRW", |
|
137 |
options = "McmcOptions" |
|
138 |
), |
|
139 |
def = function(data, |
|
140 |
model, |
|
141 |
options, |
|
142 |
from_prior = data@nObs == 0L, |
|
143 |
...) { |
|
144 | 30x |
if (model@rw1) { |
145 | 20x |
assert_true(data@nGrid >= 2) |
146 |
} else { |
|
147 | 10x |
assert_true(data@nGrid >= 3) |
148 |
} |
|
149 | ||
150 | 27x |
callNextMethod( |
151 | 27x |
data = data, |
152 | 27x |
model = model, |
153 | 27x |
options = options, |
154 | 27x |
from_prior = from_prior, |
155 |
... |
|
156 |
) |
|
157 |
} |
|
158 |
) |
|
159 | ||
160 |
# mcmc-GeneralData-DualEndpointBeta ---- |
|
161 | ||
162 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
163 |
#' [`DualEndpointBeta`] model, it is required that the value of `ref_dose_beta` |
|
164 |
#' slot is greater than the maximum dose in a grid. This requirement comes from |
|
165 |
#' definition of the beta function that is used to model dose-biomarker |
|
166 |
#' relationship in [`DualEndpointBeta`] model. The other requirement is that |
|
167 |
#' there must be at least one dose in the grid. |
|
168 |
#' |
|
169 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
170 |
#' when number of observations in `data` is `0`. For some models it might be |
|
171 |
#' necessary to specify it manually here though. |
|
172 |
#' |
|
173 |
#' @aliases mcmc-GeneralData-DualEndpointBeta |
|
174 |
#' @example examples/mcmc-DualEndpointBeta.R |
|
175 |
#' |
|
176 |
setMethod( |
|
177 |
f = "mcmc", |
|
178 |
signature = signature( |
|
179 |
data = "GeneralData", |
|
180 |
model = "DualEndpointBeta", |
|
181 |
options = "McmcOptions" |
|
182 |
), |
|
183 |
def = function(data, |
|
184 |
model, |
|
185 |
options, |
|
186 |
from_prior = data@nObs == 0L, |
|
187 |
...) { |
|
188 | 10x |
assert_true(data@nGrid >= 1) |
189 | 9x |
assert_true(model@ref_dose_beta > data@doseGrid[data@nGrid]) |
190 | ||
191 | 8x |
callNextMethod( |
192 | 8x |
data = data, |
193 | 8x |
model = model, |
194 | 8x |
options = options, |
195 | 8x |
from_prior = from_prior, |
196 |
... |
|
197 |
) |
|
198 |
} |
|
199 |
) |
|
200 | ||
201 |
# mcmc-GeneralData-DualEndpointEmax ---- |
|
202 | ||
203 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
204 |
#' [`DualEndpointEmax`] model, it is required that there is at least one dose |
|
205 |
#' in the grid. |
|
206 |
#' |
|
207 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
208 |
#' when number of observations in `data` is `0`. For some models it might be |
|
209 |
#' necessary to specify it manually here though. |
|
210 |
#' |
|
211 |
#' @aliases mcmc-GeneralData-DualEndpointEmax |
|
212 |
#' |
|
213 |
setMethod( |
|
214 |
f = "mcmc", |
|
215 |
signature = signature( |
|
216 |
data = "GeneralData", |
|
217 |
model = "DualEndpointEmax", |
|
218 |
options = "McmcOptions" |
|
219 |
), |
|
220 |
def = function(data, |
|
221 |
model, |
|
222 |
options, |
|
223 |
from_prior = data@nObs == 0L, |
|
224 |
...) { |
|
225 | 9x |
assert_true(data@nGrid >= 1) |
226 | ||
227 | 8x |
callNextMethod( |
228 | 8x |
data = data, |
229 | 8x |
model = model, |
230 | 8x |
options = options, |
231 | 8x |
from_prior = from_prior, |
232 |
... |
|
233 |
) |
|
234 |
} |
|
235 |
) |
|
236 | ||
237 |
# mcmc-GeneralData-OneParLogNormalPrior ---- |
|
238 | ||
239 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
240 |
#' [`OneParLogNormalPrior`] model, it is required that the length of |
|
241 |
#' skeleton prior probabilities vector should be equal to the length of the |
|
242 |
#' number of doses. |
|
243 |
#' |
|
244 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
245 |
#' when number of observations in `data` is `0`. For some models it might be |
|
246 |
#' necessary to specify it manually here though. |
|
247 |
#' |
|
248 |
#' @aliases mcmc-GeneralData-OneParLogNormalPrior |
|
249 |
#' |
|
250 |
setMethod( |
|
251 |
f = "mcmc", |
|
252 |
signature = signature( |
|
253 |
data = "GeneralData", |
|
254 |
model = "OneParLogNormalPrior", |
|
255 |
options = "McmcOptions" |
|
256 |
), |
|
257 |
def = function(data, |
|
258 |
model, |
|
259 |
options, |
|
260 |
from_prior = data@nObs == 0L, |
|
261 |
...) { |
|
262 | 9x |
if (!from_prior) { |
263 | 6x |
assert_true(length(model@skel_probs) == data@nGrid) |
264 |
} |
|
265 | ||
266 | 7x |
callNextMethod( |
267 | 7x |
data = data, |
268 | 7x |
model = model, |
269 | 7x |
options = options, |
270 | 7x |
from_prior = from_prior, |
271 |
... |
|
272 |
) |
|
273 |
} |
|
274 |
) |
|
275 | ||
276 |
# mcmc-GeneralData-OneParExpPrior ---- |
|
277 | ||
278 |
#' @describeIn mcmc Standard method which uses JAGS. For the |
|
279 |
#' [`OneParExpPrior`] model, it is required that the length of |
|
280 |
#' skeleton prior probabilities vector should be equal to the length of the |
|
281 |
#' number of doses. |
|
282 |
#' |
|
283 |
#' @param from_prior (`flag`)\cr sample from the prior only? Default to `TRUE` |
|
284 |
#' when number of observations in `data` is `0`. For some models it might be |
|
285 |
#' necessary to specify it manually here though. |
|
286 |
#' |
|
287 |
#' @aliases mcmc-GeneralData-OneParExpPrior |
|
288 |
#' |
|
289 |
setMethod( |
|
290 |
f = "mcmc", |
|
291 |
signature = signature( |
|
292 |
data = "GeneralData", |
|
293 |
model = "OneParExpPrior", |
|
294 |
options = "McmcOptions" |
|
295 |
), |
|
296 |
def = function(data, |
|
297 |
model, |
|
298 |
options, |
|
299 |
from_prior = data@nObs == 0L, |
|
300 |
...) { |
|
301 | 3x |
if (!from_prior) { |
302 | 2x |
assert_true(length(model@skel_probs) == data@nGrid) |
303 |
} |
|
304 | ||
305 | 2x |
callNextMethod( |
306 | 2x |
data = data, |
307 | 2x |
model = model, |
308 | 2x |
options = options, |
309 | 2x |
from_prior = from_prior, |
310 |
... |
|
311 |
) |
|
312 |
} |
|
313 |
) |
|
314 | ||
315 |
# nolint start |
|
316 | ||
317 |
## -------------------------------------------------- |
|
318 |
## The method for DataMixture usage |
|
319 |
## -------------------------------------------------- |
|
320 | ||
321 |
##' @describeIn mcmc Method for DataMixture with different from_prior default |
|
322 |
setMethod("mcmc", |
|
323 |
signature = |
|
324 |
signature( |
|
325 |
data = "DataMixture", |
|
326 |
model = "GeneralModel", |
|
327 |
options = "McmcOptions" |
|
328 |
), |
|
329 |
def = |
|
330 |
function(data, model, options, |
|
331 |
from_prior = data@nObs == 0L & data@nObsshare == 0L, |
|
332 |
...) { |
|
333 | 8x |
callNextMethod(data, model, options, from_prior = from_prior, ...) |
334 |
} |
|
335 |
) |
|
336 | ||
337 | ||
338 |
## -------------------------------------------------- |
|
339 |
## Replacement for BayesLogit::logit |
|
340 |
## -------------------------------------------------- |
|
341 | ||
342 |
#' Do MCMC sampling for Bayesian logistic regression model |
|
343 |
#' |
|
344 |
#' @param y 0/1 vector of responses |
|
345 |
#' @param X design matrix |
|
346 |
#' @param m0 prior mean vector |
|
347 |
#' @param P0 precision matrix |
|
348 |
#' @param options McmcOptions object |
|
349 |
#' |
|
350 |
#' @importFrom rjags jags.model jags.samples |
|
351 |
#' @return the matrix of samples (samples x parameters) |
|
352 |
#' @keywords internal |
|
353 |
myBayesLogit <- function(y, |
|
354 |
X, |
|
355 |
m0, |
|
356 |
P0, |
|
357 |
options) { |
|
358 |
## assertions |
|
359 | 31x |
p <- length(m0) |
360 | 31x |
nObs <- length(y) |
361 | 31x |
stopifnot( |
362 | 31x |
is.vector(y), |
363 | 31x |
all(y %in% c(0, 1)), |
364 | 31x |
is.matrix(P0), |
365 | 31x |
identical(dim(P0), c(p, p)), |
366 | 31x |
is.matrix(X), |
367 | 31x |
identical(dim(X), c(nObs, p)), |
368 | 31x |
is(options, "McmcOptions") |
369 |
) |
|
370 | ||
371 |
## get or set the seed |
|
372 | 31x |
rSeed <- try(get(".Random.seed", envir = .GlobalEnv), |
373 | 31x |
silent = TRUE |
374 |
) |
|
375 | 31x |
if (is(rSeed, "try-error")) { |
376 | ! |
set.seed(floor(runif(n = 1, min = 0, max = 1e4))) |
377 | ! |
rSeed <- get(".Random.seed", envir = .GlobalEnv) |
378 |
} |
|
379 |
## .Random.seed contains two leading integers where the second |
|
380 |
## gives the position in the following 624 long vector (see |
|
381 |
## ?set.seed). Take the current position and ensure positivity |
|
382 | 31x |
rSeed <- abs(rSeed[-c(1:2)][rSeed[2]]) |
383 | ||
384 |
## build the model according to whether we sample from prior |
|
385 |
## or not: |
|
386 | 31x |
bugsModel <- function() { |
387 | ! |
for (i in 1:nObs) |
388 |
{ |
|
389 | ! |
y[i] ~ dbern(p[i]) |
390 | ! |
logit(p[i]) <- mu[i] |
391 |
} |
|
392 | ||
393 | ! |
mu <- X[, ] %*% beta |
394 | ||
395 |
## the multivariate normal prior on the coefficients |
|
396 | ! |
beta ~ dmnorm(priorMean[], priorPrec[, ]) |
397 |
} |
|
398 | ||
399 |
## write the model file into it |
|
400 | 31x |
modelFileName <- h_jags_write_model(bugsModel) |
401 | ||
402 | 31x |
jagsModel <- rjags::jags.model(modelFileName, |
403 | 31x |
data = list( |
404 | 31x |
"X" = X, |
405 | 31x |
"y" = y, |
406 | 31x |
"nObs" = nObs, |
407 | 31x |
priorMean = m0, |
408 | 31x |
priorPrec = P0 |
409 |
), |
|
410 | 31x |
quiet = TRUE, |
411 | 31x |
inits = |
412 |
## add the RNG seed to the inits list: |
|
413 |
## (use Mersenne Twister as per R |
|
414 |
## default) |
|
415 | 31x |
list( |
416 | 31x |
.RNG.name = "base::Mersenne-Twister", |
417 | 31x |
.RNG.seed = rSeed |
418 |
), |
|
419 | 31x |
n.chains = 1, |
420 | 31x |
n.adapt = 0 |
421 |
) |
|
422 |
## burn in |
|
423 | 31x |
update(jagsModel, |
424 | 31x |
n.iter = options@burnin, |
425 | 31x |
progress.bar = "none" |
426 |
) |
|
427 | ||
428 |
## samples |
|
429 | 31x |
samplesCode <- "samples <- |
430 | 31x |
rjags::jags.samples(model=jagsModel, |
431 | 31x |
variable.names='beta', |
432 | 31x |
n.iter= |
433 | 31x |
(options@iterations - options@burnin), |
434 | 31x |
thin=options@step, |
435 | 31x |
progress.bar='none')" |
436 | ||
437 |
## this is necessary because some outputs |
|
438 |
## are written directly from the JAGS compiled |
|
439 |
## code to the outstream |
|
440 | 31x |
capture.output(eval(parse(text = samplesCode))) |
441 | ||
442 | 31x |
return(t(samples$beta[, , 1L])) |
443 |
} |
|
444 | ||
445 | ||
446 |
## ---------------------------------------------------------------------------------- |
|
447 |
## Obtain posterior samples for the two-parameter logistic pseudo DLE model |
|
448 |
## ------------------------------------------------------------------------------- |
|
449 | ||
450 | ||
451 |
##' @describeIn mcmc Obtain posterior samples for the model parameters based on the pseudo 'LogisticsIndepBeta' |
|
452 |
##' DLE model. The joint prior and posterior probability density function of |
|
453 |
##' the intercept \eqn{\phi_1} (phi1) and the slope \eqn{\phi_2} (phi2) are given in Whitehead and |
|
454 |
##' Williamson (1998) and TsuTakawa (1975). However, since asymptotically, the joint posterior probability density |
|
455 |
##' will be bivariate normal and we will use the bivariate normal distribution to |
|
456 |
##' generate posterior samples of the intercept and the slope parameters. For the prior samples of |
|
457 |
##' of the intercept and the slope a bivariate normal distribution with mean and the covariance matrix given in Whitehead and |
|
458 |
##' Williamson (1998) is used. |
|
459 |
##' |
|
460 |
##' @importFrom mvtnorm rmvnorm |
|
461 |
##' @example examples/mcmc-LogisticIndepBeta.R |
|
462 |
setMethod("mcmc", |
|
463 |
signature = |
|
464 |
signature( |
|
465 |
data = "Data", |
|
466 |
model = "LogisticIndepBeta", |
|
467 |
options = "McmcOptions" |
|
468 |
), |
|
469 |
def = |
|
470 |
function(data, model, options, |
|
471 |
...) { |
|
472 |
## update the DLE model first |
|
473 | 40x |
thismodel <- update(object = model, data = data) |
474 | ||
475 |
## decide whether we sample from the prior or not |
|
476 | 40x |
from_prior <- data@nObs == 0L |
477 | ||
478 | ||
479 |
## probabilities of risk of DLE at all dose levels |
|
480 | 40x |
pi <- (thismodel@binDLE) / (thismodel@DLEweights) |
481 |
## scalar term for the covariance matrix |
|
482 | 40x |
scalarI <- thismodel@DLEweights * pi * (1 - pi) |
483 |
## |
|
484 | 40x |
precision <- matrix(rep(0, 4), nrow = 2, ncol = 2) |
485 | ||
486 | 40x |
for (i in (1:(length(thismodel@binDLE)))) { |
487 | 80x |
precisionmat <- scalarI[i] * matrix(c(1, log(thismodel@DLEdose[i]), log(thismodel@DLEdose[i]), (log(thismodel@DLEdose[i]))^2), 2, 2) |
488 | 80x |
precision <- precision + precisionmat |
489 |
} |
|
490 | ||
491 | 40x |
if (from_prior) { |
492 |
## sample from the (asymptotic) bivariate normal prior for theta |
|
493 | ||
494 | 9x |
tmp <- mvtnorm::rmvnorm( |
495 | 9x |
n = size(options), |
496 | 9x |
mean = c(slot(thismodel, "phi1"), slot(thismodel, "phi2")), |
497 | 9x |
sigma = solve(precision) |
498 |
) |
|
499 | ||
500 | ||
501 | 9x |
samples <- list( |
502 | 9x |
phi1 = tmp[, 1], |
503 | 9x |
phi2 = tmp[, 2] |
504 |
) |
|
505 |
} else { |
|
506 | 31x |
weights <- rep(1, length(data@y)) |
507 |
## probabilities of risk of DLE at all dose levels |
|
508 | 31x |
pi <- (data@y) / weights |
509 |
## scalar term for the covariance matrix |
|
510 | 31x |
scalarI <- weights * pi * (1 - pi) |
511 |
## |
|
512 | ||
513 | 31x |
priordle <- thismodel@binDLE |
514 | 31x |
priorw1 <- thismodel@DLEweights |
515 | ||
516 | 31x |
priordose <- thismodel@DLEdose |
517 | 31x |
FitDLE <- suppressWarnings(glm(priordle / priorw1 ~ log(priordose), family = binomial(link = "logit"), weights = priorw1)) |
518 | 31x |
SFitDLE <- summary(FitDLE) |
519 |
## Obtain parameter estimates for dose-DLE curve |
|
520 | 31x |
priorphi1 <- coef(SFitDLE)[1, 1] |
521 | 31x |
priorphi2 <- coef(SFitDLE)[2, 1] |
522 | ||
523 |
## use fast special sampler here |
|
524 |
## set up design matrix |
|
525 | 31x |
X <- cbind(1, log(data@x)) |
526 | 31x |
initRes <- myBayesLogit( |
527 | 31x |
y = data@y, |
528 | 31x |
X = X, |
529 | 31x |
m0 = c(priorphi1, priorphi2), |
530 | 31x |
P0 = precision, |
531 | 31x |
options = options |
532 |
) |
|
533 | ||
534 |
## then form the samples list |
|
535 | 31x |
samples <- list( |
536 | 31x |
phi1 = initRes[, 1], |
537 | 31x |
phi2 = initRes[, 2] |
538 |
) |
|
539 |
} |
|
540 | ||
541 |
## form a Samples object for return: |
|
542 | 40x |
ret <- Samples( |
543 | 40x |
data = samples, |
544 | 40x |
options = options |
545 |
) |
|
546 | ||
547 | 40x |
return(ret) |
548 |
} |
|
549 |
) |
|
550 | ||
551 |
## ================================================================================ |
|
552 | ||
553 |
## ----------------------------------------------------------------------------------- |
|
554 |
## obtain the posterior samples for the Pseudo Efficacy log log model |
|
555 |
## ---------------------------------------------------------------------------- |
|
556 |
## |
|
557 |
##' @describeIn mcmc Obtain the posterior samples for the model parameters in the |
|
558 |
##' Efficacy log log model. Given the value of \eqn{\nu}, the precision of the efficacy responses, |
|
559 |
##' the joint prior or the posterior probability of the intercept \eqn{\theta_1} (theta1) and |
|
560 |
##' the slope \eqn{\theta_2} (theta2) is a bivariate normal distribution. The \eqn{\nu} (nu), |
|
561 |
##' the precision of the efficacy responses is either a fixed value or has a gamma distribution. |
|
562 |
##' If a gamma distribution is used, the samples of nu will be first generated. |
|
563 |
##' Then the mean of the of the nu samples |
|
564 |
##' will be used the generate samples of the intercept and slope parameters of the model |
|
565 |
##' @example examples/mcmc-Effloglog.R |
|
566 |
##' @importFrom mvtnorm rmvnorm |
|
567 |
setMethod( |
|
568 |
f = "mcmc", |
|
569 |
signature = signature( |
|
570 |
data = "DataDual", |
|
571 |
model = "Effloglog", |
|
572 |
options = "McmcOptions" |
|
573 |
), |
|
574 |
definition = function(data, model, options, ...) { |
|
575 | 27x |
model <- update(object = model, data = data) |
576 | 27x |
sample_size <- size(options) |
577 | ||
578 | 27x |
if (model@use_fixed) { |
579 | ! |
nu <- model@nu |
580 | ! |
nu_samples <- rep(nu, sample_size) |
581 |
} else { |
|
582 | 27x |
nu_samples <- rgamma(sample_size, shape = model@nu["a"], rate = model@nu["b"]) |
583 | 27x |
nu <- mean(nu_samples) |
584 |
} |
|
585 | ||
586 |
# Sample from the (asymptotic) bivariate normal prior for theta1 and theta2. |
|
587 | 27x |
tmp <- mvtnorm::rmvnorm( |
588 | 27x |
n = sample_size, |
589 | 27x |
mean = model@mu, |
590 | 27x |
sigma = solve(nu * model@Q) |
591 |
) |
|
592 | ||
593 | 27x |
samples <- list( |
594 | 27x |
theta1 = tmp[, 1], |
595 | 27x |
theta2 = tmp[, 2], |
596 | 27x |
nu = nu_samples |
597 |
) |
|
598 | ||
599 | 27x |
Samples( |
600 | 27x |
data = samples, |
601 | 27x |
options = options |
602 |
) |
|
603 |
} |
|
604 |
) |
|
605 |
## ====================================================================================== |
|
606 |
## ----------------------------------------------------------------------------------- |
|
607 |
## obtain the posterior samples for the Pseudo Efficacy Flexible form |
|
608 |
## ---------------------------------------------------------------------------- |
|
609 |
## |
|
610 |
##' @describeIn mcmc Obtain the posterior samples for the estimates in the Efficacy Flexible form. |
|
611 |
##' This is the mcmc procedure based on what is described in Lang and Brezger (2004) such that |
|
612 |
##' samples of the mean efficacy responses at all dose levels, samples of sigma2 \eqn{sigma^2}, |
|
613 |
##' the variance of the efficacy response and samples of sigma2betaW \eqn{sigma^2_{beta_W}}, the variance of |
|
614 |
##' the random walk model will |
|
615 |
##' be generated. Please refer to Lang and Brezger (2004) for the procedures and the form of |
|
616 |
##' the joint prior and posterior probability density for the mean efficacy responses. In addition, |
|
617 |
##' both sigma2 and sigma2betaW can be fixed or having an inverse-gamma prior and posterior distribution. |
|
618 |
##' Therefore, if the inverse gamma distribution(s) are used, the parameters in the distribution will be |
|
619 |
##' first updated and then samples of sigma2 and sigma2betaW will be generated using the updated parameters. |
|
620 |
##' @example examples/mcmc-EffFlexi.R |
|
621 |
setMethod("mcmc", |
|
622 |
signature = |
|
623 |
signature( |
|
624 |
data = "DataDual", |
|
625 |
model = "EffFlexi", |
|
626 |
options = "McmcOptions" |
|
627 |
), |
|
628 |
def = |
|
629 |
function(data, model, options, |
|
630 |
...) { |
|
631 |
## update the model |
|
632 | 6x |
thismodel <- update(object = model, data = data) |
633 | ||
634 | 6x |
nSamples <- size(options) |
635 | ||
636 |
## Prepare samples container |
|
637 |
### List parameter samples to save |
|
638 | 6x |
samples <- list( |
639 | 6x |
ExpEff = matrix(ncol = data@nGrid, nrow = nSamples), |
640 | 6x |
sigma2W = matrix(nrow = nSamples), |
641 | 6x |
sigma2betaW = matrix(nrow = nSamples) |
642 |
) |
|
643 |
## Prepare starting values |
|
644 |
## Index of the next sample to be saved: |
|
645 | ||
646 | 6x |
iterSave <- 1L |
647 |
## Monitoring the Metropolis-Hastings update for sigma2 |
|
648 | ||
649 | 6x |
acceptHistory <- list(sigma2W = logical(options@iterations)) |
650 | ||
651 |
## Current parameter values and also the starting values for the MCMC are set |
|
652 |
## EstEff: constant, the average of the observed efficacy values |
|
653 | ||
654 | 6x |
if (length(data@w) == 0) { |
655 | 2x |
w1 <- thismodel@eff |
656 | 2x |
x1 <- thismodel@eff_dose |
657 |
} else { |
|
658 |
## Combine pseudo data with observed efficacy responses and no DLT observed |
|
659 | 4x |
eff_obsrv <- getEff(data, no_dlt = TRUE) |
660 | 4x |
w1 <- c(thismodel@eff, eff_obsrv$w_no_dlt) |
661 | 4x |
x1 <- c(thismodel@eff_dose, eff_obsrv$x_no_dlt) |
662 |
} |
|
663 | 6x |
x1Level <- match_within_tolerance(x1, data@doseGrid) |
664 |
## betaW is constant, the average of the efficacy values |
|
665 | 6x |
betaW <- rep(mean(w1), data@nGrid) |
666 |
## sigma2betaW use fixed value or prior mean |
|
667 | 6x |
sigma2betaW <- |
668 | 6x |
if (thismodel@use_fixed[["sigma2betaW"]]) { |
669 | ! |
thismodel@sigma2betaW |
670 |
} else { |
|
671 | 6x |
thismodel@sigma2betaW["b"] / (thismodel@sigma2betaW["a"] - 1) |
672 |
} |
|
673 |
## sigma2: fixed value or just the empirical variance |
|
674 | 6x |
sigma2W <- if (thismodel@use_fixed[["sigma2W"]]) { |
675 | ! |
thismodel@sigma2W |
676 |
} else { |
|
677 | 6x |
var(w1) |
678 |
} |
|
679 |
## Set up diagonal matrix with the number of patients in the corresponding dose levels on the diagonal |
|
680 | 6x |
designWcrossprod <- crossprod(thismodel@X) |
681 | ||
682 |
### The MCMC cycle |
|
683 | ||
684 | 6x |
for (iterMcmc in seq_len(options@iterations)) |
685 |
{ ## 1) Generate coefficients for the Flexible Efficacy model |
|
686 |
## the variance |
|
687 | 21732x |
adjustedVar <- sigma2W |
688 |
## New precision matrix |
|
689 | 21732x |
thisPrecW <- designWcrossprod / adjustedVar + thismodel@RW / sigma2betaW |
690 |
## draw random normal vector |
|
691 | 21732x |
normVec <- rnorm(data@nGrid) |
692 |
## and its Cholesky factor |
|
693 | 21732x |
thisPrecWchol <- chol(thisPrecW) |
694 |
## solve betaW for L^T * betaW = normVec |
|
695 | 21732x |
betaW <- backsolve(r = thisPrecWchol, x = normVec) |
696 |
## the residual |
|
697 | 21732x |
adjustedW <- w1 - thismodel@X %*% betaW |
698 | ||
699 |
## forward substitution |
|
700 |
## solve L^T * tmp = designW ^T * adjustedW/ adjustedVar |
|
701 | ||
702 | 21732x |
tmp <- forwardsolve( |
703 | 21732x |
l = thisPrecWchol, |
704 | 21732x |
x = crossprod(thismodel@X, adjustedW) / adjustedVar, |
705 | 21732x |
upper.tri = TRUE, |
706 | 21732x |
transpose = TRUE |
707 |
) |
|
708 |
## Backward substitution solve R*tepNew =tmp |
|
709 | 21732x |
tmp <- backsolve( |
710 | 21732x |
r = thisPrecWchol, |
711 | 21732x |
x = tmp |
712 |
) |
|
713 | ||
714 |
## tmp is the mean vector of the distribution |
|
715 |
## add tmp to betaW to obtain final sample |
|
716 | ||
717 | 21732x |
betaW <- betaW + tmp |
718 | ||
719 |
## 2) Generate prior variance factor for the random walk |
|
720 |
## if fixed, do nothing |
|
721 |
## Otherwise sample from full condition |
|
722 | ||
723 | 21732x |
if (!thismodel@use_fixed[["sigma2betaW"]]) { |
724 | 21732x |
sigma2betaW <- rinvGamma( |
725 | 21732x |
n = 1L, |
726 | 21732x |
a = thismodel@sigma2betaW["a"] + thismodel@RW_rank / 2, |
727 | 21732x |
b = thismodel@sigma2betaW["b"] + crossprod(betaW, thismodel@RW %*% betaW) / 2 |
728 |
) |
|
729 |
} |
|
730 |
## 3) Generate variance for the flexible efficacy model |
|
731 |
## if fixed variance is used |
|
732 | 21732x |
if (thismodel@use_fixed[["sigma2W"]]) { ## do nothing |
733 | ! |
acceptHistory$sigma2W[iterMcmc] <- TRUE |
734 |
} else { |
|
735 |
## Metropolis-Hastings update step here, using |
|
736 |
## an inverse gamma distribution |
|
737 | 21732x |
aStar <- thismodel@sigma2W["a"] + length(x1) / 2 |
738 |
## Second parameter bStar depends on the value for sigma2W |
|
739 | 21732x |
bStar <- function(x) { |
740 | 21732x |
adjW <- w1 |
741 | 21732x |
ret <- sum((adjW - betaW[x1Level])^2) / 2 + thismodel@sigma2W["b"] |
742 | 21732x |
return(ret) |
743 |
} |
|
744 |
### Draw proposal: |
|
745 | 21732x |
bStarProposal <- bStar(sigma2W) |
746 | 21732x |
sigma2W <- rinvGamma(n = 1L, a = aStar, b = bStarProposal) |
747 |
} |
|
748 | ||
749 | ||
750 |
## 4)Save Samples |
|
751 | ||
752 | 21732x |
if (saveSample(options, iterMcmc)) { |
753 | 10216x |
samples$ExpEff[iterSave, ] <- betaW |
754 | 10216x |
samples$sigma2W[iterSave, 1] <- sigma2W |
755 | 10216x |
samples$sigma2betaW[iterSave, 1] <- sigma2betaW |
756 | 10216x |
iterSave <- iterSave + 1L |
757 |
} |
|
758 |
} |
|
759 | ||
760 | ||
761 | 6x |
ret <- Samples( |
762 | 6x |
data = samples, |
763 | 6x |
options = options |
764 |
) |
|
765 | 6x |
return(ret) |
766 |
} |
|
767 |
) |
|
768 |
# nolint end |
|
769 | ||
770 |
## ----------------------------------------------------------------------------------- |
|
771 |
## obtain the posterior samples for ordinal models |
|
772 |
## ---------------------------------------------------------------------------- |
|
773 |
## |
|
774 |
##' @describeIn mcmc Obtain the posterior samples for the model parameters in the |
|
775 |
##' `LogisticLogNormalOrdinal`. |
|
776 |
##' |
|
777 |
##' The generic `mcmc` method returns a `Samples` object with elements of the |
|
778 |
##' `data` slot named `alpha[1]`, `alpha[2]`, ..., `alpha[k]` and `beta` when |
|
779 |
##' passed a `LogisticLogNormalOrdinal` object. This makes the "alpha elements" |
|
780 |
##' awkward to access and is inconsistent with other `Model` objects. So rename |
|
781 |
##' the alpha elements to `alpha1`, `alpha2`, ..., `alpha<k>` for ease and |
|
782 |
##' consistency. |
|
783 |
##' |
|
784 |
##' @example examples/mcmc-LogisticLogNormalOrdinal.R |
|
785 |
setMethod( |
|
786 |
f = "mcmc", |
|
787 |
signature = signature( |
|
788 |
data = "DataOrdinal", |
|
789 |
model = "LogisticLogNormalOrdinal", |
|
790 |
options = "McmcOptions" |
|
791 |
), |
|
792 |
definition = function(data, model, options, ...) { |
|
793 |
# Obtain samples using the default method, but ... |
|
794 | 15x |
return_value <- callNextMethod() |
795 |
# ... rename the alpha elements from alpha[<k>] to alpha<k>, where <k> is an |
|
796 |
# integer |
|
797 | 15x |
names(return_value@data) <- gsub("\\[(\\d+)\\]", "\\1", names(return_value@data)) |
798 | 15x |
return_value |
799 |
} |
|
800 |
) |
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(object@init, allowed = union(object@datanames, object@datanames_prior)), |
22 | 2x |
"Arguments of the init function must be data names" |
23 |
) |
|
24 | 2x |
v$result() |
25 |
} |
|
26 | ||
27 |
#' @describeIn v_model_objects validates that the logistic Kadane model |
|
28 |
#' parameters are valid. |
|
29 |
v_model_logistic_kadane <- function(object) { |
|
30 | 13x |
v <- Validate() |
31 | 13x |
v$check( |
32 | 13x |
test_probability(object@theta, bounds_closed = FALSE), |
33 | 13x |
"theta must be a probability scalar > 0 and < 1" |
34 |
) |
|
35 | 13x |
is_xmin_number <- test_number(object@xmin) |
36 | 13x |
v$check(is_xmin_number, "xmin must be scalar") |
37 | ||
38 | 13x |
is_xmax_number <- test_number(object@xmax) |
39 | 13x |
v$check(is_xmax_number, "xmax must be scalar") |
40 | ||
41 | 13x |
if (is_xmin_number && is_xmax_number) { |
42 | 11x |
v$check(object@xmin < object@xmax, "xmin must be strictly smaller than xmax") |
43 |
} |
|
44 | 13x |
v$result() |
45 |
} |
|
46 | ||
47 |
#' @describeIn v_model_objects validates that the logistic Kadane model |
|
48 |
#' parameters with a beta and gamma prior are valid. |
|
49 |
v_model_logistic_kadane_beta_gamma <- function(object) { # nolintr |
|
50 | 10x |
v <- Validate() |
51 | 10x |
v$check( |
52 | 10x |
test_number(object@alpha, lower = .Machine$double.xmin, finite = TRUE), |
53 | 10x |
"Beta distribution shape parameter alpha must be a positive scalar" |
54 |
) |
|
55 | 10x |
v$check( |
56 | 10x |
test_number(object@beta, lower = .Machine$double.xmin, finite = TRUE), |
57 | 10x |
"Beta distribution shape parameter beta must be a positive scalar" |
58 |
) |
|
59 | 10x |
v$check( |
60 | 10x |
test_number(object@shape, lower = .Machine$double.xmin, finite = TRUE), |
61 | 10x |
"Gamma distribution shape parameter must be a positive scalar" |
62 |
) |
|
63 | 10x |
v$check( |
64 | 10x |
test_number(object@rate, lower = .Machine$double.xmin, finite = TRUE), |
65 | 10x |
"Gamma distribution rate parameter must be a positive scalar" |
66 |
) |
|
67 | 10x |
v$result() |
68 |
} |
|
69 | ||
70 |
#' @describeIn v_model_objects validates that `weightpar` is valid. |
|
71 |
v_model_logistic_normal_mix <- function(object) { |
|
72 | 8x |
v <- Validate() |
73 | 8x |
v$check( |
74 | 8x |
h_test_named_numeric(object@weightpar, permutation.of = c("a", "b")), |
75 | 8x |
"weightpar must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
76 |
) |
|
77 | 8x |
v$result() |
78 |
} |
|
79 | ||
80 |
#' @describeIn v_model_objects validates that `component` is a list with |
|
81 |
#' valid `ModelParamsNormal` objects as well as `weights` are correct. |
|
82 |
v_model_logistic_normal_fixed_mix <- function(object) { # nolintr |
|
83 | 7x |
v <- Validate() |
84 | 7x |
v$check( |
85 | 7x |
all(sapply(object@components, test_class, "ModelParamsNormal")), |
86 | 7x |
"components must be a list with ModelParamsNormal S4 class objects" |
87 |
) |
|
88 | 7x |
comp_valid_result <- sapply(object@components, validObject, test = TRUE) |
89 | 7x |
comp_valid <- sapply(comp_valid_result, isTRUE) |
90 | 7x |
v$check( |
91 | 7x |
all(comp_valid), |
92 | 7x |
paste( |
93 | 7x |
"components must be a list with valid ModelParamsNormal S4 class objects", |
94 | 7x |
paste(unlist(comp_valid_result[!comp_valid]), collapse = ", "), |
95 | 7x |
collapse = ", ", |
96 | 7x |
sep = ", " |
97 |
) |
|
98 |
) |
|
99 | 7x |
v$check( |
100 | 7x |
length(object@components) == length(object@weights), |
101 | 7x |
"components must have same length as weights" |
102 |
) |
|
103 | 7x |
v$check( |
104 | 7x |
test_numeric(object@weights, lower = .Machine$double.xmin, finite = TRUE, any.missing = FALSE), |
105 | 7x |
"weights must be positive" |
106 |
) |
|
107 | 7x |
v$check( |
108 | 7x |
sum(object@weights) == 1, |
109 | 7x |
"weights must sum to 1" |
110 |
) |
|
111 | 7x |
v$check( |
112 | 7x |
test_flag(object@log_normal), |
113 | 7x |
"log_normal must be TRUE or FALSE" |
114 |
) |
|
115 | 7x |
v$result() |
116 |
} |
|
117 | ||
118 |
#' @describeIn v_model_objects validates that `share_weight` represents probability. |
|
119 |
v_model_logistic_log_normal_mix <- function(object) { # nolintr |
|
120 | 3x |
v <- Validate() |
121 | 3x |
v$check( |
122 | 3x |
test_probability(object@share_weight), |
123 | 3x |
"share_weight does not specify a probability" |
124 |
) |
|
125 | 3x |
v$result() |
126 |
} |
|
127 | ||
128 |
#' @describeIn v_model_objects validates that [`DualEndpoint`] class slots are valid. |
|
129 |
v_model_dual_endpoint <- function(object) { |
|
130 | 8x |
rmin <- .Machine$double.xmin |
131 | 8x |
v <- Validate() |
132 | ||
133 | 8x |
v$check( |
134 | 8x |
test_flag(object@use_log_dose), |
135 | 8x |
"use_log_dose must be TRUE or FALSE" |
136 |
) |
|
137 | 8x |
uf_sigma2W <- object@use_fixed["sigma2W"] # nolintr |
138 | 8x |
v$check( |
139 | 8x |
test_flag(uf_sigma2W), |
140 | 8x |
"use_fixed must be a named logical vector that contains name 'sigma2W'" |
141 |
) |
|
142 | 8x |
uf_rho <- object@use_fixed["rho"] |
143 | 8x |
v$check( |
144 | 8x |
test_flag(uf_rho), |
145 | 8x |
"use_fixed must be a named logical vector that contains name 'rho'" |
146 |
) |
|
147 | ||
148 | 8x |
if (isTRUE(uf_sigma2W)) { |
149 | 5x |
v$check( |
150 | 5x |
test_number(object@sigma2W, lower = rmin, finite = TRUE), |
151 | 5x |
"sigma2W must be a positive and finite numerical scalar" |
152 |
) |
|
153 |
} else { |
|
154 |
# object@sigma2W is a vector with parameters for InverseGamma(a, b). |
|
155 | 3x |
v$check( |
156 | 3x |
h_test_named_numeric(object@sigma2W, permutation.of = c("a", "b")), |
157 | 3x |
"sigma2W must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
158 |
) |
|
159 |
} |
|
160 | ||
161 | 8x |
if (isTRUE(uf_rho)) { |
162 | 5x |
v$check( |
163 | 5x |
test_number(object@rho, lower = -1 + rmin, upper = 1 - rmin), # rmin is ignored here! |
164 | 5x |
"rho must be a number in (-1, 1)" |
165 |
) |
|
166 |
} else { |
|
167 |
# object@rho is a vector with parameters for Beta(a, b). |
|
168 | 3x |
v$check( |
169 | 3x |
h_test_named_numeric(object@rho, permutation.of = c("a", "b")), |
170 | 3x |
"rho must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
171 |
) |
|
172 |
} |
|
173 | ||
174 | 8x |
v$result() |
175 |
} |
|
176 | ||
177 |
#' @describeIn v_model_objects validates that [`DualEndpointRW`] class slots are valid. |
|
178 |
v_model_dual_endpoint_rw <- function(object) { |
|
179 | 5x |
v <- Validate() |
180 | 5x |
uf_sigma2W <- object@use_fixed["sigma2betaW"] # nolintr |
181 | 5x |
v$check( |
182 | 5x |
test_flag(uf_sigma2W), |
183 | 5x |
"use_fixed must be a named logical vector that contains name 'sigma2betaW'" |
184 |
) |
|
185 | 5x |
if (isTRUE(uf_sigma2W)) { |
186 | 2x |
v$check( |
187 | 2x |
test_number(object@sigma2betaW, lower = .Machine$double.xmin, finite = TRUE), |
188 | 2x |
"sigma2betaW must be a positive and finite numerical scalar" |
189 |
) |
|
190 |
} else { |
|
191 |
# object@sigma2betaW is a vector with parameters for InverseGamma(a, b). |
|
192 | 3x |
v$check( |
193 | 3x |
h_test_named_numeric(object@sigma2betaW, permutation.of = c("a", "b")), |
194 | 3x |
"sigma2betaW must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
195 |
) |
|
196 |
} |
|
197 | 5x |
v$result() |
198 |
} |
|
199 | ||
200 |
#' @describeIn v_model_objects validates that [`DualEndpointBeta`] class slots are valid. |
|
201 |
v_model_dual_endpoint_beta <- function(object) { |
|
202 | 5x |
v <- Validate() |
203 | ||
204 | 5x |
for (s in c("E0", "Emax", "delta1", "mode")) { |
205 | 20x |
rmin <- .Machine$double.xmin |
206 | 20x |
uf <- object@use_fixed[s] |
207 | ||
208 | 20x |
v$check( |
209 | 20x |
test_flag(uf), |
210 | 20x |
paste0("use_fixed must be a named logical vector that contains name '", s, "'") |
211 |
) |
|
212 | 20x |
if (isTRUE(uf)) { |
213 | 12x |
if (s %in% c("delta1", "mode")) { |
214 | 8x |
v$check( |
215 | 8x |
test_number(slot(object, s), lower = rmin, finite = TRUE), |
216 | 8x |
paste(s, "must be a positive and finite numerical scalar") |
217 |
) |
|
218 |
} |
|
219 |
} else { |
|
220 |
# s is a vector with parameters for Uniform(s[1], s[2]) prior. |
|
221 | 8x |
v$check( |
222 | 8x |
test_numeric( |
223 | 8x |
slot(object, s), |
224 | 8x |
lower = 0, |
225 | 8x |
finite = TRUE, |
226 | 8x |
any.missing = FALSE, |
227 | 8x |
len = 2, |
228 | 8x |
unique = TRUE, |
229 | 8x |
sorted = TRUE |
230 |
), |
|
231 | 8x |
paste(s, "must be a numerical vector of length two with non-negative, finite, unique and sorted (asc.) values") |
232 |
) |
|
233 |
} |
|
234 |
} |
|
235 | ||
236 | 5x |
v$result() |
237 |
} |
|
238 | ||
239 |
#' @describeIn v_model_objects validates that [`DualEndpointEmax`] class slots are valid. |
|
240 |
v_model_dual_endpoint_emax <- function(object) { |
|
241 | 4x |
v <- Validate() |
242 | ||
243 | 4x |
for (s in c("E0", "Emax", "ED50")) { |
244 | 12x |
rmin <- .Machine$double.xmin |
245 | 12x |
uf <- object@use_fixed[s] |
246 | ||
247 | 12x |
v$check( |
248 | 12x |
test_flag(uf), |
249 | 12x |
paste0("use_fixed must be a named logical vector that contains name '", s, "'") |
250 |
) |
|
251 | 12x |
if (isTRUE(uf)) { |
252 | 6x |
v$check( |
253 | 6x |
test_number(slot(object, s), lower = rmin, finite = TRUE), |
254 | 6x |
paste(s, "must be a positive and finite numerical scalar") |
255 |
) |
|
256 |
} else { |
|
257 |
# s is a vector with parameters for Uniform(s[1], s[2]) prior. |
|
258 | 6x |
v$check( |
259 | 6x |
test_numeric( |
260 | 6x |
slot(object, s), |
261 | 6x |
lower = 0, |
262 | 6x |
finite = TRUE, |
263 | 6x |
any.missing = FALSE, |
264 | 6x |
len = 2, |
265 | 6x |
unique = TRUE, |
266 | 6x |
sorted = TRUE |
267 |
), |
|
268 | 6x |
paste(s, "must be a numerical vector of length two with non-negative, finite, unique and sorted (asc.) values") |
269 |
) |
|
270 |
} |
|
271 |
} |
|
272 | ||
273 | 4x |
v$result() |
274 |
} |
|
275 | ||
276 |
#' @describeIn v_model_objects validates that [`LogisticIndepBeta`] class slots are valid. |
|
277 |
v_model_logistic_indep_beta <- function(object) { |
|
278 | 8x |
v <- Validate() |
279 | ||
280 | 8x |
dle_len <- length(object@binDLE) |
281 | 8x |
v$check( |
282 | 8x |
test_numeric(object@binDLE, finite = TRUE, any.missing = FALSE, min.len = 2), |
283 | 8x |
"binDLE must be a finite numerical vector of minimum length 2, without missing values" |
284 |
) |
|
285 | 8x |
v$check( |
286 | 8x |
test_numeric(object@DLEdose, finite = TRUE, any.missing = FALSE, len = dle_len), |
287 | 8x |
"DLEdose must be a finite numerical vector of the same length as 'binDLE', without missing values" |
288 |
) |
|
289 | 8x |
v$check( |
290 | 8x |
test_integer(object@DLEweights, any.missing = FALSE, len = dle_len), |
291 | 8x |
"DLEweights must be an integer vector of the same length as 'binDLE', without missing values" |
292 |
) |
|
293 | 8x |
v$check( |
294 | 8x |
test_number(object@phi1), |
295 | 8x |
"phi1 must be a numerical scalar" |
296 |
) |
|
297 | 8x |
v$check( |
298 | 8x |
test_number(object@phi2), |
299 | 8x |
"phi2 must be a numerical scalar" |
300 |
) |
|
301 | 8x |
v$check( |
302 | 8x |
h_is_positive_definite(object@Pcov), |
303 | 8x |
"Pcov must be 2x2 positive-definite matrix without any missing values" |
304 |
) |
|
305 | 8x |
v$result() |
306 |
} |
|
307 | ||
308 |
#' @describeIn v_model_objects validates that [`Effloglog`] class slots are valid. |
|
309 |
v_model_eff_log_log <- function(object) { |
|
310 | 27x |
rmin <- .Machine$double.xmin |
311 | ||
312 | 27x |
v <- Validate() |
313 | 27x |
v$check( |
314 | 27x |
test_numeric(object@eff, finite = TRUE, any.missing = FALSE, min.len = 2), |
315 | 27x |
"eff must be a finite numerical vector of minimum length 2, without missing values" |
316 |
) |
|
317 | 27x |
eff_dose_ok <- test_numeric( |
318 | 27x |
object@eff_dose, |
319 | 27x |
lower = rmin, finite = TRUE, any.missing = FALSE, len = length(object@eff) |
320 |
) |
|
321 | 27x |
v$check( |
322 | 27x |
eff_dose_ok, |
323 | 27x |
"eff_dose must be a finite numerical vector of the same length as 'eff', without missing values" |
324 |
) |
|
325 | 27x |
v$check( |
326 | 27x |
test_flag(object@use_fixed), |
327 | 27x |
"use_fixed must be a flag" |
328 |
) |
|
329 | 27x |
if (isTRUE(object@use_fixed)) { |
330 | 1x |
v$check( |
331 | 1x |
test_number(object@nu, lower = rmin, finite = TRUE), |
332 | 1x |
"nu must be a positive and finite numerical scalar" |
333 |
) |
|
334 |
} else { |
|
335 |
# object@nu is a vector with parameters for Gamma(a, b). |
|
336 | 26x |
v$check( |
337 | 26x |
h_test_named_numeric(object@nu, permutation.of = c("a", "b")), |
338 | 26x |
"nu must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
339 |
) |
|
340 |
} |
|
341 | 27x |
const_ok <- test_number(object@const, lower = 0) |
342 | 27x |
v$check(const_ok, "const must be a non-negative number") |
343 | 27x |
if (eff_dose_ok && const_ok) { |
344 | 23x |
v$check( |
345 | 23x |
min(object@data@doseGrid, object@eff_dose) > 1 - object@const, |
346 | 23x |
"For log-log model, doses and const must be such that dose + const > 1" |
347 |
) |
|
348 |
} |
|
349 | 27x |
v$check( |
350 | 27x |
test_number(object@theta1), |
351 | 27x |
"theta1 must be a numerical scalar" |
352 |
) |
|
353 | 27x |
v$check( |
354 | 27x |
test_number(object@theta2), |
355 | 27x |
"theta2 must be a numerical scalar" |
356 |
) |
|
357 | 27x |
nobs_no_dlt <- sum(!object@data@y) |
358 | 27x |
if (nobs_no_dlt + length(object@eff) > 2) { |
359 | 21x |
v$check( |
360 | 21x |
h_is_positive_definite(object@Pcov), |
361 | 21x |
"Pcov must be 2x2 positive-definite matrix without any missing values" |
362 |
) |
|
363 |
} else { |
|
364 | 6x |
v$check( |
365 | 6x |
test_matrix(object@Pcov, mode = "numeric", nrows = 2, ncols = 2) && all(is.na(object@Pcov)), |
366 | 6x |
"Pcov must be 2x2 numeric matrix with all values missing if the length of combined data is 2" |
367 |
) |
|
368 |
} |
|
369 | 27x |
v$check( |
370 | 27x |
test_numeric(object@mu, finite = TRUE, len = 2), |
371 | 27x |
"mu must be a finite numerical vector of length 2" |
372 |
) |
|
373 | 27x |
Xnrow <- ifelse(nobs_no_dlt > 0, nobs_no_dlt, length(object@eff_dose)) |
374 | 27x |
v$check( |
375 | 27x |
test_matrix(object@X, mode = "numeric", nrows = Xnrow, ncols = 2, any.missing = FALSE), |
376 | 27x |
paste( |
377 | 27x |
"X must be a finite numerical matrix of size", |
378 | 27x |
Xnrow, |
379 | 27x |
"x 2, without any missing values" |
380 |
) |
|
381 |
) |
|
382 | 27x |
v$check( |
383 | 27x |
all(object@X[, 1] == 1), |
384 | 27x |
"X must be a design matrix, i.e. first column must be of 1s" |
385 |
) |
|
386 | 27x |
v$check( |
387 | 27x |
h_is_positive_definite(object@Q), |
388 | 27x |
"Q must be 2x2 positive-definite matrix without any missing values" |
389 |
) |
|
390 | 27x |
v$check( |
391 | 27x |
test_numeric(object@Y, finite = TRUE, any.missing = FALSE, len = Xnrow), |
392 | 27x |
paste( |
393 | 27x |
"Y must be a finite numerical vector of length", |
394 | 27x |
Xnrow, |
395 | 27x |
"and without any missing values" |
396 |
) |
|
397 |
) |
|
398 | 27x |
v$result() |
399 |
} |
|
400 | ||
401 |
#' @describeIn v_model_objects validates that [`EffFlexi`] class slots are valid. |
|
402 |
v_model_eff_flexi <- function(object) { |
|
403 | 19x |
rmin <- .Machine$double.xmin |
404 | ||
405 | 19x |
v <- Validate() |
406 | 19x |
v$check( |
407 | 19x |
test_numeric(object@eff, finite = TRUE, any.missing = FALSE, min.len = 2), |
408 | 19x |
"eff must be a finite numerical vector of minimum length 2, without missing values" |
409 |
) |
|
410 | 19x |
v$check( |
411 | 19x |
test_numeric( |
412 | 19x |
object@eff_dose, |
413 | 19x |
lower = rmin, finite = TRUE, any.missing = FALSE, len = length(object@eff) |
414 |
), |
|
415 | 19x |
"eff_dose must be a finite numerical vector of the same length as 'eff', without missing values" |
416 |
) |
|
417 | ||
418 | 19x |
uf_sigma2W <- object@use_fixed["sigma2W"] # nolintr |
419 | 19x |
v$check( |
420 | 19x |
test_flag(uf_sigma2W), |
421 | 19x |
"use_fixed must be a named logical vector that contains name 'sigma2W'" |
422 |
) |
|
423 | 19x |
uf_sigma2betaW <- object@use_fixed["sigma2betaW"] # nolintr |
424 | 19x |
v$check( |
425 | 19x |
test_flag(uf_sigma2betaW), |
426 | 19x |
"use_fixed must be a named logical vector that contains name 'sigma2betaW'" |
427 |
) |
|
428 | ||
429 | 19x |
if (isTRUE(uf_sigma2W)) { |
430 | ! |
v$check( |
431 | ! |
test_number(object@sigma2W, lower = rmin, finite = TRUE), |
432 | ! |
"sigma2W must be a positive and finite numerical scalar" |
433 |
) |
|
434 |
} else { |
|
435 |
# object@sigma2W is a vector with parameters for InverseGamma(a, b). |
|
436 | 19x |
v$check( |
437 | 19x |
h_test_named_numeric(object@sigma2W, permutation.of = c("a", "b")), |
438 | 19x |
"sigma2W must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
439 |
) |
|
440 |
} |
|
441 | 19x |
if (isTRUE(uf_sigma2betaW)) { |
442 | ! |
v$check( |
443 | ! |
test_number(object@sigma2betaW, lower = rmin, finite = TRUE), |
444 | ! |
"sigma2betaW must be a positive and finite numerical scalar" |
445 |
) |
|
446 |
} else { |
|
447 |
# object@sigma2betaW is a vector with parameters for InverseGamma(a, b). |
|
448 | 19x |
v$check( |
449 | 19x |
h_test_named_numeric(object@sigma2betaW, permutation.of = c("a", "b")), |
450 | 19x |
"sigma2betaW must be a named numerical vector of length two with positive finite values and names 'a', 'b'" |
451 |
) |
|
452 |
} |
|
453 | ||
454 | 19x |
v$check( |
455 | 19x |
test_flag(object@rw1), |
456 | 19x |
"rw1 must be a flag" |
457 |
) |
|
458 | 19x |
v$check( |
459 | 19x |
test_matrix(object@X, mode = "integer", ncols = object@data@nGrid, any.missing = FALSE), |
460 | 19x |
paste("X must be an integer matrix with", object@data@nGrid, "columns and without any missing values") |
461 |
) |
|
462 | 19x |
v$check( |
463 | 19x |
all(object@X == 0L | object@X == 1L), |
464 | 19x |
"X must be a matrix with 0-1 values only" |
465 |
) |
|
466 | 19x |
v$check( |
467 | 19x |
test_matrix(object@RW, nrows = object@data@nGrid, ncols = object@data@nGrid, any.missing = FALSE), |
468 | 19x |
paste0("RW must be ", object@data@nGrid, "x", object@data@nGrid, " matrix without any missing values") |
469 |
) |
|
470 | 19x |
v$check( |
471 | 19x |
test_int(object@RW_rank) && (object@RW_rank == (object@data@nGrid - ifelse(isTRUE(object@rw1), 1L, 2L))), |
472 | 19x |
"RW_rank must be an integer equal to data@nGrid - 2L" |
473 |
) |
|
474 | 19x |
v$result() |
475 |
} |
|
476 | ||
477 |
#' @describeIn v_model_objects validates that [`DALogisticLogNormal`] class slots are valid. |
|
478 |
v_model_da_logistic_log_normal <- function(object) { |
|
479 | 6x |
v <- Validate() |
480 | ||
481 | 6x |
npiece_ok <- test_int(object@npiece) |
482 | 6x |
v$check(npiece_ok, "npiece must be a is a single integerish value") |
483 | 6x |
if (npiece_ok) { |
484 | 5x |
v$check( |
485 | 5x |
test_numeric(object@l, lower = 0, finite = TRUE, any.missing = FALSE, len = object@npiece), |
486 | 5x |
"prior parameter vector l of lambda must be a non-negative vector of length equal to npiece" |
487 |
) |
|
488 |
} |
|
489 | 6x |
v$check( |
490 | 6x |
test_number(object@c_par, finite = TRUE), |
491 | 6x |
"c_par must be a finite numerical scalar" |
492 |
) |
|
493 | 6x |
v$check( |
494 | 6x |
test_flag(object@cond_pem), |
495 | 6x |
"cond_pem must be a flag" |
496 |
) |
|
497 | 6x |
v$result() |
498 |
} |
|
499 | ||
500 |
#' @describeIn v_model_objects validates that [`TITELogisticLogNormal`] class slots are valid. |
|
501 |
v_model_tite_logistic_log_normal <- function(object) { # nolintr |
|
502 | 3x |
v <- Validate() |
503 | 3x |
v$check( |
504 | 3x |
test_string(object@weight_method, pattern = "^linear$|^adaptive$"), |
505 | 3x |
"weight_method must be a string equal either to linear or adaptive" |
506 |
) |
|
507 | 3x |
v$result() |
508 |
} |
|
509 | ||
510 |
#' @describeIn v_model_objects validates that [`OneParLogNormalPrior`] class slots are valid. |
|
511 |
v_model_one_par_exp_normal_prior <- function(object) { # nolintr |
|
512 | 8x |
v <- Validate() |
513 | ||
514 | 8x |
is_skel_prob_ok <- test_probabilities(object@skel_probs, unique = TRUE, sorted = TRUE) |
515 | 8x |
v$check( |
516 | 8x |
is_skel_prob_ok, |
517 | 8x |
"skel_probs must be a unique sorted probability values between 0 and 1" |
518 |
) |
|
519 | ||
520 | 8x |
if (is_skel_prob_ok) { |
521 |
# Validating skel_fun/skel_fun_inv on within the range of skeleton probs. |
|
522 | 5x |
skel_probs_range <- range(object@skel_probs) |
523 |
# Probabilities within the range of skel_probs. |
|
524 | 5x |
probs_in_range <- seq(from = skel_probs_range[1], to = skel_probs_range[2], by = 0.01) |
525 |
# Interpolated dose grid. |
|
526 | 5x |
doses_in_range <- object@skel_fun_inv(probs_in_range) |
527 | 5x |
v$check( |
528 | 5x |
isTRUE(all.equal(object@skel_fun(doses_in_range), probs_in_range)), |
529 | 5x |
"skel_fun_inv must be an inverse funtion of skel_fun function on within the range of sekeleton probs" |
530 |
) |
|
531 | ||
532 |
# Validating skel_fun/skel_fun_inv on outside the range of skeleton probs. |
|
533 | 5x |
probs_out_range <- c( |
534 | 5x |
seq(from = 0, to = skel_probs_range[1], length.out = 3), |
535 | 5x |
seq(from = skel_probs_range[2], to = 1, length.out = 3) |
536 |
) |
|
537 | 5x |
doses_out_range <- object@skel_fun_inv(probs_out_range) |
538 | 5x |
v$check( |
539 | 5x |
isTRUE(all.equal(object@skel_fun(doses_out_range), rep(skel_probs_range, each = 3))), |
540 | 5x |
"skel_fun_inv must be an inverse funtion of skel_fun function on outside the range of sekeleton probs" |
541 |
) |
|
542 |
} |
|
543 | ||
544 | 8x |
v$check( |
545 | 8x |
test_number(object@sigma2, lower = .Machine$double.xmin, finite = TRUE), |
546 | 8x |
"sigma2 must be a positive finite number" |
547 |
) |
|
548 | ||
549 | 8x |
v$result() |
550 |
} |
|
551 | ||
552 |
#' @describeIn v_model_objects validates that [`OneParExpPrior`] class slots are valid. |
|
553 |
v_model_one_par_exp_prior <- function(object) { |
|
554 | 8x |
v <- Validate() |
555 | ||
556 | 8x |
is_skel_prob_ok <- test_probabilities(object@skel_probs, unique = TRUE, sorted = TRUE) |
557 | 8x |
v$check( |
558 | 8x |
is_skel_prob_ok, |
559 | 8x |
"skel_probs must be a unique sorted probability values between 0 and 1" |
560 |
) |
|
561 | ||
562 | 8x |
if (is_skel_prob_ok) { |
563 |
# Validating skel_fun/skel_fun_inv on within the range of skeleton probs. |
|
564 | 5x |
skel_probs_range <- range(object@skel_probs) |
565 |
# Probabilities within the range of skel_probs. |
|
566 | 5x |
probs_in_range <- seq(from = skel_probs_range[1], to = skel_probs_range[2], by = 0.01) |
567 |
# Interpolated dose grid. |
|
568 | 5x |
doses_in_range <- object@skel_fun_inv(probs_in_range) |
569 | 5x |
v$check( |
570 | 5x |
isTRUE(all.equal(object@skel_fun(doses_in_range), probs_in_range)), |
571 | 5x |
"skel_fun_inv must be an inverse funtion of skel_fun function on within the range of sekeleton probs" |
572 |
) |
|
573 | ||
574 |
# Validating skel_fun/skel_fun_inv on outside the range of skeleton probs. |
|
575 | 5x |
probs_out_range <- c( |
576 | 5x |
seq(from = 0, to = skel_probs_range[1], length.out = 3), |
577 | 5x |
seq(from = skel_probs_range[2], to = 1, length.out = 3) |
578 |
) |
|
579 | 5x |
doses_out_range <- object@skel_fun_inv(probs_out_range) |
580 | 5x |
v$check( |
581 | 5x |
isTRUE(all.equal(object@skel_fun(doses_out_range), rep(skel_probs_range, each = 3))), |
582 | 5x |
"skel_fun_inv must be an inverse funtion of skel_fun function on outside the range of sekeleton probs" |
583 |
) |
|
584 |
} |
|
585 | ||
586 | 8x |
v$check( |
587 | 8x |
test_number(object@lambda, lower = .Machine$double.xmin, finite = TRUE), |
588 | 8x |
"lambda must be a positive finite number" |
589 |
) |
|
590 | ||
591 | 8x |
v$result() |
592 |
} |
|
593 | ||
594 |
#' @describeIn v_model_objects confirms that cov is diagonal |
|
595 |
v_logisticlognormalordinal <- function(object) { |
|
596 | ! |
v <- Validate() |
597 |
# diag(x) returns a vector, not a matrix, so cannot use identical(x, diag(x) |
|
598 | ! |
x <- object@params@cov |
599 | ! |
diag(x) <- rep(0, ncol(x)) |
600 | ! |
v$check( |
601 | ! |
all(x == 0), |
602 | ! |
"covariance matrix must be diagonal" |
603 |
) |
|
604 | ! |
v$result() |
605 |
} |
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 |
# 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 | ||
18 |
##' @include Simulations-class.R |
|
19 |
##' @include helpers.R |
|
20 |
{} |
|
21 | ||
22 | ||
23 |
##' Plot simulations |
|
24 |
##' |
|
25 |
##' Summarize the simulations with plots |
|
26 |
##' |
|
27 |
##' This plot method can be applied to \code{\linkS4class{GeneralSimulations}} |
|
28 |
##' objects in order to summarize them graphically. Possible \code{type}s of |
|
29 |
##' plots at the moment are: \describe{ \item{trajectory}{Summary of the |
|
30 |
##' trajectory of the simulated trials} \item{dosesTried}{Average proportions of |
|
31 |
##' the doses tested in patients} } You can specify one or both of these in the |
|
32 |
##' \code{type} argument. |
|
33 |
##' |
|
34 |
##' @param x the \code{\linkS4class{GeneralSimulations}} object we want |
|
35 |
##' to plot from |
|
36 |
##' @param y missing |
|
37 |
##' @param type the type of plots you want to obtain. |
|
38 |
##' @param \dots not used |
|
39 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
40 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
41 |
##' |
|
42 |
##' @importFrom ggplot2 ggplot geom_step geom_bar aes xlab ylab |
|
43 |
##' scale_linetype_manual |
|
44 |
##' @importFrom gridExtra arrangeGrob |
|
45 |
##' |
|
46 |
##' @example examples/Simulations-method-plotSIMsingle.R |
|
47 |
##' @export |
|
48 |
##' @keywords methods |
|
49 |
setMethod("plot", |
|
50 |
signature = |
|
51 |
signature( |
|
52 |
x = "GeneralSimulations", |
|
53 |
y = "missing" |
|
54 |
), |
|
55 |
def = |
|
56 |
function(x, |
|
57 |
y, |
|
58 |
type = |
|
59 |
c( |
|
60 |
"trajectory", |
|
61 |
"dosesTried" |
|
62 |
), |
|
63 |
...) { |
|
64 |
## which plots should be produced? |
|
65 | ! |
type <- match.arg(type, |
66 | ! |
several.ok = TRUE |
67 |
) |
|
68 | ! |
stopifnot(length(type) > 0L) |
69 | ||
70 |
## start the plot list |
|
71 | ! |
plotList <- list() |
72 | ! |
plotIndex <- 0L |
73 | ||
74 | ||
75 |
## summary of the trajectories |
|
76 | ! |
if ("trajectory" %in% type) { |
77 |
## get a matrix of the simulated dose trajectories, |
|
78 |
## where the rows correspond to the simulations and |
|
79 |
## the columns to the patient index: |
|
80 | ||
81 |
## If design with placebo, then exclude placebo Patients |
|
82 | ! |
if (x@data[[1]]@placebo) { |
83 | ! |
PL <- x@data[[1]]@doseGrid[1] |
84 | ! |
simDoses <- lapply( |
85 | ! |
x@data, |
86 | ! |
function(y) { |
87 | ! |
y@x[y@x != PL] |
88 |
} |
|
89 |
) |
|
90 |
} else { |
|
91 | ! |
simDoses <- lapply( |
92 | ! |
x@data, |
93 | ! |
slot, |
94 | ! |
"x" |
95 |
) |
|
96 |
} |
|
97 | ||
98 | ! |
maxPatients <- max(sapply(simDoses, length)) |
99 | ||
100 | ! |
simDosesMat <- matrix( |
101 | ! |
data = NA, |
102 | ! |
nrow = length(simDoses), |
103 | ! |
ncol = maxPatients |
104 |
) |
|
105 | ||
106 | ! |
for (i in seq_along(simDoses)) |
107 |
{ |
|
108 | ! |
simDosesMat[i, seq_along(simDoses[[i]])] <- |
109 | ! |
simDoses[[i]] |
110 |
} |
|
111 | ||
112 | ||
113 |
## extract statistics |
|
114 | ! |
stats <- c( |
115 | ! |
"Minimum", |
116 | ! |
"Lower Quartile", |
117 | ! |
"Median", |
118 | ! |
"Upper Quartile", |
119 | ! |
"Maximum" |
120 |
) |
|
121 | ! |
traj.df <- |
122 | ! |
data.frame( |
123 | ! |
patient = |
124 | ! |
rep(seq_len(maxPatients), each = 5L), |
125 | ! |
Statistic = |
126 | ! |
factor( |
127 | ! |
rep( |
128 | ! |
stats, |
129 | ! |
maxPatients |
130 |
), |
|
131 | ! |
levels = stats |
132 |
), |
|
133 | ! |
traj = |
134 | ! |
c(apply(simDosesMat, 2L, quantile, |
135 | ! |
na.rm = TRUE |
136 |
)) |
|
137 |
) |
|
138 | ||
139 |
## linetypes for the plot |
|
140 | ! |
lt <- c( |
141 | ! |
"Median" = 1, |
142 | ! |
"Lower Quartile" = 2, |
143 | ! |
"Upper Quartile" = 2, |
144 | ! |
"Minimum" = 4, |
145 | ! |
"Maximum" = 4 |
146 |
) |
|
147 | ||
148 |
## save the plot |
|
149 | ! |
if (x@data[[1]]@placebo) { |
150 | ! |
myTitle <- "Patient (placebo were excluded)" |
151 |
} else { |
|
152 | ! |
myTitle <- "Patient" |
153 |
} |
|
154 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
155 | ! |
ggplot() + |
156 | ! |
geom_step( |
157 | ! |
aes( |
158 | ! |
x = patient, |
159 | ! |
y = traj, |
160 | ! |
group = Statistic, |
161 | ! |
linetype = Statistic |
162 |
), |
|
163 | ! |
size = 1.2, colour = "blue", data = traj.df |
164 |
) + |
|
165 |
## scale_linetype_manual(values=lt) + |
|
166 | ! |
xlab(myTitle) + |
167 | ! |
ylab("Dose Level") |
168 |
} |
|
169 | ||
170 |
## average distribution of the doses tried |
|
171 | ! |
if ("dosesTried" %in% type) { |
172 |
## get the doses tried |
|
173 | ! |
simDoses <- lapply( |
174 | ! |
x@data, |
175 | ! |
slot, |
176 | ! |
"x" |
177 |
) |
|
178 | ||
179 |
## get the dose distributions by trial |
|
180 | ! |
doseDistributions <- |
181 | ! |
sapply( |
182 | ! |
simDoses, |
183 | ! |
function(s) { |
184 | ! |
if (length(s) > 0) { |
185 | ! |
prop.table(table(factor(s, levels = x@data[[1]]@doseGrid))) |
186 |
} else { |
|
187 | ! |
rep(0, length(x@data[[1]]@doseGrid)) |
188 |
} |
|
189 |
} |
|
190 |
) |
|
191 | ||
192 |
## derive the average dose distribution across trial |
|
193 |
## simulations |
|
194 | ! |
averageDoseDist <- rowMeans(doseDistributions) |
195 | ||
196 |
## get in data frame shape |
|
197 | ! |
dat <- data.frame( |
198 | ! |
dose = as.numeric(names(averageDoseDist)), |
199 | ! |
perc = averageDoseDist * 100 |
200 |
) |
|
201 | ||
202 |
## produce and save the plot |
|
203 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
204 | ! |
ggplot() + |
205 | ! |
geom_bar( |
206 | ! |
data = as.data.frame(dat), |
207 | ! |
aes(x = dose, y = perc), |
208 | ! |
stat = "identity", |
209 | ! |
position = "identity", |
210 | ! |
width = min(diff(x@data[[1]]@doseGrid)) / 2 |
211 |
) + |
|
212 | ! |
xlab("Dose level") + |
213 | ! |
ylab("Average proportion [%]") |
214 |
} |
|
215 |
## then finally plot everything |
|
216 | ||
217 |
## if there is only one plot |
|
218 | ! |
if (identical( |
219 | ! |
length(plotList), |
220 | ! |
1L |
221 |
)) { |
|
222 |
## just return it |
|
223 | ! |
return(plotList[[1L]]) |
224 |
} else { |
|
225 |
## otherwise arrange them |
|
226 | ! |
ret <- do.call( |
227 | ! |
gridExtra::arrangeGrob, |
228 | ! |
plotList |
229 |
) |
|
230 | ! |
return(ret) |
231 |
} |
|
232 |
} |
|
233 |
) |
|
234 | ||
235 | ||
236 |
##' Plot dual-endpoint simulations |
|
237 |
##' |
|
238 |
##' This plot method can be applied to \code{\linkS4class{DualSimulations}} |
|
239 |
##' objects in order to summarize them graphically. In addition to the standard |
|
240 |
##' plot types, there is |
|
241 |
##' \describe{ |
|
242 |
##' \item{sigma2W}{Plot a boxplot of the final biomarker variance estimates in |
|
243 |
##' the simulated trials} |
|
244 |
##' \item{rho}{Plot a boxplot of the final correlation estimates in |
|
245 |
##' the simulated trials} |
|
246 |
##' } |
|
247 |
##' |
|
248 |
##' @param x the \code{\linkS4class{DualSimulations}} object we want |
|
249 |
##' to plot from |
|
250 |
##' @param y missing |
|
251 |
##' @param type the type of plots you want to obtain. |
|
252 |
##' @param \dots not used |
|
253 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
254 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
255 |
##' |
|
256 |
##' @importFrom ggplot2 qplot coord_flip scale_x_discrete |
|
257 |
##' @importFrom gridExtra arrangeGrob |
|
258 |
##' |
|
259 |
##' @example examples/Simulations-method-plot-DualSimulations.R |
|
260 |
##' @export |
|
261 |
##' @keywords methods |
|
262 |
setMethod("plot", |
|
263 |
signature = |
|
264 |
signature( |
|
265 |
x = "DualSimulations", |
|
266 |
y = "missing" |
|
267 |
), |
|
268 |
def = |
|
269 |
function(x, |
|
270 |
y, |
|
271 |
type = |
|
272 |
c( |
|
273 |
"trajectory", |
|
274 |
"dosesTried", |
|
275 |
"sigma2W", |
|
276 |
"rho" |
|
277 |
), |
|
278 |
...) { |
|
279 |
## start the plot list |
|
280 | ! |
plotList <- list() |
281 | ! |
plotIndex <- 0L |
282 | ||
283 |
## which plots should be produced? |
|
284 | ! |
type <- match.arg(type, |
285 | ! |
several.ok = TRUE |
286 |
) |
|
287 | ! |
stopifnot(length(type) > 0L) |
288 | ||
289 |
## substract the specific plot types for |
|
290 |
## dual-endpoint simulation results |
|
291 | ! |
typeReduced <- setdiff( |
292 | ! |
type, |
293 | ! |
c("sigma2W", "rho") |
294 |
) |
|
295 | ||
296 |
## are there more plots from general? |
|
297 | ! |
moreFromGeneral <- (length(typeReduced) > 0) |
298 | ||
299 |
## if so, then produce these plots |
|
300 | ! |
if (moreFromGeneral) { |
301 | ! |
genPlot <- callNextMethod(x = x, y = y, type = typeReduced) |
302 |
} |
|
303 | ||
304 |
## now to the specific dual-endpoint plots: |
|
305 | ||
306 |
## biomarker variance estimates boxplot |
|
307 | ! |
if ("sigma2W" %in% type) { |
308 |
## save the plot |
|
309 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
310 | ! |
qplot(factor(0), |
311 | ! |
y = y, data = data.frame(y = x@sigma2w_est), geom = "boxplot", |
312 | ! |
xlab = "", ylab = "Biomarker variance estimates" |
313 |
) + |
|
314 | ! |
coord_flip() + scale_x_discrete(breaks = NULL) |
315 |
} |
|
316 | ||
317 |
## correlation estimates boxplot |
|
318 | ! |
if ("rho" %in% type) { |
319 |
## save the plot |
|
320 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
321 | ! |
qplot(factor(0), |
322 | ! |
y = y, data = data.frame(y = x@rho_est), geom = "boxplot", |
323 | ! |
xlab = "", ylab = "Correlation estimates" |
324 |
) + |
|
325 | ! |
coord_flip() + scale_x_discrete(breaks = NULL) |
326 |
} |
|
327 | ||
328 |
## then finally plot everything |
|
329 | ! |
if (identical( |
330 | ! |
length(plotList), |
331 | ! |
0L |
332 |
)) { |
|
333 | ! |
return(genPlot) |
334 | ! |
} else if (identical( |
335 | ! |
length(plotList), |
336 | ! |
1L |
337 |
)) { |
|
338 | ! |
ret <- plotList[[1L]] |
339 |
} else { |
|
340 | ! |
ret <- do.call( |
341 | ! |
gridExtra::arrangeGrob, |
342 | ! |
plotList |
343 |
) |
|
344 |
} |
|
345 | ||
346 | ! |
if (moreFromGeneral) { |
347 | ! |
ret <- gridExtra::arrangeGrob(genPlot, ret) |
348 |
} |
|
349 | ||
350 | ! |
return(ret) |
351 |
} |
|
352 |
) |
|
353 | ||
354 | ||
355 | ||
356 |
##' Summarize the simulations, relative to a given truth |
|
357 |
##' |
|
358 |
##' @param object the \code{\linkS4class{GeneralSimulations}} object we want to |
|
359 |
##' summarize |
|
360 |
##' @param truth a function which takes as input a dose (vector) and returns the |
|
361 |
##' true probability (vector) for toxicity |
|
362 |
##' @param target the target toxicity interval (default: 20-35%) used for the |
|
363 |
##' computations |
|
364 |
##' @param \dots Additional arguments can be supplied here for \code{truth} |
|
365 |
##' @return an object of class \code{\linkS4class{GeneralSimulationsSummary}} |
|
366 |
##' |
|
367 |
##' @export |
|
368 |
##' @keywords methods |
|
369 |
setMethod("summary", |
|
370 |
signature = |
|
371 |
signature(object = "GeneralSimulations"), |
|
372 |
def = |
|
373 |
function(object, |
|
374 |
truth, |
|
375 |
target = c(0.2, 0.35), |
|
376 |
...) { |
|
377 |
## extract dose grid |
|
378 | 2x |
doseGrid <- object@data[[1]]@doseGrid |
379 | ||
380 |
## evaluate true toxicity at doseGrid |
|
381 | 2x |
trueTox <- truth(doseGrid, ...) |
382 | ||
383 |
## find dose interval corresponding to target tox interval |
|
384 | 2x |
targetDoseInterval <- |
385 | 2x |
sapply( |
386 | 2x |
target, |
387 | 2x |
function(t) { |
388 |
## we have to be careful because it might be |
|
389 |
## that in the range of the dose grid, no |
|
390 |
## doses can be found that match the target |
|
391 |
## interval boundaries! |
|
392 |
## In that case we want to return NA |
|
393 | 2x |
r <- try( |
394 | 2x |
uniroot( |
395 | 2x |
f = function(x) { |
396 | 4x |
truth(x, ...) - t |
397 |
}, |
|
398 | 2x |
interval = |
399 | 2x |
range(doseGrid) |
400 | 2x |
)$root, |
401 | 2x |
silent = TRUE |
402 |
) |
|
403 | 2x |
if (inherits(r, "try-error")) { |
404 | 2x |
return(NA_real_) |
405 |
} else { |
|
406 | ! |
return(r) |
407 |
} |
|
408 |
} |
|
409 |
) |
|
410 | ||
411 |
## what are the levels above target interval? |
|
412 | 2x |
xAboveTarget <- which(trueTox > target[2]) |
413 | ||
414 |
## proportion of DLTs in a trial: |
|
415 | 2x |
if (object@data[[1]]@placebo) { |
416 | ! |
if (sum(object@data[[1]]@x == doseGrid[1])) { |
417 | ! |
propDLTs <- sapply( |
418 | ! |
object@data, |
419 | ! |
function(d) { |
420 | ! |
tapply( |
421 | ! |
d@y, |
422 | ! |
factor(d@x == d@doseGrid[1], |
423 | ! |
labels = c("ACTV", "PLCB") |
424 |
), |
|
425 | ! |
mean |
426 |
) |
|
427 |
} |
|
428 |
) |
|
429 |
} else { |
|
430 | ! |
propDLTs <- sapply( |
431 | ! |
object@data, |
432 | ! |
function(d) { |
433 | ! |
c("ACTV" = mean(d@y), "PLCB" = NA) |
434 |
} |
|
435 |
) |
|
436 |
} |
|
437 |
} else { |
|
438 | 2x |
propDLTs <- sapply( |
439 | 2x |
object@data, |
440 | 2x |
function(d) { |
441 | 10x |
mean(d@y) |
442 |
} |
|
443 |
) |
|
444 |
} |
|
445 | ||
446 |
## mean toxicity risk |
|
447 | 2x |
if (object@data[[1]]@placebo) { |
448 | ! |
meanToxRisk <- sapply( |
449 | ! |
object@data, |
450 | ! |
function(d) { |
451 | ! |
mean(trueTox[d@xLevel[d@xLevel != 1]]) |
452 |
} |
|
453 |
) |
|
454 |
} else { |
|
455 | 2x |
meanToxRisk <- sapply( |
456 | 2x |
object@data, |
457 | 2x |
function(d) { |
458 | 10x |
mean(trueTox[d@xLevel]) |
459 |
} |
|
460 |
) |
|
461 |
} |
|
462 | ||
463 |
## doses selected for MTD |
|
464 | 2x |
doseSelected <- object@doses |
465 | ||
466 |
## replace NA by 0 |
|
467 | 2x |
doseSelected[is.na(doseSelected)] <- 0 |
468 | ||
469 |
## dose most often selected as MTD |
|
470 | 2x |
doseMostSelected <- |
471 | 2x |
as.numeric(names(which.max(table(doseSelected)))) |
472 | 2x |
xMostSelected <- |
473 | 2x |
match_within_tolerance(doseMostSelected, |
474 | 2x |
table = doseGrid |
475 |
) |
|
476 | ||
477 |
## observed toxicity rate at dose most often selected |
|
478 |
## Note: this does not seem very useful! |
|
479 |
## Reason: In case of a fine grid, few patients if any |
|
480 |
## will have been treated at this dose. |
|
481 | 2x |
tmp <- |
482 | 2x |
sapply( |
483 | 2x |
object@data, |
484 | 2x |
function(d) { |
485 | 10x |
whichAtThisDose <- which(d@x == doseMostSelected) |
486 | 10x |
nAtThisDose <- length(whichAtThisDose) |
487 | 10x |
nDLTatThisDose <- sum(d@y[whichAtThisDose]) |
488 | 10x |
return(c( |
489 | 10x |
nAtThisDose = nAtThisDose, |
490 | 10x |
nDLTatThisDose = nDLTatThisDose |
491 |
)) |
|
492 |
} |
|
493 |
) |
|
494 | ||
495 | 2x |
obsToxRateAtDoseMostSelected <- |
496 | 2x |
mean(tmp["nDLTatThisDose", ]) / mean(tmp["nAtThisDose", ]) |
497 | ||
498 |
## number of patients overall |
|
499 | 2x |
if (object@data[[1]]@placebo) { |
500 | ! |
nObs <- sapply( |
501 | ! |
object@data, |
502 | ! |
function(x) { |
503 | ! |
data.frame( |
504 | ! |
n.ACTV = sum(x@xLevel != 1L), |
505 | ! |
n.PLCB = sum(x@xLevel == 1L) |
506 |
) |
|
507 |
} |
|
508 |
) |
|
509 | ! |
nObs <- matrix(unlist(nObs), dim(nObs)) |
510 |
} else { |
|
511 | 2x |
nObs <- sapply( |
512 | 2x |
object@data, |
513 | 2x |
slot, |
514 | 2x |
"nObs" |
515 |
) |
|
516 |
} |
|
517 | ||
518 | ||
519 |
## number of patients treated above target tox interval |
|
520 | 2x |
nAboveTarget <- sapply( |
521 | 2x |
object@data, |
522 | 2x |
function(d) { |
523 | 10x |
sum(d@xLevel %in% xAboveTarget) |
524 |
} |
|
525 |
) |
|
526 | ||
527 |
## Proportion of trials selecting target MTD |
|
528 | 2x |
toxAtDoses <- truth(doseSelected, ...) |
529 | 2x |
propAtTarget <- mean((toxAtDoses > target[1]) & |
530 | 2x |
(toxAtDoses < target[2])) |
531 | ||
532 |
## give back an object of class GeneralSimulationsSummary, |
|
533 |
## for which we then define a print / plot method |
|
534 | 2x |
ret <- |
535 | 2x |
.GeneralSimulationsSummary( |
536 | 2x |
target = target, |
537 | 2x |
target_dose_interval = targetDoseInterval, |
538 | 2x |
nsim = length(object@data), |
539 | 2x |
prop_dlts = propDLTs, |
540 | 2x |
mean_tox_risk = meanToxRisk, |
541 | 2x |
dose_selected = doseSelected, |
542 | 2x |
dose_most_selected = doseMostSelected, |
543 | 2x |
obs_tox_rate_at_dose_most_selected = obsToxRateAtDoseMostSelected, |
544 | 2x |
n_obs = nObs, |
545 | 2x |
n_above_target = nAboveTarget, |
546 | 2x |
tox_at_doses_selected = toxAtDoses, |
547 | 2x |
prop_at_target = propAtTarget, |
548 | 2x |
dose_grid = doseGrid, |
549 | 2x |
placebo = object@data[[1]]@placebo |
550 |
) |
|
551 | 2x |
return(ret) |
552 |
} |
|
553 |
) |
|
554 | ||
555 | ||
556 |
##' Summarize the model-based design simulations, relative to a given truth |
|
557 |
##' |
|
558 |
##' @param object the \code{\linkS4class{Simulations}} object we want to |
|
559 |
##' summarize |
|
560 |
##' @param truth a function which takes as input a dose (vector) and returns the |
|
561 |
##' true probability (vector) for toxicity |
|
562 |
##' @param target the target toxicity interval (default: 20-35%) used for the |
|
563 |
##' computations |
|
564 |
##' @param \dots Additional arguments can be supplied here for \code{truth} |
|
565 |
##' @return an object of class \code{\linkS4class{SimulationsSummary}} |
|
566 |
##' |
|
567 |
##' @example examples/Simulations-method-summary.R |
|
568 |
##' @export |
|
569 |
##' @keywords methods |
|
570 |
setMethod("summary", |
|
571 |
signature = |
|
572 |
signature(object = "Simulations"), |
|
573 |
def = |
|
574 |
function(object, |
|
575 |
truth, |
|
576 |
target = c(0.2, 0.35), |
|
577 |
...) { |
|
578 |
## call the parent method |
|
579 | 2x |
start <- callNextMethod( |
580 | 2x |
object = object, |
581 | 2x |
truth = truth, |
582 | 2x |
target = target, |
583 |
... |
|
584 |
) |
|
585 | ||
586 | 2x |
doseGrid <- object@data[[1]]@doseGrid |
587 | ||
588 |
## dose level most often selected as MTD |
|
589 | 2x |
xMostSelected <- |
590 | 2x |
match_within_tolerance(start@dose_most_selected, |
591 | 2x |
table = doseGrid |
592 |
) |
|
593 | ||
594 |
## fitted toxicity rate at dose most often selected |
|
595 | 2x |
fitAtDoseMostSelected <- |
596 | 2x |
sapply( |
597 | 2x |
object@fit, |
598 | 2x |
function(f) { |
599 | 10x |
f$middle[xMostSelected] |
600 |
} |
|
601 |
) |
|
602 | ||
603 |
## mean fitted toxicity (average, lower and upper quantiles) |
|
604 |
## at each dose level |
|
605 |
## (this is required for plotting) |
|
606 | 2x |
meanFitMatrix <- sapply( |
607 | 2x |
object@fit, |
608 |
"[[", |
|
609 | 2x |
"middle" |
610 |
) |
|
611 | 2x |
meanFit <- list( |
612 | 2x |
truth = |
613 | 2x |
truth(doseGrid, ...), |
614 | 2x |
average = |
615 | 2x |
rowMeans(meanFitMatrix), |
616 | 2x |
lower = |
617 | 2x |
apply( |
618 | 2x |
meanFitMatrix, |
619 | 2x |
1L, |
620 | 2x |
quantile, |
621 | 2x |
0.025 |
622 |
), |
|
623 | 2x |
upper = |
624 | 2x |
apply( |
625 | 2x |
meanFitMatrix, |
626 | 2x |
1L, |
627 | 2x |
quantile, |
628 | 2x |
0.975 |
629 |
) |
|
630 |
) |
|
631 | ||
632 |
## give back an object of class SimulationsSummary, |
|
633 |
## for which we then define a print / plot method |
|
634 | 2x |
ret <- .SimulationsSummary( |
635 | 2x |
start, |
636 | 2x |
stop_report = object@stop_report, |
637 | 2x |
additional_stats = object@additional_stats, |
638 | 2x |
fit_at_dose_most_selected = fitAtDoseMostSelected, |
639 | 2x |
mean_fit = meanFit |
640 |
) |
|
641 | ||
642 | 2x |
return(ret) |
643 |
} |
|
644 |
) |
|
645 | ||
646 |
##' Summarize the dual-endpoint design simulations, relative to given true |
|
647 |
##' dose-toxicity and dose-biomarker curves |
|
648 |
##' |
|
649 |
##' @param object the \code{\linkS4class{DualSimulations}} object we want to |
|
650 |
##' summarize |
|
651 |
##' @param trueTox a function which takes as input a dose (vector) and returns the |
|
652 |
##' true probability (vector) for toxicity. |
|
653 |
##' @param trueBiomarker a function which takes as input a dose (vector) and |
|
654 |
##' returns the true biomarker level (vector). |
|
655 |
##' @param target the target toxicity interval (default: 20-35%) used for the |
|
656 |
##' computations |
|
657 |
##' @param \dots Additional arguments can be supplied here for \code{trueTox} |
|
658 |
##' and \code{trueBiomarker} |
|
659 |
##' @return an object of class \code{\linkS4class{DualSimulationsSummary}} |
|
660 |
##' |
|
661 |
##' @example examples/Simulations-method-summary-DualSimulations.R |
|
662 |
##' @export |
|
663 |
##' @keywords methods |
|
664 |
setMethod("summary", |
|
665 |
signature = |
|
666 |
signature(object = "DualSimulations"), |
|
667 |
def = |
|
668 |
function(object, |
|
669 |
trueTox, |
|
670 |
trueBiomarker, |
|
671 |
target = c(0.2, 0.35), |
|
672 |
...) { |
|
673 |
## call the parent method |
|
674 | ! |
start <- callNextMethod( |
675 | ! |
object = object, |
676 | ! |
truth = trueTox, |
677 | ! |
target = target, |
678 |
... |
|
679 |
) |
|
680 | ||
681 | ! |
doseGrid <- object@data[[1]]@doseGrid |
682 | ||
683 |
## dose level most often selected as MTD |
|
684 | ! |
xMostSelected <- |
685 | ! |
match_within_tolerance(start@dose_most_selected, |
686 | ! |
table = doseGrid |
687 |
) |
|
688 | ||
689 |
## fitted biomarker level at dose most often selected |
|
690 | ! |
biomarkerFitAtDoseMostSelected <- |
691 | ! |
sapply( |
692 | ! |
object@fit_biomarker, |
693 | ! |
function(f) { |
694 | ! |
f$middleBiomarker[xMostSelected] |
695 |
} |
|
696 |
) |
|
697 | ||
698 |
## mean fitted biomarker curve (average, lower and upper quantiles) |
|
699 |
## at each dose level |
|
700 |
## (this is required for plotting) |
|
701 | ! |
meanBiomarkerFitMatrix <- sapply( |
702 | ! |
object@fit_biomarker, |
703 |
"[[", |
|
704 | ! |
"middleBiomarker" |
705 |
) |
|
706 | ! |
meanBiomarkerFit <- list( |
707 | ! |
truth = |
708 | ! |
trueBiomarker(doseGrid, ...), |
709 | ! |
average = |
710 | ! |
rowMeans(meanBiomarkerFitMatrix), |
711 | ! |
lower = |
712 | ! |
apply( |
713 | ! |
meanBiomarkerFitMatrix, |
714 | ! |
1L, |
715 | ! |
quantile, |
716 | ! |
0.025 |
717 |
), |
|
718 | ! |
upper = |
719 | ! |
apply( |
720 | ! |
meanBiomarkerFitMatrix, |
721 | ! |
1L, |
722 | ! |
quantile, |
723 | ! |
0.975 |
724 |
) |
|
725 |
) |
|
726 | ||
727 |
## give back an object of class DualSimulationsSummary, |
|
728 |
## for which we then define a print / plot method |
|
729 | ! |
ret <- .DualSimulationsSummary( |
730 | ! |
start, |
731 | ! |
biomarker_fit_at_dose_most_selected = biomarkerFitAtDoseMostSelected, |
732 | ! |
mean_biomarker_fit = meanBiomarkerFit |
733 |
) |
|
734 | ||
735 | ! |
return(ret) |
736 |
} |
|
737 |
) |
|
738 | ||
739 |
##' A Reference Class to represent sequentially updated reporting objects. |
|
740 |
##' @name Report |
|
741 |
##' @field object The object from which to report |
|
742 |
##' @field df the data frame to which columns are sequentially added |
|
743 |
##' @field dfNames the names to which strings are sequentially added |
|
744 |
Report <- |
|
745 |
setRefClass("Report", |
|
746 |
fields = |
|
747 |
list( |
|
748 |
object = "ANY", |
|
749 |
df = "data.frame", |
|
750 |
dfNames = "character" |
|
751 |
), |
|
752 |
methods = list( |
|
753 |
dfSave = |
|
754 |
function(res, name) { |
|
755 | ! |
df <<- cbind(df, res) |
756 | ! |
dfNames <<- c(dfNames, name) |
757 | ! |
return(res) |
758 |
}, |
|
759 |
report = |
|
760 |
function(slotName, |
|
761 |
description, |
|
762 |
percent = TRUE, |
|
763 |
digits = 0, |
|
764 |
quantiles = c(0.1, 0.9), |
|
765 |
subset = NULL, |
|
766 |
doSum = FALSE) { |
|
767 | ! |
vals <- slot(object, name = slotName) |
768 | ! |
if (!is.null(subset)) { |
769 | ! |
vals <- vals[subset, ] |
770 |
} |
|
771 | ! |
if (doSum) { |
772 | ! |
vals <- apply(vals, 2, sum) |
773 |
} |
|
774 | ! |
if (percent) { |
775 | ! |
unit <- " %" |
776 | ! |
vals <- vals * 100 |
777 |
} else { |
|
778 | ! |
unit <- "" |
779 |
} |
|
780 | ||
781 | ! |
res <- paste(round(mean(vals), digits), |
782 | ! |
unit, |
783 |
" (", |
|
784 | ! |
paste( |
785 | ! |
round( |
786 | ! |
quantile(vals, |
787 | ! |
quantiles, |
788 | ! |
na.rm = TRUE |
789 |
), |
|
790 | ! |
digits |
791 |
), |
|
792 | ! |
unit, |
793 | ! |
collapse = ", ", |
794 | ! |
sep = "" |
795 |
), |
|
796 |
")", |
|
797 | ! |
sep = "" |
798 |
) |
|
799 | ||
800 |
## print result to the buffer |
|
801 | ! |
cat( |
802 | ! |
description, ":", |
803 | ! |
"mean", |
804 | ! |
dfSave(res, slotName), |
805 | ! |
"\n" |
806 |
) |
|
807 |
} |
|
808 |
) |
|
809 |
) |
|
810 | ||
811 | ||
812 |
##' Show the summary of the simulations |
|
813 |
##' |
|
814 |
##' @param object the \code{\linkS4class{GeneralSimulationsSummary}} object we want |
|
815 |
##' to print |
|
816 |
##' @return invisibly returns a data frame of the results with one row and |
|
817 |
##' appropriate column names |
|
818 |
##' |
|
819 |
##' @export |
|
820 |
##' @keywords methods |
|
821 |
setMethod("show", |
|
822 |
signature = |
|
823 |
signature(object = "GeneralSimulationsSummary"), |
|
824 |
def = |
|
825 |
function(object) { |
|
826 | ! |
r <- Report$new( |
827 | ! |
object = object, |
828 | ! |
df = |
829 | ! |
as.data.frame(matrix( |
830 | ! |
nrow = 1, |
831 | ! |
ncol = 0 |
832 |
)), |
|
833 | ! |
dfNames = character() |
834 |
) |
|
835 | ||
836 | ! |
cat( |
837 | ! |
"Summary of", |
838 | ! |
r$dfSave(object@nsim, "nsim"), |
839 | ! |
"simulations\n\n" |
840 |
) |
|
841 | ||
842 | ! |
cat( |
843 | ! |
"Target toxicity interval was", |
844 | ! |
r$dfSave( |
845 | ! |
paste(round(object@target * 100), |
846 | ! |
collapse = ", " |
847 |
), |
|
848 | ! |
"target" |
849 |
), |
|
850 | ! |
"%\n" |
851 |
) |
|
852 | ! |
cat( |
853 | ! |
"Target dose interval corresponding to this was", |
854 | ! |
r$dfSave( |
855 | ! |
paste(round(object@target_dose_interval, 1), |
856 | ! |
collapse = ", " |
857 |
), |
|
858 | ! |
"target_dose_interval" |
859 |
), |
|
860 | ! |
"\n" |
861 |
) |
|
862 | ! |
cat( |
863 | ! |
"Intervals are corresponding to", |
864 | ! |
"10 and 90 % quantiles\n\n" |
865 |
) |
|
866 | ||
867 | ! |
if (object@placebo) { |
868 | ! |
r$report("n_obs", |
869 | ! |
"Number of patients on placebo", |
870 | ! |
percent = FALSE, |
871 | ! |
subset = 2 |
872 |
) |
|
873 | ! |
r$report("n_obs", |
874 | ! |
"Number of patients on active", |
875 | ! |
percent = FALSE, |
876 | ! |
subset = 1 |
877 |
) |
|
878 | ! |
r$report("n_obs", |
879 | ! |
"Number of patients overall", |
880 | ! |
percent = FALSE, |
881 | ! |
doSum = TRUE |
882 |
) |
|
883 |
} else { |
|
884 | ! |
r$report("n_obs", |
885 | ! |
"Number of patients overall", |
886 | ! |
percent = FALSE |
887 |
) |
|
888 |
} |
|
889 | ! |
r$report("n_above_target", |
890 | ! |
"Number of patients treated above target tox interval", |
891 | ! |
percent = FALSE |
892 |
) |
|
893 | ||
894 | ! |
if (object@placebo) { |
895 | ! |
r$report("prop_dlts", |
896 | ! |
"Proportions of DLTs in the trials for patients on placebo", |
897 | ! |
subset = 2 |
898 |
) |
|
899 | ! |
r$report("prop_dlts", |
900 | ! |
"Proportions of DLTs in the trials for patients on active", |
901 | ! |
subset = 1 |
902 |
) |
|
903 |
} else { |
|
904 | ! |
r$report( |
905 | ! |
"prop_dlts", |
906 | ! |
"Proportions of DLTs in the trials" |
907 |
) |
|
908 |
} |
|
909 | ! |
r$report( |
910 | ! |
"mean_tox_risk", |
911 | ! |
"Mean toxicity risks for the patients on active" |
912 |
) |
|
913 | ! |
r$report("dose_selected", |
914 | ! |
"Doses selected as MTD", |
915 | ! |
percent = FALSE, digits = 1 |
916 |
) |
|
917 | ! |
r$report( |
918 | ! |
"tox_at_doses_selected", |
919 | ! |
"True toxicity at doses selected" |
920 |
) |
|
921 | ! |
cat( |
922 | ! |
"Proportion of trials selecting target MTD:", |
923 | ! |
r$dfSave( |
924 | ! |
object@prop_at_target * 100, |
925 | ! |
"prop_at_target" |
926 |
), |
|
927 | ! |
"%\n" |
928 |
) |
|
929 | ! |
cat( |
930 | ! |
"Dose most often selected as MTD:", |
931 | ! |
r$dfSave( |
932 | ! |
object@dose_most_selected, |
933 | ! |
"dose_most_selected" |
934 |
), |
|
935 | ! |
"\n" |
936 |
) |
|
937 | ! |
cat( |
938 | ! |
"Observed toxicity rate at dose most often selected:", |
939 | ! |
r$dfSave( |
940 | ! |
round(object@obs_tox_rate_at_dose_most_selected * 100), |
941 | ! |
"obs_tox_rate_at_dose_most_selected" |
942 |
), |
|
943 | ! |
"%\n" |
944 |
) |
|
945 | ||
946 |
## finally assign names to the df |
|
947 |
## and return it invisibly |
|
948 | ! |
names(r$df) <- r$dfNames |
949 | ! |
invisible(r$df) |
950 |
} |
|
951 |
) |
|
952 | ||
953 |
##' Show the summary of the simulations |
|
954 |
##' |
|
955 |
##' @param object the \code{\linkS4class{SimulationsSummary}} object we want |
|
956 |
##' to print |
|
957 |
##' @return invisibly returns a data frame of the results with one row and |
|
958 |
##' appropriate column names |
|
959 |
##' |
|
960 |
##' @example examples/Simulations-method-show-SimulationsSummary.R |
|
961 |
##' @export |
|
962 |
##' @keywords methods |
|
963 |
setMethod("show", |
|
964 |
signature = |
|
965 |
signature(object = "SimulationsSummary"), |
|
966 |
def = |
|
967 |
function(object) { |
|
968 |
## call the parent method |
|
969 | ! |
df <- callNextMethod(object) |
970 | ! |
dfNames <- names(df) |
971 | ||
972 |
## start report object |
|
973 | ! |
r <- Report$new( |
974 | ! |
object = object, |
975 | ! |
df = df, |
976 | ! |
dfNames = dfNames |
977 |
) |
|
978 | ||
979 | ||
980 |
## add one reporting line |
|
981 | ! |
r$report( |
982 | ! |
"fit_at_dose_most_selected", |
983 | ! |
"Fitted toxicity rate at dose most often selected" |
984 |
) |
|
985 | ||
986 |
# Report results of additional statistics summary |
|
987 | ||
988 | ! |
if (length(unlist(object@additional_stats)) > 0) { |
989 | ! |
param_names <- h_summarize_add_stats(stats_list = object@additional_stats)[[1]] |
990 | ! |
averages <- h_summarize_add_stats(stats_list = object@additional_stats)[[2]] |
991 | ||
992 | ! |
for (i in seq_along(param_names)) { |
993 | ! |
cat(param_names[i], ":", round(averages[[i]], 2), "\n") |
994 |
} |
|
995 |
} |
|
996 | ||
997 | ||
998 |
# Report individual stopping rules with non-<NA> labels. |
|
999 | ||
1000 | ! |
stop_pct_to_print <- h_calc_report_label_percentage(object@stop_report) |
1001 | ||
1002 | ! |
if (length(stop_pct_to_print) > 0) { |
1003 | ! |
cat( |
1004 | ! |
"Stop reason triggered:\n", |
1005 | ! |
paste(names(stop_pct_to_print), ": ", round(stop_pct_to_print, 2), "%\n") |
1006 |
) |
|
1007 |
} |
|
1008 | ||
1009 |
## and return the updated information |
|
1010 | ! |
names(r$df) <- r$dfNames |
1011 | ! |
invisible(r$df) |
1012 |
} |
|
1013 |
) |
|
1014 | ||
1015 |
##' Show the summary of the dual-endpoint simulations |
|
1016 |
##' |
|
1017 |
##' @param object the \code{\linkS4class{DualSimulationsSummary}} object we want |
|
1018 |
##' to print |
|
1019 |
##' @return invisibly returns a data frame of the results with one row and |
|
1020 |
##' appropriate column names |
|
1021 |
##' |
|
1022 |
##' @example examples/Simulations-method-show-DualSimulationsSummary.R |
|
1023 |
##' @export |
|
1024 |
##' @keywords methods |
|
1025 |
setMethod("show", |
|
1026 |
signature = |
|
1027 |
signature(object = "DualSimulationsSummary"), |
|
1028 |
def = |
|
1029 |
function(object) { |
|
1030 |
## call the parent method |
|
1031 | ! |
df <- callNextMethod(object) |
1032 | ! |
dfNames <- names(df) |
1033 | ||
1034 |
## start report object |
|
1035 | ! |
r <- Report$new( |
1036 | ! |
object = object, |
1037 | ! |
df = df, |
1038 | ! |
dfNames = dfNames |
1039 |
) |
|
1040 | ||
1041 |
## add one reporting line |
|
1042 | ! |
r$report("biomarker_fit_at_dose_most_selected", |
1043 | ! |
"Fitted biomarker level at dose most often selected", |
1044 | ! |
percent = FALSE, |
1045 | ! |
digits = 1 |
1046 |
) |
|
1047 | ||
1048 |
## and return the updated information |
|
1049 | ! |
names(r$df) <- r$dfNames |
1050 | ! |
invisible(r$df) |
1051 |
} |
|
1052 |
) |
|
1053 | ||
1054 | ||
1055 |
##' Graphical display of the general simulation summary |
|
1056 |
##' |
|
1057 |
##' This plot method can be applied to |
|
1058 |
##' \code{\linkS4class{GeneralSimulationsSummary}} objects in order to |
|
1059 |
##' summarize them graphically. Possible \code{type}s of plots at the moment |
|
1060 |
##' are: |
|
1061 |
##' |
|
1062 |
##' \describe{ |
|
1063 |
##' \item{nObs}{Distribution of the number of patients in the simulated trials} |
|
1064 |
##' \item{doseSelected}{Distribution of the final selected doses in the trials. |
|
1065 |
##' Note that this can include zero entries, meaning that the trial was stopped |
|
1066 |
##' because all doses in the dose grid appeared too toxic.} |
|
1067 |
##' \item{propDLTs}{Distribution of the proportion of patients with DLTs in the |
|
1068 |
##' trials} |
|
1069 |
##' \item{nAboveTarget}{Distribution of the number of patients treated at doses |
|
1070 |
##' which are above the target toxicity interval (as specified by the |
|
1071 |
##' \code{truth} and \code{target} arguments to |
|
1072 |
##' \code{\link{summary,GeneralSimulations-method}})} |
|
1073 |
##' } |
|
1074 |
##' You can specify any subset of these in the \code{type} argument. |
|
1075 |
##' |
|
1076 |
##' @param x the \code{\linkS4class{GeneralSimulationsSummary}} object we want |
|
1077 |
##' to plot from |
|
1078 |
##' @param y missing |
|
1079 |
##' @param type the types of plots you want to obtain. |
|
1080 |
##' @param \dots not used |
|
1081 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
1082 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
1083 |
##' |
|
1084 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
1085 |
##' scale_linetype_manual scale_colour_manual |
|
1086 |
##' @importFrom gridExtra arrangeGrob |
|
1087 |
##' @export |
|
1088 |
##' @keywords methods |
|
1089 |
setMethod("plot", |
|
1090 |
signature = |
|
1091 |
signature( |
|
1092 |
x = "GeneralSimulationsSummary", |
|
1093 |
y = "missing" |
|
1094 |
), |
|
1095 |
def = |
|
1096 |
function(x, |
|
1097 |
y, |
|
1098 |
type = |
|
1099 |
c( |
|
1100 |
"nObs", |
|
1101 |
"doseSelected", |
|
1102 |
"propDLTs", |
|
1103 |
"nAboveTarget" |
|
1104 |
), |
|
1105 |
...) { |
|
1106 |
## which plots should be produced? |
|
1107 | ! |
type <- match.arg(type, |
1108 | ! |
several.ok = TRUE |
1109 |
) |
|
1110 | ! |
stopifnot(length(type) > 0L) |
1111 | ||
1112 |
## start the plot list |
|
1113 | ! |
plotList <- list() |
1114 | ! |
plotIndex <- 0L |
1115 | ||
1116 |
## distribution of overall sample size |
|
1117 | ! |
if (x@placebo) { |
1118 | ! |
if ("nObs" %in% type) { |
1119 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
1120 | ! |
h_barplot_percentages( |
1121 | ! |
x = x@n_obs[2, ], |
1122 | ! |
description = "Number of patients on active in total" |
1123 |
) |
|
1124 |
} |
|
1125 |
} else { |
|
1126 | ! |
if ("nObs" %in% type) { |
1127 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
1128 | ! |
h_barplot_percentages( |
1129 | ! |
x = x@n_obs, |
1130 | ! |
description = "Number of patients in total" |
1131 |
) |
|
1132 |
} |
|
1133 |
} |
|
1134 | ||
1135 |
## distribution of final MTD estimate |
|
1136 | ! |
if ("doseSelected" %in% type) { |
1137 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
1138 | ! |
h_barplot_percentages( |
1139 | ! |
x = x@dose_selected, |
1140 | ! |
description = "MTD estimate" |
1141 |
) |
|
1142 |
} |
|
1143 | ||
1144 |
## distribution of proportion of DLTs |
|
1145 | ! |
if (x@placebo) { |
1146 | ! |
if ("propDLTs" %in% type) { |
1147 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
1148 | ! |
h_barplot_percentages( |
1149 | ! |
x = x@prop_dlts[1, ] * 100, |
1150 | ! |
description = "Proportion of DLTs [%] on active", |
1151 | ! |
xaxisround = 1 |
1152 |
) |
|
1153 |
} |
|
1154 |
} else { |
|
1155 | ! |
if ("propDLTs" %in% type) { |
1156 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
1157 | ! |
h_barplot_percentages( |
1158 | ! |
x = x@prop_dlts * 100, |
1159 | ! |
description = "Proportion of DLTs [%]", |
1160 | ! |
xaxisround = 1 |
1161 |
) |
|
1162 |
} |
|
1163 |
} |
|
1164 | ||
1165 |
## distribution of number of patients treated at too much tox |
|
1166 | ! |
if ("nAboveTarget" %in% type) { |
1167 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
1168 | ! |
h_barplot_percentages( |
1169 | ! |
x = x@n_above_target, |
1170 | ! |
description = "Number of patients above target" |
1171 |
) |
|
1172 |
} |
|
1173 | ||
1174 |
## first combine these small plots |
|
1175 | ! |
if (length(plotList)) { |
1176 | ! |
ret <- |
1177 |
## if there is only one plot |
|
1178 | ! |
if (identical( |
1179 | ! |
length(plotList), |
1180 | ! |
1L |
1181 |
)) { |
|
1182 |
## just use that |
|
1183 | ! |
plotList[[1L]] |
1184 |
} else { |
|
1185 |
## multiple plots in this case |
|
1186 | ! |
do.call( |
1187 | ! |
gridExtra::arrangeGrob, |
1188 | ! |
plotList |
1189 |
) |
|
1190 |
} |
|
1191 |
} |
|
1192 | ||
1193 |
## then return |
|
1194 | ! |
ret |
1195 |
} |
|
1196 |
) |
|
1197 | ||
1198 | ||
1199 |
##' Plot summaries of the model-based design simulations |
|
1200 |
##' |
|
1201 |
##' Graphical display of the simulation summary |
|
1202 |
##' |
|
1203 |
##' This plot method can be applied to \code{\linkS4class{SimulationsSummary}} |
|
1204 |
##' objects in order to summarize them graphically. Possible \code{type} of |
|
1205 |
##' plots at the moment are those listed in |
|
1206 |
##' \code{\link{plot,GeneralSimulationsSummary,missing-method}} plus: |
|
1207 |
##' \describe{ |
|
1208 |
##' \item{meanFit}{Plot showing the average fitted dose-toxicity curve across |
|
1209 |
##' the trials, together with 95% credible intervals, and comparison with the |
|
1210 |
##' assumed truth (as specified by the \code{truth} argument to |
|
1211 |
##' \code{\link{summary,Simulations-method}})} |
|
1212 |
##' } |
|
1213 |
##' You can specify any subset of these in the \code{type} argument. |
|
1214 |
##' |
|
1215 |
##' @param x the \code{\linkS4class{SimulationsSummary}} object we want |
|
1216 |
##' to plot from |
|
1217 |
##' @param y missing |
|
1218 |
##' @param type the types of plots you want to obtain. |
|
1219 |
##' @param \dots not used |
|
1220 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
1221 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
1222 |
##' |
|
1223 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
1224 |
##' scale_linetype_manual scale_colour_manual |
|
1225 |
##' @importFrom gridExtra arrangeGrob |
|
1226 |
##' |
|
1227 |
##' @example examples/Simulations-method-plot-SimulationsSummary.R |
|
1228 |
##' @export |
|
1229 |
##' @keywords methods |
|
1230 |
setMethod("plot", |
|
1231 |
signature = |
|
1232 |
signature( |
|
1233 |
x = "SimulationsSummary", |
|
1234 |
y = "missing" |
|
1235 |
), |
|
1236 |
def = |
|
1237 |
function(x, |
|
1238 |
y, |
|
1239 |
type = |
|
1240 |
c( |
|
1241 |
"nObs", |
|
1242 |
"doseSelected", |
|
1243 |
"propDLTs", |
|
1244 |
"nAboveTarget", |
|
1245 |
"meanFit" |
|
1246 |
), |
|
1247 |
...) { |
|
1248 |
## which plots should be produced? |
|
1249 | ! |
type <- match.arg(type, |
1250 | ! |
several.ok = TRUE |
1251 |
) |
|
1252 | ! |
stopifnot(length(type) > 0L) |
1253 | ||
1254 |
## substract the specific plot types for model-based |
|
1255 |
## designs |
|
1256 | ! |
typeReduced <- setdiff( |
1257 | ! |
type, |
1258 | ! |
"meanFit" |
1259 |
) |
|
1260 | ||
1261 |
## are there more plots from general? |
|
1262 | ! |
moreFromGeneral <- (length(typeReduced) > 0) |
1263 | ||
1264 |
## if so, then produce these plots |
|
1265 | ! |
if (moreFromGeneral) { |
1266 | ! |
ret <- callNextMethod(x = x, y = y, type = typeReduced) |
1267 |
} |
|
1268 | ||
1269 |
## is the meanFit plot requested? |
|
1270 | ! |
if ("meanFit" %in% type) { |
1271 |
## which types of lines do we have? |
|
1272 | ! |
linetype <- c( |
1273 | ! |
"True toxicity", |
1274 | ! |
"Average estimated toxicity", |
1275 | ! |
"95% interval for estimated toxicity" |
1276 |
) |
|
1277 | ||
1278 |
## create the data frame, with |
|
1279 |
## true tox, average estimated tox, and 95% (lower, upper) |
|
1280 |
## estimated tox (in percentage) stacked below each other |
|
1281 | ! |
dat <- data.frame( |
1282 | ! |
dose = |
1283 | ! |
rep(x@dose_grid, 4L), |
1284 | ! |
group = |
1285 | ! |
rep(1:4, each = length(x@dose_grid)), |
1286 | ! |
linetype = |
1287 | ! |
factor( |
1288 | ! |
rep(linetype[c(1, 2, 3, 3)], |
1289 | ! |
each = length(x@dose_grid) |
1290 |
), |
|
1291 | ! |
levels = linetype |
1292 |
), |
|
1293 | ! |
lines = |
1294 | ! |
unlist(x@mean_fit) * 100 |
1295 |
) |
|
1296 | ||
1297 |
## linetypes for the plot |
|
1298 | ! |
lt <- c( |
1299 | ! |
"True toxicity" = 1, |
1300 | ! |
"Average estimated toxicity" = 1, |
1301 | ! |
"95% interval for estimated toxicity" = 2 |
1302 |
) |
|
1303 | ||
1304 |
## colour for the plot |
|
1305 | ! |
col <- c( |
1306 | ! |
"True toxicity" = 1, |
1307 | ! |
"Average estimated toxicity" = 2, |
1308 | ! |
"95% interval for estimated toxicity" = 2 |
1309 |
) |
|
1310 | ||
1311 |
## now create and save the plot |
|
1312 | ! |
thisPlot <- ggplot() + |
1313 | ! |
geom_line( |
1314 | ! |
aes( |
1315 | ! |
x = dose, |
1316 | ! |
y = lines, |
1317 | ! |
group = group, |
1318 | ! |
linetype = linetype, |
1319 | ! |
col = linetype |
1320 |
), |
|
1321 | ! |
data = dat |
1322 |
) |
|
1323 | ||
1324 | ! |
thisPlot <- thisPlot + |
1325 | ! |
scale_linetype_manual(values = lt) + |
1326 | ! |
scale_colour_manual(values = col) + |
1327 | ! |
xlab("Dose level") + |
1328 | ! |
ylab("Probability of DLT [%]") |
1329 | ||
1330 |
## add this plot to the bottom |
|
1331 | ! |
ret <- |
1332 | ! |
if (moreFromGeneral) { |
1333 | ! |
gridExtra::arrangeGrob(ret, thisPlot) |
1334 |
} else { |
|
1335 | ! |
thisPlot |
1336 |
} |
|
1337 |
} |
|
1338 | ||
1339 |
## then finally plot everything |
|
1340 | ! |
ret |
1341 |
} |
|
1342 |
) |
|
1343 | ||
1344 | ||
1345 |
##' Plot summaries of the dual-endpoint design simulations |
|
1346 |
##' |
|
1347 |
##' This plot method can be applied to \code{\linkS4class{DualSimulationsSummary}} |
|
1348 |
##' objects in order to summarize them graphically. Possible \code{type} of |
|
1349 |
##' plots at the moment are those listed in |
|
1350 |
##' \code{\link{plot,SimulationsSummary,missing-method}} plus: |
|
1351 |
##' \describe{ |
|
1352 |
##' \item{meanBiomarkerFit}{Plot showing the average fitted dose-biomarker curve across |
|
1353 |
##' the trials, together with 95% credible intervals, and comparison with the |
|
1354 |
##' assumed truth (as specified by the \code{trueBiomarker} argument to |
|
1355 |
##' \code{\link{summary,DualSimulations-method}})} |
|
1356 |
##' } |
|
1357 |
##' You can specify any subset of these in the \code{type} argument. |
|
1358 |
##' |
|
1359 |
##' @param x the \code{\linkS4class{DualSimulationsSummary}} object we want |
|
1360 |
##' to plot from |
|
1361 |
##' @param y missing |
|
1362 |
##' @param type the types of plots you want to obtain. |
|
1363 |
##' @param \dots not used |
|
1364 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
1365 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
1366 |
##' |
|
1367 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
1368 |
##' scale_linetype_manual scale_colour_manual |
|
1369 |
##' @importFrom gridExtra arrangeGrob |
|
1370 |
##' |
|
1371 |
##' @example examples/Simulations-method-plot-DualSimulationsSummary.R |
|
1372 |
##' @export |
|
1373 |
##' @keywords methods |
|
1374 |
setMethod("plot", |
|
1375 |
signature = |
|
1376 |
signature( |
|
1377 |
x = "DualSimulationsSummary", |
|
1378 |
y = "missing" |
|
1379 |
), |
|
1380 |
def = |
|
1381 |
function(x, |
|
1382 |
y, |
|
1383 |
type = |
|
1384 |
c( |
|
1385 |
"nObs", |
|
1386 |
"doseSelected", |
|
1387 |
"propDLTs", |
|
1388 |
"nAboveTarget", |
|
1389 |
"meanFit", |
|
1390 |
"meanBiomarkerFit" |
|
1391 |
), |
|
1392 |
...) { |
|
1393 |
## which plots should be produced? |
|
1394 | ! |
type <- match.arg(type, |
1395 | ! |
several.ok = TRUE |
1396 |
) |
|
1397 | ! |
stopifnot(length(type) > 0L) |
1398 | ||
1399 |
## substract the specific plot types for dual-endpoint |
|
1400 |
## designs |
|
1401 | ! |
typeReduced <- setdiff( |
1402 | ! |
type, |
1403 | ! |
"meanBiomarkerFit" |
1404 |
) |
|
1405 | ||
1406 |
## are there more plots from general? |
|
1407 | ! |
moreFromGeneral <- (length(typeReduced) > 0) |
1408 | ||
1409 |
## if so, then produce these plots |
|
1410 | ! |
if (moreFromGeneral) { |
1411 | ! |
ret <- callNextMethod(x = x, y = y, type = typeReduced) |
1412 |
} |
|
1413 | ||
1414 |
## is the meanBiomarkerFit plot requested? |
|
1415 | ! |
if ("meanBiomarkerFit" %in% type) { |
1416 |
## which types of lines do we have? |
|
1417 | ! |
linetype <- c( |
1418 | ! |
"True biomarker", |
1419 | ! |
"Average estimated biomarker", |
1420 | ! |
"95% interval for estimated biomarker" |
1421 |
) |
|
1422 | ||
1423 |
## create the data frame, with |
|
1424 |
## true biomarker, average estimated biomarker, and 95% (lower, upper) |
|
1425 |
## estimated biomarker stacked below each other |
|
1426 | ! |
dat <- data.frame( |
1427 | ! |
dose = |
1428 | ! |
rep(x@dose_grid, 4L), |
1429 | ! |
group = |
1430 | ! |
rep(1:4, each = length(x@dose_grid)), |
1431 | ! |
linetype = |
1432 | ! |
factor( |
1433 | ! |
rep(linetype[c(1, 2, 3, 3)], |
1434 | ! |
each = length(x@dose_grid) |
1435 |
), |
|
1436 | ! |
levels = linetype |
1437 |
), |
|
1438 | ! |
lines = |
1439 | ! |
unlist(x@mean_biomarker_fit) |
1440 |
) |
|
1441 | ||
1442 |
## linetypes for the plot |
|
1443 | ! |
lt <- c( |
1444 | ! |
"True biomarker" = 1, |
1445 | ! |
"Average estimated biomarker" = 1, |
1446 | ! |
"95% interval for estimated biomarker" = 2 |
1447 |
) |
|
1448 | ||
1449 |
## colour for the plot |
|
1450 | ! |
col <- c( |
1451 | ! |
"True biomarker" = 1, |
1452 | ! |
"Average estimated biomarker" = 2, |
1453 | ! |
"95% interval for estimated biomarker" = 2 |
1454 |
) |
|
1455 | ||
1456 |
## now create and save the plot |
|
1457 | ! |
thisPlot <- ggplot() + |
1458 | ! |
geom_line( |
1459 | ! |
aes( |
1460 | ! |
x = dose, |
1461 | ! |
y = lines, |
1462 | ! |
group = group, |
1463 | ! |
linetype = linetype, |
1464 | ! |
col = linetype |
1465 |
), |
|
1466 | ! |
data = dat |
1467 |
) |
|
1468 | ||
1469 | ! |
thisPlot <- thisPlot + |
1470 | ! |
scale_linetype_manual(values = lt) + |
1471 | ! |
scale_colour_manual(values = col) + |
1472 | ! |
xlab("Dose level") + |
1473 | ! |
ylab("Biomarker level") |
1474 | ||
1475 |
## add this plot to the bottom |
|
1476 | ! |
ret <- |
1477 | ! |
if (moreFromGeneral) { |
1478 | ! |
gridExtra::arrangeGrob(ret, thisPlot, heights = c(2 / 3, 1 / 3)) |
1479 |
} else { |
|
1480 | ! |
thisPlot |
1481 |
} |
|
1482 |
} |
|
1483 | ||
1484 |
## then finally plot everything |
|
1485 | ! |
ret |
1486 |
} |
|
1487 |
) |
|
1488 | ||
1489 | ||
1490 |
## -------------------------------------------------------------------------------------------------------- |
|
1491 |
##' Summarize the simulations, relative to a given truth |
|
1492 |
##' |
|
1493 |
##' @param object the \code{\linkS4class{PseudoSimulations}} object we want to |
|
1494 |
##' summarize |
|
1495 |
##' @param truth a function which takes as input a dose (vector) and returns the |
|
1496 |
##' true probability (vector) for toxicity |
|
1497 |
##' @param targetEndOfTrial the target probability of DLE wanted to achieve at the end of a trial |
|
1498 |
##' @param targetDuringTrial the target probability of DLE wanted to achieve during a trial |
|
1499 |
##' |
|
1500 |
##' @param \dots Additional arguments can be supplied here for \code{truth} |
|
1501 |
##' @return an object of class \code{\linkS4class{PseudoSimulationsSummary}} |
|
1502 |
##' |
|
1503 |
##' @example examples/Simulations-method-summarySIMsingle.R |
|
1504 |
##' @export |
|
1505 |
##' @keywords methods |
|
1506 |
setMethod("summary", |
|
1507 |
signature = |
|
1508 |
signature(object = "PseudoSimulations"), |
|
1509 |
def = |
|
1510 |
function(object, |
|
1511 |
truth, |
|
1512 |
targetEndOfTrial = 0.3, |
|
1513 |
targetDuringTrial = 0.35, |
|
1514 |
...) { |
|
1515 |
## extract dose grid |
|
1516 | ! |
doseGrid <- object@data[[1]]@doseGrid |
1517 | ||
1518 |
## evaluate true DLE at doseGrid |
|
1519 | ! |
trueDLE <- truth(doseGrid) |
1520 | ||
1521 |
## Inverse function of the truth function |
|
1522 | ! |
inverse <- function(f, lower = -100, upper = 100) { |
1523 | ! |
function(y) uniroot((function(x) f(x) - y), lower = lower, upper = upper)[1] |
1524 |
} |
|
1525 | ||
1526 |
## Function to obtain corresponsing dose level given target prob |
|
1527 | ! |
TD <- inverse(truth, 0, max(doseGrid)) |
1528 | ||
1529 |
## Find the dose corresponding to the target dose during trial |
|
1530 | ! |
targetDoseEndOfTrial <- as.numeric(TD(targetEndOfTrial)) |
1531 | ||
1532 | ||
1533 |
## Find the dose corresponding to the target does end of trial |
|
1534 | ! |
targetDoseDuringTrial <- as.numeric(TD(targetDuringTrial)) |
1535 | ||
1536 |
## Find the dose at doseGrid corresponding to the above two quantities |
|
1537 | ! |
targetDoseEndOfTrialAtDoseGrid <- doseGrid[max(which(targetDoseEndOfTrial - doseGrid >= 0))] |
1538 | ! |
targetDoseDuringTrialAtDoseGrid <- doseGrid[max(which(targetDoseDuringTrial - doseGrid >= 0))] |
1539 | ||
1540 |
## A summary for all TDtargetEndOfTrial dose obtained |
|
1541 | ! |
TDEOTSummary <- summary(object@final_td_target_end_of_trial_estimates) |
1542 | ||
1543 | ! |
FinalDoseRecSummary <- TDEOTSummary |
1544 | ||
1545 | ! |
ratioTDEOTSummary <- summary(object@final_tdeot_ratios) |
1546 | ! |
FinalRatioSummary <- ratioTDEOTSummary |
1547 | ||
1548 | ||
1549 |
## A summary for all TDtargetDuringTrial dose obtained |
|
1550 | ! |
TDDTSummary <- summary(object@final_td_target_during_trial_estimates) |
1551 |
## what are the levels above target End of Trial? |
|
1552 | ! |
xAboveTargetEndOfTrial <- which(trueDLE > targetEndOfTrial) |
1553 | ||
1554 |
## what are the levels above target During Trial? |
|
1555 | ! |
xAboveTargetDuringTrial <- which(trueDLE > targetDuringTrial) |
1556 | ||
1557 | ||
1558 |
## proportion of DLEs in this trial |
|
1559 | ! |
propDLE <- sapply( |
1560 | ! |
object@data, |
1561 | ! |
function(d) { |
1562 | ! |
mean(d@y) |
1563 |
} |
|
1564 |
) |
|
1565 |
### mean toxicity risk |
|
1566 | ! |
meanToxRisk <- sapply( |
1567 | ! |
object@data, |
1568 | ! |
function(d) { |
1569 | ! |
mean(trueDLE[d@xLevel]) |
1570 |
} |
|
1571 |
) |
|
1572 | ||
1573 |
## doses selected for MTD |
|
1574 | ! |
doseSelected <- object@doses |
1575 | ||
1576 |
## replace NA by 0 |
|
1577 | ! |
doseSelected[is.na(doseSelected)] <- 0 |
1578 | ||
1579 |
## dose most often selected as MTD |
|
1580 | ! |
doseMostSelected <- |
1581 | ! |
as.numeric(names(which.max(table(doseSelected)))) |
1582 | ||
1583 |
# doseRec <- doseMostSelected |
|
1584 | ||
1585 | ! |
xMostSelected <- |
1586 | ! |
match_within_tolerance(doseMostSelected, |
1587 | ! |
table = doseGrid |
1588 |
) |
|
1589 | ||
1590 |
## observed toxicity rate at dose most often selected |
|
1591 |
## Note: this does not seem very useful! |
|
1592 |
## Reason: In case of a fine grid, few patients if any |
|
1593 |
## will have been treated at this dose. |
|
1594 | ! |
tmp <- |
1595 | ! |
sapply( |
1596 | ! |
object@data, |
1597 | ! |
function(d) { |
1598 | ! |
whichAtThisDose <- which(d@x == doseMostSelected) |
1599 | ! |
nAtThisDose <- length(whichAtThisDose) |
1600 | ! |
nDLTatThisDose <- sum(d@y[whichAtThisDose]) |
1601 | ! |
return(c( |
1602 | ! |
nAtThisDose = nAtThisDose, |
1603 | ! |
nDLTatThisDose = nDLTatThisDose |
1604 |
)) |
|
1605 |
} |
|
1606 |
) |
|
1607 | ||
1608 | ! |
obsToxRateAtDoseMostSelected <- |
1609 | ! |
mean(tmp["nDLTatThisDose", ]) / mean(tmp["nAtThisDose", ]) |
1610 | ||
1611 |
## number of patients overall |
|
1612 | ! |
nObs <- sapply( |
1613 | ! |
object@data, |
1614 | ! |
slot, |
1615 | ! |
"nObs" |
1616 |
) |
|
1617 | ||
1618 |
## number of patients treated above target End of trial |
|
1619 | ! |
nAboveTargetEndOfTrial <- sapply( |
1620 | ! |
object@data, |
1621 | ! |
function(d) { |
1622 | ! |
sum(d@xLevel %in% xAboveTargetEndOfTrial) |
1623 |
} |
|
1624 |
) |
|
1625 | ||
1626 |
## number of patients treated above target During trial |
|
1627 | ! |
nAboveTargetDuringTrial <- sapply( |
1628 | ! |
object@data, |
1629 | ! |
function(d) { |
1630 | ! |
sum(d@xLevel %in% xAboveTargetDuringTrial) |
1631 |
} |
|
1632 |
) |
|
1633 | ||
1634 | ! |
toxAtDoses <- truth(doseSelected) |
1635 | ||
1636 | ||
1637 |
## Proportion of trials selecting target TDEndOfTrial and TDDuringTrial |
|
1638 | ! |
nsim <- length(object@data) |
1639 | ||
1640 | ! |
propAtTargetEndOfTrial <- (length(which(object@doses == targetDoseEndOfTrialAtDoseGrid))) / nsim |
1641 | ! |
propAtTargetDuringTrial <- (length(which(object@doses == targetDoseDuringTrialAtDoseGrid))) / nsim |
1642 | ||
1643 | ! |
RecDoseSummary <- TDEOTSummary |
1644 | ||
1645 |
## fitted probDLE at dose most often selected |
|
1646 |
## find names in the fit list (check it is with or without samples) |
|
1647 | ! |
FitNames <- sapply(object@fit, names) |
1648 | ||
1649 | ||
1650 | ! |
if ("probDLE" %in% FitNames) { |
1651 | ! |
fitAtDoseMostSelected <- sapply( |
1652 | ! |
object@fit, |
1653 | ! |
function(f) { |
1654 | ! |
f$probDLE[xMostSelected] |
1655 |
} |
|
1656 |
) |
|
1657 | ! |
meanFitMatrix <- sapply( |
1658 | ! |
object@fit, |
1659 |
"[[", |
|
1660 | ! |
"probDLE" |
1661 |
) |
|
1662 | ||
1663 | ! |
meanFit <- list( |
1664 | ! |
truth = |
1665 | ! |
truth(doseGrid), |
1666 | ! |
average = rowMeans(meanFitMatrix) |
1667 |
) |
|
1668 |
} else { |
|
1669 |
## fitted toxicity rate at dose most often selected |
|
1670 | ! |
fitAtDoseMostSelected <- |
1671 | ! |
sapply( |
1672 | ! |
object@fit, |
1673 | ! |
function(f) { |
1674 | ! |
f$middle[xMostSelected] |
1675 |
} |
|
1676 |
) |
|
1677 | ||
1678 |
## mean fitted toxicity (average, lower and upper quantiles) |
|
1679 |
## at each dose level |
|
1680 |
## (this is required for plotting) |
|
1681 | ! |
meanFitMatrix <- sapply( |
1682 | ! |
object@fit, |
1683 |
"[[", |
|
1684 | ! |
"middle" |
1685 |
) |
|
1686 | ! |
meanFit <- list( |
1687 | ! |
truth = |
1688 | ! |
truth(doseGrid), |
1689 | ! |
average = |
1690 | ! |
rowMeans(meanFitMatrix), |
1691 | ! |
lower = |
1692 | ! |
apply( |
1693 | ! |
meanFitMatrix, |
1694 | ! |
1L, |
1695 | ! |
quantile, |
1696 | ! |
0.025 |
1697 |
), |
|
1698 | ! |
upper = |
1699 | ! |
apply( |
1700 | ! |
meanFitMatrix, |
1701 | ! |
1L, |
1702 | ! |
quantile, |
1703 | ! |
0.975 |
1704 |
) |
|
1705 |
) |
|
1706 |
} |
|
1707 | ||
1708 |
## give back an object of class GeneralSimulationsSummary, |
|
1709 |
## for which we then define a print / plot method |
|
1710 | ! |
ret <- .PseudoSimulationsSummary( |
1711 | ! |
targetEndOfTrial = targetEndOfTrial, |
1712 | ! |
targetDoseEndOfTrial = targetDoseEndOfTrial, |
1713 | ! |
targetDuringTrial = targetDuringTrial, |
1714 | ! |
targetDoseDuringTrial = targetDoseDuringTrial, |
1715 | ! |
targetDoseEndOfTrialAtDoseGrid = targetDoseEndOfTrialAtDoseGrid, |
1716 | ! |
targetDoseDuringTrialAtDoseGrid = targetDoseDuringTrialAtDoseGrid, |
1717 | ! |
TDEOTSummary = TDEOTSummary, |
1718 | ! |
TDDTSummary = TDDTSummary, |
1719 | ! |
FinalDoseRecSummary = FinalDoseRecSummary, |
1720 | ! |
ratioTDEOTSummary = ratioTDEOTSummary, |
1721 | ! |
FinalRatioSummary = FinalRatioSummary, |
1722 | ! |
nsim = length(object@data), |
1723 | ! |
propDLE = propDLE, |
1724 | ! |
meanToxRisk = meanToxRisk, |
1725 | ! |
doseSelected = doseSelected, |
1726 | ! |
doseMostSelected = doseMostSelected, |
1727 |
# doseRec=doseRec, |
|
1728 | ! |
obsToxRateAtDoseMostSelected = obsToxRateAtDoseMostSelected, |
1729 | ! |
nObs = nObs, |
1730 | ! |
nAboveTargetEndOfTrial = nAboveTargetEndOfTrial, |
1731 | ! |
nAboveTargetDuringTrial = nAboveTargetDuringTrial, |
1732 | ! |
toxAtDosesSelected = toxAtDoses, |
1733 | ! |
propAtTargetEndOfTrial = propAtTargetEndOfTrial, |
1734 | ! |
propAtTargetDuringTrial = propAtTargetDuringTrial, |
1735 | ! |
doseGrid = doseGrid, |
1736 | ! |
fitAtDoseMostSelected = fitAtDoseMostSelected, |
1737 | ! |
stop_report = object@stop_report, |
1738 | ! |
meanFit = meanFit |
1739 |
) |
|
1740 | ||
1741 | ! |
return(ret) |
1742 |
} |
|
1743 |
) |
|
1744 |
## ======================================================================================================== |
|
1745 |
##' Show the summary of the simulations |
|
1746 |
##' |
|
1747 |
##' @param object the \code{\linkS4class{PseudoSimulationsSummary}} object we want |
|
1748 |
##' to print |
|
1749 |
##' @return invisibly returns a data frame of the results with one row and |
|
1750 |
##' appropriate column names |
|
1751 |
##' |
|
1752 |
##' @example examples/Simulations-method-showSIMsingle.R |
|
1753 |
##' @export |
|
1754 |
##' @keywords methods |
|
1755 | ||
1756 |
setMethod("show", |
|
1757 |
signature = |
|
1758 |
signature(object = "PseudoSimulationsSummary"), |
|
1759 |
def = |
|
1760 |
function(object) { |
|
1761 | ! |
r <- Report$new( |
1762 | ! |
object = object, |
1763 | ! |
df = |
1764 | ! |
as.data.frame(matrix( |
1765 | ! |
nrow = 1, |
1766 | ! |
ncol = 0 |
1767 |
)), |
|
1768 | ! |
dfNames = character() |
1769 |
) |
|
1770 | ! |
cat( |
1771 | ! |
"Summary of", |
1772 | ! |
r$dfSave(object@nsim, "nsim"), |
1773 | ! |
"simulations\n\n" |
1774 |
) |
|
1775 | ||
1776 | ! |
cat( |
1777 | ! |
"Target probability of DLE p(DLE) used at the end of a trial was", |
1778 | ! |
r$dfSave( |
1779 | ! |
object@targetEndOfTrial * 100, |
1780 | ! |
"targetEndOfTrial" |
1781 | ! |
), "%\n" |
1782 |
) |
|
1783 | ||
1784 | ! |
cat( |
1785 | ! |
"The dose level corresponds to the target p(DLE) used at the end of a trial, TDEOT, was", |
1786 | ! |
r$dfSave( |
1787 | ! |
object@targetDoseEndOfTrial, |
1788 | ! |
"targetDoseEndOfTrial" |
1789 | ! |
), "\n" |
1790 |
) |
|
1791 | ! |
cat( |
1792 | ! |
"TDEOT at dose Grid was", |
1793 | ! |
r$dfSave( |
1794 | ! |
object@targetDoseEndOfTrialAtDoseGrid, |
1795 | ! |
"targetDoseEndOfTrialAtDoseGrid" |
1796 | ! |
), "\n" |
1797 |
) |
|
1798 | ||
1799 | ! |
cat( |
1800 | ! |
"Target p(DLE) used during a trial was", |
1801 | ! |
r$dfSave( |
1802 | ! |
object@targetDuringTrial * 100, |
1803 | ! |
"targetDuringTrial" |
1804 | ! |
), "%\n" |
1805 |
) |
|
1806 | ||
1807 | ! |
cat( |
1808 | ! |
"The dose level corresponds to the target p(DLE) used during a trial, TDDT, was", |
1809 | ! |
r$dfSave( |
1810 | ! |
object@targetDoseDuringTrial, |
1811 | ! |
"targetDoseDuringTrial" |
1812 | ! |
), "\n" |
1813 |
) |
|
1814 | ||
1815 | ! |
cat( |
1816 | ! |
"TDDT at dose Grid was", |
1817 | ! |
r$dfSave( |
1818 | ! |
object@targetDoseDuringTrialAtDoseGrid, |
1819 | ! |
"targetDoseDuringTrialAtDoseGrid" |
1820 | ! |
), "\n" |
1821 |
) |
|
1822 | ||
1823 | ! |
r$report("nObs", |
1824 | ! |
"Number of patients overall", |
1825 | ! |
percent = FALSE |
1826 |
) |
|
1827 | ! |
r$report("nAboveTargetEndOfTrial", |
1828 | ! |
"Number of patients treated above the target p(DLE) used at the end of a trial", |
1829 | ! |
percent = FALSE |
1830 |
) |
|
1831 | ||
1832 | ! |
r$report("nAboveTargetDuringTrial", |
1833 | ! |
"Number of patients treated above the target p(DLE) used during a trial", |
1834 | ! |
percent = FALSE |
1835 |
) |
|
1836 | ||
1837 | ! |
r$report( |
1838 | ! |
"propDLE", |
1839 | ! |
"Proportions of observed DLT in the trials" |
1840 |
) |
|
1841 | ! |
r$report( |
1842 | ! |
"meanToxRisk", |
1843 | ! |
"Mean toxicity risks for the patients" |
1844 |
) |
|
1845 | ! |
r$report("doseSelected", |
1846 | ! |
"Doses selected as TDEOT", |
1847 | ! |
percent = FALSE, digits = 1 |
1848 |
) |
|
1849 |
# r$report("doseRec", |
|
1850 |
# "Doses to recommend to subsequent study", |
|
1851 |
# percent=FALSE, digits=1) |
|
1852 | ||
1853 | ! |
r$report( |
1854 | ! |
"toxAtDosesSelected", |
1855 | ! |
"True toxicity at TDEOT" |
1856 |
) |
|
1857 | ||
1858 | ! |
cat( |
1859 | ! |
"Proportion of trials selecting the TDEOT:", |
1860 | ! |
r$dfSave( |
1861 | ! |
object@propAtTargetEndOfTrial * 100, |
1862 | ! |
"percentAtTarget" |
1863 |
), |
|
1864 | ! |
"%\n" |
1865 |
) |
|
1866 | ||
1867 | ||
1868 | ! |
cat( |
1869 | ! |
"Proportion of trials selecting the TDDT:", |
1870 | ! |
r$dfSave( |
1871 | ! |
object@propAtTargetDuringTrial * 100, |
1872 | ! |
"percentAtTarget" |
1873 |
), |
|
1874 | ! |
"%\n" |
1875 |
) |
|
1876 | ||
1877 | ! |
cat( |
1878 | ! |
"Dose most often selected as TDEOT:", |
1879 | ! |
r$dfSave( |
1880 | ! |
object@doseMostSelected, |
1881 | ! |
"doseMostSelected" |
1882 |
), |
|
1883 | ! |
"\n" |
1884 |
) |
|
1885 | ! |
cat( |
1886 | ! |
"Observed toxicity rate at dose most often selected:", |
1887 | ! |
r$dfSave( |
1888 | ! |
round(object@obsToxRateAtDoseMostSelected * 100), |
1889 | ! |
"obsToxRateAtDoseMostSelected" |
1890 |
), |
|
1891 | ! |
"%\n" |
1892 |
) |
|
1893 | ! |
r$report( |
1894 | ! |
"fitAtDoseMostSelected", |
1895 | ! |
"Fitted probabilities of DLE at dose most often selected" |
1896 |
) |
|
1897 | ||
1898 | ! |
TDEOTSum <- object@TDEOTSummary |
1899 | ||
1900 | ! |
r$dfSave(as.numeric(TDEOTSum[1]), "TDEOTMin") |
1901 | ! |
r$dfSave(as.numeric(TDEOTSum[2]), "TDEOTlower") |
1902 | ! |
r$dfSave(as.numeric(TDEOTSum[3]), "TDEOTMedian") |
1903 | ! |
r$dfSave(as.numeric(TDEOTSum[4]), "TDEOTMean") |
1904 | ! |
r$dfSave(as.numeric(TDEOTSum[5]), "TDEOTUpper") |
1905 | ! |
r$dfSave(as.numeric(TDEOTSum[6]), "TDEOTMax") |
1906 | ||
1907 | ! |
cat( |
1908 | ! |
"The summary table of the final TDEOT across all simulations\n", |
1909 | ! |
capture.output(TDEOTSum)[1], "\n", |
1910 | ! |
capture.output(TDEOTSum)[2], "\n" |
1911 |
) |
|
1912 | ||
1913 | ! |
ratioTDEOTSum <- object@ratioTDEOTSummary |
1914 | ||
1915 | ! |
r$dfSave(as.numeric(ratioTDEOTSum[1]), "ratioTDEOTMin") |
1916 | ! |
r$dfSave(as.numeric(ratioTDEOTSum[2]), "ratioTDEOTlower") |
1917 | ! |
r$dfSave(as.numeric(ratioTDEOTSum[3]), "ratioTDEOTMedian") |
1918 | ! |
r$dfSave(as.numeric(ratioTDEOTSum[4]), "ratioTDEOTMean") |
1919 | ! |
r$dfSave(as.numeric(ratioTDEOTSum[5]), "ratioTDEOTUpper") |
1920 | ! |
r$dfSave(as.numeric(ratioTDEOTSum[6]), "ratioTDEOTMax") |
1921 | ||
1922 | ! |
cat( |
1923 | ! |
"The summary table of the final ratios of the TDEOT across all simulations\n", |
1924 | ! |
capture.output(ratioTDEOTSum)[1], "\n", |
1925 | ! |
capture.output(ratioTDEOTSum)[2], "\n" |
1926 |
) |
|
1927 | ||
1928 | ! |
TDDTSum <- object@TDDTSummary |
1929 | ||
1930 | ! |
r$dfSave(as.numeric(TDDTSum[1]), "TDDTMin") |
1931 | ! |
r$dfSave(as.numeric(TDDTSum[2]), "TDDTlower") |
1932 | ! |
r$dfSave(as.numeric(TDDTSum[3]), "TDDTMedian") |
1933 | ! |
r$dfSave(as.numeric(TDDTSum[4]), "TDDTMean") |
1934 | ! |
r$dfSave(as.numeric(TDDTSum[5]), "TDDTUpper") |
1935 | ! |
r$dfSave(as.numeric(TDDTSum[6]), "TDDTMax") |
1936 | ||
1937 | ! |
cat( |
1938 | ! |
"The summary table of the final TDDT across all simulations\n", |
1939 | ! |
capture.output(TDDTSum)[1], "\n", |
1940 | ! |
capture.output(TDDTSum)[2], "\n" |
1941 |
) |
|
1942 | ||
1943 | ! |
FinalDoseRecSum <- object@FinalDoseRecSummary |
1944 | ||
1945 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[1]), "FinalDoseRecMin") |
1946 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[2]), "FinalDoseReclower") |
1947 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[3]), "FinalDoseRecMedian") |
1948 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[4]), "FinalDoseRecMean") |
1949 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[5]), "FinalDoseRecUpper") |
1950 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[6]), "FinalDoseRecMax") |
1951 | ||
1952 | ! |
cat( |
1953 | ! |
"The summary table of dose levels, the optimal dose\n to recommend for subsequent study across all simulations\n", |
1954 | ! |
capture.output(FinalDoseRecSum)[1], "\n", |
1955 | ! |
capture.output(FinalDoseRecSum)[2], "\n" |
1956 |
) |
|
1957 | ||
1958 | ||
1959 | ! |
FinalratioSum <- object@FinalRatioSummary |
1960 | ||
1961 | ! |
r$dfSave(as.numeric(FinalratioSum[1]), "FinalratioMin") |
1962 | ! |
r$dfSave(as.numeric(FinalratioSum[2]), "Finalratiolower") |
1963 | ! |
r$dfSave(as.numeric(FinalratioSum[3]), "FinalratioMedian") |
1964 | ! |
r$dfSave(as.numeric(FinalratioSum[4]), "FinalratioMean") |
1965 | ! |
r$dfSave(as.numeric(FinalratioSum[5]), "FinalratioUpper") |
1966 | ! |
r$dfSave(as.numeric(FinalratioSum[6]), "FinalratioMax") |
1967 | ||
1968 | ! |
cat( |
1969 | ! |
"The summary table of the final ratios of the optimal dose for stopping across |
1970 | ! |
all simulations\n", |
1971 | ! |
capture.output(FinalratioSum)[1], "\n", |
1972 | ! |
capture.output(FinalratioSum)[2], "\n\n" |
1973 |
) |
|
1974 | ||
1975 |
# Report individual stopping rules with non-<NA> labels. |
|
1976 | ||
1977 | ! |
stop_pct_to_print <- h_calc_report_label_percentage(object@stop_report) |
1978 | ||
1979 | ! |
if (length(stop_pct_to_print) > 0) { |
1980 | ! |
cat( |
1981 | ! |
"Stop reason triggered:\n", |
1982 | ! |
paste(names(stop_pct_to_print), ": ", stop_pct_to_print, "%\n") |
1983 |
) |
|
1984 |
} |
|
1985 | ||
1986 |
## finally assign names to the df |
|
1987 |
## and return it invisibly |
|
1988 | ! |
names(r$df) <- r$dfNames |
1989 | ! |
invisible(r$df) |
1990 |
} |
|
1991 |
) |
|
1992 |
## ------------------------------------------------------------------------------------------- |
|
1993 |
##' Plot summaries of the pseudo simulations |
|
1994 |
##' |
|
1995 |
##' Graphical display of the simulation summary |
|
1996 |
##' |
|
1997 |
##' This plot method can be applied to \code{\linkS4class{PseudoSimulationsSummary}} |
|
1998 |
##' objects in order to summarize them graphically. This can be used when only DLE responses are involved |
|
1999 |
##' in the simulations. This also applied to results with or without samples generated during the simulations |
|
2000 |
##' |
|
2001 |
##' @param x the \code{\linkS4class{PseudoSimulationsSummary}} object we want |
|
2002 |
##' to plot from |
|
2003 |
##' @param y missing |
|
2004 |
##' @param type the types of plots you want to obtain. |
|
2005 |
##' @param \dots not used |
|
2006 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
2007 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
2008 |
##' |
|
2009 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
2010 |
##' scale_linetype_manual scale_colour_manual |
|
2011 |
##' @importFrom gridExtra arrangeGrob |
|
2012 |
##' |
|
2013 |
##' @example examples/Simulations-method-plotSUMsingle.R |
|
2014 |
##' @export |
|
2015 |
##' @keywords methods |
|
2016 |
##' |
|
2017 | ||
2018 |
setMethod("plot", |
|
2019 |
signature = |
|
2020 |
signature( |
|
2021 |
x = "PseudoSimulationsSummary", |
|
2022 |
y = "missing" |
|
2023 |
), |
|
2024 |
def = |
|
2025 |
function(x, |
|
2026 |
y, |
|
2027 |
type = |
|
2028 |
c( |
|
2029 |
"nObs", |
|
2030 |
"doseSelected", |
|
2031 |
"propDLE", |
|
2032 |
"nAboveTargetEndOfTrial", |
|
2033 |
"meanFit" |
|
2034 |
), |
|
2035 |
...) { |
|
2036 |
## which plots should be produced? |
|
2037 | ! |
type <- match.arg(type, |
2038 | ! |
several.ok = TRUE |
2039 |
) |
|
2040 | ! |
stopifnot(length(type) > 0L) |
2041 | ||
2042 |
## start the plot list |
|
2043 | ! |
plotList <- list() |
2044 | ! |
plotIndex <- 0L |
2045 | ||
2046 |
## distribution of overall sample size |
|
2047 | ! |
if ("nObs" %in% type) { |
2048 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
2049 | ! |
h_barplot_percentages( |
2050 | ! |
x = x@nObs, |
2051 | ! |
description = "Number of patients in total" |
2052 |
) |
|
2053 |
} |
|
2054 | ||
2055 |
## distribution of final MTD estimate |
|
2056 | ! |
if ("doseSelected" %in% type) { |
2057 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
2058 | ! |
h_barplot_percentages( |
2059 | ! |
x = x@doseSelected, |
2060 | ! |
description = "MTD estimate" |
2061 |
) |
|
2062 |
} |
|
2063 | ||
2064 |
## distribution of proportion of DLTs |
|
2065 | ! |
if ("propDLE" %in% type) { |
2066 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
2067 | ! |
h_barplot_percentages( |
2068 | ! |
x = x@propDLE * 100, |
2069 | ! |
description = "Proportion of DLE [%]", |
2070 | ! |
xaxisround = 1 |
2071 |
) |
|
2072 |
} |
|
2073 | ||
2074 |
## distribution of number of patients treated at too much tox |
|
2075 | ! |
if ("nAboveTargetEndOfTrial" %in% type) { |
2076 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
2077 | ! |
h_barplot_percentages( |
2078 | ! |
x = x@nAboveTargetEndOfTrial, |
2079 | ! |
description = "Number of patients above target" |
2080 |
) |
|
2081 |
} |
|
2082 | ||
2083 | ||
2084 |
## first combine these small plots |
|
2085 | ! |
if (length(plotList)) { |
2086 | ! |
ret <- |
2087 |
## if there is only one plot |
|
2088 | ! |
if (identical( |
2089 | ! |
length(plotList), |
2090 | ! |
1L |
2091 |
)) { |
|
2092 |
## just use that |
|
2093 | ! |
plotList[[1L]] |
2094 |
} else { |
|
2095 |
## multiple plots in this case |
|
2096 | ! |
do.call( |
2097 | ! |
gridExtra::arrangeGrob, |
2098 | ! |
plotList |
2099 |
) |
|
2100 |
} |
|
2101 |
} |
|
2102 | ||
2103 |
## the meanFit plot |
|
2104 | ||
2105 | ! |
if ("meanFit" %in% type) { ## Find if DLE samples are generated in the simulations |
2106 |
## by checking if there the lower limits of the 95% Credibility |
|
2107 |
## interval are calculated |
|
2108 | ! |
if (!is.null(x@meanFit$lower)) { |
2109 |
## which types of lines do we have? |
|
2110 | ! |
linetype <- c( |
2111 | ! |
"True toxicity", |
2112 | ! |
"Average estimated toxicity", |
2113 | ! |
"95% interval for estimated toxicity" |
2114 |
) |
|
2115 |
## create the data frame, with |
|
2116 |
## true tox, average estimated tox, and 95% (lower, upper) |
|
2117 |
## estimated tox (in percentage) stacked below each other |
|
2118 | ! |
dat <- data.frame( |
2119 | ! |
dose = |
2120 | ! |
rep(x@doseGrid, 4L), |
2121 | ! |
group = |
2122 | ! |
rep(1:4, each = length(x@doseGrid)), |
2123 | ! |
linetype = |
2124 | ! |
factor( |
2125 | ! |
rep(linetype[c(1, 2, 3, 3)], |
2126 | ! |
each = length(x@doseGrid) |
2127 |
), |
|
2128 | ! |
levels = linetype |
2129 |
), |
|
2130 | ! |
lines = |
2131 | ! |
unlist(x@meanFit) * 100 |
2132 |
) |
|
2133 | ||
2134 |
## linetypes for the plot |
|
2135 | ! |
lt <- c( |
2136 | ! |
"True toxicity" = 1, |
2137 | ! |
"Average estimated toxicity" = 1, |
2138 | ! |
"95% interval for estimated toxicity" = 2 |
2139 |
) |
|
2140 | ||
2141 |
## colour for the plot |
|
2142 | ! |
col <- c( |
2143 | ! |
"True toxicity" = 1, |
2144 | ! |
"Average estimated toxicity" = 2, |
2145 | ! |
"95% interval for estimated toxicity" = 2 |
2146 |
) |
|
2147 | ||
2148 |
## now create and save the plot |
|
2149 | ! |
thisPlot <- ggplot() + |
2150 | ! |
geom_line( |
2151 | ! |
aes( |
2152 | ! |
x = dose, |
2153 | ! |
y = lines, |
2154 | ! |
group = group, |
2155 | ! |
linetype = linetype, |
2156 | ! |
col = linetype |
2157 |
), |
|
2158 | ! |
data = dat |
2159 |
) |
|
2160 | ||
2161 | ! |
thisPlot <- thisPlot + |
2162 | ! |
scale_linetype_manual(values = lt) + |
2163 | ! |
scale_colour_manual(values = col) + |
2164 | ! |
xlab("Dose level") + |
2165 | ! |
ylab("Probability of DLE [%]") |
2166 |
} else { |
|
2167 |
## which types of lines do we have? |
|
2168 | ! |
linetype <- c( |
2169 | ! |
"True toxicity", |
2170 | ! |
"Average estimated toxicity" |
2171 |
) |
|
2172 | ||
2173 |
## create the data frame, with |
|
2174 |
## true tox, average estimated tox |
|
2175 |
## estimated tox (in percentage) stacked below each other |
|
2176 | ! |
dat <- data.frame( |
2177 | ! |
dose = |
2178 | ! |
rep(x@doseGrid, 2L), |
2179 | ! |
group = |
2180 | ! |
rep(1:2, each = length(x@doseGrid)), |
2181 | ! |
linetype = |
2182 | ! |
factor( |
2183 | ! |
rep(linetype[c(1, 2)], |
2184 | ! |
each = length(x@doseGrid) |
2185 |
), |
|
2186 | ! |
levels = linetype |
2187 |
), |
|
2188 | ! |
lines = |
2189 | ! |
unlist(x@meanFit) * 100 |
2190 |
) |
|
2191 | ||
2192 |
## linetypes for the plot |
|
2193 | ! |
lt <- c( |
2194 | ! |
"True toxicity" = 1, |
2195 | ! |
"Average estimated toxicity" = 1 |
2196 |
) |
|
2197 | ||
2198 |
## colour for the plot |
|
2199 | ! |
col <- c( |
2200 | ! |
"True toxicity" = 1, |
2201 | ! |
"Average estimated toxicity" = 2 |
2202 |
) |
|
2203 | ||
2204 |
## now create and save the plot |
|
2205 | ! |
thisPlot <- ggplot() + |
2206 | ! |
geom_line( |
2207 | ! |
aes( |
2208 | ! |
x = dose, |
2209 | ! |
y = lines, |
2210 | ! |
group = group, |
2211 | ! |
linetype = linetype, |
2212 | ! |
col = linetype |
2213 |
), |
|
2214 | ! |
data = dat |
2215 |
) |
|
2216 | ||
2217 | ! |
thisPlot <- thisPlot + |
2218 | ! |
scale_linetype_manual(values = lt) + |
2219 | ! |
scale_colour_manual(values = col) + |
2220 | ! |
xlab("Dose level") + |
2221 | ! |
ylab("Probability of DLE [%]") |
2222 |
} |
|
2223 |
} |
|
2224 | ||
2225 | ||
2226 |
## then add this plot at the bottom |
|
2227 | ! |
ret <- gridExtra::arrangeGrob(ret, thisPlot) |
2228 | ! |
ret |
2229 |
} |
|
2230 |
) |
|
2231 |
## -------------------------------------------------------------------------------------- |
|
2232 |
##' Plot simulations |
|
2233 |
##' |
|
2234 |
##' Summarize the simulations with plots |
|
2235 |
##' |
|
2236 |
##' This plot method can be applied to \code{\linkS4class{PseudoDualSimulations}} |
|
2237 |
##' objects in order to summarize them graphically. Possible \code{type}s of |
|
2238 |
##' plots at the moment are: \describe{ \item{trajectory}{Summary of the |
|
2239 |
##' trajectory of the simulated trials} \item{dosesTried}{Average proportions of |
|
2240 |
##' the doses tested in patients} \item{sigma2}{The variance of the efficacy responses}} |
|
2241 |
##' You can specify one or both of these in the |
|
2242 |
##' \code{type} argument. |
|
2243 |
##' |
|
2244 |
##' @param x the \code{\linkS4class{PseudoDualSimulations}} object we want |
|
2245 |
##' to plot from |
|
2246 |
##' @param y missing |
|
2247 |
##' @param type the type of plots you want to obtain. |
|
2248 |
##' @param \dots not used |
|
2249 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
2250 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
2251 |
##' |
|
2252 |
##' @importFrom ggplot2 ggplot geom_step geom_bar aes xlab ylab |
|
2253 |
##' scale_linetype_manual |
|
2254 |
##' @importFrom gridExtra arrangeGrob |
|
2255 |
##' |
|
2256 |
##' @example examples/Simulations-method-plotSIMDual.R |
|
2257 |
##' @export |
|
2258 |
##' @keywords methods |
|
2259 |
setMethod("plot", |
|
2260 |
signature = |
|
2261 |
signature( |
|
2262 |
x = "PseudoDualSimulations", |
|
2263 |
y = "missing" |
|
2264 |
), |
|
2265 |
def = |
|
2266 |
function(x, |
|
2267 |
y, |
|
2268 |
type = |
|
2269 |
c( |
|
2270 |
"trajectory", |
|
2271 |
"dosesTried", |
|
2272 |
"sigma2" |
|
2273 |
), |
|
2274 |
...) { |
|
2275 |
## start the plot list |
|
2276 | ! |
plotList <- list() |
2277 | ! |
plotIndex <- 0L |
2278 | ||
2279 |
## which plots should be produced? |
|
2280 | ! |
type <- match.arg(type, |
2281 | ! |
several.ok = TRUE |
2282 |
) |
|
2283 | ! |
stopifnot(length(type) > 0L) |
2284 | ||
2285 |
## substract the specific plot types for |
|
2286 |
## dual-endpoint simulation results |
|
2287 | ! |
typeReduced <- setdiff( |
2288 | ! |
type, |
2289 | ! |
"sigma2" |
2290 |
) |
|
2291 | ||
2292 |
## are there more plots from general? |
|
2293 | ! |
moreFromGeneral <- (length(typeReduced) > 0) |
2294 | ||
2295 |
## if so, then produce these plots |
|
2296 | ! |
if (moreFromGeneral) { |
2297 | ! |
genPlot <- callNextMethod(x = x, y = y, type = typeReduced) |
2298 |
} |
|
2299 | ||
2300 |
## now to the specific dual-endpoint plots: |
|
2301 | ||
2302 |
## Efficacy variance estimates boxplot |
|
2303 | ! |
if ("sigma2" %in% type) { |
2304 |
## save the plot |
|
2305 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
2306 | ! |
qplot(factor(0), |
2307 | ! |
y = y, data = data.frame(y = x@sigma2_est), geom = "boxplot", |
2308 | ! |
xlab = "", ylab = "Efficacy variance estimates" |
2309 |
) + |
|
2310 | ! |
coord_flip() + scale_x_discrete(breaks = NULL) |
2311 |
} |
|
2312 | ||
2313 | ||
2314 |
## then finally plot everything |
|
2315 | ! |
if (identical( |
2316 | ! |
length(plotList), |
2317 | ! |
0L |
2318 |
)) { |
|
2319 | ! |
return(genPlot) |
2320 | ! |
} else if (identical( |
2321 | ! |
length(plotList), |
2322 | ! |
1L |
2323 |
)) { |
|
2324 | ! |
ret <- plotList[[1L]] |
2325 |
} else { |
|
2326 | ! |
ret <- do.call( |
2327 | ! |
gridExtra::arrangeGrob, |
2328 | ! |
plotList |
2329 |
) |
|
2330 |
} |
|
2331 | ||
2332 | ! |
if (moreFromGeneral) { |
2333 | ! |
ret <- gridExtra::arrangeGrob(genPlot, ret, heights = c(2 / 3, 1 / 3)) |
2334 |
} |
|
2335 | ||
2336 | ! |
return(ret) |
2337 |
} |
|
2338 |
) |
|
2339 |
## --------------------------------------------------------------------------------- |
|
2340 |
##' |
|
2341 |
##' This plot method can be applied to \code{\linkS4class{PseudoDualFlexiSimulations}} |
|
2342 |
##' objects in order to summarize them graphically. Possible \code{type}s of |
|
2343 |
##' plots at the moment are: \describe{ \item{trajectory}{Summary of the |
|
2344 |
##' trajectory of the simulated trials} \item{dosesTried}{Average proportions of |
|
2345 |
##' the doses tested in patients} \item{sigma2}{The variance of the efficacy responses} |
|
2346 |
##' \item{sigma2betaW}{The variance of the random walk model}} |
|
2347 |
##' You can specify one or both of these in the |
|
2348 |
##' \code{type} argument. |
|
2349 |
##' |
|
2350 |
##' @param x the \code{\linkS4class{PseudoDualFlexiSimulations}} object we want |
|
2351 |
##' to plot from |
|
2352 |
##' @param y missing |
|
2353 |
##' @param type the type of plots you want to obtain. |
|
2354 |
##' @param \dots not used |
|
2355 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
2356 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
2357 |
##' |
|
2358 |
##' @importFrom ggplot2 ggplot geom_step geom_bar aes xlab ylab |
|
2359 |
##' scale_linetype_manual |
|
2360 |
##' @importFrom gridExtra arrangeGrob |
|
2361 |
##' |
|
2362 |
##' @example examples/Simulations-method-plotSIMDualFlexi.R |
|
2363 |
##' @export |
|
2364 |
##' @keywords methods |
|
2365 |
setMethod("plot", |
|
2366 |
signature = |
|
2367 |
signature( |
|
2368 |
x = "PseudoDualFlexiSimulations", |
|
2369 |
y = "missing" |
|
2370 |
), |
|
2371 |
def = |
|
2372 |
function(x, |
|
2373 |
y, |
|
2374 |
type = |
|
2375 |
c( |
|
2376 |
"trajectory", |
|
2377 |
"dosesTried", |
|
2378 |
"sigma2", |
|
2379 |
"sigma2betaW" |
|
2380 |
), |
|
2381 |
...) { |
|
2382 |
## start the plot list |
|
2383 | ! |
plotList <- list() |
2384 | ! |
plotIndex <- 0L |
2385 | ||
2386 |
## which plots should be produced? |
|
2387 | ! |
type <- match.arg(type, |
2388 | ! |
several.ok = TRUE |
2389 |
) |
|
2390 | ! |
stopifnot(length(type) > 0L) |
2391 | ||
2392 |
## substract the specific plot types for |
|
2393 |
## dual-endpoint simulation results |
|
2394 | ! |
typeReduced <- setdiff(type, "sigma2betaW") |
2395 | ||
2396 |
## are there more plots from general? |
|
2397 | ! |
moreFromGeneral <- (length(typeReduced) > 0) |
2398 | ||
2399 |
## if so, then produce these plots |
|
2400 | ! |
if (moreFromGeneral) { |
2401 | ! |
genPlot <- callNextMethod(x = x, y = y, type = typeReduced) |
2402 |
} |
|
2403 | ||
2404 |
## now to the specific dual-endpoint plots: |
|
2405 |
## random walk model variance estimates boxplot |
|
2406 | ||
2407 | ! |
if ("sigma2betaW" %in% type) { |
2408 |
## save the plot |
|
2409 | ! |
plotList[[plotIndex <- plotIndex + 1L]] <- |
2410 | ! |
qplot(factor(0), |
2411 | ! |
y = y, data = data.frame(y = x@sigma2betaWest), geom = "boxplot", |
2412 | ! |
xlab = "", ylab = "Random walk model variance estimates" |
2413 |
) + |
|
2414 | ! |
coord_flip() + scale_x_discrete(breaks = NULL) |
2415 |
} |
|
2416 | ||
2417 |
## then finally plot everything |
|
2418 | ! |
if (identical( |
2419 | ! |
length(plotList), |
2420 | ! |
0L |
2421 |
)) { |
|
2422 | ! |
return(genPlot) |
2423 | ! |
} else if (identical( |
2424 | ! |
length(plotList), |
2425 | ! |
1L |
2426 |
)) { |
|
2427 | ! |
ret <- plotList[[1L]] |
2428 |
} else { |
|
2429 | ! |
ret <- do.call( |
2430 | ! |
gridExtra::arrangeGrob, |
2431 | ! |
plotList |
2432 |
) |
|
2433 |
} |
|
2434 | ||
2435 | ! |
if (moreFromGeneral) { |
2436 | ! |
ret <- gridExtra::arrangeGrob(genPlot, ret, heights = c(2 / 3, 1 / 3)) |
2437 |
} |
|
2438 | ||
2439 | ! |
return(ret) |
2440 |
} |
|
2441 |
) |
|
2442 | ||
2443 |
## ----------------------------------------------------------------------------------------- |
|
2444 |
##' Summary for Pseudo Dual responses simulations, relative to a given pseudo DLE and efficacy model |
|
2445 |
##' (except the EffFlexi class model) |
|
2446 |
##' |
|
2447 |
##' @param object the \code{\linkS4class{PseudoDualSimulations}} object we want to summarize |
|
2448 |
##' @param trueDLE a function which takes as input a dose (vector) and returns the true probability (vector) |
|
2449 |
##' of DLE |
|
2450 |
##' @param trueEff a function which takes as input a dose (vector) and returns the mean efficacy value(s) (vector). |
|
2451 |
##' @param targetEndOfTrial the target probability of DLE that are used at the end of a trial. Default at 0.3. |
|
2452 |
##' @param targetDuringTrial the target probability of DLE that are used during the trial. Default at 0.35. |
|
2453 |
##' @param \dots Additional arguments can be supplied here for \code{trueDLE} and \code{trueEff} |
|
2454 |
##' @return an object of class \code{\linkS4class{PseudoDualSimulationsSummary}} |
|
2455 |
##' |
|
2456 |
##' @example examples/Simulations-method-summarySIMDual.R |
|
2457 |
##' @export |
|
2458 |
##' @keywords methods |
|
2459 |
setMethod("summary", |
|
2460 |
signature = |
|
2461 |
signature(object = "PseudoDualSimulations"), |
|
2462 |
def = |
|
2463 |
function(object, |
|
2464 |
trueDLE, |
|
2465 |
trueEff, |
|
2466 |
targetEndOfTrial = 0.3, |
|
2467 |
targetDuringTrial = 0.35, |
|
2468 |
...) { |
|
2469 |
## call the parent method |
|
2470 | ! |
start <- callNextMethod( |
2471 | ! |
object = object, |
2472 | ! |
truth = trueDLE, |
2473 | ! |
targetEndOfTrial = targetEndOfTrial, |
2474 | ! |
targetDuringTrial = targetDuringTrial, |
2475 |
... |
|
2476 |
) |
|
2477 | ! |
doseGrid <- object@data[[1]]@doseGrid |
2478 | ||
2479 |
## ## dose level most often selected as MTD (TDtargetEnd of Trial) |
|
2480 | ! |
xMostSelected <- |
2481 | ! |
match_within_tolerance(start@doseMostSelected, |
2482 | ! |
table = doseGrid |
2483 |
) |
|
2484 | ||
2485 |
## check if true Eff is a function |
|
2486 |
## check if special case applies |
|
2487 | ! |
isTrueEffFx <- is.function(trueEff) |
2488 | ||
2489 | ! |
TDtargetEndOfTrial <- start@targetDoseEndOfTrial |
2490 | ||
2491 | ||
2492 | ! |
if (isTrueEffFx) { |
2493 | ! |
negtrueGainfn <- function(dose) { |
2494 | ! |
return(-(trueEff(dose)) / (1 + (trueDLE(dose) / (1 - trueDLE(dose))))) |
2495 |
} |
|
2496 | ! |
Gstar <- optim(exp(1), negtrueGainfn, method = "BFGS")$par |
2497 | ! |
maxGainValue <- -(optim(exp(1), negtrueGainfn, method = "BFGS")$value) |
2498 | ! |
GstarAtDoseGrid <- doseGrid[max(which(Gstar - doseGrid >= 0))] |
2499 |
} else { |
|
2500 | ! |
trueGain <- (trueEff) / (1 + (trueDLE(doseGrid) / (1 - trueDLE(doseGrid)))) |
2501 | ! |
maxGainValue <- max(trueGain) |
2502 | ! |
Gstar <- doseGrid[which.max(trueGain)] |
2503 | ! |
GstarAtDoseGrid <- Gstar |
2504 |
} |
|
2505 | ||
2506 |
## A summary for all final Gstar obtained |
|
2507 | ! |
GstarSummary <- summary(object@final_gstar_estimates) |
2508 | ! |
ratioGstarSummary <- summary(object@final_gstar_ratios) |
2509 | ||
2510 | ! |
FinalDoseRecSummary <- summary(object@final_optimal_dose) |
2511 | ! |
FinalRatioSummary <- summary(object@final_ratios) |
2512 | ||
2513 | ||
2514 | ||
2515 |
## find names in the fit efficacy list (check it is with or without samples) |
|
2516 | ! |
FitNames <- sapply(object@fit_eff, names) |
2517 | ! |
if ("ExpEff" %in% FitNames) { |
2518 |
## fitted efficacy level at dose most often selected |
|
2519 | ! |
EffFitAtDoseMostSelected <- sapply( |
2520 | ! |
object@fit_eff, |
2521 | ! |
function(f) { |
2522 | ! |
f$ExpEff[xMostSelected] |
2523 |
} |
|
2524 |
) |
|
2525 | ! |
meanEffFitMatrix <- sapply( |
2526 | ! |
object@fit_eff, |
2527 |
"[[", |
|
2528 | ! |
"ExpEff" |
2529 |
) |
|
2530 | ||
2531 | ! |
meanEffFit <- list( |
2532 | ! |
truth = |
2533 | ! |
trueEff(doseGrid), |
2534 | ! |
average = rowMeans(meanEffFitMatrix) |
2535 |
) |
|
2536 |
} else { ## fitted efficacy level at dose most often selected |
|
2537 | ! |
EffFitAtDoseMostSelected <- |
2538 | ! |
sapply( |
2539 | ! |
object@fit_eff, |
2540 | ! |
function(f) { |
2541 | ! |
f$middle[xMostSelected] |
2542 |
} |
|
2543 |
) |
|
2544 | ||
2545 |
## mean fitted curve (average, lower and upper quantiles) |
|
2546 |
## at each dose level |
|
2547 |
## (this is required for plotting) |
|
2548 | ! |
meanEffFitMatrix <- sapply( |
2549 | ! |
object@fit_eff, |
2550 |
"[[", |
|
2551 | ! |
"middle" |
2552 |
) |
|
2553 | ||
2554 |
## check if special case applies |
|
2555 | ||
2556 | ! |
if (isTrueEffFx) { |
2557 | ! |
TRUTHeff <- trueEff(doseGrid) |
2558 |
} else { |
|
2559 | ! |
TRUTHeff <- trueEff |
2560 |
} |
|
2561 | ||
2562 | ! |
meanEffFit <- list( |
2563 | ! |
truth = |
2564 | ! |
TRUTHeff, |
2565 | ! |
average = |
2566 | ! |
rowMeans(meanEffFitMatrix), |
2567 | ! |
lower = |
2568 | ! |
apply( |
2569 | ! |
meanEffFitMatrix, |
2570 | ! |
1L, |
2571 | ! |
quantile, |
2572 | ! |
0.025 |
2573 |
), |
|
2574 | ! |
upper = |
2575 | ! |
apply( |
2576 | ! |
meanEffFitMatrix, |
2577 | ! |
1L, |
2578 | ! |
quantile, |
2579 | ! |
0.975 |
2580 |
) |
|
2581 |
) |
|
2582 |
} |
|
2583 | ||
2584 |
## give back an object of class PseudoDualSimulationsSummary, |
|
2585 |
## for which we then define a print / plot method |
|
2586 | ! |
ret <- .PseudoDualSimulationsSummary( |
2587 | ! |
start, |
2588 | ! |
targetGstar = Gstar, |
2589 | ! |
targetGstarAtDoseGrid = GstarAtDoseGrid, |
2590 | ! |
GstarSummary = GstarSummary, |
2591 | ! |
ratioGstarSummary = ratioGstarSummary, |
2592 | ! |
FinalDoseRecSummary = FinalDoseRecSummary, |
2593 | ! |
FinalRatioSummary = FinalRatioSummary, |
2594 | ! |
EffFitAtDoseMostSelected = EffFitAtDoseMostSelected, |
2595 | ! |
meanEffFit = meanEffFit, |
2596 | ! |
stop_report = object@stop_report |
2597 |
) |
|
2598 | ||
2599 | ! |
return(ret) |
2600 |
} |
|
2601 |
) |
|
2602 |
## -------------------------------------------------------------------------------------------------- |
|
2603 |
##' Summary for Pseudo Dual responses simulations given a pseudo DLE model and the Flexible efficacy model. |
|
2604 |
##' |
|
2605 |
##' @param object the \code{\linkS4class{PseudoDualFlexiSimulations}} object we want to summarize |
|
2606 |
##' @param trueDLE a function which takes as input a dose (vector) and returns the true probability of DLE (vector) |
|
2607 |
##' @param trueEff a vector which takes as input the true mean efficacy values at all dose levels (in order) |
|
2608 |
##' @param targetEndOfTrial the target probability of DLE that are used at the end of a trial. Default at 0.3. |
|
2609 |
##' @param targetDuringTrial the target probability of DLE that are used during the trial. Default at 0.35. |
|
2610 |
##' @param \dots Additional arguments can be supplied here for \code{trueDLE} and \code{trueEff} |
|
2611 |
##' @return an object of class \code{\linkS4class{PseudoDualSimulationsSummary}} |
|
2612 |
##' |
|
2613 |
##' @example examples/Simulations-method-summarySIMDualFlexi.R |
|
2614 |
##' @export |
|
2615 |
##' @keywords methods |
|
2616 |
setMethod("summary", |
|
2617 |
signature = |
|
2618 |
signature(object = "PseudoDualFlexiSimulations"), |
|
2619 |
def = |
|
2620 |
function(object, |
|
2621 |
trueDLE, |
|
2622 |
trueEff, |
|
2623 |
targetEndOfTrial = 0.3, |
|
2624 |
targetDuringTrial = 0.35, |
|
2625 |
...) { |
|
2626 |
## call the parent method |
|
2627 | ! |
start <- callNextMethod( |
2628 | ! |
object = object, |
2629 | ! |
trueDLE = trueDLE, |
2630 | ! |
trueEff = trueEff, |
2631 | ! |
targetEndOfTrial = targetEndOfTrial, |
2632 | ! |
targetDuringTrial = targetDuringTrial, |
2633 |
... |
|
2634 |
) |
|
2635 | ||
2636 | ||
2637 |
## give back an object of class PseudoDualSimulationsSummary, |
|
2638 |
## for which we then define a print / plot method |
|
2639 | ! |
ret <- .PseudoDualSimulationsSummary(start) |
2640 | ||
2641 | ! |
return(ret) |
2642 |
} |
|
2643 |
) |
|
2644 | ||
2645 |
## ---------------------------------------------------------------------------------------- |
|
2646 |
##' Show the summary of Pseudo Dual simulations summary |
|
2647 |
##' |
|
2648 |
##' @param object the \code{\linkS4class{PseudoDualSimulationsSummary}} object we want to print |
|
2649 |
##' @return invisibly returns a data frame of the results with one row and appropriate column names |
|
2650 |
##' |
|
2651 |
##' |
|
2652 |
##' @example examples/Simulations-method-showSIMDual.R |
|
2653 |
##' @export |
|
2654 |
##' @keywords methods |
|
2655 |
setMethod("show", |
|
2656 |
signature = |
|
2657 |
signature(object = "PseudoDualSimulationsSummary"), |
|
2658 |
def = |
|
2659 |
function(object) { |
|
2660 |
## call the parent method |
|
2661 | ! |
df <- callNextMethod(object) |
2662 | ! |
dfNames <- names(df) |
2663 | ||
2664 |
## start report object |
|
2665 | ! |
r <- Report$new( |
2666 | ! |
object = object, |
2667 | ! |
df = df, |
2668 | ! |
dfNames = dfNames |
2669 |
) |
|
2670 | ||
2671 |
## add three reporting lines |
|
2672 | ! |
cat( |
2673 | ! |
"Target Gstar, the dose which gives the maximum gain value was", |
2674 | ! |
r$dfSave( |
2675 | ! |
object@targetGstar, |
2676 | ! |
"targetGstar" |
2677 | ! |
), "\n" |
2678 |
) |
|
2679 | ! |
cat( |
2680 | ! |
"Target Gstar at dose Grid was", |
2681 | ! |
r$dfSave( |
2682 | ! |
object@targetGstarAtDoseGrid, |
2683 | ! |
"targetGstarAtDoseGrid" |
2684 | ! |
), "\n" |
2685 |
) |
|
2686 | ||
2687 | ! |
GstarSum <- object@GstarSummary |
2688 | ||
2689 | ! |
r$dfSave(as.numeric(GstarSum[1]), "GstarMin") |
2690 | ! |
r$dfSave(as.numeric(GstarSum[2]), "Gstarlower") |
2691 | ! |
r$dfSave(as.numeric(GstarSum[3]), "GstarMedian") |
2692 | ! |
r$dfSave(as.numeric(GstarSum[4]), "GstarMean") |
2693 | ! |
r$dfSave(as.numeric(GstarSum[5]), "GstarUpper") |
2694 | ! |
r$dfSave(as.numeric(GstarSum[6]), "GstarMax") |
2695 | ||
2696 | ! |
cat( |
2697 | ! |
"The summary table of the final Gstar across all simulations\n", |
2698 | ! |
capture.output(GstarSum)[1], "\n", |
2699 | ! |
capture.output(GstarSum)[2], "\n" |
2700 |
) |
|
2701 | ||
2702 | ! |
ratioGstarSum <- object@ratioGstarSummary |
2703 | ||
2704 | ! |
r$dfSave(as.numeric(ratioGstarSum[1]), "ratioGstarMin") |
2705 | ! |
r$dfSave(as.numeric(ratioGstarSum[2]), "ratioGstarlower") |
2706 | ! |
r$dfSave(as.numeric(ratioGstarSum[3]), "ratioGstarMedian") |
2707 | ! |
r$dfSave(as.numeric(ratioGstarSum[4]), "ratioGstarMean") |
2708 | ! |
r$dfSave(as.numeric(ratioGstarSum[5]), "ratioGstarUpper") |
2709 | ! |
r$dfSave(as.numeric(ratioGstarSum[6]), "ratioGstarMax") |
2710 | ||
2711 | ! |
cat( |
2712 | ! |
"The summary table of the final ratios of the Gstar across all simulations\n", |
2713 | ! |
capture.output(ratioGstarSum)[1], "\n", |
2714 | ! |
capture.output(ratioGstarSum)[2], "\n" |
2715 |
) |
|
2716 | ||
2717 | ! |
FinalDoseRecSum <- object@FinalDoseRecSummary |
2718 | ||
2719 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[1]), "FinalDoseRecMin") |
2720 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[2]), "FinalDoseReclower") |
2721 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[3]), "FinalDoseRecMedian") |
2722 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[4]), "FinalDoseRecMean") |
2723 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[5]), "FinalDoseRecUpper") |
2724 | ! |
r$dfSave(as.numeric(FinalDoseRecSum[6]), "FinalDoseRecMax") |
2725 | ||
2726 | ! |
cat( |
2727 | ! |
"The summary table of dose levels, the optimal dose\n to recommend for subsequent study across all simulations\n", |
2728 | ! |
capture.output(FinalDoseRecSum)[1], "\n", |
2729 | ! |
capture.output(FinalDoseRecSum)[2], "\n" |
2730 |
) |
|
2731 | ||
2732 | ! |
FinalratioSum <- object@FinalRatioSummary |
2733 | ||
2734 | ! |
r$dfSave(as.numeric(FinalratioSum[1]), "FinalratioMin") |
2735 | ! |
r$dfSave(as.numeric(FinalratioSum[2]), "Finalratiolower") |
2736 | ! |
r$dfSave(as.numeric(FinalratioSum[3]), "FinalratioMedian") |
2737 | ! |
r$dfSave(as.numeric(FinalratioSum[4]), "FinalratioMean") |
2738 | ! |
r$dfSave(as.numeric(FinalratioSum[5]), "FinalratioUpper") |
2739 | ! |
r$dfSave(as.numeric(FinalratioSum[6]), "FinalratioMax") |
2740 | ||
2741 | ! |
cat( |
2742 | ! |
"The summary table of the final ratios of the optimal dose for stopping across |
2743 | ! |
all simulations\n", |
2744 | ! |
capture.output(FinalratioSum)[1], "\n", |
2745 | ! |
capture.output(FinalratioSum)[2], "\n" |
2746 |
) |
|
2747 | ||
2748 | ||
2749 | ! |
r$report("EffFitAtDoseMostSelected", |
2750 | ! |
"Fitted expected efficacy level at dose most often selected", |
2751 | ! |
percent = FALSE, |
2752 | ! |
digits = 1 |
2753 |
) |
|
2754 | ||
2755 |
# Report individual stopping rules with non-<NA> labels. |
|
2756 | ||
2757 | ! |
stop_pct_to_print <- h_calc_report_label_percentage(object@stop_report) |
2758 | ||
2759 | ! |
if (length(stop_pct_to_print) > 0) { |
2760 | ! |
cat( |
2761 | ! |
"Stop reason triggered:\n", |
2762 | ! |
paste(names(stop_pct_to_print), ": ", stop_pct_to_print, "%\n") |
2763 |
) |
|
2764 |
} |
|
2765 | ||
2766 | ||
2767 |
## and return the updated information |
|
2768 | ! |
names(r$df) <- r$dfNames |
2769 | ! |
invisible(r$df) |
2770 |
} |
|
2771 |
) |
|
2772 | ||
2773 |
## -------------------------------------------------------------------------------------------------- |
|
2774 |
##' Plot the summary of Pseudo Dual Simulations summary |
|
2775 |
##' |
|
2776 |
##' This plot method can be applied to \code{\linkS4class{PseudoDualSimulationsSummary}} objects in order |
|
2777 |
##' to summarize them graphically. Possible \code{type} of plots at the moment are those listed in |
|
2778 |
##' \code{\link{plot,PseudoSimulationsSummary,missing-method}} plus: |
|
2779 |
##' \describe{\item{meanEffFit}{Plot showing the fitted dose-efficacy curve. If no samples are involved, only the |
|
2780 |
##' average fitted dose-efficacy curve across the trials will be plotted. If samples (DLE and efficacy) are involved, |
|
2781 |
##' the average fitted dose-efficacy curve across the trials, together with the 95% credibility interval; and comparison |
|
2782 |
##' with the assumed truth (as specified by the \code{trueEff} argument to |
|
2783 |
##' \code{\link{summary,PseudoDualSimulations-method}})}} |
|
2784 |
##' You can specify any subset of these in the \code{type} argument. |
|
2785 |
##' |
|
2786 |
##' @param x the \code{\linkS4class{PseudoDualSimulationsSummary}} object we want to plot from |
|
2787 |
##' @param y missing |
|
2788 |
##' @param type the types of plots you want to obtain. |
|
2789 |
##' @param \dots not used |
|
2790 |
##' @return A single \code{\link[ggplot2]{ggplot}} object if a single plot is |
|
2791 |
##' asked for, otherwise a \code{\link{gridExtra}{gTree}} object. |
|
2792 |
##' |
|
2793 |
##' @importFrom ggplot2 geom_histogram ggplot aes xlab ylab geom_line |
|
2794 |
##' scale_linetype_manual scale_colour_manual |
|
2795 |
##' @importFrom gridExtra arrangeGrob |
|
2796 |
##' |
|
2797 |
##' @example examples/Simulations-method-plotSUMDual.R |
|
2798 |
##' @export |
|
2799 |
##' @keywords methods |
|
2800 |
setMethod("plot", |
|
2801 |
signature = |
|
2802 |
signature( |
|
2803 |
x = "PseudoDualSimulationsSummary", |
|
2804 |
y = "missing" |
|
2805 |
), |
|
2806 |
def = |
|
2807 |
function(x, |
|
2808 |
y, |
|
2809 |
type = |
|
2810 |
c( |
|
2811 |
"nObs", |
|
2812 |
"doseSelected", |
|
2813 |
"propDLE", |
|
2814 |
"nAboveTargetEndOfTrial", |
|
2815 |
"meanFit", |
|
2816 |
"meanEffFit" |
|
2817 |
), |
|
2818 |
...) { |
|
2819 |
## which plots should be produced? |
|
2820 | ! |
type <- match.arg(type, |
2821 | ! |
several.ok = TRUE |
2822 |
) |
|
2823 | ! |
stopifnot(length(type) > 0L) |
2824 | ||
2825 |
## substract the specific plot types for dual-endpoint |
|
2826 |
## designs |
|
2827 | ! |
typeReduced <- setdiff( |
2828 | ! |
type, |
2829 | ! |
"meanEffFit" |
2830 |
) |
|
2831 | ||
2832 |
## are there more plots from general? |
|
2833 | ! |
moreFromGeneral <- (length(typeReduced) > 0) |
2834 | ||
2835 |
## if so, then produce these plots |
|
2836 | ! |
if (moreFromGeneral) { |
2837 | ! |
ret <- callNextMethod(x = x, y = y, type = typeReduced) |
2838 |
} |
|
2839 | ||
2840 |
## is the meanBiomarkerFit plot requested? |
|
2841 | ! |
if ("meanEffFit" %in% type) { ## Find if Effsamples are generated in the simulations |
2842 |
## by checking if there the lower limits of the 95% Credibility |
|
2843 |
## interval are calculated |
|
2844 | ! |
if (!is.null(x@meanEffFit$lower)) { |
2845 |
## which types of lines do we have? |
|
2846 | ! |
linetype <- c( |
2847 | ! |
"True Expected Efficacy", |
2848 | ! |
"Average estimated expected efficacy", |
2849 | ! |
"95% interval for estimated expected efficacy" |
2850 |
) |
|
2851 | ||
2852 |
## create the data frame, with |
|
2853 |
## true biomarker, average estimated expected efficacy, and 95% (lower, upper) |
|
2854 |
## estimated biomarker stacked below each other |
|
2855 | ! |
dat <- data.frame( |
2856 | ! |
dose = |
2857 | ! |
rep(x@doseGrid, 4L), |
2858 | ! |
group = |
2859 | ! |
rep(1:4, each = length(x@doseGrid)), |
2860 | ! |
linetype = |
2861 | ! |
factor( |
2862 | ! |
rep(linetype[c(1, 2, 3, 3)], |
2863 | ! |
each = length(x@doseGrid) |
2864 |
), |
|
2865 | ! |
levels = linetype |
2866 |
), |
|
2867 | ! |
lines = |
2868 | ! |
unlist(x@meanEffFit) |
2869 |
) |
|
2870 | ||
2871 |
## linetypes for the plot |
|
2872 | ! |
lt <- c( |
2873 | ! |
"True Expected Efficacy" = 1, |
2874 | ! |
"Average estimated expected efficacy" = 1, |
2875 | ! |
"95% interval for estimated expected efficacy" = 2 |
2876 |
) |
|
2877 | ||
2878 |
## colour for the plot |
|
2879 | ! |
col <- c( |
2880 | ! |
"True Expected Efficacy" = 1, |
2881 | ! |
"Average estimated expected efficacy" = 4, |
2882 | ! |
"95% interval for estimated expected efficacy" = 4 |
2883 |
) |
|
2884 | ||
2885 |
## now create and save the plot |
|
2886 | ! |
thisPlot <- ggplot() + |
2887 | ! |
geom_line( |
2888 | ! |
aes( |
2889 | ! |
x = dose, |
2890 | ! |
y = lines, |
2891 | ! |
group = group, |
2892 | ! |
linetype = linetype, |
2893 | ! |
col = linetype |
2894 |
), |
|
2895 | ! |
data = dat |
2896 |
) |
|
2897 | ||
2898 | ! |
thisPlot <- thisPlot + |
2899 | ! |
scale_linetype_manual(values = lt) + |
2900 | ! |
scale_colour_manual(values = col) + |
2901 | ! |
xlab("Dose level") + |
2902 | ! |
ylab("Expected Efficacy level") |
2903 |
} else { |
|
2904 | ! |
linetype <- c( |
2905 | ! |
"True Expected Efficacy", |
2906 | ! |
"Average estimated expected efficacy" |
2907 |
) |
|
2908 | ||
2909 |
## create the data frame, with |
|
2910 |
## true biomarker, average estimated expected efficacy |
|
2911 | ! |
dat <- data.frame( |
2912 | ! |
dose = |
2913 | ! |
rep(x@doseGrid, 2L), |
2914 | ! |
group = |
2915 | ! |
rep(1:2, each = length(x@doseGrid)), |
2916 | ! |
linetype = |
2917 | ! |
factor( |
2918 | ! |
rep(linetype[c(1, 2)], |
2919 | ! |
each = length(x@doseGrid) |
2920 |
), |
|
2921 | ! |
levels = linetype |
2922 |
), |
|
2923 | ! |
lines = |
2924 | ! |
unlist(x@meanEffFit) |
2925 |
) |
|
2926 | ||
2927 |
## linetypes for the plot |
|
2928 | ! |
lt <- c( |
2929 | ! |
"True Expected Efficacy" = 1, |
2930 | ! |
"Average estimated expected efficacy" = 1 |
2931 |
) |
|
2932 | ||
2933 |
## colour for the plot |
|
2934 | ! |
col <- c( |
2935 | ! |
"True Expected Efficacy" = 1, |
2936 | ! |
"Average estimated expected efficacy" = 4 |
2937 |
) |
|
2938 | ||
2939 |
## now create and save the plot |
|
2940 | ! |
thisPlot <- ggplot() + |
2941 | ! |
geom_line( |
2942 | ! |
aes( |
2943 | ! |
x = dose, |
2944 | ! |
y = lines, |
2945 | ! |
group = group, |
2946 | ! |
linetype = linetype, |
2947 | ! |
col = linetype |
2948 |
), |
|
2949 | ! |
data = dat |
2950 |
) |
|
2951 | ||
2952 | ! |
thisPlot <- thisPlot + |
2953 | ! |
scale_linetype_manual(values = lt) + |
2954 | ! |
scale_colour_manual(values = col) + |
2955 | ! |
xlab("Dose level") + |
2956 | ! |
ylab("Expected Efficacy level") |
2957 |
} |
|
2958 | ||
2959 |
## add this plot to the bottom |
|
2960 | ! |
ret <- |
2961 | ! |
if (moreFromGeneral) { |
2962 | ! |
gridExtra::arrangeGrob(ret, thisPlot, heights = c(2 / 3, 1 / 3)) |
2963 |
} else { |
|
2964 | ! |
thisPlot |
2965 |
} |
|
2966 |
} |
|
2967 | ||
2968 |
## then finally plot everything |
|
2969 | ! |
ret |
2970 |
} |
|
2971 |
) |
|
2972 | ||
2973 |
# nolint end |
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(x, min.len = 1, max.len = 2, any.missing = FALSE, unique = TRUE) |
14 | ||
15 | 600x |
if (length(x) == 1) { |
16 | 391x |
if (x == "toxicity") { |
17 | 165x |
x <- c("toxicity", "toxicities") |
18 |
} else { |
|
19 | 226x |
x[2] <- paste0(x[1], "s") |
20 |
} |
|
21 |
} |
|
22 | 600x |
x |
23 |
} |
|
24 | ||
25 |
#' Append Units to a Numeric Dose |
|
26 |
#' |
|
27 |
#' @param units (`character`)\cr the units to be displayed |
|
28 |
#' @keywords internal |
|
29 |
#' @return if `units` is `NA`, then `NA`. Otherwise, `units`, ensuring that exactly |
|
30 |
#' one space precedes the first non-whitespace character |
|
31 |
h_prepare_units <- function(units = NA) { |
|
32 | 222x |
assert_character(units, len = 1) |
33 | ||
34 | 222x |
ifelse( |
35 | 222x |
is.na(units), |
36 |
"", |
|
37 | 222x |
paste0(" ", stringr::str_trim(units, "left")) |
38 |
) |
|
39 |
} |
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 | 27x |
assert_integer(grade, len = 1, lower = 1) |
19 | 25x |
assert_class(obj, "Samples") |
20 | 24x |
assert_subset(c(paste0("alpha", 1:grade), "beta"), names(obj@data)) |
21 |
# Execute |
|
22 | 23x |
d <- list("alpha0" = obj@data[[paste0("alpha", grade)]], "alpha1" = obj@data$beta) |
23 | 23x |
Samples(data = d, options = obj@options) |
24 |
} |
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(object@ID, len = object@nObs, any.missing = FALSE, unique = TRUE, null.ok = TRUE), |
25 | 2x |
"ID must be of type integer and length nObs and unique" |
26 |
) |
|
27 | 2x |
v$check( |
28 | 2x |
test_integer(object@cohort, lower = 0L, len = object@nObs, any.missing = FALSE, sorted = TRUE), |
29 | 2x |
"cohort must be of type integer and length nObs and contain non-negative, sorted values" |
30 |
) |
|
31 | 2x |
v$result() |
32 |
} |
|
33 | ||
34 |
#' @describeIn v_data_objects helper function which verifies whether |
|
35 |
#' the `dose` values are unique in each and every different `cohort`. |
|
36 |
#' @param dose (`numeric`)\cr dose values. |
|
37 |
#' @param cohort (`integer`)\cr cohort indices parallel to `doses`. |
|
38 |
#' @return `TRUE` if `dose` is unique per `cohort`, otherwise `FALSE`. |
|
39 |
h_doses_unique_per_cohort <- function(dose, cohort) { |
|
40 | 1841x |
assert_numeric(dose) |
41 | 1841x |
assert_integer(cohort) |
42 | ||
43 | 1841x |
num_doses_per_cohort <- tapply( |
44 | 1841x |
X = dose, |
45 | 1841x |
INDEX = cohort, |
46 | 1841x |
FUN = function(d) length(unique(d)) |
47 |
) |
|
48 | 1841x |
all(num_doses_per_cohort == 1L) |
49 |
} |
|
50 | ||
51 |
#' Helper Function performing validation Common to Data and DataOrdinal |
|
52 |
#' |
|
53 |
#' @rdname h_validate_common_data_slots |
|
54 |
#' @param object (`Data` or `DataOrdinal`)\cr the object to be validated |
|
55 |
#' @returns a `Validate` object containing the result of the validation |
|
56 |
h_validate_common_data_slots <- function(object) { |
|
57 | 1839x |
v <- Validate() |
58 | 1839x |
v$check( |
59 | 1839x |
test_double(object@x, len = object@nObs, any.missing = FALSE), |
60 | 1839x |
"Doses vector x must be of type double and length nObs" |
61 |
) |
|
62 | 1839x |
v$check( |
63 | 1839x |
test_double(object@doseGrid, len = object@nGrid, any.missing = FALSE, unique = TRUE, sorted = TRUE), |
64 | 1839x |
"doseGrid must be of type double and length nGrid and contain unique, sorted values" |
65 |
) |
|
66 | 1839x |
v$check( |
67 | 1839x |
test_int(object@nGrid), |
68 | 1839x |
"Number of dose grid values nGrid must be scalar integer" |
69 |
) |
|
70 | 1839x |
v$check( |
71 | 1839x |
test_integer(object@xLevel, len = object@nObs, any.missing = FALSE), |
72 | 1839x |
"Levels xLevel for the doses the patients have been given must be of type integer and length nObs" |
73 |
) |
|
74 | 1839x |
v$check( |
75 | 1839x |
test_flag(object@placebo), |
76 | 1839x |
"The placebo flag must be scalar logical" |
77 |
) |
|
78 | 1839x |
v$check( |
79 | 1839x |
test_subset(object@x, object@doseGrid), |
80 | 1839x |
"Dose values in x must be from doseGrid" |
81 |
) |
|
82 | 1839x |
v$check( |
83 | 1839x |
h_all_equivalent(object@x, object@doseGrid[object@xLevel]), |
84 | 1839x |
"x must be equivalent to doseGrid[xLevel] (up to numerical tolerance)" |
85 |
) |
|
86 | 1839x |
if (object@placebo) { |
87 | 324x |
is_placebo <- object@x == object@doseGrid[1] |
88 | 324x |
v$check( |
89 | 324x |
test_set_equal(object@cohort, object@cohort[!is_placebo]), |
90 | 324x |
"A cohort with only placebo is not allowed" |
91 |
) |
|
92 | 324x |
v$check( |
93 | 324x |
h_doses_unique_per_cohort(dose = object@x[!is_placebo], cohort = object@cohort[!is_placebo]), |
94 | 324x |
"There must be only one dose level, other than placebo, per cohort" |
95 |
) |
|
96 |
} else { |
|
97 | 1515x |
v$check( |
98 | 1515x |
h_doses_unique_per_cohort(dose = object@x, cohort = object@cohort), |
99 | 1515x |
"There must be only one dose level per cohort" |
100 |
) |
|
101 |
} |
|
102 | 1839x |
v |
103 |
} |
|
104 | ||
105 |
#' @describeIn v_data_objects validates that the [`Data`] object contains |
|
106 |
#' valid elements with respect to their types, dependency and length. |
|
107 |
v_data <- function(object) { |
|
108 | 4x |
v <- h_validate_common_data_slots(object) |
109 | 4x |
v$check( |
110 | 4x |
test_integer(object@y, lower = 0, upper = 1, len = object@nObs, any.missing = FALSE), |
111 | 4x |
"DLT vector y must be nObs long and contain 0 or 1 integers only" |
112 |
) |
|
113 | ||
114 | 4x |
v$result() |
115 |
} |
|
116 | ||
117 |
#' @describeIn v_data_objects validates that the [`DataDual`] object |
|
118 |
#' contains valid biomarker vector with respect to its type and the length. |
|
119 |
v_data_dual <- function(object) { |
|
120 | 2x |
v <- Validate() |
121 | 2x |
v$check( |
122 | 2x |
test_double(object@w, len = object@nObs, any.missing = FALSE), |
123 | 2x |
"Biomarker vector w must be of type double and length nObs" |
124 |
) |
|
125 | 2x |
v$result() |
126 |
} |
|
127 | ||
128 |
#' @describeIn v_data_objects validates that the [`DataParts`] object |
|
129 |
#' contains valid elements with respect to their types, dependency and length. |
|
130 |
v_data_parts <- function(object) { |
|
131 | 5x |
v <- Validate() |
132 | 5x |
v$check( |
133 | 5x |
test_integer(object@part, lower = 1, upper = 2, len = object@nObs, any.missing = FALSE), |
134 | 5x |
"vector part must be nObs long and contain 1 or 2 integers only" |
135 |
) |
|
136 | 5x |
v$check( |
137 | 5x |
test_int(object@nextPart, lower = 1, upper = 2), |
138 | 5x |
"nextPart must be integer scalar 1 or 2" |
139 |
) |
|
140 | 5x |
v$check( |
141 | 5x |
test_numeric(object@part1Ladder, any.missing = FALSE, sorted = TRUE, unique = TRUE), |
142 | 5x |
"part1Ladder must be of type double and contain unique, sorted values" |
143 |
) |
|
144 | 5x |
v$check( |
145 | 5x |
test_subset(object@part1Ladder, object@doseGrid), |
146 | 5x |
"part1Ladder must have all entries from doseGrid" |
147 |
) |
|
148 | 5x |
v$result() |
149 |
} |
|
150 | ||
151 |
#' @describeIn v_data_objects validates that the [`DataMixture`] object |
|
152 |
#' contains valid elements with respect to their types, dependency and length. |
|
153 |
v_data_mixture <- function(object) { |
|
154 | 5x |
v <- Validate() |
155 | ||
156 |
# In if clause so that below test_* won't fail. |
|
157 | 5x |
if (!test_int(object@nObsshare)) { |
158 | 1x |
return("nObsshare must be of type integer of length 1") |
159 |
} |
|
160 | 4x |
v$check( |
161 | 4x |
test_numeric(object@xshare, len = object@nObsshare, any.missing = FALSE), |
162 | 4x |
"Dose vector xshare must be of type double and length nObsshare" |
163 |
) |
|
164 | 4x |
v$check( |
165 | 4x |
test_integer(object@yshare, lower = 0, upper = 1, len = object@nObsshare, any.missing = FALSE), |
166 | 4x |
"DLT vector yshare must be nObsshare long and contain 0 or 1 integers only" |
167 |
) |
|
168 | 4x |
v$check( |
169 | 4x |
test_subset(object@xshare, object@doseGrid), |
170 | 4x |
"Dose values in xshare must be from doseGrid" |
171 |
) |
|
172 | 4x |
v$result() |
173 |
} |
|
174 | ||
175 |
#' @describeIn v_data_objects validates that the [`DataDA`] object |
|
176 |
#' contains valid elements with respect to their types, dependency and length. |
|
177 |
v_data_da <- function(object) { |
|
178 | 4x |
v <- Validate() |
179 |
# In if clause so that below test_* won't fail. |
|
180 | 4x |
if (!(test_number(object@Tmax) && object@Tmax > 0)) { |
181 | 1x |
return("DLT window Tmax must be of type double of length 1 and greater than 0") |
182 |
} |
|
183 | 3x |
v$check( |
184 | 3x |
test_numeric(object@u, upper = object@Tmax, len = object@nObs, any.missing = FALSE) && |
185 | 3x |
all(object@u >= 0), |
186 | 3x |
"u must be of type double, nObs length, non-negative, not missing and not greater than Tmax" |
187 |
) |
|
188 | 3x |
v$check( |
189 | 3x |
test_numeric(object@t0, lower = 0, len = object@nObs, any.missing = FALSE, sorted = TRUE), |
190 | 3x |
"t0 must be of type double, nObs length, sorted non-negative" |
191 |
) |
|
192 | 3x |
v$result() |
193 |
} |
|
194 | ||
195 |
#' @describeIn v_data_objects validates that the [`DataOrdinal`] object |
|
196 |
#' contains valid elements with respect to their types, dependency and length. |
|
197 |
v_data_ordinal <- function(object) { |
|
198 | 8x |
v <- h_validate_common_data_slots(object) |
199 | 8x |
v$check( |
200 | 8x |
test_integer(object@y, lower = 0, upper = length(object@yCategories) - 1, len = object@nObs, any.missing = FALSE), |
201 | 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 |
202 |
) |
|
203 | 8x |
v$check( |
204 | 8x |
length(unique(names(object@yCategories))) == length(names(object@yCategories)), |
205 | 8x |
"yCategory labels must be unique" |
206 |
) |
|
207 | 8x |
v$result() |
208 |
} |
|
209 | ||
210 |
#' @describeIn v_data_objects validates that the [`DataGrouped`] object |
|
211 |
#' contains valid group information. |
|
212 |
v_data_grouped <- function(object) { |
|
213 | 3x |
v <- Validate() |
214 | 3x |
v$check( |
215 | 3x |
test_factor( |
216 | 3x |
object@group, |
217 | 3x |
levels = c("mono", "combo"), |
218 | 3x |
len = object@nObs, |
219 | 3x |
any.missing = FALSE |
220 |
), |
|
221 | 3x |
"group must be factor with levels mono and combo of length nObs without missings" |
222 |
) |
|
223 | 3x |
v$result() |
224 |
} |
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 |
# Validate |
|
61 | 24x |
assert_flag(asis) |
62 | 18x |
assert_flag(use_values) |
63 | 18x |
assert_format(fmt) |
64 |
# Initialise |
|
65 | 18x |
biomarker_label <- h_prepare_labels(biomarker_label) |
66 | 18x |
tox_label <- h_prepare_labels(tox_label) |
67 | 18x |
units <- h_prepare_units(units) |
68 |
# Execute |
|
69 | 18x |
toxModel <- ProbitLogNormal( |
70 | 18x |
cov = x@betaZ_params@cov, |
71 | 18x |
mean = x@betaZ_params@mean, |
72 | 18x |
ref_dose = x@ref_dose |
73 |
) |
|
74 | 18x |
rv <- paste0( |
75 | 18x |
"The relationships between dose and ", |
76 | 18x |
tox_label[1], |
77 | 18x |
" and between dose and ", |
78 | 18x |
biomarker_label[1], |
79 | 18x |
" will be modelled simultaneously.\n\n", |
80 | 18x |
knit_print( |
81 | 18x |
toxModel, |
82 | 18x |
asis = asis, |
83 | 18x |
tox_label = tox_label, |
84 | 18x |
use_values = use_values, |
85 | 18x |
fmt = fmt, |
86 | 18x |
units = units, |
87 |
... |
|
88 |
), |
|
89 | 18x |
"\n\n", |
90 | 18x |
"The ", |
91 | 18x |
biomarker_label[1], |
92 | 18x |
" response `w` at dose `d` is modelled as ", |
93 | 18x |
"$$ w(d) \\sim N(f(d), \\sigma_w^2) $$ \n\nwhere ", |
94 | 18x |
h_knit_print_render_biomarker_model(x, use_values = use_values, ...) |
95 |
) |
|
96 | 18x |
if (asis) { |
97 | 6x |
rv <- knitr::asis_output(rv) |
98 |
} |
|
99 | 18x |
rv |
100 |
} |
|
101 | ||
102 |
#' @description `r lifecycle::badge("experimental")` |
|
103 |
#' @noRd |
|
104 |
h_knit_print_render_biomarker_model.DualEndpoint <- function(x, ..., use_values = TRUE) { |
|
105 | ! |
"f(d) is a function of dose that is defined elsewhere." |
106 |
} |
|
107 | ||
108 |
# DualEndpointBeta ---- |
|
109 | ||
110 |
#' @description `r lifecycle::badge("experimental")` |
|
111 |
#' @noRd |
|
112 |
h_knit_print_render_biomarker_model.DualEndpointBeta <- function(x, ...) { |
|
113 | 4x |
paste0( |
114 | 4x |
"f(d) is a parametric rescaled beta density function such that\n\n", |
115 | 4x |
"$$ f(d) = ", |
116 | 4x |
"E_0 + (E_{max} - E_0) \\times Beta(\\delta_1, \\delta_2) \\times ", |
117 | 4x |
"\\left(\\frac{d}{d_{max}}\\right)^{\\delta_1} \\times \\left(1 - ", |
118 | 4x |
"\\frac{d}{d_{max}}\\right)^{\\delta_2} $$\n\n", |
119 | 4x |
"where d~max~ is the maximum dose in the dose grid, δ~1~ and ", |
120 | 4x |
"δ~2~ are the parameters of the Beta function and ", |
121 | 4x |
"E~0~ and E~max~ are, respectively, the minimum and maximum levels of the ", |
122 | 4x |
"biomarker. The mode can be written as \n\n", |
123 | 4x |
"$$ \\text{mode} = \\frac{\\delta_1}{\\delta_1 + \\delta_2} $$\n\n", |
124 | 4x |
" and this is the parameterisation used to define the model.\n\n", |
125 | 4x |
"In this case, \n\n", |
126 | 4x |
ifelse( |
127 | 4x |
length(x@E0) == 1, |
128 | 4x |
paste0("$$ E_0 = ", x@E0, " $$\n\n)"), |
129 | 4x |
paste0("$$ E_0 \\sim U(", x@E0[1], ", ", x@E0[2], ") $$\n\n") |
130 |
), |
|
131 | 4x |
ifelse( |
132 | 4x |
length(x@Emax) == 1, |
133 | 4x |
paste0("$$ E_{max} = ", x@Emax, " $$\n\n)"), |
134 | 4x |
paste0("$$ E_{max} \\sim U(", x@Emax[1], ", ", x@Emax[2], ") $$\n\n") |
135 |
), |
|
136 | 4x |
ifelse( |
137 | 4x |
length(x@delta1) == 1, |
138 | 4x |
paste0("$$ \\delta_1 = ", x@delta1, " $$\n\n)"), |
139 | 4x |
paste0("$$ \\delta_1 \\sim U(", x@delta1[1], ", ", x@delta1[2], ") $$\n\n") |
140 |
), |
|
141 | 4x |
ifelse( |
142 | 4x |
length(x@mode) == 1, |
143 | 4x |
paste0("$$ \\text{mode} = ", x@mode, " $$\n\n)"), |
144 | 4x |
paste0("$$ \\text{mode} \\sim U(", x@mode[1], ", ", x@mode[2], ") $$\n\n") |
145 |
), |
|
146 | 4x |
" and \n\n", |
147 | 4x |
ifelse( |
148 | 4x |
length(x@ref_dose_beta) == 1, |
149 | 4x |
paste0("$$ d_{max} = ", x@ref_dose_beta, " $$\n\n"), |
150 | 4x |
paste0("$$ d_{max} \\sim U(", x@ref_dose_beta[1], ", ", x@ref_dose_beta[2], ") $$\n\n") |
151 |
) |
|
152 |
) |
|
153 |
} |
|
154 | ||
155 |
# DualEndpointEmax ---- |
|
156 | ||
157 |
#' @description `r lifecycle::badge("experimental")` |
|
158 |
#' @noRd |
|
159 |
h_knit_print_render_biomarker_model.DualEndpointEmax <- function(x, ...) { |
|
160 | 4x |
paste0( |
161 | 4x |
"f(d) is a parametric Emax density function such that\n\n", |
162 | 4x |
"$$ f(d) = ", |
163 | 4x |
"E_0 + \\frac{(E_{max} - E_0) \\times \\frac{d}{d^*}}{\\text{ED}_{50} + \\frac{d}{d^*}} $$\n\n", |
164 | 4x |
"where d* is the reference dose, E~0~ and E~max~ are, respectively, the ", |
165 | 4x |
"minimum and maximum levels of the biomarker and ED~50~ is the dose achieving ", |
166 | 4x |
"half the maximum effect, 0.5 × E~max~.\n\n", |
167 | 4x |
"In this case, \n\n", |
168 | 4x |
ifelse( |
169 | 4x |
length(x@E0) == 1, |
170 | 4x |
paste0("$$ E_0 = ", x@E0, " $$\n\n)"), |
171 | 4x |
paste0("$$ E_0 \\sim U(", x@E0[1], ", ", x@E0[2], ") $$\n\n") |
172 |
), |
|
173 | 4x |
ifelse( |
174 | 4x |
length(x@Emax) == 1, |
175 | 4x |
paste0("$$ E_{max} = ", x@Emax, " $$\n\n)"), |
176 | 4x |
paste0("$$ E_{max} \\sim U(", x@Emax[1], ", ", x@Emax[2], ") $$\n\n") |
177 |
), |
|
178 | 4x |
ifelse( |
179 | 4x |
length(x@ED50) == 1, |
180 | 4x |
paste0("$$ \\text{ED}_{50} = ", x@ED50, " $$\n\n)"), |
181 | 4x |
paste0("$$ \\text{ED}_{50} \\sim U(", x@ED50[1], ", ", x@ED50[2], ") $$\n\n") |
182 |
), |
|
183 | 4x |
" and \n\n", |
184 | 4x |
ifelse( |
185 | 4x |
length(x@ref_dose_emax) == 1, |
186 | 4x |
paste0("$$ d^* = ", x@ref_dose_emax, " $$\n\n"), |
187 | 4x |
paste0("$$ d^* \\sim U(", x@ref_dose_emax[1], ", ", x@ref_dose_emax[2], ") $$\n\n") |
188 |
) |
|
189 |
) |
|
190 |
} |
|
191 | ||
192 |
# DualEndpointRW ---- |
|
193 | ||
194 |
#' @description `r lifecycle::badge("experimental")` |
|
195 |
#' @noRd |
|
196 |
h_knit_print_render_biomarker_model.DualEndpointRW <- function(x, ..., use_values = TRUE) { |
|
197 | 10x |
paste0( |
198 | 10x |
"f(d) is a ", |
199 | 10x |
ifelse(x@rw1, "first", "second"), |
200 | 10x |
" order random walk such that\n\n", |
201 | 10x |
"$$ f(d) = ", |
202 | 10x |
"\\beta_{W_i} - \\beta_{W_{i - ", |
203 | 10x |
ifelse(x@rw1, "1", "2"), |
204 |
"}}", |
|
205 | 10x |
"\\sim N(0, ", |
206 | 10x |
ifelse(x@rw1, "", "2 \\times "), |
207 | 10x |
ifelse( |
208 | 10x |
use_values & length(x@sigma2betaW) == 1, |
209 | 10x |
x@sigma2betaW, |
210 | 10x |
"\\sigma_{\\beta_W}^2" |
211 |
), |
|
212 | 10x |
" \\times (d_i - d_{i - ", |
213 | 10x |
ifelse(x@rw1, "1", "2"), |
214 |
"})", |
|
215 |
")", |
|
216 | 10x |
" $$\n\n", |
217 | 10x |
ifelse( |
218 | 10x |
length(x@sigma2betaW) == 1, |
219 | 10x |
ifelse( |
220 | 10x |
use_values, |
221 |
"", |
|
222 | 10x |
paste0(" and $\\sigma_{\\beta_W}^2$ is fixed at ", x@sigma2betaW) |
223 |
), |
|
224 | 10x |
paste0( |
225 | 10x |
" and the prior for $\\sigma_{\\beta_W}^2$ is an inverse-gamma distribution with parameters ", |
226 | 10x |
"a = ", |
227 | 10x |
x@sigma2betaW["a"], |
228 | 10x |
" and b = ", |
229 | 10x |
x@sigma2betaW["b"] |
230 |
) |
|
231 |
) |
|
232 |
) |
|
233 |
} |
|
234 | ||
235 |
# ModelParamsNormal ---- |
|
236 | ||
237 |
#' Render a Normal Prior |
|
238 |
#' |
|
239 |
#' @param x (`ModelParamsNormal`)\cr the object to be rendered |
|
240 |
#' @param use_values (`flag`)\cr print the values associated with hyperparameters, |
|
241 |
#' or the symbols used to define the hyper-parameters. That is, for example, mu or 1. |
|
242 |
#' @param fmt (`character`)\cr the `sprintf` format string used to render |
|
243 |
#' numerical values. Ignored if `use_values` is `FALSE`. |
|
244 |
#' @param params (`character`)\cr The names of the model parameters. See Usage |
|
245 |
#' Notes below. |
|
246 |
#' @param preamble (`character`)\cr The text used to introduce the LaTeX representation |
|
247 |
#' of the model |
|
248 |
#' @param asis (`flag`)\cr wrap the return value in a call to `knitr::asis_output`? |
|
249 |
#' @param theta (`character`)\cr the LaTeX representation of the theta vector |
|
250 |
#' @param ... Not used at present |
|
251 |
#' @section Usage Notes: |
|
252 |
#' `params` must be a character vector of length equal to that of `x@mean` (and |
|
253 |
#' `x@cov`). Its values represent the parameters of the model as entries in the |
|
254 |
#' vector `theta`, on the left-hand side of "~" in the definition of the prior. |
|
255 |
#' If named, names should be valid LaTeX, escaped as usual for R character variables. |
|
256 |
#' For example, `"\\alpha"` or `"\\beta_0"`. If unnamed, names are constructed by |
|
257 |
#' pre-pending an escaped backslash to each value provided. |
|
258 |
#' @return A character string containing a LaTeX rendition of the object. |
|
259 |
#' @description `r lifecycle::badge("experimental")` |
|
260 |
#' @export |
|
261 |
#' @rdname knit_print |
|
262 |
#' @method knit_print ModelParamsNormal |
|
263 |
knit_print.ModelParamsNormal <- function( |
|
264 |
x, |
|
265 |
use_values = TRUE, |
|
266 |
fmt = "%5.2f", |
|
267 |
params = c("alpha", "beta"), |
|
268 |
preamble = "The prior for θ is given by\\n", |
|
269 |
asis = TRUE, |
|
270 |
theta = "\\theta", |
|
271 |
...) { |
|
272 |
# Validate |
|
273 | 146x |
assert_class(x, "ModelParamsNormal") |
274 | 146x |
assert_format(fmt) |
275 | 146x |
assert_character(preamble, len = 1) |
276 | 146x |
assert_true(length(x@mean) == length(params)) |
277 | 146x |
assert_flag(asis) |
278 |
# Initialise |
|
279 | 144x |
n <- length(params) |
280 | 144x |
if (is.null(names(params))) { |
281 | 42x |
names(params) <- paste0("\\", params) |
282 |
} |
|
283 |
# Execute |
|
284 |
# Construct LaTeX representation of mean vector |
|
285 | 144x |
mu <- sapply( |
286 | 144x |
1:n, |
287 | 144x |
function(i) { |
288 | 312x |
ifelse( |
289 | 312x |
use_values, |
290 | 312x |
sprintf(fmt, x@mean[i]), |
291 | 312x |
paste0("\\mu_{\\", params[i], "}") |
292 |
) |
|
293 |
} |
|
294 |
) |
|
295 |
# Construct LaTeX representation of covariance matrix |
|
296 | 144x |
cov <- sapply( |
297 | 144x |
1:n, |
298 | 144x |
function(i) { |
299 | 312x |
sapply( |
300 | 312x |
1:n, |
301 | 312x |
function(j) { |
302 | 712x |
ifelse( |
303 | 712x |
use_values, |
304 | 712x |
sprintf(fmt, x@cov[i, j]), |
305 | 712x |
ifelse( |
306 | 712x |
i == j, |
307 | 712x |
paste0("\\sigma_{\\", params[i], "}^2"), |
308 | 712x |
paste0("\\rho\\sigma_{\\", params[i], "}\\sigma_{\\", params[j], "}") |
309 |
) |
|
310 |
) |
|
311 |
} |
|
312 |
) |
|
313 |
} |
|
314 |
) |
|
315 |
# Construct LaTeX representation of prior |
|
316 | 144x |
rv <- paste0( |
317 | 144x |
preamble, |
318 | 144x |
"$$ \\boldsymbol", |
319 | 144x |
theta, |
320 | 144x |
" = \\begin{bmatrix}", |
321 | 144x |
paste0(names(params), collapse = " \\\\ "), |
322 | 144x |
"\\end{bmatrix}", |
323 | 144x |
"\\sim N \\left(\\begin{bmatrix}", |
324 | 144x |
paste0(mu, collapse = " \\\\ "), |
325 | 144x |
"\\end{bmatrix} , ", |
326 | 144x |
"\\begin{bmatrix} ", |
327 | 144x |
paste0( |
328 | 144x |
sapply( |
329 | 144x |
1:n, |
330 | 144x |
function(j) { |
331 | 312x |
stringr::str_trim(paste0(cov[, j], collapse = " & ")) |
332 |
} |
|
333 |
), |
|
334 | 144x |
collapse = " \\\\ " |
335 |
), |
|
336 | 144x |
"\\end{bmatrix}", |
337 | 144x |
" \\right)", |
338 | 144x |
" $$\n\n" |
339 |
) |
|
340 | 144x |
if (asis) { |
341 | 46x |
rv <- knitr::asis_output(rv) |
342 |
} |
|
343 | 144x |
rv |
344 |
} |
|
345 | ||
346 |
# GeneralModel ---- |
|
347 | ||
348 |
#' @export |
|
349 |
#' @rdname knit_print |
|
350 |
#' @method knit_print GeneralModel |
|
351 |
knit_print.GeneralModel <- function( |
|
352 |
x, |
|
353 |
..., |
|
354 |
params = c("alpha", "beta"), |
|
355 |
asis = TRUE, |
|
356 |
use_values = TRUE, |
|
357 |
fmt = "%5.2f", |
|
358 |
units = NA) { |
|
359 |
# Validate |
|
360 | 90x |
assert_flag(asis) |
361 | 78x |
assert_flag(use_values) |
362 | 78x |
assert_format(fmt) |
363 |
# Execute |
|
364 | 78x |
rv <- paste0( |
365 | 78x |
h_knit_print_render_model(x, use_values = use_values, fmt = fmt, ...), |
366 | 78x |
knit_print(x@params, ..., asis = asis, use_values = use_values, fmt = fmt, params = params), |
367 | 78x |
"\\n\\n", |
368 | 78x |
h_knit_print_render_ref_dose(x, use_values = use_values, fmt = fmt, unit = unit) |
369 |
) |
|
370 | 78x |
if (asis) { |
371 | 26x |
rv <- knitr::asis_output(rv) |
372 |
} |
|
373 | 78x |
rv |
374 |
} |
|
375 | ||
376 |
#' @keywords internal |
|
377 |
h_knit_print_render_ref_dose.GeneralModel <- function(x, ..., use_values = TRUE, fmt = "%5.2f", units = NA) { |
|
378 |
# Validate |
|
379 | 90x |
assert_character(units, len = 1) |
380 |
# Initialise |
|
381 | 90x |
units <- h_prepare_units(units) |
382 |
# Execute |
|
383 | 90x |
ref_dose <- ifelse( |
384 | 90x |
use_values, |
385 | 90x |
paste0( |
386 | 90x |
" The reference dose will be ", |
387 | 90x |
stringr::str_trim(sprintf(fmt, x@ref_dose)), |
388 | 90x |
units, |
389 | 90x |
".\n\n" |
390 |
), |
|
391 |
"" |
|
392 |
) |
|
393 | 90x |
ref_dose |
394 |
} |
|
395 | ||
396 |
# LogisticKadane ---- |
|
397 | ||
398 |
#' @keywords internal |
|
399 |
h_knit_print_render_ref_dose.LogisticKadane <- function(x, ...) { |
|
400 |
# The LogisticKadane class has no reference dose slot |
|
401 |
"" |
|
402 |
} |
|
403 | ||
404 |
#' @description `r lifecycle::badge("experimental")` |
|
405 |
#' @rdname knit_print |
|
406 |
#' @export |
|
407 |
#' @method knit_print LogisticKadane |
|
408 |
knit_print.LogisticKadane <- function( |
|
409 |
x, |
|
410 |
..., |
|
411 |
asis = TRUE, |
|
412 |
use_values = TRUE, |
|
413 |
fmt = "%5.2f", |
|
414 |
units = NA, |
|
415 |
tox_label = "toxicity") { |
|
416 |
# Validate |
|
417 | 6x |
assert_flag(asis) |
418 | 4x |
assert_flag(use_values) |
419 | 4x |
assert_format(fmt) |
420 |
# Initialise |
|
421 | 4x |
tox_label <- h_prepare_labels(tox_label) |
422 | 4x |
units <- h_prepare_units(units) |
423 |
# Execute |
|
424 | 4x |
rv <- paste0( |
425 | 4x |
"A logistic model using the parameterisation of Kadane (1980) will ", |
426 | 4x |
"describe the relationship between dose and ", |
427 | 4x |
tox_label[1], |
428 | 4x |
".\n\n ", |
429 | 4x |
ifelse( |
430 | 4x |
use_values, |
431 | 4x |
paste0( |
432 | 4x |
"Let the minimum (x~min~) and maximum (x~max~) doses be ", |
433 | 4x |
paste0(stringr::str_trim(sprintf(fmt, x@xmin)), units), |
434 | 4x |
" and ", |
435 | 4x |
paste0(stringr::str_trim(sprintf(fmt, x@xmax)), units), |
436 | 4x |
".\n\n" |
437 |
), |
|
438 | 4x |
"Let x~min~ and x~max~ denote, respectively, the minimum and maximum doses.\n\n " |
439 |
), |
|
440 | 4x |
"Further, let θ denote the target toxicity rate and ρ~0~ = p(DLT | D = x~min~).\n\n", |
441 | 4x |
"Let γ be the dose with target toxicity rate θ, so that p(DLT | D = γ) = θ", |
442 | 4x |
ifelse( |
443 | 4x |
use_values, |
444 | 4x |
paste0(" = ", x@theta, ".\n\n"), |
445 | 4x |
".\n\n" |
446 |
), |
|
447 | 4x |
"Using this parameterisation, standard logistic regression model has slope ", |
448 | 4x |
"$$ \\frac{\\gamma \\text{logit}(\\rho_0) - x_{min} \\text{logit}(\\theta)}{\\gamma - x_{min}} $$", |
449 | 4x |
" and intercept ", |
450 | 4x |
"$$ \\frac{\\text{logit}(\\theta) - logit(\\rho_0)}{\\gamma - x_{min}} $$", |
451 | 4x |
" The priors for Γ and Ρ~0~ are ", |
452 | 4x |
ifelse( |
453 | 4x |
use_values, |
454 | 4x |
paste0("$$ \\Gamma \\sim U(", sprintf(fmt, x@xmin), ", ", sprintf(fmt, x@xmax), ") $$"), |
455 | 4x |
"$$ \\Gamma \\sim U(x_{min}, x_{max}) $$" |
456 |
), |
|
457 | 4x |
" and, independently, ", |
458 | 4x |
ifelse( |
459 | 4x |
use_values, |
460 | 4x |
paste0("$$ \\mathrm{P}_0 \\sim U(0, ", x@theta, ") $$"), |
461 | 4x |
"$$ \\mathrm{P}_0 \\sim U(0, \\theta) $$" |
462 |
), |
|
463 | 4x |
"\n\n Note that x~min~ and x~max~ need not be equal to the smallest and ", |
464 | 4x |
"largest values in the `doseGrid` slot of the corresponding `Data` object.\n\n" |
465 |
) |
|
466 | ||
467 | 4x |
if (asis) { |
468 | 2x |
rv <- knitr::asis_output(rv) |
469 |
} |
|
470 | 4x |
rv |
471 |
} |
|
472 | ||
473 |
# LogisticKadaneBetaGamma ---- |
|
474 | ||
475 |
#' @description `r lifecycle::badge("experimental")` |
|
476 |
#' @rdname knit_print |
|
477 |
#' @export |
|
478 |
#' @method knit_print LogisticKadaneBetaGamma |
|
479 |
knit_print.LogisticKadaneBetaGamma <- function( |
|
480 |
x, |
|
481 |
..., |
|
482 |
asis = TRUE, |
|
483 |
use_values = TRUE, |
|
484 |
fmt = "%5.2f", |
|
485 |
tox_label = "toxicity", |
|
486 |
units = NA) { |
|
487 |
# Validate |
|
488 | 6x |
assert_flag(asis) |
489 | 4x |
assert_flag(use_values) |
490 | 4x |
assert_format(fmt) |
491 |
# Initialise |
|
492 | 4x |
units <- h_prepare_units(units) |
493 | 4x |
tox_label <- h_prepare_labels(tox_label) |
494 |
# Execute |
|
495 | 4x |
rv <- paste0( |
496 | 4x |
"A logistic model using the parameterisation of Kadane (1980) will ", |
497 | 4x |
"describe the relationship between dose and ", |
498 | 4x |
tox_label[1], |
499 | 4x |
", using a Beta ", |
500 | 4x |
"distribution as the prior for ρ~0~ and a Gamma distribution as the prior ", |
501 | 4x |
"for γ.\n\n ", |
502 | 4x |
ifelse( |
503 | 4x |
use_values, |
504 | 4x |
paste0( |
505 | 4x |
"Let the minimum (x~min~) and maximum (x~max~) doses be ", |
506 | 4x |
paste0(stringr::str_trim(sprintf(fmt, x@xmin)), units), |
507 | 4x |
" and ", |
508 | 4x |
paste0(stringr::str_trim(sprintf(fmt, x@xmax)), units), |
509 | 4x |
".\n\n" |
510 |
), |
|
511 | 4x |
"Let x~min~ and x~max~ denote, respectively, the minimum and maximum doses.\n\n " |
512 |
), |
|
513 | 4x |
"Further, let θ denote the target toxicity rate and ρ~0~ = p(DLT | D = x~min~).\n\n", |
514 | 4x |
"Let γ be the dose with target toxicity rate θ, so that p(DLT | D = γ) = θ", |
515 | 4x |
ifelse( |
516 | 4x |
use_values, |
517 | 4x |
paste0(" = ", x@theta, ".\n\n"), |
518 | 4x |
".\n\n" |
519 |
), |
|
520 | 4x |
"Using this parameterisation, standard logistic regression model has slope ", |
521 | 4x |
"$$ \\frac{\\gamma \\text{logit}(\\rho_0) - x_{min} \\text{logit}(\\theta)}{\\gamma - x_{min}} $$", |
522 | 4x |
" and intercept ", |
523 | 4x |
"$$ \\frac{\\text{logit}(\\theta) - logit(\\rho_0)}{\\gamma - x_{min}} $$", |
524 | 4x |
" The priors for Γ and Ρ~0~ are ", |
525 | 4x |
ifelse( |
526 | 4x |
use_values, |
527 | 4x |
paste0("$$ \\Gamma \\sim U(", sprintf(fmt, x@shape), ", ", sprintf(fmt, x@rate), ") $$"), |
528 | 4x |
"$$ \\Gamma \\sim Gamma( \\text{shape}, \\text{rate}) $$" |
529 |
), |
|
530 | 4x |
" and, independently, ", |
531 | 4x |
ifelse( |
532 | 4x |
use_values, |
533 | 4x |
paste0("$$ \\mathrm{P}_0 \\sim Beta(", x@alpha, ", ", x@beta, ") $$"), |
534 | 4x |
"$$ \\mathrm{P}_0 \\sim Beta(\\alpha, \\beta) $$" |
535 |
), |
|
536 | 4x |
"\n\n Note that x~min~ and x~max~ need not be equal to the smallest and ", |
537 | 4x |
"largest values in the `doseGrid` slot of the corresponding `Data` object.\n\n" |
538 |
) |
|
539 | ||
540 | 4x |
if (asis) { |
541 | 2x |
rv <- knitr::asis_output(rv) |
542 |
} |
|
543 | 4x |
rv |
544 |
} |
|
545 | ||
546 |
# LogisticLogNormal ---- |
|
547 | ||
548 |
#' @description `r lifecycle::badge("experimental")` |
|
549 |
#' @noRd |
|
550 |
h_knit_print_render_model.LogisticLogNormal <- function(x, tox_label = "toxicity", ...) { |
|
551 | 24x |
tox_label <- h_prepare_labels(tox_label) |
552 | 24x |
z <- "e^{\\alpha + \\beta \\cdot log(d/d_{ref})}" |
553 | 24x |
paste0( |
554 | 24x |
"A logistic log normal model will describe the relationship between dose and ", |
555 | 24x |
tox_label[1], |
556 |
": ", |
|
557 | 24x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{", z, "}{1 + ", z, "} $$\\n ", |
558 | 24x |
"where d~ref~ denotes a reference dose.\n\n" |
559 |
) |
|
560 |
} |
|
561 | ||
562 |
#' @description `r lifecycle::badge("experimental")` |
|
563 |
#' @rdname knit_print |
|
564 |
#' @export |
|
565 |
#' @method knit_print LogisticLogNormal |
|
566 |
knit_print.LogisticLogNormal <- function( |
|
567 |
x, |
|
568 |
..., |
|
569 |
use_values = TRUE, |
|
570 |
fmt = "%5.2f", |
|
571 |
params = c( |
|
572 |
"\\alpha" = "alpha", |
|
573 |
"log(\\beta)" = "beta" |
|
574 |
), |
|
575 |
preamble = "The prior for θ is given by\\n", |
|
576 |
asis = TRUE) { |
|
577 | 30x |
assert_flag(asis) |
578 |
# Can't use NextMethod() on a S4 class |
|
579 | 24x |
knit_print.GeneralModel( |
580 | 24x |
x, |
581 |
..., |
|
582 | 24x |
use_values = use_values, |
583 | 24x |
fmt = fmt, |
584 | 24x |
params = params, |
585 | 24x |
preamble = preamble, |
586 | 24x |
asis = asis |
587 |
) |
|
588 |
} |
|
589 | ||
590 |
# LogisticLogNormalMixture ---- |
|
591 | ||
592 |
#' @description `r lifecycle::badge("experimental")` |
|
593 |
#' @noRd |
|
594 |
h_knit_print_render_model.LogisticLogNormalMixture <- function( |
|
595 |
x, |
|
596 |
use_values = TRUE, |
|
597 |
tox_label = "toxicity", |
|
598 |
...) { |
|
599 | 4x |
tox_label <- h_prepare_labels(tox_label) |
600 | 4x |
z1 <- "e^{\\alpha_1 + \\beta_1 \\cdot log(d/d^*)}" |
601 | 4x |
z2 <- "e^{\\alpha_2 + \\beta_2 \\cdot log(d/d^*)}" |
602 | 4x |
pi_text <- ifelse( |
603 | 4x |
use_values, |
604 | 4x |
x@share_weight, |
605 | 4x |
"\\pi" |
606 |
) |
|
607 | 4x |
paste0( |
608 | 4x |
"A mixture of two logistic log normal models will describe the relationship between dose and ", |
609 | 4x |
tox_label[1], |
610 |
": ", |
|
611 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = ", |
612 | 4x |
pi_text, |
613 | 4x |
" \\times \\frac{", z1, "}{1 + ", z1, "} + (1 - ", |
614 | 4x |
pi_text, |
615 | 4x |
") \\times \\frac{", z2, "}{1 + ", z2, "} $$", |
616 | 4x |
ifelse( |
617 | 4x |
use_values, |
618 | 4x |
"where d* denotes a reference dose.\n\n", |
619 | 4x |
"where d* denotes a reference dose and π is a fixed value between 0 and 1.\n\n" |
620 |
) |
|
621 |
) |
|
622 |
} |
|
623 | ||
624 |
#' @export |
|
625 |
#' @rdname knit_print |
|
626 |
#' @method knit_print LogisticLogNormalMixture |
|
627 |
knit_print.LogisticLogNormalMixture <- function(x, ..., asis = TRUE, use_values = TRUE, fmt = "%5.2f", units = NA) { |
|
628 |
# Validate |
|
629 | 6x |
assert_flag(asis) |
630 | 4x |
assert_flag(use_values) |
631 | 4x |
assert_format(fmt) |
632 |
# Execute |
|
633 | 4x |
rv <- paste0( |
634 | 4x |
h_knit_print_render_model(x, use_values = use_values, fmt = fmt, ...), |
635 | 4x |
knit_print( |
636 | 4x |
x@params, |
637 |
..., |
|
638 | 4x |
asis = asis, |
639 | 4x |
use_values = use_values, |
640 | 4x |
fmt = fmt, |
641 | 4x |
preamble = "The priors for both θ~1~ and θ~2~ are given by\\n" |
642 |
), |
|
643 | 4x |
"\\n\\n", |
644 | 4x |
h_knit_print_render_ref_dose(x, use_values = use_values, fmt = fmt, unit = unit) |
645 |
) |
|
646 | 4x |
if (asis) { |
647 | 2x |
rv <- knitr::asis_output(rv) |
648 |
} |
|
649 | 4x |
rv |
650 |
} |
|
651 | ||
652 |
# LogisticLogNormalSub ---- |
|
653 | ||
654 |
#' @description `r lifecycle::badge("experimental")` |
|
655 |
#' @noRd |
|
656 |
h_knit_print_render_model.LogisticLogNormalSub <- function(x, ..., tox_label = "toxicity") { |
|
657 | 4x |
tox_label <- h_prepare_labels(tox_label) |
658 | 4x |
z <- "e^{\\alpha + \\beta \\cdot (d \\, - \\, d^*)}" |
659 | 4x |
paste0( |
660 | 4x |
"A logistic log normal model with subtractive dose normalisation will ", |
661 | 4x |
"describe the relationship between dose and ", |
662 | 4x |
tox_label[1], |
663 | 4x |
": \n\n", |
664 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{", z, "}{1 + ", z, "} $$\\n ", |
665 | 4x |
"where d* denotes a reference dose.\n\n" |
666 |
) |
|
667 |
} |
|
668 | ||
669 |
#' @description `r lifecycle::badge("experimental")` |
|
670 |
#' @rdname knit_print |
|
671 |
#' @export |
|
672 |
#' @method knit_print LogisticLogNormalSub |
|
673 |
knit_print.LogisticLogNormalSub <- function( |
|
674 |
x, |
|
675 |
..., |
|
676 |
use_values = TRUE, |
|
677 |
fmt = "%5.2f", |
|
678 |
params = c( |
|
679 |
"\\alpha" = "alpha", |
|
680 |
"log(\\beta)" = "beta" |
|
681 |
), |
|
682 |
preamble = "The prior for θ is given by\\n", |
|
683 |
asis = TRUE) { |
|
684 | 6x |
NextMethod(params = params) |
685 |
} |
|
686 | ||
687 |
# LogisticNormal ---- |
|
688 | ||
689 |
#' @description `r lifecycle::badge("experimental")` |
|
690 |
#' @noRd |
|
691 |
h_knit_print_render_model.LogisticNormal <- function(x, ..., tox_label = "toxicity") { |
|
692 | 4x |
tox_label <- h_prepare_labels(tox_label) |
693 | 4x |
z <- "e^{\\alpha + \\beta \\cdot d/d^*}" |
694 | 4x |
paste0( |
695 | 4x |
"A logistic log normal model will describe the relationship between dose and ", |
696 | 4x |
tox_label[1], |
697 |
": ", |
|
698 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{", z, "}{1 + ", z, "} $$\\n ", |
699 | 4x |
"where d* denotes a reference dose.\n\n" |
700 |
) |
|
701 |
} |
|
702 | ||
703 |
# ProbitLogNormal ---- |
|
704 | ||
705 |
#' @description `r lifecycle::badge("experimental")` |
|
706 |
#' @noRd |
|
707 |
h_knit_print_render_model.ProbitLogNormal <- function(x, tox_label = "toxicity", ...) { |
|
708 | 22x |
tox_label <- h_prepare_labels(tox_label) |
709 | 22x |
paste0( |
710 | 22x |
"A probit log normal model will describe the relationship between dose and ", |
711 | 22x |
tox_label[1], |
712 |
": ", |
|
713 | 22x |
"$$ \\Phi^{-1}(Tox | d) = f(X = 1 | \\theta, d) = \\alpha + \\beta \\cdot log(d/d^*) $$\\n ", |
714 | 22x |
"where d* denotes a reference dose.\n\n" |
715 |
) |
|
716 |
} |
|
717 | ||
718 |
# ProbitLogNormalRel ---- |
|
719 | ||
720 |
#' @description `r lifecycle::badge("experimental")` |
|
721 |
#' @noRd |
|
722 |
h_knit_print_render_model.ProbitLogNormalRel <- function( |
|
723 |
x, |
|
724 |
..., |
|
725 |
tox_label = "toxicity", |
|
726 |
asis = TRUE) { |
|
727 | 4x |
assert_flag(asis) |
728 | 4x |
tox_label <- h_prepare_labels(tox_label) |
729 | 4x |
paste0( |
730 | 4x |
"A probit log normal model will describe the relationship between dose and ", |
731 | 4x |
tox_label[1], |
732 |
": ", |
|
733 | 4x |
"$$ \\Phi^{-1}(Tox | d) = f(X = 1 | \\theta, d) = \\alpha + \\beta \\cdot d/d^* $$\\n ", |
734 | 4x |
"where d* denotes a reference dose.\n\n" |
735 |
) |
|
736 |
} |
|
737 | ||
738 |
# LogisticNormalMixture ---- |
|
739 | ||
740 |
#' @description `r lifecycle::badge("experimental")` |
|
741 |
#' @noRd |
|
742 |
h_knit_print_render_model.LogisticNormalMixture <- function(x, ..., tox_label = "toxicity") { |
|
743 | 4x |
tox_label <- h_prepare_labels(tox_label) |
744 | 4x |
z <- "e^{\\alpha + \\beta \\cdot log(d/d^*)}" |
745 | 4x |
paste0( |
746 | 4x |
"A mixture of two logistic log normal models will describe the relationship between dose and ", |
747 | 4x |
tox_label[1], |
748 |
": ", |
|
749 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{", z, "}{1 + ", z, "} $$\\n ", |
750 | 4x |
"where d* denotes a reference dose.\n\n" |
751 |
) |
|
752 |
} |
|
753 | ||
754 |
#' @export |
|
755 |
#' @rdname knit_print |
|
756 |
#' @method knit_print LogisticNormalMixture |
|
757 |
knit_print.LogisticNormalMixture <- function(x, ..., asis = TRUE, use_values = TRUE, fmt = "%5.2f", units = NA) { |
|
758 |
# Validate |
|
759 | 6x |
assert_flag(asis) |
760 | 4x |
assert_flag(use_values) |
761 | 4x |
assert_format(fmt) |
762 |
# Execute |
|
763 | 4x |
rv <- paste0( |
764 | 4x |
h_knit_print_render_model(x, use_values = use_values, fmt = fmt, ...), |
765 | 4x |
"The prior for θ is given by\\n", |
766 | 4x |
"$$ \\theta = \\begin{bmatrix} \\alpha \\\\ log(\\beta) \\end{bmatrix}", |
767 | 4x |
" \\sim ", |
768 | 4x |
"w \\cdot ", |
769 | 4x |
knit_print( |
770 | 4x |
x@comp1, |
771 | 4x |
params = c("\\alpha" = "alpha", "\\beta" = "beta") |
772 |
), |
|
773 | 4x |
" + (1 - w) \\cdot ", |
774 | 4x |
knit_print( |
775 | 4x |
x@comp2, |
776 | 4x |
params = c("\\alpha" = "alpha", "\\beta" = "beta") |
777 |
), |
|
778 | 4x |
" $$\\n\\n", |
779 | 4x |
" and the prior for w is given by \n\n", |
780 | 4x |
" $$ w \\sim Beta(", x@weightpar[1], ", ", x@weightpar[2], ") $$\\n\\n", |
781 | 4x |
h_knit_print_render_ref_dose(x, units = units, fmt = fmt, use_values = use_values, ...) |
782 |
) |
|
783 | 4x |
if (asis) { |
784 | 2x |
rv <- knitr::asis_output(rv) |
785 |
} |
|
786 | 4x |
rv |
787 |
} |
|
788 | ||
789 |
# LogisticNormalFixedMixture ---- |
|
790 | ||
791 |
#' @export |
|
792 |
#' @rdname knit_print |
|
793 |
#' @method knit_print LogisticNormalFixedMixture |
|
794 |
knit_print.LogisticNormalFixedMixture <- function(x, ..., asis = TRUE, use_values = TRUE, fmt = "%5.2f", units = NA) { |
|
795 |
# Validate |
|
796 | 6x |
assert_flag(asis) |
797 | 4x |
assert_flag(use_values) |
798 | 4x |
assert_format(fmt) |
799 |
# Execute |
|
800 | 4x |
beta <- ifelse(x@log_normal, "log(\\beta)", "\\beta") |
801 | 4x |
rv <- paste0( |
802 | 4x |
h_knit_print_render_model(x, use_values = use_values, fmt = fmt, ...), |
803 | 4x |
" The prior for θ is given by\\n\\n", |
804 | 4x |
"$$ \\theta = \\begin{bmatrix} \\alpha \\\\ ", beta, " \\end{bmatrix}", |
805 | 4x |
" \\sim \\sum_{i=1}^{", length(x@components), "}", |
806 | 4x |
"w_i \\cdot N \\left( \\mathbf{\\mu}_i , \\mathbf{\\Sigma}_i \\right)", |
807 | 4x |
" $$ \\n\\n", |
808 | 4x |
" with \\n\\n", |
809 | 4x |
"$$ \\sum_{i=1}^{", length(x@components), "} w_i = 1 $$ \\n\\n", |
810 | 4x |
" The individual components of the mixture are " |
811 |
) |
|
812 | 4x |
if (x@log_normal) { |
813 | ! |
params <- c("\\alpha" = "alpha", "log(\\beta)" = "beta") |
814 |
} else { |
|
815 | 4x |
params <- c("\\alpha" = "alpha", "\\beta" = "beta") |
816 |
} |
|
817 | 4x |
for (i in seq_along(x@components)) { |
818 | 8x |
comp <- x@components[[i]] |
819 | 8x |
rv <- paste0( |
820 | 8x |
rv, |
821 | 8x |
knit_print( |
822 | 8x |
comp, |
823 | 8x |
params = params, |
824 | 8x |
preamble = " ", |
825 | 8x |
use_values = use_values, |
826 | 8x |
fmt = fmt, |
827 | 8x |
theta = paste0("\\theta_", i) |
828 |
), |
|
829 | 8x |
" with weight ", x@weights[i], |
830 | 8x |
ifelse( |
831 | 8x |
i < length(x@components), |
832 | 8x |
" and", |
833 |
" " |
|
834 |
) |
|
835 |
) |
|
836 |
} |
|
837 | 4x |
rv <- paste0( |
838 | 4x |
rv, |
839 | 4x |
" \\n\\n ", |
840 | 4x |
h_knit_print_render_ref_dose(x, units = units, fmt = fmt, use_values = use_values, ...) |
841 |
) |
|
842 | 4x |
if (asis) { |
843 | 2x |
rv <- knitr::asis_output(rv) |
844 |
} |
|
845 | 4x |
rv |
846 |
} |
|
847 | ||
848 |
#' @description `r lifecycle::badge("experimental")` |
|
849 |
#' @noRd |
|
850 |
h_knit_print_render_model.LogisticNormalFixedMixture <- function(x, ..., tox_label = "toxicity") { |
|
851 | 4x |
tox_label <- h_prepare_labels(tox_label) |
852 | 4x |
z <- "e^{\\alpha + \\beta \\cdot log(d/d^*)}" |
853 | 4x |
paste0( |
854 | 4x |
"A mixture of ", |
855 | 4x |
length(x@components), |
856 | 4x |
" logistic log normal models with fixed weights will describe the relationship ", |
857 | 4x |
"between dose and ", |
858 | 4x |
tox_label[1], |
859 |
": ", |
|
860 | 4x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{", z, "}{1 + ", z, "} $$\\n ", |
861 | 4x |
"where d* denotes a reference dose.\n\n" |
862 |
) |
|
863 |
} |
|
864 | ||
865 |
# ModelLogNormal ---- |
|
866 | ||
867 |
#' @description `r lifecycle::badge("experimental")` |
|
868 |
#' @noRd |
|
869 |
h_knit_print_render_model.ModelLogNormal <- function(x, ...) { |
|
870 | 4x |
"The model used to characterise the dose toxicity relationship is defined in subclasses.\n\n" |
871 |
} |
|
872 | ||
873 |
# OneParLogNormalPrior ---- |
|
874 | ||
875 |
#' @description `r lifecycle::badge("experimental")` |
|
876 |
#' @rdname knit_print |
|
877 |
#' @export |
|
878 |
#' @method knit_print OneParLogNormalPrior |
|
879 |
knit_print.OneParLogNormalPrior <- function( |
|
880 |
x, |
|
881 |
..., |
|
882 |
tox_label = "toxicity", |
|
883 |
asis = TRUE, |
|
884 |
use_values = TRUE, |
|
885 |
fmt = "%5.2f") { |
|
886 | 12x |
assert_flag(asis) |
887 | ||
888 | 8x |
tox_label <- h_prepare_labels(tox_label) |
889 | 8x |
s2text <- ifelse( |
890 | 8x |
use_values, |
891 | 8x |
stringr::str_trim(sprintf(fmt, x@sigma2)), |
892 | 8x |
"\\sigma^2" |
893 |
) |
|
894 | 8x |
rv <- paste0( |
895 | 8x |
"The relationship between dose and ", |
896 | 8x |
tox_label[1], |
897 | 8x |
" will be modelled using a version ", |
898 | 8x |
"of the one parameter CRM of O'Quigley et al (1990) with an exponential prior on the ", |
899 | 8x |
"power parameter for the skeleton prior probabilities, with", |
900 | 8x |
ifelse( |
901 | 8x |
use_values, |
902 | 8x |
paste0("$$ \\Theta \\sim Exp(", s2text, ") $$"), |
903 | 8x |
"$$ \\Theta \\sim Exp(\\lambda) $$" |
904 |
), |
|
905 | 8x |
"and skeleton probabilities as in the table below.\n\n" |
906 |
) |
|
907 | 8x |
if (asis) { |
908 | 4x |
rv <- knitr::asis_output(rv) |
909 |
} |
|
910 | 8x |
rv |
911 |
} |
|
912 | ||
913 |
# OneParExpPrior ---- |
|
914 | ||
915 |
#' @description `r lifecycle::badge("experimental")` |
|
916 |
#' @rdname knit_print |
|
917 |
#' @export |
|
918 |
#' @method knit_print OneParExpPrior |
|
919 |
knit_print.OneParExpPrior <- function(x, ..., asis = TRUE) { |
|
920 | 6x |
assert_flag(asis) |
921 | 4x |
rv <- "TODO\n\n" |
922 | 4x |
if (asis) { |
923 | 2x |
rv <- knitr::asis_output(rv) |
924 |
} |
|
925 | 4x |
rv |
926 |
} |
|
927 | ||
928 |
# LogisticLogNormalGrouped ---- |
|
929 | ||
930 |
#' @description `r lifecycle::badge("experimental")` |
|
931 |
#' @rdname knit_print |
|
932 |
#' @export |
|
933 |
#' @method knit_print LogisticLogNormalGrouped |
|
934 |
knit_print.LogisticLogNormalGrouped <- function( |
|
935 |
x, |
|
936 |
..., |
|
937 |
use_values = TRUE, |
|
938 |
fmt = "%5.2f", |
|
939 |
params = c( |
|
940 |
"\\alpha" = "alpha", |
|
941 |
"\\beta" = "beta", |
|
942 |
"log(\\delta_0)" = "delta_0", |
|
943 |
"log(\\delta_1)" = "delta_1" |
|
944 |
), |
|
945 |
preamble = "The prior for θ is given by\\n", |
|
946 |
asis = TRUE) { |
|
947 | 10x |
NextMethod(params = params) |
948 |
} |
|
949 | ||
950 |
#' @description `r lifecycle::badge("experimental")` |
|
951 |
#' @noRd |
|
952 |
h_knit_print_render_model.LogisticLogNormalGrouped <- function(x, tox_label = "toxicity", ...) { |
|
953 | 8x |
tox_label <- h_prepare_labels(tox_label) |
954 | 8x |
z <- "e^{(\\alpha + I_c \\times \\delta_0) + (\\beta + I_c \\times \\delta_1) \\cdot log(d/d^*)}" |
955 | 8x |
paste0( |
956 | 8x |
"A logistic log normal model will describe the relationship between dose and ", |
957 | 8x |
tox_label[1], |
958 |
": ", |
|
959 | 8x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{", z, "}{1 + ", z, "} $$\\n ", |
960 | 8x |
"where d* denotes a reference dose and I~c~ is a binary indicator which ", |
961 | 8x |
"is 1 for the combo arm and 0 for the mono arm.\n\n" |
962 |
) |
|
963 |
} |
|
964 | ||
965 |
# LogisticLogNormalOrdinal ---- |
|
966 | ||
967 |
#' @description `r lifecycle::badge("experimental")` |
|
968 |
#' @noRd |
|
969 |
h_knit_print_render_model.LogisticLogNormalOrdinal <- function(x, ...) { |
|
970 | 8x |
z <- "e^{\\alpha_k + \\beta \\cdot log(d/d^*)}" |
971 | 8x |
paste0( |
972 | 8x |
"Let p~k~(d) be the probability that the response of a patient treated at ", |
973 | 8x |
"dose d is in category k *_or higher_*, k=0, ..., K; d=1, ..., D.\n\nThen ", |
974 | 8x |
"$$ p_k(d) = f(X \\ge k \\; | \\; \\theta, d) = \\begin{cases} 1 & k = 0 \\\\ ", |
975 | 8x |
"\\frac{", z, "}{1 + ", z, "} & k=1, ..., K", |
976 | 8x |
"\\end{cases} $$\n\n", |
977 | 8x |
"where d* denotes a reference dose.\n\nThe αs are constrained ", |
978 | 8x |
"such that α~1~ > α~2~ > ... > α~K~.\n\n" |
979 |
) |
|
980 |
} |
|
981 | ||
982 |
# LogisticLogNormalOrdinal ---- |
|
983 | ||
984 |
#' @description `r lifecycle::badge("experimental")` |
|
985 |
#' @rdname knit_print |
|
986 |
#' @export |
|
987 |
#' @method knit_print LogisticLogNormalOrdinal |
|
988 |
knit_print.LogisticLogNormalOrdinal <- function( |
|
989 |
x, |
|
990 |
..., |
|
991 |
use_values = TRUE, |
|
992 |
fmt = "%5.2f", |
|
993 |
params = NA, |
|
994 |
preamble = "The prior for θ is given by\\n", |
|
995 |
asis = TRUE) { |
|
996 | 10x |
assert_flag(asis) |
997 | 8x |
if (is.na(params)) { |
998 | 8x |
params <- c( |
999 | 8x |
paste0("alpha_", 1:(length(x@params@mean) - 1)), |
1000 | 8x |
"beta" |
1001 |
) |
|
1002 | 8x |
names(params) <- paste0("\\", params) |
1003 |
} |
|
1004 | 8x |
NextMethod(params = params) |
1005 |
} |
|
1006 | ||
1007 |
# LogisticIndepBeta ---- |
|
1008 | ||
1009 |
#' @description `r lifecycle::badge("experimental")` |
|
1010 |
#' @rdname knit_print |
|
1011 |
#' @export |
|
1012 |
#' @method knit_print LogisticIndepBeta |
|
1013 |
knit_print.LogisticIndepBeta <- function( |
|
1014 |
x, |
|
1015 |
..., |
|
1016 |
use_values = TRUE, |
|
1017 |
fmt = "%5.2f", |
|
1018 |
params = NA, |
|
1019 |
tox_label = "DLAE", |
|
1020 |
preamble = "The prior for θ is given by\\n", |
|
1021 |
asis = TRUE) { |
|
1022 | 28x |
assert_flag(asis) |
1023 | ||
1024 | 26x |
tox_label <- h_prepare_labels(tox_label) |
1025 | 26x |
y <- tidy(x) |
1026 | 26x |
z <- "e^{\\phi_1 + \\phi_2 \\cdot log(d)}" |
1027 | 26x |
posterior <- ModelParamsNormal(mean = c(x@phi1, x@phi2), cov = x@Pcov) |
1028 |
# knit_print.ModelParamsNormal expects no row or column names |
|
1029 | 26x |
rownames(posterior@cov) <- NULL |
1030 | 26x |
colnames(posterior@cov) <- NULL |
1031 | ||
1032 | 26x |
rv <- paste0( |
1033 | 26x |
"A logistic log normal model will describe the relationship between dose and ", |
1034 | 26x |
tox_label[1], |
1035 |
": ", |
|
1036 | 26x |
"$$ p(Tox | d) = f(X = 1 | \\theta, d) = \\frac{", z, "}{1 + ", z, "} $$\\n ", |
1037 | 26x |
"The prior is expressed in terms of pseudo data and, consequently, the number ", |
1038 | 26x |
" of cases and of ", |
1039 | 26x |
tox_label[2], |
1040 | 26x |
" need not be whole numbers.\n\nThe pseudo data are ", |
1041 | 26x |
"defined in the following table:\n\n", |
1042 | 26x |
paste0( |
1043 | 26x |
do.call( |
1044 | 26x |
function(x) { |
1045 | 26x |
kableExtra::kable_styling( |
1046 | 26x |
knitr::kable(x, col.names = c("Dose", "N", tox_label[2])), |
1047 | 26x |
bootstrap_options = c("striped", "hover", "condensed") |
1048 |
) |
|
1049 |
}, |
|
1050 | 26x |
list(x = y$pseudoData) |
1051 |
), |
|
1052 | 26x |
collapse = "\n" |
1053 |
), |
|
1054 | 26x |
ifelse( |
1055 | 26x |
nrow(y$data) == 0, |
1056 | 26x |
"\n\nNo observed data has yet been recorded.\n", |
1057 | 26x |
paste( |
1058 | 26x |
"\n\nThe observed data are given in the following table:\n\n", |
1059 | 26x |
paste((do.call(knitr::kable, list(x = y$data))), collapse = "\n") |
1060 |
) |
|
1061 |
), |
|
1062 | 26x |
knit_print( |
1063 | 26x |
posterior, |
1064 | 26x |
preamble = paste0( |
1065 | 26x |
"\n\nTogether, the pseudo and observed data give rise to ", |
1066 | 26x |
"the following posterior for the model parameters:\n\n" |
1067 |
), |
|
1068 | 26x |
params = c("\\phi_1" = "phi1", "\\phi_2" = "phi2"), |
1069 | 26x |
theta = "\\phi", |
1070 | 26x |
asis = FALSE, |
1071 |
... |
|
1072 |
), |
|
1073 | 26x |
"\n\n" |
1074 |
) |
|
1075 | ||
1076 | 26x |
if (asis) { |
1077 | 2x |
rv <- knitr::asis_output(rv) |
1078 |
} |
|
1079 | 26x |
rv |
1080 |
} |
|
1081 | ||
1082 |
# Effloglog ---- |
|
1083 | ||
1084 |
#' @description `r lifecycle::badge("experimental")` |
|
1085 |
#' @param eff_label (`character`)\cr the term used to describe efficacy |
|
1086 |
#' @rdname knit_print |
|
1087 |
#' @export |
|
1088 |
#' @method knit_print Effloglog |
|
1089 |
knit_print.Effloglog <- function( |
|
1090 |
x, |
|
1091 |
..., |
|
1092 |
use_values = TRUE, |
|
1093 |
fmt = "%5.2f", |
|
1094 |
params = NA, |
|
1095 |
tox_label = "DLAE", |
|
1096 |
eff_label = "efficacy", |
|
1097 |
label = "participant", |
|
1098 |
preamble = "The prior for θ is given by\\n", |
|
1099 |
asis = TRUE) { |
|
1100 | 18x |
assert_flag(asis) |
1101 | 16x |
assert_character(eff_label, len = 1, any.missing = FALSE) |
1102 | ||
1103 |
# Prepare |
|
1104 | 16x |
tox_label <- h_prepare_labels(tox_label) |
1105 | 16x |
eff_label <- h_prepare_labels(eff_label) |
1106 | 16x |
label <- h_prepare_labels(label) |
1107 | ||
1108 | 16x |
y <- tidy(x) |
1109 |
# knit_print.ModelParamsNormal expects no row or column names |
|
1110 | 16x |
posterior <- ModelParamsNormal(mean = c(x@theta1, x@theta2), cov = x@Q) |
1111 | 16x |
rownames(posterior@cov) <- NULL |
1112 | 16x |
colnames(posterior@cov) <- NULL |
1113 | ||
1114 | 16x |
rv <- paste0( |
1115 | 16x |
"A linear log-log model with a pseudo data prior will describe the ", |
1116 | 16x |
"relationship between dose and ", |
1117 | 16x |
eff_label[1], |
1118 | 16x |
". The model is given by\n ", |
1119 | 16x |
"$$ y_i = \\theta_1 + \\theta_2 \\cdot \\log(\\log(d_i + k)) + \\epsilon_i $$\\n ", |
1120 | 16x |
"where k is a constant (equal to ", |
1121 | 16x |
x@const, |
1122 | 16x |
"), y~i~ is the ", |
1123 | 16x |
eff_label[1], |
1124 | 16x |
" response for ", |
1125 | 16x |
label[1], |
1126 | 16x |
" i, treated at dose d~i~ and ε~i~ is an error term. ", |
1127 | 16x |
"The εs are iid N(0, ν^-1^).\n\n ", |
1128 | 16x |
"The ", |
1129 | 16x |
ifelse( |
1130 | 16x |
length(x@nu) == 1, |
1131 | 16x |
paste0( |
1132 | 16x |
ifelse(nrow(y$data) == 0, "prior", "posterior"), |
1133 | 16x |
" value of ν is ", |
1134 | 16x |
x@nu, |
1135 |
"." |
|
1136 |
), |
|
1137 | 16x |
paste0( |
1138 | 16x |
ifelse(nrow(y$data) == 0, "prior", "posterior"), |
1139 | 16x |
" distribution of ν is currently Γ(", |
1140 | 16x |
sprintf(fmt, x@nu[1]), |
1141 |
", ", |
|
1142 | 16x |
sprintf(fmt, x@nu[2]), |
1143 |
")." |
|
1144 |
) |
|
1145 |
), |
|
1146 | 16x |
"\n\nThe joint distribution of ", |
1147 | 16x |
"θ~1~ and θ~2~ is given by\n\n", |
1148 | 16x |
"$$ \\boldsymbol\\theta = \\begin{bmatrix}\\theta_1 \\\\ \\theta_2\\end{bmatrix} ", |
1149 | 16x |
"\\sim N\\left(\\mu, \\nu \\boldsymbol{Q}^\\intercal \\right) $$ \nwhere ", |
1150 | 16x |
"$\\boldsymbol{Q} = \\boldsymbol{X_0}^\\intercal\\boldsymbol{X_0} + ", |
1151 | 16x |
"\\boldsymbol{X}^\\intercal\\boldsymbol{X}$ and **X~0~** is a design matrix ", |
1152 | 16x |
"based on the dose levels in the pseudo data and **X** is a design matrix ", |
1153 | 16x |
"based on the dose levels of ", |
1154 | 16x |
label[2], |
1155 | 16x |
"' no-", |
1156 | 16x |
tox_label[1], |
1157 |
" ", |
|
1158 | 16x |
eff_label[1], |
1159 | 16x |
" responses in the observed data, if any.\n\n", |
1160 | 16x |
ifelse( |
1161 | 16x |
nrow(y$data) == 0, |
1162 | 16x |
"\n\nNo observed data has yet been recorded.\n", |
1163 | 16x |
paste( |
1164 | 16x |
"\n\nThe data observed to date are given in the following table:\n\n", |
1165 | 16x |
paste( |
1166 | 16x |
(do.call( |
1167 | 16x |
function(z) { |
1168 | 4x |
z %>% |
1169 | 4x |
dplyr::select(-c(NObs, NGrid, DoseGrid, XLevel)) %>% |
1170 | 4x |
knitr::kable() %>% |
1171 | 4x |
kableExtra::kable_styling( |
1172 | 4x |
bootstrap_options = c("striped", "hover", "condensed") |
1173 |
) |
|
1174 |
}, |
|
1175 | 16x |
list(z = y$data) |
1176 |
)), |
|
1177 | 16x |
collapse = "\n" |
1178 |
) |
|
1179 |
) |
|
1180 |
), |
|
1181 | 16x |
knit_print( |
1182 | 16x |
posterior, |
1183 | 16x |
preamble = paste0( |
1184 | 16x |
"\n\nTogether, the pseudo and observed data give rise to ", |
1185 | 16x |
"the following posterior for the model parameters:\n\n" |
1186 |
), |
|
1187 | 16x |
params = c("\\theta_1" = "theta1", "\\theta_2" = "theta2"), |
1188 | 16x |
asis = FALSE, |
1189 |
... |
|
1190 |
), |
|
1191 | 16x |
"\n\n" |
1192 |
) |
|
1193 | ||
1194 | 16x |
if (asis) { |
1195 | 2x |
rv <- knitr::asis_output(rv) |
1196 |
} |
|
1197 | 16x |
rv |
1198 |
} |
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(dose_target, |
|
57 |
dose_mg, |
|
58 |
prob_target, |
|
59 |
placebo, |
|
60 |
model, |
|
61 |
model_eff) { |
|
62 | 17x |
assert_number(dose_target, na.ok = TRUE) |
63 | 17x |
assert_number(dose_mg, na.ok = TRUE) |
64 | 17x |
assert_probability(prob_target) |
65 | 17x |
assert_flag(placebo) |
66 | 17x |
assert_class(model, "ModelTox") |
67 | 17x |
assert_class(model_eff, "Effloglog") |
68 | ||
69 |
# Find the variance of the log of target dose. |
|
70 | 17x |
mat <- matrix( |
71 | 17x |
c( |
72 | 17x |
-1 / (model@phi2), |
73 | 17x |
-(log(prob_target / (1 - prob_target)) - model@phi1) / (model@phi2)^2 |
74 |
), |
|
75 | 17x |
nrow = 1 |
76 |
) |
|
77 | 17x |
var_dose_target <- as.vector(mat %*% model@Pcov %*% t(mat)) |
78 | ||
79 |
# 95% credibility interval for target dose. |
|
80 | 17x |
ci_dose_target <- exp(log(dose_target) + c(-1, 1) * 1.96 * sqrt(var_dose_target)) |
81 | 17x |
cir_dose_target <- ci_dose_target[2] / ci_dose_target[1] |
82 | ||
83 |
# Find the variance of the log of dose_mg. |
|
84 |
# First, find the covariance matrix of all the parameters, phi1, phi2, theta1 and theta2 |
|
85 |
# given that phi1 and phi2 are independent of theta1 and theta2. |
|
86 | 17x |
log_dose_mg <- log(dose_mg + ifelse(placebo, model_eff@const, 0)) |
87 | ||
88 |
# Find a delta_g matrix for a variance according to Yeung et. al (2015). |
|
89 | 17x |
mean_eff_mg <- model_eff@theta1 + model_eff@theta2 * log(log_dose_mg) |
90 | 17x |
denom <- model@phi2 * mean_eff_mg * (1 + model@phi2 * log_dose_mg) |
91 | 17x |
dgphi1 <- -(mean_eff_mg * log_dose_mg * model@phi2 - model_eff@theta2) / denom |
92 | 17x |
dgphi2 <- -(log_dose_mg * (mean_eff_mg * (1 + log_dose_mg * model@phi2) - model_eff@theta2)) / denom |
93 | 17x |
dgtheta1 <- -(log_dose_mg * model@phi2) / denom |
94 | 17x |
dgtheta2_num <- -(exp(model@phi1 + model@phi2 * log_dose_mg) * (model@phi2 * log_dose_mg * log(log_dose_mg) - 1) - 1) |
95 | 17x |
dgtheta2 <- dgtheta2_num / denom |
96 | 17x |
delta_g <- matrix(c(dgphi1, dgphi2, dgtheta1, dgtheta2), 4, 1) |
97 | ||
98 | 17x |
zero_matrix <- matrix(0, 2, 2) |
99 | 17x |
cov_beta <- cbind(rbind(model@Pcov, zero_matrix), rbind(zero_matrix, model_eff@Pcov)) |
100 | 17x |
var_log_dose_mg <- as.vector(t(delta_g) %*% cov_beta %*% delta_g) |
101 | ||
102 |
# 95% credibility interval for max gain dose. |
|
103 | 17x |
ci_mg <- exp(log_dose_mg + c(-1, 1) * 1.96 * sqrt(var_log_dose_mg)) |
104 | 17x |
ci_ratio_mg <- ci_mg[2] / ci_mg[1] |
105 | ||
106 | 17x |
list( |
107 | 17x |
ci_dose_target = ci_dose_target, |
108 | 17x |
ci_ratio_dose_target = cir_dose_target, |
109 | 17x |
ci_dose_mg = ci_mg, |
110 | 17x |
ci_ratio_dose_mg = ci_ratio_mg |
111 |
) |
|
112 |
} |
|
113 | ||
114 |
## next best at grid ---- |
|
115 | ||
116 |
#' Get Closest Grid Doses for a Given Target Doses for `nextBest-NextBestMaxGain` Method. |
|
117 |
#' |
|
118 |
#' @description `r lifecycle::badge("experimental")` |
|
119 |
#' |
|
120 |
#' Helper function that for a given target doses finds the dose in grid that is |
|
121 |
#' closest and below the target. There are four different targets in the context |
|
122 |
#' of [`nextBest-NextBestMaxGain()`] method: \eqn{min(`dose_mg`, `dose_target_drt`)}, |
|
123 |
#' `dose_mg`, `dose_target_drt` or `dose_target_eot`. |
|
124 |
#' |
|
125 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
126 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
127 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
128 |
#' @param dose_grid (`numeric`)\cr all possible doses. |
|
129 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
130 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level in the `dose_grid` |
|
131 |
#' is considered as placebo. |
|
132 |
#' |
|
133 |
#' @export |
|
134 |
#' |
|
135 |
h_next_best_mg_doses_at_grid <- function(dose_target_drt, |
|
136 |
dose_target_eot, |
|
137 |
dose_mg, |
|
138 |
dose_grid, |
|
139 |
doselimit, |
|
140 |
placebo) { |
|
141 | 26x |
assert_number(dose_target_drt, na.ok = TRUE) |
142 | 26x |
assert_number(dose_target_eot, na.ok = TRUE) |
143 | 26x |
assert_number(dose_mg, na.ok = TRUE) |
144 | ||
145 | 26x |
doses_eligible <- h_next_best_eligible_doses(dose_grid, doselimit, placebo) |
146 | ||
147 |
# h_find_interval assumes that elements in doses_eligible are strictly increasing. |
|
148 | 26x |
next_dose_lev <- h_find_interval(min(dose_mg, dose_target_drt), doses_eligible) |
149 | 26x |
next_dose <- doses_eligible[next_dose_lev] |
150 | ||
151 | 26x |
next_dose_mg_lev <- h_find_interval(dose_mg, doses_eligible) |
152 | 26x |
next_dose_mg <- doses_eligible[next_dose_mg_lev] |
153 | ||
154 | 26x |
next_dose_lev_drt <- h_find_interval(dose_target_drt, doses_eligible) |
155 | 26x |
next_dose_drt <- doses_eligible[next_dose_lev_drt] |
156 | ||
157 | 26x |
next_dose_lev_eot <- h_find_interval(dose_target_eot, doses_eligible) |
158 | 26x |
next_dose_eot <- doses_eligible[next_dose_lev_eot] |
159 | ||
160 | 26x |
next_dose_list <- list( |
161 | 26x |
next_dose = next_dose, |
162 | 26x |
next_dose_drt = next_dose_drt, |
163 | 26x |
next_dose_eot = next_dose_eot, |
164 | 26x |
next_dose_mg = next_dose_mg |
165 |
) |
|
166 |
} |
|
167 | ||
168 |
## eligible doses ---- |
|
169 | ||
170 |
#' Get Eligible Doses from the Dose Grid. |
|
171 |
#' |
|
172 |
#' @description `r lifecycle::badge("experimental")` |
|
173 |
#' |
|
174 |
#' Helper function that gets the eligible doses from the dose grid. |
|
175 |
#' The eligible doses are the doses which do not exceed a given |
|
176 |
#' `doselimit`. For placebo design, if safety allows (i.e. if there is at least |
|
177 |
#' one non-placebo dose which does not exceed the dose limit), the placebo dose |
|
178 |
#' it then excluded from the eligible doses. |
|
179 |
#' |
|
180 |
#' @param dose_grid (`numeric`)\cr all possible doses. |
|
181 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
182 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level in the `dose_grid` |
|
183 |
#' is considered as placebo. |
|
184 |
#' @param levels (`flag`)\cr if `TRUE` the levels of eligible doses are returned, |
|
185 |
#' otherwise, the doses (default). |
|
186 |
#' |
|
187 |
#' @return A numeric vector with eligible doses or eligible dose levels if `levels` |
|
188 |
#' flag is `TRUE`. |
|
189 |
#' |
|
190 |
#' @export |
|
191 |
#' @examples |
|
192 |
#' dose_grid <- c(0.001, seq(25, 200, 25)) |
|
193 |
#' h_next_best_eligible_doses(dose_grid, 79, TRUE) |
|
194 |
#' h_next_best_eligible_doses(dose_grid, 24, TRUE) |
|
195 |
h_next_best_eligible_doses <- function(dose_grid, |
|
196 |
doselimit, |
|
197 |
placebo, |
|
198 |
levels = FALSE) { |
|
199 | 373x |
assert_numeric(dose_grid, finite = TRUE, any.missing = FALSE, min.len = 1L, sorted = TRUE) |
200 | 371x |
assert_number(doselimit) |
201 | 371x |
assert_flag(placebo) |
202 | 371x |
assert_flag(levels) |
203 | ||
204 | 371x |
is_dose_eligible <- dose_grid <= doselimit |
205 | 371x |
if (placebo && sum(is_dose_eligible) > 1L) { |
206 | 40x |
is_dose_eligible[1] <- FALSE |
207 |
} |
|
208 | ||
209 | 371x |
if (levels) { |
210 | 290x |
is_dose_eligible |
211 |
} else { |
|
212 | 81x |
dose_grid[is_dose_eligible] |
213 |
} |
|
214 |
} |
|
215 | ||
216 |
## plot ---- |
|
217 | ||
218 |
#' Building the Plot for `nextBest-NextBestNCRMLoss` Method. |
|
219 |
#' |
|
220 |
#' @description `r lifecycle::badge("experimental")` |
|
221 |
#' |
|
222 |
#' Helper function which creates the plot for [`nextBest-NextBestNCRMLoss()`] |
|
223 |
#' method. |
|
224 |
#' |
|
225 |
#' @param prob_mat (`numeric`)\cr matrix with probabilities of a grid doses |
|
226 |
#' to be in a given interval. If `is_unacceptable_specified` is `TRUE`, there |
|
227 |
#' must be 4 intervals (columns) in `prob_mat`: `underdosing`, `target`, |
|
228 |
#' `excessive`, `unacceptable`. Otherwise, there must be 3 intervals (columns): |
|
229 |
#' `underdosing`, `target`, `overdose`. Number of rows must be equal to number |
|
230 |
#' of doses in a grid. |
|
231 |
#' @param posterior_loss (`numeric`)\cr posterior losses. |
|
232 |
#' @param max_overdose_prob (`number`)\cr maximum overdose posterior |
|
233 |
#' probability that is allowed. |
|
234 |
#' @param dose_grid (`numeric`)\cr dose grid. |
|
235 |
#' @param max_eligible_dose_level (`number`)\cr maximum eligible dose level in |
|
236 |
#' the `dose_grid`. |
|
237 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
238 |
#' @param next_dose (`number`)\cr next best dose. |
|
239 |
#' @param is_unacceptable_specified (`flag`)\cr is unacceptable interval specified? |
|
240 |
#' |
|
241 |
#' @export |
|
242 |
h_next_best_ncrm_loss_plot <- function(prob_mat, |
|
243 |
posterior_loss, |
|
244 |
max_overdose_prob, |
|
245 |
dose_grid, |
|
246 |
max_eligible_dose_level, |
|
247 |
doselimit, |
|
248 |
next_dose, |
|
249 |
is_unacceptable_specified) { |
|
250 | 8x |
assert_numeric(dose_grid, finite = TRUE, any.missing = FALSE, sorted = TRUE) |
251 | 8x |
n_grid <- length(dose_grid) |
252 | 8x |
assert_flag(is_unacceptable_specified) |
253 | 8x |
assert_probabilities(prob_mat) |
254 | 8x |
assert_matrix(prob_mat, min.cols = 3, max.cols = 4, nrows = n_grid, col.names = "named") |
255 | 8x |
if (!is_unacceptable_specified) { |
256 | 4x |
assert_names(colnames(prob_mat), permutation.of = c("underdosing", "target", "overdose")) |
257 |
} else { |
|
258 | 4x |
assert_names(colnames(prob_mat), permutation.of = c("underdosing", "target", "excessive", "unacceptable")) |
259 |
} |
|
260 | 8x |
assert_numeric(posterior_loss, finite = TRUE, any.missing = FALSE, len = n_grid) |
261 | 8x |
assert_probability(max_overdose_prob) |
262 | 8x |
assert_number(max_eligible_dose_level, lower = 0, upper = n_grid) |
263 | 8x |
assert_number(doselimit) |
264 | 8x |
assert_number(next_dose, na.ok = TRUE) |
265 | ||
266 |
# Build plots, first for the target probability. |
|
267 | 8x |
p1 <- ggplot() + |
268 | 8x |
geom_bar( |
269 | 8x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "target"] * 100), |
270 | 8x |
aes(x = .data$Dose, y = .data$y), |
271 | 8x |
stat = "identity", |
272 | 8x |
position = "identity", |
273 | 8x |
width = min(diff(dose_grid)) / 2, |
274 | 8x |
colour = "darkgreen", |
275 | 8x |
fill = "darkgreen" |
276 |
) + |
|
277 | 8x |
ylim(c(0, 100)) + |
278 | 8x |
ylab(paste("Target probability [%]")) |
279 | ||
280 | 8x |
if (is.finite(doselimit)) { |
281 | 5x |
p1 <- p1 + geom_vline(xintercept = doselimit, lwd = 1.1, lty = 2, colour = "black") |
282 |
} |
|
283 | ||
284 | 8x |
if (max_eligible_dose_level > 0) { |
285 | 8x |
p1 <- p1 + |
286 | 8x |
geom_vline(xintercept = dose_grid[max_eligible_dose_level], lwd = 1.1, lty = 2, colour = "red") |
287 |
} |
|
288 | ||
289 | 8x |
p_loss <- ggplot() + |
290 |
# For the loss function. |
|
291 | 8x |
geom_bar( |
292 | 8x |
data = data.frame(Dose = dose_grid, y = posterior_loss), |
293 | 8x |
aes(x = .data$Dose, y = .data$y), |
294 | 8x |
stat = "identity", |
295 | 8x |
position = "identity", |
296 | 8x |
width = min(diff(dose_grid)) / 2, |
297 | 8x |
colour = "darkgreen", |
298 | 8x |
fill = "darkgreen" |
299 |
) + |
|
300 | 8x |
geom_point( |
301 | 8x |
aes(x = next_dose, y = max(posterior_loss) + 0.2), |
302 | 8x |
size = 3, |
303 | 8x |
pch = 25, |
304 | 8x |
col = "red", |
305 | 8x |
bg = "red" |
306 |
) + |
|
307 | 8x |
ylab(paste("Loss function")) |
308 | ||
309 | 8x |
if (!is_unacceptable_specified) { |
310 |
# Second, for the overdosing probability. |
|
311 | 4x |
p2 <- ggplot() + |
312 | 4x |
geom_bar( |
313 | 4x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "overdose"] * 100), |
314 | 4x |
aes(x = .data$Dose, y = .data$y), |
315 | 4x |
stat = "identity", |
316 | 4x |
position = "identity", |
317 | 4x |
width = min(diff(dose_grid)) / 2, |
318 | 4x |
colour = "red", |
319 | 4x |
fill = "red" |
320 |
) + |
|
321 | 4x |
geom_hline( |
322 | 4x |
yintercept = max_overdose_prob * 100, lwd = 1.1, lty = 2, |
323 | 4x |
colour = "black" |
324 |
) + |
|
325 | 4x |
ylim(c(0, 100)) + |
326 | 4x |
ylab("Overdose probability [%]") |
327 | ||
328 |
# Combine it all together. |
|
329 | 4x |
plots_single <- list(plot1 = p1, plot2 = p2, plot_loss = p_loss) |
330 | 4x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, p_loss, nrow = 3) |
331 |
} else { |
|
332 |
# Plot in case of 4 toxicity intervals. Second, for the overdosing probability. |
|
333 | 4x |
p2 <- ggplot() + |
334 | 4x |
geom_bar( |
335 | 4x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "excessive"] * 100), |
336 | 4x |
aes(x = .data$Dose, y = .data$y), |
337 | 4x |
stat = "identity", |
338 | 4x |
position = "identity", |
339 | 4x |
width = min(diff(dose_grid)) / 2, |
340 | 4x |
colour = "red", |
341 | 4x |
fill = "red" |
342 |
) + |
|
343 | 4x |
ylim(c(0, 100)) + |
344 | 4x |
ylab("Excessive probability [%]") |
345 | ||
346 | 4x |
p3 <- ggplot() + |
347 | 4x |
geom_bar( |
348 | 4x |
data = data.frame(Dose = dose_grid, y = prob_mat[, "unacceptable"] * 100), |
349 | 4x |
aes(x = .data$Dose, y = .data$y), |
350 | 4x |
stat = "identity", |
351 | 4x |
position = "identity", |
352 | 4x |
width = min(diff(dose_grid)) / 2, |
353 | 4x |
colour = "red", |
354 | 4x |
fill = "red" |
355 |
) + |
|
356 | 4x |
ylim(c(0, 100)) + |
357 | 4x |
ylab("Unacceptable probability [%]") |
358 | ||
359 |
# Combine it all together. |
|
360 | 4x |
plots_single <- list(plot1 = p1, plot2 = p2, plot3 = p3, plot_loss = p_loss) |
361 | 4x |
plot_joint <- gridExtra::arrangeGrob(p1, p2, p3, p_loss, nrow = 4) |
362 |
} |
|
363 | ||
364 | 8x |
list(plots_single = plots_single, plot_joint = plot_joint) |
365 |
} |
|
366 | ||
367 |
#' Building the Plot for `nextBest-NextBestTDsamples` Method. |
|
368 |
#' |
|
369 |
#' @description `r lifecycle::badge("experimental")` |
|
370 |
#' |
|
371 |
#' Helper function which creates the plot for [`nextBest-NextBestTDsamples()`] |
|
372 |
#' method. |
|
373 |
#' |
|
374 |
#' @param dose_target_drt_samples (`numeric`)\cr vector of in-trial samples. |
|
375 |
#' @param dose_target_eot_samples (`numeric`)\cr vector of end-of-trial samples. |
|
376 |
#' @param dose_target_drt (`number`)\cr target in-trial estimate. |
|
377 |
#' @param dose_target_eot (`number`)\cr target end-of-trial estimate. |
|
378 |
#' @param dose_grid_range (`numeric`)\cr range of dose grid. |
|
379 |
#' @param nextBest (`NextBestTDsamples`)\cr the rule for the next best dose. |
|
380 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
381 |
#' @param next_dose (`number`)\cr next best dose. |
|
382 |
#' |
|
383 |
#' @export |
|
384 |
#' |
|
385 |
h_next_best_tdsamples_plot <- function(dose_target_drt_samples, |
|
386 |
dose_target_eot_samples, |
|
387 |
dose_target_drt, |
|
388 |
dose_target_eot, |
|
389 |
dose_grid_range, |
|
390 |
nextBest, |
|
391 |
doselimit, |
|
392 |
next_dose) { |
|
393 | 17x |
assert_numeric(dose_target_drt_samples, any.missing = FALSE) |
394 | 17x |
assert_numeric(dose_target_eot_samples, any.missing = FALSE) |
395 | 17x |
assert_number(dose_target_drt) |
396 | 17x |
assert_number(dose_target_eot) |
397 | 17x |
assert_range(dose_grid_range, finite = TRUE, unique = FALSE) |
398 | 17x |
assert_class(nextBest, "NextBestTDsamples") |
399 | 17x |
assert_number(doselimit) |
400 | 17x |
assert_number(next_dose, na.ok = TRUE) |
401 | ||
402 | 17x |
lbl1 <- paste("TD", nextBest@prob_target_drt * 100, "Estimate") |
403 | 17x |
lbl2 <- paste("TD", nextBest@prob_target_eot * 100, "Estimate") |
404 | 17x |
labels <- data.frame( |
405 | 17x |
Type = c("during", "end", "Max", "Next"), |
406 | 17x |
Alpha = c(0.25, 0.25, 1, 1), |
407 | 17x |
x = c( |
408 | 17x |
dose_target_drt, |
409 | 17x |
dose_target_eot, |
410 | 17x |
min(doselimit, dose_grid_range[2]), |
411 | 17x |
next_dose |
412 |
) |
|
413 |
) |
|
414 | 17x |
p <- ggplot( |
415 | 17x |
data = rbind( |
416 | 17x |
data.frame(period = "during", TD = dose_target_drt_samples), |
417 | 17x |
data.frame(period = "end", TD = dose_target_eot_samples) |
418 |
), |
|
419 | 17x |
aes(x = .data$TD, colour = .data$period), |
420 |
) + |
|
421 | 17x |
geom_density( |
422 | 17x |
aes(fill = .data$period, colour = .data$period), |
423 | 17x |
alpha = 0.25, |
424 | 17x |
bounds = dose_grid_range, |
425 | 17x |
show.legend = FALSE |
426 |
) + |
|
427 | 17x |
geom_vline(data = labels, aes(xintercept = x, colour = Type)) + |
428 | 17x |
ylab("Posterior density") + |
429 | 17x |
scale_colour_manual( |
430 | 17x |
name = NULL, |
431 | 17x |
values = c("during" = "orange", "end" = "violet", "Max" = "red", "Next" = "blue"), |
432 | 17x |
labels = c("during" = lbl1, "end" = lbl2, "Max" = "Max", "Next" = "Next") |
433 |
) + |
|
434 | 17x |
scale_fill_manual( |
435 | 17x |
values = c("during" = "orange", "end" = "violet") |
436 |
) |
|
437 |
} |
|
438 | ||
439 |
#' Building the Plot for `nextBest-NextBestTD` Method. |
|
440 |
#' |
|
441 |
#' @description `r lifecycle::badge("experimental")` |
|
442 |
#' |
|
443 |
#' Helper function which creates the plot for [`nextBest-NextBestTD()`] method. |
|
444 |
#' |
|
445 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
446 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
447 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
448 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
449 |
#' @param data (`Data`)\cr the data object from which the dose grid will be fetched. |
|
450 |
#' @param prob_dlt (`numeric`)\cr DLT probabilities for doses in grid. |
|
451 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
452 |
#' @param next_dose (`number`)\cr next best dose. |
|
453 |
#' |
|
454 |
#' @export |
|
455 |
#' |
|
456 |
h_next_best_td_plot <- function(prob_target_drt, |
|
457 |
dose_target_drt, |
|
458 |
prob_target_eot, |
|
459 |
dose_target_eot, |
|
460 |
data, |
|
461 |
prob_dlt, |
|
462 |
doselimit, |
|
463 |
next_dose) { |
|
464 | 18x |
assert_probability(prob_target_drt) |
465 | 18x |
assert_number(dose_target_drt) |
466 | 18x |
assert_probability(prob_target_eot) |
467 | 18x |
assert_number(dose_target_eot) |
468 | 18x |
assert_class(data, "Data") |
469 | 18x |
assert_probabilities(prob_dlt, len = data@nGrid) |
470 | 18x |
assert_number(doselimit) |
471 | 18x |
assert_number(next_dose, na.ok = TRUE) |
472 | ||
473 | 18x |
dosegrid_range <- dose_grid_range(data) |
474 | ||
475 | 18x |
p <- ggplot( |
476 | 18x |
data = data.frame(x = data@doseGrid, y = prob_dlt), |
477 | 18x |
aes(x = .data$x, y = .data$y) |
478 |
) + |
|
479 | 18x |
geom_line(colour = "red", linewidth = 1.5) + |
480 | 18x |
coord_cartesian(xlim = c(0, dosegrid_range[2])) + |
481 | 18x |
ylim(c(0, 1)) + |
482 | 18x |
xlab("Dose Levels") + |
483 | 18x |
ylab("Probability of DLT") |
484 | 18x |
if (h_in_range(dose_target_drt, range = dosegrid_range, bounds_closed = TRUE)) { |
485 | 17x |
p <- p + |
486 | 17x |
geom_point( |
487 | 17x |
data = data.frame(x = dose_target_drt, y = prob_target_drt), |
488 | 17x |
aes(x = .data$x, y = .data$y), |
489 | 17x |
colour = "orange", |
490 | 17x |
shape = 15, |
491 | 17x |
size = 8 |
492 |
) + |
|
493 | 17x |
annotate( |
494 | 17x |
geom = "text", |
495 | 17x |
label = paste("TD", prob_target_drt * 100, "Estimate"), |
496 | 17x |
x = dose_target_drt + 1, |
497 | 17x |
y = prob_target_drt - 0.2, |
498 | 17x |
size = 5, |
499 | 17x |
colour = "orange" |
500 |
) |
|
501 |
} |
|
502 | ||
503 | 18x |
if (h_in_range(dose_target_eot, range = dosegrid_range, bounds_closed = TRUE)) { |
504 | 17x |
p <- p + |
505 | 17x |
geom_point( |
506 | 17x |
data = data.frame(x = dose_target_eot, y = prob_target_eot), |
507 | 17x |
aes(x = .data$x, y = .data$y), |
508 | 17x |
colour = "violet", |
509 | 17x |
shape = 16, |
510 | 17x |
size = 8 |
511 |
) + |
|
512 | 17x |
annotate( |
513 | 17x |
geom = "text", |
514 | 17x |
label = paste("TD", prob_target_eot * 100, "Estimate"), |
515 | 17x |
x = dose_target_eot + 1, |
516 | 17x |
y = prob_target_eot - 0.1, |
517 | 17x |
size = 5, |
518 | 17x |
colour = "violet" |
519 |
) |
|
520 |
} |
|
521 | ||
522 | 18x |
maxdoselimit <- min(doselimit, dosegrid_range[2]) |
523 | ||
524 | 18x |
p + |
525 | 18x |
geom_vline(xintercept = maxdoselimit, colour = "brown", lwd = 1.1) + |
526 | 18x |
geom_text( |
527 | 18x |
data = data.frame(x = maxdoselimit, y = 0), |
528 | 18x |
aes(x = .data$x, y = .data$y, label = "Max", hjust = +1, vjust = -30), |
529 | 18x |
angle = 90, |
530 | 18x |
vjust = 1.5, |
531 | 18x |
hjust = 0.5, |
532 | 18x |
colour = "brown", |
533 |
) + |
|
534 | 18x |
geom_vline(xintercept = next_dose, colour = "purple", lwd = 1.1) + |
535 | 18x |
geom_text( |
536 | 18x |
data = data.frame(x = next_dose, y = 0), |
537 | 18x |
aes(x = .data$x, y = .data$y, label = "Next", hjust = 0, vjust = -30), |
538 | 18x |
angle = 90, |
539 | 18x |
vjust = -0.5, |
540 | 18x |
hjust = 0.5, |
541 | 18x |
colour = "purple" |
542 |
) |
|
543 |
} |
|
544 | ||
545 |
#' Building the Plot for `nextBest-NextBestMaxGain` Method. |
|
546 |
#' |
|
547 |
#' @description `r lifecycle::badge("experimental")` |
|
548 |
#' |
|
549 |
#' Helper function which creates the plot for [`nextBest-NextBestMaxGain()`] method. |
|
550 |
#' |
|
551 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
552 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
553 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
554 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
555 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
556 |
#' @param max_gain (`number`)\cr the maximum gain estimate. |
|
557 |
#' @param next_dose (`number`)\cr next best dose. |
|
558 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
559 |
#' @param data (`DataDual`)\cr the data object from which the dose grid will be fetched. |
|
560 |
#' @param model (`ModelTox`)\cr the DLT model. |
|
561 |
#' @param model_eff (`Effloglog`)\cr the efficacy model. |
|
562 |
#' |
|
563 |
#' @export |
|
564 |
#' |
|
565 |
h_next_best_mg_plot <- function(prob_target_drt, |
|
566 |
dose_target_drt, |
|
567 |
prob_target_eot, |
|
568 |
dose_target_eot, |
|
569 |
dose_mg, |
|
570 |
max_gain, |
|
571 |
next_dose, |
|
572 |
doselimit, |
|
573 |
data, |
|
574 |
model, |
|
575 |
model_eff) { |
|
576 | 17x |
assert_probability(prob_target_drt) |
577 | 17x |
assert_number(dose_target_drt) |
578 | 17x |
assert_probability(prob_target_eot) |
579 | 17x |
assert_number(dose_target_eot) |
580 | 17x |
assert_number(dose_mg, na.ok = TRUE) |
581 | 17x |
assert_number(max_gain, na.ok = TRUE) |
582 | 17x |
assert_number(next_dose, na.ok = TRUE) |
583 | 17x |
assert_number(doselimit) |
584 | 17x |
assert_class(data, "Data") |
585 | 17x |
assert_class(model, "ModelTox") |
586 | 17x |
assert_class(model_eff, "Effloglog") |
587 | ||
588 | 17x |
dosegrid_range <- dose_grid_range(data) |
589 | ||
590 | 17x |
data_plot <- data.frame( |
591 | 17x |
dose = rep(data@doseGrid, 3), |
592 | 17x |
y = c( |
593 | 17x |
prob(dose = data@doseGrid, model = model), |
594 | 17x |
efficacy(dose = data@doseGrid, model = model_eff), |
595 | 17x |
gain(dose = data@doseGrid, model_dle = model, model_eff = model_eff) |
596 |
), |
|
597 | 17x |
group = c( |
598 | 17x |
rep("p(DLE)", data@nGrid), |
599 | 17x |
rep("Expected Efficacy", data@nGrid), |
600 | 17x |
rep("Gain", data@nGrid) |
601 |
) |
|
602 |
) |
|
603 | ||
604 | 17x |
p <- ggplot(data = data_plot, aes(x = .data$dose, y = .data$y)) + |
605 | 17x |
geom_line(aes(group = group, color = group), linewidth = 1.5) + |
606 | 17x |
ggplot2::scale_colour_manual(name = "curves", values = c("blue", "green3", "red")) + |
607 | 17x |
coord_cartesian(xlim = c(0, dosegrid_range[2])) + |
608 | 17x |
ylim(range(data_plot$y)) + |
609 | 17x |
xlab("Dose Level") + |
610 | 17x |
ylab("Values") |
611 | ||
612 | 17x |
if (h_in_range(dose_target_eot, range = dosegrid_range, bounds_closed = FALSE)) { |
613 | 17x |
lab <- paste("TD", prob_target_eot * 100, "Estimate") |
614 | 17x |
p <- p + |
615 | 17x |
geom_point( |
616 | 17x |
data = data.frame(x = dose_target_eot, y = prob_target_eot), |
617 | 17x |
aes(x = .data$x, y = .data$y), |
618 | 17x |
colour = "violet", |
619 | 17x |
shape = 16, |
620 | 17x |
size = 8 |
621 |
) + |
|
622 | 17x |
annotate( |
623 | 17x |
geom = "text", label = lab, x = dose_target_eot - 1, y = 0.2, size = 5, colour = "violet" |
624 |
) |
|
625 |
} |
|
626 | ||
627 | 17x |
if (h_in_range(dose_mg, range = dosegrid_range, bounds_closed = FALSE)) { |
628 | 16x |
p <- p + |
629 | 16x |
geom_point( |
630 | 16x |
data = data.frame(x = dose_mg, y = max_gain), |
631 | 16x |
aes(x = .data$x, y = .data$y), |
632 | 16x |
colour = "green3", |
633 | 16x |
shape = 17, |
634 | 16x |
size = 8 |
635 |
) + |
|
636 | 16x |
annotate( |
637 | 16x |
"text", |
638 | 16x |
label = "Max Gain Estimate", x = dose_mg, y = max_gain - 0.1, size = 5, colour = "green3" |
639 |
) |
|
640 |
} |
|
641 | ||
642 | 17x |
if (h_in_range(dose_target_drt, range = dosegrid_range, bounds_closed = FALSE)) { |
643 | 17x |
lab <- paste("TD", prob_target_drt * 100, "Estimate") |
644 | 17x |
p <- p + |
645 | 17x |
geom_point( |
646 | 17x |
data = data.frame(x = dose_target_drt, y = prob_target_drt), |
647 | 17x |
aes(x = .data$x, y = .data$y), |
648 | 17x |
colour = "orange", |
649 | 17x |
shape = 15, |
650 | 17x |
size = 8 |
651 |
) + |
|
652 | 17x |
annotate( |
653 | 17x |
geom = "text", label = lab, x = dose_target_drt + 25, y = prob_target_drt + 0.01, size = 5, colour = "orange" |
654 |
) |
|
655 |
} |
|
656 | ||
657 | 17x |
maxdoselimit <- min(doselimit, dosegrid_range[2]) |
658 | ||
659 | 17x |
p + |
660 | 17x |
geom_vline(xintercept = maxdoselimit, colour = "brown", lwd = 1.1) + |
661 | 17x |
annotate( |
662 | 17x |
geom = "text", |
663 | 17x |
label = "Max", |
664 | 17x |
x = maxdoselimit - 2, |
665 | 17x |
y = max(data_plot$y), |
666 | 17x |
size = 5, |
667 | 17x |
angle = 90, |
668 | 17x |
vjust = -0.5, |
669 | 17x |
hjust = 0.5, |
670 | 17x |
colour = "brown" |
671 |
) + |
|
672 | 17x |
geom_vline(xintercept = next_dose, colour = "purple", lwd = 1.1) + |
673 | 17x |
annotate( |
674 | 17x |
geom = "text", |
675 | 17x |
label = "Next", |
676 | 17x |
x = next_dose + 1, |
677 | 17x |
y = max(data_plot$y) - 0.05, |
678 | 17x |
size = 5, |
679 | 17x |
angle = 90, |
680 | 17x |
vjust = 1.5, |
681 | 17x |
hjust = 0.5, |
682 | 17x |
color = "purple" |
683 |
) |
|
684 |
} |
|
685 | ||
686 |
#' Building the Plot for `nextBest-NextBestMaxGainSamples` Method. |
|
687 |
#' |
|
688 |
#' @description `r lifecycle::badge("experimental")` |
|
689 |
#' |
|
690 |
#' Helper function which creates the plot for [`nextBest-NextBestMaxGainSamples()`] method. |
|
691 |
#' |
|
692 |
#' @param prob_target_drt (`proportion`)\cr target DLT probability during the trial. |
|
693 |
#' @param dose_target_drt (`number`)\cr target dose estimate during the trial. |
|
694 |
#' @param prob_target_eot (`proportion`)\cr target DLT probability at the end of the trial. |
|
695 |
#' @param dose_target_eot (`number`)\cr target dose estimate at the end of the trial. |
|
696 |
#' @param dose_mg (`number`)\cr the dose corresponding to the maximum gain. |
|
697 |
#' @param dose_mg_samples (`numeric`)\cr for every sample, the dose (from the dose grid) |
|
698 |
#' that gives the maximum gain value. |
|
699 |
#' @param next_dose (`number`)\cr next best dose. |
|
700 |
#' @param doselimit (`number`)\cr the maximum allowed next dose. |
|
701 |
#' @param dose_grid_range (`numeric`)\cr dose grid range. |
|
702 |
#' |
|
703 |
#' @export |
|
704 |
#' |
|
705 |
h_next_best_mgsamples_plot <- function(prob_target_drt, |
|
706 |
dose_target_drt, |
|
707 |
prob_target_eot, |
|
708 |
dose_target_eot, |
|
709 |
dose_mg, |
|
710 |
dose_mg_samples, |
|
711 |
next_dose, |
|
712 |
doselimit, |
|
713 |
dose_grid_range) { |
|
714 | 9x |
assert_range(dose_grid_range, finite = TRUE, unique = FALSE) |
715 | 9x |
assert_probability(prob_target_drt) |
716 | 9x |
assert_number(dose_target_drt) |
717 | 9x |
assert_probability(prob_target_eot) |
718 | 9x |
assert_number(dose_target_eot) |
719 | 9x |
assert_number(dose_mg, na.ok = TRUE) |
720 | 9x |
assert_numeric( |
721 | 9x |
dose_mg_samples, |
722 | 9x |
lower = dose_grid_range[1], upper = dose_grid_range[2], finite = TRUE, any.missing = FALSE |
723 |
) |
|
724 | 9x |
assert_number(next_dose, na.ok = TRUE) |
725 | 9x |
assert_number(doselimit) |
726 | ||
727 | 9x |
p <- ggplot() + |
728 | 9x |
geom_histogram( |
729 | 9x |
data = data.frame(Gstar = dose_mg_samples), |
730 | 9x |
aes(x = .data$Gstar), |
731 | 9x |
fill = "darkgreen", |
732 | 9x |
colour = "green3", |
733 | 9x |
binwidth = 25 |
734 |
) + |
|
735 | 9x |
coord_cartesian(xlim = c(0, dose_grid_range[2])) + |
736 | 9x |
ylab("Posterior density") |
737 | ||
738 | 9x |
if (h_in_range(dose_target_drt, range = dose_grid_range, bounds_closed = FALSE)) { |
739 | 9x |
lab <- paste("TD", prob_target_drt * 100, "Estimate") |
740 | 9x |
p <- p + |
741 | 9x |
geom_vline(xintercept = dose_target_drt, colour = "orange", lwd = 1.1) + |
742 | 9x |
annotate( |
743 | 9x |
geom = "text", label = lab, x = dose_target_drt, y = 0, hjust = -0.1, vjust = -20, size = 5, colour = "orange" |
744 |
) |
|
745 |
} |
|
746 | ||
747 | 9x |
if (h_in_range(dose_target_eot, range = dose_grid_range, bounds_closed = FALSE)) { |
748 | 9x |
lab <- paste("TD", prob_target_eot * 100, "Estimate") |
749 | 9x |
p <- p + |
750 | 9x |
geom_vline(xintercept = dose_target_eot, colour = "violet", lwd = 1.1) + |
751 | 9x |
annotate( |
752 | 9x |
geom = "text", label = lab, x = dose_target_eot, y = 0, hjust = -0.1, vjust = -25, size = 5, colour = "violet" |
753 |
) |
|
754 |
} |
|
755 | ||
756 | 9x |
if (h_in_range(dose_mg, range = dose_grid_range, bounds_closed = FALSE)) { |
757 | 6x |
lab <- "Gstar Estimate" |
758 | 6x |
p <- p + |
759 | 6x |
geom_vline(xintercept = dose_mg, colour = "green", lwd = 1.1) + |
760 | 6x |
annotate( |
761 | 6x |
geom = "text", label = lab, x = dose_mg, y = 0, hjust = -0.1, vjust = -25, size = 5, colour = "green" |
762 |
) |
|
763 |
} |
|
764 | ||
765 | 9x |
maxdoselimit <- min(doselimit, dose_grid_range[2]) |
766 | ||
767 | 9x |
p + |
768 | 9x |
geom_vline(xintercept = maxdoselimit, colour = "red", lwd = 1.1) + |
769 | 9x |
annotate( |
770 | 9x |
geom = "text", label = "Max", x = maxdoselimit, y = 0, hjust = +1, vjust = -35, colour = "red" |
771 |
) + |
|
772 | 9x |
geom_vline(xintercept = next_dose, colour = "blue", lwd = 1.1) + |
773 | 9x |
annotate( |
774 | 9x |
geom = "text", label = "Next", x = next_dose, y = 0, hjust = 0.1, vjust = -30, colour = "blue" |
775 |
) |
|
776 |
} |
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 | 39044x |
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 | 23880x |
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("get", |
|
78 |
signature = |
|
79 |
signature( |
|
80 |
x = "Samples", |
|
81 |
pos = "character", |
|
82 |
envir = "ANY", |
|
83 |
mode = "ANY", |
|
84 |
inherits = "ANY" |
|
85 |
), |
|
86 |
def = |
|
87 |
function(x, |
|
88 |
pos, |
|
89 |
envir = NULL, |
|
90 |
mode = NULL, |
|
91 |
inherits = NULL) { |
|
92 |
## check the parameter name |
|
93 | 17x |
assert_scalar(pos) |
94 | 16x |
assert_choice(pos, names(x)) |
95 | ||
96 |
## get the samples for this parameter |
|
97 | 15x |
d <- x@data[[pos]] |
98 |
## this can be either a vector or a matrix |
|
99 | ||
100 |
## how many parameters do we have? |
|
101 | 15x |
nPars <- NCOL(d) |
102 | ||
103 |
## what are the names of all parameter |
|
104 |
## elements? |
|
105 | 15x |
elements <- |
106 | 15x |
if (nPars == 1L) { |
107 | 11x |
pos |
108 |
} else { |
|
109 | 4x |
paste(pos, |
110 | 4x |
"[", seq_len(nPars), "]", |
111 | 4x |
sep = "" |
112 |
) |
|
113 |
} |
|
114 | ||
115 |
## in case we have a vector parameter |
|
116 | 15x |
if (nPars > 1L) { |
117 |
## what are the indices to be returned? |
|
118 | 4x |
indices <- |
119 | 4x |
if (is.null(envir)) { |
120 | 1x |
seq_along(elements) |
121 |
} else { |
|
122 | 3x |
assert_integer(envir) |
123 | 1x |
assert_subset(envir, seq_along(elements)) |
124 |
} |
|
125 | ||
126 |
## subset the data matrix and par names appropriately |
|
127 | 1x |
d <- d[, indices, drop = FALSE] |
128 | 1x |
elements <- elements[indices] |
129 | ||
130 |
## and also reduce the number of parameters |
|
131 | 1x |
nPars <- length(indices) |
132 |
} |
|
133 | ||
134 |
## now we can build |
|
135 | 12x |
ret <- data.frame( |
136 | 12x |
Iteration = seq_len(NROW(d)), |
137 | 12x |
Chain = 1L, |
138 | 12x |
Parameter = |
139 | 12x |
factor(rep(elements, each = NROW(d)), |
140 | 12x |
levels = elements |
141 |
), |
|
142 | 12x |
value = as.numeric(d) |
143 |
) |
|
144 | ||
145 |
## add the attributes |
|
146 | 12x |
ret <- structure(ret, |
147 | 12x |
nChains = 1L, |
148 | 12x |
nParameters = nPars, |
149 | 12x |
nIterations = x@options@iterations, |
150 | 12x |
nBurnin = x@options@burnin, |
151 | 12x |
nThin = x@options@step, |
152 | 12x |
description = elements, |
153 | 12x |
parallel = FALSE |
154 |
) |
|
155 | 12x |
return(ret) |
156 |
} |
|
157 |
) |
|
158 | ||
159 | ||
160 |
## -------------------------------------------------- |
|
161 |
## Get fitted curves from Samples |
|
162 |
## -------------------------------------------------- |
|
163 | ||
164 |
#' Fit method for the Samples class |
|
165 |
#' |
|
166 |
#' Note this new generic function is necessary because the \code{\link{fitted}} |
|
167 |
#' function only allows the first argument \code{object} to appear in the |
|
168 |
#' signature. But we need also other arguments in the signature. |
|
169 |
#' |
|
170 |
#' @param object the \code{\linkS4class{Samples}} object |
|
171 |
#' @param model the \code{\linkS4class{GeneralModel}} object |
|
172 |
#' @param data the \code{\linkS4class{Data}} object |
|
173 |
#' @param \dots passed down to the [prob()] method. |
|
174 |
#' @return the data frame with required information (see method details) |
|
175 |
#' |
|
176 |
#' @export |
|
177 |
#' @keywords methods |
|
178 |
setGeneric("fit", |
|
179 |
def = |
|
180 |
function(object, |
|
181 |
model, |
|
182 |
data, |
|
183 |
...) { |
|
184 |
## there should be no default method, |
|
185 |
## therefore just forward to next method! |
|
186 | 103x |
standardGeneric("fit") |
187 |
}, |
|
188 |
valueClass = "data.frame" |
|
189 |
) |
|
190 | ||
191 | ||
192 |
## -------------------------------------------------- |
|
193 |
## Get fitted dose-tox curve from Samples |
|
194 |
## -------------------------------------------------- |
|
195 | ||
196 |
#' @param points at which dose levels is the fit requested? default is the dose |
|
197 |
#' grid |
|
198 |
#' @param quantiles the quantiles to be calculated (default: 0.025 and |
|
199 |
#' 0.975) |
|
200 |
#' @param middle the function for computing the middle point. Default: |
|
201 |
#' \code{\link{mean}} |
|
202 |
#' |
|
203 |
#' @describeIn fit This method returns a data frame with dose, middle, lower |
|
204 |
#' and upper quantiles for the dose-toxicity curve |
|
205 |
#' @example examples/Sample-methods-fit.R |
|
206 |
#' |
|
207 |
setMethod("fit", |
|
208 |
signature = |
|
209 |
signature( |
|
210 |
object = "Samples", |
|
211 |
model = "GeneralModel", |
|
212 |
data = "Data" |
|
213 |
), |
|
214 |
def = |
|
215 |
function(object, |
|
216 |
model, |
|
217 |
data, |
|
218 |
points = data@doseGrid, |
|
219 |
quantiles = c(0.025, 0.975), |
|
220 |
middle = mean, |
|
221 |
...) { |
|
222 |
## some checks |
|
223 | 66x |
assert_probability_range(quantiles) |
224 | 65x |
assert_numeric(points) |
225 | ||
226 |
## first we have to get samples from the dose-tox |
|
227 |
## curve at the dose grid points. |
|
228 | 64x |
probSamples <- matrix( |
229 | 64x |
nrow = size(object), |
230 | 64x |
ncol = length(points) |
231 |
) |
|
232 | ||
233 |
## evaluate the probs, for all samples. |
|
234 | 64x |
for (i in seq_along(points)) { |
235 |
## Now we want to evaluate for the |
|
236 |
## following dose: |
|
237 | 3743x |
probSamples[, i] <- prob( |
238 | 3743x |
dose = points[i], |
239 | 3743x |
model, |
240 | 3743x |
object, |
241 |
... |
|
242 |
) |
|
243 |
} |
|
244 | ||
245 |
## extract middle curve |
|
246 | 64x |
middleCurve <- apply(probSamples, 2L, FUN = middle) |
247 | ||
248 |
## extract quantiles |
|
249 | 64x |
quantCurve <- apply(probSamples, 2L, quantile, |
250 | 64x |
prob = quantiles |
251 |
) |
|
252 | ||
253 |
## now create the data frame |
|
254 | 64x |
ret <- data.frame( |
255 | 64x |
dose = points, |
256 | 64x |
middle = middleCurve, |
257 | 64x |
lower = quantCurve[1, ], |
258 | 64x |
upper = quantCurve[2, ] |
259 |
) |
|
260 | ||
261 |
## return it |
|
262 | 64x |
return(ret) |
263 |
} |
|
264 |
) |
|
265 | ||
266 |
## -------------------------------------------------- |
|
267 |
## Get fitted dose-tox and dose-biomarker curves from Samples |
|
268 |
## -------------------------------------------------- |
|
269 | ||
270 |
#' @describeIn fit This method returns a data frame with dose, and middle, |
|
271 |
#' lower and upper quantiles, for both the dose-tox and dose-biomarker (suffix |
|
272 |
#' "Biomarker") curves, for all grid points (Note that currently only the grid |
|
273 |
#' points can be used, because the DualEndpointRW models only allow that) |
|
274 |
#' |
|
275 |
#' @example examples/Sample-methods-fit-DualEndpoint.R |
|
276 |
setMethod("fit", |
|
277 |
signature = |
|
278 |
signature( |
|
279 |
object = "Samples", |
|
280 |
model = "DualEndpoint", |
|
281 |
data = "DataDual" |
|
282 |
), |
|
283 |
def = |
|
284 |
function(object, |
|
285 |
model, |
|
286 |
data, |
|
287 |
quantiles = c(0.025, 0.975), |
|
288 |
middle = mean, |
|
289 |
...) { |
|
290 |
## some checks |
|
291 | 3x |
assert_probability_range(quantiles) |
292 | ||
293 |
## first obtain the dose-tox curve results from the parent method |
|
294 | 3x |
start <- callNextMethod( |
295 | 3x |
object = object, |
296 | 3x |
model = model, |
297 | 3x |
data = data, |
298 | 3x |
points = data@doseGrid, |
299 | 3x |
quantiles = quantiles, |
300 | 3x |
middle = middle, |
301 |
... |
|
302 |
) |
|
303 | ||
304 |
## now obtain the dose-biomarker results |
|
305 | ||
306 |
## get the biomarker level samples |
|
307 |
## at the dose grid points. |
|
308 | 3x |
biomLevelSamples <- biomarker(xLevel = seq_len(data@nGrid), model, samples = object) |
309 | ||
310 |
## extract middle curve |
|
311 | 3x |
middleCurve <- apply(biomLevelSamples, 2L, FUN = middle) |
312 | ||
313 |
## extract quantiles |
|
314 | 3x |
quantCurve <- apply(biomLevelSamples, 2L, quantile, |
315 | 3x |
prob = quantiles |
316 |
) |
|
317 | ||
318 |
## now create the data frame |
|
319 | 3x |
biomResults <- data.frame( |
320 | 3x |
middleBiomarker = middleCurve, |
321 | 3x |
lowerBiomarker = quantCurve[1, ], |
322 | 3x |
upperBiomarker = quantCurve[2, ] |
323 |
) |
|
324 | ||
325 |
## return both, pasted together |
|
326 | 3x |
return(cbind(start, biomResults)) |
327 |
} |
|
328 |
) |
|
329 | ||
330 |
## -------------------------------------------------- |
|
331 |
## Approximate posterior with (log) normal distribution |
|
332 |
## -------------------------------------------------- |
|
333 | ||
334 |
#' Approximate posterior with (log) normal distribution |
|
335 |
#' |
|
336 |
#' To reproduce the resultant approximate model in the future exactly, include |
|
337 |
#' \code{seed = xxxx} in the call to `approximate`. |
|
338 |
#' |
|
339 |
#' @param object the \code{\linkS4class{Samples}} object |
|
340 |
#' @param model the \code{\linkS4class{GeneralModel}} object |
|
341 |
#' @param data the \code{\linkS4class{Data}} object |
|
342 |
#' @param \dots additional arguments (see methods) |
|
343 |
#' @return a `list` containing the approximation model and, if requested, a |
|
344 |
#' `ggplot2` object containing a graphical representation of the fitted model |
|
345 |
#' |
|
346 |
#' @export |
|
347 |
#' @keywords methods |
|
348 |
setGeneric("approximate", |
|
349 |
def = |
|
350 |
function(object, model, data, ...) { |
|
351 |
## there should be no default method, |
|
352 |
## therefore just forward to next method! |
|
353 | 7x |
standardGeneric("approximate") |
354 |
}, |
|
355 |
valueClass = "list" |
|
356 |
) |
|
357 | ||
358 | ||
359 |
##' @param points optional parameter, which gives the dose values at which |
|
360 |
##' the approximation should rely on (default: 5 values equally spaced from |
|
361 |
##' minimum to maximum of the dose grid) |
|
362 |
##' @param refDose the reference dose to be used (default: median of |
|
363 |
##' \code{points}) |
|
364 |
##' @param logNormal use the log-normal prior? (not default) otherwise, the |
|
365 |
##' normal prior for the logistic regression coefficients is used |
|
366 |
##' @param verbose be verbose (progress statements)? (default) |
|
367 |
##' @param create_plot add a `ggplot2` object to the return value (default) |
|
368 |
##' |
|
369 |
##' @describeIn approximate Here the \dots argument can transport additional arguments for |
|
370 |
##' \code{\link{Quantiles2LogisticNormal}}, e.g. in order to control the |
|
371 |
##' approximation quality, etc. |
|
372 |
##' |
|
373 |
##' @example examples/Sample-methods-approximate.R |
|
374 |
setMethod("approximate", |
|
375 |
signature = |
|
376 |
signature(object = "Samples"), |
|
377 |
def = |
|
378 |
function(object, |
|
379 |
model, |
|
380 |
data, |
|
381 |
points = |
|
382 |
seq( |
|
383 |
from = min(data@doseGrid), |
|
384 |
to = max(data@doseGrid), |
|
385 |
length = 5L |
|
386 |
), |
|
387 |
refDose = median(points), |
|
388 |
logNormal = FALSE, |
|
389 |
verbose = TRUE, |
|
390 |
create_plot = TRUE, |
|
391 |
...) { |
|
392 |
# Validation |
|
393 | 7x |
assert_logical(logNormal) |
394 | 6x |
assert_logical(verbose) |
395 | 5x |
assert_logical(create_plot) |
396 | 4x |
assert_numeric(points) |
397 | 3x |
assert_numeric(refDose) |
398 |
## get the required quantiles at these dose levels: |
|
399 | 2x |
quants <- fit(object, |
400 | 2x |
model, |
401 | 2x |
data, |
402 | 2x |
points = points, |
403 | 2x |
quantiles = c(0.025, 0.975), |
404 | 2x |
middle = median |
405 |
) |
|
406 | ||
407 |
## get better starting values if it is already a logistic normal |
|
408 |
## model |
|
409 | 2x |
if (is(model, "LogisticNormal") && (!logNormal)) { |
410 | ! |
means <- sapply( |
411 | ! |
object@data, |
412 | ! |
mean |
413 |
) |
|
414 | ! |
cov <- cov(as.data.frame(object@data)) |
415 | ||
416 | ! |
parstart <- c( |
417 | ! |
means[1], means[2], |
418 | ! |
sqrt(cov[1, 1]), sqrt(cov[2, 2]), |
419 | ! |
cov2cor(cov)[1, 2] |
420 |
) |
|
421 | 2x |
} else if (is(model, "LogisticLogNormal") && logNormal) { |
422 | 1x |
datTrafo <- with( |
423 | 1x |
object@data, |
424 | 1x |
cbind( |
425 | 1x |
alpha0, |
426 | 1x |
log(alpha1) |
427 |
) |
|
428 |
) |
|
429 | ||
430 | 1x |
means <- colMeans(datTrafo) |
431 | 1x |
cov <- cov(datTrafo) |
432 | ||
433 | 1x |
parstart <- c( |
434 | 1x |
means[1], means[2], |
435 | 1x |
sqrt(cov[1, 1]), sqrt(cov[2, 2]), |
436 | 1x |
cov2cor(cov)[1, 2] |
437 |
) |
|
438 |
} else { |
|
439 | 1x |
parstart <- NULL |
440 |
} |
|
441 | ||
442 |
## run the approx function |
|
443 | 2x |
quantRes <- Quantiles2LogisticNormal( |
444 | 2x |
dosegrid = quants$dose, |
445 | 2x |
refDose = refDose, |
446 | 2x |
lower = quants$lower, |
447 | 2x |
upper = quants$upper, |
448 | 2x |
median = quants$middle, |
449 | 2x |
verbose = verbose, |
450 | 2x |
parstart = parstart, |
451 | 2x |
logNormal = logNormal, |
452 |
... |
|
453 |
) |
|
454 | 2x |
rv <- list() |
455 | 2x |
rv$model <- quantRes$model |
456 | 2x |
if (create_plot) { |
457 | 2x |
rv$plot <- tibble::as_tibble(quantRes$required) %>% |
458 | 2x |
tibble::add_column(Type = "original") %>% |
459 | 2x |
tibble::add_column(x = points) %>% |
460 | 2x |
dplyr::bind_rows( |
461 | 2x |
tibble::as_tibble(quantRes$quantiles) %>% |
462 | 2x |
tibble::add_column(Type = "approximation") %>% |
463 | 2x |
tibble::add_column(x = points) |
464 |
) %>% |
|
465 | 2x |
tidyr::pivot_longer( |
466 | 2x |
c(lower, median, upper), |
467 | 2x |
names_to = "Line", |
468 | 2x |
values_to = "y" |
469 |
) %>% |
|
470 | 2x |
ggplot( |
471 | 2x |
aes( |
472 | 2x |
x = x, |
473 | 2x |
y = y, |
474 | 2x |
colour = Type, |
475 | 2x |
group = interaction(Type, .data$Line), |
476 | 2x |
linetype = (.data$Line == "median") |
477 |
) |
|
478 |
) + |
|
479 | 2x |
geom_line() + |
480 | 2x |
scale_colour_manual( |
481 | 2x |
name = " ", |
482 | 2x |
values = c("red", "blue") |
483 |
) + |
|
484 | 2x |
scale_linetype_manual( |
485 | 2x |
name = " ", |
486 | 2x |
values = c("dotted", "solid"), |
487 | 2x |
labels = c("95% CI", "Median"), |
488 | 2x |
guide = guide_legend(reverse = TRUE) |
489 |
) + |
|
490 | 2x |
labs( |
491 | 2x |
x = "Dose", |
492 | 2x |
y = "p(Tox)" |
493 |
) + |
|
494 | 2x |
theme_light() |
495 |
} |
|
496 | ||
497 |
## return the results |
|
498 | 2x |
return(rv) |
499 |
} |
|
500 |
) |
|
501 | ||
502 |
## -------------------------------------------------- |
|
503 |
## Plot dose-tox fit from a model |
|
504 |
## -------------------------------------------------- |
|
505 | ||
506 | ||
507 |
#' Plotting dose-toxicity model fits |
|
508 |
#' |
|
509 |
#' @param x the \code{\linkS4class{Samples}} object |
|
510 |
#' @param y the \code{\linkS4class{GeneralModel}} object |
|
511 |
#' @param data the \code{\linkS4class{Data}} object |
|
512 |
#' @param xlab the x axis label |
|
513 |
#' @param ylab the y axis label |
|
514 |
#' @param showLegend should the legend be shown? (default) |
|
515 |
#' @param \dots not used |
|
516 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
517 |
#' object for the dose-toxicity model fit |
|
518 |
#' |
|
519 |
#' @example examples/Sample-methods-plot.R |
|
520 |
#' @export |
|
521 |
setMethod("plot", |
|
522 |
signature = |
|
523 |
signature( |
|
524 |
x = "Samples", |
|
525 |
y = "GeneralModel" |
|
526 |
), |
|
527 |
def = |
|
528 |
function(x, y, data, ..., |
|
529 |
xlab = "Dose level", |
|
530 |
ylab = "Probability of DLT [%]", |
|
531 |
showLegend = TRUE) { |
|
532 |
## check args |
|
533 | 5x |
assert_logical(showLegend) |
534 | ||
535 |
## get the fit |
|
536 | 4x |
plotData <- fit(x, |
537 | 4x |
model = y, |
538 | 4x |
data = data, |
539 | 4x |
quantiles = c(0.025, 0.975), |
540 | 4x |
middle = mean, |
541 |
... |
|
542 |
) |
|
543 | ||
544 |
## make the plot |
|
545 | 4x |
gdata <- |
546 | 4x |
with( |
547 | 4x |
plotData, |
548 | 4x |
data.frame( |
549 | 4x |
x = rep(dose, 3), |
550 | 4x |
y = c(middle, lower, upper) * 100, |
551 | 4x |
group = |
552 | 4x |
rep(c("mean", "lower", "upper"), |
553 | 4x |
each = nrow(plotData) |
554 |
), |
|
555 | 4x |
Type = |
556 | 4x |
factor( |
557 | 4x |
c( |
558 | 4x |
rep( |
559 | 4x |
"Estimate", |
560 | 4x |
nrow(plotData) |
561 |
), |
|
562 | 4x |
rep( |
563 | 4x |
"95% Credible Interval", |
564 | 4x |
nrow(plotData) * 2 |
565 |
) |
|
566 |
), |
|
567 | 4x |
levels = |
568 | 4x |
c( |
569 | 4x |
"Estimate", |
570 | 4x |
"95% Credible Interval" |
571 |
) |
|
572 |
) |
|
573 |
) |
|
574 |
) |
|
575 | ||
576 | 4x |
ret <- gdata %>% ggplot() + |
577 | 4x |
geom_line( |
578 | 4x |
aes( |
579 | 4x |
x = x, |
580 | 4x |
y = y, |
581 | 4x |
group = group, |
582 | 4x |
linetype = Type, |
583 |
), |
|
584 | 4x |
colour = I("red"), |
585 |
) + |
|
586 | 4x |
coord_cartesian(ylim = c(0, 100)) + |
587 | 4x |
labs( |
588 | 4x |
x = xlab, |
589 | 4x |
y = ylab, |
590 |
) |
|
591 | ||
592 | 4x |
ret <- ret + |
593 | 4x |
scale_linetype_manual( |
594 | 4x |
breaks = |
595 | 4x |
c( |
596 | 4x |
"Estimate", |
597 | 4x |
"95% Credible Interval" |
598 |
), |
|
599 | 4x |
values = c(1, 2), guide = ifelse(showLegend, "legend", "none") |
600 |
) |
|
601 | ||
602 | 4x |
return(ret) |
603 |
} |
|
604 |
) |
|
605 | ||
606 | ||
607 |
## -------------------------------------------------- |
|
608 |
## Special method for dual endpoint model |
|
609 |
## -------------------------------------------------- |
|
610 | ||
611 | ||
612 |
#' Plotting dose-toxicity and dose-biomarker model fits |
|
613 |
#' |
|
614 |
#' When we have the dual endpoint model, |
|
615 |
#' also the dose-biomarker fit is shown in the plot |
|
616 |
#' |
|
617 |
#' @param x the \code{\linkS4class{Samples}} object |
|
618 |
#' @param y the \code{\linkS4class{DualEndpoint}} object |
|
619 |
#' @param data the \code{\linkS4class{DataDual}} object |
|
620 |
#' @param extrapolate should the biomarker fit be extrapolated to the whole |
|
621 |
#' dose grid? (default) |
|
622 |
#' @param showLegend should the legend be shown? (not default) |
|
623 |
#' @param \dots additional arguments for the parent method |
|
624 |
#' \code{\link{plot,Samples,GeneralModel-method}} |
|
625 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
626 |
#' object with the dose-toxicity and dose-biomarker model fits |
|
627 |
#' |
|
628 |
#' @example examples/Sample-methods-plot-DualEndpoint.R |
|
629 |
#' @export |
|
630 |
setMethod("plot", |
|
631 |
signature = |
|
632 |
signature( |
|
633 |
x = "Samples", |
|
634 |
y = "DualEndpoint" |
|
635 |
), |
|
636 |
def = |
|
637 |
function(x, y, data, extrapolate = TRUE, showLegend = FALSE, ...) { |
|
638 | 2x |
assert_logical(extrapolate) |
639 | ||
640 |
## call the superclass method, to get the toxicity plot |
|
641 | 1x |
plot1 <- callNextMethod(x, y, data, showLegend = showLegend, ...) |
642 | ||
643 |
## only look at these dose levels for the plot: |
|
644 | 1x |
xLevels <- |
645 | 1x |
if (extrapolate) { |
646 | 1x |
seq_along(data@doseGrid) |
647 |
} else { |
|
648 | ! |
1:max(data@xLevel) |
649 |
} |
|
650 | ||
651 |
## get the plot data for the biomarker plot |
|
652 | 1x |
functionSamples <- biomarker(xLevel = xLevels, model = y, samples = x) |
653 | ||
654 |
## extract mean curve |
|
655 | 1x |
meanCurve <- colMeans(functionSamples) |
656 | ||
657 |
## extract quantiles |
|
658 | 1x |
quantiles <- c(0.025, 0.975) |
659 | 1x |
quantCurve <- apply(functionSamples, 2L, quantile, |
660 | 1x |
prob = quantiles |
661 |
) |
|
662 | ||
663 |
## now create the data frame |
|
664 | 1x |
plotData <- data.frame( |
665 | 1x |
dose = data@doseGrid[xLevels], |
666 | 1x |
mean = meanCurve, |
667 | 1x |
lower = quantCurve[1, ], |
668 | 1x |
upper = quantCurve[2, ] |
669 |
) |
|
670 | ||
671 |
## make the second plot |
|
672 | 1x |
gdata <- |
673 | 1x |
with( |
674 | 1x |
plotData, |
675 | 1x |
data.frame( |
676 | 1x |
x = rep(dose, 3), |
677 | 1x |
y = c(mean, lower, upper), |
678 | 1x |
group = |
679 | 1x |
rep(c("mean", "lower", "upper"), |
680 | 1x |
each = nrow(plotData) |
681 |
), |
|
682 | 1x |
Type = |
683 | 1x |
factor( |
684 | 1x |
c( |
685 | 1x |
rep( |
686 | 1x |
"Estimate", |
687 | 1x |
nrow(plotData) |
688 |
), |
|
689 | 1x |
rep( |
690 | 1x |
"95% Credible Interval", |
691 | 1x |
nrow(plotData) * 2 |
692 |
) |
|
693 |
), |
|
694 | 1x |
levels = |
695 | 1x |
c( |
696 | 1x |
"Estimate", |
697 | 1x |
"95% Credible Interval" |
698 |
) |
|
699 |
) |
|
700 |
) |
|
701 |
) |
|
702 | 1x |
plot2 <- gdata %>% ggplot() + |
703 | 1x |
geom_line( |
704 | 1x |
aes( |
705 | 1x |
x = x, |
706 | 1x |
y = y, |
707 | 1x |
group = group, |
708 | 1x |
linetype = Type |
709 |
), |
|
710 | 1x |
colour = I("blue") |
711 |
) + |
|
712 | 1x |
labs( |
713 | 1x |
x = "Dose level", |
714 | 1x |
y = "Biomarker level" |
715 |
) |
|
716 | ||
717 | 1x |
plot2 <- plot2 + |
718 | 1x |
scale_linetype_manual( |
719 | 1x |
breaks = |
720 | 1x |
c( |
721 | 1x |
"Estimate", |
722 | 1x |
"95% Credible Interval" |
723 |
), |
|
724 | 1x |
values = c(1, 2), |
725 | 1x |
guide = ifelse(showLegend, "legend", "none") |
726 |
) |
|
727 | ||
728 |
## arrange both plots side by side |
|
729 | 1x |
ret <- gridExtra::arrangeGrob(plot1, plot2, ncol = 2) |
730 | 1x |
return(ret) |
731 |
} |
|
732 |
) |
|
733 | ||
734 | ||
735 |
## ------------------------------------------------------------------------------------- |
|
736 |
## Get fitted dose-tox curve from Samples for 'LogisticIndepBeta' model class |
|
737 |
## ------------------------------------------------------------------------------------ |
|
738 |
#' @describeIn fit This method return a data frame with dose, middle lower and upper quantiles |
|
739 |
#' for the dose-DLE curve using DLE samples for \dQuote{LogisticIndepBeta} model class |
|
740 |
#' @example examples/Samples-method-fitDLE.R |
|
741 |
setMethod("fit", |
|
742 |
signature = |
|
743 |
signature( |
|
744 |
object = "Samples", |
|
745 |
model = "LogisticIndepBeta", |
|
746 |
data = "Data" |
|
747 |
), |
|
748 |
def = |
|
749 |
function(object, |
|
750 |
model, |
|
751 |
data, |
|
752 |
points = data@doseGrid, |
|
753 |
quantiles = c(0.025, 0.975), |
|
754 |
middle = mean, |
|
755 |
...) { |
|
756 |
## some checks |
|
757 | 10x |
assert_probability_range(quantiles) |
758 | 8x |
assert_numeric(points) |
759 | ||
760 |
## first we have to get samples from the dose-tox |
|
761 |
## curve at the dose grid points. |
|
762 | 7x |
probSamples <- matrix( |
763 | 7x |
nrow = size(object), |
764 | 7x |
ncol = length(points) |
765 |
) |
|
766 | ||
767 |
## evaluate the probs, for all samples. |
|
768 | 7x |
for (i in seq_along(points)) { |
769 |
## Now we want to evaluate for the |
|
770 |
## following dose: |
|
771 | 84x |
probSamples[, i] <- prob( |
772 | 84x |
dose = points[i], |
773 | 84x |
model, |
774 | 84x |
object |
775 |
) |
|
776 |
} |
|
777 | ||
778 |
## extract middle curve |
|
779 | 7x |
middleCurve <- apply(probSamples, 2L, FUN = middle) |
780 | ||
781 |
## extract quantiles |
|
782 | 7x |
quantCurve <- apply(probSamples, 2L, quantile, |
783 | 7x |
prob = quantiles |
784 |
) |
|
785 | ||
786 |
## now create the data frame |
|
787 | 7x |
ret <- data.frame( |
788 | 7x |
dose = points, |
789 | 7x |
middle = middleCurve, |
790 | 7x |
lower = quantCurve[1, ], |
791 | 7x |
upper = quantCurve[2, ] |
792 |
) |
|
793 | ||
794 |
## return it |
|
795 | 7x |
return(ret) |
796 |
} |
|
797 |
) |
|
798 | ||
799 |
## ------------------------------------------------------------------------------------- |
|
800 |
## Get fitted dose-efficacy curve from Samples for 'Effloglog' model class |
|
801 |
## ------------------------------------------------------------------------------------ |
|
802 | ||
803 |
#' @describeIn fit This method returns a data frame with dose, middle, lower, upper quantiles for |
|
804 |
#' the dose-efficacy curve using efficacy samples for \dQuote{Effloglog} model class |
|
805 |
#' @example examples/Samples-method-fitEff.R |
|
806 |
setMethod("fit", |
|
807 |
signature = |
|
808 |
signature( |
|
809 |
object = "Samples", |
|
810 |
model = "Effloglog", |
|
811 |
data = "DataDual" |
|
812 |
), |
|
813 |
def = |
|
814 |
function(object, |
|
815 |
model, |
|
816 |
data, |
|
817 |
points = data@doseGrid, |
|
818 |
quantiles = c(0.025, 0.975), |
|
819 |
middle = mean, |
|
820 |
...) { |
|
821 |
## some checks |
|
822 | 8x |
assert_probability_range(quantiles) |
823 | 6x |
assert_numeric(points) |
824 | ||
825 |
## first we have to get samples from the dose-tox |
|
826 |
## curve at the dose grid points. |
|
827 | 5x |
ExpEffSamples <- matrix( |
828 | 5x |
nrow = size(object), |
829 | 5x |
ncol = length(points) |
830 |
) |
|
831 | ||
832 |
## evaluate the probs, for all samples. |
|
833 | 5x |
for (i in seq_along(points)) { |
834 |
## Now we want to evaluate for the |
|
835 |
## following dose: |
|
836 | 60x |
ExpEffSamples[, i] <- efficacy( |
837 | 60x |
dose = points[i], |
838 | 60x |
model, |
839 | 60x |
object |
840 |
) |
|
841 |
} |
|
842 | ||
843 |
## extract middle curve |
|
844 | 5x |
middleCurve <- apply(ExpEffSamples, 2L, FUN = middle) |
845 | ||
846 |
## extract quantiles |
|
847 | 5x |
quantCurve <- apply(ExpEffSamples, 2L, quantile, |
848 | 5x |
prob = quantiles |
849 |
) |
|
850 | ||
851 |
## now create the data frame |
|
852 | 5x |
ret <- data.frame( |
853 | 5x |
dose = points, |
854 | 5x |
middle = middleCurve, |
855 | 5x |
lower = quantCurve[1, ], |
856 | 5x |
upper = quantCurve[2, ] |
857 |
) |
|
858 | ||
859 |
## return it |
|
860 | 5x |
return(ret) |
861 |
} |
|
862 |
) |
|
863 |
## ========================================================================================== |
|
864 |
## -------------------------------------------------------------------- |
|
865 |
## Get fitted dose-efficacy based on the Efficacy Flexible model |
|
866 |
## ------------------------------------------------------------- |
|
867 |
#' @describeIn fit This method returns a data frame with dose, middle, lower and upper |
|
868 |
#' quantiles for the dose-efficacy curve using efficacy samples for \dQuote{EffFlexi} |
|
869 |
#' model class |
|
870 |
#' @example examples/Samples-method-fitEffFlexi.R |
|
871 |
setMethod("fit", |
|
872 |
signature = |
|
873 |
signature( |
|
874 |
object = "Samples", |
|
875 |
model = "EffFlexi", |
|
876 |
data = "DataDual" |
|
877 |
), |
|
878 |
def = |
|
879 |
function(object, |
|
880 |
model, |
|
881 |
data, |
|
882 |
points = data@doseGrid, |
|
883 |
quantiles = c(0.025, 0.975), |
|
884 |
middle = mean, |
|
885 |
...) { |
|
886 |
## some checks |
|
887 | 4x |
assert_probability_range(quantiles) |
888 | 2x |
assert_numeric(points) |
889 | ||
890 |
## first we have to get samples from the dose-tox |
|
891 |
## curve at the dose grid points. |
|
892 | 1x |
ExpEffSamples <- matrix( |
893 | 1x |
nrow = size(object), |
894 | 1x |
ncol = length(points) |
895 |
) |
|
896 | ||
897 |
## evaluate the probs, for all samples. |
|
898 | 1x |
for (i in seq_along(points)) { |
899 |
## Now we want to evaluate for the |
|
900 |
## following dose: |
|
901 | 12x |
ExpEffSamples[, i] <- efficacy( |
902 | 12x |
dose = points[i], |
903 | 12x |
model, |
904 | 12x |
object |
905 |
) |
|
906 |
} |
|
907 | ||
908 |
## extract middle curve |
|
909 | 1x |
middleCurve <- apply(ExpEffSamples, 2L, FUN = middle) |
910 | ||
911 |
## extract quantiles |
|
912 | 1x |
quantCurve <- apply(ExpEffSamples, 2L, quantile, |
913 | 1x |
prob = quantiles |
914 |
) |
|
915 | ||
916 |
## now create the data frame |
|
917 | 1x |
ret <- data.frame( |
918 | 1x |
dose = points, |
919 | 1x |
middle = middleCurve, |
920 | 1x |
lower = quantCurve[1, ], |
921 | 1x |
upper = quantCurve[2, ] |
922 |
) |
|
923 | ||
924 |
## return it |
|
925 | 1x |
return(ret) |
926 |
} |
|
927 |
) |
|
928 | ||
929 |
#' @describeIn fit This method returns a data frame with dose, middle, lower |
|
930 |
#' and upper quantiles for the dose-efficacy curve using efficacy samples |
|
931 |
#' for the \dQuote{LogisticLogNormalOrdinal} model class |
|
932 |
#' @example examples/Sample-methods-fit-LogisticLogNormalOrdinal.R |
|
933 |
setMethod( |
|
934 |
"fit", |
|
935 |
signature = signature( |
|
936 |
object = "Samples", |
|
937 |
model = "LogisticLogNormalOrdinal", |
|
938 |
data = "DataOrdinal" |
|
939 |
), |
|
940 |
def = function(object, |
|
941 |
model, |
|
942 |
data, |
|
943 |
points = data@doseGrid, |
|
944 |
quantiles = c(0.025, 0.975), |
|
945 |
middle = mean, |
|
946 |
...) { |
|
947 |
# Validation |
|
948 | 15x |
assert_probability_range(quantiles) |
949 | 12x |
assert_numeric(points) |
950 | 11x |
assert_function(middle) |
951 | ||
952 |
# Begin |
|
953 |
# Get samples from the dose-tox curve at the dose grid points. |
|
954 | 10x |
probSamples <- matrix( |
955 | 10x |
nrow = size(object), |
956 | 10x |
ncol = length(points) |
957 |
) |
|
958 |
# Evaluate the probs, for all samples. |
|
959 | 10x |
for (i in seq_along(points)) { |
960 |
# Now we want to evaluate for the following dose: |
|
961 | 68x |
probSamples[, i] <- prob( |
962 | 68x |
dose = points[i], |
963 | 68x |
model, |
964 | 68x |
object, |
965 |
... |
|
966 |
) |
|
967 |
} |
|
968 |
# Extract middle curve |
|
969 | 10x |
middleCurve <- apply(probSamples, 2L, FUN = middle) |
970 |
# Extract quantiles |
|
971 | 10x |
quantCurve <- apply(probSamples, 2L, quantile, prob = quantiles) |
972 | ||
973 |
# Create the data frame... |
|
974 | 10x |
ret <- data.frame( |
975 | 10x |
dose = points, |
976 | 10x |
middle = middleCurve, |
977 | 10x |
lower = quantCurve[1, ], |
978 | 10x |
upper = quantCurve[2, ] |
979 |
) |
|
980 | ||
981 |
# ...and return it |
|
982 | 10x |
return(ret) |
983 |
} |
|
984 |
) |
|
985 |
## ============================================================== |
|
986 |
## ---------------------------------------------------------------- |
|
987 |
## Get fitted values at all dose levels from gain samples |
|
988 |
## ----------------------------------------------------------------- |
|
989 |
#' Get the fitted values for the gain values at all dose levels based on |
|
990 |
#' a given pseudo DLE model, DLE sample, a pseudo efficacy model, a Efficacy sample |
|
991 |
#' and data. This method returns a data frame with dose, middle, lower and upper quantiles |
|
992 |
#' of the gain value samples |
|
993 |
#' |
|
994 |
#' @param DLEmodel the DLE pseudo model of \code{\linkS4class{ModelTox}} class object |
|
995 |
#' @param DLEsamples the DLE samples of \code{\linkS4class{Samples}} class object |
|
996 |
#' @param Effmodel the efficacy pseudo model of \code{\linkS4class{ModelEff}} class object |
|
997 |
#' @param Effsamples the efficacy samples of \code{\linkS4class{Samples}} class object |
|
998 |
#' @param data the data input of \code{\linkS4class{DataDual}} class object |
|
999 |
#' @param \dots additional arguments for methods |
|
1000 |
#' |
|
1001 |
#' @export |
|
1002 |
#' @keywords methods |
|
1003 |
#' @example examples/Samples-method-fitGain.R |
|
1004 |
setGeneric("fitGain", |
|
1005 |
def = |
|
1006 |
function(DLEmodel, |
|
1007 |
DLEsamples, |
|
1008 |
Effmodel, |
|
1009 |
Effsamples, |
|
1010 |
data, |
|
1011 |
...) { |
|
1012 |
## there should be no default method, |
|
1013 |
## therefore just forward to next method! |
|
1014 | 10x |
standardGeneric("fitGain") |
1015 |
}, |
|
1016 |
valueClass = "data.frame" |
|
1017 |
) |
|
1018 | ||
1019 |
#' @describeIn fitGain This method returns a data frame with dose, middle, lower, upper quantiles for |
|
1020 |
#' the gain values obtained given the DLE and the efficacy samples |
|
1021 |
#' @param points at which dose levels is the fit requested? default is the dose |
|
1022 |
#' grid |
|
1023 |
#' @param quantiles the quantiles to be calculated (default: 0.025 and |
|
1024 |
#' 0.975) |
|
1025 |
#' @param middle the function for computing the middle point. Default: |
|
1026 |
#' \code{\link{mean}} |
|
1027 |
#' @example examples/Samples-method-fitGain.R |
|
1028 |
setMethod("fitGain", |
|
1029 |
signature = |
|
1030 |
signature( |
|
1031 |
DLEmodel = "ModelTox", |
|
1032 |
DLEsamples = "Samples", |
|
1033 |
Effmodel = "ModelEff", |
|
1034 |
Effsamples = "Samples", |
|
1035 |
data = "DataDual" |
|
1036 |
), |
|
1037 |
def = |
|
1038 |
function(DLEmodel, |
|
1039 |
DLEsamples, |
|
1040 |
Effmodel, |
|
1041 |
Effsamples, |
|
1042 |
data, |
|
1043 |
points = data@doseGrid, |
|
1044 |
quantiles = c(0.025, 0.975), |
|
1045 |
middle = mean, |
|
1046 |
...) { |
|
1047 |
## some checks |
|
1048 | 10x |
assert_probability_range(quantiles) |
1049 | 6x |
assert_numeric(points) |
1050 | ||
1051 |
## first we have to get samples from the gain |
|
1052 |
## at the dose grid points. |
|
1053 | 4x |
GainSamples <- matrix( |
1054 | 4x |
nrow = size(DLEsamples), |
1055 | 4x |
ncol = length(points) |
1056 |
) |
|
1057 | ||
1058 |
## evaluate the probs, for all gain samples. |
|
1059 | 4x |
for (i in seq_along(points)) { |
1060 |
## Now we want to evaluate for the |
|
1061 |
## following dose: |
|
1062 | 48x |
GainSamples[, i] <- gain( |
1063 | 48x |
dose = points[i], |
1064 | 48x |
DLEmodel, |
1065 | 48x |
DLEsamples, |
1066 | 48x |
Effmodel, |
1067 | 48x |
Effsamples |
1068 |
) |
|
1069 |
} |
|
1070 | ||
1071 |
## extract middle curve |
|
1072 | 4x |
middleCurve <- apply(GainSamples, 2L, FUN = middle) |
1073 | ||
1074 |
## extract quantiles |
|
1075 | 4x |
quantCurve <- apply(GainSamples, 2L, quantile, |
1076 | 4x |
prob = quantiles |
1077 |
) |
|
1078 | ||
1079 |
## now create the data frame |
|
1080 | 4x |
ret <- data.frame( |
1081 | 4x |
dose = points, |
1082 | 4x |
middle = middleCurve, |
1083 | 4x |
lower = quantCurve[1, ], |
1084 | 4x |
upper = quantCurve[2, ] |
1085 |
) |
|
1086 | ||
1087 |
## return it |
|
1088 | 4x |
return(ret) |
1089 |
} |
|
1090 |
) |
|
1091 |
## --------------------------------------------------------------------------------- |
|
1092 |
## Plot the fitted dose-DLE curve with pseudo DLE model with samples |
|
1093 |
## ------------------------------------------------------------------------------- |
|
1094 |
#' Plot the fitted dose-DLE curve using a \code{\linkS4class{ModelTox}} class model with samples |
|
1095 |
#' |
|
1096 |
#' @param x the \code{\linkS4class{Samples}} object |
|
1097 |
#' @param y the \code{\linkS4class{ModelTox}} model class object |
|
1098 |
#' @param data the \code{\linkS4class{Data}} object |
|
1099 |
#' @param xlab the x axis label |
|
1100 |
#' @param ylab the y axis label |
|
1101 |
#' @param showLegend should the legend be shown? (default) |
|
1102 |
#' @param \dots not used |
|
1103 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
1104 |
#' object for the dose-DLE model fit |
|
1105 |
#' |
|
1106 |
#' @example examples/Samples-method-plotModelTox.R |
|
1107 |
#' @export |
|
1108 |
#' @keywords methods |
|
1109 |
setMethod("plot", |
|
1110 |
signature = |
|
1111 |
signature( |
|
1112 |
x = "Samples", |
|
1113 |
y = "ModelTox" |
|
1114 |
), |
|
1115 |
def = |
|
1116 |
function(x, y, data, ..., |
|
1117 |
xlab = "Dose level", |
|
1118 |
ylab = "Probability of DLT [%]", |
|
1119 |
showLegend = TRUE) { |
|
1120 |
## check args |
|
1121 | 2x |
assert_logical(showLegend) |
1122 | ||
1123 | ||
1124 |
## get the fit |
|
1125 | 1x |
plotData <- fit(x, |
1126 | 1x |
model = y, |
1127 | 1x |
data = data, |
1128 | 1x |
quantiles = c(0.025, 0.975), |
1129 | 1x |
middle = mean |
1130 |
) |
|
1131 | ||
1132 |
## make the plot |
|
1133 | 1x |
gdata <- |
1134 | 1x |
with( |
1135 | 1x |
plotData, |
1136 | 1x |
data.frame( |
1137 | 1x |
x = rep(dose, 3), |
1138 | 1x |
y = c(middle, lower, upper) * 100, |
1139 | 1x |
group = |
1140 | 1x |
rep(c("mean", "lower", "upper"), |
1141 | 1x |
each = nrow(plotData) |
1142 |
), |
|
1143 | 1x |
Type = |
1144 | 1x |
factor( |
1145 | 1x |
c( |
1146 | 1x |
rep( |
1147 | 1x |
"Estimate", |
1148 | 1x |
nrow(plotData) |
1149 |
), |
|
1150 | 1x |
rep( |
1151 | 1x |
"95% Credible Interval", |
1152 | 1x |
nrow(plotData) * 2 |
1153 |
) |
|
1154 |
), |
|
1155 | 1x |
levels = |
1156 | 1x |
c( |
1157 | 1x |
"Estimate", |
1158 | 1x |
"95% Credible Interval" |
1159 |
) |
|
1160 |
) |
|
1161 |
) |
|
1162 |
) |
|
1163 | ||
1164 | 1x |
ret <- gdata %>% ggplot() + |
1165 | 1x |
geom_line( |
1166 | 1x |
aes( |
1167 | 1x |
x = x, |
1168 | 1x |
y = y, |
1169 | 1x |
group = group, |
1170 | 1x |
linetype = Type |
1171 |
), |
|
1172 | 1x |
colour = I("red"), |
1173 |
) + |
|
1174 | 1x |
coord_cartesian(ylim = c(0, 100)) + |
1175 | 1x |
labs( |
1176 | 1x |
x = xlab, |
1177 | 1x |
y = ylab |
1178 |
) |
|
1179 | ||
1180 | 1x |
ret <- ret + |
1181 | 1x |
scale_linetype_manual( |
1182 | 1x |
breaks = |
1183 | 1x |
c( |
1184 | 1x |
"Estimate", |
1185 | 1x |
"95% Credible Interval" |
1186 |
), |
|
1187 | 1x |
values = c(1, 2), guide = ifelse(showLegend, "legend", "none") |
1188 |
) |
|
1189 | ||
1190 | 1x |
return(ret) |
1191 |
} |
|
1192 |
) |
|
1193 | ||
1194 | ||
1195 |
# -------------------------------------------------------------------------------------------- |
|
1196 |
## Plot the fitted dose-efficacy curve using a pseudo efficacy model with samples |
|
1197 |
## ------------------------------------------------------------------------------------------- |
|
1198 |
#' Plot the fitted dose-efficacy curve using a model from \code{\linkS4class{ModelEff}} class |
|
1199 |
#' with samples |
|
1200 |
#' |
|
1201 |
#' @param x the \code{\linkS4class{Samples}} object |
|
1202 |
#' @param y the \code{\linkS4class{ModelEff}} model class object |
|
1203 |
#' @param data the \code{\linkS4class{Data}} object |
|
1204 |
#' @param xlab the x axis label |
|
1205 |
#' @param ylab the y axis label |
|
1206 |
#' @param showLegend should the legend be shown? (default) |
|
1207 |
#' @param \dots not used |
|
1208 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
1209 |
#' object for the dose-efficacy model fit |
|
1210 |
#' |
|
1211 |
#' @example examples/Samples-method-plotModelEff.R |
|
1212 |
#' @export |
|
1213 |
#' @keywords methods |
|
1214 |
setMethod("plot", |
|
1215 |
signature = |
|
1216 |
signature( |
|
1217 |
x = "Samples", |
|
1218 |
y = "ModelEff" |
|
1219 |
), |
|
1220 |
def = |
|
1221 |
function(x, y, data, ..., |
|
1222 |
xlab = "Dose level", |
|
1223 |
ylab = "Expected Efficacy", |
|
1224 |
showLegend = TRUE) { |
|
1225 |
## check args |
|
1226 | 4x |
assert_logical(showLegend) |
1227 | ||
1228 |
## get the fit |
|
1229 | 2x |
plotData <- fit(x, |
1230 | 2x |
model = y, |
1231 | 2x |
data = data, |
1232 | 2x |
quantiles = c(0.025, 0.975), |
1233 | 2x |
middle = mean |
1234 |
) |
|
1235 | ||
1236 |
## make the plot |
|
1237 | 2x |
gdata <- |
1238 | 2x |
with( |
1239 | 2x |
plotData, |
1240 | 2x |
data.frame( |
1241 | 2x |
x = rep(dose, 3), |
1242 | 2x |
y = c(middle, lower, upper), |
1243 | 2x |
group = |
1244 | 2x |
rep(c("mean", "lower", "upper"), |
1245 | 2x |
each = nrow(plotData) |
1246 |
), |
|
1247 | 2x |
Type = |
1248 | 2x |
factor( |
1249 | 2x |
c( |
1250 | 2x |
rep( |
1251 | 2x |
"Estimate", |
1252 | 2x |
nrow(plotData) |
1253 |
), |
|
1254 | 2x |
rep( |
1255 | 2x |
"95% Credible Interval", |
1256 | 2x |
nrow(plotData) * 2 |
1257 |
) |
|
1258 |
), |
|
1259 | 2x |
levels = |
1260 | 2x |
c( |
1261 | 2x |
"Estimate", |
1262 | 2x |
"95% Credible Interval" |
1263 |
) |
|
1264 |
) |
|
1265 |
) |
|
1266 |
) |
|
1267 | ||
1268 | 2x |
ret <- gdata %>% ggplot() + |
1269 | 2x |
geom_line( |
1270 | 2x |
aes( |
1271 | 2x |
x = x, |
1272 | 2x |
y = y, |
1273 | 2x |
group = group, |
1274 | 2x |
linetype = Type |
1275 |
), |
|
1276 | 2x |
colour = I("blue") |
1277 |
) + |
|
1278 | 2x |
labs( |
1279 | 2x |
x = xlab, |
1280 | 2x |
y = ylab |
1281 |
) + |
|
1282 | 2x |
coord_cartesian(xlim = c(0, max(data@doseGrid))) |
1283 | ||
1284 | 2x |
ret <- ret + |
1285 | 2x |
scale_linetype_manual( |
1286 | 2x |
breaks = |
1287 | 2x |
c( |
1288 | 2x |
"Estimate", |
1289 | 2x |
"95% Credible Interval" |
1290 |
), |
|
1291 | 2x |
values = c(1, 2), guide = ifelse(showLegend, "legend", "none") |
1292 |
) |
|
1293 | ||
1294 | 2x |
return(ret) |
1295 |
} |
|
1296 |
) |
|
1297 | ||
1298 |
## ---------------------------------------------------------------------------------------- |
|
1299 |
## Plot of fitted dose-DLE curve based on a pseudo DLE model without sample |
|
1300 |
## ------------------------------------------------------------------------------------- |
|
1301 |
#' Plot of the fitted dose-tox based with a given pseudo DLE model and data without samples |
|
1302 |
#' |
|
1303 |
#' @param x the data of \code{\linkS4class{Data}} class object |
|
1304 |
#' @param y the model of the \code{\linkS4class{ModelTox}} class object |
|
1305 |
#' @param xlab the x axis label |
|
1306 |
#' @param ylab the y axis label |
|
1307 |
#' @param showLegend should the legend be shown? (default) |
|
1308 |
#' @param \dots not used |
|
1309 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
1310 |
#' object for the dose-DLE model plot |
|
1311 |
#' |
|
1312 |
#' @example examples/Samples-method-plotModelToxNoSamples.R |
|
1313 |
#' @export |
|
1314 |
#' @keywords methods |
|
1315 |
setMethod("plot", |
|
1316 |
signature = |
|
1317 |
signature( |
|
1318 |
x = "Data", |
|
1319 |
y = "ModelTox" |
|
1320 |
), |
|
1321 |
def = |
|
1322 |
function(x, y, |
|
1323 |
xlab = "Dose level", |
|
1324 |
ylab = "Probability of DLE", |
|
1325 |
showLegend = TRUE, ...) { |
|
1326 |
## check args |
|
1327 | 2x |
assert_logical(showLegend) |
1328 | ||
1329 |
## Make sure the right model estimates are use with the given data |
|
1330 | 1x |
y <- update(object = y, data = x) |
1331 | ||
1332 | ||
1333 |
## create data frame |
|
1334 | ||
1335 | 1x |
plotData <- data.frame( |
1336 | 1x |
dose = x@doseGrid, |
1337 | 1x |
probDLE = prob( |
1338 | 1x |
dose = x@doseGrid, |
1339 | 1x |
model = y |
1340 |
) |
|
1341 |
) |
|
1342 |
## Look for TD30 and TD35 |
|
1343 | 1x |
TD30 <- dose( |
1344 | 1x |
x = 0.30, |
1345 | 1x |
model = y |
1346 |
) |
|
1347 | 1x |
TD35 <- dose( |
1348 | 1x |
x = 0.35, |
1349 | 1x |
model = y |
1350 |
) |
|
1351 | ||
1352 |
## make the plot |
|
1353 | 1x |
gdata <- with( |
1354 | 1x |
plotData, |
1355 | 1x |
data.frame( |
1356 | 1x |
x = dose, |
1357 | 1x |
y = probDLE, |
1358 | 1x |
group = rep("Estimated DLE", each = nrow(plotData)), |
1359 | 1x |
Type = factor(rep("Estimated DLE", nrow(plotData)), levels = "Estimated DLE") |
1360 |
) |
|
1361 |
) |
|
1362 | ||
1363 | 1x |
plot1 <- gdata %>% ggplot() + |
1364 | 1x |
geom_line( |
1365 | 1x |
aes( |
1366 | 1x |
x = x, |
1367 | 1x |
y = y, |
1368 | 1x |
group = group, |
1369 | 1x |
linetype = Type |
1370 |
), |
|
1371 | 1x |
colour = I("red"), |
1372 | 1x |
linewidth = 1.5 |
1373 |
) + |
|
1374 | 1x |
labs( |
1375 | 1x |
x = xlab, |
1376 | 1x |
y = ylab |
1377 |
) + |
|
1378 | 1x |
coord_cartesian(ylim = c(0, 1)) + |
1379 | 1x |
scale_linetype_manual( |
1380 | 1x |
breaks = "Estimated DLE", |
1381 | 1x |
values = c(1, 2), |
1382 | 1x |
guide = ifelse(showLegend, "legend", "none") |
1383 |
) |
|
1384 | 1x |
return(plot1) |
1385 |
} |
|
1386 |
) |
|
1387 | ||
1388 | ||
1389 |
## --------------------------------------------------------------------------------------------- |
|
1390 |
## Plot the fitted dose-efficacy curve given a pseudo efficacy model without samples |
|
1391 |
## ---------------------------------------------------------------------------------- |
|
1392 |
#' Plot of the fitted dose-efficacy based with a given pseudo efficacy model and data without samples |
|
1393 |
#' |
|
1394 |
#' @param x the data of \code{\linkS4class{DataDual}} class object |
|
1395 |
#' @param y the model of the \code{\linkS4class{ModelEff}} class object |
|
1396 |
#' @param xlab the x axis label |
|
1397 |
#' @param ylab the y axis label |
|
1398 |
#' @param showLegend should the legend be shown? (default) |
|
1399 |
#' @param \dots not used |
|
1400 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
1401 |
#' object for the dose-efficacy model plot |
|
1402 |
#' |
|
1403 |
#' @example examples/Samples-method-plotModelEffNoSamples.R |
|
1404 |
#' @export |
|
1405 |
#' @keywords methods |
|
1406 |
setMethod("plot", |
|
1407 |
signature = |
|
1408 |
signature( |
|
1409 |
x = "DataDual", |
|
1410 |
y = "ModelEff" |
|
1411 |
), |
|
1412 |
def = |
|
1413 |
function(x, y, ..., |
|
1414 |
xlab = "Dose level", |
|
1415 |
ylab = "Expected Efficacy", |
|
1416 |
showLegend = TRUE) { |
|
1417 |
## check args |
|
1418 | 1x |
assert_logical(showLegend) |
1419 | 1x |
y <- update(object = y, data = x) |
1420 | ||
1421 |
## create data frame |
|
1422 | ||
1423 | 1x |
plotEffData <- data.frame( |
1424 | 1x |
dose = x@doseGrid, |
1425 | 1x |
ExpEff = efficacy( |
1426 | 1x |
dose = x@doseGrid, |
1427 | 1x |
model = y |
1428 |
) |
|
1429 |
) |
|
1430 | ||
1431 |
## make the second plot |
|
1432 | 1x |
ggdata <- with( |
1433 | 1x |
plotEffData, |
1434 | 1x |
data.frame( |
1435 | 1x |
x = dose, |
1436 | 1x |
y = ExpEff, |
1437 | 1x |
group = rep("Estimated Expected Efficacy", each = nrow(plotEffData)), |
1438 | 1x |
Type = factor(rep("Estimated Expected Efficacy", nrow(plotEffData)), levels = "Estimated Expected Efficacy") |
1439 |
) |
|
1440 |
) |
|
1441 | ||
1442 |
## Get efficacy plot |
|
1443 | 1x |
plot2 <- ggplot(data = ggdata, aes(x = x, y = y), group = group) + |
1444 | 1x |
xlab("Dose Levels") + |
1445 | 1x |
ylab(paste("Estimated Expected Efficacy")) + |
1446 | 1x |
xlim(c(0, max(x@doseGrid))) + |
1447 | 1x |
geom_line(colour = I("blue"), linewidth = 1.5) |
1448 | ||
1449 | 1x |
plot2 <- plot2 + |
1450 | 1x |
geom_line(linewidth = 1.5, colour = "blue") |
1451 | ||
1452 | ||
1453 | 1x |
return(plot2) |
1454 |
} |
|
1455 |
) |
|
1456 | ||
1457 |
## ---------------------------------------------------------------------------------------------------------- |
|
1458 |
## Plot the gain curve using a pseudo DLE and a pseudo Efficacy model with samples |
|
1459 |
## ---------------------------------------------------------------------------------------------------- |
|
1460 |
#' Plot the gain curve in addition with the dose-DLE and dose-efficacy curve using a given DLE pseudo model, |
|
1461 |
#' a DLE sample, a given efficacy pseudo model and an efficacy sample |
|
1462 |
#' |
|
1463 |
#' @param DLEmodel the dose-DLE model of \code{\linkS4class{ModelTox}} class object |
|
1464 |
#' @param DLEsamples the DLE sample of \code{\linkS4class{Samples}} class object |
|
1465 |
#' @param Effmodel the dose-efficacy model of \code{\linkS4class{ModelEff}} class object |
|
1466 |
#' @param Effsamples the efficacy sample of of \code{\linkS4class{Samples}} class object |
|
1467 |
#' @param data the data input of \code{\linkS4class{DataDual}} class object |
|
1468 |
#' @param \dots not used |
|
1469 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
1470 |
#' object for the plot |
|
1471 |
#' |
|
1472 |
#' @example examples/Samples-method-plotGain.R |
|
1473 |
#' @export |
|
1474 |
#' @keywords methods |
|
1475 |
setGeneric("plotGain", |
|
1476 |
def = |
|
1477 |
function(DLEmodel, |
|
1478 |
DLEsamples, |
|
1479 |
Effmodel, |
|
1480 |
Effsamples, |
|
1481 |
data, ...) { |
|
1482 | 2x |
standardGeneric("plotGain") |
1483 |
} |
|
1484 |
) |
|
1485 |
#' @describeIn plotGain Standard method |
|
1486 |
setMethod("plotGain", |
|
1487 |
signature = |
|
1488 |
signature( |
|
1489 |
DLEmodel = "ModelTox", |
|
1490 |
DLEsamples = "Samples", |
|
1491 |
Effmodel = "ModelEff", |
|
1492 |
Effsamples = "Samples" |
|
1493 |
), |
|
1494 |
def = |
|
1495 |
function(DLEmodel, DLEsamples, Effmodel, Effsamples, data, ...) { |
|
1496 |
## Get fitted values for probabilities of DLE at all dose levels |
|
1497 | ||
1498 | 1x |
plotDLEData <- fit(DLEsamples, |
1499 | 1x |
model = DLEmodel, |
1500 | 1x |
data = data, |
1501 | 1x |
quantiles = c(0.025, 0.975), |
1502 | 1x |
middle = mean |
1503 |
) |
|
1504 | ||
1505 |
## Get fitted values for mean efficacy values at all dose levels |
|
1506 | 1x |
plotEffData <- fit(Effsamples, |
1507 | 1x |
model = Effmodel, |
1508 | 1x |
data = data, |
1509 | 1x |
quantiles = c(0.025, 0.975), |
1510 | 1x |
middle = mean |
1511 |
) |
|
1512 | ||
1513 |
## Get fitted values for gain values at all dose levels |
|
1514 | 1x |
plotGainData <- fitGain( |
1515 | 1x |
DLEmodel = DLEmodel, |
1516 | 1x |
DLEsamples = DLEsamples, |
1517 | 1x |
Effmodel = Effmodel, |
1518 | 1x |
Effsamples = Effsamples, |
1519 | 1x |
data = data |
1520 |
) |
|
1521 | ||
1522 |
## For each of the dose levels, take the mean for the probabilties of DLE, mean efiicacy values |
|
1523 |
## and gain values. Hence combine them into a data frame |
|
1524 | ||
1525 | 1x |
plotData <- data.frame( |
1526 | 1x |
dose = rep(data@doseGrid, 3), |
1527 | 1x |
values = c( |
1528 | 1x |
plotDLEData$middle, |
1529 | 1x |
plotEffData$middle, |
1530 | 1x |
plotGainData$middle |
1531 |
) |
|
1532 |
) |
|
1533 |
## only the line plots for the mean value of the DLE, efficacy and gain samples |
|
1534 |
## at all dose levels |
|
1535 | 1x |
gdata <- with( |
1536 | 1x |
plotData, |
1537 | 1x |
data.frame( |
1538 | 1x |
x = dose, |
1539 | 1x |
y = values, |
1540 | 1x |
group = c( |
1541 | 1x |
rep("p(DLE)", length(data@doseGrid)), |
1542 | 1x |
rep("Mean Expected Efficacy", length(data@doseGrid)), |
1543 | 1x |
rep("Gain", length(data@doseGrid)) |
1544 |
), |
|
1545 | 1x |
Type = factor("Estimate", levels = "Estimate") |
1546 |
) |
|
1547 |
) |
|
1548 | ||
1549 | 1x |
plot1 <- ggplot(data = gdata, aes(x = x, y = y)) + |
1550 | 1x |
geom_line(aes(group = group, color = group), linewidth = 1.5) + |
1551 | 1x |
scale_colour_manual(name = "curves", values = c("green3", "blue", "red")) + |
1552 | 1x |
xlab("Dose Level") + |
1553 | 1x |
xlim(c(0, max(data@doseGrid))) + |
1554 | 1x |
ylab(paste("Values")) + |
1555 | 1x |
ylim(c(min(gdata$y), max(gdata$y))) |
1556 | 1x |
return(plot1) |
1557 |
} |
|
1558 |
) |
|
1559 | ||
1560 |
## ---------------------------------------------------------------------------------------------------- |
|
1561 |
## Plot the gain curve using a pseudo DLE and a pseudo Efficacy model without samples |
|
1562 |
## ---------------------------------------------------------------------------------------------------- |
|
1563 |
#' Plot the gain curve in addition with the dose-DLE and dose-efficacy curve using a given DLE pseudo model, |
|
1564 |
#' and a given efficacy pseudo model |
|
1565 |
#' |
|
1566 |
#' @describeIn plotGain Standard method |
|
1567 |
#' @param size (`integer`)\cr a vector of length two defining the sizes of |
|
1568 |
#' the shapes used to identify the doses with, respectively, p(DLE = 0.3) and the |
|
1569 |
#' maximum gain |
|
1570 |
#' @param shape (`integer`)\cr a vector of length two defining the shapes |
|
1571 |
#' used to identify the doses with, respectively, p(DLE = 0.3) and the maximum gain |
|
1572 |
#' |
|
1573 |
#' @example examples/Samples-method-plotGainNoSamples.R |
|
1574 |
#' @export |
|
1575 |
#' @keywords methods |
|
1576 |
setMethod("plotGain", |
|
1577 |
signature = |
|
1578 |
signature( |
|
1579 |
DLEmodel = "ModelTox", |
|
1580 |
DLEsamples = "missing", |
|
1581 |
Effmodel = "ModelEff", |
|
1582 |
Effsamples = "missing" |
|
1583 |
), |
|
1584 |
def = |
|
1585 |
function(DLEmodel, Effmodel, data, size = c(8L, 8L), shape = c(16L, 17L), ...) { |
|
1586 | 1x |
assert_integer(size, len = 2, any.missing = FALSE, lower = 0, upper = 20) |
1587 | 1x |
assert_integer(shape, len = 2, any.missing = FALSE, unique = TRUE, lower = 0, upper = 25) |
1588 |
## Make sure the model estimates are corresponds to the input data |
|
1589 | 1x |
DLEmodel <- update(object = DLEmodel, data = data) |
1590 | 1x |
Effmodel <- update(object = Effmodel, data = data) |
1591 | ||
1592 | 1x |
plotData <- data.frame( |
1593 | 1x |
dose = rep(data@doseGrid, 3), |
1594 | 1x |
values = c( |
1595 | 1x |
prob( |
1596 | 1x |
dose = data@doseGrid, |
1597 | 1x |
model = DLEmodel |
1598 |
), |
|
1599 | 1x |
efficacy( |
1600 | 1x |
dose = data@doseGrid, |
1601 | 1x |
model = Effmodel |
1602 |
), |
|
1603 | 1x |
gain( |
1604 | 1x |
dose = data@doseGrid, |
1605 | 1x |
model_dle = DLEmodel, |
1606 | 1x |
model_eff = Effmodel |
1607 |
) |
|
1608 |
) |
|
1609 |
) |
|
1610 | 1x |
gdata <- with( |
1611 | 1x |
plotData, |
1612 | 1x |
data.frame( |
1613 | 1x |
x = dose, |
1614 | 1x |
y = values, |
1615 | 1x |
group = c( |
1616 | 1x |
rep("p(DLE)", length(data@doseGrid)), |
1617 | 1x |
rep("Expected Efficacy", length(data@doseGrid)), |
1618 | 1x |
rep("Gain", length(data@doseGrid)) |
1619 |
), |
|
1620 | 1x |
colour = rep(c("blue", "green3", "red")), |
1621 | 1x |
Type = factor("Estimate", levels = "Estimate") |
1622 |
) |
|
1623 |
) |
|
1624 | ||
1625 |
# if changing the line type is unacceptable, consider |
|
1626 |
# https://stackoverflow.com/questions/25632242/filled-and-hollow-shapes-where-the-fill-color-the-line-color |
|
1627 | 1x |
plot1 <- ggplot(data = gdata, aes(x = x, y = y)) + |
1628 | 1x |
geom_line(aes(group = group, linetype = group, colour = group), linewidth = 1) + |
1629 | 1x |
scale_colour_manual( |
1630 | 1x |
name = "Curves", |
1631 | 1x |
values = c("blue", "green3", "red") |
1632 |
) + |
|
1633 | 1x |
scale_linetype_manual( |
1634 | 1x |
name = "Curves", |
1635 | 1x |
values = c("solid", "dotted", "dashed") |
1636 |
) + |
|
1637 | 1x |
xlab("Dose Level") + |
1638 | 1x |
ylab(paste("Values")) |
1639 | ||
1640 | 1x |
TD30 <- dose(x = 0.3, model = DLEmodel) |
1641 | ||
1642 | 1x |
Gainfun <- function(DOSE) { |
1643 | 66x |
-gain(DOSE, model_dle = DLEmodel, model_eff = Effmodel) |
1644 |
} |
|
1645 | 1x |
Gstar <- ( |
1646 | 1x |
optim( |
1647 | 1x |
min(data@doseGrid), |
1648 | 1x |
Gainfun, |
1649 | 1x |
method = "L-BFGS-B", |
1650 | 1x |
lower = min(data@doseGrid), |
1651 | 1x |
upper = max(data@doseGrid) |
1652 | 1x |
)$par |
1653 |
) |
|
1654 | 1x |
MaxGain <- -( |
1655 | 1x |
optim( |
1656 | 1x |
min(data@doseGrid), |
1657 | 1x |
Gainfun, |
1658 | 1x |
method = "L-BFGS-B", |
1659 | 1x |
lower = min(data@doseGrid), |
1660 | 1x |
upper = max(data@doseGrid) |
1661 | 1x |
)$value |
1662 |
) |
|
1663 | ||
1664 | 1x |
if ((TD30 < min(data@doseGrid)) | (TD30 > max(data@doseGrid))) { |
1665 | ! |
plot1 <- plot1 |
1666 | ! |
message(paste("TD30", paste(TD30, " not within dose Grid"))) |
1667 |
} else { |
|
1668 | 1x |
plot1 <- plot1 + |
1669 | 1x |
geom_point( |
1670 | 1x |
data = data.frame(x = TD30, y = 0.3), |
1671 | 1x |
aes(x = x, y = y), |
1672 | 1x |
colour = "violet", |
1673 | 1x |
shape = 16, |
1674 | 1x |
size = 8 |
1675 |
) + |
|
1676 | 1x |
annotate( |
1677 | 1x |
"text", |
1678 | 1x |
label = "p(DLE=0.3)", |
1679 | 1x |
x = TD30 + 1, |
1680 | 1x |
y = 0.2, |
1681 | 1x |
size = 5, |
1682 | 1x |
colour = "violet" |
1683 |
) |
|
1684 |
} |
|
1685 | ||
1686 |
# Add annotated point estimates to graph |
|
1687 | 1x |
point_data <- tibble::tibble( |
1688 | 1x |
Text = NA_character_, |
1689 | 1x |
X = NA_real_, |
1690 | 1x |
Y = NA_real_, |
1691 | 1x |
Shape = NA_real_, |
1692 | 1x |
Size = NA_real_, |
1693 | 1x |
Colour = NA_character_, |
1694 | 1x |
.rows = 0 |
1695 |
) |
|
1696 | ||
1697 | 1x |
if ((TD30 < min(data@doseGrid)) | (TD30 > max(data@doseGrid))) { |
1698 | ! |
message(paste("TD30", paste(TD30, " not within dose Grid"))) |
1699 |
} else { |
|
1700 | 1x |
point_data <- point_data %>% |
1701 | 1x |
tibble::add_row( |
1702 | 1x |
X = TD30, |
1703 | 1x |
Y = 0.3, |
1704 | 1x |
Shape = shape[1], |
1705 | 1x |
Size = size[1], |
1706 | 1x |
Colour = "violet", |
1707 | 1x |
Text = "p(DLE=0.3)" |
1708 |
) |
|
1709 |
} |
|
1710 | 1x |
if ((Gstar < min(data@doseGrid)) | (Gstar > max(data@doseGrid))) { |
1711 | ! |
print(paste("Gstar=", paste(Gstar, " not within dose Grid"))) |
1712 |
} else { |
|
1713 | 1x |
plot1 <- plot1 + |
1714 | 1x |
geom_point( |
1715 | 1x |
data = data.frame(x = Gstar, y = MaxGain), |
1716 | 1x |
aes(x = x, y = y), |
1717 | 1x |
colour = "green3", |
1718 | 1x |
shape = 17, |
1719 | 1x |
size = 8 |
1720 |
) + |
|
1721 | 1x |
annotate( |
1722 | 1x |
"text", |
1723 | 1x |
label = "Max Gain", |
1724 | 1x |
x = Gstar, |
1725 | 1x |
y = MaxGain - 0.1, |
1726 | 1x |
size = 5, |
1727 | 1x |
colour = "green3" |
1728 |
) |
|
1729 |
} |
|
1730 | 1x |
point_data <- point_data %>% |
1731 | 1x |
tibble::add_row( |
1732 | 1x |
X = Gstar, |
1733 | 1x |
Y = MaxGain, |
1734 | 1x |
Shape = shape[2], |
1735 | 1x |
Size = size[2], |
1736 | 1x |
Colour = "green3", |
1737 | 1x |
Text = "Max Gain" |
1738 |
) |
|
1739 | ||
1740 | ||
1741 | 1x |
plot1 <- plot1 + |
1742 | 1x |
geom_point( |
1743 | 1x |
data = point_data, |
1744 | 1x |
inherit.aes = FALSE, |
1745 | 1x |
aes( |
1746 | 1x |
x = .data$X, |
1747 | 1x |
y = .data$Y, |
1748 | 1x |
shape = as.factor(.data$Shape), |
1749 | 1x |
fill = .data$Colour |
1750 |
), |
|
1751 | 1x |
colour = point_data$Colour, |
1752 | 1x |
size = point_data$Size, |
1753 |
) + |
|
1754 | 1x |
scale_fill_manual( |
1755 | 1x |
name = "Estimates", |
1756 | 1x |
labels = c("p(DLE = 0.3)", "Max Gain"), |
1757 | 1x |
values = point_data$Colour |
1758 |
) + |
|
1759 | 1x |
scale_shape_discrete( |
1760 | 1x |
name = "Estimates", |
1761 | 1x |
labels = c("p(DLE = 0.3)", "Max Gain"), |
1762 | 1x |
breaks = point_data$Shape |
1763 |
) + |
|
1764 | 1x |
guides( |
1765 | 1x |
shape = guide_legend(override.aes = list(color = c("violet", "green3"))) |
1766 |
) + |
|
1767 | 1x |
coord_cartesian( |
1768 | 1x |
xlim = c(0, max(data@doseGrid)), |
1769 | 1x |
ylim = c(min(gdata$y), max(gdata$y)) |
1770 |
) |
|
1771 | 1x |
return(plot1) |
1772 |
} |
|
1773 |
) |
|
1774 |
## ========================================================================================== |
|
1775 | ||
1776 |
## ------------------------------------------------------------------------------- |
|
1777 |
## Plot of the DLE and efficacy curve sides by side with samples |
|
1778 |
## ----------------------------------------------------------------------------- |
|
1779 |
#' Plot of the DLE and efficacy curve side by side given a DLE pseudo model, |
|
1780 |
#' a DLE sample, an efficacy pseudo model and a given efficacy sample |
|
1781 |
#' |
|
1782 |
#' @param DLEmodel the pseudo DLE model of \code{\linkS4class{ModelTox}} class object |
|
1783 |
#' @param DLEsamples the DLE samples of \code{\linkS4class{Samples}} class object |
|
1784 |
#' @param Effmodel the pseudo efficacy model of \code{\linkS4class{ModelEff}} class object |
|
1785 |
#' @param Effsamples the Efficacy samples of \code{\linkS4class{Samples}} class object |
|
1786 |
#' @param data the data input of \code{\linkS4class{DataDual}} class object |
|
1787 |
#' @param extrapolate should the biomarker fit be extrapolated to the whole |
|
1788 |
#' dose grid? (default) |
|
1789 |
#' @param showLegend should the legend be shown? (not default) |
|
1790 |
#' @param \dots additional arguments for the parent method |
|
1791 |
#' \code{\link{plot,Samples,GeneralModel-method}} |
|
1792 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
1793 |
#' object with the dose-toxicity and dose-efficacy model fits |
|
1794 |
#' |
|
1795 |
#' @example examples/Samples-method-plotDualResponses.R |
|
1796 |
#' |
|
1797 |
#' @export |
|
1798 |
#' @keywords methods |
|
1799 |
setGeneric("plotDualResponses", |
|
1800 |
def = |
|
1801 |
function(DLEmodel, |
|
1802 |
DLEsamples, |
|
1803 |
Effmodel, |
|
1804 |
Effsamples, |
|
1805 |
data, ...) { |
|
1806 | 4x |
standardGeneric("plotDualResponses") |
1807 |
} |
|
1808 |
) |
|
1809 | ||
1810 |
#' @describeIn plotDualResponses function still to be documented |
|
1811 |
setMethod("plotDualResponses", |
|
1812 |
signature = |
|
1813 |
signature( |
|
1814 |
DLEmodel = "ModelTox", |
|
1815 |
DLEsamples = "Samples", |
|
1816 |
Effmodel = "ModelEff", |
|
1817 |
Effsamples = "Samples" |
|
1818 |
), |
|
1819 |
def = |
|
1820 |
function(DLEmodel, DLEsamples, Effmodel, Effsamples, data, extrapolate = TRUE, showLegend = FALSE, ...) { |
|
1821 | 3x |
assert_logical(extrapolate) |
1822 | 2x |
assert_logical(showLegend) |
1823 |
## Get Toxicity plot |
|
1824 |
## get the fit |
|
1825 | ||
1826 | 1x |
plotDLEData <- fit(DLEsamples, |
1827 | 1x |
model = DLEmodel, |
1828 | 1x |
data = data, |
1829 | 1x |
quantiles = c(0.025, 0.975), |
1830 | 1x |
middle = mean |
1831 |
) |
|
1832 | ||
1833 |
## make the plot |
|
1834 | 1x |
gdata <- |
1835 | 1x |
with( |
1836 | 1x |
plotDLEData, |
1837 | 1x |
data.frame( |
1838 | 1x |
x = rep(dose, 3), |
1839 | 1x |
y = c(middle, lower, upper) * 100, |
1840 | 1x |
group = |
1841 | 1x |
rep(c("mean", "lower", "upper"), |
1842 | 1x |
each = nrow(plotDLEData) |
1843 |
), |
|
1844 | 1x |
Type = |
1845 | 1x |
factor( |
1846 | 1x |
c( |
1847 | 1x |
rep( |
1848 | 1x |
"Estimate", |
1849 | 1x |
nrow(plotDLEData) |
1850 |
), |
|
1851 | 1x |
rep( |
1852 | 1x |
"95% Credible Interval", |
1853 | 1x |
nrow(plotDLEData) * 2 |
1854 |
) |
|
1855 |
), |
|
1856 | 1x |
levels = |
1857 | 1x |
c( |
1858 | 1x |
"Estimate", |
1859 | 1x |
"95% Credible Interval" |
1860 |
) |
|
1861 |
) |
|
1862 |
) |
|
1863 |
) |
|
1864 | ||
1865 | 1x |
ret1 <- gdata %>% ggplot() + |
1866 | 1x |
geom_line( |
1867 | 1x |
aes( |
1868 | 1x |
x = x, |
1869 | 1x |
y = y, |
1870 | 1x |
group = group, |
1871 | 1x |
linetype = Type |
1872 |
), |
|
1873 | 1x |
colour = I("red"), |
1874 |
) + |
|
1875 | 1x |
labs( |
1876 | 1x |
x = "Dose Levels", |
1877 | 1x |
y = "Probability of DLE [%]" |
1878 |
) + |
|
1879 | 1x |
coord_cartesian(ylim = c(0, 100)) + |
1880 | 1x |
scale_linetype_manual( |
1881 | 1x |
breaks = c( |
1882 | 1x |
"Estimate", |
1883 | 1x |
"95% Credible Interval" |
1884 |
), |
|
1885 | 1x |
values = c(1, 2), |
1886 | 1x |
guide = ifelse(showLegend, "legend", "none") |
1887 |
) |
|
1888 |
## only look at these dose levels for the plot: |
|
1889 | ||
1890 | 1x |
xLevels <- if (extrapolate) { |
1891 | 1x |
seq_along(data@doseGrid) |
1892 |
} else { |
|
1893 | ! |
1:max(data@xLevel) |
1894 |
} |
|
1895 | ||
1896 |
## get the plot data for the efficacy |
|
1897 | 1x |
functionSamples <- matrix( |
1898 | 1x |
nrow = size(Effsamples), |
1899 | 1x |
ncol = length(xLevels) |
1900 |
) |
|
1901 |
## evaluate the efficacy for all samples |
|
1902 | 1x |
for (i in seq_along(xLevels)) { |
1903 |
## Now we want to evaluate for the following dose |
|
1904 | 12x |
functionSamples[, i] <- efficacy( |
1905 | 12x |
dose = data@doseGrid[xLevels[i]], |
1906 | 12x |
model = Effmodel, |
1907 | 12x |
samples = Effsamples |
1908 |
) |
|
1909 |
} |
|
1910 |
## extract mean curve |
|
1911 | 1x |
meanCurve <- colMeans(functionSamples) |
1912 | ||
1913 |
## extract quantiles |
|
1914 | 1x |
quantiles <- c(0.025, 0.975) |
1915 | 1x |
quantCurve <- apply(functionSamples, 2L, quantile, prob = quantiles) |
1916 | ||
1917 |
## now create the data frame |
|
1918 | 1x |
plotEffData <- data.frame( |
1919 | 1x |
dose = data@doseGrid[xLevels], |
1920 | 1x |
mean = meanCurve, |
1921 | 1x |
lower = quantCurve[1, ], |
1922 | 1x |
upper = quantCurve[2, ] |
1923 |
) |
|
1924 |
## make the second plot |
|
1925 | 1x |
ggdata <- with(plotEffData, data.frame( |
1926 | 1x |
x = rep(dose, 3), |
1927 | 1x |
y = c(mean, lower, upper), |
1928 | 1x |
group = |
1929 | 1x |
rep(c("mean", "lower", "upper"), |
1930 | 1x |
each = nrow(plotEffData) |
1931 |
), |
|
1932 | 1x |
Type = |
1933 | 1x |
factor( |
1934 | 1x |
c( |
1935 | 1x |
rep( |
1936 | 1x |
"Estimate", |
1937 | 1x |
nrow(plotEffData) |
1938 |
), |
|
1939 | 1x |
rep( |
1940 | 1x |
"95% Credible Interval", |
1941 | 1x |
nrow(plotEffData) * 2 |
1942 |
) |
|
1943 |
), |
|
1944 | 1x |
levels = |
1945 | 1x |
c( |
1946 | 1x |
"Estimate", |
1947 | 1x |
"95% Credible Interval" |
1948 |
) |
|
1949 |
) |
|
1950 |
)) |
|
1951 | ||
1952 | 1x |
plot2 <- ggdata %>% ggplot() + |
1953 | 1x |
geom_line( |
1954 | 1x |
aes( |
1955 | 1x |
x = x, |
1956 | 1x |
y = y, |
1957 | 1x |
group = group, |
1958 | 1x |
linetype = Type |
1959 |
), |
|
1960 | 1x |
colour = I("blue"), |
1961 |
) + |
|
1962 | 1x |
labs( |
1963 | 1x |
x = "Dose level", |
1964 | 1x |
y = "Expected Efficacy" |
1965 |
) + |
|
1966 | 1x |
scale_linetype_manual( |
1967 | 1x |
breaks = |
1968 | 1x |
c( |
1969 | 1x |
"Estimate", |
1970 | 1x |
"95% Credible Interval" |
1971 |
), |
|
1972 | 1x |
values = c(1, 2), |
1973 | 1x |
guide = ifelse(showLegend, "legend", "none") |
1974 |
) |
|
1975 | ||
1976 |
## arrange both plots side by side |
|
1977 | 1x |
ret <- gridExtra::arrangeGrob(ret1, plot2, ncol = 2) |
1978 | 1x |
return(ret) |
1979 |
} |
|
1980 |
) |
|
1981 | ||
1982 |
## ------------------------------------------------------------------------------ |
|
1983 |
## Plot of the DLE and efficacy curve sides by side without samples |
|
1984 |
## ----------------------------------------------------------------------------- |
|
1985 |
#' Plot of the dose-DLE and dose-efficacy curve side by side given a DLE pseudo model |
|
1986 |
#' and a given pseudo efficacy model without DLE and efficacy samples |
|
1987 |
#' |
|
1988 |
#' @describeIn plotDualResponses Plot the DLE and efficacy curve side by side given a DLE model |
|
1989 |
#' and an efficacy model without any samples |
|
1990 |
#' |
|
1991 |
#' @example examples/Samples-method-plotDualResponsesNoSamples.R |
|
1992 |
#' |
|
1993 |
#' @export |
|
1994 |
#' @keywords methods |
|
1995 |
setMethod("plotDualResponses", |
|
1996 |
signature = |
|
1997 |
signature( |
|
1998 |
DLEmodel = "ModelTox", |
|
1999 |
DLEsamples = "missing", |
|
2000 |
Effmodel = "ModelEff", |
|
2001 |
Effsamples = "missing" |
|
2002 |
), |
|
2003 |
def = |
|
2004 |
function(DLEmodel, Effmodel, data, ...) { |
|
2005 |
## Get Toxicity plot |
|
2006 |
## get the fit |
|
2007 | ||
2008 | ||
2009 |
## Make sure the model estimates are corresponds to the input data |
|
2010 | 1x |
DLEmodel <- update(object = DLEmodel, data = data) |
2011 | 1x |
Effmodel <- update(object = Effmodel, data = data) |
2012 | ||
2013 | ||
2014 | 1x |
plotDLEData <- data.frame( |
2015 | 1x |
dose = data@doseGrid, |
2016 | 1x |
probDLE = prob( |
2017 | 1x |
dose = data@doseGrid, |
2018 | 1x |
model = DLEmodel |
2019 |
) |
|
2020 |
) |
|
2021 |
## make the plot |
|
2022 | 1x |
gdata <- with( |
2023 | 1x |
plotDLEData, |
2024 | 1x |
data.frame( |
2025 | 1x |
x = dose, |
2026 | 1x |
y = probDLE, |
2027 | 1x |
group = rep("Estimated DLE", each = nrow(plotDLEData)), |
2028 | 1x |
Type = factor(rep("Estimated DLE", nrow(plotDLEData)), levels = "Estimated DLE") |
2029 |
) |
|
2030 |
) |
|
2031 | ||
2032 | 1x |
plot1 <- ggplot(data = gdata, aes(x = x, y = y), group = group) + |
2033 | 1x |
xlab("Dose Levels") + |
2034 | 1x |
ylab(paste("Probability of DLE")) + |
2035 | 1x |
ylim(c(0, 1)) + |
2036 | 1x |
xlim(c(0, max(data@doseGrid))) + |
2037 | 1x |
geom_line(colour = I("red"), linewidth = 1.5) |
2038 | ||
2039 | ||
2040 | 1x |
plot1 <- plot1 + |
2041 | 1x |
geom_line(linewidth = 1.5, colour = "red") |
2042 | ||
2043 |
## only look at these dose levels for the plot: |
|
2044 | ||
2045 |
## get the plot data for the efficacy |
|
2046 | 1x |
plotEffData <- data.frame( |
2047 | 1x |
dose = data@doseGrid, |
2048 | 1x |
ExpEff = efficacy( |
2049 | 1x |
dose = data@doseGrid, |
2050 | 1x |
model = Effmodel |
2051 |
) |
|
2052 |
) |
|
2053 | ||
2054 |
## make the second plot |
|
2055 | 1x |
ggdata <- with( |
2056 | 1x |
plotEffData, |
2057 | 1x |
data.frame( |
2058 | 1x |
x = dose, |
2059 | 1x |
y = ExpEff, |
2060 | 1x |
group = rep("Estimated Expected Efficacy", each = nrow(plotEffData)), |
2061 | 1x |
Type = factor(rep("Estimated Expected Efficacy", nrow(plotEffData)), levels = "Estimated Expected Efficacy") |
2062 |
) |
|
2063 |
) |
|
2064 | ||
2065 |
## Get efficacy plot |
|
2066 | 1x |
plot2 <- ggplot(data = ggdata, aes(x = x, y = y), group = group) + |
2067 | 1x |
xlab("Dose Levels") + |
2068 | 1x |
ylab(paste("Estimatimated Expected Efficacy")) + |
2069 | 1x |
xlim(c(0, max(data@doseGrid))) + |
2070 | 1x |
geom_line(colour = I("blue"), linewidth = 1.5) |
2071 | ||
2072 | 1x |
plot2 <- plot2 + |
2073 | 1x |
geom_line(linewidth = 1.5, colour = "blue") |
2074 | ||
2075 |
## arrange both plots side by side |
|
2076 | 1x |
ret <- gridExtra::arrangeGrob(plot1, plot2, ncol = 2) |
2077 | 1x |
return(ret) |
2078 |
} |
|
2079 |
) |
|
2080 |
## ======================================================================================================= |
|
2081 | ||
2082 |
## ---------------------------------------------------------------- |
|
2083 |
## Get fitted DLT free survival (piecewise exponential model) based on |
|
2084 |
## the DA-CRM model |
|
2085 |
## ----------------------------------------------------------------- |
|
2086 |
#' Get the fitted DLT free survival (piecewise exponential model). |
|
2087 |
#' This function returns a data frame with dose, middle, lower and upper |
|
2088 |
#' quantiles for the `PEM` curve. If hazard=TRUE, |
|
2089 |
#' @param object mcmc samples |
|
2090 |
#' @param model the mDA-CRM model |
|
2091 |
#' @param data the data input, a \code{\linkS4class{DataDA}} class object |
|
2092 |
#' @param quantiles the quantiles to be calculated (default: 0.025 and |
|
2093 |
#' 0.975) |
|
2094 |
#' @param middle the function for computing the middle point. Default: |
|
2095 |
#' \code{\link{mean}} |
|
2096 |
#' @param hazard should the the hazard over time be plotted based on the `PEM`? (not default) |
|
2097 |
#' Otherwise ... |
|
2098 |
#' @param \dots additional arguments for methods |
|
2099 |
#' |
|
2100 |
#' @export |
|
2101 |
#' @keywords methods |
|
2102 |
setGeneric("fitPEM", |
|
2103 |
def = |
|
2104 |
function(object, |
|
2105 |
model, |
|
2106 |
data, |
|
2107 |
quantiles = c(0.025, 0.975), |
|
2108 |
middle = mean, |
|
2109 |
hazard = FALSE, |
|
2110 |
...) { |
|
2111 |
## there should be no default method, |
|
2112 |
## therefore just forward to next method! |
|
2113 | 6x |
standardGeneric("fitPEM") |
2114 |
}, |
|
2115 |
valueClass = "data.frame" |
|
2116 |
) |
|
2117 | ||
2118 | ||
2119 |
#' Likelihood of DLTs in each interval |
|
2120 |
#' |
|
2121 |
#' This is a helper function for the `fitPEM` methods below. |
|
2122 |
#' |
|
2123 |
#' @param lambda the vector of piecewise hazards |
|
2124 |
#' @param Tmax the end of the time interval for DLTs |
|
2125 |
#' @return vector with the probabilities for DLTs within the intervals. |
|
2126 |
#' |
|
2127 |
#' @keywords internal |
|
2128 |
DLTLikelihood <- function(lambda, |
|
2129 |
Tmax) { |
|
2130 | 6000x |
npiece <- length(lambda) |
2131 | 6000x |
h <- seq(from = 0L, to = Tmax, length = npiece + 1) |
2132 | ||
2133 |
# Length of each time interval; |
|
2134 | 6000x |
sT <- rep(0, npiece) |
2135 | ||
2136 | ||
2137 | 6000x |
for (i in 1:npiece) { |
2138 | 60000x |
sT[i] <- h[i + 1] - h[i] |
2139 |
} |
|
2140 | ||
2141 | ||
2142 |
# calculate the exponential part of the distribution: |
|
2143 | 6000x |
s_ij <- function(t, j) { |
2144 | 3600000x |
if (t > h[j]) { |
2145 | 3e+06x |
min(t - h[j], h[j + 1] - h[j]) |
2146 |
} else { |
|
2147 | 6e+05x |
0 |
2148 |
} |
|
2149 |
} |
|
2150 | ||
2151 | ||
2152 |
# The cumulative hazard function |
|
2153 | 6000x |
expNmu <- function(t) { |
2154 | 360000x |
ret <- 1 |
2155 | 360000x |
for (j in 1:npiece) { |
2156 | 3600000x |
ret <- ret * exp(-lambda[j] * s_ij(t, j)) |
2157 |
} |
|
2158 | 360000x |
return(ret) |
2159 |
} |
|
2160 | ||
2161 |
# CDF of the piecewise exponential |
|
2162 | 6000x |
piece_exp_cdf <- function(x) { |
2163 | 120000x |
1 - expNmu(x) |
2164 |
} |
|
2165 | ||
2166 | 6000x |
DLTFreeS <- function(x) { |
2167 | 120000x |
(expNmu(x) - expNmu(Tmax)) / piece_exp_cdf(Tmax) |
2168 |
} |
|
2169 | ||
2170 | 6000x |
pDLT <- rep(0, npiece + 1) |
2171 | ||
2172 | 6000x |
for (i in 1:(npiece)) { |
2173 | 60000x |
pDLT[i] <- DLTFreeS(h[i]) - DLTFreeS(h[i + 1]) |
2174 |
} |
|
2175 | ||
2176 | 6000x |
return(pDLT) |
2177 |
} |
|
2178 | ||
2179 |
## -------------------------------------------------------------------- |
|
2180 |
## Get fitted DLT free survival (piecewise exponential model) based on |
|
2181 |
## the DA-CRM model |
|
2182 |
## ------------------------------------------------------------- |
|
2183 |
#' @describeIn fitPEM This method works for the \code{\linkS4class{DALogisticLogNormal}} |
|
2184 |
#' model class. |
|
2185 |
#' @example examples/Samples-method-fitPEM-DALogisticLogNormal.R |
|
2186 |
setMethod("fitPEM", |
|
2187 |
signature = |
|
2188 |
signature( |
|
2189 |
object = "Samples", |
|
2190 |
model = "DALogisticLogNormal", |
|
2191 |
data = "DataDA" |
|
2192 |
), |
|
2193 |
def = |
|
2194 |
function(object, |
|
2195 |
model, |
|
2196 |
data, |
|
2197 |
quantiles = c(0.025, 0.975), |
|
2198 |
middle = mean, |
|
2199 |
hazard = FALSE, |
|
2200 |
...) { |
|
2201 |
## some checks |
|
2202 | 6x |
assert_probability_range(quantiles) |
2203 | 3x |
assert_logical(hazard) |
2204 |
## Plot points |
|
2205 | 2x |
points <- seq(0, data@Tmax, length = model@npiece + 1) |
2206 |
## first we have to get samples from the PEM |
|
2207 |
## at intercept points and 2 middel points between |
|
2208 |
## intercepts. |
|
2209 | 2x |
PEMSamples <- matrix( |
2210 | 2x |
nrow = size(object), |
2211 | 2x |
ncol = length(points) |
2212 |
) |
|
2213 | ||
2214 | 2x |
i_max <- max(seq_along(points)) |
2215 |
## evaluate the probs, for all samples. |
|
2216 | ||
2217 |
# The PEM |
|
2218 | 2x |
if (hazard == FALSE) { |
2219 | 2x |
PEMSamples <- t(apply(object@data$lambda, 1, function(x) { |
2220 | 6000x |
fit <- DLTLikelihood(x, data@Tmax) |
2221 | 6000x |
return(fit) |
2222 |
})) |
|
2223 | ! |
} else if (hazard == TRUE) { |
2224 | ! |
for (i in seq_along(points)) { |
2225 | ! |
if (i == i_max) { |
2226 | ! |
PEMSamples[, i_max] <- object@data$lambda[, model@npiece] |
2227 |
} else { |
|
2228 | ! |
PEMSamples[, i] <- object@data$lambda[, i] |
2229 |
} |
|
2230 |
} |
|
2231 |
} |
|
2232 | ||
2233 |
## extract middle curve |
|
2234 | 2x |
middleCurve <- apply(PEMSamples, 2L, FUN = middle) |
2235 | ||
2236 |
## extract quantiles |
|
2237 | 2x |
quantCurve <- apply(PEMSamples, 2L, quantile, |
2238 | 2x |
prob = quantiles |
2239 |
) |
|
2240 | ||
2241 |
## now create the data frame |
|
2242 | 2x |
ret <- data.frame( |
2243 | 2x |
time = points, |
2244 | 2x |
middle = middleCurve, |
2245 | 2x |
lower = quantCurve[1, ], |
2246 | 2x |
upper = quantCurve[2, ] |
2247 |
) |
|
2248 | ||
2249 |
## return it |
|
2250 | 2x |
return(ret) |
2251 |
} |
|
2252 |
) |
|
2253 | ||
2254 |
## ======================================================================================================= |
|
2255 | ||
2256 | ||
2257 |
## -------------------------------------------------- |
|
2258 |
## Plot survival curve fit over time |
|
2259 |
## -------------------------------------------------- |
|
2260 | ||
2261 |
## todo: add example file |
|
2262 |
#' Plotting dose-toxicity model fits |
|
2263 |
#' |
|
2264 |
#' @param x the \code{\linkS4class{Samples}} object |
|
2265 |
#' @param y the \code{\linkS4class{DALogisticLogNormal}} object |
|
2266 |
#' @param data the \code{\linkS4class{DataDA}} object |
|
2267 |
#' @param hazard see \code{\link{fitPEM}} for the explanation |
|
2268 |
#' @param \dots not used |
|
2269 |
#' @param showLegend should the legend be shown? (default) |
|
2270 |
#' @return This returns the \code{\link[ggplot2]{ggplot}} |
|
2271 |
#' object for the dose-toxicity model fit |
|
2272 |
#' |
|
2273 |
#' @export |
|
2274 |
setMethod("plot", |
|
2275 |
signature = |
|
2276 |
signature( |
|
2277 |
x = "Samples", |
|
2278 |
y = "DALogisticLogNormal" |
|
2279 |
), |
|
2280 |
def = |
|
2281 |
function(x, y, data, hazard = FALSE, ..., |
|
2282 |
showLegend = TRUE) { |
|
2283 |
## check args |
|
2284 | 3x |
assert_logical(showLegend) |
2285 | 2x |
assert_logical(hazard) |
2286 | ||
2287 |
## call the superclass method, to get the toxicity plot |
|
2288 | 1x |
plot1 <- callNextMethod(x, y, data, showLegend = showLegend, ...) |
2289 | ||
2290 |
## get the fit |
|
2291 | 1x |
fitData <- fitPEM(x, |
2292 | 1x |
model = y, |
2293 | 1x |
data = data, |
2294 | 1x |
quantiles = c(0.025, 0.975), |
2295 | 1x |
middle = mean, |
2296 | 1x |
hazard = hazard |
2297 |
) |
|
2298 | ||
2299 |
## make the plot |
|
2300 | 1x |
Tpoints <- seq(0, data@Tmax, length = y@npiece + 1) |
2301 | 1x |
plotData <- |
2302 | 1x |
with( |
2303 | 1x |
fitData, |
2304 | 1x |
data.frame( |
2305 | 1x |
x = rep(Tpoints, 3), |
2306 | 1x |
y = c(middle, lower, upper) * 100, |
2307 | 1x |
group = |
2308 | 1x |
rep(c("mean", "lower", "upper"), |
2309 | 1x |
each = nrow(fitData) |
2310 |
), |
|
2311 | 1x |
Type = |
2312 | 1x |
factor( |
2313 | 1x |
c( |
2314 | 1x |
rep( |
2315 | 1x |
"Estimate", |
2316 | 1x |
nrow(fitData) |
2317 |
), |
|
2318 | 1x |
rep( |
2319 | 1x |
"95% Credible Interval", |
2320 | 1x |
nrow(fitData) * 2 |
2321 |
) |
|
2322 |
), |
|
2323 | 1x |
levels = |
2324 | 1x |
c( |
2325 | 1x |
"Estimate", |
2326 | 1x |
"95% Credible Interval" |
2327 |
) |
|
2328 |
) |
|
2329 |
) |
|
2330 |
) |
|
2331 | 1x |
plot2 <- plotData %>% ggplot() + |
2332 | 1x |
geom_step( |
2333 | 1x |
aes( |
2334 | 1x |
x = x, |
2335 | 1x |
y = y, |
2336 | 1x |
group = group, |
2337 | 1x |
linetype = Type |
2338 |
), |
|
2339 | 1x |
colour = I("blue") |
2340 |
) + |
|
2341 | 1x |
labs( |
2342 | 1x |
x = "Time", |
2343 | 1x |
y = if (hazard) "Hazard rate*100" else "Probability of DLT [%]" |
2344 |
) + |
|
2345 | 1x |
coord_cartesian( |
2346 | 1x |
ylim = if (hazard) range(plotData$y) else c(0, 100) |
2347 |
) |
|
2348 | ||
2349 | 1x |
ret <- gridExtra::arrangeGrob(plot1, plot2, ncol = 2) |
2350 | 1x |
return(ret) |
2351 |
} |
|
2352 |
) |
|
2353 | ||
2354 | ||
2355 |
## ======================================================================================================= |
|
2356 | ||
2357 |
# tidy ---- |
|
2358 | ||
2359 |
## Samples |
|
2360 | ||
2361 |
## tidy-Samples ---- |
|
2362 | ||
2363 |
#' @rdname tidy |
|
2364 |
#' @aliases tidy-Samples |
|
2365 |
#' @example examples/Samples-method-tidy.R |
|
2366 |
#' @export |
|
2367 |
setMethod( |
|
2368 |
f = "tidy", |
|
2369 |
signature = signature(x = "Samples"), |
|
2370 |
definition = function(x, ...) { |
|
2371 | 3x |
rv <- lapply( |
2372 | 3x |
slotNames(x), |
2373 | 3x |
function(nm) { |
2374 | 6x |
if (nm == "data") { |
2375 | 3x |
lapply( |
2376 | 3x |
names(x@data), |
2377 | 3x |
function(nm) { |
2378 | 6x |
as_tibble(get(x, nm)) |
2379 |
} |
|
2380 |
) %>% |
|
2381 | 3x |
dplyr::bind_rows() %>% |
2382 | 3x |
tidyr::pivot_wider( |
2383 | 3x |
names_from = Parameter, |
2384 | 3x |
values_from = value |
2385 |
) %>% |
|
2386 | 3x |
dplyr::bind_cols(h_handle_attributes(get(x, names(x@data)[1]))) |
2387 |
} else { |
|
2388 | 3x |
slot(x, nm) %>% |
2389 | 3x |
tidy() %>% |
2390 | 3x |
dplyr::bind_cols() |
2391 |
} |
|
2392 |
} |
|
2393 |
) |
|
2394 | 3x |
names(rv) <- c("data", "options") |
2395 | 3x |
rv <- rv %>% h_tidy_class(x) |
2396 | 3x |
rv |
2397 |
} |
|
2398 |
) |
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(names(model_params), len = length(model_params), any.missing = FALSE, unique = TRUE) |
72 | ||
73 | 1x |
samples <- Samples( |
74 | 1x |
data = model_params, |
75 | 1x |
options = McmcOptions(samples = length(model_params[[1]])) |
76 |
) |
|
77 | 1x |
function(x) { |
78 | ! |
dose(x = x, model = model, samples = samples) |
79 |
} |
|
80 |
} |
|
81 |
) |
|
82 | ||
83 |
## LogisticLogNormalOrdinal ---- |
|
84 | ||
85 |
#' @describeIn doseFunction |
|
86 |
#' |
|
87 |
#' @param grade (`integer`)\cr the toxicity grade for which the dose function is |
|
88 |
#' required |
|
89 |
#' |
|
90 |
#' @aliases doseFunction-LogisticLogNormalOrdinal |
|
91 |
#' @example examples/Model-method-doseFunctionLogisticLogNormalOrdinal.R |
|
92 |
#' @export |
|
93 |
setMethod( |
|
94 |
f = "doseFunction", |
|
95 |
signature = "LogisticLogNormalOrdinal", |
|
96 |
definition = function(model, grade, ...) { |
|
97 | 8x |
model_params <- list(...) |
98 | 8x |
assert_character( |
99 | 8x |
names(model_params), |
100 | 8x |
len = length(model_params), |
101 | 8x |
any.missing = FALSE, |
102 | 8x |
unique = TRUE |
103 |
) |
|
104 | 6x |
assert_integer(grade, lower = 1, len = 1) |
105 | 4x |
coll <- makeAssertCollection() |
106 | 4x |
if (!(paste0("alpha", grade) %in% names(model_params))) { |
107 | 2x |
coll$push( |
108 | 2x |
paste0( |
109 | 2x |
"Since grade = ", grade, ", a parameter named 'alpha", grade, |
110 | 2x |
"' must appear the call" |
111 |
) |
|
112 |
) |
|
113 |
} |
|
114 | 4x |
reportAssertions(coll) |
115 |
# Create dummy intercept columns if necessary |
|
116 | 2x |
for (g in seq_along(grade)) { |
117 | 2x |
if (!(paste0("alpha", g) %in% names(model_params))) { |
118 | 1x |
model_params[[paste0("alpha", g)]] <- rep(0, length(model_params[[1]])) |
119 |
} |
|
120 |
} |
|
121 | ||
122 | 2x |
samples <- Samples( |
123 | 2x |
data = model_params, |
124 | 2x |
options = McmcOptions(samples = length(model_params[[1]])) |
125 |
) |
|
126 | 2x |
function(x) { |
127 | 38x |
dose(x = x, model = model, samples = samples, grade = grade) |
128 |
} |
|
129 |
} |
|
130 |
) |
|
131 | ||
132 |
# probFunction ---- |
|
133 | ||
134 |
## generic ---- |
|
135 | ||
136 |
#' Getting the Prob Function for a Given Model Type |
|
137 |
#' |
|
138 |
#' @description `r lifecycle::badge("experimental")` |
|
139 |
#' |
|
140 |
#' A function that returns a [prob()] function that computes the toxicity |
|
141 |
#' probabilities for a given dose level, based on the model specific parameters. |
|
142 |
#' |
|
143 |
#' @param model (`GeneralModel` or `ModelTox`)\cr the model. |
|
144 |
#' @param ... model specific parameters. |
|
145 |
#' |
|
146 |
#' @return A [prob()] function that computes toxicity probabilities. |
|
147 |
#' |
|
148 |
#' @seealso [prob()], [doseFunction()]. |
|
149 |
#' |
|
150 |
#' @export |
|
151 |
#' @example examples/Model-method-probFunction.R |
|
152 |
#' |
|
153 |
setGeneric( |
|
154 |
name = "probFunction", |
|
155 |
def = function(model, ...) { |
|
156 | 22x |
standardGeneric("probFunction") |
157 |
}, |
|
158 |
valueClass = "function" |
|
159 |
) |
|
160 | ||
161 |
## GeneralModel ---- |
|
162 | ||
163 |
#' @describeIn probFunction |
|
164 |
#' |
|
165 |
#' @aliases probFunction-GeneralModel |
|
166 |
#' @export |
|
167 |
#' |
|
168 |
setMethod( |
|
169 |
f = "probFunction", |
|
170 |
signature = "GeneralModel", |
|
171 |
definition = function(model, ...) { |
|
172 | 12x |
model_params <- list(...) |
173 | 12x |
assert_subset(names(model_params), model@sample, empty.ok = FALSE) |
174 | ||
175 | 10x |
samples <- Samples( |
176 | 10x |
data = model_params, |
177 | 10x |
options = McmcOptions(samples = NROW(model_params[[1]])) |
178 |
) |
|
179 | 10x |
function(dose, ...) { |
180 | 90x |
prob(dose = dose, model = model, samples = samples, ...) |
181 |
} |
|
182 |
} |
|
183 |
) |
|
184 | ||
185 |
## ModelTox ---- |
|
186 | ||
187 |
#' @describeIn probFunction |
|
188 |
#' |
|
189 |
#' @aliases probFunction-ModelTox |
|
190 |
#' @export |
|
191 |
#' |
|
192 |
setMethod( |
|
193 |
f = "probFunction", |
|
194 |
signature = "ModelTox", |
|
195 |
definition = function(model, ...) { |
|
196 | 8x |
model_params <- list(...) |
197 | 8x |
assert_character(names(model_params), len = length(model_params), any.missing = FALSE, unique = TRUE) |
198 | ||
199 | 7x |
samples <- Samples( |
200 | 7x |
data = model_params, |
201 | 7x |
options = McmcOptions(samples = length(model_params[[1]])) |
202 |
) |
|
203 | 7x |
function(dose) { |
204 | 42x |
prob(dose = dose, model = model, samples = samples) |
205 |
} |
|
206 |
} |
|
207 |
) |
|
208 | ||
209 |
## LogisticLogNormalOrdinal ---- |
|
210 | ||
211 |
#' @describeIn probFunction |
|
212 |
#' |
|
213 |
#' @param grade (`integer`)\cr the toxicity grade for which the dose function is |
|
214 |
#' required |
|
215 |
#' |
|
216 |
#' @aliases probFunction-LogisticLogNormalOrdinal |
|
217 |
#' @example examples/Model-method-probFunctionLogisticLogNormalOrdinal.R |
|
218 |
#' @export |
|
219 |
setMethod( |
|
220 |
f = "probFunction", |
|
221 |
signature = "LogisticLogNormalOrdinal", |
|
222 |
definition = function(model, grade, ...) { |
|
223 | 2x |
model_params <- list(...) |
224 | 2x |
assert_character( |
225 | 2x |
names(model_params), |
226 | 2x |
len = length(model_params), |
227 | 2x |
any.missing = FALSE, |
228 | 2x |
unique = TRUE |
229 |
) |
|
230 | 2x |
assert_integer(grade, lower = 1, len = 1) |
231 | 2x |
coll <- makeAssertCollection() |
232 | 2x |
if (!(paste0("alpha", grade) %in% names(model_params))) { |
233 | ! |
coll$push( |
234 | ! |
paste0( |
235 | ! |
"Since grade = ", grade, ", a parameter named 'alpha", grade, |
236 | ! |
"' must appear the call" |
237 |
) |
|
238 |
) |
|
239 |
} |
|
240 | 2x |
reportAssertions(coll) |
241 |
# Create dummy intercept columns if necessary |
|
242 | 2x |
for (g in seq_along(grade)) { |
243 | 2x |
if (!(paste0("alpha", g) %in% names(model_params))) { |
244 | 1x |
model_params[[paste0("alpha", g)]] <- rep(0, length(model_params[[1]])) |
245 |
} |
|
246 |
} |
|
247 | ||
248 | 2x |
samples <- Samples( |
249 | 2x |
data = model_params, |
250 | 2x |
options = McmcOptions(samples = length(model_params[[1]])) |
251 |
) |
|
252 | 2x |
function(dose) { |
253 | 20x |
prob(dose = dose, model = model, samples = samples, grade = grade) |
254 |
} |
|
255 |
} |
|
256 |
) |
|
257 | ||
258 | ||
259 |
# efficacyFunction ---- |
|
260 | ||
261 |
## generic ---- |
|
262 | ||
263 |
#' Getting the Efficacy Function for a Given Model Type |
|
264 |
#' |
|
265 |
#' @description `r lifecycle::badge("experimental")` |
|
266 |
#' |
|
267 |
#' A function that returns an [efficacy()] function that computes expected |
|
268 |
#' efficacy for a given dose level, based on the model specific parameters. |
|
269 |
#' |
|
270 |
#' @param model (`ModelEff`)\cr the model. |
|
271 |
#' @param ... model specific parameters. |
|
272 |
#' |
|
273 |
#' @return A [efficacy()] function that computes expected efficacy. |
|
274 |
#' |
|
275 |
#' @seealso [efficacy()]. |
|
276 |
#' |
|
277 |
#' @export |
|
278 |
#' @example examples/Model-method-efficacyFunction.R |
|
279 |
#' |
|
280 |
setGeneric( |
|
281 |
name = "efficacyFunction", |
|
282 |
def = function(model, ...) { |
|
283 | 5x |
standardGeneric("efficacyFunction") |
284 |
}, |
|
285 |
valueClass = "function" |
|
286 |
) |
|
287 | ||
288 |
## ModelEff ---- |
|
289 | ||
290 |
#' @describeIn efficacyFunction |
|
291 |
#' |
|
292 |
#' @aliases efficacyFunction-ModelEff |
|
293 |
#' @export |
|
294 |
#' |
|
295 |
setMethod( |
|
296 |
f = "efficacyFunction", |
|
297 |
signature = "ModelEff", |
|
298 |
definition = function(model, ...) { |
|
299 | 5x |
model_params <- list(...) |
300 | 5x |
assert_character(names(model_params), len = length(model_params), any.missing = FALSE, unique = TRUE) |
301 | ||
302 | 4x |
samples <- Samples( |
303 | 4x |
data = model_params, |
304 | 4x |
options = McmcOptions(samples = NROW(model_params[[1]])) |
305 |
) |
|
306 | 4x |
function(dose) { |
307 | 17x |
efficacy(dose = dose, model = model, samples = samples) |
308 |
} |
|
309 |
} |
|
310 |
) |
|
311 | ||
312 |
# dose ---- |
|
313 | ||
314 |
## generic ---- |
|
315 | ||
316 |
#' Computing the Doses for a given independent variable, Model and Samples |
|
317 |
#' |
|
318 |
#' @description `r lifecycle::badge("stable")` |
|
319 |
#' |
|
320 |
#' A function that computes the dose reaching a specific target value of a |
|
321 |
#' given variable that dose depends on. The meaning of this variable depends |
|
322 |
#' on the type of the model. For instance, for single agent dose escalation |
|
323 |
#' model or pseudo DLE (dose-limiting events)/toxicity model, this variable |
|
324 |
#' represents the a probability of the occurrence of a DLE. For efficacy models, |
|
325 |
#' it represents expected efficacy. |
|
326 |
#' The doses are computed based on the samples of the model parameters (samples). |
|
327 |
#' |
|
328 |
#' @details The `dose()` function computes the doses corresponding to a value of |
|
329 |
#' a given independent variable, using samples of the model parameter(s). |
|
330 |
#' If you work with multivariate model parameters, then assume that your model |
|
331 |
#' specific `dose()` method receives a samples matrix where the rows |
|
332 |
#' correspond to the sampling index, i.e. the layout is then |
|
333 |
#' `nSamples x dimParameter`. |
|
334 |
#' |
|
335 |
#' @note The [dose()] and [prob()] methods are the inverse of each other, for |
|
336 |
#' all [dose()] methods for which its first argument, i.e. a given independent |
|
337 |
#' variable that dose depends on, represents toxicity probability. |
|
338 |
#' |
|
339 |
#' @param x (`proportion` or `numeric`)\cr a value of an independent variable |
|
340 |
#' on which dose depends. |
|
341 |
#' The following recycling rule applies when `samples` is not missing: vectors |
|
342 |
#' of size 1 will be recycled to the size of the sample |
|
343 |
#' (i.e. `size(samples)`). Otherwise, `x` must have the same size |
|
344 |
#' as the sample. |
|
345 |
#' @param model (`GeneralModel` or `ModelPseudo`)\cr the model. |
|
346 |
#' @param samples (`Samples`)\cr the samples of model's parameters that will be |
|
347 |
#' used to compute the resulting doses. Can also be missing for some models. |
|
348 |
#' @param ... model specific parameters when `samples` are not used. |
|
349 |
#' |
|
350 |
#' @return A `number` or `numeric` vector with the doses. |
|
351 |
#' If non-scalar `samples` were used, then every element in the returned vector |
|
352 |
#' corresponds to one element of a sample. Hence, in this case, the output |
|
353 |
#' vector is of the same length as the sample vector. If scalar `samples` were |
|
354 |
#' used or no `samples` were used, e.g. for pseudo DLE/toxicity `model`, |
|
355 |
#' then the output is of the same length as the length of the `prob`. |
|
356 |
#' |
|
357 |
#' @seealso [doseFunction()], [prob()], [efficacy()]. |
|
358 |
#' |
|
359 |
#' @export |
|
360 |
#' @example examples/Model-method-dose.R |
|
361 |
#' |
|
362 |
setGeneric( |
|
363 |
name = "dose", |
|
364 |
def = function(x, model, samples, ...) { |
|
365 | 3815x |
standardGeneric("dose") |
366 |
}, |
|
367 |
valueClass = "numeric" |
|
368 |
) |
|
369 | ||
370 |
## LogisticNormal ---- |
|
371 | ||
372 |
#' @describeIn dose compute the dose level reaching a specific target |
|
373 |
#' probability of the occurrence of a DLE (`x`). |
|
374 |
#' |
|
375 |
#' @aliases dose-LogisticNormal |
|
376 |
#' @export |
|
377 |
#' |
|
378 |
setMethod( |
|
379 |
f = "dose", |
|
380 |
signature = signature( |
|
381 |
x = "numeric", |
|
382 |
model = "LogisticNormal", |
|
383 |
samples = "Samples" |
|
384 |
), |
|
385 |
definition = function(x, model, samples) { |
|
386 | 12x |
assert_probabilities(x) |
387 | 10x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
388 | 10x |
assert_length(x, len = size(samples)) |
389 | ||
390 | 9x |
alpha0 <- samples@data$alpha0 |
391 | 9x |
alpha1 <- samples@data$alpha1 |
392 | 9x |
ref_dose <- as.numeric(model@ref_dose) |
393 | 9x |
exp((logit(x) - alpha0) / alpha1) * ref_dose |
394 |
} |
|
395 |
) |
|
396 | ||
397 |
## LogisticLogNormal ---- |
|
398 | ||
399 |
#' @describeIn dose compute the dose level reaching a specific target |
|
400 |
#' probability of the occurrence of a DLE (`x`). |
|
401 |
#' |
|
402 |
#' @aliases dose-LogisticLogNormal |
|
403 |
#' @export |
|
404 |
#' |
|
405 |
setMethod( |
|
406 |
f = "dose", |
|
407 |
signature = signature( |
|
408 |
x = "numeric", |
|
409 |
model = "LogisticLogNormal", |
|
410 |
samples = "Samples" |
|
411 |
), |
|
412 |
definition = function(x, model, samples) { |
|
413 | 1522x |
assert_probabilities(x) |
414 | 1520x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
415 | 1520x |
assert_length(x, len = size(samples)) |
416 | ||
417 | 1519x |
alpha0 <- samples@data$alpha0 |
418 | 1519x |
alpha1 <- samples@data$alpha1 |
419 | 1519x |
ref_dose <- as.numeric(model@ref_dose) |
420 | 1519x |
exp((logit(x) - alpha0) / alpha1) * ref_dose |
421 |
} |
|
422 |
) |
|
423 | ||
424 |
## LogisticLogNormalOrdinal ---- |
|
425 | ||
426 |
#' @describeIn dose compute the dose level reaching a specific target |
|
427 |
#' probability of the occurrence of a DLE (`x`). |
|
428 |
#' |
|
429 |
#' In the case of a `LogisticLogNormalOrdinal` model, `dose` returns only the |
|
430 |
#' probability of toxicity at the given grade or higher |
|
431 |
#' |
|
432 |
#' @param grade (`integer`)\cr The toxicity grade for which probabilities are required |
|
433 |
#' |
|
434 |
#' @aliases dose-LogisticLogNormalOrdinal |
|
435 |
#' @example examples/Model-method-doseLogisticLogNormalOrdinal.R |
|
436 |
#' @export |
|
437 |
#' |
|
438 |
setMethod( |
|
439 |
f = "dose", |
|
440 |
signature = signature( |
|
441 |
x = "numeric", |
|
442 |
model = "LogisticLogNormalOrdinal", |
|
443 |
samples = "Samples" |
|
444 |
), |
|
445 |
definition = function(x, model, samples, grade) { |
|
446 | 97x |
assert_probabilities(x) |
447 | 95x |
assert_length(x, len = size(samples)) |
448 | 95x |
assert_integer(grade, len = 1, lower = 1, upper = (length(names(samples@data)) - 1)) |
449 | 92x |
a <- paste0("alpha", grade) |
450 | 92x |
assert_subset(c(a, "beta"), names(samples)) |
451 | ||
452 | 92x |
alpha <- samples@data[[a]] |
453 | 92x |
beta <- samples@data$beta |
454 | 92x |
ref_dose <- as.numeric(model@ref_dose) |
455 | 92x |
exp((logit(x) - alpha) / beta) * ref_dose |
456 |
} |
|
457 |
) |
|
458 | ||
459 |
## LogisticLogNormalSub ---- |
|
460 | ||
461 |
#' @describeIn dose compute the dose level reaching a specific target |
|
462 |
#' probability of the occurrence of a DLE (`x`). |
|
463 |
#' |
|
464 |
#' @aliases dose-LogisticLogNormalSub |
|
465 |
#' @export |
|
466 |
#' |
|
467 |
setMethod( |
|
468 |
f = "dose", |
|
469 |
signature = signature( |
|
470 |
x = "numeric", |
|
471 |
model = "LogisticLogNormalSub", |
|
472 |
samples = "Samples" |
|
473 |
), |
|
474 |
definition = function(x, model, samples) { |
|
475 | 6x |
assert_probabilities(x) |
476 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
477 | 4x |
assert_length(x, len = size(samples)) |
478 | ||
479 | 3x |
alpha0 <- samples@data$alpha0 |
480 | 3x |
alpha1 <- samples@data$alpha1 |
481 | 3x |
ref_dose <- model@ref_dose |
482 | 3x |
((logit(x) - alpha0) / alpha1) + ref_dose |
483 |
} |
|
484 |
) |
|
485 | ||
486 |
## ProbitLogNormal ---- |
|
487 | ||
488 |
#' @describeIn dose compute the dose level reaching a specific target |
|
489 |
#' probability of the occurrence of a DLE (`x`). |
|
490 |
#' |
|
491 |
#' @aliases dose-ProbitLogNormal |
|
492 |
#' @export |
|
493 |
#' |
|
494 |
setMethod( |
|
495 |
f = "dose", |
|
496 |
signature = signature( |
|
497 |
x = "numeric", |
|
498 |
model = "ProbitLogNormal", |
|
499 |
samples = "Samples" |
|
500 |
), |
|
501 |
definition = function(x, model, samples) { |
|
502 | 6x |
assert_probabilities(x) |
503 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
504 | 4x |
assert_length(x, len = size(samples)) |
505 | ||
506 | 3x |
alpha0 <- samples@data$alpha0 |
507 | 3x |
alpha1 <- samples@data$alpha1 |
508 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
509 | 3x |
exp((probit(x) - alpha0) / alpha1) * ref_dose |
510 |
} |
|
511 |
) |
|
512 | ||
513 |
## ProbitLogNormalRel ---- |
|
514 | ||
515 |
#' @describeIn dose compute the dose level reaching a specific target |
|
516 |
#' probability of the occurrence of a DLE (`x`). |
|
517 |
#' |
|
518 |
#' @aliases dose-ProbitLogNormalRel |
|
519 |
#' @export |
|
520 |
#' |
|
521 |
setMethod( |
|
522 |
f = "dose", |
|
523 |
signature = signature( |
|
524 |
x = "numeric", |
|
525 |
model = "ProbitLogNormalRel", |
|
526 |
samples = "Samples" |
|
527 |
), |
|
528 |
definition = function(x, model, samples) { |
|
529 | 6x |
assert_probabilities(x) |
530 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
531 | 4x |
assert_length(x, len = size(samples)) |
532 | ||
533 | 3x |
alpha0 <- samples@data$alpha0 |
534 | 3x |
alpha1 <- samples@data$alpha1 |
535 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
536 | 3x |
((probit(x) - alpha0) / alpha1) * ref_dose |
537 |
} |
|
538 |
) |
|
539 | ||
540 |
## LogisticLogNormalGrouped ---- |
|
541 | ||
542 |
#' @describeIn dose method for [`LogisticLogNormalGrouped`] which needs `group` |
|
543 |
#' argument in addition. |
|
544 |
#' @param group (`character` or `factor`)\cr for [`LogisticLogNormalGrouped`], |
|
545 |
#' indicating whether to calculate the dose for the `mono` or for |
|
546 |
#' the `combo` arm. |
|
547 |
#' @aliases dose-LogisticLogNormalGrouped |
|
548 |
#' @export |
|
549 |
#' |
|
550 |
setMethod( |
|
551 |
f = "dose", |
|
552 |
signature = signature( |
|
553 |
x = "numeric", |
|
554 |
model = "LogisticLogNormalGrouped", |
|
555 |
samples = "Samples" |
|
556 |
), |
|
557 |
definition = function(x, model, samples, group) { |
|
558 | 6x |
assert_probabilities(x) |
559 | 6x |
assert_subset(c("alpha0", "delta0", "alpha1", "delta1"), names(samples)) |
560 | 6x |
assert_length(x, len = size(samples)) |
561 | 6x |
assert_multi_class(group, c("character", "factor")) |
562 | 5x |
assert_subset(as.character(group), choices = c("mono", "combo")) |
563 | 5x |
assert_length(group, len = size(samples)) |
564 | ||
565 | 5x |
alpha0 <- samples@data$alpha0 |
566 | 5x |
delta0 <- samples@data$delta0 |
567 | 5x |
alpha1 <- samples@data$alpha1 |
568 | 5x |
delta1 <- samples@data$delta1 |
569 | 5x |
ref_dose <- as.numeric(model@ref_dose) |
570 | 5x |
is_combo <- as.integer(group == "combo") |
571 | 5x |
exp((logit(x) - (alpha0 + is_combo * delta0)) / (alpha1 + is_combo * delta1)) * ref_dose |
572 |
} |
|
573 |
) |
|
574 | ||
575 |
## LogisticKadane ---- |
|
576 | ||
577 |
#' @describeIn dose compute the dose level reaching a specific target |
|
578 |
#' probability of the occurrence of a DLE (`x`). |
|
579 |
#' |
|
580 |
#' @aliases dose-LogisticKadane |
|
581 |
#' @export |
|
582 |
#' |
|
583 |
setMethod( |
|
584 |
f = "dose", |
|
585 |
signature = signature( |
|
586 |
x = "numeric", |
|
587 |
model = "LogisticKadane", |
|
588 |
samples = "Samples" |
|
589 |
), |
|
590 |
definition = function(x, model, samples) { |
|
591 | 10x |
assert_probabilities(x) |
592 | 8x |
assert_subset(c("rho0", "gamma"), names(samples)) |
593 | 8x |
assert_length(x, len = size(samples)) |
594 | ||
595 | 7x |
rho0 <- samples@data$rho0 |
596 | 7x |
gamma <- samples@data$gamma |
597 | 7x |
theta <- model@theta |
598 | 7x |
xmin <- model@xmin |
599 | 7x |
num <- gamma * (logit(x) - logit(rho0)) + xmin * (logit(theta) - logit(x)) |
600 | 7x |
num / (logit(theta) - logit(rho0)) |
601 |
} |
|
602 |
) |
|
603 | ||
604 |
## LogisticKadaneBetaGamma ---- |
|
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-LogisticKadaneBetaGamma |
|
610 |
#' @export |
|
611 |
#' |
|
612 |
setMethod( |
|
613 |
f = "dose", |
|
614 |
signature = signature( |
|
615 |
x = "numeric", |
|
616 |
model = "LogisticKadaneBetaGamma", |
|
617 |
samples = "Samples" |
|
618 |
), |
|
619 |
definition = function(x, model, samples) { |
|
620 | 6x |
assert_probabilities(x) |
621 | 4x |
assert_subset(c("rho0", "gamma"), names(samples)) |
622 | 4x |
assert_length(x, len = size(samples)) |
623 | ||
624 | 3x |
rho0 <- samples@data$rho0 |
625 | 3x |
gamma <- samples@data$gamma |
626 | 3x |
theta <- model@theta |
627 | 3x |
xmin <- model@xmin |
628 | 3x |
num <- gamma * (logit(x) - logit(rho0)) + xmin * (logit(theta) - logit(x)) |
629 | 3x |
num / (logit(theta) - logit(rho0)) |
630 |
} |
|
631 |
) |
|
632 | ||
633 |
## LogisticNormalMixture ---- |
|
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-LogisticNormalMixture |
|
639 |
#' @export |
|
640 |
#' |
|
641 |
setMethod( |
|
642 |
f = "dose", |
|
643 |
signature = signature( |
|
644 |
x = "numeric", |
|
645 |
model = "LogisticNormalMixture", |
|
646 |
samples = "Samples" |
|
647 |
), |
|
648 |
definition = function(x, model, samples) { |
|
649 | 6x |
assert_probabilities(x) |
650 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
651 | 4x |
assert_length(x, len = size(samples)) |
652 | ||
653 | 3x |
alpha0 <- samples@data$alpha0 |
654 | 3x |
alpha1 <- samples@data$alpha1 |
655 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
656 | 3x |
exp((logit(x) - alpha0) / alpha1) * ref_dose |
657 |
} |
|
658 |
) |
|
659 | ||
660 |
## LogisticNormalFixedMixture ---- |
|
661 | ||
662 |
#' @describeIn dose compute the dose level reaching a specific target |
|
663 |
#' probability of the occurrence of a DLE (`x`). |
|
664 |
#' |
|
665 |
#' @aliases dose-LogisticNormalFixedMixture |
|
666 |
#' @export |
|
667 |
#' |
|
668 |
setMethod( |
|
669 |
f = "dose", |
|
670 |
signature = signature( |
|
671 |
x = "numeric", |
|
672 |
model = "LogisticNormalFixedMixture", |
|
673 |
samples = "Samples" |
|
674 |
), |
|
675 |
definition = function(x, model, samples) { |
|
676 | 6x |
assert_probabilities(x) |
677 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
678 | 4x |
assert_length(x, len = size(samples)) |
679 | ||
680 | 3x |
alpha0 <- samples@data$alpha0 |
681 | 3x |
alpha1 <- samples@data$alpha1 |
682 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
683 | 3x |
exp((logit(x) - alpha0) / alpha1) * ref_dose |
684 |
} |
|
685 |
) |
|
686 | ||
687 |
## LogisticLogNormalMixture ---- |
|
688 | ||
689 |
#' @describeIn dose compute the dose level reaching a specific target |
|
690 |
#' probability of the occurrence of a DLE (`x`). |
|
691 |
#' |
|
692 |
#' @aliases dose-LogisticLogNormalMixture |
|
693 |
#' @export |
|
694 |
#' |
|
695 |
setMethod( |
|
696 |
f = "dose", |
|
697 |
signature = signature( |
|
698 |
x = "numeric", |
|
699 |
model = "LogisticLogNormalMixture", |
|
700 |
samples = "Samples" |
|
701 |
), |
|
702 |
definition = function(x, model, samples) { |
|
703 | 1x |
stop("not implemented") |
704 |
} |
|
705 |
) |
|
706 | ||
707 |
## DualEndpoint ---- |
|
708 | ||
709 |
#' @describeIn dose compute the dose level reaching a specific target |
|
710 |
#' probability of the occurrence of a DLE (`x`). |
|
711 |
#' |
|
712 |
#' @aliases dose-DualEndpoint |
|
713 |
#' @export |
|
714 |
#' |
|
715 |
setMethod( |
|
716 |
f = "dose", |
|
717 |
signature = signature( |
|
718 |
x = "numeric", |
|
719 |
model = "DualEndpoint", |
|
720 |
samples = "Samples" |
|
721 |
), |
|
722 |
definition = function(x, model, samples) { |
|
723 | 10x |
assert_probabilities(x) |
724 | 8x |
assert_subset("betaZ", names(samples)) |
725 | 8x |
assert_length(x, len = size(samples)) |
726 | ||
727 | 7x |
betaZ <- samples@data$betaZ |
728 | 7x |
ref_dose <- as.numeric(model@ref_dose) |
729 | 7x |
dose_temp <- (qnorm(x) - betaZ[, 1]) / betaZ[, 2] |
730 | 7x |
if (model@use_log_dose) { |
731 | 4x |
exp(dose_temp) * ref_dose |
732 |
} else { |
|
733 | 3x |
dose_temp * ref_dose |
734 |
} |
|
735 |
} |
|
736 |
) |
|
737 | ||
738 |
## LogisticIndepBeta ---- |
|
739 | ||
740 |
#' @describeIn dose compute the dose level reaching a specific target |
|
741 |
#' probability of the occurrence of a DLE (`x`). |
|
742 |
#' |
|
743 |
#' @aliases dose-LogisticIndepBeta |
|
744 |
#' @export |
|
745 |
#' |
|
746 |
setMethod( |
|
747 |
f = "dose", |
|
748 |
signature = signature( |
|
749 |
x = "numeric", |
|
750 |
model = "LogisticIndepBeta", |
|
751 |
samples = "Samples" |
|
752 |
), |
|
753 |
definition = function(x, model, samples) { |
|
754 | 1559x |
assert_probabilities(x) |
755 | 1557x |
assert_subset(c("phi1", "phi2"), names(samples)) |
756 | 1557x |
assert_length(x, len = size(samples)) |
757 | ||
758 | 1556x |
phi1 <- samples@data$phi1 |
759 | 1556x |
phi2 <- samples@data$phi2 |
760 | 1556x |
log_dose <- (log(x / (1 - x)) - phi1) / phi2 |
761 | 1556x |
exp(log_dose) |
762 |
} |
|
763 |
) |
|
764 | ||
765 |
## LogisticIndepBeta-noSamples ---- |
|
766 | ||
767 |
#' @describeIn dose compute the dose level reaching a specific target |
|
768 |
#' probability of the occurrence of a DLE (`x`). |
|
769 |
#' All model parameters (except `x`) should be present in the `model` object. |
|
770 |
#' |
|
771 |
#' @aliases dose-LogisticIndepBeta-noSamples |
|
772 |
#' @export |
|
773 |
#' |
|
774 |
setMethod( |
|
775 |
f = "dose", |
|
776 |
signature = signature( |
|
777 |
x = "numeric", |
|
778 |
model = "LogisticIndepBeta", |
|
779 |
samples = "missing" |
|
780 |
), |
|
781 |
definition = function(x, model) { |
|
782 | 548x |
assert_probabilities(x) |
783 | 547x |
model_params <- h_slots(model, c("phi1", "phi2")) |
784 | 547x |
nsamples <- length(model_params[[1]]) |
785 | 547x |
samples <- Samples(data = model_params, options = McmcOptions(samples = nsamples)) |
786 | 547x |
assert_length(x, len = nsamples) |
787 | ||
788 | 547x |
dose(x, model, samples) |
789 |
} |
|
790 |
) |
|
791 | ||
792 |
## Effloglog-noSamples ---- |
|
793 | ||
794 |
#' @describeIn dose compute the dose level reaching a specific target |
|
795 |
#' probability of the occurrence of a DLE (`x`). |
|
796 |
#' All model parameters (except `x`) should be present in the `model` object. |
|
797 |
#' |
|
798 |
#' @aliases dose-Effloglog-noSamples |
|
799 |
#' @export |
|
800 |
#' |
|
801 |
setMethod( |
|
802 |
f = "dose", |
|
803 |
signature = signature( |
|
804 |
x = "numeric", |
|
805 |
model = "Effloglog", |
|
806 |
samples = "missing" |
|
807 |
), |
|
808 |
definition = function(x, model) { |
|
809 | ! |
assert_numeric(x, min.len = 1L, any.missing = FALSE, finite = TRUE) |
810 | ! |
theta1 <- model@theta1 |
811 | ! |
theta2 <- model@theta2 |
812 | ! |
constant <- model@const |
813 | ! |
assert_scalar(theta1) |
814 | ! |
assert_scalar(theta2) |
815 | ! |
assert_scalar(constant) |
816 | ||
817 | ! |
exp(exp((x - theta1) / theta2)) - constant |
818 |
} |
|
819 |
) |
|
820 | ||
821 |
## EffFlexi ---- |
|
822 | ||
823 |
#' @describeIn dose compute the dose level reaching a specific target |
|
824 |
#' probability of the occurrence of a DLE (`x`). For this method `x` must |
|
825 |
#' be a scalar. |
|
826 |
#' |
|
827 |
#' @aliases dose-EffFlexi |
|
828 |
#' @export |
|
829 |
#' |
|
830 |
setMethod( |
|
831 |
f = "dose", |
|
832 |
signature = signature( |
|
833 |
x = "numeric", |
|
834 |
model = "EffFlexi", |
|
835 |
samples = "Samples" |
|
836 |
), |
|
837 |
definition = function(x, model, samples) { |
|
838 | ! |
assert_number(x) |
839 | ! |
assert_subset("ExpEff", names(samples)) |
840 | ||
841 | ! |
samples_efficacy <- samples@data$ExpEff |
842 | ! |
dose_grid <- model@data@doseGrid |
843 | ||
844 |
# Find dose level for a given expected efficacy level using linear interpolation. |
|
845 | ! |
apply(samples_efficacy, 1, function(se) { |
846 | ! |
se_leq_x <- se <= x |
847 | ! |
dose_level0 <- max(which(se_leq_x)) |
848 | ! |
dose_level1 <- min(which(!se_leq_x)) |
849 | ! |
eff0 <- se[dose_level0] |
850 | ! |
eff1 <- se[dose_level1] |
851 | ! |
dose0 <- dose_grid[dose_level0] |
852 | ! |
dose1 <- dose_grid[dose_level1] |
853 | ! |
dose0 + (dose1 - dose0) * ((x - eff0) / (eff1 - eff0)) |
854 |
}) |
|
855 |
} |
|
856 |
) |
|
857 | ||
858 |
## OneParLogNormalPrior ---- |
|
859 | ||
860 |
#' @describeIn dose compute the dose level reaching a specific target |
|
861 |
#' probability of the occurrence of a DLT (`x`). |
|
862 |
#' |
|
863 |
#' @aliases dose-OneParLogNormalPrior |
|
864 |
#' @export |
|
865 |
#' |
|
866 |
setMethod( |
|
867 |
f = "dose", |
|
868 |
signature = signature( |
|
869 |
x = "numeric", |
|
870 |
model = "OneParLogNormalPrior", |
|
871 |
samples = "Samples" |
|
872 |
), |
|
873 |
definition = function(x, model, samples) { |
|
874 | 7x |
assert_probabilities(x) |
875 | 5x |
assert_subset("alpha", names(samples)) |
876 | 5x |
assert_length(x, len = size(samples)) |
877 | ||
878 | 4x |
alpha <- samples@data$alpha |
879 | 4x |
skel_fun_inv <- model@skel_fun_inv |
880 | 4x |
skel_fun_inv(x^(1 / exp(alpha))) |
881 |
} |
|
882 |
) |
|
883 | ||
884 |
## OneParExpPrior ---- |
|
885 | ||
886 |
#' @describeIn dose compute the dose level reaching a specific target |
|
887 |
#' probability of the occurrence of a DLT (`x`). |
|
888 |
#' |
|
889 |
#' @aliases dose-OneParExpPrior |
|
890 |
#' @export |
|
891 |
#' |
|
892 |
setMethod( |
|
893 |
f = "dose", |
|
894 |
signature = signature( |
|
895 |
x = "numeric", |
|
896 |
model = "OneParExpPrior", |
|
897 |
samples = "Samples" |
|
898 |
), |
|
899 |
definition = function(x, model, samples) { |
|
900 | 7x |
assert_probabilities(x) |
901 | 5x |
assert_subset("theta", names(samples)) |
902 | 5x |
assert_length(x, len = size(samples)) |
903 | ||
904 | 4x |
theta <- samples@data$theta |
905 | 4x |
skel_fun_inv <- model@skel_fun_inv |
906 | 4x |
assert_numeric(theta, lower = .Machine$double.xmin, finite = TRUE) |
907 | 4x |
skel_fun_inv(x^(1 / theta)) |
908 |
} |
|
909 |
) |
|
910 | ||
911 |
# prob ---- |
|
912 | ||
913 |
## generic ---- |
|
914 | ||
915 |
#' Computing Toxicity Probabilities for a Given Dose, Model and Samples |
|
916 |
#' |
|
917 |
#' @description `r lifecycle::badge("stable")` |
|
918 |
#' |
|
919 |
#' A function that computes the probability of the occurrence of a DLE at a |
|
920 |
#' specified dose level, based on the model parameters (samples). |
|
921 |
#' |
|
922 |
#' @details The `prob()` function computes the probability of toxicity for given |
|
923 |
#' doses, using samples of the model parameter(s). |
|
924 |
#' If you work with multivariate model parameters, then assume that your model |
|
925 |
#' specific `prob()` method receives a samples matrix where the rows |
|
926 |
#' correspond to the sampling index, i.e. the layout is then |
|
927 |
#' `nSamples x dimParameter`. |
|
928 |
#' |
|
929 |
#' @note The [prob()] and [dose()] functions are the inverse of |
|
930 |
#' each other, for all [dose()] methods for which its first argument, i.e. a |
|
931 |
#' given independent variable that dose depends on, represents toxicity |
|
932 |
#' probability. |
|
933 |
#' |
|
934 |
#' @param dose (`number` or `numeric`)\cr the dose which is targeted. |
|
935 |
#' The following recycling rule applies when `samples` is not missing: vectors |
|
936 |
#' of size 1 will be recycled to the size of the sample |
|
937 |
#' (i.e. `size(samples)`). Otherwise, `dose` must have the same |
|
938 |
#' size as the sample. |
|
939 |
#' @param model (`GeneralModel` or `ModelTox`)\cr the model for single agent |
|
940 |
#' dose escalation or pseudo DLE (dose-limiting events)/toxicity model. |
|
941 |
#' @param samples (`Samples`)\cr the samples of model's parameters that will be |
|
942 |
#' used to compute toxicity probabilities. Can also be missing for some models. |
|
943 |
#' @param ... model specific parameters when `samples` are not used. |
|
944 |
#' |
|
945 |
#' @return A `proportion` or `numeric` vector with the toxicity probabilities. |
|
946 |
#' If non-scalar `samples` were used, then every element in the returned vector |
|
947 |
#' corresponds to one element of a sample. Hence, in this case, the output |
|
948 |
#' vector is of the same length as the sample vector. If scalar `samples` were |
|
949 |
#' used or no `samples` were used, e.g. for pseudo DLE/toxicity `model`, |
|
950 |
#' then the output is of the same length as the length of the `dose`. In the |
|
951 |
#' case of `LogisticLogNormalOrdinal`, the probabilities relate to toxicities |
|
952 |
#' of grade given by `grade`. |
|
953 |
#' |
|
954 |
#' @seealso [probFunction()], [dose()], [efficacy()]. |
|
955 |
#' |
|
956 |
#' @export |
|
957 |
#' @example examples/Model-method-prob.R |
|
958 |
#' |
|
959 |
setGeneric( |
|
960 |
name = "prob", |
|
961 |
def = function(dose, model, samples, ...) { |
|
962 | 20351x |
standardGeneric("prob") |
963 |
}, |
|
964 |
valueClass = c("numeric", "list") |
|
965 |
) |
|
966 | ||
967 |
## LogisticNormal ---- |
|
968 | ||
969 |
#' @describeIn prob |
|
970 |
#' |
|
971 |
#' @aliases prob-LogisticNormal |
|
972 |
#' @export |
|
973 |
#' |
|
974 |
setMethod( |
|
975 |
f = "prob", |
|
976 |
signature = signature( |
|
977 |
dose = "numeric", |
|
978 |
model = "LogisticNormal", |
|
979 |
samples = "Samples" |
|
980 |
), |
|
981 |
definition = function(dose, model, samples, ...) { |
|
982 | 244x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1) |
983 | 243x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
984 | 243x |
assert_length(dose, len = size(samples)) |
985 | ||
986 | 242x |
alpha0 <- samples@data$alpha0 |
987 | 242x |
alpha1 <- samples@data$alpha1 |
988 | 242x |
ref_dose <- as.numeric(model@ref_dose) |
989 | 242x |
plogis(alpha0 + alpha1 * log(dose / ref_dose)) |
990 |
} |
|
991 |
) |
|
992 | ||
993 |
## LogisticLogNormal ---- |
|
994 | ||
995 |
#' @describeIn prob |
|
996 |
#' |
|
997 |
#' @aliases prob-LogisticLogNormal |
|
998 |
#' @export |
|
999 |
#' |
|
1000 |
setMethod( |
|
1001 |
f = "prob", |
|
1002 |
signature = signature( |
|
1003 |
dose = "numeric", |
|
1004 |
model = "LogisticLogNormal", |
|
1005 |
samples = "Samples" |
|
1006 |
), |
|
1007 |
definition = function(dose, model, samples, ...) { |
|
1008 | 2966x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1009 | 2965x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
1010 | 2965x |
assert_length(dose, len = size(samples)) |
1011 | ||
1012 | 2964x |
alpha0 <- samples@data$alpha0 |
1013 | 2964x |
alpha1 <- samples@data$alpha1 |
1014 | 2964x |
ref_dose <- as.numeric(model@ref_dose) |
1015 | 2964x |
plogis(alpha0 + alpha1 * log(dose / ref_dose)) |
1016 |
} |
|
1017 |
) |
|
1018 | ||
1019 |
## LogisticLogNormalSub ---- |
|
1020 | ||
1021 |
#' @describeIn prob |
|
1022 |
#' |
|
1023 |
#' @aliases prob-LogisticLogNormalSub |
|
1024 |
#' @export |
|
1025 |
#' |
|
1026 |
setMethod( |
|
1027 |
f = "prob", |
|
1028 |
signature = signature( |
|
1029 |
dose = "numeric", |
|
1030 |
model = "LogisticLogNormalSub", |
|
1031 |
samples = "Samples" |
|
1032 |
), |
|
1033 |
definition = function(dose, model, samples, ...) { |
|
1034 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1035 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
1036 | 4x |
assert_length(dose, len = size(samples)) |
1037 | ||
1038 | 3x |
alpha0 <- samples@data$alpha0 |
1039 | 3x |
alpha1 <- samples@data$alpha1 |
1040 | 3x |
ref_dose <- model@ref_dose |
1041 | 3x |
plogis(alpha0 + alpha1 * (dose - ref_dose)) |
1042 |
} |
|
1043 |
) |
|
1044 | ||
1045 |
## ProbitLogNormal ---- |
|
1046 | ||
1047 |
#' @describeIn prob |
|
1048 |
#' |
|
1049 |
#' @aliases prob-ProbitLogNormal |
|
1050 |
#' @export |
|
1051 |
#' |
|
1052 |
setMethod( |
|
1053 |
f = "prob", |
|
1054 |
signature = signature( |
|
1055 |
dose = "numeric", |
|
1056 |
model = "ProbitLogNormal", |
|
1057 |
samples = "Samples" |
|
1058 |
), |
|
1059 |
definition = function(dose, model, samples, ...) { |
|
1060 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1061 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
1062 | 4x |
assert_length(dose, len = size(samples)) |
1063 | ||
1064 | 3x |
alpha0 <- samples@data$alpha0 |
1065 | 3x |
alpha1 <- samples@data$alpha1 |
1066 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
1067 | 3x |
pnorm(alpha0 + alpha1 * log(dose / ref_dose)) |
1068 |
} |
|
1069 |
) |
|
1070 | ||
1071 |
## ProbitLogNormalRel ---- |
|
1072 | ||
1073 |
#' @describeIn prob |
|
1074 |
#' |
|
1075 |
#' @aliases prob-ProbitLogNormalRel |
|
1076 |
#' @export |
|
1077 |
#' |
|
1078 |
setMethod( |
|
1079 |
f = "prob", |
|
1080 |
signature = signature( |
|
1081 |
dose = "numeric", |
|
1082 |
model = "ProbitLogNormalRel", |
|
1083 |
samples = "Samples" |
|
1084 |
), |
|
1085 |
definition = function(dose, model, samples, ...) { |
|
1086 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1087 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
1088 | 4x |
assert_length(dose, len = size(samples)) |
1089 | ||
1090 | 3x |
alpha0 <- samples@data$alpha0 |
1091 | 3x |
alpha1 <- samples@data$alpha1 |
1092 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
1093 | 3x |
pnorm(alpha0 + alpha1 * (dose / ref_dose)) |
1094 |
} |
|
1095 |
) |
|
1096 | ||
1097 |
## LogisticLogNormalGrouped ---- |
|
1098 | ||
1099 |
#' @describeIn prob method for [`LogisticLogNormalGrouped`] which needs `group` |
|
1100 |
#' argument in addition. |
|
1101 |
#' @param group (`character` or `factor`)\cr for [`LogisticLogNormalGrouped`], |
|
1102 |
#' indicating whether to calculate the probability for the `mono` or for |
|
1103 |
#' the `combo` arm. |
|
1104 |
#' @aliases prob-LogisticLogNormalGrouped |
|
1105 |
#' @export |
|
1106 |
#' |
|
1107 |
setMethod( |
|
1108 |
f = "prob", |
|
1109 |
signature = signature( |
|
1110 |
dose = "numeric", |
|
1111 |
model = "LogisticLogNormalGrouped", |
|
1112 |
samples = "Samples" |
|
1113 |
), |
|
1114 |
definition = function(dose, model, samples, group, ...) { |
|
1115 | 15132x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1116 | 15132x |
assert_subset(c("alpha0", "delta0", "alpha1", "delta1"), names(samples)) |
1117 | 15132x |
assert_length(dose, len = size(samples)) |
1118 | 15132x |
assert_multi_class(group, c("character", "factor")) |
1119 | 15131x |
assert_subset(as.character(group), choices = c("mono", "combo")) |
1120 | 15131x |
assert_length(group, len = size(samples)) |
1121 | ||
1122 | 15131x |
alpha0 <- samples@data$alpha0 |
1123 | 15131x |
delta0 <- samples@data$delta0 |
1124 | 15131x |
alpha1 <- samples@data$alpha1 |
1125 | 15131x |
delta1 <- samples@data$delta1 |
1126 | 15131x |
ref_dose <- as.numeric(model@ref_dose) |
1127 | 15131x |
is_combo <- as.integer(group == "combo") |
1128 | 15131x |
plogis((alpha0 + is_combo * delta0) + (alpha1 + is_combo * delta1) * log(dose / ref_dose)) |
1129 |
} |
|
1130 |
) |
|
1131 | ||
1132 |
## LogisticKadane ---- |
|
1133 | ||
1134 |
#' @describeIn prob |
|
1135 |
#' |
|
1136 |
#' @aliases prob-LogisticKadane |
|
1137 |
#' @export |
|
1138 |
#' |
|
1139 |
setMethod( |
|
1140 |
f = "prob", |
|
1141 |
signature = signature( |
|
1142 |
dose = "numeric", |
|
1143 |
model = "LogisticKadane", |
|
1144 |
samples = "Samples" |
|
1145 |
), |
|
1146 |
definition = function(dose, model, samples, ...) { |
|
1147 | 9x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1148 | 8x |
assert_subset(c("rho0", "gamma"), names(samples)) |
1149 | 8x |
assert_length(dose, len = size(samples)) |
1150 | ||
1151 | 7x |
rho0 <- samples@data$rho0 |
1152 | 7x |
gamma <- samples@data$gamma |
1153 | 7x |
theta <- model@theta |
1154 | 7x |
xmin <- model@xmin |
1155 | 7x |
num <- gamma * logit(rho0) - xmin * logit(theta) + (logit(theta) - logit(rho0)) * dose |
1156 | 7x |
plogis(num / (gamma - xmin)) |
1157 |
} |
|
1158 |
) |
|
1159 | ||
1160 |
## LogisticKadaneBetaGamma ---- |
|
1161 | ||
1162 |
#' @describeIn prob |
|
1163 |
#' |
|
1164 |
#' @aliases prob-LogisticKadaneBetaGamma |
|
1165 |
#' @export |
|
1166 |
#' |
|
1167 |
setMethod( |
|
1168 |
f = "prob", |
|
1169 |
signature = signature( |
|
1170 |
dose = "numeric", |
|
1171 |
model = "LogisticKadaneBetaGamma", |
|
1172 |
samples = "Samples" |
|
1173 |
), |
|
1174 |
definition = function(dose, model, samples, ...) { |
|
1175 | ! |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1176 | ! |
assert_subset(c("rho0", "gamma"), names(samples)) |
1177 | ! |
assert_length(dose, len = size(samples)) |
1178 | ||
1179 | ! |
rho0 <- samples@data$rho0 |
1180 | ! |
gamma <- samples@data$gamma |
1181 | ! |
theta <- model@theta |
1182 | ! |
xmin <- model@xmin |
1183 | ! |
num <- gamma * logit(rho0) - xmin * logit(theta) + (logit(theta) - logit(rho0)) * dose |
1184 | ! |
plogis(num / (gamma - xmin)) |
1185 |
} |
|
1186 |
) |
|
1187 | ||
1188 |
## LogisticNormalMixture ---- |
|
1189 | ||
1190 |
#' @describeIn prob |
|
1191 |
#' |
|
1192 |
#' @aliases prob-LogisticNormalMixture |
|
1193 |
#' @export |
|
1194 |
#' |
|
1195 |
setMethod( |
|
1196 |
f = "prob", |
|
1197 |
signature = signature( |
|
1198 |
dose = "numeric", |
|
1199 |
model = "LogisticNormalMixture", |
|
1200 |
samples = "Samples" |
|
1201 |
), |
|
1202 |
definition = function(dose, model, samples, ...) { |
|
1203 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1204 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
1205 | 4x |
assert_length(dose, len = size(samples)) |
1206 | ||
1207 | 3x |
alpha0 <- samples@data$alpha0 |
1208 | 3x |
alpha1 <- samples@data$alpha1 |
1209 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
1210 | 3x |
plogis(alpha0 + alpha1 * log(dose / ref_dose)) |
1211 |
} |
|
1212 |
) |
|
1213 | ||
1214 |
## LogisticNormalFixedMixture ---- |
|
1215 | ||
1216 |
#' @describeIn prob |
|
1217 |
#' |
|
1218 |
#' @aliases prob-LogisticNormalFixedMixture |
|
1219 |
#' @export |
|
1220 |
#' |
|
1221 |
setMethod( |
|
1222 |
f = "prob", |
|
1223 |
signature = signature( |
|
1224 |
dose = "numeric", |
|
1225 |
model = "LogisticNormalFixedMixture", |
|
1226 |
samples = "Samples" |
|
1227 |
), |
|
1228 |
definition = function(dose, model, samples, ...) { |
|
1229 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1230 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
1231 | 4x |
assert_length(dose, len = size(samples)) |
1232 | ||
1233 | 3x |
alpha0 <- samples@data$alpha0 |
1234 | 3x |
alpha1 <- samples@data$alpha1 |
1235 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
1236 | 3x |
plogis(alpha0 + alpha1 * log(dose / ref_dose)) |
1237 |
} |
|
1238 |
) |
|
1239 | ||
1240 |
## LogisticLogNormalMixture ---- |
|
1241 | ||
1242 |
#' @describeIn prob |
|
1243 |
#' |
|
1244 |
#' @aliases prob-LogisticLogNormalMixture |
|
1245 |
#' @export |
|
1246 |
#' |
|
1247 |
setMethod( |
|
1248 |
f = "prob", |
|
1249 |
signature = signature( |
|
1250 |
dose = "numeric", |
|
1251 |
model = "LogisticLogNormalMixture", |
|
1252 |
samples = "Samples" |
|
1253 |
), |
|
1254 |
definition = function(dose, model, samples, ...) { |
|
1255 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1256 | 4x |
assert_subset(c("alpha0", "alpha1"), names(samples)) |
1257 | 4x |
assert_length(dose, len = size(samples)) |
1258 | ||
1259 | 3x |
alpha0 <- samples@data$alpha0 |
1260 | 3x |
alpha1 <- samples@data$alpha1 |
1261 | 3x |
comp <- samples@data$comp |
1262 | 3x |
ref_dose <- as.numeric(model@ref_dose) |
1263 | 3x |
sel <- cbind(seq_along(comp), comp) |
1264 | 3x |
plogis(alpha0[sel] + alpha1[sel] * log(dose / ref_dose)) |
1265 |
} |
|
1266 |
) |
|
1267 | ||
1268 |
## DualEndpoint ---- |
|
1269 | ||
1270 |
#' @describeIn prob |
|
1271 |
#' |
|
1272 |
#' @aliases prob-DualEndpoint |
|
1273 |
#' @export |
|
1274 |
#' |
|
1275 |
setMethod( |
|
1276 |
f = "prob", |
|
1277 |
signature = signature( |
|
1278 |
dose = "numeric", |
|
1279 |
model = "DualEndpoint", |
|
1280 |
samples = "Samples" |
|
1281 |
), |
|
1282 |
definition = function(dose, model, samples, ...) { |
|
1283 | 160x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1284 | 159x |
assert_subset("betaZ", names(samples)) |
1285 | 159x |
assert_length(dose, len = size(samples)) |
1286 | ||
1287 | 158x |
betaZ <- samples@data$betaZ |
1288 | 158x |
ref_dose <- as.numeric(model@ref_dose) |
1289 | 158x |
stand_dose <- if (model@use_log_dose) { |
1290 | 58x |
log(dose / ref_dose) |
1291 |
} else { |
|
1292 | 100x |
dose / ref_dose |
1293 |
} |
|
1294 | 158x |
pnorm(betaZ[, 1] + betaZ[, 2] * stand_dose) |
1295 |
} |
|
1296 |
) |
|
1297 | ||
1298 |
## LogisticIndepBeta ---- |
|
1299 | ||
1300 |
#' @describeIn prob compute toxicity probabilities of the occurrence of a DLE at |
|
1301 |
#' a specified dose level, based on the samples of [`LogisticIndepBeta`] model |
|
1302 |
#' parameters. |
|
1303 |
#' |
|
1304 |
#' @aliases prob-LogisticIndepBeta |
|
1305 |
#' @export |
|
1306 |
#' |
|
1307 |
setMethod( |
|
1308 |
f = "prob", |
|
1309 |
signature = signature( |
|
1310 |
dose = "numeric", |
|
1311 |
model = "LogisticIndepBeta", |
|
1312 |
samples = "Samples" |
|
1313 |
), |
|
1314 |
definition = function(dose, model, samples, ...) { |
|
1315 | 950x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1316 | 949x |
assert_subset(c("phi1", "phi2"), names(samples)) |
1317 | 949x |
assert_length(dose, len = size(samples)) |
1318 | ||
1319 | 947x |
phi1 <- samples@data$phi1 |
1320 | 947x |
phi2 <- samples@data$phi2 |
1321 | 947x |
log_dose <- log(dose) |
1322 | 947x |
exp(phi1 + phi2 * log_dose) / (1 + exp(phi1 + phi2 * log_dose)) |
1323 |
} |
|
1324 |
) |
|
1325 | ||
1326 |
## LogisticIndepBeta-noSamples ---- |
|
1327 | ||
1328 |
#' @describeIn prob compute toxicity probabilities of the occurrence of a DLE at |
|
1329 |
#' a specified dose level, based on the [`LogisticIndepBeta`] model parameters. |
|
1330 |
#' All model parameters (except `dose`) should be present in the `model` object. |
|
1331 |
#' |
|
1332 |
#' @aliases prob-LogisticIndepBeta-noSamples |
|
1333 |
#' @export |
|
1334 |
#' |
|
1335 |
setMethod( |
|
1336 |
f = "prob", |
|
1337 |
signature = signature( |
|
1338 |
dose = "numeric", |
|
1339 |
model = "LogisticIndepBeta", |
|
1340 |
samples = "missing" |
|
1341 |
), |
|
1342 |
definition = function(dose, model, ...) { |
|
1343 | 682x |
model_params <- h_slots(model, c("phi1", "phi2")) |
1344 | 682x |
nsamples <- length(model_params[[1]]) |
1345 | 682x |
samples <- Samples(data = model_params, options = McmcOptions(samples = nsamples)) |
1346 | ||
1347 | 682x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1348 | 681x |
assert_length(dose, len = nsamples) |
1349 | ||
1350 | 681x |
prob(dose, model, samples) |
1351 |
} |
|
1352 |
) |
|
1353 | ||
1354 |
## OneParLogNormalPrior ---- |
|
1355 | ||
1356 |
#' @describeIn prob |
|
1357 |
#' |
|
1358 |
#' @aliases prob-OneParLogNormalPrior |
|
1359 |
#' @export |
|
1360 |
#' |
|
1361 |
setMethod( |
|
1362 |
f = "prob", |
|
1363 |
signature = signature( |
|
1364 |
dose = "numeric", |
|
1365 |
model = "OneParLogNormalPrior", |
|
1366 |
samples = "Samples" |
|
1367 |
), |
|
1368 |
definition = function(dose, model, samples, ...) { |
|
1369 | 30x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1370 | 29x |
assert_subset("alpha", names(samples)) |
1371 | 29x |
assert_length(dose, len = size(samples)) |
1372 | ||
1373 | 28x |
alpha <- samples@data$alpha |
1374 | 28x |
skel_fun <- model@skel_fun |
1375 | 28x |
skel_fun(dose)^exp(alpha) |
1376 |
} |
|
1377 |
) |
|
1378 | ||
1379 |
## OneParExpPrior ---- |
|
1380 | ||
1381 |
#' @describeIn prob |
|
1382 |
#' |
|
1383 |
#' @aliases prob-OneParExpPrior |
|
1384 |
#' @export |
|
1385 |
#' |
|
1386 |
setMethod( |
|
1387 |
f = "prob", |
|
1388 |
signature = signature( |
|
1389 |
dose = "numeric", |
|
1390 |
model = "OneParExpPrior", |
|
1391 |
samples = "Samples" |
|
1392 |
), |
|
1393 |
definition = function(dose, model, samples, ...) { |
|
1394 | 5x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1395 | 4x |
assert_subset("theta", names(samples)) |
1396 | 4x |
assert_length(dose, len = size(samples)) |
1397 | ||
1398 | 3x |
theta <- samples@data$theta |
1399 | 3x |
skel_fun <- model@skel_fun |
1400 | 3x |
skel_fun(dose)^theta |
1401 |
} |
|
1402 |
) |
|
1403 | ||
1404 |
## LogisticLogNormalOrdinal ---- |
|
1405 | ||
1406 |
#' Calculate a grade-specific probability of toxicity for a given dose. |
|
1407 |
#' @describeIn prob |
|
1408 |
#' |
|
1409 |
#' @param grade (`integer` or `integer_vector`)\cr The toxicity grade for which probabilities are required |
|
1410 |
#' @param cumulative (`flag`)\cr Should the returned probability be cumulative |
|
1411 |
#' (the default) or grade-specific? |
|
1412 |
#' @aliases prob-LogisticLogNormalOrdinal |
|
1413 |
#' @export |
|
1414 |
#' |
|
1415 |
setMethod( |
|
1416 |
f = "prob", |
|
1417 |
signature = signature( |
|
1418 |
dose = "numeric", |
|
1419 |
model = "LogisticLogNormalOrdinal", |
|
1420 |
samples = "Samples" |
|
1421 |
), |
|
1422 |
definition = function(dose, model, samples, grade, cumulative = TRUE, ...) { |
|
1423 | 143x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1424 | 142x |
assert_integer( |
1425 | 142x |
grade, |
1426 | 142x |
min.len = 1, |
1427 | 142x |
max.len = length(model@params@mean) - 1, |
1428 | 142x |
lower = 0, |
1429 | 142x |
upper = length(model@params@mean) - 1 |
1430 |
) |
|
1431 | 140x |
assert_subset( |
1432 | 140x |
names(samples), |
1433 | 140x |
c(paste0("alpha", 0:(length(model@params@mean) - 1)), "beta") |
1434 |
) |
|
1435 | 140x |
assert_length(dose, len = size(samples)) |
1436 | 140x |
assert_flag(cumulative) |
1437 | ||
1438 | 138x |
rv <- lapply( |
1439 | 138x |
grade, |
1440 | 138x |
function(g) { |
1441 | 148x |
alpha <- samples@data[[paste0("alpha", g)]] |
1442 | 148x |
beta <- samples@data$beta |
1443 | 148x |
ref_dose <- as.numeric(model@ref_dose) |
1444 | ||
1445 | 148x |
cumulative_prob <- plogis(alpha + beta * log(dose / ref_dose)) |
1446 | 148x |
if (cumulative | g == as.integer(length(model@params@mean) - 1)) { |
1447 | 138x |
return(cumulative_prob) |
1448 |
} |
|
1449 | ||
1450 |
# Calculate grade-specific probabilities |
|
1451 | 10x |
alpha0 <- samples@data[[paste0("alpha", g + 1)]] |
1452 | 10x |
grade_prob <- cumulative_prob - plogis(alpha0 + beta * log(dose / ref_dose)) |
1453 | 10x |
return(grade_prob) |
1454 |
} |
|
1455 |
) |
|
1456 | 138x |
if (length(rv) == 1) { |
1457 | 128x |
return(rv[[1]]) |
1458 |
} |
|
1459 | 10x |
names(rv) <- as.character(grade) |
1460 | 10x |
return(rv) |
1461 |
} |
|
1462 |
) |
|
1463 | ||
1464 |
# efficacy ---- |
|
1465 | ||
1466 |
## generic ---- |
|
1467 | ||
1468 |
#' Computing Expected Efficacy for a Given Dose, Model and Samples |
|
1469 |
#' |
|
1470 |
#' @description `r lifecycle::badge("stable")` |
|
1471 |
#' |
|
1472 |
#' A function that computes the value of expected efficacy at a specified dose |
|
1473 |
#' level, based on the model specific parameters. The model parameters (samples) |
|
1474 |
#' are obtained based on prior specified in form of pseudo data combined with |
|
1475 |
#' observed responses (if any). |
|
1476 |
#' |
|
1477 |
#' @details The `efficacy()` function computes the expected efficacy for given |
|
1478 |
#' doses, using samples of the model parameter(s). |
|
1479 |
#' If you work with multivariate model parameters, then assume that your model |
|
1480 |
#' specific `efficacy()` method receives a samples matrix where the rows |
|
1481 |
#' correspond to the sampling index, i.e. the layout is then |
|
1482 |
#' `nSamples x dimParameter`. |
|
1483 |
#' |
|
1484 |
#' @param dose (`numeric`)\cr the dose which is targeted. |
|
1485 |
#' The following recycling rule applies when `samples` is not missing: vectors |
|
1486 |
#' of size 1 will be recycled to the size of the sample |
|
1487 |
#' (i.e. `size(samples)`). Otherwise, `dose` must have the same |
|
1488 |
#' size as the sample. |
|
1489 |
#' @param model (`ModelEff`)\cr the efficacy model with pseudo data prior. |
|
1490 |
#' @param samples (`Samples`)\cr samples of model's parameters that will be |
|
1491 |
#' used to compute expected efficacy values. Can also be missing for some |
|
1492 |
#' models. |
|
1493 |
#' @param ... model specific parameters when `samples` are not used. |
|
1494 |
#' |
|
1495 |
#' @return A `numeric` vector with the values of expected efficacy. |
|
1496 |
#' If non-scalar `samples` were used, then every element in the returned vector |
|
1497 |
#' corresponds to one element of a sample. Hence, in this case, the output |
|
1498 |
#' vector is of the same length as the sample vector. If scalar `samples` were |
|
1499 |
#' used or no `samples` were used, e.g. for pseudo DLE/toxicity `model`, |
|
1500 |
#' then the output is of the same length as the length of the `dose`. |
|
1501 |
#' |
|
1502 |
#' @seealso [dose()], [prob()]. |
|
1503 |
#' |
|
1504 |
#' @export |
|
1505 |
#' @example examples/Model-method-efficacy.R |
|
1506 |
setGeneric( |
|
1507 |
name = "efficacy", |
|
1508 |
def = function(dose, model, samples, ...) { |
|
1509 | 1589x |
standardGeneric("efficacy") |
1510 |
}, |
|
1511 |
valueClass = "numeric" |
|
1512 |
) |
|
1513 | ||
1514 |
## Effloglog ---- |
|
1515 | ||
1516 |
#' @describeIn efficacy compute the expected efficacy at a specified dose level, |
|
1517 |
#' based on the samples of [`Effloglog`] model parameters. |
|
1518 |
#' |
|
1519 |
#' @aliases efficacy-Effloglog |
|
1520 |
#' @export |
|
1521 |
#' |
|
1522 |
setMethod( |
|
1523 |
f = "efficacy", |
|
1524 |
signature = signature( |
|
1525 |
dose = "numeric", |
|
1526 |
model = "Effloglog", |
|
1527 |
samples = "Samples" |
|
1528 |
), |
|
1529 |
definition = function(dose, model, samples) { |
|
1530 | 896x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1531 | 895x |
assert_subset(c("theta1", "theta2"), names(samples)) |
1532 | 894x |
assert_length(dose, len = size(samples)) |
1533 | ||
1534 | 893x |
theta1 <- samples@data$theta1 |
1535 | 893x |
theta2 <- samples@data$theta2 |
1536 | 893x |
constant <- model@const |
1537 | 893x |
theta1 + theta2 * log(log(dose + constant)) |
1538 |
} |
|
1539 |
) |
|
1540 | ||
1541 |
## Effloglog-noSamples ---- |
|
1542 | ||
1543 |
#' @describeIn efficacy compute the expected efficacy at a specified dose level, |
|
1544 |
#' based on the [`Effloglog`] model parameters. |
|
1545 |
#' All model parameters (except `dose`) should be present in the `model` object. |
|
1546 |
#' |
|
1547 |
#' @aliases efficacy-Effloglog-noSamples |
|
1548 |
#' @export |
|
1549 |
#' |
|
1550 |
setMethod( |
|
1551 |
f = "efficacy", |
|
1552 |
signature = signature( |
|
1553 |
dose = "numeric", |
|
1554 |
model = "Effloglog", |
|
1555 |
samples = "missing" |
|
1556 |
), |
|
1557 |
definition = function(dose, model) { |
|
1558 | 666x |
model_params <- h_slots(model, c("theta1", "theta2")) |
1559 | 666x |
nsamples <- length(model_params[[1]]) |
1560 | 666x |
samples <- Samples(data = model_params, options = McmcOptions(samples = nsamples)) |
1561 | ||
1562 | 666x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1563 | 665x |
assert_length(dose, len = nsamples) |
1564 | ||
1565 | 665x |
efficacy(dose, model, samples) |
1566 |
} |
|
1567 |
) |
|
1568 | ||
1569 |
## EffFlexi ---- |
|
1570 | ||
1571 |
#' @describeIn efficacy compute the expected efficacy at a specified dose level, |
|
1572 |
#' based on the samples of [`EffFlexi`] model parameters. If a given dose in |
|
1573 |
#' the `dose` vector is from outside of the dose grid range, the `NA_real` is |
|
1574 |
#' returned for this dose and the warning is thrown. |
|
1575 |
#' |
|
1576 |
#' @aliases efficacy-EffFlexi |
|
1577 |
#' @export |
|
1578 |
#' |
|
1579 |
setMethod( |
|
1580 |
f = "efficacy", |
|
1581 |
signature = signature( |
|
1582 |
dose = "numeric", |
|
1583 |
model = "EffFlexi", |
|
1584 |
samples = "Samples" |
|
1585 |
), |
|
1586 |
definition = function(dose, model, samples) { |
|
1587 | 27x |
n_samples <- size(samples) |
1588 | 27x |
assert_numeric(dose, lower = 0L, any.missing = FALSE, min.len = 1L) |
1589 | 26x |
assert_true(model@data@nGrid >= 1L) |
1590 | 26x |
assert_subset("ExpEff", names(samples)) |
1591 | 25x |
assert_length(dose, len = n_samples) |
1592 | ||
1593 | 24x |
dose_grid <- model@data@doseGrid |
1594 | 24x |
dose_level <- match_within_tolerance(dose, dose_grid) |
1595 | 24x |
dose[which(!is.na(dose_level))] <- dose_grid[stats::na.omit(dose_level)] |
1596 | ||
1597 |
# linear interpolation, NA for doses that are outside of the dose_grid range. |
|
1598 | 24x |
samples_eff <- samples@data$ExpEff |
1599 | 24x |
eff <- if (length(dose) == n_samples) { |
1600 | 4x |
sapply(seq_len(n_samples), function(s) { |
1601 | 16x |
stats::approx(dose_grid, samples_eff[s, ], xout = dose[s])$y |
1602 |
}) |
|
1603 |
} else { |
|
1604 | 20x |
eff <- apply(samples_eff, 1, function(s) { |
1605 | 120020x |
stats::approx(dose_grid, s, xout = dose)$y |
1606 |
}) |
|
1607 | 20x |
as.vector(eff) |
1608 |
} |
|
1609 | ||
1610 | 24x |
if (any(is.na(eff))) { |
1611 | 4x |
warning( |
1612 | 4x |
paste("At least one dose out of", paste(dose, collapse = ", "), "is outside of the dose grid range") |
1613 |
) |
|
1614 |
} |
|
1615 | 24x |
eff |
1616 |
} |
|
1617 |
) |
|
1618 | ||
1619 |
# biomarker ---- |
|
1620 | ||
1621 |
## generic ---- |
|
1622 | ||
1623 |
#' Get the Biomarker Levels for a Given Dual-Endpoint Model, Given Dose Levels and Samples |
|
1624 |
#' |
|
1625 |
#' @details This function simply returns a specific columns (with the indices equal |
|
1626 |
#' to `xLevel`) of the biomarker samples matrix, which is included in the the |
|
1627 |
#' `samples` object. |
|
1628 |
#' |
|
1629 |
#' @description `r lifecycle::badge("experimental")` |
|
1630 |
#' |
|
1631 |
#' @param xLevel (`integer`)\cr the levels for the doses the patients have been |
|
1632 |
#' given w.r.t dose grid. See [`Data`] for more details. |
|
1633 |
#' @param model (`DualEndpoint`)\cr the model. |
|
1634 |
#' @param samples (`Samples`)\cr the samples of model's parameters that store |
|
1635 |
#' the value of biomarker levels for all doses on the dose grid. |
|
1636 |
#' @param ... not used. |
|
1637 |
#' |
|
1638 |
#' @return The biomarker levels. |
|
1639 |
#' |
|
1640 |
#' @export |
|
1641 |
#' @example examples/Model-method-biomarker.R |
|
1642 |
#' |
|
1643 |
setGeneric( |
|
1644 |
name = "biomarker", |
|
1645 |
def = function(xLevel, model, samples, ...) { |
|
1646 | 54x |
standardGeneric("biomarker") |
1647 |
}, |
|
1648 |
valueClass = c("numeric", "array") |
|
1649 |
) |
|
1650 | ||
1651 |
## DualEndpoint ---- |
|
1652 | ||
1653 |
#' @describeIn biomarker |
|
1654 |
#' |
|
1655 |
#' @aliases biomarker-DualEndpoint |
|
1656 |
#' @export |
|
1657 |
#' |
|
1658 |
setMethod( |
|
1659 |
f = "biomarker", |
|
1660 |
signature = signature( |
|
1661 |
xLevel = "integer", |
|
1662 |
model = "DualEndpoint", |
|
1663 |
samples = "Samples" |
|
1664 |
), |
|
1665 |
def = function(xLevel, model, samples, ...) { |
|
1666 | 53x |
assert_integer( |
1667 | 53x |
xLevel, |
1668 | 53x |
lower = 1, |
1669 | 53x |
upper = ncol(samples@data$betaW), |
1670 | 53x |
any.missing = FALSE, |
1671 | 53x |
min.len = 1 |
1672 |
) |
|
1673 | ||
1674 | 52x |
samples@data$betaW[, xLevel] |
1675 |
} |
|
1676 |
) |
|
1677 | ||
1678 |
# gain ---- |
|
1679 | ||
1680 |
## generic ---- |
|
1681 | ||
1682 |
#' Compute Gain Values based on Pseudo DLE and a Pseudo Efficacy Models and |
|
1683 |
#' Using Optional Samples. |
|
1684 |
#' |
|
1685 |
#' @details This function computes the gain values for a given dose level, |
|
1686 |
#' pseudo DLE and Efficacy models as well as a given DLE and Efficacy samples. |
|
1687 |
#' |
|
1688 |
#' @description `r lifecycle::badge("stable")` |
|
1689 |
#' |
|
1690 |
#' @param dose (`number` or `numeric`)\cr the dose which is targeted. |
|
1691 |
#' The following recycling rule applies when samples are not missing: vectors |
|
1692 |
#' of size 1 will be recycled to the size of the sample. Otherwise, `dose` |
|
1693 |
#' must have the same size as the sample. |
|
1694 |
#' @param model_dle (`ModelTox`)\cr pseudo DLE (dose-limiting events)/toxicity |
|
1695 |
#' model. |
|
1696 |
#' @param samples_dle (`Samples`)\cr the samples of model's |
|
1697 |
#' parameters that will be used to compute toxicity probabilities. Can also be |
|
1698 |
#' missing for some models. |
|
1699 |
#' @param model_eff (`ModelEff`)\cr the efficacy model with pseudo data prior. |
|
1700 |
#' @param samples_eff (`Samples`)\cr samples of model's parameters that will be |
|
1701 |
#' used to compute expected efficacy values. Can also be missing for some |
|
1702 |
#' models. |
|
1703 |
#' @param ... not used. |
|
1704 |
#' |
|
1705 |
#' @return The gain values. |
|
1706 |
#' |
|
1707 |
#' @export |
|
1708 |
#' @example examples/Model-method-gain.R |
|
1709 |
#' |
|
1710 |
setGeneric( |
|
1711 |
name = "gain", |
|
1712 |
def = function(dose, model_dle, samples_dle, model_eff, samples_eff, ...) { |
|
1713 | 778x |
standardGeneric("gain") |
1714 |
}, |
|
1715 |
valueClass = "numeric" |
|
1716 |
) |
|
1717 | ||
1718 |
## ModelTox-ModelEff ---- |
|
1719 | ||
1720 |
#' @describeIn gain |
|
1721 |
#' |
|
1722 |
#' @aliases gain-ModelTox-ModelEff |
|
1723 |
#' @export |
|
1724 |
#' |
|
1725 |
setMethod( |
|
1726 |
f = "gain", |
|
1727 |
signature = signature( |
|
1728 |
dose = "numeric", |
|
1729 |
model_dle = "ModelTox", |
|
1730 |
samples_dle = "Samples", |
|
1731 |
model_eff = "ModelEff", |
|
1732 |
samples_eff = "Samples" |
|
1733 |
), |
|
1734 |
definition = function(dose, model_dle, samples_dle, model_eff, samples_eff, ...) { |
|
1735 | 137x |
dle <- prob(dose, model_dle, samples_dle) |
1736 | 136x |
eff <- efficacy(dose, model_eff, samples_eff) |
1737 | 136x |
assert_length(dle, len = length(eff)) |
1738 | 136x |
eff / (1 + (dle / (1 - dle))) |
1739 |
} |
|
1740 |
) |
|
1741 | ||
1742 |
## ModelTox-ModelEff-noSamples---- |
|
1743 | ||
1744 |
#' @describeIn gain Compute the gain value for a given dose level, pseudo DLE |
|
1745 |
#' and Efficacy models without DLE and the Efficacy samples. |
|
1746 |
#' |
|
1747 |
#' @aliases gain-ModelTox-Effloglog-noSamples |
|
1748 |
#' @export |
|
1749 |
#' @example examples/Model-method-gainNoSamples.R |
|
1750 |
#' |
|
1751 |
setMethod( |
|
1752 |
f = "gain", |
|
1753 |
signature = signature( |
|
1754 |
dose = "numeric", |
|
1755 |
model_dle = "ModelTox", |
|
1756 |
samples_dle = "missing", |
|
1757 |
model_eff = "Effloglog", |
|
1758 |
samples_eff = "missing" |
|
1759 |
), |
|
1760 |
definition = function(dose, model_dle, model_eff, ...) { |
|
1761 | 641x |
dle <- prob(dose, model_dle) |
1762 | 641x |
eff <- efficacy(dose, model_eff) |
1763 | 641x |
assert_length(dle, len = length(eff)) |
1764 | 641x |
eff / (1 + (dle / (1 - dle))) |
1765 |
} |
|
1766 |
) |
|
1767 | ||
1768 |
# update ---- |
|
1769 | ||
1770 |
## ModelPseudo ---- |
|
1771 | ||
1772 |
#' Update method for the [`ModelPseudo`] model class. This is a method to update |
|
1773 |
#' the model class slots (estimates, parameters, variables and etc.), when the |
|
1774 |
#' new data (e.g. new observations of responses) are available. This method is |
|
1775 |
#' mostly used to obtain new modal estimates for pseudo model parameters. |
|
1776 |
#' |
|
1777 |
#' @param object (`ModelPseudo`)\cr the model to update. |
|
1778 |
#' @param data (`Data`)\cr all currently available of data. |
|
1779 |
#' @param ... not used. |
|
1780 |
#' |
|
1781 |
#' @return the new [`ModelPseudo`] class object. |
|
1782 |
#' |
|
1783 |
#' @aliases update-ModelPseudo |
|
1784 |
#' @export |
|
1785 |
#' @example examples/Model-method-update.R |
|
1786 |
#' |
|
1787 |
setMethod( |
|
1788 |
f = "update", |
|
1789 |
signature = signature( |
|
1790 |
object = "ModelPseudo" |
|
1791 |
), |
|
1792 |
definition = function(object, data, ...) { |
|
1793 | 139x |
assert_class(data, "Data") |
1794 | ||
1795 | 138x |
constructor_name <- class(object) |
1796 | 138x |
arg_names <- setdiff(formalArgs(constructor_name), "data") |
1797 | 138x |
do.call( |
1798 | 138x |
constructor_name, |
1799 | 138x |
c(h_slots(object, arg_names), list(data = data)) |
1800 |
) |
|
1801 |
} |
|
1802 |
) |
|
1803 | ||
1804 |
# tidy ---- |
|
1805 | ||
1806 |
# LogisticIndepBeta |
|
1807 | ||
1808 |
#' Tidy Method for the [`LogisticIndepBeta`] Class |
|
1809 |
#' |
|
1810 |
#' @description `r lifecycle::badge("experimental")` |
|
1811 |
#' |
|
1812 |
#' A method that tidies a [`LogisticIndepBeta`] object. |
|
1813 |
#' |
|
1814 |
#' @return The [`list`] of [`tibble`] objects. |
|
1815 |
#' |
|
1816 |
#' @aliases tidy-LogisticIndepBeta |
|
1817 |
#' @rdname tidy |
|
1818 |
#' @method tidy LogisticIndepBeta |
|
1819 |
#' @export |
|
1820 |
#' @example examples/LogisticIndepBeta-method-tidy.R |
|
1821 |
#' |
|
1822 |
setMethod( |
|
1823 |
f = "tidy", |
|
1824 |
signature = signature(x = "LogisticIndepBeta"), |
|
1825 |
definition = function(x, ...) { |
|
1826 | 36x |
start <- callNextMethod() |
1827 |
# N$DLEweights Dose$DLEdose Tox$binDLE |
|
1828 | 36x |
pseudoData <- tibble::tibble( |
1829 | 36x |
Dose = dplyr::pull(start$DLEdose), |
1830 | 36x |
N = dplyr::pull(start$DLEweights), |
1831 | 36x |
Tox = dplyr::pull(start$binDLE) |
1832 |
) |
|
1833 | 36x |
params <- tibble::tibble( |
1834 | 36x |
Param = c("Phi1", "Phi2"), |
1835 | 36x |
mean = c(dplyr::pull(start$phi1), dplyr::pull(start$phi2)), |
1836 | 36x |
cov = as.list(start$Pcov) |
1837 |
) |
|
1838 | 36x |
list( |
1839 | 36x |
pseudoData = pseudoData, |
1840 | 36x |
data = start$data, |
1841 | 36x |
params = params |
1842 |
) %>% |
|
1843 | 36x |
h_tidy_class(x) |
1844 |
} |
|
1845 |
) |
|
1846 | ||
1847 |
# Effloglog |
|
1848 | ||
1849 |
#' Tidy Method for the [`Effloglog`] Class |
|
1850 |
#' |
|
1851 |
#' @description `r lifecycle::badge("experimental")` |
|
1852 |
#' |
|
1853 |
#' A method that tidies a [`Effloglog`] object. |
|
1854 |
#' |
|
1855 |
#' @return The [`list`] of [`tibble`] objects. |
|
1856 |
#' |
|
1857 |
#' @aliases tidy-Effloglog |
|
1858 |
#' @rdname tidy |
|
1859 |
#' @method tidy Edffloglog |
|
1860 |
#' @export |
|
1861 |
#' @example examples/Effloglog-method-tidy.R |
|
1862 |
#' |
|
1863 |
setMethod( |
|
1864 |
f = "tidy", |
|
1865 |
signature = signature(x = "Effloglog"), |
|
1866 |
definition = function(x, ...) { |
|
1867 | 22x |
start <- callNextMethod() |
1868 | 22x |
pseudoData <- tibble::tibble( |
1869 | 22x |
Dose = dplyr::pull(start$eff_dose), |
1870 | 22x |
Response = dplyr::pull(start$eff) |
1871 |
) |
|
1872 | 22x |
params <- tibble::tibble( |
1873 | 22x |
Param = c("theta1", "theta2"), |
1874 | 22x |
mean = c(dplyr::pull(start$theta1), dplyr::pull(start$theta2)), |
1875 | 22x |
cov = as.list(start$Pcov) |
1876 |
) |
|
1877 | 22x |
list( |
1878 | 22x |
pseudoData = pseudoData, |
1879 | 22x |
data = start$data, |
1880 | 22x |
params = params |
1881 |
) %>% |
|
1882 | 22x |
h_tidy_class(x) |
1883 |
} |
|
1884 |
) |
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 | 10x |
assert_flag(blind) |
133 | 10x |
assert_flag(legend) |
134 | 10x |
assert_character(tox_labels, any.missing = FALSE, unique = TRUE) |
135 | 10x |
assert_integer(tox_shapes, any.missing = FALSE, unique = TRUE) |
136 | 10x |
assert_true(length(tox_shapes) == length(tox_labels)) |
137 | 10x |
assert_subset(x@y, as.integer(0:(length(tox_shapes) - 1))) |
138 | 10x |
if (x@nObs == 0L) { |
139 | ! |
return() |
140 |
} |
|
141 | 10x |
df <- h_plot_data_df(x, blind, ...) |
142 | ||
143 | 10x |
p <- ggplot(df, aes(x = patient, y = dose)) + |
144 | 10x |
geom_point(aes(shape = toxicity, colour = toxicity), size = 3) + |
145 | 10x |
scale_colour_manual( |
146 | 10x |
name = "Toxicity", |
147 | 10x |
values = tox_labels, |
148 | 10x |
breaks = names(tox_labels), |
149 | 10x |
guide = guide_legend(reverse = TRUE) |
150 |
) + |
|
151 | 10x |
scale_shape_manual( |
152 | 10x |
name = "Toxicity", |
153 | 10x |
values = tox_shapes, |
154 | 10x |
breaks = names(tox_shapes), |
155 | 10x |
guide = guide_legend(reverse = TRUE) |
156 |
) + |
|
157 | 10x |
scale_x_continuous(breaks = df$patient, minor_breaks = NULL) + |
158 | 10x |
scale_y_continuous( |
159 | 10x |
breaks = sort(unique(c(0, df$dose))), |
160 | 10x |
minor_breaks = NULL, |
161 | 10x |
limits = c(0, max(df$dose) * 1.1) |
162 |
) + |
|
163 | 10x |
xlab("Patient") + |
164 | 10x |
ylab("Dose Level") |
165 | ||
166 | 10x |
p <- p + h_plot_data_cohort_lines(df$cohort, placebo = x@placebo) |
167 | ||
168 | 10x |
if (!blind) { |
169 | 4x |
p <- p + |
170 | 4x |
geom_text( |
171 | 4x |
aes(label = ID, size = 2), |
172 | 4x |
data = df, |
173 | 4x |
hjust = 0, |
174 | 4x |
vjust = 0.5, |
175 | 4x |
angle = 90, |
176 | 4x |
colour = "black", |
177 | 4x |
show.legend = FALSE |
178 |
) |
|
179 |
} |
|
180 | ||
181 | 10x |
if (!legend) { |
182 | 6x |
p <- p + theme(legend.position = "none") |
183 |
} |
|
184 | ||
185 | 10x |
p |
186 |
} |
|
187 | ||
188 |
#' Helper Function Containing Common Functionality |
|
189 |
#' |
|
190 |
#' Used by `dose_grid_range-Data` and `dose_grid_range-DataOrdinal` |
|
191 |
#' @param object (`Data` or `DataOrdinal`)\cr the object for which the dose grid |
|
192 |
#' range is required |
|
193 |
#' @param ignore_placebo (`flag`)\cr should placebo dose (if any) not be counted? |
|
194 |
#' |
|
195 |
h_obtain_dose_grid_range <- function(object, ignore_placebo) { |
|
196 | 168x |
assert_flag(ignore_placebo) |
197 | ||
198 | 166x |
dose_grid <- if (ignore_placebo && object@placebo && object@nGrid >= 1) { |
199 | 12x |
object@doseGrid[-1] |
200 |
} else { |
|
201 | 154x |
object@doseGrid |
202 |
} |
|
203 | ||
204 | 166x |
if (length(dose_grid) == 0L) { |
205 | 10x |
c(-Inf, Inf) |
206 |
} else { |
|
207 | 156x |
range(dose_grid) |
208 |
} |
|
209 |
} |
|
210 | ||
211 |
#' Convert a Ordinal Data to the Equivalent Binary Data for a Specific |
|
212 |
#' Grade |
|
213 |
#' |
|
214 |
#' @description `r lifecycle::badge("experimental")` |
|
215 |
#' |
|
216 |
#' A simple helper function that takes a [`DataOrdinal`] object and an |
|
217 |
#' integer grade and converts them to the equivalent `Data` object. |
|
218 |
#' |
|
219 |
#' @param data_ord (`DataOrdinal`)\cr the `DataOrdinal` object to covert |
|
220 |
#' @param grade (`integer`)\cr the toxicity grade for which the equivalent data |
|
221 |
#' is required. |
|
222 |
#' @return A [`Data`] object. |
|
223 |
#' |
|
224 |
#' @export |
|
225 |
h_convert_ordinal_data <- function(data_ord, grade) { |
|
226 |
# Validate |
|
227 | 27x |
assert_integer(grade, len = 1, lower = 1) |
228 | 27x |
assert_class(data_ord, "DataOrdinal") |
229 |
# Execute |
|
230 | 27x |
Data( |
231 | 27x |
ID = data_ord@ID, |
232 | 27x |
cohort = data_ord@cohort, |
233 | 27x |
x = data_ord@x, |
234 | 27x |
y = as.integer(data_ord@y >= grade), |
235 | 27x |
doseGrid = data_ord@doseGrid, |
236 | 27x |
nGrid = data_ord@nGrid, |
237 | 27x |
xLevel = data_ord@xLevel, |
238 | 27x |
placebo = data_ord@placebo |
239 |
) |
|
240 |
} |
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(x, ..., asis = TRUE, label = c("participant", "participants")) { |
|
36 | 57x |
assert_flag(asis) |
37 | ||
38 | 55x |
label <- h_prepare_labels(label) |
39 | 55x |
rv <- paste0("A constant size of ", x@size, " ", label[ifelse(x@size == 1, 1, 2)], ".\n\n") |
40 | 55x |
if (asis) { |
41 | 5x |
rv <- knitr::asis_output(rv) |
42 |
} |
|
43 | 55x |
rv |
44 |
} |
|
45 | ||
46 |
#' Render a `CohortSizeRange` Object |
|
47 |
#' |
|
48 |
#' @description `r lifecycle::badge("experimental")` |
|
49 |
#' @param ... passed to `knitr::kable` |
|
50 |
#' @inherit knit_print.CohortSizeConst return |
|
51 |
#' @section Usage Notes: |
|
52 |
#' The default value of `col.names` is `c("Lower", "Upper", "Cohort size")` and |
|
53 |
#' that of `caption` is `"Defined by the dose to be used in the next cohort"`. |
|
54 |
#' These values can be overridden by passing `col.names` and `caption` in the |
|
55 |
#' function call. |
|
56 |
#' @export |
|
57 |
#' @method knit_print CohortSizeRange |
|
58 |
#' @rdname knit_print |
|
59 |
knit_print.CohortSizeRange <- function(x, ..., asis = TRUE) { |
|
60 | 40x |
assert_flag(asis) |
61 | ||
62 | 38x |
param <- list(...) |
63 | 38x |
if (!("col.names" %in% names(param))) { |
64 | 38x |
param[["col.names"]] <- c("Lower", "Upper", "Cohort size") |
65 |
} |
|
66 | 38x |
if (!("caption" %in% names(param))) { |
67 | 38x |
param[["caption"]] <- "Defined by the dose to be used in the next cohort" |
68 |
} |
|
69 | 38x |
x <- tidy(x) |
70 | 38x |
param[["x"]] <- x |
71 | 38x |
rv <- kableExtra::add_header_above( |
72 | 38x |
do.call(knitr::kable, param), |
73 | 38x |
c("Dose" = 2, " " = 1) |
74 |
) |
|
75 | 38x |
rv <- paste0(rv, "\n\n") |
76 | ||
77 | 38x |
if (asis) { |
78 | 8x |
rv <- knitr::asis_output(rv) |
79 |
} |
|
80 | 38x |
rv |
81 |
} |
|
82 | ||
83 |
#' Render a `CohortSizeDLT` Object |
|
84 |
#' |
|
85 |
#' @description `r lifecycle::badge("experimental")` |
|
86 |
#' @inherit knit_print.CohortSizeConst return |
|
87 |
#' @param ... Passed to [knitr::kable()]. |
|
88 |
#' |
|
89 |
#' @section Usage Notes: |
|
90 |
#' The by default, the columns are labelled `Lower`, `Upper` and `Cohort size`. |
|
91 |
#' The table's caption is `Defined by the number of <tox_label[2]> so far observed`. |
|
92 |
#' These values can be overridden by passing `col.names` and `caption` in the |
|
93 |
#' function call. |
|
94 |
#' |
|
95 |
#' @export |
|
96 |
#' @method knit_print CohortSizeDLT |
|
97 |
#' @rdname knit_print |
|
98 |
knit_print.CohortSizeDLT <- function(x, ..., tox_label = "toxicity", asis = TRUE) { |
|
99 | 36x |
assert_flag(asis) |
100 | 34x |
param <- list(...) |
101 | 34x |
tox_label <- h_prepare_labels(tox_label) |
102 | ||
103 | 34x |
if (!("col.names" %in% names(param))) { |
104 | 34x |
param[["col.names"]] <- c("Lower", "Upper", "Cohort size") |
105 |
} |
|
106 | 34x |
if (!("caption" %in% names(param))) { |
107 | 34x |
param[["caption"]] <- paste0("Defined by the number of ", tox_label[2], " so far observed") |
108 |
} |
|
109 | 34x |
param[["x"]] <- tidy(x) |
110 | 34x |
headers <- c(2, 1) |
111 | 34x |
names(headers) <- c(paste0("No of ", tox_label[2]), " ") |
112 | 34x |
rv <- kableExtra::add_header_above( |
113 | 34x |
do.call(knitr::kable, param), |
114 | 34x |
headers |
115 |
) |
|
116 | 34x |
rv <- paste0(rv, "\n\n") |
117 | ||
118 | 34x |
if (asis) { |
119 | 6x |
rv <- knitr::asis_output(rv) |
120 |
} |
|
121 | 34x |
rv |
122 |
} |
|
123 | ||
124 |
#' Render a `CohortSizeParts` Object |
|
125 |
#' |
|
126 |
#' @description `r lifecycle::badge("experimental")` |
|
127 |
#' @inherit knit_print.CohortSizeConst return |
|
128 |
#' @inheritSection knit_print.CohortSizeConst Usage Notes |
|
129 |
#' |
|
130 |
#' @export |
|
131 |
#' @method knit_print CohortSizeParts |
|
132 |
#' @rdname knit_print |
|
133 |
knit_print.CohortSizeParts <- function(x, ..., asis = TRUE, label = c("participant", "participants")) { |
|
134 | 10x |
assert_flag(asis) |
135 | ||
136 | 8x |
label <- h_prepare_labels(label) |
137 | 8x |
rv <- paste0( |
138 | 8x |
"A size of ", |
139 | 8x |
x@cohort_sizes[1], |
140 |
" ", |
|
141 | 8x |
label[ifelse(x@cohort_sizes[1] == 1, 1, 2)], |
142 | 8x |
" in the first part and ", |
143 | 8x |
x@cohort_sizes[2], |
144 |
" ", |
|
145 | 8x |
label[ifelse(x@cohort_sizes[2] == 1, 1, 2)], |
146 | 8x |
" in the second.\n\n" |
147 |
) |
|
148 | 8x |
if (asis) { |
149 | 5x |
rv <- knitr::asis_output(rv) |
150 |
} |
|
151 | 8x |
rv |
152 |
} |
|
153 | ||
154 |
#' Render a `CohortSizeMax` Object |
|
155 |
#' |
|
156 |
#' @description `r lifecycle::badge("experimental")` |
|
157 |
#' @inherit knit_print.CohortSizeConst return |
|
158 |
#' @param ... passed through to the `knit_print` methods of the constituent |
|
159 |
#' rules |
|
160 |
#' |
|
161 |
#' @export |
|
162 |
#' @method knit_print CohortSizeMax |
|
163 |
#' @rdname knit_print |
|
164 |
knit_print.CohortSizeMax <- function(x, ..., asis = TRUE) { |
|
165 | 28x |
assert_flag(asis) |
166 | ||
167 | 26x |
params <- list(...) |
168 | 26x |
params[["asis"]] <- asis |
169 | 26x |
rv <- paste0( |
170 | 26x |
"The maximum of the cohort sizes defined in the following rules:", |
171 | 26x |
paste0( |
172 | 26x |
lapply( |
173 | 26x |
x@cohort_sizes, |
174 | 26x |
function(x) { |
175 | 52x |
knit_print(x, ..., asis = asis) |
176 |
} |
|
177 |
), |
|
178 | 26x |
collapse = "\n" |
179 |
), |
|
180 | 26x |
"\n\n", |
181 | 26x |
paste = "\n" |
182 |
) |
|
183 | ||
184 | 26x |
if (asis) { |
185 | 2x |
rv <- knitr::asis_output(rv) |
186 |
} |
|
187 | 26x |
rv |
188 |
} |
|
189 | ||
190 |
#' Render a `CohortSizeMin` Object |
|
191 |
#' |
|
192 |
#' @description `r lifecycle::badge("experimental")` |
|
193 |
#' @inherit knit_print.CohortSizeConst return |
|
194 |
#' @param ... passed through to the `knit_print` methods of the constituent |
|
195 |
#' rules |
|
196 |
#' |
|
197 |
#' @export |
|
198 |
#' @method knit_print CohortSizeMin |
|
199 |
#' @rdname knit_print |
|
200 |
knit_print.CohortSizeMin <- function(x, ..., asis = TRUE) { |
|
201 | 6x |
assert_flag(asis) |
202 | 4x |
rv <- paste0( |
203 | 4x |
"The minimum of the cohort sizes defined in the following rules:", |
204 | 4x |
paste0( |
205 | 4x |
lapply( |
206 | 4x |
x@cohort_sizes, |
207 | 4x |
function(x, ...) { |
208 | 8x |
knit_print(x, asis = asis, ...) |
209 |
} |
|
210 |
), |
|
211 | 4x |
collapse = "\n" |
212 |
), |
|
213 | 4x |
"\n\n", |
214 | 4x |
sep = "\n" |
215 |
) |
|
216 | 4x |
if (asis) { |
217 | 2x |
rv <- knitr::asis_output(rv) |
218 |
} |
|
219 | 4x |
rv |
220 |
} |
|
221 | ||
222 |
#' Render a `CohortSizeOrdinal` Object |
|
223 |
#' |
|
224 |
#' @description `r lifecycle::badge("experimental")` |
|
225 |
#' @inherit knit_print.CohortSizeConst return |
|
226 |
#' @param ... passed through to the `knit_print` method of the standard rule |
|
227 |
#' |
|
228 |
#' @export |
|
229 |
#' @method knit_print CohortSizeOrdinal |
|
230 |
#' @rdname knit_print |
|
231 |
knit_print.CohortSizeOrdinal <- function(x, ..., tox_label = "toxicity", asis = TRUE) { |
|
232 | 21x |
assert_flag(asis) |
233 | 19x |
tox_label <- h_prepare_labels(tox_label) |
234 | ||
235 | 19x |
rv <- paste0( |
236 | 19x |
"Based on a ", |
237 | 19x |
tox_label[1], |
238 | 19x |
" grade of ", |
239 | 19x |
x@grade, |
240 |
": ", |
|
241 | 19x |
paste0(knit_print(x@rule, asis = asis, ...), collapse = "\n"), |
242 | 19x |
"\n\n", |
243 | 19x |
sep = "\n" |
244 |
) |
|
245 | ||
246 | 19x |
if (asis) { |
247 | 2x |
rv <- knitr::asis_output(rv) |
248 |
} |
|
249 | 19x |
rv |
250 |
} |
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(x, |
|
63 |
y, |
|
64 |
blind = FALSE, |
|
65 |
legend = TRUE, |
|
66 |
tox_labels = NULL, |
|
67 |
tox_shapes = NULL, |
|
68 |
...) { |
|
69 | 2x |
if (is.null(tox_shapes)) { |
70 | 2x |
assert_true(length(x@yCategories) <= 9) |
71 | 2x |
tox_shapes <- c(17L, 16L, 15L, 18L, 0L:2L, 5L, 6L)[seq_along(x@yCategories)] |
72 | 2x |
names(tox_shapes) <- names(x@yCategories) |
73 |
} |
|
74 | 2x |
if (is.null(tox_labels)) { |
75 | 2x |
assert_true(length(x@yCategories) <= 5) |
76 | 2x |
tox_labels <- switch(length(x@yCategories), |
77 | 2x |
c("black"), |
78 | 2x |
c("black", "red"), |
79 | 2x |
c("black", "orange", "red"), |
80 | 2x |
c("black", "green", "orange", "red"), |
81 | 2x |
c("black", "green", "yellow", "orange", "red") |
82 |
) |
|
83 | 2x |
names(tox_labels) <- names(x@yCategories) |
84 |
} |
|
85 | 2x |
h_plot_data_dataordinal( |
86 | 2x |
x, |
87 | 2x |
blind, |
88 | 2x |
legend, |
89 | 2x |
tox_labels = tox_labels, |
90 | 2x |
tox_shapes = tox_shapes, |
91 |
... |
|
92 |
) |
|
93 |
} |
|
94 |
) |
|
95 | ||
96 |
## DataDual ---- |
|
97 | ||
98 |
#' Plot Method for the [`DataDual`] Class |
|
99 |
#' |
|
100 |
#' @description `r lifecycle::badge("stable")` |
|
101 |
#' |
|
102 |
#' A method that creates a plot for [`DataDual`] object. |
|
103 |
#' |
|
104 |
#' @param x (`DataDual`)\cr object we want to plot. |
|
105 |
#' @param y (`missing`)\cr missing object, for compatibility with the generic |
|
106 |
#' function. |
|
107 |
#' @param blind (`flag`)\cr indicates whether to blind the data. |
|
108 |
#' If `TRUE`, then placebo subjects are reported at the same level |
|
109 |
#' as the active dose level in the corresponding cohort, |
|
110 |
#' and DLTs are always assigned to the first subjects in a cohort. |
|
111 |
#' @param ... passed to the first inherited method `plot` after this current |
|
112 |
#' method. |
|
113 |
#' |
|
114 |
#' @return The [`ggplot2`] object. |
|
115 |
#' |
|
116 |
#' @aliases plot-DataDual |
|
117 |
#' @export |
|
118 |
#' @example examples/Data-method-plot-DataDual.R |
|
119 |
#' |
|
120 |
setMethod( |
|
121 |
f = "plot", |
|
122 |
signature = signature(x = "DataDual", y = "missing"), |
|
123 |
definition = function(x, y, blind = FALSE, ...) { |
|
124 | 2x |
assert_flag(blind) |
125 | ||
126 |
# Call the superclass method, to get the first plot. |
|
127 | 2x |
plot1 <- callNextMethod(x, blind = blind, legend = FALSE, ...) |
128 | ||
129 |
# Create the second, biomarker plot. |
|
130 | 2x |
df <- h_plot_data_df(x, blind, biomarker = x@w) |
131 | ||
132 | 2x |
plot2 <- ggplot(df, aes(x = dose, y = biomarker)) + |
133 | 2x |
geom_point(aes(shape = toxicity, colour = toxicity), size = 3) + |
134 | 2x |
scale_colour_manual( |
135 | 2x |
name = "Toxicity", values = c(Yes = "red", No = "black") |
136 |
) + |
|
137 | 2x |
scale_shape_manual(name = "Toxicity", values = c(Yes = 17, No = 16)) + |
138 | 2x |
xlab("Dose Level") + |
139 | 2x |
ylab("Biomarker") |
140 | ||
141 | 2x |
if (!blind) { |
142 | 1x |
plot2 <- plot2 + |
143 | 1x |
geom_text( |
144 | 1x |
aes( |
145 | 1x |
y = biomarker + 0.02 * diff(range(biomarker)), |
146 | 1x |
label = patient, size = 2 |
147 |
), |
|
148 | 1x |
data = df, |
149 | 1x |
hjust = 0, |
150 | 1x |
vjust = 0.5, |
151 | 1x |
angle = 90, |
152 | 1x |
colour = "black", |
153 | 1x |
show.legend = FALSE |
154 |
) |
|
155 |
} |
|
156 | ||
157 |
# Arrange both plots side by side. |
|
158 | 2x |
gridExtra::arrangeGrob(plot1, plot2, ncol = 2) |
159 |
} |
|
160 |
) |
|
161 | ||
162 |
## DataDA ---- |
|
163 | ||
164 |
#' Plot Method for the [`DataDA`] Class |
|
165 |
#' |
|
166 |
#' @description `r lifecycle::badge("stable")` |
|
167 |
#' |
|
168 |
#' A method that creates a plot for [`DataDA`] object. |
|
169 |
#' |
|
170 |
#' @param x (`DataDA`)\cr object we want to plot. |
|
171 |
#' @param y (`missing`)\cr missing object, for compatibility with the generic |
|
172 |
#' function. |
|
173 |
#' @param blind (`flag`)\cr indicates whether to blind the data. |
|
174 |
#' If `TRUE`, then placebo subjects are reported at the same level |
|
175 |
#' as the active dose level in the corresponding cohort, |
|
176 |
#' and DLTs are always assigned to the first subjects in a cohort. |
|
177 |
#' @param ... passed to the first inherited method `plot` after this current |
|
178 |
#' method. |
|
179 |
#' |
|
180 |
#' @return The [`ggplot2`] object. |
|
181 |
#' |
|
182 |
#' @aliases plot-DataDA |
|
183 |
#' @export |
|
184 |
#' @example examples/Data-method-plot-DataDA.R |
|
185 |
#' |
|
186 |
setMethod( |
|
187 |
f = "plot", |
|
188 |
signature = signature(x = "DataDA", y = "missing"), |
|
189 |
definition = function(x, y, blind = FALSE, ...) { |
|
190 | 2x |
assert_flag(blind) |
191 | ||
192 |
# Call the superclass method, to get the first plot. |
|
193 | 2x |
plot1 <- callNextMethod(x, blind = blind, legend = FALSE, ...) |
194 | ||
195 |
# Prepare data set for the second, time plot. |
|
196 | 2x |
df <- h_plot_data_df(x, blind, u = x@u, t0 = x@t0) |
197 | 2x |
df$censored <- ifelse(df$u < x@Tmax & df$toxicity == 0, 1, 0) |
198 | 2x |
df$tend <- df$t0 + df$u # `tend` stands for `time end` |
199 | 2x |
df$t0_case <- "Start" |
200 | 2x |
df$tend_case <- ifelse( |
201 | 2x |
df$toxicity == "Yes", |
202 | 2x |
"Yes", |
203 | 2x |
ifelse(df$censored, "Censored", "No") |
204 |
) |
|
205 | ||
206 |
# Build plot object. |
|
207 | 2x |
plot2 <- ggplot(df, aes(x = t0, y = patient)) + |
208 | 2x |
geom_segment(aes(xend = tend, yend = patient)) + |
209 | 2x |
geom_point(aes(shape = t0_case, colour = t0_case), size = 3) + |
210 | 2x |
geom_point( |
211 | 2x |
aes(x = tend, shape = tend_case, colour = tend_case), |
212 | 2x |
size = 3 |
213 |
) + |
|
214 | 2x |
scale_colour_manual( |
215 | 2x |
name = "Toxicity", |
216 | 2x |
values = c( |
217 | 2x |
Yes = "red", No = "black", Start = "black", Censored = "black" |
218 |
) |
|
219 |
) + |
|
220 | 2x |
scale_shape_manual( |
221 | 2x |
name = "Toxicity", |
222 | 2x |
values = c(Yes = 17, No = 16, Start = 1, Censored = 4) |
223 |
) + |
|
224 | 2x |
scale_y_continuous(breaks = df$patient, minor_breaks = NULL) + |
225 | 2x |
xlab("Time") + |
226 | 2x |
ylab("Patient") |
227 | ||
228 | 2x |
plot2 <- plot2 + |
229 | 2x |
h_plot_data_cohort_lines(df$cohort, placebo = x@placebo, vertical = FALSE) |
230 | ||
231 | 2x |
if (!blind) { |
232 | 1x |
plot2 <- plot2 + |
233 | 1x |
geom_text( |
234 | 1x |
aes(label = ID, size = 2), |
235 | 1x |
size = 3, |
236 | 1x |
hjust = 1.5, |
237 | 1x |
vjust = 0, |
238 | 1x |
angle = 0, |
239 | 1x |
colour = "black", |
240 | 1x |
show.legend = FALSE |
241 |
) |
|
242 |
} |
|
243 | ||
244 |
# Arrange both plots side by side. |
|
245 | 2x |
gridExtra::arrangeGrob(plot1, plot2, ncol = 1) |
246 |
} |
|
247 |
) |
|
248 | ||
249 |
# update ---- |
|
250 | ||
251 |
## Data ---- |
|
252 | ||
253 |
#' Updating `Data` Objects |
|
254 |
#' |
|
255 |
#' @description `r lifecycle::badge("stable")` |
|
256 |
#' |
|
257 |
#' A method that updates existing [`Data`] object with new data. |
|
258 |
#' |
|
259 |
#' @param object (`Data`)\cr object you want to update. |
|
260 |
#' @param x (`number`)\cr the dose level (one level only!). |
|
261 |
#' @param y (`integer`)\cr the DLT vector (0/1 vector) for all patients in this |
|
262 |
#' cohort. You can also supply `numeric` vectors, but these will then be |
|
263 |
#' converted to `integer` internally. |
|
264 |
#' @param ID (`integer`)\cr the patient IDs. |
|
265 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
266 |
#' `integer` internally. |
|
267 |
#' @param new_cohort (`flag`)\cr if `TRUE` (default) the new data are assigned |
|
268 |
#' to a new cohort. |
|
269 |
#' @param check (`flag`)\cr whether the validation of the updated object should |
|
270 |
#' be conducted. See details below. |
|
271 |
#' @param ... not used. |
|
272 |
#' |
|
273 |
#' @return The new, updated [`Data`] object. |
|
274 |
#' |
|
275 |
#' @details The current implementation of this `update` method allows for |
|
276 |
#' updating the `Data` class object by adding a single dose level `x` only. |
|
277 |
#' However, there might be some use cases where the new cohort to be added |
|
278 |
#' contains a placebo and active dose. Hence, such update would need to be |
|
279 |
#' performed iteratively by calling the `update` method twice. For example, |
|
280 |
#' in the first call a user can add a placebo, and then in the second call, |
|
281 |
#' an active dose. Since having a cohort with placebo only is not allowed, |
|
282 |
#' the `update` method would normally throw the error when attempting to add |
|
283 |
#' a placebo in the first call. To allow for such updates, the `check` |
|
284 |
#' parameter should be then set to `FALSE` for that first call. |
|
285 |
#' |
|
286 |
#' @aliases update-Data |
|
287 |
#' @export |
|
288 |
#' @example examples/Data-method-update.R |
|
289 |
#' |
|
290 |
setMethod( |
|
291 |
f = "update", |
|
292 |
signature = signature(object = "Data"), |
|
293 |
definition = function(object, |
|
294 |
x, |
|
295 |
y, |
|
296 |
ID = length(object@ID) + seq_along(y), |
|
297 |
new_cohort = TRUE, |
|
298 |
check = TRUE, |
|
299 |
...) { |
|
300 | 430x |
assert_numeric(x, min.len = 0, max.len = 1) |
301 | 430x |
assert_integerish(y, lower = 0, upper = 1, any.missing = FALSE) |
302 | 430x |
assert_integerish(ID, len = length(y), any.missing = FALSE) |
303 | 430x |
assert_disjunct(object@ID, ID) |
304 | 430x |
assert_flag(new_cohort) |
305 | 430x |
assert_flag(check) |
306 | ||
307 |
# How many additional patients, ie. the length of the update. |
|
308 | 430x |
n <- length(y) |
309 | ||
310 |
# Which grid level is the dose? |
|
311 | 430x |
gridLevel <- match_within_tolerance(x, object@doseGrid) |
312 | 430x |
object@xLevel <- c(object@xLevel, rep(gridLevel, n)) |
313 | ||
314 |
# Add dose. |
|
315 | 430x |
object@x <- c(object@x, rep(as.numeric(x), n)) |
316 | ||
317 |
# Add DLT data. |
|
318 | 430x |
object@y <- c(object@y, as.integer(y)) |
319 | ||
320 |
# Add ID. |
|
321 | 430x |
object@ID <- c(object@ID, as.integer(ID)) |
322 | ||
323 |
# Add cohort number. |
|
324 | 430x |
new_cohort_id <- if (object@nObs == 0) { |
325 | 67x |
1L |
326 |
} else { |
|
327 | 363x |
tail(object@cohort, 1L) + ifelse(new_cohort, 1L, 0L) |
328 |
} |
|
329 | 430x |
object@cohort <- c(object@cohort, rep(new_cohort_id, n)) |
330 | ||
331 |
# Increment sample size. |
|
332 | 430x |
object@nObs <- object@nObs + n |
333 | ||
334 | 430x |
if (check) { |
335 | 358x |
validObject(object) |
336 |
} |
|
337 | ||
338 | 429x |
object |
339 |
} |
|
340 |
) |
|
341 | ||
342 |
## DataOrdinal ---- |
|
343 | ||
344 |
#' Updating `DataOrdinal` Objects |
|
345 |
#' |
|
346 |
#' @description `r lifecycle::badge("experimental")` |
|
347 |
#' |
|
348 |
#' A method that updates existing [`DataOrdinal`] object with new data. |
|
349 |
#' |
|
350 |
#' @param object (`DataOrdinal`)\cr object you want to update. |
|
351 |
#' @param x (`number`)\cr the dose level (one level only!). |
|
352 |
#' @param y (`integer`)\cr the vector of toxicity grades (0, 1, 2, ...) for all |
|
353 |
#' patients in this cohort. You can also supply `numeric` vectors, but these |
|
354 |
#' will then be converted to `integer` internally. |
|
355 |
#' @param ID (`integer`)\cr the patient IDs. |
|
356 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
357 |
#' `integer` internally. |
|
358 |
#' @param new_cohort (`flag`)\cr if `TRUE` (default) the new data are assigned |
|
359 |
#' to a new cohort. |
|
360 |
#' @param check (`flag`)\cr whether the validation of the updated object should |
|
361 |
#' be conducted. See Details below. |
|
362 |
#' @param ... not used. |
|
363 |
#' |
|
364 |
#' @return The new, updated [`DataOrdinal`] object. |
|
365 |
#' |
|
366 |
#' @details The current implementation of this `update` method allows for |
|
367 |
#' updating the `DataOrdinal` class object by adding a single dose level `x` only. |
|
368 |
#' However, there might be some use cases where the new cohort to be added |
|
369 |
#' contains a placebo and active dose. Hence, such update would need to be |
|
370 |
#' performed iteratively by calling the `update` method twice. For example, |
|
371 |
#' in the first call a user can add a placebo, and then in the second call, |
|
372 |
#' an active dose. Since having a cohort with placebo only is not allowed, |
|
373 |
#' the `update` method would normally throw the error when attempting to add |
|
374 |
#' a placebo in the first call. To allow for such updates, the `check` |
|
375 |
#' parameter should be then set to `FALSE` for that first call. |
|
376 |
#' |
|
377 |
#' @aliases update-DataOrdinal |
|
378 |
#' @export |
|
379 |
#' @example examples/DataOrdinal-method-update.R |
|
380 |
#' |
|
381 |
setMethod( |
|
382 |
f = "update", |
|
383 |
signature = signature(object = "DataOrdinal"), |
|
384 |
definition = function(object, |
|
385 |
x, |
|
386 |
y, |
|
387 |
ID = length(object@ID) + seq_along(y), |
|
388 |
new_cohort = TRUE, |
|
389 |
check = TRUE, |
|
390 |
...) { |
|
391 | 5x |
assert_numeric(x, min.len = 0, max.len = 1) |
392 | 5x |
assert_integerish(y, lower = 0, upper = length(object@yCategories) - 1, any.missing = FALSE) |
393 | 5x |
assert_integerish(ID, unique = TRUE, any.missing = FALSE, len = length(y)) |
394 | 5x |
assert_disjunct(object@ID, ID) |
395 | 5x |
assert_flag(new_cohort) |
396 | 5x |
assert_flag(check) |
397 | ||
398 |
# How many additional patients, ie. the length of the update. |
|
399 | 5x |
n <- length(y) |
400 | ||
401 |
# Which grid level is the dose? |
|
402 | 5x |
gridLevel <- match_within_tolerance(x, object@doseGrid) |
403 | 5x |
object@xLevel <- c(object@xLevel, rep(gridLevel, n)) |
404 | ||
405 |
# Add dose. |
|
406 | 5x |
object@x <- c(object@x, rep(as.numeric(x), n)) |
407 | ||
408 |
# Add DLT data. |
|
409 | 5x |
object@y <- c(object@y, as.integer(y)) |
410 | ||
411 |
# Add ID. |
|
412 | 5x |
object@ID <- c(object@ID, as.integer(ID)) |
413 | ||
414 |
# Add cohort number. |
|
415 | 5x |
new_cohort_id <- if (object@nObs == 0) { |
416 | 1x |
1L |
417 |
} else { |
|
418 | 4x |
tail(object@cohort, 1L) + ifelse(new_cohort, 1L, 0L) |
419 |
} |
|
420 | 5x |
object@cohort <- c(object@cohort, rep(new_cohort_id, n)) |
421 | ||
422 |
# Increment sample size. |
|
423 | 5x |
object@nObs <- object@nObs + n |
424 | ||
425 | 5x |
if (check) { |
426 | 4x |
validObject(object) |
427 |
} |
|
428 | ||
429 | 4x |
object |
430 |
} |
|
431 |
) |
|
432 | ||
433 |
## DataParts ---- |
|
434 | ||
435 |
#' Updating `DataParts` Objects |
|
436 |
#' |
|
437 |
#' @description `r lifecycle::badge("stable")` |
|
438 |
#' |
|
439 |
#' A method that updates existing [`DataParts`] object with new data. |
|
440 |
#' |
|
441 |
#' @param object (`DataParts`)\cr object you want to update. |
|
442 |
#' @inheritParams update,Data-method |
|
443 |
#' @param ... further arguments passed to `Data` update method [`update-Data`]. |
|
444 |
#' @param check (`flag`)\cr whether the validation of the updated object |
|
445 |
#' should be conducted. See help for [`update-Data`] for more details |
|
446 |
#' on the use case of this parameter. |
|
447 |
#' |
|
448 |
#' @return The new, updated [`DataParts`] object. |
|
449 |
#' |
|
450 |
#' @aliases update-DataParts |
|
451 |
#' @export |
|
452 |
#' @example examples/Data-method-update-DataParts.R |
|
453 |
#' |
|
454 |
setMethod( |
|
455 |
f = "update", |
|
456 |
signature = signature(object = "DataParts"), |
|
457 |
definition = function(object, x, y, ..., check = TRUE) { |
|
458 | 3x |
assert_numeric(y) |
459 | 3x |
assert_flag(check) |
460 | ||
461 |
# Update slots corresponding to `Data` class. |
|
462 | 3x |
object <- callNextMethod(object = object, x = x, y = y, ..., check = FALSE) |
463 | ||
464 |
# Update the part information. |
|
465 | ||
466 | 3x |
object@part <- c(object@part, rep(object@nextPart, length(y))) |
467 | ||
468 |
# Decide which part the next cohort will belong to: |
|
469 |
# only if the `nextPart` was 1, it can potentially be required |
|
470 |
# to change it to 2 (once it is 2, it stays). |
|
471 | 3x |
if (object@nextPart == 1L) { |
472 |
# If there was a DLT in one of the cohorts, |
|
473 |
# or if the current dose was the highest from part 1. |
|
474 | 3x |
if (any(object@y == 1L) || x == max(object@part1Ladder)) { |
475 |
# Then this closes part 1 and the next cohort will be from part 2. |
|
476 | 3x |
object@nextPart <- 2L |
477 |
} |
|
478 |
} |
|
479 | ||
480 | 3x |
if (check) { |
481 | 3x |
validObject(object) |
482 |
} |
|
483 | ||
484 | 3x |
object |
485 |
} |
|
486 |
) |
|
487 | ||
488 |
## DataDual ---- |
|
489 | ||
490 |
#' Updating `DataDual` Objects |
|
491 |
#' |
|
492 |
#' @description `r lifecycle::badge("stable")` |
|
493 |
#' |
|
494 |
#' A method that updates existing [`DataDual`] object with new data. |
|
495 |
#' |
|
496 |
#' @param object (`DataDual`)\cr object you want to update. |
|
497 |
#' @param w (`numeric`)\cr the continuous vector of biomarker values |
|
498 |
#' for all the patients in this update. |
|
499 |
#' @param ... further arguments passed to `Data` update method [`update-Data`]. |
|
500 |
#' @param check (`flag`)\cr whether the validation of the updated object |
|
501 |
#' should be conducted. See help for [`update-Data`] for more details |
|
502 |
#' on the use case of this parameter. |
|
503 |
#' |
|
504 |
#' @return The new, updated [`DataDual`] object. |
|
505 |
#' |
|
506 |
#' @aliases update-DataDual |
|
507 |
#' @export |
|
508 |
#' @example examples/Data-method-update-DataDual.R |
|
509 |
#' |
|
510 |
setMethod( |
|
511 |
f = "update", |
|
512 |
signature = signature(object = "DataDual"), |
|
513 |
definition = function(object, w, ..., check = TRUE) { |
|
514 | 21x |
assert_numeric(w) |
515 | 21x |
assert_flag(check) |
516 | ||
517 |
# Update slots corresponding to `Data` class. |
|
518 | 21x |
object <- callNextMethod(object = object, ..., check = FALSE) |
519 | ||
520 |
# Update the biomarker information. |
|
521 | 21x |
object@w <- c(object@w, w) |
522 | ||
523 | 21x |
if (check) { |
524 | 21x |
validObject(object) |
525 |
} |
|
526 | ||
527 | 21x |
object |
528 |
} |
|
529 |
) |
|
530 | ||
531 |
## DataDA ---- |
|
532 | ||
533 |
#' Updating `DataDA` Objects |
|
534 |
#' |
|
535 |
#' @description `r lifecycle::badge("stable")` |
|
536 |
#' |
|
537 |
#' A method that updates existing [`DataDA`] object with new data. |
|
538 |
#' |
|
539 |
#' @note This function is capable of not only adding new patients but also |
|
540 |
#' updates existing ones with respect to `y`, `t0`, `u` slots. |
|
541 |
#' |
|
542 |
#' @param object (`DataDA`)\cr object you want to update. |
|
543 |
#' @param u (`numeric`)\cr the new DLT free survival times for all patients, |
|
544 |
#' i.e. for existing patients in the `object` as well as for new patients. |
|
545 |
#' @param t0 (`numeric`)\cr the time that each patient starts DLT observation |
|
546 |
#' window. This parameter covers all patients, i.e. existing patients in the |
|
547 |
#' `object` as well as for new patients. |
|
548 |
#' @param trialtime (`number`)\cr current time in the trial, i.e. a followup |
|
549 |
#' time. |
|
550 |
#' @param y (`numeric`)\cr the new DLTs for all patients, i.e. for existing |
|
551 |
#' patients in the `object` as well as for new patients. |
|
552 |
#' @param ... further arguments passed to `Data` update method [`update-Data`]. |
|
553 |
#' These are used when there are new patients to be added to the cohort. |
|
554 |
#' @param check (`flag`)\cr whether the validation of the updated object |
|
555 |
#' should be conducted. See help for [`update-Data`] for more details |
|
556 |
#' on the use case of this parameter. |
|
557 |
#' |
|
558 |
#' @return The new, updated [`DataDA`] object. |
|
559 |
#' |
|
560 |
#' @aliases update-DataDA |
|
561 |
#' @export |
|
562 |
#' @example examples/Data-method-update-DataDA.R |
|
563 |
#' |
|
564 |
setMethod( |
|
565 |
f = "update", |
|
566 |
signature = signature(object = "DataDA"), |
|
567 |
definition = function(object, |
|
568 |
u, |
|
569 |
t0, |
|
570 |
trialtime, |
|
571 |
y, |
|
572 |
..., |
|
573 |
check = TRUE) { |
|
574 | 17x |
assert_flag(check) |
575 | 17x |
assert_numeric(y, lower = 0, upper = 1) |
576 | 17x |
assert_true(length(y) == 0 || length(y) >= object@nObs) |
577 | 17x |
assert_numeric(u, lower = 0, len = length(y)) |
578 | 17x |
assert_numeric(t0, lower = 0, len = length(y)) |
579 | 17x |
assert_integerish(y * (trialtime >= t0 + u)) |
580 | 17x |
if (length(y) > 0) { |
581 | 16x |
assert_number(trialtime, lower = max(c(object@t0, t0))) |
582 |
} |
|
583 | ||
584 |
# How many additional patients. |
|
585 | 16x |
n <- max(length(y) - object@nObs, 0L) |
586 | ||
587 |
# Update slots corresponding to `Data` class. |
|
588 | 16x |
object <- callNextMethod( |
589 | 16x |
object = object, |
590 | 16x |
y = y[object@nObs + seq_len(n)], # Empty vector when n = 0. |
591 |
..., |
|
592 | 16x |
check = FALSE |
593 |
) |
|
594 | ||
595 |
# DLT will be observed once the followup time >= the time to DLT |
|
596 |
# and y = 1 at the same time. |
|
597 | 16x |
object@y <- as.integer(y * (trialtime >= t0 + u)) |
598 | ||
599 |
# Update DLT free survival time. |
|
600 | 16x |
object@u <- apply(rbind(u, trialtime - t0), 2, min) |
601 | ||
602 |
# Update t0. |
|
603 | 16x |
object@t0 <- t0 |
604 | ||
605 | 16x |
if (check) { |
606 | 16x |
validObject(object) |
607 |
} |
|
608 | ||
609 | 16x |
object |
610 |
} |
|
611 |
) |
|
612 | ||
613 |
# getEff ---- |
|
614 | ||
615 |
## generic ---- |
|
616 | ||
617 |
#' Extracting Efficacy Responses for Subjects Categorized by the DLT |
|
618 |
#' |
|
619 |
#' @description `r lifecycle::badge("stable")` |
|
620 |
#' |
|
621 |
#' A method that extracts efficacy responses for subjects and categorizes it |
|
622 |
#' with respect to DLT, i.e. DLT or no DLT. The efficacy responses |
|
623 |
#' are reported together with their corresponding dose levels. |
|
624 |
#' |
|
625 |
#' @param object (`DataDual`)\cr object from which the responses and dose levels |
|
626 |
#' are extracted. |
|
627 |
#' @param ... further arguments passed to class-specific methods. |
|
628 |
#' @return `list` with efficacy responses categorized by the DLT value. |
|
629 |
#' @export |
|
630 |
#' |
|
631 |
setGeneric( |
|
632 |
name = "getEff", |
|
633 |
def = function(object, ...) { |
|
634 | 209x |
standardGeneric("getEff") |
635 |
}, |
|
636 |
valueClass = "list" |
|
637 |
) |
|
638 | ||
639 |
## DataDual ---- |
|
640 | ||
641 |
#' @rdname getEff |
|
642 |
#' |
|
643 |
#' @param no_dlt (`flag`)\cr should only no DLT responses be returned? Otherwise, |
|
644 |
#' all responses are returned. |
|
645 |
#' |
|
646 |
#' @aliases getEff-DataDual |
|
647 |
#' @example examples/Data-method-getEff.R |
|
648 |
#' |
|
649 |
setMethod( |
|
650 |
f = "getEff", |
|
651 |
signature = signature(object = "DataDual"), |
|
652 |
definition = function(object, no_dlt = FALSE) { |
|
653 | 209x |
assert_flag(no_dlt) |
654 | ||
655 | 209x |
is_dlt <- object@y == 1L |
656 | 209x |
is_no_dlt <- !is_dlt |
657 | ||
658 | 209x |
eff <- if (any(is_no_dlt)) { |
659 | 150x |
list(x_no_dlt = object@x[is_no_dlt], w_no_dlt = object@w[is_no_dlt]) |
660 |
} else { |
|
661 | 59x |
list(x_no_dlt = NULL, w_no_dlt = NULL) |
662 |
} |
|
663 | ||
664 | 209x |
if (!no_dlt) { |
665 | 3x |
eff_dlt <- if (any(is_dlt)) { |
666 | 2x |
list(x_dlt = object@x[is_dlt], w_dlt = object@w[is_dlt]) |
667 |
} else { |
|
668 | 1x |
list(x_dlt = NULL, w_dlt = NULL) |
669 |
} |
|
670 | 3x |
eff <- c(eff, eff_dlt) |
671 |
} |
|
672 | 209x |
eff |
673 |
} |
|
674 |
) |
|
675 | ||
676 |
# ngrid ---- |
|
677 | ||
678 |
## generic ---- |
|
679 | ||
680 |
#' Number of Doses in Grid |
|
681 |
#' |
|
682 |
#' @description `r lifecycle::badge("stable")` |
|
683 |
#' |
|
684 |
#' A function that gets the number of doses in grid. User can choose whether |
|
685 |
#' the placebo dose (if any) should be counted or not. |
|
686 |
#' |
|
687 |
#' @param object (`Data`)\cr object with dose grid. |
|
688 |
#' @param ignore_placebo (`flag`)\cr should placebo dose (if any) not be counted? |
|
689 |
#' @param ... further arguments passed to class-specific methods. |
|
690 |
#' @return `integer` the number of doses in grid. |
|
691 |
#' @export |
|
692 |
#' |
|
693 |
setGeneric( |
|
694 |
name = "ngrid", |
|
695 |
def = function(object, ignore_placebo = TRUE, ...) { |
|
696 | 18x |
assert_flag(ignore_placebo) |
697 | 16x |
standardGeneric("ngrid") |
698 |
}, |
|
699 |
valueClass = "integer" |
|
700 |
) |
|
701 | ||
702 |
## Data ---- |
|
703 | ||
704 |
#' @rdname ngrid |
|
705 |
#' |
|
706 |
#' @aliases ngrid-Data |
|
707 |
#' @example examples/Data-method-ngrid.R |
|
708 |
#' |
|
709 |
setMethod( |
|
710 |
f = "ngrid", |
|
711 |
signature = signature(object = "Data"), |
|
712 |
definition = function(object, ignore_placebo, ...) { |
|
713 | 16x |
if (ignore_placebo && object@placebo) { |
714 | 4x |
max(object@nGrid - 1L, 0L) |
715 |
} else { |
|
716 | 12x |
object@nGrid |
717 |
} |
|
718 |
} |
|
719 |
) |
|
720 | ||
721 |
# dose_grid_range ---- |
|
722 | ||
723 |
## generic ---- |
|
724 | ||
725 |
#' Getting the Dose Grid Range |
|
726 |
#' |
|
727 |
#' @description `r lifecycle::badge("stable")` |
|
728 |
#' |
|
729 |
#' A function that returns a vector of length two with the minimum and maximum |
|
730 |
#' dose in a grid. It returns `c(-Inf, Inf)` if the range cannot be determined, |
|
731 |
#' which happens when the dose grid is empty. User can choose whether the |
|
732 |
#' placebo dose (if any) should be counted or not. |
|
733 |
#' |
|
734 |
#' @param object (`Data`)\cr object with dose grid. |
|
735 |
#' @param ... further arguments passed to class-specific methods. |
|
736 |
#' @return A `numeric` vector containing the minimum and maximum of all the |
|
737 |
#' doses in a grid or `c(-Inf, Inf)`. |
|
738 |
#' |
|
739 |
#' @export |
|
740 |
#' |
|
741 |
setGeneric( |
|
742 |
name = "dose_grid_range", |
|
743 |
def = function(object, ...) { |
|
744 | 168x |
standardGeneric("dose_grid_range") |
745 |
}, |
|
746 |
valueClass = "numeric" |
|
747 |
) |
|
748 | ||
749 |
## Data ---- |
|
750 | ||
751 |
#' @rdname dose_grid_range |
|
752 |
#' |
|
753 |
#' @param ignore_placebo (`flag`)\cr should placebo dose (if any) not be counted? |
|
754 |
#' |
|
755 |
#' @aliases dose_grid_range-Data |
|
756 |
#' @example examples/Data-method-dose_grid_range.R |
|
757 |
#' |
|
758 |
setMethod( |
|
759 |
f = "dose_grid_range", |
|
760 |
signature = signature(object = "Data"), |
|
761 |
definition = function(object, ignore_placebo = TRUE) { |
|
762 | 152x |
h_obtain_dose_grid_range(object, ignore_placebo) |
763 |
} |
|
764 |
) |
|
765 | ||
766 | ||
767 |
## DataOrdinal ---- |
|
768 | ||
769 |
#' @include Data-methods.R |
|
770 |
#' @rdname dose_grid_range |
|
771 |
#' @description `r lifecycle::badge("experimental")` |
|
772 |
#' |
|
773 |
#' @param ignore_placebo (`flag`)\cr should placebo dose (if any) not be counted? |
|
774 |
#' |
|
775 |
#' @aliases dose_grid_range-Data |
|
776 |
#' @example examples/DataOrdinal-method-dose_grid_range.R |
|
777 |
#' |
|
778 |
setMethod( |
|
779 |
f = "dose_grid_range", |
|
780 |
signature = signature(object = "DataOrdinal"), |
|
781 |
definition = function(object, ignore_placebo = TRUE) { |
|
782 | 16x |
h_obtain_dose_grid_range(object, ignore_placebo) |
783 |
} |
|
784 |
) |
|
785 | ||
786 |
# tidy ---- |
|
787 | ||
788 |
## GeneralData ---- |
|
789 | ||
790 |
#' Tidy Method for the [`GeneralData`] Class |
|
791 |
#' |
|
792 |
#' @description `r lifecycle::badge("experimental")` |
|
793 |
#' |
|
794 |
#' A method that tidies a [`GeneralData`] object. |
|
795 |
#' |
|
796 |
#' @return The [`tibble`] object. |
|
797 |
#' |
|
798 |
#' @aliases tidy-GeneralData |
|
799 |
#' @rdname tidy |
|
800 |
#' @export |
|
801 |
#' @example examples/GeneralData-method-tidy.R |
|
802 |
#' |
|
803 |
setMethod( |
|
804 |
f = "tidy", |
|
805 |
signature = signature(x = "GeneralData"), |
|
806 |
definition = function(x, ...) { |
|
807 | 196x |
d <- tibble::tibble( |
808 | 196x |
ID = x@ID, |
809 | 196x |
Cohort = x@cohort, |
810 | 196x |
Dose = x@x, |
811 | 196x |
XLevel = x@xLevel, |
812 | 196x |
Tox = as.logical(x@y), |
813 | 196x |
Placebo = x@placebo, |
814 | 196x |
NObs = x@nObs, |
815 | 196x |
NGrid = x@nGrid, |
816 | 196x |
DoseGrid = list(x@doseGrid) |
817 | 196x |
) %>% h_tidy_class(x) |
818 |
} |
|
819 |
) |
|
820 | ||
821 |
## DataGrouped ---- |
|
822 | ||
823 |
#' Tidy Method for the [`DataGrouped`] Class |
|
824 |
#' |
|
825 |
#' @description `r lifecycle::badge("experimental")` |
|
826 |
#' |
|
827 |
#' A method that tidies a [`DataGrouped`] object. |
|
828 |
#' |
|
829 |
#' @return The [`tibble`] object. |
|
830 |
#' |
|
831 |
#' @aliases tidy-DataGrouped |
|
832 |
#' @rdname tidy |
|
833 |
#' @export |
|
834 |
#' @example examples/GeneralData-method-tidy.R |
|
835 |
#' |
|
836 |
setMethod( |
|
837 |
f = "tidy", |
|
838 |
signature = signature(x = "DataGrouped"), |
|
839 |
definition = function(x, ...) { |
|
840 | 6x |
d <- callNextMethod() |
841 | 6x |
d %>% |
842 | 6x |
tibble::add_column(Group = x@group) %>% |
843 | 6x |
h_tidy_class(x) |
844 |
} |
|
845 |
) |
|
846 | ||
847 |
## DataDA ---- |
|
848 | ||
849 |
#' Tidy Method for the [`DataDA`] Class |
|
850 |
#' |
|
851 |
#' @description `r lifecycle::badge("experimental")` |
|
852 |
#' |
|
853 |
#' A method that tidies a [`DataDA`] object. |
|
854 |
#' |
|
855 |
#' @return The [`tibble`] object. |
|
856 |
#' |
|
857 |
#' @aliases tidy-DataDA |
|
858 |
#' @rdname tidy |
|
859 |
#' @export |
|
860 |
#' @example examples/GeneralData-method-tidy.R |
|
861 |
#' |
|
862 |
setMethod( |
|
863 |
f = "tidy", |
|
864 |
signature = signature(x = "DataDA"), |
|
865 |
definition = function(x, ...) { |
|
866 | 18x |
d <- callNextMethod() |
867 | 18x |
d %>% |
868 | 18x |
tibble::add_column(U = x@u) %>% |
869 | 18x |
tibble::add_column(T0 = x@t0) %>% |
870 | 18x |
tibble::add_column(TMax = x@Tmax) %>% |
871 | 18x |
h_tidy_class(x) |
872 |
} |
|
873 |
) |
|
874 | ||
875 |
## DataDA ---- |
|
876 | ||
877 |
#' Tidy Method for the [`DataDual`] Class |
|
878 |
#' |
|
879 |
#' @description `r lifecycle::badge("experimental")` |
|
880 |
#' |
|
881 |
#' A method that tidies a [`DataDual`] object. |
|
882 |
#' |
|
883 |
#' @return The [`tibble`] object. |
|
884 |
#' |
|
885 |
#' @aliases tidy-DataDual |
|
886 |
#' @rdname tidy |
|
887 |
#' @export |
|
888 |
#' @example examples/GeneralData-method-tidy.R |
|
889 |
#' |
|
890 |
setMethod( |
|
891 |
f = "tidy", |
|
892 |
signature = signature(x = "DataDual"), |
|
893 |
definition = function(x, ...) { |
|
894 | 71x |
d <- callNextMethod() |
895 | 71x |
d %>% |
896 | 71x |
tibble::add_column(W = x@w) %>% |
897 | 71x |
h_tidy_class(x) |
898 |
} |
|
899 |
) |
|
900 | ||
901 |
## DataParts ---- |
|
902 | ||
903 |
#' Tidy Method for the [`DataParts`] Class |
|
904 |
#' |
|
905 |
#' @description `r lifecycle::badge("experimental")` |
|
906 |
#' |
|
907 |
#' A method that tidies a [`DataParts`] object. |
|
908 |
#' |
|
909 |
#' @return The [`tibble`] object. |
|
910 |
#' |
|
911 |
#' @aliases tidy-DataParts |
|
912 |
#' @rdname tidy |
|
913 |
#' @export |
|
914 |
#' @example examples/GeneralData-method-tidy.R |
|
915 |
#' |
|
916 |
setMethod( |
|
917 |
f = "tidy", |
|
918 |
signature = signature(x = "DataParts"), |
|
919 |
definition = function(x, ...) { |
|
920 | 6x |
d <- callNextMethod() |
921 | 6x |
d %>% |
922 | 6x |
tibble::add_column(Part = x@part) %>% |
923 | 6x |
tibble::add_column(NextPart = x@nextPart) %>% |
924 | 6x |
tibble::add_column(Part1Ladder = list(x@part1Ladder)) %>% |
925 | 6x |
h_tidy_class(x) |
926 |
} |
|
927 |
) |
|
928 | ||
929 |
## DataMixture ---- |
|
930 | ||
931 |
#' Tidy Method for the [`DataMixture`] Class |
|
932 |
#' |
|
933 |
#' @description `r lifecycle::badge("experimental")` |
|
934 |
#' |
|
935 |
#' A method that tidies a [`DataMixture`] object. |
|
936 |
#' @section Usage Notes: |
|
937 |
#' The prior observations are indicated by a `Cohort` value of `0` in the returned |
|
938 |
#' `tibble`. |
|
939 |
#' @return The [`tibble`] object. |
|
940 |
#' |
|
941 |
#' @aliases tidy-DataMixture |
|
942 |
#' @rdname tidy |
|
943 |
#' @export |
|
944 |
#' @example examples/GeneralData-method-tidy.R |
|
945 |
#' |
|
946 |
setMethod( |
|
947 |
f = "tidy", |
|
948 |
signature = signature(x = "DataMixture"), |
|
949 |
definition = function(x, ...) { |
|
950 | 6x |
observed <- callNextMethod() |
951 | 6x |
tibble::tibble( |
952 | 6x |
Cohort = 0, |
953 | 6x |
Dose = x@xshare, |
954 | 6x |
Tox = as.logical(x@yshare), |
955 | 6x |
ID = sort(seq_along(x@xshare)), |
956 | 6x |
Placebo = x@placebo, |
957 | 6x |
NObs = x@nObs, |
958 | 6x |
NGrid = x@nGrid, |
959 | 6x |
DoseGrid = list(x@doseGrid), |
960 | 6x |
XLevel = which(x@doseGrid %in% x@xshare) |
961 |
) %>% |
|
962 | 6x |
dplyr::bind_rows(observed) %>% |
963 | 6x |
h_tidy_class(x) |
964 |
} |
|
965 |
) |
|
966 | ||
967 | ||
968 |
## DataOrdinal ---- |
|
969 | ||
970 |
#' Tidy Method for the [`DataMixture`] Class |
|
971 |
#' |
|
972 |
#' @description `r lifecycle::badge("experimental")` |
|
973 |
#' |
|
974 |
#' A method that tidies a [`DataOrdinal`] object. |
|
975 |
#' @section Usage Notes: |
|
976 |
#' @return The [`tibble`] object. |
|
977 |
#' |
|
978 |
#' @aliases tidy-DataOrdinal |
|
979 |
#' @rdname tidy |
|
980 |
#' @export |
|
981 |
#' @example examples/GeneralData-method-tidy.R |
|
982 |
#' |
|
983 |
setMethod( |
|
984 |
f = "tidy", |
|
985 |
signature = signature(x = "DataOrdinal"), |
|
986 |
definition = function(x, ...) { |
|
987 | 23x |
y <- tibble::tibble( |
988 | 23x |
ID = x@ID, |
989 | 23x |
Cohort = x@cohort, |
990 | 23x |
Dose = x@x, |
991 | 23x |
Tox = x@y, |
992 | 23x |
Placebo = x@placebo, |
993 | 23x |
NObs = x@nObs, |
994 | 23x |
NGrid = x@nGrid, |
995 | 23x |
DoseGrid = list(x@doseGrid), |
996 | 23x |
XLevel = x@xLevel |
997 |
) %>% |
|
998 | 23x |
tidyr::pivot_wider( |
999 | 23x |
names_from = "Tox", |
1000 | 23x |
values_from = "Tox", |
1001 | 23x |
names_prefix = "Cat", |
1002 | 23x |
values_fill = 0 |
1003 |
) |
|
1004 | 23x |
if (nrow(y) > 0) { |
1005 | 8x |
y <- y %>% |
1006 | 8x |
dplyr::mutate( |
1007 | 8x |
dplyr::across(tidyselect::matches("Cat\\d+"), \(x) x > 0) |
1008 |
) %>% |
|
1009 | 8x |
dplyr::rowwise() %>% |
1010 | 8x |
dplyr::mutate( |
1011 | 8x |
AnyTox = any(dplyr::across(c(tidyselect::starts_with("Cat"), -Cat0), any)), |
1012 |
# Direct assignment fails on GitHub |
|
1013 | 8x |
Cat0 = !AnyTox |
1014 |
) %>% |
|
1015 | 8x |
dplyr::select(-AnyTox) %>% |
1016 | 8x |
dplyr::ungroup() |
1017 |
} |
|
1018 | 23x |
y <- y %>% h_tidy_class(x) |
1019 | 23x |
y |
1020 |
} |
|
1021 |
) |
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 | 371x |
assert_class(object, "GeneralData") |
32 | 371x |
assert_character(where) |
33 | 371x |
assert_subset(where, slotNames(object)) |
34 | 370x |
assert_number(dummy) |
35 | ||
36 | 370x |
if (object@nObs == 1L) { |
37 | 6x |
for (i in where) { |
38 |
# Add dummy value and preserve the class. |
|
39 | 13x |
slot(object, i) <- as(c(slot(object, i), dummy), class(slot(object, i))) |
40 |
} |
|
41 |
} |
|
42 | 370x |
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 | 744x |
assert_function(model1) |
67 | 744x |
assert_function(model2) |
68 | 744x |
assert_class(body(model1), "{") |
69 | 743x |
assert_class(body(model2), "{") |
70 | ||
71 |
# This workaround is needed to avoid bugs related to covr-injected code. |
|
72 | 743x |
if (h_covr_active()) { |
73 | 743x |
body(model2) <- h_covr_detrace(body(model2)) |
74 |
} |
|
75 | ||
76 | 743x |
body2 <- as.list(body(model2)) |
77 | 743x |
if (length(body2) >= 2) { |
78 | 742x |
body1 <- as.list(body(model1)) |
79 | 742x |
body(model1) <- as.call(c(body1, body2[-1])) |
80 |
} |
|
81 | 743x |
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 | 364x |
assert_class(model, "GeneralModel") |
104 | 364x |
assert_class(data, "GeneralData") |
105 | ||
106 | 364x |
inits <- do.call(model@init, h_slots(data, formalArgs(model@init))) |
107 | 364x |
assert_list(inits) |
108 | 363x |
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 | 367x |
assert_class(model, "GeneralModel") |
129 | 367x |
assert_class(data, "GeneralData") |
130 | 367x |
assert_flag(from_prior) |
131 | ||
132 |
# 1) Extract variables from `data` as required by `modelspecs`. |
|
133 | 367x |
ms_args_names <- formalArgs(model@modelspecs) |
134 | 367x |
ms_args <- if ("from_prior" %in% ms_args_names) { |
135 | 336x |
c(h_slots(data, setdiff(ms_args_names, "from_prior")), list(from_prior = from_prior)) |
136 |
} else { |
|
137 | 31x |
h_slots(data, ms_args_names) |
138 |
} |
|
139 | 367x |
modelspecs <- do.call(model@modelspecs, ms_args) |
140 | 367x |
assert_list(modelspecs) |
141 | ||
142 |
# 2) Extract variables from `data` as required by `datanames`. |
|
143 | 366x |
datanames <- if (from_prior) { |
144 | 41x |
model@datanames_prior |
145 |
} else { |
|
146 | 325x |
union(model@datanames, model@datanames_prior) |
147 |
} |
|
148 | ||
149 |
# Add dummy to ensure that e.g. `x` and `y` in `data` won't be treated as |
|
150 |
# scalars by `JAGS` if `data@nObs == 1`, which leads to failures. |
|
151 | 366x |
add_where <- setdiff(datanames, c("nObs", "nGrid", "nObsshare", "yshare", "xshare", "Tmax")) |
152 | 366x |
data <- h_jags_add_dummy(data, where = add_where) |
153 | ||
154 | 366x |
data_model <- h_slots(data, datanames) |
155 | 366x |
c(data_model, modelspecs) |
156 |
} |
|
157 | ||
158 |
#' Writing JAGS Model to a File |
|
159 |
#' |
|
160 |
#' @description `r lifecycle::badge("stable")` |
|
161 |
#' |
|
162 |
#' This function converts a R function with JAGS model into a text and then |
|
163 |
#' writes it into a given file. During the "model into text" conversion, the |
|
164 |
#' format of numbers of which absolute value is less than `0.001` or greater |
|
165 |
#' than `10000` is changed. These numbers will be converted into scientific |
|
166 |
#' format with specified number of significant digits using [formatC()] |
|
167 |
#' function. |
|
168 |
#' |
|
169 |
#' @note JAGS syntax allows truncation specification like `dnorm(...) I(...)`, |
|
170 |
#' which is illegal in R. To overcome this incompatibility, use dummy operator |
|
171 |
#' `\%_\%` before `I(...)`, i.e. `dnorm(...) \%_\% I(...)` in the model's |
|
172 |
#' code. This dummy operator `\%_\%` will be removed just before saving the |
|
173 |
#' JAGS code into a file. |
|
174 |
#' Due to technical issues related to conversion of numbers to scientific |
|
175 |
#' format, it is required that the body of a model function does not contain |
|
176 |
#' `TEMP_NUM_PREF_` or `_TEMP_NUM_SUF` character constants in its body. |
|
177 |
#' |
|
178 |
#' @param model (`function`)\cr function containing the JAGS model. |
|
179 |
#' @param file (`string` or `NULL`)\cr the name of the file (including the |
|
180 |
#' optional path) where the model will be saved. If `NULL`, the file will be |
|
181 |
#' created in a `R_crmPack` folder placed under temporary directory indicated |
|
182 |
#' by [tempdir()] function. |
|
183 |
#' @param digits (`count`)\cr a desired number of significant digits for |
|
184 |
#' for numbers used in JAGS input, see [formatC()]. |
|
185 |
#' @return The name of the file where the model was saved. |
|
186 |
#' |
|
187 |
#' @export |
|
188 |
#' @example examples/helpers-jags_write_model.R |
|
189 |
#' |
|
190 |
h_jags_write_model <- function(model, file = NULL, digits = 5) { |
|
191 | 393x |
assert_function(model) |
192 | 393x |
assert_count(digits) |
193 | ||
194 |
# This workaround is needed to avoid bugs related to covr-injected code. |
|
195 | 393x |
if (h_covr_active()) { |
196 | 393x |
body(model) <- h_covr_detrace(body(model)) |
197 |
} |
|
198 | ||
199 | 393x |
if (!is.null(file)) { |
200 | 1x |
assert_path_for_output(file) |
201 |
} else { |
|
202 | 392x |
dir <- file.path(tempdir(), "R_crmPack") |
203 |
# Don't warn, as the temp dir often exists (which is OK). |
|
204 | 392x |
dir.create(dir, showWarnings = FALSE) |
205 | 392x |
file <- tempfile( |
206 | 392x |
pattern = "jags_model_fun", |
207 | 392x |
tmpdir = dir, |
208 | 392x |
fileext = ".txt" |
209 |
) |
|
210 |
} |
|
211 | ||
212 |
# Replace scientific notation. |
|
213 | 393x |
model_sci_replaced <- h_rapply( |
214 | 393x |
x = body(model), |
215 | 393x |
fun = h_format_number, |
216 | 393x |
classes = c("integer", "numeric"), |
217 | 393x |
digits = digits, |
218 | 393x |
prefix = "TEMP_NUM_PREF_", |
219 | 393x |
suffix = "_TEMP_NUM_SUF" |
220 |
) |
|
221 |
# Transform `model` body into character vector. |
|
222 | 393x |
model_text <- deparse(model_sci_replaced, control = NULL) |
223 | 393x |
model_text <- gsub("\"TEMP_NUM_PREF_|_TEMP_NUM_SUF\"", "", model_text) |
224 | 393x |
model_text <- gsub("%_% ", "", model_text) |
225 | 393x |
model_text <- c("model", model_text) |
226 | ||
227 | 393x |
log_trace("Writting JAGS model function into: %s", file) |
228 | 393x |
writeLines(model_text, con = file) |
229 | 393x |
file |
230 |
} |
|
231 | ||
232 |
#' Extracting Samples from `JAGS` `mcarray` Object |
|
233 |
#' |
|
234 |
#' @description `r lifecycle::badge("stable")` |
|
235 |
#' |
|
236 |
#' A simple helper function that extracts a sample from |
|
237 |
#' [`rjags::mcarray.object`] S3 class object. The [`rjags::mcarray.object`] |
|
238 |
#' object is used by the [rjags::jags.samples()] function to represent MCMC |
|
239 |
#' output from a `JAGS` model. |
|
240 |
#' |
|
241 |
#' @param x an [`rjags::mcarray.object`] object. |
|
242 |
#' |
|
243 |
#' @export |
|
244 |
#' |
|
245 |
h_jags_extract_samples <- function(x) { |
|
246 | 1009x |
assert_class(x, "mcarray") |
247 | ||
248 | 1009x |
x <- x[, , 1L] |
249 |
# In case that there are multiple parameters in a node. |
|
250 | 1009x |
if (is.matrix(x)) { |
251 | 138x |
x <- t(x) |
252 |
} |
|
253 | 1009x |
x |
254 |
} |
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(ordinals, min.len = length(gap) - 1, any.missing = FALSE, unique = TRUE) |
23 | ||
24 | 24x |
if (length(gap) == 1) { |
25 | 5x |
paste0( |
26 | 5x |
"- The gap between consecutive enrolments should always be at least ", |
27 | 5x |
gap[1], |
28 |
" ", |
|
29 | 5x |
ifelse(gap[1] == 1, time_unit[1], time_unit[2]), |
30 | 5x |
".\n\n" |
31 |
) |
|
32 |
} else { |
|
33 | 19x |
paste0( |
34 | 19x |
paste0( |
35 | 19x |
lapply( |
36 | 19x |
seq_along(1:(length(gap) - 1)), |
37 | 19x |
function(n) { |
38 | 21x |
paste0( |
39 | 21x |
"- The gap between the enrolment of the ", |
40 | 21x |
ordinals[n], |
41 | 21x |
" and the ", |
42 | 21x |
ordinals[n + 1], |
43 |
" ", |
|
44 | 21x |
label[2], |
45 | 21x |
" in the cohort should be at least ", |
46 | 21x |
gap[n], |
47 |
" ", |
|
48 | 21x |
ifelse(gap[n] == 1, time_unit[1], time_unit[2]) |
49 |
) |
|
50 |
} |
|
51 |
), |
|
52 | 19x |
collapse = "\n\n" |
53 |
), |
|
54 | 19x |
"\n", |
55 | 19x |
paste0( |
56 | 19x |
"- The gap between all subsequent ", |
57 | 19x |
label[2], |
58 | 19x |
" should be at least ", |
59 | 19x |
gap[length(gap)], |
60 |
" ", |
|
61 | 19x |
ifelse(gap[length(gap)] == 1, time_unit[1], time_unit[2]), |
62 | 19x |
"\n" |
63 |
), |
|
64 | 19x |
sep = "\n" |
65 |
) |
|
66 |
} |
|
67 |
} |
|
68 |
# Methods ---- |
|
69 | ||
70 |
# SafetyWindow ---- |
|
71 | ||
72 |
#' @description `r lifecycle::badge("experimental")` |
|
73 |
#' @inheritParams knit_print.StoppingTargetProb |
|
74 |
#' @inheritParams knit_print.CohortSizeConst |
|
75 |
#' @section Usage Notes: |
|
76 |
#' `label` should be a character vector of length 1 or 2. If of length 2, the first |
|
77 |
#' element describes a count of 1 and the second describes all other counts. |
|
78 |
#' If of length 1, the character `s` is appended to the value when the count is not 1. |
|
79 |
#' @rdname knit_print |
|
80 |
#' @export |
|
81 |
#' @method knit_print SafetyWindow |
|
82 |
knit_print.SafetyWindow <- function( |
|
83 |
x, |
|
84 |
..., |
|
85 |
asis = TRUE, |
|
86 |
time_unit = "day", |
|
87 |
label = "participant") { |
|
88 | 20x |
assert_character(time_unit, min.len = 1, max.len = 2, any.missing = FALSE) |
89 | 20x |
assert_flag(asis) |
90 | ||
91 | 20x |
label <- h_prepare_labels(label) |
92 | 20x |
if (length(time_unit) == 1) { |
93 | 20x |
time_unit[2] <- paste0(time_unit[1], "s") |
94 |
} |
|
95 | ||
96 | 20x |
rv <- paste0( |
97 | 20x |
"To protect the welfare of individual ", |
98 | 20x |
label[2], |
99 | 20x |
", the rate of enrolment within each cohort will be restricted.\n\n" |
100 |
) |
|
101 | ||
102 | 20x |
if (asis) { |
103 | ! |
rv <- knitr::asis_output(rv) |
104 |
} |
|
105 | 20x |
rv |
106 |
} |
|
107 | ||
108 |
# SafetyWindowConst ---- |
|
109 | ||
110 |
#' @description `r lifecycle::badge("experimental")` |
|
111 |
#' @inheritParams knit_print.StoppingTargetProb |
|
112 |
#' @inheritParams knit_print.CohortSizeConst |
|
113 |
#' @param time_unit (`character`)\cr the word used to describe units of time. |
|
114 |
#' See Usage Notes below. |
|
115 |
#' @param ordinals (`character`)\cr a character vector whose nth defines the |
|
116 |
#' word used as the written representation of the nth ordinal number. |
|
117 |
#' @section Usage Notes: |
|
118 |
#' `label` and `time_unit` are, collectively, labels. |
|
119 |
#' |
|
120 |
#' A label should be a character vector of length 1 or 2. If of length 2, the first |
|
121 |
#' element describes a count of 1 and the second describes all other counts. |
|
122 |
#' If of length 1, the character `s` is appended to the value when the count is not 1. |
|
123 |
#' @rdname knit_print |
|
124 |
#' @export |
|
125 |
#' @method knit_print SafetyWindowConst |
|
126 |
knit_print.SafetyWindowConst <- function( |
|
127 |
x, |
|
128 |
..., |
|
129 |
asis = TRUE, |
|
130 |
label = "participant", |
|
131 |
ordinals = c( |
|
132 |
"first", "second", "third", "fourth", "fifth", "sixth", |
|
133 |
"seventh", "eighth", "ninth", "tenth" |
|
134 |
), |
|
135 |
time_unit = "day") { |
|
136 | 19x |
assert_character(time_unit, min.len = 1, max.len = 2, any.missing = FALSE) |
137 | 18x |
assert_character(ordinals, min.len = length(x@gap) - 1, any.missing = FALSE, unique = TRUE) |
138 | 16x |
assert_flag(asis) |
139 | ||
140 | 14x |
label <- h_prepare_labels(label) |
141 | 13x |
if (length(time_unit) == 1) { |
142 | 13x |
time_unit[2] <- paste0(time_unit[1], "s") |
143 |
} |
|
144 | ||
145 | 13x |
rv <- paste0( |
146 | 13x |
knit_print.SafetyWindow(x, asis = FALSE, label = label, ...), |
147 | 13x |
"For all cohorts:\n\n", |
148 | 13x |
h_describe_safety_gap(x@gap, ordinals, label, time_unit), |
149 | 13x |
"Before the next cohort can open, all ", |
150 | 13x |
label[2], |
151 | 13x |
" in the current cohort must have been followed up for at least ", |
152 | 13x |
x@follow, |
153 |
" ", |
|
154 | 13x |
ifelse(x@follow == 1, time_unit[1], time_unit[2]), |
155 | 13x |
" and at least one ", |
156 | 13x |
label[1], |
157 | 13x |
" must have been followed up for at least ", |
158 | 13x |
x@follow_min, |
159 |
" ", |
|
160 | 13x |
ifelse(x@follow_min == 1, time_unit[1], time_unit[2]), |
161 | 13x |
".\n\n" |
162 |
) |
|
163 | ||
164 | 13x |
if (asis) { |
165 | 2x |
rv <- knitr::asis_output(rv) |
166 |
} |
|
167 | 13x |
rv |
168 |
} |
|
169 | ||
170 |
# SafetyWindowSize ---- |
|
171 | ||
172 |
#' @description `r lifecycle::badge("experimental")` |
|
173 |
#' @inheritParams knit_print.SafetyWindowConst |
|
174 |
#' @inherit SafetyWindowConst sections |
|
175 |
#' @param level (`count`)\cr the markdown level at which the headings for cohort size |
|
176 |
#' will be printed. An integer between 1 and 6 |
|
177 |
#' @rdname knit_print |
|
178 |
#' @export |
|
179 |
#' @method knit_print SafetyWindowSize |
|
180 |
knit_print.SafetyWindowSize <- function( |
|
181 |
x, |
|
182 |
..., |
|
183 |
asis = TRUE, |
|
184 |
# We could use package english here and avoid the need for `ordinals`, but |
|
185 |
# is an extra dependency for very limited benefit |
|
186 |
ordinals = c( |
|
187 |
"first", "second", "third", "fourth", "fifth", "sixth", "seventh", |
|
188 |
"eighth", "ninth", "tenth" |
|
189 |
), |
|
190 |
label = "participant", |
|
191 |
time_unit = "day", |
|
192 |
level = 2L) { |
|
193 | 13x |
assert_character(time_unit, min.len = 1, max.len = 2, any.missing = FALSE) |
194 | 12x |
assert_flag(asis) |
195 | 10x |
assert_integer(level, lower = 1, upper = 6, any.missing = FALSE) |
196 | ||
197 | 8x |
label <- h_prepare_labels(label) |
198 | 7x |
if (length(time_unit) == 1) { |
199 | 7x |
time_unit[2] <- paste0(time_unit[1], "s") |
200 |
} |
|
201 | ||
202 | 7x |
rv <- paste0( |
203 | 7x |
knit_print.SafetyWindow(x, asis = FALSE, label = label, ...), |
204 | 7x |
paste0( |
205 | 7x |
lapply( |
206 | 7x |
seq_along(x@size), |
207 | 7x |
function(i) { |
208 | 13x |
paste0( |
209 | 13x |
dplyr::case_when( |
210 | 13x |
i == 1 ~ paste0( |
211 | 13x |
stringr::str_dup("#", level), |
212 | 13x |
" For cohort sizes of less than ", |
213 | 13x |
x@size[2] |
214 |
), |
|
215 | 13x |
i == length(x@size) ~ paste0( |
216 | 13x |
stringr::str_dup("#", level), |
217 | 13x |
" For cohort sizes of ", |
218 | 13x |
x@size[i], |
219 | 13x |
" or more" |
220 |
), |
|
221 | 13x |
TRUE ~ paste0( |
222 | 13x |
stringr::str_dup("#", level), |
223 | 13x |
" For cohort sizes greater than or equal to ", |
224 | 13x |
x@size[i], |
225 | 13x |
" and strictly less than ", |
226 | 13x |
x@size[i + 1] |
227 |
) |
|
228 |
), |
|
229 | 13x |
"\n\n", |
230 | 13x |
h_describe_safety_gap(x@gap[[i]], ordinals, label, time_unit) |
231 |
) |
|
232 |
} |
|
233 |
), |
|
234 | 7x |
collapse = "\n" |
235 |
) |
|
236 |
) |
|
237 | ||
238 | 5x |
rv <- paste0( |
239 | 5x |
rv, |
240 | 5x |
"For all cohorts, before the next cohort can open, all ", |
241 | 5x |
label[2], |
242 | 5x |
" in the current cohort must have been followed up for at least ", |
243 | 5x |
x@follow, |
244 |
" ", |
|
245 | 5x |
ifelse(x@follow == 1, time_unit[1], time_unit[2]), |
246 | 5x |
" and at least one ", |
247 | 5x |
label[1], |
248 | 5x |
" must have been followed up for at least ", |
249 | 5x |
x@follow_min, |
250 |
" ", |
|
251 | 5x |
ifelse(x@follow_min == 1, time_unit[1], time_unit[2]), |
252 | 5x |
".\n\n" |
253 |
) |
|
254 | ||
255 | 5x |
if (asis) { |
256 | 2x |
rv <- knitr::asis_output(rv) |
257 |
} |
|
258 | 5x |
rv |
259 |
} |
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("Class DataGeneral cannot be instantiated directly. Please use one of its subclasses instead.")) |
44 |
} |
|
45 | ||
46 |
# Data ---- |
|
47 | ||
48 |
## class ---- |
|
49 | ||
50 |
#' `Data` |
|
51 |
#' |
|
52 |
#' @description `r lifecycle::badge("stable")` |
|
53 |
#' |
|
54 |
#' [`Data`] is a class for the data input. |
|
55 |
#' It inherits from [`GeneralData`]. |
|
56 |
#' |
|
57 |
#' @slot x (`numeric`)\cr the doses for the patients. |
|
58 |
#' @slot y (`integer`)\cr the vector of toxicity events (0 or 1 integers). |
|
59 |
#' @slot doseGrid (`numeric`)\cr the vector of all possible doses (sorted), |
|
60 |
#' i.e. the dose grid. |
|
61 |
#' @slot nGrid (`integer`)\cr number of gridpoints. |
|
62 |
#' @slot xLevel (`integer`)\cr the levels for the doses the patients have been given, |
|
63 |
#' w.r.t `doseGrid`. |
|
64 |
#' @slot placebo (`logical`)\cr if `TRUE` the first dose level |
|
65 |
#' in the `doseGrid`is considered as PLACEBO. |
|
66 |
#' |
|
67 |
#' @aliases Data |
|
68 |
#' @export |
|
69 |
#' |
|
70 |
.Data <- setClass( |
|
71 |
Class = "Data", |
|
72 |
contains = "GeneralData", |
|
73 |
slots = c( |
|
74 |
x = "numeric", |
|
75 |
y = "integer", |
|
76 |
doseGrid = "numeric", |
|
77 |
nGrid = "integer", |
|
78 |
xLevel = "integer", |
|
79 |
placebo = "logical" |
|
80 |
), |
|
81 |
prototype = prototype( |
|
82 |
x = numeric(), |
|
83 |
y = integer(), |
|
84 |
doseGrid = numeric(), |
|
85 |
nGrid = 0L, |
|
86 |
xLevel = integer(), |
|
87 |
placebo = FALSE |
|
88 |
), |
|
89 |
validity = v_data |
|
90 |
) |
|
91 | ||
92 |
## constructor ---- |
|
93 | ||
94 |
#' @rdname Data-class |
|
95 |
#' |
|
96 |
#' @details The `cohort` can be missing if and only if `placebo` is equal to |
|
97 |
#' `FALSE`. |
|
98 |
#' |
|
99 |
#' @note `ID` and `cohort` can be missing. Then a message will be issued |
|
100 |
#' and the variables will be filled with default IDs and best guesses cohort, |
|
101 |
#' i.e. a sorted (in ascending order) sequence of values from `{1, 2, ...}`. |
|
102 |
#' |
|
103 |
#' @param x (`numeric`)\cr the doses for the patients. |
|
104 |
#' @param y (`integer`)\cr the vector of toxicity events (0 or 1). |
|
105 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
106 |
#' `integer` internally. |
|
107 |
#' @param ID (`integer`)\cr unique patient IDs. |
|
108 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
109 |
#' `integer` internally. |
|
110 |
#' @param cohort (`integer`)\cr the cohort (non-negative sorted) indices. |
|
111 |
#' You can also supply `numeric` vectors, but these will then be converted to |
|
112 |
#' `integer` internally. |
|
113 |
#' @param doseGrid (`numeric`)\cr all possible doses. |
|
114 |
#' @param placebo (`flag`)\cr if `TRUE` the first dose level |
|
115 |
#' in the `doseGrid` is considered as placebo. |
|
116 |
#' @param ... not used. |
|
117 |
#' |
|
118 |
#' @export |
|
119 |
#' @example examples/Data-class.R |
|
120 |
#' |
|
121 |
Data <- function(x = numeric(), |
|
122 |
y = integer(), |
|
123 |
ID = integer(), |
|
124 |
cohort = integer(), |
|
125 |
doseGrid = numeric(), |
|
126 |
placebo = FALSE, |
|
127 |
...) { |
|
128 | 894x |
assert_numeric(x) |
129 | 894x |
assert_integerish(y, lower = 0, upper = 1, any.missing = FALSE) |
130 | 894x |
assert_integerish(ID, unique = TRUE, any.missing = FALSE) |
131 | 894x |
assert_integerish(cohort) |
132 | 894x |
assert_numeric(doseGrid, any.missing = FALSE, unique = TRUE) |
133 | 894x |
assert_flag(placebo) |
134 | ||
135 | 894x |
doseGrid <- sort(doseGrid) |
136 | 894x |
assert_subset(x, doseGrid) |
137 | ||
138 | 894x |
if (length(ID) == 0 && length(x) > 0) { |
139 | 1x |
message("Used default patient IDs!") |
140 | 1x |
ID <- seq_along(x) |
141 |
} else { |
|
142 | 893x |
assert_integerish(ID, unique = TRUE) |
143 |
} |
|
144 | ||
145 | 894x |
if (!placebo && length(cohort) == 0 && length(x) > 0) { |
146 | 1x |
message("Used best guess cohort indices!") |
147 |
# This is just assuming that consecutive patients |
|
148 |
# in the data set are in the same cohort if they |
|
149 |
# have the same dose. Note that this could be wrong, |
|
150 |
# if two subsequent cohorts are at the same dose. |
|
151 | 1x |
cohort <- as.integer(c(1, 1 + cumsum(diff(x) != 0))) |
152 |
} else { |
|
153 | 893x |
assert_integerish(cohort) |
154 |
} |
|
155 | ||
156 | 894x |
.Data( |
157 | 894x |
x = as.numeric(x), |
158 | 894x |
y = as.integer(y), |
159 | 894x |
ID = as.integer(ID), |
160 | 894x |
cohort = as.integer(cohort), |
161 | 894x |
doseGrid = as.numeric(doseGrid), |
162 | 894x |
nObs = length(x), |
163 | 894x |
nGrid = length(doseGrid), |
164 | 894x |
xLevel = match_within_tolerance(x, doseGrid), |
165 | 894x |
placebo = placebo |
166 |
) |
|
167 |
} |
|
168 | ||
169 |
## default constructor ---- |
|
170 | ||
171 |
#' @rdname Data-class |
|
172 |
#' @note Typically, end users will not use the `.DefaultData()` function. |
|
173 |
#' @export |
|
174 |
.DefaultData <- function() { |
|
175 | 15x |
Data( |
176 | 15x |
doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100), |
177 | 15x |
ID = 1L:3L, |
178 | 15x |
cohort = 1L:3L, |
179 | 15x |
x = c(1, 3, 5), |
180 | 15x |
y = rep(0L, 3) |
181 |
) |
|
182 |
} |
|
183 | ||
184 |
# DataDual ---- |
|
185 | ||
186 |
## class ---- |
|
187 | ||
188 |
#' `DataDual` |
|
189 |
#' |
|
190 |
#' @description `r lifecycle::badge("stable")` |
|
191 |
#' |
|
192 |
#' [`DataDual`] is a class for the dual endpoint data. |
|
193 |
#' It inherits from [`Data`] and it contains additional biomarker information. |
|
194 |
#' |
|
195 |
#' @slot w (`numeric`)\cr the continuous vector of biomarker values. |
|
196 |
#' |
|
197 |
#' @aliases DataDual |
|
198 |
#' @export |
|
199 |
#' |
|
200 |
.DataDual <- setClass( |
|
201 |
Class = "DataDual", |
|
202 |
slots = c(w = "numeric"), |
|
203 |
prototype = prototype(w = numeric()), |
|
204 |
contains = "Data", |
|
205 |
validity = v_data_dual |
|
206 |
) |
|
207 | ||
208 |
## constructor ---- |
|
209 | ||
210 |
#' @rdname DataDual-class |
|
211 |
#' |
|
212 |
#' @param w (`numeric`)\cr the continuous vector of biomarker values. |
|
213 |
#' @param ... parameters passed to [Data()]. |
|
214 |
#' |
|
215 |
#' @export |
|
216 |
#' @example examples/Data-class-DataDual.R |
|
217 |
#' |
|
218 |
DataDual <- function(w = numeric(), |
|
219 |
...) { |
|
220 | 183x |
d <- Data(...) |
221 | 183x |
.DataDual(d, w = w) |
222 |
} |
|
223 | ||
224 | ||
225 |
## default constructor ---- |
|
226 | ||
227 |
#' @rdname DataDual-class |
|
228 |
#' @note Typically, end users will not use the `.DefaultDataDual()` function. |
|
229 |
#' @export |
|
230 |
.DefaultDataDual <- function() { |
|
231 | 6x |
set.seed(1230) |
232 | 6x |
DataDual( |
233 | 6x |
x = c(0.1, 0.5, 1.5, 3, 6, 10, 10, 10), |
234 | 6x |
y = c(0, 0, 0, 0, 0, 0, 1, 0), |
235 | 6x |
w = rnorm(8), |
236 | 6x |
doseGrid = c(0.1, 0.5, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)), |
237 | 6x |
ID = 1L:8L, |
238 | 6x |
cohort = as.integer(c(1, 2, 3, 4, 5, 6, 6, 6)) |
239 |
) |
|
240 |
} |
|
241 | ||
242 |
# DataParts ---- |
|
243 | ||
244 |
## class ---- |
|
245 | ||
246 |
#' `DataParts` |
|
247 |
#' |
|
248 |
#' @description `r lifecycle::badge("stable")` |
|
249 |
#' |
|
250 |
#' [`DataParts`] is a class for the data with two study parts. |
|
251 |
#' It inherits from [`Data`] and it contains additional information |
|
252 |
#' on the two study parts. |
|
253 |
#' |
|
254 |
#' @slot part (`integer`)\cr which part does each of the patients belong to? |
|
255 |
#' @slot nextPart (`count`)\cr what is the part for the next cohort (1 or 2)? |
|
256 |
#' @slot part1Ladder (`numeric`)\cr what is the escalation ladder for |
|
257 |
#' part 1? This shall be an ordered subset of the `doseGrid`. |
|
258 |
#' |
|
259 |
#' @aliases DataParts |
|
260 |
#' @export |
|
261 |
#' |
|
262 |
.DataParts <- setClass( |
|
263 |
Class = "DataParts", |
|
264 |
slots = c( |
|
265 |
part = "integer", |
|
266 |
nextPart = "integer", |
|
267 |
part1Ladder = "numeric" |
|
268 |
), |
|
269 |
prototype = prototype( |
|
270 |
part = integer(), |
|
271 |
nextPart = 1L, |
|
272 |
part1Ladder = numeric() |
|
273 |
), |
|
274 |
contains = "Data", |
|
275 |
validity = v_data_parts |
|
276 |
) |
|
277 | ||
278 |
## constructor ---- |
|
279 | ||
280 |
#' @rdname DataParts-class |
|
281 |
#' |
|
282 |
#' @param part (`integer`)\cr which part does each of the patients belong to? |
|
283 |
#' @param nextPart (`count`)\cr what is the part for the next cohort (1 or 2)? |
|
284 |
#' @param part1Ladder (`numeric`)\cr what is the escalation ladder for part 1? |
|
285 |
#' This shall be an ordered subset of the `doseGrid`. |
|
286 |
#' @param ... parameters passed to [Data()]. |
|
287 |
#' |
|
288 |
#' @export |
|
289 |
#' @example examples/Data-class-DataParts.R |
|
290 |
#' |
|
291 |
DataParts <- function(part = integer(), |
|
292 |
nextPart = 1L, |
|
293 |
part1Ladder = numeric(), |
|
294 |
...) { |
|
295 | 26x |
d <- Data(...) |
296 | 26x |
.DataParts( |
297 | 26x |
d, |
298 | 26x |
part = part, |
299 | 26x |
nextPart = nextPart, |
300 | 26x |
part1Ladder = part1Ladder |
301 |
) |
|
302 |
} |
|
303 | ||
304 |
## default constructor ---- |
|
305 | ||
306 |
#' @rdname DataParts-class |
|
307 |
#' @note Typically, end users will not use the `.DefaultDataParts()` function. |
|
308 |
#' @export |
|
309 |
.DefaultDataParts <- function() { |
|
310 | 5x |
DataParts( |
311 | 5x |
x = c(0.1, 0.5, 1.5), |
312 | 5x |
y = c(0, 0, 0), |
313 | 5x |
ID = 1:3, |
314 | 5x |
cohort = 1:3, |
315 | 5x |
doseGrid = c(0.1, 0.5, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)), |
316 | 5x |
part = c(1L, 1L, 1L), |
317 | 5x |
nextPart = 1L, |
318 | 5x |
part1Ladder = c(0.1, 0.5, 1.5, 3, 6, 10) |
319 |
) |
|
320 |
} |
|
321 | ||
322 | ||
323 |
# DataMixture ---- |
|
324 | ||
325 |
## class ---- |
|
326 | ||
327 |
#' `DataMixture` |
|
328 |
#' |
|
329 |
#' @description `r lifecycle::badge("stable")` |
|
330 |
#' |
|
331 |
#' [`DataMixture`] is a class for the data with mixture sharing. |
|
332 |
#' It inherits from [`Data`] and it contains additional information |
|
333 |
#' on the mixture sharing. |
|
334 |
#' |
|
335 |
#' @slot xshare (`numeric`)\cr the doses for the share patients. |
|
336 |
#' @slot yshare (`integer`)\cr the vector of toxicity events (0 or 1) |
|
337 |
#' for the share patients. |
|
338 |
#' @slot nObsshare (`count`)\cr number of share patients. |
|
339 |
#' |
|
340 |
#' @aliases DataMixture |
|
341 |
#' @export |
|
342 |
#' |
|
343 |
.DataMixture <- setClass( |
|
344 |
Class = "DataMixture", |
|
345 |
slots = c( |
|
346 |
xshare = "numeric", |
|
347 |
yshare = "integer", |
|
348 |
nObsshare = "integer" |
|
349 |
), |
|
350 |
prototype = prototype( |
|
351 |
xshare = numeric(), |
|
352 |
yshare = integer(), |
|
353 |
nObsshare = 0L |
|
354 |
), |
|
355 |
contains = "Data", |
|
356 |
validity = v_data_mixture |
|
357 |
) |
|
358 | ||
359 |
## constructor ---- |
|
360 | ||
361 |
#' @rdname DataMixture-class |
|
362 |
#' |
|
363 |
#' @param xshare (`numeric`)\cr the doses for the share patients. |
|
364 |
#' @param yshare (`integer`)\cr the vector of toxicity events (0 or 1) |
|
365 |
#' for the share patients. You can also supply `numeric` vectors, |
|
366 |
#' but these will then be converted to `integer` internally. |
|
367 |
#' @param ... parameters passed to [Data()]. |
|
368 |
#' |
|
369 |
#' @export |
|
370 |
#' @example examples/Data-class-DataMixture.R |
|
371 |
#' |
|
372 |
DataMixture <- function(xshare = numeric(), |
|
373 |
yshare = integer(), |
|
374 |
...) { |
|
375 | 8x |
d <- Data(...) |
376 | 8x |
assert_integerish(yshare) |
377 | 8x |
assert_numeric(xshare) |
378 | 8x |
.DataMixture( |
379 | 8x |
d, |
380 | 8x |
xshare = as.numeric(xshare), |
381 | 8x |
yshare = as.integer(yshare), |
382 | 8x |
nObsshare = length(xshare) |
383 |
) |
|
384 |
} |
|
385 | ||
386 |
## default constructor ---- |
|
387 | ||
388 |
#' @rdname DataMixture-class |
|
389 |
#' @note Typically, end users will not use the `.DefaultDataMixture()` function. |
|
390 |
#' @export |
|
391 |
.DefaultDataMixture <- function() { |
|
392 | 6x |
DataMixture( |
393 | 6x |
xshare = c(12, 14, 16, 18.0), |
394 | 6x |
yshare = c(0L, 1L, 1L, 1L), |
395 | 6x |
nObsshare = 4L, |
396 | 6x |
x = c(0.1, 0.5, 1.5), |
397 | 6x |
y = c(0, 0, 0), |
398 | 6x |
ID = 1L:3L, |
399 | 6x |
cohort = 1L:3L, |
400 | 6x |
doseGrid = c(0.1, 0.5, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)) |
401 |
) |
|
402 |
} |
|
403 | ||
404 | ||
405 |
# DataDA ---- |
|
406 | ||
407 |
## class ---- |
|
408 | ||
409 |
#' `DataDA` |
|
410 |
#' |
|
411 |
#' @description `r lifecycle::badge("stable")` |
|
412 |
#' |
|
413 |
#' [`DataDA`] is a class for the time-to-DLT augmented data. |
|
414 |
#' It inherits from [`Data`] and it contains additional DLT free survival times. |
|
415 |
#' |
|
416 |
#' @note `survival time` here refers to the time period for which the subject |
|
417 |
#' did not experience any DLT, and is not referring to deaths. |
|
418 |
#' |
|
419 |
#' @slot u (`numeric`)\cr the continuous vector of DLT free survival times. |
|
420 |
#' @slot t0 (`numeric`)\cr time of initial dosing for each patient. |
|
421 |
#' Non-negative values sorted in ascending order. |
|
422 |
#' @slot Tmax (`number`)\cr the DLT observation period. |
|
423 |
#' |
|
424 |
#' @aliases DataDA |
|
425 |
#' @export |
|
426 |
#' |
|
427 |
.DataDA <- setClass( |
|
428 |
Class = "DataDA", |
|
429 |
slots = c( |
|
430 |
u = "numeric", |
|
431 |
t0 = "numeric", |
|
432 |
Tmax = "numeric" |
|
433 |
), |
|
434 |
prototype = prototype( |
|
435 |
u = numeric(), |
|
436 |
t0 = numeric(), |
|
437 |
Tmax = 0 + .Machine$double.xmin |
|
438 |
), |
|
439 |
contains = "Data", |
|
440 |
validity = v_data_da |
|
441 |
) |
|
442 | ||
443 |
## constructor ---- |
|
444 | ||
445 |
#' @rdname DataDA-class |
|
446 |
#' |
|
447 |
#' @param u (`numeric`)\cr the continuous vector of DLT free survival times. |
|
448 |
#' @param t0 (`numeric`)\cr time of initial dosing for each patient. |
|
449 |
#' Non-negative values sorted in ascending order. |
|
450 |
#' Default to vector of 0s of length equal to length of `u`. |
|
451 |
#' @param Tmax (`number`)\cr the DLT observation period. |
|
452 |
#' @param ... parameters passed to [Data()]. |
|
453 |
#' |
|
454 |
#' @export |
|
455 |
#' @example examples/Data-class-DataDA.R |
|
456 |
#' |
|
457 |
DataDA <- function(u = numeric(), |
|
458 |
t0 = numeric(length(u)), |
|
459 |
Tmax = 0 + .Machine$double.xmin, |
|
460 |
...) { |
|
461 | 26x |
d <- Data(...) |
462 | 26x |
.DataDA( |
463 | 26x |
d, |
464 | 26x |
u = as.numeric(u), |
465 | 26x |
t0 = as.numeric(t0), |
466 | 26x |
Tmax = as.numeric(Tmax) |
467 |
) |
|
468 |
} |
|
469 | ||
470 |
## default constructor ---- |
|
471 | ||
472 |
#' @rdname DataDA-class |
|
473 |
#' @note Typically, end users will not use the `.DefaultDataDA()` function. |
|
474 |
#' @export |
|
475 |
.DefaultDataDA <- function() { |
|
476 | 6x |
DataDA( |
477 | 6x |
u = c(42, 30, 15, 5, 20, 25, 30, 60), |
478 | 6x |
t0 = c(0, 15, 30, 40, 55, 70, 75, 85), |
479 | 6x |
Tmax = 60, |
480 | 6x |
x = c(0.1, 0.5, 1.5, 3, 6, 10, 10, 10), |
481 | 6x |
y = c(0, 0, 1, 1, 0, 0, 1, 0), |
482 | 6x |
doseGrid = c(0.1, 0.5, 1.5, 3, 6, seq(from = 10, to = 80, by = 2)), |
483 | 6x |
ID = 1L:8L, |
484 | 6x |
cohort = as.integer(c(1, 2, 3, 4, 5, 6, 6, 6)) |
485 |
) |
|
486 |
} |
|
487 | ||
488 |
# DataOrdinal ---- |
|
489 | ||
490 |
## class ---- |
|
491 | ||
492 |
#' `DataOrdinal` |
|
493 |
#' |
|
494 |
#' @description `r lifecycle::badge("experimental")` |
|
495 |
#' |
|
496 |
#' [`DataOrdinal`] is a class for ordinal toxicity data. |
|
497 |
#' It inherits from [`GeneralData`] and it describes toxicity responses on an |
|
498 |
#' ordinal rather than binary scale. |
|
499 |
#' |
|
500 |
#' @note This class has been implemented as a sibling of the existing `Data` class |
|
501 |
#' (rather than as a parent or child) to minimise the risk of unintended side |
|
502 |
#' effects on existing classes and methods. |
|
503 |
#' |
|
504 |
#' The default setting for the `yCategories` slot replicates the behaviour |
|
505 |
#' of the existing `Data` class. |
|
506 |
#' |
|
507 |
#' @aliases DataOrdinal |
|
508 |
#' @export |
|
509 |
.DataOrdinal <- setClass( |
|
510 |
Class = "DataOrdinal", |
|
511 |
contains = "GeneralData", |
|
512 |
slots = c( |
|
513 |
x = "numeric", |
|
514 |
y = "integer", |
|
515 |
doseGrid = "numeric", |
|
516 |
nGrid = "integer", |
|
517 |
xLevel = "integer", |
|
518 |
yCategories = "integer", |
|
519 |
placebo = "logical" |
|
520 |
), |
|
521 |
prototype = prototype( |
|
522 |
x = numeric(), |
|
523 |
y = integer(), |
|
524 |
doseGrid = numeric(), |
|
525 |
nGrid = 0L, |
|
526 |
xLevel = integer(), |
|
527 |
yCategories = c("No DLT" = 0L, "DLT" = 1L), |
|
528 |
placebo = FALSE |
|
529 |
), |
|
530 |
validity = v_data_ordinal |
|
531 |
) |
|
532 | ||
533 |
## constructor ---- |
|
534 | ||
535 |
#' @rdname DataOrdinal-class |
|
536 |
#' @param yCategories (named `integer`)\cr the names and codes for the |
|
537 |
#' toxicity categories used in the data. Category labels are taken from the |
|
538 |
#' names of the vector. The names of the vector must be unique and its values |
|
539 |
#' must be sorted and take the values 0, 1, 2, ... |
|
540 |
#' @inheritParams Data |
|
541 |
#' @inherit Data details note params |
|
542 |
#' @example examples/Data-class-DataOrdinal.R |
|
543 |
#' @export |
|
544 |
DataOrdinal <- function(x = numeric(), |
|
545 |
y = integer(), |
|
546 |
ID = integer(), |
|
547 |
cohort = integer(), |
|
548 |
doseGrid = numeric(), |
|
549 |
placebo = FALSE, |
|
550 |
yCategories = c("No DLT" = 0L, "DLT" = 1L), |
|
551 |
...) { |
|
552 | 70x |
assert_numeric(doseGrid, any.missing = FALSE, unique = TRUE) |
553 | 70x |
assert_integerish( |
554 | 70x |
yCategories, |
555 | 70x |
any.missing = FALSE, |
556 | 70x |
unique = TRUE, |
557 | 70x |
names = "unique", |
558 | 70x |
min.len = 2 |
559 |
) |
|
560 | 70x |
assert_flag(placebo) |
561 | ||
562 | 70x |
doseGrid <- as.numeric(sort(doseGrid)) |
563 | ||
564 | 70x |
if (length(ID) == 0 && length(x) > 0) { |
565 | ! |
message("Used default patient IDs!") |
566 | ! |
ID <- seq_along(x) |
567 |
} else { |
|
568 | 70x |
assert_integerish(ID, unique = TRUE) |
569 |
} |
|
570 | ||
571 | 70x |
if (!placebo && length(cohort) == 0 && length(x) > 0) { |
572 | ! |
message("Used best guess cohort indices!") |
573 |
# This is just assuming that consecutive patients |
|
574 |
# in the data set are in the same cohort if they |
|
575 |
# have the same dose. Note that this could be wrong, |
|
576 |
# if two subsequent cohorts are at the same dose. |
|
577 | ! |
cohort <- as.integer(c(1, 1 + cumsum(diff(x) != 0))) |
578 |
} else { |
|
579 | 70x |
assert_integerish(cohort) |
580 |
} |
|
581 | ||
582 | 70x |
.DataOrdinal( |
583 | 70x |
x = as.numeric(x), |
584 | 70x |
y = as.integer(y), |
585 | 70x |
ID = as.integer(ID), |
586 | 70x |
cohort = as.integer(cohort), |
587 | 70x |
doseGrid = doseGrid, |
588 | 70x |
nObs = length(x), |
589 | 70x |
nGrid = length(doseGrid), |
590 | 70x |
xLevel = match_within_tolerance(x = x, table = doseGrid), |
591 | 70x |
placebo = placebo, |
592 | 70x |
yCategories = yCategories |
593 |
) |
|
594 |
} |
|
595 | ||
596 | ||
597 |
## default constructor ---- |
|
598 | ||
599 |
#' @rdname DataOrdinal-class |
|
600 |
#' @note Typically, end users will not use the `.DefaultDataOrdinal()` function. |
|
601 |
#' @export |
|
602 |
.DefaultDataOrdinal <- function() { |
|
603 | 23x |
DataOrdinal( |
604 | 23x |
x = c(10, 20, 30, 40, 50, 50, 50, 60, 60, 60), |
605 | 23x |
y = as.integer(c(0, 0, 0, 0, 0, 1, 0, 0, 1, 2)), |
606 | 23x |
ID = 1L:10L, |
607 | 23x |
cohort = as.integer(c(1:4, 5, 5, 5, 6, 6, 6)), |
608 | 23x |
doseGrid = c(seq(from = 10, to = 100, by = 10)), |
609 | 23x |
yCategories = c("No tox" = 0L, "Sub-tox AE" = 1L, "DLT" = 2L), |
610 | 23x |
placebo = FALSE |
611 |
) |
|
612 |
} |
|
613 | ||
614 |
# DataGrouped ---- |
|
615 | ||
616 |
## class ---- |
|
617 | ||
618 |
#' `DataGrouped` |
|
619 |
#' |
|
620 |
#' @description `r lifecycle::badge("stable")` |
|
621 |
#' |
|
622 |
#' [`DataGrouped`] is a class for a two groups dose escalation data set, |
|
623 |
#' comprised of a monotherapy (`mono`) and a combination therapy (`combo`) |
|
624 |
#' arm. It inherits from [`Data`] and it contains the additional group information. |
|
625 |
#' |
|
626 |
#' @slot group (`factor`)\cr whether `mono` or `combo` was used. |
|
627 |
#' |
|
628 |
#' @aliases DataGrouped |
|
629 |
#' @export |
|
630 |
.DataGrouped <- setClass( |
|
631 |
Class = "DataGrouped", |
|
632 |
slots = c( |
|
633 |
group = "factor" |
|
634 |
), |
|
635 |
prototype = prototype( |
|
636 |
group = factor(levels = c("mono", "combo")) |
|
637 |
), |
|
638 |
contains = "Data", |
|
639 |
validity = v_data_grouped |
|
640 |
) |
|
641 | ||
642 |
#' @rdname DataGrouped-class |
|
643 |
#' |
|
644 |
#' @param group (`factor` or `character`)\cr whether `mono` or `combo` was used. |
|
645 |
#' If `character` then will be coerced to `factor` with the correct levels |
|
646 |
#' internally. |
|
647 |
#' @param ... parameters passed to [Data()]. |
|
648 |
#' |
|
649 |
#' @export |
|
650 |
#' @example examples/Data-class-DataGrouped.R |
|
651 |
#' |
|
652 |
DataGrouped <- function(group = character(), |
|
653 |
...) { |
|
654 | 81x |
d <- Data(...) |
655 | 81x |
if (!is.factor(group)) { |
656 | 81x |
assert_character(group) |
657 | 81x |
assert_subset(group, choices = c("mono", "combo")) |
658 | 81x |
group <- factor(group, levels = c("mono", "combo")) |
659 |
} |
|
660 | 81x |
.DataGrouped( |
661 | 81x |
d, |
662 | 81x |
group = group |
663 |
) |
|
664 |
} |
|
665 | ||
666 |
## default constructor ---- |
|
667 | ||
668 |
#' @rdname DataGrouped-class |
|
669 |
#' @note Typically, end users will not use the `.DefaultDataGrouped()` function. |
|
670 |
#' @export |
|
671 |
.DefaultDataGrouped <- function() { |
|
672 | 7x |
DataGrouped( |
673 | 7x |
group = c("mono", "mono", "combo"), |
674 | 7x |
x = c(1, 3, 5), |
675 | 7x |
y = c(0, 0, 0), |
676 | 7x |
ID = 1L:3L, |
677 | 7x |
cohort = 1L:3L, |
678 | 7x |
doseGrid = c(1, 3, 5, 10, 15, 20, 25, 40, 50, 80, 100), |
679 | 7x |
placebo = FALSE |
680 |
) |
|
681 |
} |
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 | 12x |
assert_flag(asis) |
13 | ||
14 | 10x |
rv <- paste0( |
15 | 10x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
16 | 10x |
"Based on a toxicity grade of ", |
17 | 10x |
x@grade, |
18 |
": ", |
|
19 | 10x |
paste0(knit_print(x@rule, asis = asis, ...), collapse = "\n") |
20 |
) |
|
21 | ||
22 | 10x |
if (asis) { |
23 | 2x |
rv <- knitr::asis_output(rv) |
24 |
} |
|
25 | 10x |
rv |
26 |
} |
|
27 | ||
28 |
# StoppingMaxGainCIRatio ---- |
|
29 | ||
30 |
#' @description `r lifecycle::badge("experimental")` |
|
31 |
#' @inheritParams knit_print.StoppingTargetProb |
|
32 |
#' @rdname knit_print |
|
33 |
#' @export |
|
34 |
#' @method knit_print StoppingMaxGainCIRatio |
|
35 |
knit_print.StoppingMaxGainCIRatio <- function( |
|
36 |
x, |
|
37 |
..., |
|
38 |
asis = TRUE) { |
|
39 | 8x |
assert_flag(asis) |
40 | ||
41 | 6x |
rv <- paste0( |
42 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
43 | 6x |
"If the ratio of the upper to the lower limit of the posterior 95% credible ", |
44 | 6x |
"interval for the probability of toxicity at the target dose (the smaller ", |
45 | 6x |
"of the MTD for ", |
46 | 6x |
100 * x@prob_target, |
47 | 6x |
"% target and GStar) is less than or equal to ", |
48 | 6x |
x@target_ratio, |
49 | 6x |
".\n\n" |
50 |
) |
|
51 | ||
52 | 6x |
if (asis) { |
53 | 2x |
rv <- knitr::asis_output(rv) |
54 |
} |
|
55 | 6x |
rv |
56 |
} |
|
57 | ||
58 |
# StoppingList ---- |
|
59 | ||
60 |
#' @description `r lifecycle::badge("experimental")` |
|
61 |
#' @inheritParams knit_print.StoppingTargetProb |
|
62 |
#' @param preamble (`character`)\cr the text that introduces the list of rules |
|
63 |
#' @param indent (`integer`)\cr the indent level of the current stopping rule |
|
64 |
#' list. Spaces with length `indent * 4` will be prepended to the beginning of |
|
65 |
#' the rendered stopping rule list. |
|
66 |
#' @rdname knit_print |
|
67 |
#' @export |
|
68 |
#' @method knit_print StoppingList |
|
69 |
knit_print.StoppingList <- function( |
|
70 |
x, |
|
71 |
..., |
|
72 |
preamble, |
|
73 |
indent = 0L, |
|
74 |
asis = TRUE) { |
|
75 | 63x |
assert_flag(asis) |
76 | 57x |
assert_integer(indent, lower = 0) |
77 | ||
78 | 57x |
if (missing(preamble)) { |
79 | 9x |
case_string <- switch( |
80 | 9x |
as.character(length(x@stop_list)), |
81 | 9x |
`1` = "rule ", |
82 | 9x |
"rules " |
83 |
) |
|
84 | 9x |
preamble <- paste0( |
85 | 9x |
"If the result of applying the summary function to the following ", |
86 | 9x |
case_string, |
87 | 9x |
"is `TRUE`:\n" |
88 |
) |
|
89 |
}else { |
|
90 | 48x |
assert_character(preamble, len = 1, any.missing = FALSE) |
91 |
} |
|
92 | ||
93 | 57x |
rules_list <- paste0( |
94 | 57x |
lapply( |
95 | 57x |
x@stop_list, |
96 | 57x |
function(z, indent) { |
97 | 131x |
paste0( |
98 | 131x |
strrep(" ", indent * 4), |
99 | 131x |
"- ", knit_print(z, asis = FALSE, indent = indent + 1L, ...)) |
100 |
}, |
|
101 | 57x |
indent = indent |
102 |
), |
|
103 | 57x |
collapse = "\n" |
104 |
) |
|
105 | 57x |
rv <- paste0( |
106 | 57x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
107 | 57x |
preamble, |
108 | 57x |
"\n", |
109 | 57x |
rules_list, |
110 | 57x |
"\n\n" |
111 |
) |
|
112 | ||
113 | 57x |
if (asis) { |
114 | 6x |
rv <- knitr::asis_output(rv) |
115 |
} |
|
116 | 57x |
rv |
117 |
} |
|
118 | ||
119 |
# StoppingAny ---- |
|
120 | ||
121 |
#' @description `r lifecycle::badge("experimental")` |
|
122 |
#' @inheritParams knit_print.StoppingList |
|
123 |
#' @rdname knit_print |
|
124 |
#' @export |
|
125 |
#' @method knit_print StoppingAny |
|
126 |
knit_print.StoppingAny <- function( |
|
127 |
x, |
|
128 |
..., |
|
129 |
preamble, |
|
130 |
asis = TRUE) { |
|
131 | ||
132 | 32x |
if (missing(preamble)) { |
133 | 32x |
case_string <- switch( |
134 | 32x |
as.character(length(x@stop_list)), |
135 | 32x |
`1` = c("this ", "rule is "), |
136 | 32x |
`2` = c("either of the ", "rules are "), |
137 | 32x |
c("any of the ", "rules are ") # this works as default case |
138 |
) |
|
139 | 32x |
preamble <- paste0( |
140 | 32x |
"If ", case_string[1], |
141 | 32x |
"following ", case_string[2], |
142 | 32x |
"`TRUE`:\n" |
143 |
) |
|
144 |
} |
|
145 | 32x |
knit_print.StoppingList(x, ..., preamble = preamble, asis = asis) |
146 |
} |
|
147 | ||
148 |
#' @description `r lifecycle::badge("experimental")` |
|
149 |
#' @inheritParams knit_print.StoppingList |
|
150 |
#' @rdname knit_print |
|
151 |
#' @export |
|
152 |
#' @method knit_print StoppingAll |
|
153 |
knit_print.StoppingAll <- function( |
|
154 |
x, |
|
155 |
..., |
|
156 |
preamble, |
|
157 |
asis = TRUE) { |
|
158 | 20x |
if (missing(preamble)) { |
159 | 20x |
case_string <- switch( |
160 | 20x |
as.character(length(x@stop_list)), |
161 | 20x |
`1` = c("this ", "rule is "), |
162 | 20x |
`2` = c("both of the ", "rules are "), |
163 | 20x |
c("all of the ", "rules are ") # this works as default case |
164 |
) |
|
165 | 20x |
preamble <- paste0( |
166 | 20x |
"If ", case_string[1], |
167 | 20x |
"following ", case_string[2], |
168 | 20x |
"`TRUE`:\n" |
169 |
) |
|
170 |
} |
|
171 | 20x |
knit_print.StoppingList(x, ..., preamble = preamble, asis = asis) |
172 |
} |
|
173 | ||
174 |
# StoppingTDCIRatio ---- |
|
175 | ||
176 |
#' @description `r lifecycle::badge("experimental")` |
|
177 |
#' @inheritParams knit_print.StoppingTargetProb |
|
178 |
#' @rdname knit_print |
|
179 |
#' @export |
|
180 |
#' @method knit_print StoppingTDCIRatio |
|
181 |
knit_print.StoppingTDCIRatio <- function( |
|
182 |
x, |
|
183 |
..., |
|
184 |
dose_label = "the next best dose", |
|
185 |
tox_label = "toxicity", |
|
186 |
fmt_string = paste0( |
|
187 |
"%sIf, at %s, the ratio of the upper to the lower limit of the posterior ", |
|
188 |
"95%% credible interval for %s (targetting %2.0f%%) is less than or equal to " |
|
189 |
), |
|
190 |
asis = TRUE) { |
|
191 | 8x |
assert_flag(asis) |
192 | 6x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
193 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
194 | 6x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
195 | ||
196 | 6x |
rv <- paste0( |
197 | 6x |
sprintf( |
198 | 6x |
fmt_string, |
199 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
200 | 6x |
dose_label, |
201 | 6x |
tox_label, |
202 | 6x |
100 * x@prob_target |
203 |
), |
|
204 | 6x |
x@target_ratio, |
205 | 6x |
".\n\n" |
206 |
) |
|
207 | ||
208 | 6x |
if (asis) { |
209 | 2x |
rv <- knitr::asis_output(rv) |
210 |
} |
|
211 | 6x |
rv |
212 |
} |
|
213 | ||
214 |
# StoppingTargetBiomarker ---- |
|
215 | ||
216 |
#' @description `r lifecycle::badge("experimental")` |
|
217 |
#' @inheritParams knit_print.StoppingTargetProb |
|
218 |
#' @param biomarker_label (`character`)\cr the term used to describe the biomarker |
|
219 |
#' @rdname knit_print |
|
220 |
#' @export |
|
221 |
#' @method knit_print StoppingTargetBiomarker |
|
222 |
knit_print.StoppingTargetBiomarker <- function( |
|
223 |
x, |
|
224 |
..., |
|
225 |
dose_label = "the next best dose", |
|
226 |
biomarker_label = "the target biomarker", |
|
227 |
fmt_string = paste0( |
|
228 |
"%sIf, at %s, the posterior probability that %s is in the range ", |
|
229 |
"(%.2f, %.2f)%s is %.0f%% or more.\n\n" |
|
230 |
), |
|
231 |
asis = TRUE) { |
|
232 | 14x |
assert_flag(asis) |
233 | 12x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
234 | 12x |
assert_character(biomarker_label, len = 1, any.missing = FALSE) |
235 | ||
236 | 12x |
rv <- sprintf( |
237 | 12x |
fmt_string, |
238 | 12x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
239 | 12x |
dose_label, |
240 | 12x |
biomarker_label, |
241 | 12x |
x@target[1], |
242 | 12x |
x@target[2], |
243 | 12x |
ifelse( |
244 | 12x |
x@is_relative, |
245 | 12x |
paste0(", relative to the maximum value of ", biomarker_label, ","), |
246 |
"" |
|
247 |
), |
|
248 | 12x |
100 * x@prob |
249 |
) |
|
250 | ||
251 | 12x |
if (asis) { |
252 | 2x |
rv <- knitr::asis_output(rv) |
253 |
} |
|
254 | 12x |
rv |
255 |
} |
|
256 | ||
257 |
# StoppingLowestDoseHSRBeta ---- |
|
258 | ||
259 |
#' @description `r lifecycle::badge("experimental")` |
|
260 |
#' @inheritParams knit_print.StoppingTargetProb |
|
261 |
#' @rdname knit_print |
|
262 |
#' @export |
|
263 |
#' @method knit_print StoppingLowestDoseHSRBeta |
|
264 |
knit_print.StoppingLowestDoseHSRBeta <- function( |
|
265 |
x, |
|
266 |
..., |
|
267 |
tox_label = "toxicity", |
|
268 |
fmt_string = paste0( |
|
269 |
"%sIf, using a Hard Stopping Rule with a prior of Beta(%.0f, %.0f), the ", |
|
270 |
"lowest dose in the dose grid has a posterior probability of %s of ", |
|
271 |
"%.0f%% or more.\n\n" |
|
272 |
), |
|
273 |
asis = TRUE) { |
|
274 | 8x |
assert_flag(asis) |
275 | 6x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
276 | ||
277 | 6x |
rv <- sprintf( |
278 | 6x |
fmt_string, |
279 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
280 | 6x |
x@a, |
281 | 6x |
x@b, |
282 | 6x |
tox_label, |
283 | 6x |
100 * x@prob |
284 |
) |
|
285 | ||
286 | 6x |
if (asis) { |
287 | 2x |
rv <- knitr::asis_output(rv) |
288 |
} |
|
289 | 6x |
rv |
290 |
} |
|
291 | ||
292 |
# StoppingMTDCV ---- |
|
293 | ||
294 |
#' @description `r lifecycle::badge("experimental")` |
|
295 |
#' @inheritParams knit_print.StoppingTargetProb |
|
296 |
#' @rdname knit_print |
|
297 |
#' @export |
|
298 |
#' @method knit_print StoppingMTDCV |
|
299 |
knit_print.StoppingMTDCV <- function( |
|
300 |
x, |
|
301 |
..., |
|
302 |
fmt_string = paste0( |
|
303 |
"%sIf the posterior estimate of the robust coefficient of variation of ", |
|
304 |
"the MTD (targetting %2.0f%%), is than or equal to %.0f%%.\n\n" |
|
305 |
), |
|
306 |
asis = TRUE) { |
|
307 | 8x |
assert_flag(asis) |
308 | 6x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
309 | ||
310 | 6x |
rv <- sprintf( |
311 | 6x |
fmt_string, |
312 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
313 | 6x |
100 * x@target, |
314 | 6x |
100 * x@thresh_cv |
315 |
) |
|
316 | ||
317 | 6x |
if (asis) { |
318 | 2x |
rv <- knitr::asis_output(rv) |
319 |
} |
|
320 | 6x |
rv |
321 |
} |
|
322 | ||
323 |
# StoppingMTDdistribution ---- |
|
324 | ||
325 |
#' @description `r lifecycle::badge("experimental")` |
|
326 |
#' @inheritParams knit_print.StoppingTargetProb |
|
327 |
#' @rdname knit_print |
|
328 |
#' @export |
|
329 |
#' @method knit_print StoppingMTDdistribution |
|
330 |
knit_print.StoppingMTDdistribution <- function( |
|
331 |
x, |
|
332 |
..., |
|
333 |
fmt_string = "%sIf the mean posterior probability of %s at %.0f%% of %s is at least %4.2f.\n\n", |
|
334 |
dose_label = "the next best dose", |
|
335 |
tox_label = "toxicity", |
|
336 |
asis = TRUE) { |
|
337 | 8x |
assert_flag(asis) |
338 | 6x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
339 | 6x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
340 | 6x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
341 | ||
342 | 6x |
rv <- sprintf( |
343 | 6x |
fmt_string, |
344 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
345 | 6x |
tox_label, |
346 | 6x |
100 * x@thresh, |
347 | 6x |
dose_label, |
348 | 6x |
x@prob |
349 |
) |
|
350 | ||
351 | 6x |
if (asis) { |
352 | 2x |
rv <- knitr::asis_output(rv) |
353 |
} |
|
354 | 6x |
rv |
355 |
} |
|
356 | ||
357 |
# StoppingHighestDose ---- |
|
358 | ||
359 |
#' @description `r lifecycle::badge("experimental")` |
|
360 |
#' @param asis (`flag`)\cr Not used at present |
|
361 |
#' @rdname knit_print |
|
362 |
#' @export |
|
363 |
#' @method knit_print StoppingHighestDose |
|
364 |
knit_print.StoppingHighestDose <- function( |
|
365 |
x, |
|
366 |
..., |
|
367 |
dose_label = "the highest dose in the dose grid", |
|
368 |
asis = TRUE) { |
|
369 | 8x |
rv <- paste0( |
370 | 8x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
371 | 8x |
"If the next best dose is ", |
372 | 8x |
dose_label, |
373 | 8x |
".\n\n" |
374 |
) |
|
375 | ||
376 | 8x |
if (asis) { |
377 | 2x |
rv <- knitr::asis_output(rv) |
378 |
} |
|
379 | 6x |
rv |
380 |
} |
|
381 | ||
382 |
# StoppingSpecificDose ---- |
|
383 | ||
384 |
#' @description `r lifecycle::badge("experimental")` |
|
385 |
#' @param asis (`flag`)\cr Not used at present |
|
386 |
#' @inheritParams knit_print.StoppingTargetProb |
|
387 |
#' @rdname knit_print |
|
388 |
#' @export |
|
389 |
#' @method knit_print StoppingSpecificDose |
|
390 |
knit_print.StoppingSpecificDose <- function( |
|
391 |
x, |
|
392 |
..., |
|
393 |
dose_label = as.character(x@dose), |
|
394 |
asis = TRUE) { |
|
395 | 8x |
x@rule@report_label <- x@report_label |
396 | 8x |
knit_print( |
397 | 8x |
x@rule, |
398 |
..., |
|
399 | 8x |
dose_label = dose_label, |
400 | 8x |
asis = asis |
401 |
) |
|
402 |
} |
|
403 | ||
404 |
# StoppingTargetProb ---- |
|
405 | ||
406 |
#' @description `r lifecycle::badge("experimental")` |
|
407 |
#' @param asis (`flag`)\cr Not used at present |
|
408 |
#' @param fmt_string (`character`)\cr the character string that defines the format |
|
409 |
#' of the output |
|
410 |
#' @param dose_label (`character`)\cr the term used to describe the target dose |
|
411 |
#' @param tox_label (`character`)\cr the term used to describe toxicity |
|
412 |
#' @rdname knit_print |
|
413 |
#' @export |
|
414 |
#' @method knit_print StoppingTargetProb |
|
415 |
knit_print.StoppingTargetProb <- function( |
|
416 |
x, |
|
417 |
..., |
|
418 |
fmt_string = paste0( |
|
419 |
"%sIf the probability of %s at %s is in the range [%4.2f, %4.2f] ", |
|
420 |
"is at least %4.2f.\n\n" |
|
421 |
), |
|
422 |
dose_label = "the next best dose", |
|
423 |
tox_label = "toxicity", |
|
424 |
asis = TRUE) { |
|
425 | 60x |
assert_flag(asis) |
426 | 56x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
427 | 56x |
assert_character(tox_label, len = 1, any.missing = FALSE) |
428 | 56x |
assert_character(fmt_string, len = 1, any.missing = FALSE) |
429 | ||
430 | 56x |
rv <- sprintf( |
431 | 56x |
fmt_string, |
432 | 56x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
433 | 56x |
tox_label, |
434 | 56x |
dose_label, |
435 | 56x |
x@target[1], |
436 | 56x |
x@target[2], |
437 | 56x |
x@prob |
438 |
) |
|
439 | ||
440 | 56x |
if (asis) { |
441 | 6x |
rv <- knitr::asis_output(rv) |
442 |
} |
|
443 | 56x |
rv |
444 |
} |
|
445 | ||
446 |
# StoppingMinCohorts ---- |
|
447 | ||
448 |
#' @description `r lifecycle::badge("experimental")` |
|
449 |
#' @param asis (`flag`)\cr Not used at present |
|
450 |
#' @rdname knit_print |
|
451 |
#' @export |
|
452 |
#' @method knit_print StoppingMinCohorts |
|
453 |
knit_print.StoppingMinCohorts <- function( |
|
454 |
x, |
|
455 |
..., |
|
456 |
asis = TRUE) { |
|
457 | 43x |
assert_flag(asis) |
458 | ||
459 | 41x |
rv <- paste0( |
460 | 41x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
461 | 41x |
"If ", |
462 | 41x |
x@nCohorts, |
463 | 41x |
" or more cohorts have been treated.\n\n" |
464 |
) |
|
465 | 41x |
if (asis) { |
466 | 2x |
rv <- knitr::asis_output(rv) |
467 |
} |
|
468 | 41x |
rv |
469 |
} |
|
470 | ||
471 |
# StoppingMinPatients ---- |
|
472 | ||
473 |
#' @description `r lifecycle::badge("experimental")` |
|
474 |
#' @param label (`character`)\cr the term used to label participants |
|
475 |
#' @param asis (`flag`)\cr Not used at present |
|
476 |
#' @rdname knit_print |
|
477 |
#' @export |
|
478 |
#' @method knit_print StoppingMinPatients |
|
479 |
knit_print.StoppingMinPatients <- function( |
|
480 |
x, |
|
481 |
..., |
|
482 |
label = "participant", |
|
483 |
asis = TRUE) { |
|
484 | 80x |
assert_flag(asis) |
485 | 78x |
label <- h_prepare_labels(label) |
486 | ||
487 | 78x |
rv <- paste0( |
488 | 78x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
489 | 78x |
"If ", |
490 | 78x |
x@nPatients, |
491 | 78x |
paste0(" or more ", label[2], " have been treated."), |
492 | 78x |
"\n\n" |
493 |
) |
|
494 | 78x |
if (asis) { |
495 | 2x |
rv <- knitr::asis_output(rv) |
496 |
} |
|
497 | 78x |
rv |
498 |
} |
|
499 | ||
500 |
# StoppingPatientsNearDose ---- |
|
501 | ||
502 |
#' @description `r lifecycle::badge("experimental")` |
|
503 |
#' @param label (`character`)\cr the term used to label participants |
|
504 |
#' @inheritParams knit_print.StoppingTargetProb |
|
505 |
#' @param asis (`flag`)\cr Not used at present |
|
506 |
#' @rdname knit_print |
|
507 |
#' @export |
|
508 |
#' @method knit_print StoppingPatientsNearDose |
|
509 |
knit_print.StoppingPatientsNearDose <- function( |
|
510 |
x, |
|
511 |
..., |
|
512 |
dose_label = "the next best dose", |
|
513 |
label = "participants", |
|
514 |
asis = TRUE) { |
|
515 | 8x |
assert_flag(asis) |
516 | 6x |
assert_character(label, len = 1, any.missing = FALSE) |
517 | 6x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
518 | ||
519 | 6x |
rv <- paste0( |
520 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
521 | 6x |
"If ", |
522 | 6x |
x@nPatients, |
523 | 6x |
paste0(" or more ", label, " have been treated "), |
524 | 6x |
ifelse( |
525 | 6x |
x@percentage == 0, |
526 | 6x |
"at ", |
527 | 6x |
paste0("within ", x@percentage, "% of ") |
528 |
), |
|
529 | 6x |
dose_label, |
530 | 6x |
".\n\n" |
531 |
) |
|
532 | 6x |
if (asis) { |
533 | 2x |
rv <- knitr::asis_output(rv) |
534 |
} |
|
535 | 6x |
rv |
536 |
} |
|
537 | ||
538 |
# StoppingCohortsNearDose ---- |
|
539 | ||
540 |
#' @description `r lifecycle::badge("experimental")` |
|
541 |
#' @param asis (`flag`)\cr Not used at present |
|
542 |
#' @inheritParams knit_print.StoppingTargetProb |
|
543 |
#' @rdname knit_print |
|
544 |
#' @export |
|
545 |
#' @method knit_print StoppingCohortsNearDose |
|
546 |
knit_print.StoppingCohortsNearDose <- function( |
|
547 |
x, |
|
548 |
..., |
|
549 |
dose_label = "the next best dose", |
|
550 |
asis = TRUE) { |
|
551 | 8x |
assert_flag(asis) |
552 | 6x |
assert_character(dose_label, len = 1, any.missing = FALSE) |
553 | ||
554 | 6x |
rv <- paste0( |
555 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
556 | 6x |
"If ", |
557 | 6x |
x@nCohorts, |
558 | 6x |
" or more cohorts have been treated ", |
559 | 6x |
ifelse( |
560 | 6x |
x@percentage == 0, |
561 | 6x |
"at ", |
562 | 6x |
paste0("within ", x@percentage, "% of ") |
563 |
), |
|
564 | 6x |
dose_label, |
565 | 6x |
".\n\n" |
566 |
) |
|
567 | ||
568 | 6x |
if (asis) { |
569 | 2x |
rv <- knitr::asis_output(rv) |
570 |
} |
|
571 | 6x |
rv |
572 |
} |
|
573 | ||
574 |
# StoppingMissingDose ---- |
|
575 | ||
576 |
#' @description `r lifecycle::badge("experimental")` |
|
577 |
#' @param asis (`flag`)\cr Not used at present |
|
578 |
#' @rdname knit_print |
|
579 |
#' @export |
|
580 |
#' @method knit_print StoppingMissingDose |
|
581 |
knit_print.StoppingMissingDose <- function( |
|
582 |
x, |
|
583 |
..., |
|
584 |
asis = TRUE) { |
|
585 | 8x |
assert_flag(asis) |
586 | ||
587 | 6x |
rv <- paste0( |
588 | 6x |
ifelse(is.na(x@report_label), "", paste0(x@report_label, ": ")), |
589 | 6x |
"If the dose returned by <code>nextBest()</code> is ", |
590 | 6x |
"<code>NA</code>, or if the trial includes a placebo dose, the placebo dose.\n\n" |
591 |
) |
|
592 | ||
593 | 6x |
if (asis) { |
594 | 2x |
rv <- knitr::asis_output(rv) |
595 |
} |
|
596 | 6x |
rv |
597 |
} |
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(object@stop_report, |
56 | ! |
mode = "logical", |
57 | ! |
nrows = nSims, |
58 | ! |
min.cols = 1, |
59 | ! |
any.missing = FALSE |
60 |
), |
|
61 | ! |
"stop_report must be a matrix of mode logical in which the number of rows |
62 | ! |
equals the number of simulations and which must not contain any missing values" |
63 |
) |
|
64 | ||
65 | ! |
v$result() |
66 |
} |
|
67 | ||
68 |
#' @describeIn v_general_simulations validates that the [`DualSimulations`] object and |
|
69 |
#' capture the dose-biomarker `fits`, and the `sigma2W` and `rho` estimates. |
|
70 |
#' |
|
71 |
v_dual_simulations <- function(object) { |
|
72 | ! |
v <- Validate() |
73 | ||
74 | ! |
nSims <- length(object@data) |
75 | ||
76 | ! |
v$check( |
77 | ! |
identical(length(object@fit_biomarker), nSims), |
78 | ! |
"fit_biomarker list has to have same length as data" |
79 |
) |
|
80 | ! |
v$check( |
81 | ! |
identical(length(object@rho_est), nSims), |
82 | ! |
"rho_est vector has to have same length as data" |
83 |
) |
|
84 | ! |
v$check( |
85 | ! |
identical(length(object@sigma2w_est), nSims), |
86 | ! |
"sigma2w_est has to have same length as data" |
87 |
) |
|
88 | ||
89 | ! |
v$result() |
90 |
} |
|
91 | ||
92 |
# PseudoSimulations ---- |
|
93 | ||
94 |
#' Internal Helper Functions for Validation of [`PseudoSimulations`] Objects |
|
95 |
#' |
|
96 |
#' @description `r lifecycle::badge("stable")` |
|
97 |
#' |
|
98 |
#' These functions are only used internally to validate the format of an input |
|
99 |
#' [`PseudoSimulations`] or inherited classes and therefore not exported. |
|
100 |
#' |
|
101 |
#' @name v_pseudo_simulations |
|
102 |
#' @param object (`PseudoSimulations`)\cr object to validate. |
|
103 |
#' @return A `character` vector with the validation failure messages, |
|
104 |
#' or `TRUE` in case validation passes. |
|
105 |
NULL |
|
106 | ||
107 |
#' @describeIn v_pseudo_simulations validates that the [`PseudoSimulations`] object |
|
108 |
#' contains valid `fit`, `FinalTDtargetEndOfTrialEstimates` , |
|
109 |
#' `FinalTDtargetDuringTrialAtDoseGrid`,`FinalTDtargetEndOfTrialAtDoseGrid` , |
|
110 |
#' `FinalTDEOTCIs`, `FinalTDEOTRatios`, `FinalCIs`, `FinalRatios`, |
|
111 |
#' object and valid `stopReasons` simulations. |
|
112 | ||
113 |
v_pseudo_simulations <- function(object) { |
|
114 | ! |
v <- Validate() |
115 | ||
116 | ! |
nSims <- length(object@data) |
117 | ! |
v$check( |
118 | ! |
identical(length(object@stop_reasons), nSims), |
119 | ! |
"stopReasons must have same length as data" |
120 |
) |
|
121 | ||
122 | ! |
v$result() |
123 |
} |
|
124 | ||
125 |
#' @describeIn v_pseudo_simulations validates that the [`PseudoDualSimulations`] object |
|
126 |
#' contains valid `fit_eff`, `final_gstar_estimates` , `final_gstar_at_dose_grid`, |
|
127 |
#' `final_gstar_cis` , `final_gstar_ratios`, `final_optimal_dose`, `final_optimal_dose_at_dose_grid` |
|
128 |
#' object and valid `sigma2_est` simulations. |
|
129 | ||
130 |
v_pseudo_dual_simulations <- function(object) { |
|
131 | ! |
v <- Validate() |
132 | ! |
nSims <- length(object@data) |
133 | ! |
v$check( |
134 | ! |
identical(length(object@sigma2_est), nSims), |
135 | ! |
"sigma2_est has to have same length as data" |
136 |
) |
|
137 | ! |
v$result() |
138 |
} |
|
139 | ||
140 |
#' @describeIn v_pseudo_simulations validates that the [`PseudoDualFlexiSimulations`] |
|
141 |
#' object contains valid `sigma2betaWest` vector of the final posterior mean |
|
142 |
#' sigma2betaW estimates.`FinalGstarEstimates` , `FinalGstarAtDoseGrid`, |
|
143 |
#' |
|
144 |
v_pseudo_dual_flex_simulations <- function(object) { |
|
145 | ! |
v <- Validate() |
146 | ! |
nSims <- length(object@data) |
147 | ! |
v$check( |
148 | ! |
identical(length(object@sigma2betaWest), nSims), |
149 | ! |
"sigma2betaWest has to have same length as data" |
150 |
) |
|
151 | ! |
v$result() |
152 |
} |
|
153 | ||
154 |
#' @describeIn v_general_simulations validates that the [`DASimulations`] object |
|
155 |
#' contains valid `trialduration` the vector of trial duration values for all |
|
156 |
#' simulations. |
|
157 | ||
158 |
v_da_simulations <- function(object) { |
|
159 | ! |
v <- Validate() |
160 | ||
161 | ! |
nSims <- length(object@data) |
162 | ||
163 | ! |
v$check( |
164 | ! |
identical(length(object@trialduration), nSims), |
165 | ! |
"trialduration vector has to have same length as data" |
166 |
) |
|
167 | ||
168 | ! |
v$result() |
169 |
} |
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 | 25x |
assert_number(seed, null.ok = TRUE) |
20 | ||
21 | 25x |
if (!exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) { |
22 | ! |
runif(1) |
23 |
} |
|
24 | ||
25 | 25x |
if (is.null(seed)) { |
26 | 4x |
get(".Random.seed", envir = .GlobalEnv) |
27 |
} else { |
|
28 | 21x |
seed <- as.integer(seed) |
29 | 21x |
r_seed <- get(".Random.seed", envir = .GlobalEnv) |
30 |
# Make sure r_seed exists in parent frame. |
|
31 | 21x |
assign(".r_seed", r_seed, envir = parent.frame()) |
32 | 21x |
set.seed(seed) |
33 |
# Here we need the r_seed in the parent.frame! |
|
34 | 21x |
do.call( |
35 | 21x |
"on.exit", |
36 | 21x |
list(quote(assign(".Random.seed", .r_seed, envir = .GlobalEnv))), |
37 | 21x |
envir = parent.frame() |
38 |
) |
|
39 | 21x |
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 | 25x |
assert_flag(parallel) |
68 | 24x |
assert_integerish(n_cores, lower = 1) |
69 | ||
70 | 23x |
if (!parallel) { |
71 | 23x |
lapply( |
72 | 23x |
X = seq_len(nsim), |
73 | 23x |
FUN = fun |
74 |
) |
|
75 |
} else { |
|
76 |
# Process all simulations. |
|
77 | ! |
cores <- min( |
78 | ! |
as.integer(n_cores), |
79 | ! |
parallelly::availableCores() |
80 |
) |
|
81 | ||
82 |
# Start the cluster. |
|
83 | ! |
cl <- parallel::makeCluster(cores) |
84 | ||
85 |
# Load the required R package. |
|
86 | ! |
parallel::clusterEvalQ(cl, { |
87 | ! |
library(crmPack) |
88 | ! |
NULL |
89 |
}) |
|
90 | ||
91 |
# Export local variables from the caller environment. |
|
92 |
# Note: parent.frame() is different from parent.env() which returns |
|
93 |
# the environment where this function has been defined! |
|
94 | ! |
parallel::clusterExport( |
95 | ! |
cl = cl, |
96 | ! |
varlist = vars, |
97 | ! |
envir = parent.frame() |
98 |
) |
|
99 | ||
100 |
# Export all global variables. |
|
101 | ! |
parallel::clusterExport( |
102 | ! |
cl = cl, |
103 | ! |
varlist = ls(.GlobalEnv) |
104 |
) |
|
105 | ||
106 |
# Load user extensions from global options. |
|
107 | ! |
crmpack_extensions <- getOption("crmpack_extensions") |
108 | ! |
if (is.null(crmpack_extensions) != TRUE) { |
109 | ! |
tryCatch( |
110 |
{ |
|
111 | ! |
parallel::clusterCall(cl, crmpack_extensions) |
112 |
}, |
|
113 | ! |
error = function(e) { |
114 | ! |
stop("Failed to export crmpack_extensions: ", e$message) |
115 |
} |
|
116 |
) |
|
117 |
} |
|
118 | ||
119 |
# Do the computations in parallel. |
|
120 | ! |
res <- parallel::parLapply( |
121 | ! |
cl = cl, |
122 | ! |
X = seq_len(nsim), |
123 | ! |
fun = fun |
124 |
) |
|
125 | ||
126 |
# Stop the cluster. |
|
127 | ! |
parallel::stopCluster(cl) |
128 | ||
129 | ! |
res |
130 |
} |
|
131 |
} |
|
132 | ||
133 | ||
134 | ||
135 | ||
136 |
#' Helper Function to call truth calculation |
|
137 |
#' |
|
138 |
#' @param dose (`number`)\cr current dose. |
|
139 |
#' @param truth (`function`)\cr defines the true probability for a DLT at a dose. |
|
140 |
#' @param this_args (`data.frame`)\cr list of arguments for the truth. |
|
141 |
#' @return The updated `this_truth`. |
|
142 |
#' |
|
143 |
#' @keywords internal |
|
144 |
h_this_truth <- function(dose, this_args, truth) { |
|
145 | 80x |
do.call( |
146 | 80x |
truth, |
147 |
## First argument: the dose |
|
148 | 80x |
c( |
149 | 80x |
dose, |
150 |
## Following arguments |
|
151 | 80x |
this_args |
152 |
) |
|
153 |
) |
|
154 |
} |
|
155 | ||
156 | ||
157 |
#' Helper Function to create return list for Simulations output |
|
158 |
#' |
|
159 |
#' @param resultList (`list`)\cr raw iteration output. |
|
160 |
#' |
|
161 |
#' @return aggregated output for simulation object `list`. |
|
162 |
#' |
|
163 |
#' @keywords internal |
|
164 |
h_simulations_output_format <- function(resultList) { |
|
165 |
## put everything in the Simulations format: |
|
166 | ||
167 |
## setup the list for the simulated data objects |
|
168 | 7x |
dataList <- lapply(resultList, "[[", "data") |
169 | ||
170 |
## the vector of the final dose recommendations |
|
171 | 7x |
recommendedDoses <- as.numeric(sapply(resultList, "[[", "dose")) |
172 | ||
173 |
## setup the list for the final fits |
|
174 | 7x |
fitList <- lapply(resultList, "[[", "fit") |
175 | ||
176 |
## the reasons for stopping |
|
177 | 7x |
stopReasons <- lapply(resultList, "[[", "stop") |
178 | ||
179 |
# individual stopping rule results as matrix, labels as column names |
|
180 | 7x |
stopResults <- lapply(resultList, "[[", "report_results") |
181 | 7x |
stop_matrix <- as.matrix(do.call(rbind, stopResults)) |
182 | ||
183 |
# Result list of additional statistical summary. |
|
184 | 7x |
additional_stats <- lapply(resultList, "[[", "additional_stats") |
185 | ||
186 | 7x |
return(list( |
187 | 7x |
dataList = dataList, |
188 | 7x |
recommendedDoses = recommendedDoses, |
189 | 7x |
fitList = fitList, |
190 | 7x |
stopReasons = stopReasons, |
191 | 7x |
stopResults = stopResults, |
192 | 7x |
additional_stats = additional_stats, |
193 | 7x |
stop_matrix = stop_matrix |
194 |
)) |
|
195 |
} |
|
196 | ||
197 | ||
198 |
#' Helper function to recursively unpack stopping rules and return lists with |
|
199 |
#' logical value and label given |
|
200 |
#' |
|
201 |
#' @param stopit_tree object from simulate method |
|
202 |
#' @return named list |
|
203 | ||
204 |
h_unpack_stopit <- function(stopit_tree) { |
|
205 | 315x |
label <- attr(stopit_tree, "report_label") |
206 | 315x |
value <- stopit_tree[1] |
207 | 315x |
names(value) <- label |
208 | 315x |
value |
209 | 315x |
if (is.null(attr(stopit_tree, "individual"))) { |
210 | 279x |
return(value) |
211 |
} else { |
|
212 | 36x |
return(unlist(c(value, lapply(attr(stopit_tree, "individual"), h_unpack_stopit)))) |
213 |
} |
|
214 |
} |
|
215 | ||
216 | ||
217 | ||
218 |
#' Helper function to determine the dlts including first separate and placebo |
|
219 |
#' condition |
|
220 |
#' |
|
221 |
#' @param data (`Data`)\cr what data to start from. |
|
222 |
#' @param dose (`number`)\cr current dose. |
|
223 |
#' @param prob (`function`)\cr defines the true probability for a DLT at a dose. |
|
224 |
#' @param prob_placebo (`function`)\cr defines the true probability for a DLT at a placebo condition. |
|
225 |
#' @param cohort_size (`number`)\cr the cohort size to use. |
|
226 |
#' @param cohort_size_placebo (`number`)\cr the cohort size to use for placebo condition. |
|
227 |
#' @param dose_grid (`numeric`)\cr the dose_grid as specified by the user. |
|
228 |
#' @param first_separate (`flag`)\cr whether the first patient is enrolled separately. |
|
229 |
#' @return updated data object |
|
230 |
#' @keywords internal |
|
231 | ||
232 | ||
233 |
h_determine_dlts <- function(data, |
|
234 |
dose, |
|
235 |
prob, |
|
236 |
prob_placebo, |
|
237 |
cohort_size, |
|
238 |
cohort_size_placebo, |
|
239 |
dose_grid, |
|
240 |
first_separate) { |
|
241 | 200x |
assert_class(data, "Data") |
242 | 200x |
assert_number(dose) |
243 | 200x |
assert_number(prob) |
244 | 200x |
assert_number(cohort_size) |
245 | 200x |
assert_flag(first_separate) |
246 | ||
247 | 200x |
if (first_separate && cohort_size > 1) { |
248 | 29x |
dlts <- rbinom(n = 1, size = 1, prob = prob) |
249 | 29x |
if ((data@placebo) && cohort_size_placebo > 0) { |
250 | ! |
dlts_placebo <- rbinom(n = 1, size = 1, prob = prob_placebo) |
251 |
} |
|
252 | 29x |
if (dlts == 0) { |
253 | 16x |
dlts <- c(dlts, rbinom(n = cohort_size - 1L, size = 1, prob = prob)) |
254 | 16x |
if ((data@placebo) && cohort_size_placebo > 0) { |
255 | ! |
dlts_placebo <- c(dlts_placebo, rbinom( |
256 | ! |
n = cohort_size_placebo, # cohort_size_placebo - 1? |
257 | ! |
size = 1, |
258 | ! |
prob = prob_placebo |
259 |
)) |
|
260 |
} |
|
261 |
} |
|
262 |
} else { |
|
263 | 171x |
dlts <- rbinom(n = cohort_size, size = 1, prob = prob) |
264 | 171x |
if ((data@placebo) && cohort_size_placebo > 0) { |
265 | 6x |
dlts_placebo <- rbinom(n = cohort_size_placebo, size = 1, prob = prob_placebo) |
266 |
} |
|
267 |
} |
|
268 | ||
269 | ||
270 | 200x |
if ((data@placebo) && cohort_size_placebo > 0) { |
271 | 6x |
this_data <- update( |
272 | 6x |
object = data, |
273 | 6x |
x = dose_grid, |
274 | 6x |
y = dlts_placebo, |
275 | 6x |
check = FALSE |
276 |
) |
|
277 | ||
278 |
## update the data with active dose |
|
279 | 6x |
this_data <- update( |
280 | 6x |
object = this_data, |
281 | 6x |
x = dose, |
282 | 6x |
y = dlts, |
283 | 6x |
new_cohort = FALSE |
284 |
) |
|
285 |
} else { |
|
286 |
## update the data with this cohort |
|
287 | 194x |
this_data <- update( |
288 | 194x |
object = data, |
289 | 194x |
x = dose, |
290 | 194x |
y = dlts |
291 |
) |
|
292 |
} |
|
293 | 200x |
return(this_data) |
294 |
} |
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 = |
|
34 |
list( |
|
35 |
Data( |
|
36 |
x = 1:2, |
|
37 |
y = 0:1, |
|
38 |
doseGrid = 1:2, |
|
39 |
ID = 1L:2L, |
|
40 |
cohort = 1L:2L |
|
41 |
), |
|
42 |
Data( |
|
43 |
x = 3:4, |
|
44 |
y = 0:1, |
|
45 |
doseGrid = 3:4, |
|
46 |
ID = 1L:2L, |
|
47 |
cohort = 1L:2L |
|
48 |
) |
|
49 |
), |
|
50 |
doses = c(1, 2), |
|
51 |
seed = 1L |
|
52 |
), |
|
53 |
contains = "CrmPackClass", |
|
54 |
validity = v_general_simulations |
|
55 |
) |
|
56 | ||
57 |
## constructor ---- |
|
58 | ||
59 |
#' @rdname GeneralSimulations-class |
|
60 |
#' |
|
61 |
#' @param data (`list`)\cr see slot definition. |
|
62 |
#' @param doses (`numeric`)\cr see slot definition. |
|
63 |
#' @param seed (`integer`)\cr see slot definition. |
|
64 |
#' |
|
65 |
#' @example examples/Simulations-class-GeneralSimulations.R |
|
66 |
#' @export |
|
67 |
GeneralSimulations <- function(data, |
|
68 |
doses, |
|
69 |
seed) { |
|
70 | 45x |
assert_integerish(seed) |
71 | 45x |
.GeneralSimulations( |
72 | 45x |
data = data, |
73 | 45x |
doses = doses, |
74 | 45x |
seed = as.integer(seed) |
75 |
) |
|
76 |
} |
|
77 | ||
78 | ||
79 |
## default constructor |
|
80 | ||
81 |
#' @rdname GeneralSimulations-class |
|
82 |
#' @note Typically, end users will not use the `.DefaultGeneralSimulations()` function. |
|
83 |
#' @export |
|
84 |
.DefaultGeneralSimulations <- function() { |
|
85 | 5x |
GeneralSimulations( |
86 | 5x |
data = list( |
87 | 5x |
Data(x = 1:3, y = c(0, 1, 0), doseGrid = 1:3, ID = 1L:3L, cohort = 1L:3L), |
88 | 5x |
Data(x = 4:6, y = c(0, 1, 0), doseGrid = 4:6, ID = 1L:3L, cohort = 1L:3L) |
89 |
), |
|
90 | 5x |
doses = c(1, 2), |
91 | 5x |
seed = 123 |
92 |
) |
|
93 |
} |
|
94 | ||
95 | ||
96 |
# Simulations ---- |
|
97 | ||
98 |
## class ---- |
|
99 | ||
100 |
#' `Simulations` |
|
101 |
#' |
|
102 |
#' @description `r lifecycle::badge("stable")` |
|
103 |
#' |
|
104 |
#' This class captures the trial simulations from model based designs. |
|
105 |
#' Additional slots `fit`, `stop_reasons`, `stop_report`,`additional_stats` compared to |
|
106 |
#' the general class [`GeneralSimulations`]. |
|
107 |
#' |
|
108 |
#' @slot fit (`list`)\cr final fits |
|
109 |
#' @slot stop_reasons (`list`)\cr stopping reasons for each simulation run |
|
110 |
#' @slot stop_report matrix of stopping rule outcomes |
|
111 |
#' @slot additional_stats list of additional statistical summary |
|
112 |
#' @aliases Simulations |
|
113 |
#' @export |
|
114 |
.Simulations <- |
|
115 |
setClass( |
|
116 |
Class = "Simulations", |
|
117 |
slots = c( |
|
118 |
fit = "list", |
|
119 |
stop_report = "matrix", |
|
120 |
stop_reasons = "list", |
|
121 |
additional_stats = "list" |
|
122 |
), |
|
123 |
prototype = prototype( |
|
124 |
fit = |
|
125 |
list( |
|
126 |
c(0.1, 0.2), |
|
127 |
c(0.1, 0.2) |
|
128 |
), |
|
129 |
stop_report = matrix(TRUE, nrow = 2), |
|
130 |
stop_reasons = |
|
131 |
list("A", "A"), |
|
132 |
additional_stats = |
|
133 |
list(a = 1, b = 1) |
|
134 |
), |
|
135 |
contains = "GeneralSimulations", |
|
136 |
validity = v_simulations |
|
137 |
) |
|
138 | ||
139 |
## constructor ---- |
|
140 | ||
141 |
#' @rdname Simulations-class |
|
142 |
#' |
|
143 |
#' @param fit (`list`)\cr see slot definition. |
|
144 |
#' @param stop_reasons (`list`)\cr see slot definition. |
|
145 |
#' @param stop_report see [`Simulations`] |
|
146 |
#' @param additional_stats (`list`)\cr see slot definition. |
|
147 |
#' @param \dots additional parameters from [`GeneralSimulations`] |
|
148 |
#' |
|
149 |
#' @example examples/Simulations-class-Simulations.R |
|
150 |
#' @export |
|
151 |
Simulations <- function(fit, |
|
152 |
stop_reasons, |
|
153 |
stop_report, |
|
154 |
additional_stats, |
|
155 |
...) { |
|
156 | 32x |
start <- GeneralSimulations(...) |
157 | 32x |
.Simulations(start, |
158 | 32x |
fit = fit, |
159 | 32x |
stop_report = stop_report, |
160 | 32x |
stop_reasons = stop_reasons, |
161 | 32x |
additional_stats = additional_stats |
162 |
) |
|
163 |
} |
|
164 | ||
165 |
## default constructor ---- |
|
166 | ||
167 |
#' @rdname Simulations-class |
|
168 |
#' @note Typically, end users will not use the `.DefaultSimulations()` function. |
|
169 |
#' @export |
|
170 |
.DefaultSimulations <- function() { |
|
171 | ! |
design <- .DefaultDesign() |
172 | ! |
myTruth <- probFunction(design@model, alpha0 = 7, alpha1 = 8) |
173 | ||
174 | ! |
simulate( |
175 | ! |
design, |
176 | ! |
args = NULL, |
177 | ! |
truth = myTruth, |
178 | ! |
nsim = 1, |
179 | ! |
seed = 819, |
180 | ! |
mcmcOptions = .DefaultMcmcOptions(), |
181 | ! |
parallel = FALSE |
182 |
) |
|
183 |
} |
|
184 | ||
185 |
# DualSimulations ---- |
|
186 | ||
187 |
## class ---- |
|
188 | ||
189 |
#' `DualSimulations` |
|
190 |
#' |
|
191 |
#' @description `r lifecycle::badge("stable")` |
|
192 |
#' |
|
193 |
#' This class captures the trial simulations from dual-endpoint model based |
|
194 |
#' designs. In comparison to the parent class [`Simulations`], |
|
195 |
#' it contains additional slots to capture the dose-biomarker `fits`, and the |
|
196 |
#' `sigma2W` and `rho` estimates. |
|
197 |
#' |
|
198 |
#' @slot rho_est (`numeric`)\cr vector of final posterior median rho estimates |
|
199 |
#' @slot sigma2w_est (`numeric`)\cr vector of final posterior median sigma2W estimates |
|
200 |
#' @slot fit_biomarker (`list`)\cr with the final dose-biomarker curve fits |
|
201 |
#' @aliases DualSimulations |
|
202 |
#' @export |
|
203 |
.DualSimulations <- |
|
204 |
setClass( |
|
205 |
Class = "DualSimulations", |
|
206 |
slots = c( |
|
207 |
rho_est = "numeric", |
|
208 |
sigma2w_est = "numeric", |
|
209 |
fit_biomarker = "list" |
|
210 |
), |
|
211 |
prototype = prototype( |
|
212 |
rho_est = c(0.2, 0.3), |
|
213 |
sigma2w_est = c(0.2, 0.3), |
|
214 |
fit_biomarker = |
|
215 |
list( |
|
216 |
c(0.1, 0.2), |
|
217 |
c(0.1, 0.2) |
|
218 |
) |
|
219 |
), |
|
220 |
contains = "Simulations", |
|
221 |
validity = v_dual_simulations |
|
222 |
) |
|
223 | ||
224 | ||
225 |
## constructor ---- |
|
226 | ||
227 |
#' @rdname DualSimulations-class |
|
228 |
#' |
|
229 |
#' @param rho_est (`numeric`)\cr see [`DualSimulations`] |
|
230 |
#' @param sigma2w_est (`numeric`)\cr [`DualSimulations`] |
|
231 |
#' @param fit_biomarker (`list`)\cr see [`DualSimulations`] |
|
232 |
#' @param \dots additional parameters from [`Simulations`] |
|
233 |
#' |
|
234 |
#' @example examples/Simulations-class-DualSimulations.R |
|
235 |
#' @export |
|
236 |
DualSimulations <- function(rho_est, |
|
237 |
sigma2w_est, |
|
238 |
fit_biomarker, |
|
239 |
...) { |
|
240 | 7x |
start <- Simulations(...) |
241 | 7x |
.DualSimulations(start, |
242 | 7x |
rho_est = rho_est, |
243 | 7x |
sigma2w_est = sigma2w_est, |
244 | 7x |
fit_biomarker = fit_biomarker |
245 |
) |
|
246 |
} |
|
247 | ||
248 |
## default constructor ---- |
|
249 | ||
250 |
#' @rdname DualSimulations-class |
|
251 |
#' @note Typically, end users will not use the `.DefaultDualSimulations()` function. |
|
252 |
#' @export |
|
253 |
.DefaultDualSimulations <- function() { |
|
254 | 5x |
DualSimulations( |
255 | 5x |
rho_est = c(0.25, 0.35), |
256 | 5x |
sigma2w_est = c(0.15, 0.25), |
257 | 5x |
fit_biomarker = list(c(0.3, 0.4), c(0.4, 0.5)), |
258 | 5x |
fit = list( |
259 | 5x |
c(0.1, 0.2), |
260 | 5x |
c(0.3, 0.4) |
261 |
), |
|
262 | 5x |
stop_report = matrix(c(TRUE, FALSE), nrow = 2), |
263 | 5x |
stop_reasons = list("A", "B"), |
264 | 5x |
additional_stats = list(a = 1, b = 1), |
265 | 5x |
data = list( |
266 | 5x |
Data( |
267 | 5x |
x = 1:2, |
268 | 5x |
y = 0:1, |
269 | 5x |
doseGrid = 1:2, |
270 | 5x |
ID = 1L:2L, |
271 | 5x |
cohort = 1L:2L |
272 |
), |
|
273 | 5x |
Data( |
274 | 5x |
x = 3:4, |
275 | 5x |
y = 0:1, |
276 | 5x |
doseGrid = 3:4, |
277 | 5x |
ID = 1L:2L, |
278 | 5x |
cohort = 1L:2L |
279 |
) |
|
280 |
), |
|
281 | 5x |
doses = c(1, 2), |
282 | 5x |
seed = 123L |
283 |
) |
|
284 |
} |
|
285 | ||
286 |
#' `GeneralSimulationsSummary` |
|
287 |
#' |
|
288 |
#' @description `r lifecycle::badge("stable")` |
|
289 |
#' |
|
290 |
#' This class captures the summary of general simulations output. Note that objects |
|
291 |
#' should not be created by users, therefore no initialization |
|
292 |
#' function is provided for this class. |
|
293 |
#' |
|
294 |
#' @slot target (`numeric`)\cr target toxicity interval |
|
295 |
#' @slot target_dose_interval (`numeric`)\cr corresponding target dose interval |
|
296 |
#' @slot nsim (`integer`)\cr number of simulations |
|
297 |
#' @slot prop_dlts (`ANY`)\cr A numeric array (multi-dimensional) or list representing proportions of DLTs in the trials |
|
298 |
#' @slot mean_tox_risk (`numeric`)\cr mean toxicity risks for the patients |
|
299 |
#' @slot dose_selected (`numeric`)\cr doses selected as MTD |
|
300 |
#' @slot tox_at_doses_selected (`numeric`)\cr true toxicity at doses selected |
|
301 |
#' @slot prop_at_target (`numeric`)\cr Proportion of trials selecting target MTD |
|
302 |
#' @slot dose_most_selected (`numeric`)\cr dose most often selected as MTD |
|
303 |
#' @slot obs_tox_rate_at_dose_most_selected (`numeric`)\cr observed toxicity rate at dose most often selected |
|
304 |
#' @slot n_obs (`ANY`)\cr A numeric array (multi-dimensional) or list representing number of patients overall. |
|
305 |
#' @slot n_above_target (`integer`)\cr number of patients treated above target tox interval |
|
306 |
#' @slot dose_grid (`numeric`)\cr the dose grid that has been used |
|
307 |
#' @slot placebo (`logical`)\cr set to TRUE (default is FALSE) for a design with placebo |
|
308 |
#' @aliases GeneralSimulationsSummary |
|
309 |
#' @export |
|
310 |
.GeneralSimulationsSummary <- |
|
311 |
setClass( |
|
312 |
Class = "GeneralSimulationsSummary", |
|
313 |
slots = c( |
|
314 |
target = "numeric", |
|
315 |
target_dose_interval = "numeric", |
|
316 |
nsim = "integer", |
|
317 |
prop_dlts = "ANY", |
|
318 |
mean_tox_risk = "numeric", |
|
319 |
dose_selected = "numeric", |
|
320 |
tox_at_doses_selected = "numeric", |
|
321 |
prop_at_target = "numeric", |
|
322 |
dose_most_selected = "numeric", |
|
323 |
obs_tox_rate_at_dose_most_selected = "numeric", |
|
324 |
n_obs = "ANY", |
|
325 |
n_above_target = "integer", |
|
326 |
dose_grid = "numeric", |
|
327 |
placebo = "logical" |
|
328 |
) |
|
329 |
) |
|
330 | ||
331 |
## default constructor ---- |
|
332 | ||
333 |
#' @rdname GeneralSimulationsSummary-class |
|
334 |
#' @note Typically, end users will not use the `.DefaultGeneralSimulationsSummary()` function. |
|
335 |
#' @export |
|
336 |
.DefaultGeneralSimulationsSummary <- function() { |
|
337 | 2x |
stop( |
338 | 2x |
paste( |
339 | 2x |
"Class GeneralSimulationsSummary cannot be instantiated directly.", |
340 | 2x |
"Please use one of its subclasses instead." |
341 |
) |
|
342 |
) |
|
343 |
} |
|
344 | ||
345 |
## SimulationsSummary ---- |
|
346 | ||
347 |
## class ---- |
|
348 | ||
349 |
#' `SimulationsSummary` |
|
350 |
#' |
|
351 |
#' @description `r lifecycle::badge("stable")` |
|
352 |
#' |
|
353 |
#' In addition to the slots in the parent class [`GeneralSimulationsSummary`], |
|
354 |
#' it contains two slots with model fit information. |
|
355 |
#' |
|
356 |
#' @slot stop_report (`matrix`)\cr matrix of stopping rule outcomes |
|
357 |
#' @slot fit_at_dose_most_selected (`numeric`)\cr fitted toxicity rate at dose most often selected |
|
358 |
#' @slot additional_stats (`list`)\cr list of additional statistical summary |
|
359 |
#' @slot mean_fit (`list`)\cr list with the average, lower (2.5%) and upper (97.5%) |
|
360 |
#' quantiles of the mean fitted toxicity at each dose level |
|
361 |
#' |
|
362 |
#' @aliases SimulationsSummary |
|
363 |
#' @export |
|
364 |
.SimulationsSummary <- |
|
365 |
setClass( |
|
366 |
Class = "SimulationsSummary", |
|
367 |
slots = c( |
|
368 |
stop_report = "matrix", |
|
369 |
fit_at_dose_most_selected = "numeric", |
|
370 |
additional_stats = "list", |
|
371 |
mean_fit = "list" |
|
372 |
), |
|
373 |
contains = "GeneralSimulationsSummary" |
|
374 |
) |
|
375 | ||
376 |
## default constructor ---- |
|
377 | ||
378 |
#' @rdname SimulationsSummary-class |
|
379 |
#' @note Typically, end users will not use the `.DefaultSimulationsSummary()` function. |
|
380 |
#' @export |
|
381 |
.DefaultSimulationsSummary <- function() { |
|
382 | 1x |
stop(paste( |
383 | 1x |
"Class SimulationsSummary cannot be instantiated directly.", |
384 | 1x |
"Please use one of its subclasses instead." |
385 |
)) |
|
386 |
} |
|
387 | ||
388 |
# DualSimulationsSummary ---- |
|
389 | ||
390 |
# class ---- |
|
391 | ||
392 |
#' `DualSimulationsSummary` |
|
393 |
#' |
|
394 |
#' @description `r lifecycle::badge("stable")` |
|
395 |
#' This class captures the summary of dual-endpoint simulations output. |
|
396 |
#' In comparison to its parent class [`SimulationsSummary`], it has additional slots. |
|
397 |
#' |
|
398 |
#' @slot biomarker_fit_at_dose_most_selected (`numeric`)\cr fitted biomarker level at most often selected dose. |
|
399 |
#' @slot mean_biomarker_fit (`list`)\cr list with average, lower (2.5%) and upper (97.5%) quantiles of |
|
400 |
#' mean fitted biomarker level at each dose |
|
401 |
#' @aliases DualSimulationsSummary |
|
402 |
#' @export |
|
403 |
.DualSimulationsSummary <- |
|
404 |
setClass( |
|
405 |
Class = "DualSimulationsSummary", |
|
406 |
slots = c( |
|
407 |
biomarker_fit_at_dose_most_selected = "numeric", |
|
408 |
mean_biomarker_fit = "list" |
|
409 |
), |
|
410 |
contains = "SimulationsSummary" |
|
411 |
) |
|
412 | ||
413 |
# default constructor |
|
414 | ||
415 |
#' @rdname DualSimulationsSummary-class |
|
416 |
#' @note Typically, end users will not use the `.DefaultDualSimulationsSummary()` function. |
|
417 |
#' @export |
|
418 |
.DefaultDualSimulationsSummary <- function() { |
|
419 | ! |
empty_data <- DataDual(doseGrid = c(1, 3, 5, 10, 15, 20, 25, 30)) |
420 | ||
421 | ! |
my_model <- DualEndpointRW( |
422 | ! |
mean = c(0, 1), |
423 | ! |
cov = matrix(c(1, 0, 0, 1), nrow = 2), |
424 | ! |
sigma2betaW = 0.01, |
425 | ! |
sigma2W = c(a = 0.1, b = 0.1), |
426 | ! |
rho = c(a = 1, b = 1), |
427 | ! |
rw1 = TRUE |
428 |
) |
|
429 | ||
430 | ! |
my_next_best <- NextBestDualEndpoint( |
431 | ! |
target = c(0.9, 1), |
432 | ! |
overdose = c(0.35, 1), |
433 | ! |
max_overdose_prob = 0.25 |
434 |
) |
|
435 | ||
436 | ! |
my_size1 <- CohortSizeRange( |
437 | ! |
intervals = c(0, 30), |
438 | ! |
cohort_size = c(1, 3) |
439 |
) |
|
440 | ! |
my_size2 <- CohortSizeDLT( |
441 | ! |
intervals = c(0, 1), |
442 | ! |
cohort_size = c(1, 3) |
443 |
) |
|
444 | ! |
my_size <- maxSize(my_size1, my_size2) |
445 | ||
446 | ! |
my_stopping1 <- StoppingTargetBiomarker( |
447 | ! |
target = c(0.9, 1), |
448 | ! |
prob = 0.5 |
449 |
) |
|
450 | ||
451 | ! |
my_stopping <- my_stopping1 | StoppingMinPatients(10) | StoppingMissingDose() |
452 | ||
453 | ! |
my_increments <- IncrementsRelative( |
454 | ! |
intervals = c(0, 20), |
455 | ! |
increments = c(1, 0.33) |
456 |
) |
|
457 | ||
458 | ! |
my_design <- DualDesign( |
459 | ! |
model = my_model, |
460 | ! |
data = empty_data, |
461 | ! |
nextBest = my_next_best, |
462 | ! |
stopping = my_stopping, |
463 | ! |
increments = my_increments, |
464 | ! |
cohort_size = CohortSizeConst(3), |
465 | ! |
startingDose = 3 |
466 |
) |
|
467 | ||
468 | ! |
beta_mod <- function(dose, e0, eMax, delta1, delta2, scal) { |
469 | ! |
maxDens <- (delta1^delta1) * (delta2^delta2) / ((delta1 + delta2)^(delta1 + delta2)) |
470 | ! |
dose <- dose / scal |
471 | ! |
e0 + eMax / maxDens * (dose^delta1) * (1 - dose)^delta2 |
472 |
} |
|
473 | ||
474 | ! |
true_biomarker <- function(dose) { |
475 | ! |
beta_mod(dose, e0 = 0.2, eMax = 0.6, delta1 = 5, delta2 = 5 * 0.5 / 0.5, scal = 100) |
476 |
} |
|
477 | ||
478 | ! |
true_tox <- function(dose) { |
479 | ! |
pnorm((dose - 60) / 10) |
480 |
} |
|
481 | ||
482 | ! |
x <- simulate( |
483 | ! |
object = my_design, |
484 | ! |
trueTox = true_tox, |
485 | ! |
trueBiomarker = true_biomarker, |
486 | ! |
sigma2W = 0.01, |
487 | ! |
rho = 0, |
488 | ! |
nsim = 1, |
489 | ! |
parallel = FALSE, |
490 | ! |
seed = 3, |
491 | ! |
startingDose = 6, |
492 | ! |
mcmcOptions = .DefaultMcmcOptions() |
493 |
) |
|
494 |
} |
|
495 | ||
496 |
# PseudoSimulations ---- |
|
497 | ||
498 |
## class ---- |
|
499 | ||
500 |
#' `PseudoSimulations` |
|
501 |
#' |
|
502 |
#' @description `r lifecycle::badge("stable")` |
|
503 |
#' This class captures trial simulations from designs using pseudo model. |
|
504 |
#' It has additional slots `fit` and `stop_reasons` compared to the |
|
505 |
#' general class [`GeneralSimulations`]. |
|
506 |
#' |
|
507 |
#' @slot fit (`list`)\cr final fit values. |
|
508 |
#' @slot final_td_target_during_trial_estimates (`numeric`)\cr final estimates of the `td_target_during_trial`. |
|
509 |
#' @slot final_td_target_end_of_trial_estimates (`numeric`)\cr final estimates of the `td_target_end_of_trial`. |
|
510 |
#' @slot final_td_target_during_trial_at_dose_grid (`numeric`) |
|
511 |
#' \cr dose levels at dose grid closest below the final `td_target_during_trial` estimates. |
|
512 |
#' @slot final_td_target_end_of_trial_at_dose_grid (`numeric`) |
|
513 |
#' \cr dose levels at dose grid closest below the final `td_target_end_of_trial` estimates. |
|
514 |
#' @slot final_tdeot_cis (`list`)\cr 95% credibility intervals of the final estimates for `td_target_end_of_trial`. |
|
515 |
#' @slot final_tdeot_ratios (`numeric`)\cr ratio of the upper to the lower 95% |
|
516 |
#' credibility intervals for `td_target_end_of_trial`. |
|
517 |
#' @slot final_cis (`list`)\cr final 95% credibility intervals for `td_target_end_of_trial` estimates. |
|
518 |
#' @slot final_ratios (`numeric`)\cr final ratios of the upper to the lower 95% |
|
519 |
#' credibility interval for `td_target_end_of_trial`. |
|
520 |
#' @slot stop_report (`matrix`)\cr outcomes of stopping rules. |
|
521 |
#' @slot stop_reasons (`list`)\cr reasons for stopping each simulation run. |
|
522 |
#' |
|
523 |
#' @aliases PseudoSimulations |
|
524 |
#' @export |
|
525 |
.PseudoSimulations <- |
|
526 |
setClass( |
|
527 |
Class = "PseudoSimulations", |
|
528 |
slots = c( |
|
529 |
fit = "list", |
|
530 |
final_td_target_during_trial_estimates = "numeric", |
|
531 |
final_td_target_end_of_trial_estimates = "numeric", |
|
532 |
final_td_target_during_trial_at_dose_grid = "numeric", |
|
533 |
final_td_target_end_of_trial_at_dose_grid = "numeric", |
|
534 |
final_tdeot_cis = "list", |
|
535 |
final_tdeot_ratios = "numeric", |
|
536 |
final_cis = "list", |
|
537 |
final_ratios = "numeric", |
|
538 |
stop_report = "matrix", |
|
539 |
stop_reasons = "list" |
|
540 |
), |
|
541 |
prototype = prototype( |
|
542 |
final_td_target_during_trial_estimates = c(0.1, 0.1), |
|
543 |
final_td_target_end_of_trial_estimates = c(0.1, 0.1), |
|
544 |
final_td_target_during_trial_at_dose_grid = c(0.1, 0.1), |
|
545 |
final_td_target_end_of_trial_at_dose_grid = c(0.1, 0.1), |
|
546 |
final_tdeot_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
547 |
final_tdeot_ratios = c(0.1, 0.1), |
|
548 |
final_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
549 |
final_ratios = c(0.1, 0.1), |
|
550 |
stop_report = matrix(TRUE, nrow = 2), |
|
551 |
stop_reasons = list("A", "A") |
|
552 |
), |
|
553 |
contains = "GeneralSimulations", |
|
554 |
validity = v_pseudo_simulations |
|
555 |
) |
|
556 | ||
557 |
## constructor ---- |
|
558 | ||
559 |
#' @rdname PseudoSimulations-class |
|
560 |
#' |
|
561 |
#' @param fit (`list`)\cr see slot definition. |
|
562 |
#' @param final_td_target_during_trial_estimates (`numeric`)\cr see slot definition. |
|
563 |
#' @param final_td_target_end_of_trial_estimates (`numeric`)\cr see slot definition. |
|
564 |
#' @param final_td_target_during_trial_at_dose_grid (`numeric`)\cr see slot definition. |
|
565 |
#' @param final_td_target_end_of_trial_at_dose_grid (`numeric`)\cr see slot definition. |
|
566 |
#' @param final_tdeot_cis (`list`)\cr see slot definition. |
|
567 |
#' @param final_tdeot_ratios (`numeric`)\cr see slot definition. |
|
568 |
#' @param final_cis (`list`)\cr see slot definition. |
|
569 |
#' @param final_ratios (`numeric`)\cr see slot definition. |
|
570 |
#' @param stop_report see [`PseudoSimulations`] |
|
571 |
#' @param stop_reasons (`list`)\cr see slot definition. |
|
572 |
#' @param \dots additional parameters from [`GeneralSimulations`] |
|
573 |
#' |
|
574 |
#' @export |
|
575 |
PseudoSimulations <- function(fit, |
|
576 |
final_td_target_during_trial_estimates, |
|
577 |
final_td_target_end_of_trial_estimates, |
|
578 |
final_td_target_during_trial_at_dose_grid, |
|
579 |
final_td_target_end_of_trial_at_dose_grid, |
|
580 |
final_tdeot_cis, |
|
581 |
final_tdeot_ratios, |
|
582 |
final_cis, |
|
583 |
final_ratios, |
|
584 |
stop_report, |
|
585 |
stop_reasons, |
|
586 |
...) { |
|
587 | 6x |
start <- GeneralSimulations(...) |
588 | 6x |
.PseudoSimulations(start, |
589 | 6x |
fit = fit, |
590 | 6x |
final_td_target_during_trial_estimates = final_td_target_during_trial_estimates, |
591 | 6x |
final_td_target_end_of_trial_estimates = final_td_target_end_of_trial_estimates, |
592 | 6x |
final_td_target_during_trial_at_dose_grid = final_td_target_during_trial_at_dose_grid, |
593 | 6x |
final_td_target_end_of_trial_at_dose_grid = final_td_target_end_of_trial_at_dose_grid, |
594 | 6x |
final_tdeot_cis = final_tdeot_cis, |
595 | 6x |
final_tdeot_ratios = final_tdeot_ratios, |
596 | 6x |
final_cis = final_cis, |
597 | 6x |
final_ratios = final_ratios, |
598 | 6x |
stop_report = stop_report, |
599 | 6x |
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("Class PseudoSimulations cannot be instantiated directly. Please use one of its subclasses instead.") |
610 |
} |
|
611 | ||
612 |
# PseudoDualSimulations ---- |
|
613 | ||
614 |
## class ---- |
|
615 | ||
616 |
#' `PseudoDualSimulations` |
|
617 |
#' |
|
618 |
#' @description `r lifecycle::badge("stable")` |
|
619 |
#' This class conducts trial simulations for designs using both the |
|
620 |
#' DLE and efficacy responses. It defines final values for |
|
621 |
#' efficacy fit and DLE, estimates of Gstar, optimal dose and sigma2. |
|
622 |
#' |
|
623 |
#' @slot fit_eff (`list`)\cr final values of efficacy fit. |
|
624 |
#' @slot final_gstar_estimates (`numeric`)\cr final Gstar estimates. |
|
625 |
#' @slot final_gstar_at_dose_grid (`numeric`)\cr final Gstar estimates at dose grid. |
|
626 |
#' @slot final_gstar_cis (`list`)\cr list of 95% confidence interval for Gstar estimates. |
|
627 |
#' @slot final_gstar_ratios (`numeric`)\cr ratios of confidence intervals for Gstar estimates. |
|
628 |
#' @slot final_optimal_dose (`numeric`)\cr final optimal dose. |
|
629 |
#' @slot final_optimal_dose_at_dose_grid (`numeric`)\cr final optimal dose at dose grid. |
|
630 |
#' @slot sigma2_est (`numeric`)\cr final sigma2 estimates. |
|
631 |
#' |
|
632 |
#' @aliases PseudoDualSimulations |
|
633 |
#' @export |
|
634 |
.PseudoDualSimulations <- |
|
635 |
setClass( |
|
636 |
Class = "PseudoDualSimulations", |
|
637 |
slots = c( |
|
638 |
fit_eff = "list", |
|
639 |
final_gstar_estimates = "numeric", |
|
640 |
final_gstar_at_dose_grid = "numeric", |
|
641 |
final_gstar_cis = "list", |
|
642 |
final_gstar_ratios = "numeric", |
|
643 |
final_optimal_dose = "numeric", |
|
644 |
final_optimal_dose_at_dose_grid = "numeric", |
|
645 |
sigma2_est = "numeric" |
|
646 |
), |
|
647 |
prototype = prototype( |
|
648 |
final_gstar_estimates = c(0.1, 0.1), |
|
649 |
final_gstar_at_dose_grid = c(0.1, 0.1), |
|
650 |
final_gstar_cis = list(c(0.1, 0.2), c(0.1, 0.2)), |
|
651 |
final_gstar_ratios = c(0.01, 0.01), |
|
652 |
final_optimal_dose = c(0.01, 0.01), |
|
653 |
final_optimal_dose_at_dose_grid = c(0.01, 0.01), |
|
654 |
sigma2_est = c(0.001, 0.002) |
|
655 |
), |
|
656 |
contains = "PseudoSimulations", |
|
657 |
validity = v_pseudo_dual_simulations |
|
658 |
) |
|
659 | ||
660 |
## constructor ---- |
|
661 | ||
662 |
#' @rdname PseudoDualSimulations-class |
|
663 |
#' |
|
664 |
#' @param fit_eff (`list`)\cr see slot definition. |
|
665 |
#' @param final_gstar_estimates (`numeric`)\cr see slot definition. |
|
666 |
#' @param final_gstar_at_dose_grid (`numeric`)\cr see slot definition. |
|
667 |
#' @param final_gstar_cis (`list`)\cr see slot definition. |
|
668 |
#' @param final_gstar_ratios (`numeric`)\cr see slot definition. |
|
669 |
#' @param final_optimal_dose (`numeric`)\cr see slot definition. |
|
670 |
#' @param final_optimal_dose_at_dose_grid (`numeric`)\cr see slot definition. |
|
671 |
#' @param sigma2_est (`numeric`)\cr see slot definition. |
|
672 |
#' @param \dots additional parameters from [`PseudoSimulations`] |
|
673 |
#' @export |
|
674 |
PseudoDualSimulations <- function(fit_eff, |
|
675 |
final_gstar_estimates, |
|
676 |
final_gstar_at_dose_grid, |
|
677 |
final_gstar_cis, |
|
678 |
final_gstar_ratios, |
|
679 |
final_optimal_dose, |
|
680 |
final_optimal_dose_at_dose_grid, |
|
681 |
sigma2_est, |
|
682 |
...) { |
|
683 | 3x |
start <- PseudoSimulations(...) |
684 | 3x |
.PseudoDualSimulations(start, |
685 | 3x |
fit_eff = fit_eff, |
686 | 3x |
final_gstar_estimates = final_gstar_estimates, |
687 | 3x |
final_gstar_at_dose_grid = final_gstar_at_dose_grid, |
688 | 3x |
final_gstar_cis = final_gstar_cis, |
689 | 3x |
final_gstar_ratios = final_gstar_ratios, |
690 | 3x |
final_optimal_dose = final_optimal_dose, |
691 | 3x |
final_optimal_dose_at_dose_grid = final_optimal_dose_at_dose_grid, |
692 | 3x |
sigma2_est = sigma2_est |
693 |
) |
|
694 |
} |
|
695 | ||
696 |
## default constructor ---- |
|
697 | ||
698 |
#' @rdname PseudoDualSimulations-class |
|
699 |
#' @note Do not use the `.DefaultPseudoDualSimulations()` function. |
|
700 |
#' @export |
|
701 |
.DefaultPseudoDualSimulations <- function() { |
|
702 | 1x |
stop("Class PseudoDualSimulations cannot be instantiated directly. Please use a subclass.") |
703 |
} |
|
704 | ||
705 |
# nolint start |
|
706 |
# PseudoDualFlexiSimulations ---- |
|
707 | ||
708 |
## class ---- |
|
709 | ||
710 |
## ------------------------------------------------------------------------------- |
|
711 |
## Class for Pseudo simulation using DLE and efficacy responses using 'EffFlex' efficacy model |
|
712 |
## ----------------------------------------------------------------------------------- |
|
713 |
##' This is a class which captures the trial simulations design using both the |
|
714 |
##' DLE and efficacy responses. The design of model from \code{\linkS4class{ModelTox}} |
|
715 |
##' class and the efficacy model from \code{\linkS4class{EffFlexi}} class |
|
716 |
##' It contains all slots from |
|
717 |
##' \code{\linkS4class{GeneralSimulations}}, \code{\linkS4class{PseudoSimulations}} |
|
718 |
##' and \code{\linkS4class{PseudoDualSimulations}} object. |
|
719 |
##' In comparison to the parent class \code{\linkS4class{PseudoDualSimulations}}, |
|
720 |
##' it contains additional slots to |
|
721 |
##' capture the sigma2betaW estimates. |
|
722 |
##' |
|
723 |
##' @slot sigma2betaWest the vector of the final posterior mean sigma2betaW estimates |
|
724 |
##' |
|
725 |
##' @export |
|
726 |
##' @keywords class |
|
727 |
.PseudoDualFlexiSimulations <- |
|
728 |
setClass( |
|
729 |
Class = "PseudoDualFlexiSimulations", |
|
730 |
representation(sigma2betaWest = "numeric"), |
|
731 |
prototype(sigma2betaWest = c(0.001, 0.002)), |
|
732 |
contains = "PseudoDualSimulations", |
|
733 |
validity = v_pseudo_dual_flex_simulations |
|
734 |
) |
|
735 | ||
736 |
validObject(.PseudoDualFlexiSimulations()) |
|
737 | ||
738 |
##' Initialization function for 'PseudoDualFlexiSimulations' class |
|
739 |
##' @param sigma2betaWest please refer to \code{\linkS4class{PseudoDualFlexiSimulations}} class object |
|
740 |
##' @param \dots additional parameters from \code{\linkS4class{PseudoDualSimulations}} |
|
741 |
##' @return the \code{\linkS4class{PseudoDualFlexiSimulations}} object |
|
742 |
PseudoDualFlexiSimulations <- function(sigma2betaWest, |
|
743 |
...) { |
|
744 | ! |
start <- PseudoDualSimulations(...) |
745 | ! |
.PseudoDualFlexiSimulations(start, |
746 | ! |
sigma2betaWest = sigma2betaWest |
747 |
) |
|
748 |
} |
|
749 | ||
750 |
## default constructor ---- |
|
751 | ||
752 |
#' @rdname PseudoDualFlexiSimulations-class |
|
753 |
#' @note Typically, end users will not use the `.DefaultPseudoFlexiSimulations()` function. |
|
754 |
#' @export |
|
755 |
.DefaultPseudoDualFlexiSimulations <- function() { |
|
756 | 1x |
stop(paste0("Class PseudoFlexiSimulations cannot be instantiated directly. Please use one of its subclasses instead.")) |
757 |
} |
|
758 | ||
759 |
## ------------------------------------------------------------------------------------------------------- |
|
760 |
## ================================================================================================ |
|
761 | ||
762 |
##' Class for the summary of pseudo-models simulations output |
|
763 |
##' |
|
764 |
##' Note that objects should not be created by users, therefore no |
|
765 |
##' initialization function is provided for this class. |
|
766 |
##' |
|
767 |
##' @slot targetEndOfTrial the target probability of DLE wanted at the end of a trial |
|
768 |
##' @slot targetDoseEndOfTrial the dose level corresponds to the target probability |
|
769 |
##' of DLE wanted at the end of a trial, TDEOT |
|
770 |
##' @slot targetDoseEndOfTrialAtDoseGrid the dose level at dose grid corresponds to the target probability |
|
771 |
##' of DLE wanted at the end of a trial |
|
772 |
##' @slot targetDuringTrial the target probability of DLE wanted during a trial |
|
773 |
##' @slot targetDoseDuringTrial the dose level corresponds to the target probability of DLE |
|
774 |
##' wanted during the trial. TDDT |
|
775 |
##' @slot targetDoseDuringTrialAtDoseGrid the dose level at dose grid corresponds to the target probability |
|
776 |
##' of DLE wanted during a trial |
|
777 |
##' @slot TDEOTSummary the six-number table summary, include the lowest, the 25th precentile (lower quartile), |
|
778 |
##' the 50th percentile (median), the mean, the 27th percentile and the highest values of the |
|
779 |
##' final dose levels obtained corresponds to the target probability of DLE |
|
780 |
##' want at the end of a trial across all simulations |
|
781 |
##' @slot TDDTSummary the six-number table summary, include the lowest, the 25th precentile (lower quartile), |
|
782 |
##' the 50th percentile (median), the mean, the 27th percentile and the highest values of the |
|
783 |
##' final dose levels obtained corresponds to the target probability of DLE |
|
784 |
##' want during a trial across all simulations |
|
785 |
##' @slot FinalDoseRecSummary the six-number table summary, include the lowest, the 25th precentile (lower quartile), |
|
786 |
##' the 50th percentile (median), the mean, the 27th percentile and the highest values of the |
|
787 |
##' final optimal doses, which is either the TDEOT when only DLE response are incorporated into |
|
788 |
##' the escalation procedure or the minimum of the TDEOT and Gstar when DLE and efficacy responses are |
|
789 |
##' incorporated, across all simulations |
|
790 |
##' @slot ratioTDEOTSummary the six-number summary table of the final ratios of the upper to the lower 95% |
|
791 |
##' credibility intervals of the final TDEOTs across all simulations |
|
792 |
##' @slot FinalRatioSummary the six-number summary table of the final ratios of the upper to the lower 95% |
|
793 |
##' credibility intervals of the final optimal doses across all simulations |
|
794 |
##' #@slot doseRec the dose level that will be recommend for subsequent study |
|
795 |
##' @slot nsim number of simulations |
|
796 |
##' @slot propDLE proportions of DLE in the trials |
|
797 |
##' @slot meanToxRisk mean toxicity risks for the patients |
|
798 |
##' @slot doseSelected doses selected as MTD (targetDoseEndOfTrial) |
|
799 |
##' @slot toxAtDosesSelected true toxicity at doses selected |
|
800 |
##' @slot propAtTargetEndOfTrial Proportion of trials selecting at the doseGrid closest below the MTD, the |
|
801 |
##' targetDoseEndOfTrial |
|
802 |
##' @slot propAtTargetDuringTrial Proportion of trials selecting at the doseGrid closest below the |
|
803 |
##' targetDoseDuringTrial |
|
804 |
##' @slot doseMostSelected dose most often selected as MTD |
|
805 |
##' @slot obsToxRateAtDoseMostSelected observed toxicity rate at dose most often |
|
806 |
##' selected |
|
807 |
##' @slot nObs number of patients overall |
|
808 |
##' @slot nAboveTargetEndOfTrial number of patients treated above targetDoseEndOfTrial |
|
809 |
##' @slot nAboveTargetDuringTrial number of patients treated above targetDoseDuringTrial |
|
810 |
##' @slot doseGrid the dose grid that has been used |
|
811 |
##' @slot fitAtDoseMostSelected fitted toxicity rate at dose most often selected |
|
812 |
##' @slot meanFit list with the average, lower (2.5%) and upper (97.5%) |
|
813 |
##' quantiles of the mean fitted toxicity at each dose level |
|
814 |
##' @slot stop_report matrix of stopping rule outcomes |
|
815 |
##' |
|
816 |
##' @export |
|
817 |
##' @keywords classes |
|
818 |
.PseudoSimulationsSummary <- |
|
819 |
setClass( |
|
820 |
Class = "PseudoSimulationsSummary", |
|
821 |
representation( |
|
822 |
targetEndOfTrial = "numeric", |
|
823 |
targetDoseEndOfTrial = "numeric", |
|
824 |
targetDoseEndOfTrialAtDoseGrid = "numeric", |
|
825 |
targetDuringTrial = "numeric", |
|
826 |
targetDoseDuringTrial = "numeric", |
|
827 |
targetDoseDuringTrialAtDoseGrid = "numeric", |
|
828 |
TDEOTSummary = "table", |
|
829 |
TDDTSummary = "table", |
|
830 |
FinalDoseRecSummary = "table", |
|
831 |
ratioTDEOTSummary = "table", |
|
832 |
FinalRatioSummary = "table", |
|
833 |
# doseRec="numeric", |
|
834 |
nsim = "integer", |
|
835 |
propDLE = "numeric", |
|
836 |
meanToxRisk = "numeric", |
|
837 |
doseSelected = "numeric", |
|
838 |
toxAtDosesSelected = "numeric", |
|
839 |
propAtTargetEndOfTrial = "numeric", |
|
840 |
propAtTargetDuringTrial = "numeric", |
|
841 |
doseMostSelected = "numeric", |
|
842 |
obsToxRateAtDoseMostSelected = "numeric", |
|
843 |
nObs = "integer", |
|
844 |
nAboveTargetEndOfTrial = "integer", |
|
845 |
nAboveTargetDuringTrial = "integer", |
|
846 |
doseGrid = "numeric", |
|
847 |
fitAtDoseMostSelected = "numeric", |
|
848 |
meanFit = "list", |
|
849 |
stop_report = "matrix" |
|
850 |
) |
|
851 |
) |
|
852 | ||
853 |
## default constructor ---- |
|
854 | ||
855 |
#' @rdname GeneralSimulationsSummary-class |
|
856 |
#' @note Typically, end users will not use the `.DefaultPseudoSimulationsSummary()` function. |
|
857 |
#' @export |
|
858 |
.DefaultPseudoSimulationsSummary <- function() { |
|
859 | 1x |
stop(paste0("Class PseudoSimulationsSummary cannot be instantiated directly. Please use one of its subclasses instead.")) |
860 |
} |
|
861 | ||
862 |
## --------------------------------------------------------------------------------------------- |
|
863 |
##' Class for the summary of the dual responses simulations using pseudo models |
|
864 |
##' |
|
865 |
##' It contains all slots from \code{\linkS4class{PseudoSimulationsSummary}} object. In addition to |
|
866 |
##' the slots in the parent class \code{\linkS4class{PseudoSimulationsSummary}}, it contains four |
|
867 |
##' more slots for the efficacy model fit information. |
|
868 |
##' |
|
869 |
##' Note that objects should not be created by users, therefore no initialization function |
|
870 |
##' is provided for this class. |
|
871 |
##' |
|
872 |
##' @slot targetGstar the target dose level such that its gain value is at maximum |
|
873 |
##' @slot targetGstarAtDoseGrid the dose level at dose Grid closest and below Gstar |
|
874 |
##' @slot GstarSummary the six-number table summary (lowest, 25th, 50th (median), 75th percentile, mean |
|
875 |
##' and highest value) of the final Gstar values obtained across all simulations |
|
876 |
##' @slot ratioGstarSummary the six-number summary table of the ratios of the upper to the lower 95% |
|
877 |
##' credibility intervals of the final Gstar across all simulations |
|
878 |
##' @slot EffFitAtDoseMostSelected fitted expected mean efficacy value at dose most often |
|
879 |
##' selected |
|
880 |
##' @slot meanEffFit list with mean, lower (2.5%) and upper (97.5%) quantiles of the fitted expected |
|
881 |
##' efficacy value at each dose level. |
|
882 |
##' |
|
883 |
##' @export |
|
884 |
##' @keywords class |
|
885 |
.PseudoDualSimulationsSummary <- |
|
886 |
setClass( |
|
887 |
Class = "PseudoDualSimulationsSummary", |
|
888 |
contains = "PseudoSimulationsSummary", |
|
889 |
representation = |
|
890 |
representation( |
|
891 |
targetGstar = "numeric", |
|
892 |
targetGstarAtDoseGrid = "numeric", |
|
893 |
GstarSummary = "table", |
|
894 |
ratioGstarSummary = "table", |
|
895 |
EffFitAtDoseMostSelected = "numeric", |
|
896 |
meanEffFit = "list" |
|
897 |
) |
|
898 |
) |
|
899 | ||
900 |
## default constructor ---- |
|
901 | ||
902 |
#' @rdname PseudoDualSimulationsSummary-class |
|
903 |
#' @note Typically, end users will not use the `.DefaultPseudoDualSimulationsSummary()` function. |
|
904 |
#' @export |
|
905 |
.DefaultPseudoDualSimulationsSummary <- function() { |
|
906 | 1x |
stop(paste0("Class PseudoDualSimulationsSummary cannot be instantiated directly. Please use one of its subclasses instead.")) |
907 |
} |
|
908 | ||
909 |
## --------------------------------------------------------------------------------------------- |
|
910 | ||
911 |
##' Class for the simulations output from DA based designs |
|
912 |
##' |
|
913 |
##' This class captures the trial simulations from DA based |
|
914 |
##' designs. In comparison to the parent class \code{\linkS4class{Simulations}}, |
|
915 |
##' it contains additional slots to capture the time to DLT fits, additional |
|
916 |
##' parameters and the trial duration. |
|
917 |
##' |
|
918 |
##' @slot trialduration the vector of trial duration values for all simulations. |
|
919 |
##' |
|
920 |
##' @export |
|
921 |
##' @keywords classes |
|
922 |
.DASimulations <- |
|
923 |
setClass( |
|
924 |
Class = "DASimulations", |
|
925 |
representation(trialduration = "numeric"), |
|
926 |
prototype(trialduration = rep(0, 2)), |
|
927 |
contains = "Simulations", |
|
928 |
validity = v_da_simulations |
|
929 |
) |
|
930 |
validObject(.DASimulations()) |
|
931 | ||
932 | ||
933 |
##' Initialization function for `DASimulations` |
|
934 |
##' |
|
935 |
##' @param trialduration see \code{\linkS4class{DASimulations}} |
|
936 |
##' @param \dots additional parameters from \code{\link{Simulations}} |
|
937 |
##' @return the \code{\linkS4class{DASimulations}} object |
|
938 |
##' |
|
939 |
##' @export |
|
940 |
##' @keywords methods |
|
941 |
DASimulations <- function(trialduration, |
|
942 |
...) { |
|
943 | ! |
start <- Simulations(...) |
944 | ! |
.DASimulations(start, |
945 | ! |
trialduration = trialduration |
946 |
) |
|
947 |
} |
|
948 | ||
949 | ||
950 |
## default constructor ---- |
|
951 | ||
952 |
#' @rdname DASimulations-class |
|
953 |
#' @note Typically, end users will not use the `.DASimulations()` function. This |
|
954 |
#' function has a noticeable execution time. |
|
955 |
#' @export |
|
956 |
.DefaultDASimulations <- function() { |
|
957 | ! |
design <- .DefaultDADesign() |
958 | ! |
myTruth <- probFunction(design@model, alpha0 = 2, alpha1 = 3) |
959 | ! |
exp_cond.cdf <- function(x, onset = 15) { |
960 | ! |
a <- stats::pexp(28, 1 / onset, lower.tail = FALSE) |
961 | ! |
1 - (stats::pexp(x, 1 / onset, lower.tail = FALSE) - a) / (1 - a) |
962 |
} |
|
963 | ||
964 | ! |
simulate( |
965 | ! |
design, |
966 | ! |
args = NULL, |
967 | ! |
truthTox = myTruth, |
968 | ! |
truthSurv = exp_cond.cdf, |
969 | ! |
trueTmax = 80, |
970 | ! |
nsim = 2, |
971 | ! |
seed = 819, |
972 | ! |
mcmcOptions = .DefaultMcmcOptions(), |
973 | ! |
firstSeparate = TRUE, |
974 | ! |
deescalate = FALSE, |
975 | ! |
parallel = FALSE |
976 |
) |
|
977 |
} |
|
978 |
# nolint end |
|
979 | ||
980 |
# tidy |
|
981 | ||
982 |
## tidy-Simulations ---- |
|
983 | ||
984 |
#' @rdname tidy |
|
985 |
#' @aliases tidy-Simulations |
|
986 |
#' @example examples/Simulations-method-tidy.R |
|
987 |
#' @export |
|
988 |
setMethod( |
|
989 |
f = "tidy", |
|
990 |
signature = signature(x = "Simulations"), |
|
991 |
definition = function(x, ...) { |
|
992 | 6x |
slot_names <- slotNames(x) |
993 | 6x |
rv <- list() |
994 | 6x |
for (nm in slot_names) { |
995 | 50x |
if (!is.function(slot(x, nm))) { |
996 | 50x |
if (nm %in% c("stop_reasons", "additional_stats")) { |
997 |
} else { |
|
998 | 38x |
rv[[nm]] <- h_tidy_slot(x, nm) |
999 |
} |
|
1000 |
} |
|
1001 |
} |
|
1002 |
# Column bind of all list elements have the same number of rows |
|
1003 | 6x |
if (length(rv) > 1 & length(unique(sapply(rv, nrow))) == 1) { |
1004 | ! |
rv <- rv %>% dplyr::bind_cols() |
1005 |
} |
|
1006 | 6x |
rv %>% h_tidy_class(x) |
1007 |
} |
|
1008 |
) |
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(x, .ignore = c("names", "class", "description", "row.names")) { |
|
19 | 3x |
a <- attributes(x) |
20 | 3x |
valid_names <- setdiff(names(a), .ignore) |
21 | 3x |
lapply( |
22 | 3x |
valid_names, |
23 | 3x |
function(n) { |
24 | 18x |
z <- attr(x, n) |
25 | 18x |
rv <- NULL |
26 |
# Some Design classes have attributes that are functions or CrmPackClass objects |
|
27 | 18x |
if (!is.function(z)) { |
28 | 18x |
if (length(z) == 1) { |
29 | 18x |
if (is(z, "CrmPackClass")) { |
30 | ! |
z <- z %>% tidy() |
31 |
} |
|
32 | 18x |
rv <- tibble::tibble(X = z) |
33 |
} else { |
|
34 | ! |
if (length(z) == 0) { |
35 | ! |
rv <- tibble::tibble(X = NA) |
36 |
} else { |
|
37 | ! |
if (is(z, "CrmPackClass")) { |
38 | ! |
rv <- z %>% tidy() |
39 |
} else { |
|
40 | ! |
rv <- tibble::tibble(X = list(z)) |
41 |
} |
|
42 |
} |
|
43 |
} |
|
44 | 18x |
names(rv) <- n |
45 |
} |
|
46 | 18x |
rv |
47 |
} |
|
48 |
) %>% |
|
49 | 3x |
dplyr::bind_cols() |
50 |
} |
|
51 | ||
52 |
#' Tidy a Single Slot of a CrmPackObject |
|
53 |
#' |
|
54 |
#' @description `r lifecycle::badge("experimental")` |
|
55 |
#' |
|
56 |
#' A helper function that converts a single slot of a `CrmPackObject` to a tibble. |
|
57 |
#' If the slots value is a `list`, each element of the list is tidied individually. |
|
58 |
#' |
|
59 |
#' @param obj (`CrmPackObject`)\cr object to be converted. |
|
60 |
#' @param slot_name (`character`)\cr name of the slot to be tidied. |
|
61 |
#' @param col (`character`)\cr The name of the corresponding column in the tidied |
|
62 |
#' tibble. Defaults to `slot_name`. |
|
63 |
#' @param attributes (`flag`)\cr shoud the object's attributes, if any, be added |
|
64 |
#' to the output tibble |
|
65 |
#' |
|
66 |
#' @return A [`tibble`] |
|
67 |
#' |
|
68 |
#' @keywords internal |
|
69 |
#' @importFrom rlang := |
|
70 |
#' @noRd |
|
71 |
h_tidy_slot <- function(obj, slot_name, col = NULL, attributes = FALSE) { |
|
72 | 2342x |
if (is.list(slot(obj, slot_name))) { |
73 | 57x |
return( |
74 | 57x |
lapply( |
75 | 57x |
slot(obj, slot_name), |
76 | 57x |
function(x) { |
77 | 116x |
if (is.data.frame(x)) { |
78 | 6x |
return(x) |
79 | 110x |
} else if (is.list(x) && stringr::str_detect(class(x)[1], stringr::fixed("tbl_"))) { |
80 |
# Already tidied to a list. |
|
81 | ! |
return(x) |
82 | 110x |
} else if (is.numeric(x) | is.character(x)) { |
83 |
# tidy.numeric & tidy.character are deprecated |
|
84 | 12x |
return(tibble::tibble(!!{{ slot_name }} := x)) |
85 |
} else { |
|
86 | 98x |
return(x %>% tidy()) |
87 |
} |
|
88 |
} |
|
89 |
) |
|
90 |
) |
|
91 |
} |
|
92 | 2285x |
if (is(slot(obj, slot_name), "CrmPackClass")) { |
93 | 302x |
rv <- slot(obj, slot_name) %>% |
94 | 302x |
tidy() |
95 |
} else { |
|
96 | 1983x |
if (is.null(col)) { |
97 | 1983x |
col <- slot_name |
98 |
} |
|
99 | 1983x |
rv <- tibble::tibble({{ col }} := slot(obj, slot_name)) |
100 |
} |
|
101 | 2285x |
if (attributes) { |
102 | ! |
a <- h_handle_attributes(slot(obj, slot_name)) |
103 | ! |
if (nrow(a) > 0) { |
104 | ! |
rv <- rv %>% dplyr::bind_cols(a) |
105 |
} |
|
106 |
} |
|
107 | 2285x |
rv |
108 |
} |
|
109 | ||
110 |
#' Tidy All Slots of a CrmPackObject |
|
111 |
#' |
|
112 |
#' @description `r lifecycle::badge("experimental")` |
|
113 |
#' |
|
114 |
#' A helper function that converts all the slots of a `CrmPackObject` to a |
|
115 |
#' (list of) tibble(s). |
|
116 |
#' |
|
117 |
#' @param obj (`CrmPackObject`)\cr object to be tidied. |
|
118 |
#' @param ... passed to h_tidy_slot |
|
119 |
#' |
|
120 |
#' @return A (list of) [`tibble`](s) |
|
121 |
#' |
|
122 |
#' @keywords internal |
|
123 |
#' @noRd |
|
124 |
h_tidy_all_slots <- function(obj, ...) { |
|
125 | 676x |
slot_names <- slotNames(obj) |
126 | 676x |
rv <- list() |
127 | 676x |
for (nm in slot_names) { |
128 | 2552x |
if (!is.function(slot(obj, nm))) { |
129 | 2292x |
rv[[nm]] <- h_tidy_slot(obj, nm, ...) |
130 |
} |
|
131 |
} |
|
132 |
# Column bind of all list elements have the same number of rows |
|
133 | 676x |
if (length(rv) > 1 && length(unique(sapply(rv, nrow))) == 1) { |
134 | 369x |
rv <- rv %>% dplyr::bind_cols() # nolint |
135 |
} |
|
136 | 676x |
rv |
137 |
} |
|
138 | ||
139 |
#' Amend the Class of a Tibble to Indicate that it Contains a Tidied `CrmPackObject` |
|
140 |
#' |
|
141 |
#' @description `r lifecycle::badge("experimental")` |
|
142 |
#' |
|
143 |
#' A helper function that prepends `tbl_<cls>`, where `<cls>` is the first |
|
144 |
#' element of the class attribute of the original `CrmPackObject` to the class |
|
145 |
#' attribute of a tibble |
|
146 |
#' |
|
147 |
#' @param d (`tibble`)\cr the tibble containing the tidied version of `obj`. |
|
148 |
#' @param obj (`CrmPackObject`)\cr object to be converted. |
|
149 |
#' |
|
150 |
#' @return `d`, with an amended class attribute |
|
151 |
#' |
|
152 |
#' @keywords internal |
|
153 |
#' @noRd |
|
154 |
h_tidy_class <- function(d, obj) { |
|
155 | 1181x |
cls <- class(obj) |
156 | 1181x |
class(d) <- c(paste0("tbl_", cls[1]), class(d)) |
157 | 1181x |
d |
158 |
} |
|
159 | ||
160 |
#' Convert a `CrmPackObject`'s "Interval list" to a Min-Max |
|
161 |
#' |
|
162 |
#' @description `r lifecycle::badge("experimental")` |
|
163 |
#' |
|
164 |
#' `CrmPackClass` objects that define a set of intervals (such as `CohortSizeRange`) |
|
165 |
#' typically contain a left-open vector that dfines the intervals. For example, |
|
166 |
#' `my_size <- CohortSizeRange(intervals = c(0, 20), cohort_size = c(1, 3))` defines |
|
167 |
#' two dose ranges: [0, 20) and [20, Inf). This is convenient for coding, but |
|
168 |
#' awkward for reporting. This helper function converts this single-column |
|
169 |
#' representation to a two-column representation that explicitly defines the |
|
170 |
#' lower and upper ends of each interval. Using the example above, the converted |
|
171 |
#' tibble would look like this: |
|
172 |
#' |
|
173 |
#' | cohort_size | min | max | |
|
174 |
#' | ----------: | ---: | ---: | |
|
175 |
#' | 1 | -Inf | 20 | |
|
176 |
#' | 3 | 20 | Inf | |
|
177 |
#' |
|
178 |
#' @param x (`tibble`)\cr the tibble to be converted. |
|
179 |
#' @param col (`tidy-eval`)\cr column containing the intervals. |
|
180 |
#' @param min_col (`character`)\cr name of the column containing the lower end |
|
181 |
#' of the interval in the returned value. |
|
182 |
#' @param max_col (`character`)\cr name of the column containing the upper end |
|
183 |
#' of the interval in the returned value. |
|
184 |
#' @param range_min (`numeric`)\cr value of the lower end of the first interval. |
|
185 |
#' @param range_max (`numeric`)\cr value of the upper end of the last interval. |
|
186 |
#' |
|
187 |
#' @return A `tibble` in min-max format, with one row more than the input tibble. |
|
188 |
#' |
|
189 |
#' @importFrom rlang := |
|
190 |
#' @keywords internal |
|
191 |
#' @noRd |
|
192 |
h_range_to_minmax <- function( |
|
193 |
x, |
|
194 |
col, |
|
195 |
min_col = "min", |
|
196 |
max_col = "max", |
|
197 |
range_min = -Inf, |
|
198 |
range_max = Inf) { |
|
199 | 242x |
vals <- x %>% dplyr::pull({{ col }}) |
200 | 242x |
tibble( |
201 | 242x |
{{ min_col }} := c(range_min, vals), |
202 | 242x |
{{ max_col }} := c(vals, range_max) |
203 |
) |
|
204 |
} |
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(..., tol = sqrt(.Machine$double.eps), .var.name = vname(x), add = NULL) { |
|
118 |
# assert_equal <- makeAssertionFunction(check_equal) fails with error "Error |
|
119 |
# in `checkmate::makeAssertion(..., res, .var.name, add)`: unused argument |
|
120 |
# (add)", possibly because of the use of ... in check_equal. |
|
121 | 7x |
res <- check_equal(..., tol = tol) |
122 | 7x |
makeAssertion(list(...), res, .var.name, add) |
123 |
} |
|
124 |
# nolint end |
|
125 | ||
126 |
# assert_probabilities ---- |
|
127 | ||
128 |
#' Check if an argument is a probability vector |
|
129 |
#' |
|
130 |
#' @description `r lifecycle::badge("stable")` |
|
131 |
#' |
|
132 |
#' Check if every element in a given numerical vector or matrix represents a |
|
133 |
#' probability, that is a number within (0, 1) interval, that can optionally be |
|
134 |
#' closed at any side. |
|
135 |
#' |
|
136 |
#' @note If there are any missing or non-finite values in `x`, this function |
|
137 |
#' returns `FALSE`, regardless of the values of other elements in `x`. |
|
138 |
#' |
|
139 |
#' @param x (`numeric`)\cr vector or matrix with numerical values to check. |
|
140 |
#' @param bounds_closed (`logical`)\cr should bounds be closed? This can be a |
|
141 |
#' scalar or vector of length two. If it is a scalar, then its value applies |
|
142 |
#' equally to lower bound \eqn{0} and upper bound \eqn{1}. If this is a vector |
|
143 |
#' with two flags, the first flag corresponds to the lower bound \eqn{0} |
|
144 |
#' only, and the second to the upper bound \eqn{1} only. |
|
145 |
#' @inheritParams checkmate::check_numeric |
|
146 |
#' |
|
147 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
148 |
#' |
|
149 |
#' @seealso [`assertions`] for more details. |
|
150 |
#' |
|
151 |
#' @export |
|
152 |
#' @examples |
|
153 |
#' x <- c(0, 0.2, 0.1, 0.3, 1) |
|
154 |
#' check_probabilities(x) |
|
155 |
#' check_probabilities(x, bounds_closed = FALSE) |
|
156 |
#' check_probabilities(x, bounds_closed = c(FALSE, TRUE)) |
|
157 |
check_probabilities <- function(x, bounds_closed = TRUE, len = NULL, unique = FALSE, sorted = FALSE) { |
|
158 | 10499x |
assert_numeric(x) |
159 | 10498x |
assert_logical(bounds_closed, min.len = 1, max.len = 2, any.missing = FALSE) |
160 | 10498x |
assert_number(len, null.ok = TRUE) |
161 | 10498x |
assert_flag(sorted) |
162 | ||
163 | 10498x |
result <- check_numeric( |
164 | 10498x |
x, |
165 | 10498x |
finite = TRUE, any.missing = FALSE, len = len, unique = unique, sorted = sorted |
166 |
) |
|
167 | ||
168 | 10498x |
if (isTRUE(result)) { |
169 | 10419x |
in_bounds <- all(h_in_range(x, range = c(0L, 1L), bounds_closed = bounds_closed)) |
170 | 10419x |
if (!in_bounds) { |
171 | 142x |
result <- paste( |
172 | 142x |
"Probability must be within", |
173 | 142x |
ifelse(bounds_closed[1], "[0,", "(0,"), |
174 | 142x |
ifelse(tail(bounds_closed, 1), "1]", "1)"), |
175 | 142x |
"bounds but it is not" |
176 |
) |
|
177 |
} |
|
178 |
} |
|
179 | ||
180 | 10498x |
result |
181 |
} |
|
182 | ||
183 |
#' @rdname check_probabilities |
|
184 |
#' @inheritParams check_probabilities |
|
185 |
#' @export |
|
186 |
assert_probabilities <- makeAssertionFunction(check_probabilities) |
|
187 | ||
188 |
#' @rdname check_probabilities |
|
189 |
#' @inheritParams check_probabilities |
|
190 |
#' @export |
|
191 |
test_probabilities <- makeTestFunction(check_probabilities) |
|
192 | ||
193 |
#' @rdname check_probabilities |
|
194 |
#' @inheritParams check_probabilities |
|
195 |
#' @export |
|
196 |
expect_probabilities <- makeExpectationFunction(check_probabilities) |
|
197 | ||
198 |
# assert_probability ---- |
|
199 | ||
200 |
#' Check if an argument is a single probability value |
|
201 |
#' |
|
202 |
#' @description `r lifecycle::badge("stable")` |
|
203 |
#' |
|
204 |
#' Check if a given value represents a probability, that is a number within |
|
205 |
#' (0, 1) interval, that can optionally be closed at any side. |
|
206 |
#' |
|
207 |
#' @param x (`number`)\cr a single value to check. |
|
208 |
#' @inheritParams check_probabilities |
|
209 |
#' |
|
210 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
211 |
#' |
|
212 |
#' @seealso [`assertions`] for more details. |
|
213 |
#' |
|
214 |
#' @export |
|
215 |
#' @examples |
|
216 |
#' check_probability(0.5) |
|
217 |
#' check_probability(0, bounds_closed = FALSE) |
|
218 |
#' check_probability(0, bounds_closed = c(FALSE, TRUE)) |
|
219 |
check_probability <- function(x, bounds_closed = TRUE) { |
|
220 | 5633x |
check_probabilities(x = x, bounds_closed = bounds_closed, len = 1) |
221 |
} |
|
222 | ||
223 |
#' @rdname check_probability |
|
224 |
#' @inheritParams check_probability |
|
225 |
#' @export |
|
226 |
assert_probability <- makeAssertionFunction(check_probability) |
|
227 | ||
228 |
#' @rdname check_probability |
|
229 |
#' @inheritParams check_probability |
|
230 |
#' @export |
|
231 |
test_probability <- makeTestFunction(check_probability) |
|
232 | ||
233 |
#' @rdname check_probability |
|
234 |
#' @inheritParams check_probability |
|
235 |
#' @export |
|
236 |
expect_probability <- makeExpectationFunction(check_probability) |
|
237 | ||
238 |
# assert_probability_range ---- |
|
239 | ||
240 |
#' Check if an argument is a probability range |
|
241 |
#' |
|
242 |
#' @description `r lifecycle::badge("stable")` |
|
243 |
#' |
|
244 |
#' Check if a given numerical interval represents a probability range, that is |
|
245 |
#' a sub-interval of (0, 1) interval, that can optionally be closed at any side. |
|
246 |
#' |
|
247 |
#' @param x (`number`)\cr an interval to check. |
|
248 |
#' @inheritParams check_probabilities |
|
249 |
#' |
|
250 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
251 |
#' |
|
252 |
#' @seealso [`assertions`] for more details. |
|
253 |
#' |
|
254 |
#' @export |
|
255 |
#' @examples |
|
256 |
#' x <- c(0, 0.2) |
|
257 |
#' check_probability_range(x) |
|
258 |
#' check_probability_range(rev(x)) |
|
259 |
#' check_probability_range(x, bounds_closed = FALSE) |
|
260 |
#' check_probability_range(x, bounds_closed = c(FALSE, TRUE)) |
|
261 |
check_probability_range <- function(x, bounds_closed = TRUE) { |
|
262 | 760x |
check_probabilities(x = x, bounds_closed = bounds_closed, len = 2, sorted = TRUE) |
263 |
} |
|
264 | ||
265 |
#' @rdname check_probability_range |
|
266 |
#' @inheritParams check_probability_range |
|
267 |
#' @export |
|
268 |
assert_probability_range <- makeAssertionFunction(check_probability_range) |
|
269 | ||
270 |
#' @rdname check_probability_range |
|
271 |
#' @inheritParams check_probability_range |
|
272 |
#' @export |
|
273 |
test_probability_range <- makeTestFunction(check_probability_range) |
|
274 | ||
275 |
#' @rdname check_probability_range |
|
276 |
#' @inheritParams check_probability_range |
|
277 |
#' @export |
|
278 |
expect_probability_range <- makeExpectationFunction(check_probability_range) |
|
279 | ||
280 |
# assert_length ---- |
|
281 | ||
282 |
#' Check if vectors are of compatible lengths |
|
283 |
#' |
|
284 |
#' @description `r lifecycle::badge("stable")` |
|
285 |
#' |
|
286 |
#' Two vectors are of compatible size if and only if: \cr |
|
287 |
#' 1. At least one vector has size 1 \cr |
|
288 |
#' 2. or both vectors are of the same size. \cr |
|
289 |
#' |
|
290 |
#' @param x (`any`)\cr the first vector, any object for which [length()] |
|
291 |
#' function is defined. |
|
292 |
#' @param len (`count`)\cr the length of the second vector. |
|
293 |
#' @inheritParams checkmate::check_numeric |
|
294 |
#' |
|
295 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
296 |
#' |
|
297 |
#' @seealso [`assertions`] for more details. |
|
298 |
#' |
|
299 |
#' @export |
|
300 |
#' @examples |
|
301 |
#' check_length(1:5, 1) |
|
302 |
#' check_length(1:5, 6) |
|
303 |
#' check_length(1:5, 5) |
|
304 |
#' check_length(10, 1) |
|
305 |
#' check_length(10, 9) |
|
306 |
check_length <- function(x, len) { |
|
307 | 41628x |
x_len <- length(x) |
308 | 41628x |
assert_true(x_len >= 1L) |
309 | 41627x |
assert_count(len) |
310 | ||
311 | 41623x |
if (x_len == 1L || len == 1L || x_len == len) { |
312 | 41594x |
TRUE |
313 |
} else { |
|
314 | 29x |
paste( |
315 | 29x |
"x is of length", |
316 | 29x |
x_len, |
317 | 29x |
"which is not allowed; the allowed lengths are: 1 or", |
318 | 29x |
len, |
319 | 29x |
collapse = "" |
320 |
) |
|
321 |
} |
|
322 |
} |
|
323 | ||
324 |
#' @rdname check_length |
|
325 |
#' @inheritParams check_length |
|
326 |
#' @export |
|
327 |
assert_length <- makeAssertionFunction(check_length) |
|
328 | ||
329 |
#' @rdname check_length |
|
330 |
#' @inheritParams check_length |
|
331 |
#' @export |
|
332 |
test_length <- makeTestFunction(check_length) |
|
333 | ||
334 |
# assert_range ---- |
|
335 | ||
336 |
#' Check that an argument is a numerical range |
|
337 |
#' |
|
338 |
#' @description `r lifecycle::badge("stable")` |
|
339 |
#' |
|
340 |
#' An argument `x` is a numerical range if and only if (all conditions must be met): |
|
341 |
#' 1. Is an object of type: `integer` or `double`. |
|
342 |
#' 2. Is a vector or length two such that the value of the first number is not |
|
343 |
#' less than the second number. Equalness is allowed if and only if `unique` flag |
|
344 |
#' is set to `TRUE`. |
|
345 |
#' 3. Lower bound of the interval is greater than or equal to `lower` and |
|
346 |
#' upper bound of the interval is less than or equal to `upper`. |
|
347 |
#' 4. It contains only finite (given that `finite` is `TRUE`) and non-missing values. |
|
348 |
#' |
|
349 |
#' @inheritParams checkmate::check_numeric |
|
350 |
#' |
|
351 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
352 |
#' |
|
353 |
#' @seealso [`assertions`] for more details. |
|
354 |
#' |
|
355 |
#' @export |
|
356 |
#' @examples |
|
357 |
#' check_range(c(1, 5)) |
|
358 |
#' check_range(c(-5, 1)) |
|
359 |
#' check_range(c(4, 1)) |
|
360 |
#' check_range(c(1, 1)) |
|
361 |
#' check_range(c(1, 1), unique = FALSE) |
|
362 |
#' check_range(1:3) |
|
363 |
check_range <- function(x, lower = -Inf, upper = Inf, finite = FALSE, unique = TRUE) { |
|
364 | 93x |
assert_number(lower) |
365 | 92x |
assert_number(upper) |
366 | 91x |
assert_flag(finite) |
367 | 90x |
assert_flag(unique) |
368 | ||
369 | 89x |
result <- check_numeric( |
370 | 89x |
x, |
371 | 89x |
lower = lower, |
372 | 89x |
upper = upper, |
373 | 89x |
finite = finite, |
374 | 89x |
any.missing = FALSE, |
375 | 89x |
len = 2, |
376 | 89x |
unique = unique, |
377 | 89x |
sorted = TRUE |
378 |
) |
|
379 | ||
380 | 89x |
if (!isTRUE(result)) { |
381 | 24x |
result <- paste("x must be a valid numerical range.", result) |
382 |
} |
|
383 | 89x |
result |
384 |
} |
|
385 | ||
386 |
#' @rdname check_range |
|
387 |
#' @inheritParams check_range |
|
388 |
#' @export |
|
389 |
assert_range <- makeAssertionFunction(check_range) |
|
390 | ||
391 |
#' @rdname check_range |
|
392 |
#' @inheritParams check_range |
|
393 |
#' @export |
|
394 |
test_range <- makeTestFunction(check_range) |
|
395 | ||
396 |
#' @rdname check_range |
|
397 |
#' @inheritParams check_range |
|
398 |
#' @export |
|
399 |
expect_range <- makeExpectationFunction(check_range) |
|
400 | ||
401 |
# assert_format ---- |
|
402 | ||
403 |
#' Check that an argument is a valid format specification |
|
404 |
#' |
|
405 |
#' @description `r lifecycle::badge("stable")` |
|
406 |
#' |
|
407 |
#' @inheritParams checkmate::check_numeric |
|
408 |
#' |
|
409 |
#' @return `TRUE` if successful, otherwise a string with the error message. |
|
410 |
#' |
|
411 |
#' @seealso [`assertions`] for more details. |
|
412 |
#' |
|
413 |
#' @export |
|
414 |
#' @examples |
|
415 |
#' check_format("%5.2f") |
|
416 |
check_format <- function(x, len = NULL, min.len = NULL, max.len = NULL) { |
|
417 | 262x |
assert_number(len, lower = 1, null.ok = TRUE) |
418 | 262x |
assert_number(min.len, lower = 1, null.ok = TRUE) |
419 | 262x |
assert_number(max.len, lower = 1, null.ok = TRUE) |
420 | ||
421 | 262x |
result <- check_character( |
422 | 262x |
x, |
423 | 262x |
len = len, |
424 | 262x |
min.len = min.len, |
425 | 262x |
max.len = max.len, |
426 | 262x |
any.missing = FALSE, |
427 |
# https://stackoverflow.com/questions/446285/validate-sprintf-format-from-input-field-with-regex |
|
428 | 262x |
pattern = "%(?:\\d+\\$)?[+-]?(?:[ 0]|'.{1})?-?\\d*(?:\\.\\d+)?[bcdeEufFgGosxX]", |
429 |
) |
|
430 | ||
431 | 262x |
if (!isTRUE(result)) { |
432 | ! |
result <- paste("x must be a valid format specifier.", result) |
433 |
} |
|
434 | 262x |
result |
435 |
} |
|
436 | ||
437 |
#' @rdname check_format |
|
438 |
#' @inheritParams check_format |
|
439 |
#' @export |
|
440 |
assert_format <- makeAssertionFunction(check_format) |
|
441 | ||
442 |
#' @rdname check_format |
|
443 |
#' @inheritParams check_format |
|
444 |
#' @export |
|
445 |
test_format <- makeTestFunction(check_format) |
|
446 | ||
447 |
#' @rdname check_format |
|
448 |
#' @inheritParams check_format |
|
449 |
#' @export |
|
450 |
expect_format <- makeExpectationFunction(check_format) |
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(use_fixed, # nolintr |
|
28 |
sigma2W, |
|
29 |
comp) { |
|
30 | 206x |
if (use_fixed) { |
31 | 114x |
assert_number(sigma2W, lower = 0 + .Machine$double.xmin, finite = TRUE) |
32 | 112x |
comp$modelspecs$precW <- 1 / sigma2W |
33 |
} else { |
|
34 | 92x |
assert_true(h_test_named_numeric(sigma2W, permutation.of = c("a", "b"))) |
35 | 91x |
comp$priormodel <- h_jags_join_models( |
36 | 91x |
comp$priormodel, |
37 | 91x |
function() { |
38 | ! |
precW ~ dgamma(precWa, precWb) |
39 |
} |
|
40 |
) |
|
41 | 91x |
comp$modelspecs$precWa <- sigma2W[["a"]] |
42 | 91x |
comp$modelspecs$precWb <- sigma2W[["b"]] |
43 | 91x |
comp$init$precW <- 1 |
44 | 91x |
comp$sample <- c(comp$sample, "precW") |
45 |
} |
|
46 | 203x |
comp |
47 |
} |
|
48 | ||
49 |
#' Update [`DualEndpoint`] class model components with regard to DLT and biomarker |
|
50 |
#' correlation. |
|
51 |
#' |
|
52 |
#' @description `r lifecycle::badge("stable")` |
|
53 |
#' |
|
54 |
#' A simple helper function that takes [`DualEndpoint`] model existing components |
|
55 |
#' (`priormodel`, `modelspecs`, `init`, `sample`), and updates them with regard to |
|
56 |
#' DLT and biomarker correlation `rho`. |
|
57 |
#' |
|
58 |
#' @param use_fixed (`flag`)\cr indicates whether a fixed value for DLT and |
|
59 |
#' biomarker correlation `rho` should be used or not. If `rho` is not supposed |
|
60 |
#' to be a fixed value, a prior distribution from the scaled Beta family will |
|
61 |
#' be used. See the details below, under `rho` argument. |
|
62 |
#' @param rho (`numeric`)\cr DLT and biomarker correlation. It must be either a |
|
63 |
#' fixed value (between `-1` and `1`), or a named vector with two elements, |
|
64 |
#' named `a` and `b` for the Beta prior on the transformation |
|
65 |
#' `kappa = (rho + 1) / 2`, which is in `(0, 1)`. For example, `a = 1, b = 1` |
|
66 |
#' leads to a uniform prior on `rho`. |
|
67 |
#' @param comp (`list`)\cr a named list with model components that will be updated. |
|
68 |
#' The names should be: `priormodel`, `modelspecs`, `init`, `sample`. For |
|
69 |
#' definitions of the components, see [`GeneralModel`] class. |
|
70 |
#' The `modelspecs` and `init` components on `comp` list are specified up to |
|
71 |
#' the body of corresponding `GeneralModel@modelspecs` and `GeneralModel@init` |
|
72 |
#' functions. These bodies are simply a lists itself. |
|
73 |
#' |
|
74 |
#' @return A `list` with updated model components. |
|
75 |
#' |
|
76 |
#' @export |
|
77 |
h_model_dual_endpoint_rho <- function(use_fixed, |
|
78 |
rho, |
|
79 |
comp) { |
|
80 | 207x |
rmin <- .Machine$double.xmin |
81 | 207x |
if (use_fixed) { |
82 | 115x |
assert_number(rho, lower = -1 + rmin, upper = 1 - rmin) |
83 | 112x |
comp$modelspecs$rho <- rho |
84 |
} else { |
|
85 | 92x |
assert_true(h_test_named_numeric(rho, permutation.of = c("a", "b"))) |
86 | 91x |
comp$priormodel <- h_jags_join_models( |
87 | 91x |
comp$priormodel, |
88 | 91x |
function() { |
89 | ! |
kappa ~ dbeta(rhoa, rhob) |
90 | ! |
rho <- 2 * kappa - 1 |
91 |
} |
|
92 |
) |
|
93 | 91x |
comp$modelspecs$rhoa <- rho[["a"]] |
94 | 91x |
comp$modelspecs$rhob <- rho[["b"]] |
95 | 91x |
comp$init$kappa <- 0.5 |
96 | 91x |
comp$sample <- c(comp$sample, "rho") |
97 |
} |
|
98 | 203x |
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(use_fixed, # nolintr |
|
125 |
sigma2betaW, |
|
126 |
de) { |
|
127 | 57x |
modelspecs <- de@modelspecs |
128 | 57x |
init <- de@init |
129 | ||
130 | 57x |
if (use_fixed) { |
131 | 45x |
assert_number(sigma2betaW, lower = 0 + .Machine$double.xmin, finite = TRUE) |
132 | 43x |
ms <- list(precBetaW = 1 / sigma2betaW) |
133 |
} else { |
|
134 | 12x |
assert_true(h_test_named_numeric(sigma2betaW, permutation.of = c("a", "b"))) |
135 |
# gamma prior for random walk precision. |
|
136 | 11x |
de@priormodel <- h_jags_join_models( |
137 | 11x |
de@priormodel, |
138 | 11x |
function() { |
139 | ! |
precBetaW ~ dgamma(precBetaWa, precBetaWb) |
140 |
} |
|
141 |
) |
|
142 | 11x |
ms <- list(precBetaWa = sigma2betaW[["a"]], precBetaWb = sigma2betaW[["b"]]) |
143 | 11x |
de@init <- function(y) { |
144 | 10x |
c(init(y), list(precBetaW = 1)) |
145 |
} |
|
146 | 11x |
de@sample <- c(de@sample, "precBetaW") |
147 |
} |
|
148 | 54x |
de@modelspecs <- function(from_prior) { |
149 | 31x |
c(modelspecs(from_prior), ms) |
150 |
} |
|
151 | 54x |
de |
152 |
} |
|
153 | ||
154 |
#' Update certain components of [`DualEndpoint`] model with regard to parameters |
|
155 |
#' of the function that models dose-biomarker relationship defined in the |
|
156 |
#' [`DualEndpointBeta`] class. |
|
157 |
#' |
|
158 |
#' @description `r lifecycle::badge("stable")` |
|
159 |
#' |
|
160 |
#' A simple helper function that takes [`DualEndpoint`] object and updates |
|
161 |
#' `use_fixed`, `priormodel`, `modelspecs`, `init`, `sample` slots with regard |
|
162 |
#' to a given parameter of the dose-biomarker relationship \eqn{f(x)} defined in |
|
163 |
#' the [`DualEndpointBeta`] class. This update solely depends on whether a given |
|
164 |
#' parameter's value `param` is a fixed-valued scalar or two-elements numeric |
|
165 |
#' vector. In the later case, it is assumed that `param` represents two |
|
166 |
#' parameters of a probability distribution that will be used in `priormodel` |
|
167 |
#' function to generate values for the `param_name` parameter of \eqn{f(x)}. |
|
168 |
#' See the help page for [`DualEndpointBeta`] class for more details. |
|
169 |
#' |
|
170 |
#' @param param (`numeric`)\cr the value of a given `param_name` parameter of |
|
171 |
#' the dose-biomarker relationship function \eqn{f(x)}. Either a fixed-valued |
|
172 |
#' scalar or vector with two elements that are the parameters of a probability |
|
173 |
#' distribution that will be used in `priormodel` function to generate values |
|
174 |
#' for the `param_name` parameter of \eqn{f(x)}. |
|
175 |
#' @param param_name (`string`)\cr the name of the parameter of \eqn{f(x)}, |
|
176 |
#' whose value depends on `param`. |
|
177 |
#' @param param_suffix (`character`)\cr the two suffixes to be appended to |
|
178 |
#' the elements of `param_name` and then used when updating `modelspecs`. |
|
179 |
#' The value of this argument is ignored when `param` is a scalar. |
|
180 |
#' @param priormodel (`function` or `NULL`)\cr a function representing the |
|
181 |
#' `JAGS` prior specification that will be appended to existing |
|
182 |
#' `de@priormodel` specification if `param` is not a scalar. Otherwise, |
|
183 |
#' `de@priormodel` remains unchanged. |
|
184 |
#' @param de (`DualEnpoint`)\cr dual endpoint model whose slots will be updated. |
|
185 |
#' |
|
186 |
#' @return A [`DualEndpoint`] model with updated `use_fixed`, `priormodel`, |
|
187 |
#' `modelspecs`, `init`, `sample` slots. |
|
188 |
#' |
|
189 |
#' @export |
|
190 |
h_model_dual_endpoint_beta <- function(param, |
|
191 |
param_name, |
|
192 |
param_suffix = c("_low", "_high"), |
|
193 |
priormodel = NULL, |
|
194 |
de) { |
|
195 | 179x |
assert_numeric(param, min.len = 1, max.len = 2, any.missing = FALSE) |
196 | 178x |
assert_string(param_name) |
197 | 177x |
assert_class(de, "DualEndpoint") |
198 | ||
199 | 177x |
use_fixed <- setNames(test_number(param), param_name) |
200 | 177x |
modelspecs <- de@modelspecs |
201 | 177x |
init <- de@init |
202 | ||
203 | 177x |
if (use_fixed) { |
204 | 81x |
ms <- setNames(list(param), param_name) |
205 |
} else { |
|
206 | 96x |
assert_character(param_suffix, len = 2, unique = TRUE, any.missing = FALSE) |
207 | 95x |
assert_function(priormodel) |
208 | 94x |
param_name2 <- paste0(param_name, param_suffix) |
209 | ||
210 | 94x |
de@priormodel <- h_jags_join_models( |
211 | 94x |
de@priormodel, |
212 | 94x |
priormodel |
213 |
) |
|
214 | 94x |
ms <- setNames(list(param[1], param[2]), param_name2) |
215 | 94x |
de@init <- function(y) { |
216 | 21x |
c(init(y), setNames(list(mean(param)), param_name)) |
217 |
} |
|
218 | 94x |
de@sample <- c(de@sample, param_name) |
219 |
} |
|
220 | 175x |
de@modelspecs <- function(from_prior) { |
221 | 60x |
c(modelspecs(from_prior), ms) |
222 |
} |
|
223 | 175x |
de@use_fixed <- c(de@use_fixed, use_fixed) |
224 | 175x |
de |
225 |
} |
|
226 | ||
227 |
#' Convert an ordinal CRM model to the Equivalent Binary CRM Model for a Specific |
|
228 |
#' Grade |
|
229 |
#' |
|
230 |
#' @description `r lifecycle::badge("experimental")` |
|
231 |
#' |
|
232 |
#' A simple helper function that takes a [`LogisticLogNormalOrdinal`] and an |
|
233 |
#' integer grade and converts them to the equivalent `LogisticLogNormal` model. |
|
234 |
#' |
|
235 |
#' @param x (`LogisticLogNormalOrdinal`)\cr the `LogisticLogNormalOrdinal` |
|
236 |
#' model to covert |
|
237 |
#' @param grade (`integer`)\cr the toxicity grade for which the equivalent model |
|
238 |
#' is required. |
|
239 |
#' @return A [`LogisticLogNormal`] model. |
|
240 |
#' |
|
241 |
#' @export |
|
242 |
h_convert_ordinal_model <- function(x, grade) { |
|
243 |
# Validate |
|
244 | 21x |
assert_integer(grade, len = 1, lower = 1) |
245 | 21x |
assert_class(x, "LogisticLogNormalOrdinal") |
246 |
# Execute |
|
247 | 21x |
LogisticLogNormal( |
248 | 21x |
mean = x@params@mean[-grade], |
249 | 21x |
cov = x@params@cov[-grade, -grade], |
250 | 21x |
ref_dose = x@ref_dose |
251 |
) |
|
252 |
} |
1 |
##' @include helpers.R |
|
2 |
##' @include Model-class.R |
|
3 |
NULL |
|
4 | ||
5 |
# nolint start |
|
6 | ||
7 |
##' Convert prior quantiles (lower, median, upper) to logistic (log) |
|
8 |
##' normal model |
|
9 |
##' |
|
10 |
##' This function uses generalized simulated annealing to optimize |
|
11 |
##' a \code{\linkS4class{LogisticNormal}} model to be as close as possible |
|
12 |
##' to the given prior quantiles. |
|
13 |
##' |
|
14 |
##' @param dosegrid the dose grid |
|
15 |
##' @param refDose the reference dose |
|
16 |
##' @param lower the lower quantiles |
|
17 |
##' @param median the medians |
|
18 |
##' @param upper the upper quantiles |
|
19 |
##' @param level the credible level of the (lower, upper) intervals (default: |
|
20 |
##' 0.95) |
|
21 |
##' @param logNormal use the log-normal prior? (not default) otherwise, the |
|
22 |
##' normal prior for the logistic regression coefficients is used |
|
23 |
##' @param parstart starting values for the parameters. By default, these |
|
24 |
##' are determined from the medians supplied. |
|
25 |
##' @param parlower lower bounds on the parameters (intercept alpha and the |
|
26 |
##' slope beta, the corresponding standard deviations and the correlation.) |
|
27 |
##' @param parupper upper bounds on the parameters |
|
28 |
##' @param seed seed for random number generation |
|
29 |
##' @param verbose be verbose? (default) |
|
30 |
##' @param control additional options for the optimisation routine, see |
|
31 |
##' \code{\link[GenSA]{GenSA}} for more details |
|
32 |
##' @return a list with the best approximating \code{model} |
|
33 |
##' (\code{\linkS4class{LogisticNormal}} or |
|
34 |
##' \code{\linkS4class{LogisticLogNormal}}), the resulting \code{quantiles}, the |
|
35 |
##' \code{required} quantiles and the \code{distance} to the required quantiles, |
|
36 |
##' as well as the final \code{parameters} (which could be used for running the |
|
37 |
##' algorithm a second time) |
|
38 |
##' |
|
39 |
##' @importFrom GenSA GenSA |
|
40 |
##' @importFrom mvtnorm rmvnorm |
|
41 |
##' @export |
|
42 |
##' @keywords programming |
|
43 |
Quantiles2LogisticNormal <- function(dosegrid, |
|
44 |
refDose, |
|
45 |
lower, |
|
46 |
median, |
|
47 |
upper, |
|
48 |
level = 0.95, |
|
49 |
logNormal = FALSE, |
|
50 |
parstart = NULL, |
|
51 |
parlower = c(-10, -10, 0, 0, -0.95), |
|
52 |
parupper = c(10, 10, 10, 10, 0.95), |
|
53 |
seed = 12345, |
|
54 |
verbose = TRUE, |
|
55 |
control = |
|
56 |
list( |
|
57 |
threshold.stop = 0.01, |
|
58 |
maxit = 50000, |
|
59 |
temperature = 50000, |
|
60 |
max.time = 600 |
|
61 |
)) { |
|
62 |
## extracts and checks |
|
63 | 2x |
nDoses <- length(dosegrid) |
64 | ||
65 | 2x |
assert_flag(logNormal) |
66 | 2x |
assert_flag(verbose) |
67 | 2x |
assert_probability(level, bounds_closed = FALSE) |
68 | 2x |
stopifnot( |
69 | 2x |
!is.unsorted(dosegrid, strictly = TRUE), |
70 |
## the medians must be monotonically increasing: |
|
71 | 2x |
!is.unsorted(median), |
72 | 2x |
identical(length(lower), nDoses), |
73 | 2x |
identical(length(median), nDoses), |
74 | 2x |
identical(length(upper), nDoses), |
75 | 2x |
all(lower < median), |
76 | 2x |
all(upper > median), |
77 | 2x |
identical(length(parlower), 5L), |
78 | 2x |
identical(length(parupper), 5L), |
79 | 2x |
all(parlower < parstart), |
80 | 2x |
all(parstart < parupper) |
81 |
) |
|
82 | ||
83 |
## put verbose argument in the control list |
|
84 | 2x |
control$verbose <- verbose |
85 | ||
86 |
## parametrize in terms of the means for the intercept alpha and the |
|
87 |
## (log) slope beta, |
|
88 |
## the corresponding standard deviations and the correlation. |
|
89 |
## Define start values for optimisation: |
|
90 | 2x |
startValues <- |
91 | 2x |
if (is.null(parstart)) { |
92 |
## find approximate means for alpha and slope beta |
|
93 |
## from fitting logistic model to medians: |
|
94 | 1x |
startAlphaBeta <- |
95 | 1x |
coef(lm(I(logit(median)) ~ I(log(dosegrid / refDose)))) |
96 | ||
97 |
## overall starting values: |
|
98 | 1x |
c( |
99 | 1x |
meanAlpha = |
100 | 1x |
startAlphaBeta[1], |
101 | 1x |
meanBeta = |
102 | 1x |
if (logNormal) log(startAlphaBeta[2]) else startAlphaBeta[2], |
103 | 1x |
sdAlpha = |
104 | 1x |
1, |
105 | 1x |
sdBeta = |
106 | 1x |
1, |
107 | 1x |
correlation = |
108 | 1x |
0 |
109 |
) |
|
110 |
} else { |
|
111 | 1x |
parstart |
112 |
} |
|
113 | ||
114 |
## what is the target function which we want to minimize? |
|
115 | 2x |
target <- function(param) { |
116 |
## form the mean vector and covariance matrix |
|
117 | 4x |
mean <- param[1:2] |
118 | 4x |
cov <- matrix( |
119 | 4x |
c( |
120 | 4x |
param[3]^2, |
121 | 4x |
prod(param[3:5]), |
122 | 4x |
prod(param[3:5]), |
123 | 4x |
param[4]^2 |
124 |
), |
|
125 | 4x |
nrow = 2L, ncol = 2L |
126 |
) |
|
127 | ||
128 |
## simulate from the corresponding normal distribution |
|
129 | 4x |
set.seed(seed) |
130 | 4x |
normalSamples <- mvtnorm::rmvnorm( |
131 | 4x |
n = 1e4L, |
132 | 4x |
mean = mean, |
133 | 4x |
sigma = cov |
134 |
) |
|
135 | ||
136 |
## extract separate coefficients |
|
137 | 4x |
alphaSamples <- normalSamples[, 1L] |
138 | 4x |
betaSamples <- if (logNormal) exp(normalSamples[, 2L]) else normalSamples[, 2L] |
139 | ||
140 |
## and compute resulting quantiles |
|
141 | 4x |
quants <- matrix( |
142 | 4x |
nrow = length(dosegrid), |
143 | 4x |
ncol = 3L |
144 |
) |
|
145 | 4x |
colnames(quants) <- c("lower", "median", "upper") |
146 | ||
147 |
## process each dose after another: |
|
148 | 4x |
for (i in seq_along(dosegrid)) |
149 |
{ |
|
150 |
## create samples of the probability |
|
151 | 20x |
probSamples <- |
152 | 20x |
plogis(alphaSamples + betaSamples * log(dosegrid[i] / refDose)) |
153 | ||
154 |
## compute lower, median and upper quantile |
|
155 | 20x |
quants[i, ] <- |
156 | 20x |
quantile(probSamples, |
157 | 20x |
probs = c((1 - level) / 2, 0.5, (1 + level) / 2) |
158 |
) |
|
159 |
} |
|
160 | ||
161 |
## now we can compute the target value |
|
162 | 4x |
ret <- max(abs(quants - c(lower, median, upper))) |
163 | 4x |
return(structure(ret, |
164 | 4x |
mean = mean, |
165 | 4x |
cov = cov, |
166 | 4x |
quantiles = quants |
167 |
)) |
|
168 |
} |
|
169 | ||
170 | 2x |
set.seed(seed) |
171 |
## now optimise the target |
|
172 | 2x |
genSAres <- GenSA::GenSA( |
173 | 2x |
par = startValues, |
174 | 2x |
fn = target, |
175 | 2x |
lower = parlower, |
176 | 2x |
upper = parupper, |
177 | 2x |
control = control |
178 |
) |
|
179 | 2x |
distance <- genSAres$value |
180 | 2x |
pars <- genSAres$par |
181 | 2x |
targetRes <- target(pars) |
182 | ||
183 |
## and construct the model |
|
184 | 2x |
ret <- |
185 | 2x |
if (logNormal) { |
186 | 1x |
LogisticLogNormal( |
187 | 1x |
mean = attr(targetRes, "mean"), |
188 | 1x |
cov = attr(targetRes, "cov"), |
189 | 1x |
ref_dose = refDose |
190 |
) |
|
191 |
} else { |
|
192 | 1x |
LogisticNormal( |
193 | 1x |
mean = attr(targetRes, "mean"), |
194 | 1x |
cov = attr(targetRes, "cov"), |
195 | 1x |
ref_dose = refDose |
196 |
) |
|
197 |
} |
|
198 | ||
199 |
## return it together with the resulting distance and the quantiles |
|
200 | 2x |
return(list( |
201 | 2x |
model = ret, |
202 | 2x |
parameters = pars, |
203 | 2x |
quantiles = attr(targetRes, "quantiles"), |
204 | 2x |
required = cbind(lower, median, upper), |
205 | 2x |
distance = distance |
206 |
)) |
|
207 |
} |
|
208 | ||
209 |
# nolint end |
|
210 | ||
211 |
#' Helper for Minimal Informative Unimodal Beta Distribution |
|
212 |
#' |
|
213 |
#' As defined in Neuenschwander et al (2008), this function computes the |
|
214 |
#' parameters of the minimal informative unimodal beta distribution, given the |
|
215 |
#' request that the p-quantile should be q, i.e. `X ~ Be(a, b)` with |
|
216 |
#' `Pr(X <= q) = p`. |
|
217 |
#' |
|
218 |
#' @param p (`number`)\cr the probability. |
|
219 |
#' @param q (`number`)\cr the quantile. |
|
220 |
#' @return A list with the two resulting beta parameters `a` and `b`. |
|
221 |
#' |
|
222 |
#' @keywords internal |
|
223 |
h_get_min_inf_beta <- function(p, q) { |
|
224 | 3x |
assert_probability(p, bounds_closed = FALSE) |
225 | 3x |
assert_probability(q, bounds_closed = FALSE) |
226 | ||
227 | 3x |
if (q > p) { |
228 | 1x |
list( |
229 | 1x |
a = log(p) / log(q), |
230 | 1x |
b = 1 |
231 |
) |
|
232 |
} else { |
|
233 | 2x |
list( |
234 | 2x |
a = 1, |
235 | 2x |
b = log(1 - p) / log(1 - q) |
236 |
) |
|
237 |
} |
|
238 |
} |
|
239 | ||
240 |
# nolint start |
|
241 | ||
242 |
##' Construct a minimally informative prior |
|
243 |
##' |
|
244 |
##' This function constructs a minimally informative prior, which is captured in |
|
245 |
##' a \code{\linkS4class{LogisticNormal}} (or |
|
246 |
##' \code{\linkS4class{LogisticLogNormal}}) object. |
|
247 |
##' |
|
248 |
##' Based on the proposal by Neuenschwander et al (2008, Statistics in |
|
249 |
##' Medicine), a minimally informative prior distribution is constructed. The |
|
250 |
##' required key input is the minimum (\eqn{d_{1}} in the notation of the |
|
251 |
##' Appendix A.1 of that paper) and the maximum value (\eqn{d_{J}}) of the dose |
|
252 |
##' grid supplied to this function. Then \code{threshmin} is the probability |
|
253 |
##' threshold \eqn{q_{1}}, such that any probability of DLT larger than |
|
254 |
##' \eqn{q_{1}} has only 5% probability. Therefore \eqn{q_{1}} is the 95% |
|
255 |
##' quantile of the beta distribution and hence \eqn{p_{1} = 0.95}. Likewise, |
|
256 |
##' \code{threshmax} is the probability threshold \eqn{q_{J}}, such that any |
|
257 |
##' probability of DLT smaller than \eqn{q_{J}} has only 5% probability |
|
258 |
##' (\eqn{p_{J} = 0.05}). The probabilities \eqn{1 - p_{1}} and \eqn{p_{J}} can be |
|
259 |
##' controlled with the arguments \code{probmin} and \code{probmax}, respectively. |
|
260 |
##' Subsequently, for all doses supplied in the |
|
261 |
##' \code{dosegrid} argument, beta distributions are set up from the assumption |
|
262 |
##' that the prior medians are linear in log-dose on the logit scale, and |
|
263 |
##' \code{\link{Quantiles2LogisticNormal}} is used to transform the resulting |
|
264 |
##' quantiles into an approximating \code{\linkS4class{LogisticNormal}} (or |
|
265 |
##' \code{\linkS4class{LogisticLogNormal}}) model. Note that the reference dose |
|
266 |
##' is not required for these computations. |
|
267 |
##' |
|
268 |
##' @param dosegrid the dose grid |
|
269 |
##' @param refDose the reference dose |
|
270 |
##' @param threshmin Any toxicity probability above this threshold would |
|
271 |
##' be very unlikely (see \code{probmin}) at the minimum dose (default: 0.2) |
|
272 |
##' @param threshmax Any toxicity probability below this threshold would |
|
273 |
##' be very unlikely (see \code{probmax}) at the maximum dose (default: 0.3) |
|
274 |
##' @param probmin the prior probability of exceeding \code{threshmin} at the |
|
275 |
##' minimum dose (default: 0.05) |
|
276 |
##' @param probmax the prior probability of being below \code{threshmax} at the |
|
277 |
##' maximum dose (default: 0.05) |
|
278 |
##' @param \dots additional arguments for computations, see |
|
279 |
##' \code{\link{Quantiles2LogisticNormal}}, e.g. \code{refDose} and |
|
280 |
##' \code{logNormal=TRUE} to obtain a minimal informative log normal prior. |
|
281 |
##' @return see \code{\link{Quantiles2LogisticNormal}} |
|
282 |
##' |
|
283 |
##' @example examples/MinimalInformative.R |
|
284 |
##' @export |
|
285 |
##' @keywords programming |
|
286 |
MinimalInformative <- function(dosegrid, |
|
287 |
refDose, |
|
288 |
threshmin = 0.2, |
|
289 |
threshmax = 0.3, |
|
290 |
probmin = 0.05, |
|
291 |
probmax = 0.05, |
|
292 |
...) { |
|
293 |
## extracts and checks |
|
294 | ! |
nDoses <- length(dosegrid) |
295 | ||
296 | ! |
assert_probability(threshmin, bounds_closed = FALSE) |
297 | ! |
assert_probability(threshmax, bounds_closed = FALSE) |
298 | ! |
assert_probability(probmin, bounds_closed = FALSE) |
299 | ! |
assert_probability(probmax, bounds_closed = FALSE) |
300 | ! |
stopifnot( |
301 | ! |
!is.unsorted(dosegrid, strictly = TRUE) |
302 |
) |
|
303 | ! |
xmin <- dosegrid[1] |
304 | ! |
xmax <- dosegrid[nDoses] |
305 | ||
306 |
## derive the beta distributions at the lowest and highest dose |
|
307 | ! |
betaAtMin <- h_get_min_inf_beta( |
308 | ! |
q = threshmin, |
309 | ! |
p = 1 - probmin |
310 |
) |
|
311 | ! |
betaAtMax <- h_get_min_inf_beta( |
312 | ! |
q = threshmax, |
313 | ! |
p = probmax |
314 |
) |
|
315 | ||
316 |
## get the medians of those beta distributions |
|
317 | ! |
medianMin <- with( |
318 | ! |
betaAtMin, |
319 | ! |
qbeta(p = 0.5, a, b) |
320 |
) |
|
321 | ! |
medianMax <- with( |
322 | ! |
betaAtMax, |
323 | ! |
qbeta(p = 0.5, a, b) |
324 |
) |
|
325 | ||
326 |
## now determine the medians of all beta distributions |
|
327 | ! |
beta <- (logit(medianMax) - logit(medianMin)) / (log(xmax) - log(xmin)) |
328 | ! |
alpha <- logit(medianMax) - beta * log(xmax / refDose) |
329 | ! |
medianDosegrid <- plogis(alpha + beta * log(dosegrid / refDose)) |
330 | ||
331 |
## finally for all doses calculate 95% credible interval bounds |
|
332 |
## (lower and upper) |
|
333 | ! |
lower <- upper <- dosegrid |
334 | ! |
for (i in seq_along(dosegrid)) |
335 |
{ |
|
336 |
## get min inf beta distribution |
|
337 | ! |
thisMinBeta <- h_get_min_inf_beta( |
338 | ! |
p = 0.5, |
339 | ! |
q = medianDosegrid[i] |
340 |
) |
|
341 | ||
342 |
## derive required quantiles |
|
343 | ! |
lower[i] <- with( |
344 | ! |
thisMinBeta, |
345 | ! |
qbeta(p = 0.025, a, b) |
346 |
) |
|
347 | ! |
upper[i] <- with( |
348 | ! |
thisMinBeta, |
349 | ! |
qbeta(p = 0.975, a, b) |
350 |
) |
|
351 |
} |
|
352 | ||
353 |
## now go to Quantiles2LogisticNormal |
|
354 | ! |
Quantiles2LogisticNormal( |
355 | ! |
dosegrid = dosegrid, |
356 | ! |
refDose = refDose, |
357 | ! |
lower = lower, |
358 | ! |
median = medianDosegrid, |
359 | ! |
upper = upper, |
360 | ! |
level = 0.95, |
361 |
... |
|
362 |
) |
|
363 |
} |
|
364 | ||
365 |
# nolint end |
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("Class NextBest should not be instantiated directly. Please use one of its subclasses instead.")) |
37 |
} |
|
38 | ||
39 | ||
40 |
# NextBestMTD ---- |
|
41 | ||
42 |
## class ---- |
|
43 | ||
44 |
#' `NextBestMTD` |
|
45 |
#' |
|
46 |
#' @description `r lifecycle::badge("stable")` |
|
47 |
#' |
|
48 |
#' [`NextBestMTD`] is the class for next best dose based on MTD estimate. |
|
49 |
#' |
|
50 |
#' @slot target (`proportion`)\cr target toxicity probability, except 0 or 1. |
|
51 |
#' @slot derive (`function`)\cr a function which derives the final next best MTD |
|
52 |
#' estimate, based on vector of posterior MTD samples. It must therefore accept |
|
53 |
#' one and only one argument, which is a numeric vector, and return a number. |
|
54 |
#' |
|
55 |
#' @aliases NextBestMTD |
|
56 |
#' @export |
|
57 |
#' |
|
58 |
.NextBestMTD <- setClass( |
|
59 |
Class = "NextBestMTD", |
|
60 |
slots = c( |
|
61 |
target = "numeric", |
|
62 |
derive = "function" |
|
63 |
), |
|
64 |
prototype = prototype( |
|
65 |
target = 0.3, |
|
66 |
derive = function(mtd_samples) { |
|
67 |
quantile(mtd_samples, probs = 0.3) |
|
68 |
} |
|
69 |
), |
|
70 |
contains = "NextBest", |
|
71 |
validity = v_next_best_mtd |
|
72 |
) |
|
73 | ||
74 |
## constructor ---- |
|
75 | ||
76 |
#' @rdname NextBestMTD-class |
|
77 |
#' |
|
78 |
#' @param target (`proportion`)\cr see slot definition. |
|
79 |
#' @param derive (`function`)\cr see slot definition. |
|
80 |
#' |
|
81 |
#' @export |
|
82 |
#' @example examples/Rules-class-NextBestMTD.R |
|
83 |
#' |
|
84 |
NextBestMTD <- function(target, derive) { |
|
85 | 42x |
.NextBestMTD( |
86 | 42x |
target = target, |
87 | 42x |
derive = derive |
88 |
) |
|
89 |
} |
|
90 | ||
91 |
## default constructor ---- |
|
92 | ||
93 |
#' @rdname NextBestMTD-class |
|
94 |
#' @note Typically, end users will not use the `.DefaultNextBestMTD()` function. |
|
95 |
#' @export |
|
96 |
.DefaultNextBestMTD <- function() { |
|
97 | 8x |
NextBestMTD( |
98 | 8x |
target = 0.33, |
99 | 8x |
derive = function(mtd_samples) { |
100 | 10x |
quantile(mtd_samples, probs = 0.25) |
101 |
} |
|
102 |
) |
|
103 |
} |
|
104 | ||
105 |
# NextBestNCRM ---- |
|
106 | ||
107 |
## class ---- |
|
108 | ||
109 |
#' `NextBestNCRM` |
|
110 |
#' |
|
111 |
#' @description `r lifecycle::badge("stable")` |
|
112 |
#' |
|
113 |
#' [`NextBestNCRM`] is the class for next best dose that finds the next dose |
|
114 |
#' with high posterior probability to be in the target toxicity interval. |
|
115 |
#' |
|
116 |
#' @details To avoid numerical problems, the dose selection algorithm has been |
|
117 |
#' implemented as follows: First admissible doses are found, which are those |
|
118 |
#' with probability to fall in `overdose` category being below `max_overdose_prob`. |
|
119 |
#' Next, within the admissible doses, the maximum probability to fall in the |
|
120 |
#' `target` category is calculated. If that is above 5% (i.e. it is not just |
|
121 |
#' numerical error), then the corresponding dose is the next recommended dose. |
|
122 |
#' Otherwise, the highest admissible dose is the next recommended dose. |
|
123 |
#' |
|
124 |
#' @slot target (`numeric`)\cr the target toxicity interval (limits included). |
|
125 |
#' @slot overdose (`numeric`)\cr the overdose toxicity interval (lower limit |
|
126 |
#' excluded, upper limit included). It is used to filter probability samples. |
|
127 |
#' @slot max_overdose_prob (`proportion`)\cr maximum overdose posterior |
|
128 |
#' probability that is allowed, except 0 or 1. |
|
129 |
#' |
|
130 |
#' @aliases NextBestNCRM |
|
131 |
#' @export |
|
132 |
#' |
|
133 |
.NextBestNCRM <- setClass( |
|
134 |
Class = "NextBestNCRM", |
|
135 |
slots = c( |
|
136 |
target = "numeric", |
|
137 |
overdose = "numeric", |
|
138 |
max_overdose_prob = "numeric" |
|
139 |
), |
|
140 |
prototype = prototype( |
|
141 |
target = c(0.2, 0.35), |
|
142 |
overdose = c(0.35, 1), |
|
143 |
max_overdose_prob = 0.25 |
|
144 |
), |
|
145 |
contains = "NextBest", |
|
146 |
validity = v_next_best_ncrm |
|
147 |
) |
|
148 | ||
149 |
## constructor ---- |
|
150 | ||
151 |
#' @rdname NextBestNCRM-class |
|
152 |
#' |
|
153 |
#' @param target (`numeric`)\cr see slot definition. |
|
154 |
#' @param overdose (`numeric`)\cr see slot definition. |
|
155 |
#' @param max_overdose_prob (`proportion`)\cr see slot definition. |
|
156 |
#' @export |
|
157 |
#' @example examples/Rules-class-NextBestNCRM.R |
|
158 |
#' |
|
159 |
NextBestNCRM <- function(target, |
|
160 |
overdose, |
|
161 |
max_overdose_prob) { |
|
162 | 65x |
.NextBestNCRM( |
163 | 65x |
target = target, |
164 | 65x |
overdose = overdose, |
165 | 65x |
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(target = c(0.2, 0.35), overdose = c(0.35, 1), max_overdose_prob = 0.25) |
176 |
} |
|
177 | ||
178 |
# NextBestNCRMLoss ---- |
|
179 | ||
180 |
## class ---- |
|
181 | ||
182 |
#' `NextBestNCRMLoss` |
|
183 |
#' |
|
184 |
#' @description `r lifecycle::badge("stable")` |
|
185 |
#' |
|
186 |
#' [`NextBestNCRMLoss`] is the class based on NCRM rule and loss function. |
|
187 |
#' This class is similar to [`NextBestNCRM`] class, but differences are the |
|
188 |
#' addition of loss function and re-defined toxicity intervals, see each |
|
189 |
#' toxicity interval documentation and the note for details. As in NCRM rule, first admissible doses are found, |
|
190 |
#' which are those with probability to fall in overdose category being below |
|
191 |
#' `max_overdose_prob`. Next, within the admissible doses, the loss function is |
|
192 |
#' calculated, i.e. `losses` %*% `target`. Finally, the corresponding |
|
193 |
#' dose with lowest loss function (Bayes risk) is recommended for the next dose. |
|
194 |
#' |
|
195 |
#' @slot target (`numeric`)\cr the target toxicity interval (limits included). |
|
196 |
#' It has to be a probability range excluding 0 and 1. |
|
197 |
#' @slot overdose (`numeric`)\cr the overdose toxicity interval (lower limit |
|
198 |
#' excluded, upper limit included) or the excessive toxicity interval (lower |
|
199 |
#' limit excluded, upper limit included) if unacceptable is not provided. |
|
200 |
#' It has to be a probability range. It is used to filter probability samples. |
|
201 |
#' @slot unacceptable (`numeric`)\cr an unacceptable toxicity |
|
202 |
#' interval (lower limit excluded, upper limit included). This must be |
|
203 |
#' specified if the `overdose` does not include 1. Otherwise, it is `c(1, 1)` |
|
204 |
#' (default), which is essentially a scalar equals 1. It has to be a |
|
205 |
#' probability range. |
|
206 |
#' @slot losses (`numeric`)\cr a vector specifying the loss function. If the |
|
207 |
#' `unacceptable` is provided, the vector length must be 4, otherwise 3. |
|
208 |
#' |
|
209 |
#' @note The loss function should be a vector of either 3 or 4 values. |
|
210 |
#' This is because the loss function values must be specified for each |
|
211 |
#' interval, that is under-dosing, target toxicity, and overdosing toxicity or |
|
212 |
#' under-dosing, target toxicity, overdosing (excessive) toxicity, and |
|
213 |
#' unacceptable toxicity intervals. |
|
214 |
#' |
|
215 |
#' @aliases NextBestNCRMLoss |
|
216 |
#' @export |
|
217 |
#' |
|
218 |
.NextBestNCRMLoss <- setClass( |
|
219 |
Class = "NextBestNCRMLoss", |
|
220 |
slots = c( |
|
221 |
unacceptable = "numeric", |
|
222 |
losses = "numeric" |
|
223 |
), |
|
224 |
prototype = prototype( |
|
225 |
unacceptable = c(1, 1), |
|
226 |
losses = c(1, 0, 2) |
|
227 |
), |
|
228 |
contains = "NextBestNCRM", |
|
229 |
validity = v_next_best_ncrm_loss |
|
230 |
) |
|
231 | ||
232 |
## constructor ---- |
|
233 | ||
234 |
#' @rdname NextBestNCRMLoss-class |
|
235 |
#' |
|
236 |
#' @param target (`numeric`)\cr see slot definition. |
|
237 |
#' @param overdose (`numeric`)\cr see slot definition. |
|
238 |
#' @param unacceptable (`numeric`)\cr see slot definition. |
|
239 |
#' @param max_overdose_prob (`proportion`)\cr see slot definition in [`NextBestNCRM`]. |
|
240 |
#' @param losses (`numeric`)\cr see slot definition. |
|
241 |
#' |
|
242 |
#' @export |
|
243 |
#' @example examples/Rules-class-NextBestNCRMLoss.R |
|
244 |
#' |
|
245 |
NextBestNCRMLoss <- function(target, |
|
246 |
overdose, |
|
247 |
unacceptable = c(1, 1), |
|
248 |
max_overdose_prob, |
|
249 |
losses) { |
|
250 | 23x |
.NextBestNCRMLoss( |
251 | 23x |
target = target, |
252 | 23x |
overdose = overdose, |
253 | 23x |
unacceptable = unacceptable, |
254 | 23x |
max_overdose_prob = max_overdose_prob, |
255 | 23x |
losses = losses |
256 |
) |
|
257 |
} |
|
258 | ||
259 |
## default constructor ---- |
|
260 | ||
261 |
#' @rdname NextBestNCRMLoss-class |
|
262 |
#' @note Typically, end users will not use the `.DefaultNextBestnCRMLoss()` function. |
|
263 |
#' @export |
|
264 |
.DefaultNextBestNCRMLoss <- function() { |
|
265 | 10x |
NextBestNCRMLoss( |
266 | 10x |
target = c(0.2, 0.35), |
267 | 10x |
overdose = c(0.35, 0.6), |
268 | 10x |
unacceptable = c(0.6, 1), |
269 | 10x |
max_overdose_prob = 0.25, |
270 | 10x |
losses = c(1, 0, 1, 2) |
271 |
) |
|
272 |
} |
|
273 | ||
274 | ||
275 |
# NextBestThreePlusThree ---- |
|
276 | ||
277 |
## class ---- |
|
278 | ||
279 |
#' `NextBestThreePlusThree` |
|
280 |
#' |
|
281 |
#' @description `r lifecycle::badge("stable")` |
|
282 |
#' |
|
283 |
#' [`NextBestThreePlusThree`] is the class for next best dose that |
|
284 |
#' implements the classical 3+3 dose recommendation. No input is required, |
|
285 |
#' hence this class has no slots. |
|
286 |
#' |
|
287 |
#' @aliases NextBestThreePlusThree |
|
288 |
#' @export |
|
289 |
#' |
|
290 |
.NextBestThreePlusThree <- setClass( |
|
291 |
Class = "NextBestThreePlusThree", |
|
292 |
contains = "NextBest" |
|
293 |
) |
|
294 | ||
295 |
## constructor ---- |
|
296 | ||
297 |
#' @rdname NextBestThreePlusThree-class |
|
298 |
#' |
|
299 |
#' @export |
|
300 |
#' @examples |
|
301 |
#' # Next best dose class object using the classical 3+3 design. |
|
302 |
#' my_next_best <- NextBestThreePlusThree() |
|
303 |
NextBestThreePlusThree <- function() { |
|
304 | 32x |
.NextBestThreePlusThree() |
305 |
} |
|
306 | ||
307 |
## default constructor ---- |
|
308 | ||
309 |
#' @rdname NextBestThreePlusThree-class |
|
310 |
#' @note Typically, end users will not use the `.DefaultNextBestThreePlusThree()` function. |
|
311 |
#' @export |
|
312 |
.DefaultNextBestThreePlusThree <- function() { |
|
313 | 8x |
NextBestThreePlusThree() |
314 |
} |
|
315 | ||
316 |
# NextBestDualEndpoint ---- |
|
317 | ||
318 |
## class ---- |
|
319 | ||
320 |
#' `NextBestDualEndpoint` |
|
321 |
#' |
|
322 |
#' @description `r lifecycle::badge("experimental")` |
|
323 |
#' |
|
324 |
#' [`NextBestDualEndpoint`] is the class for next best dose that is based on the |
|
325 |
#' dual endpoint model. |
|
326 |
#' |
|
327 |
#' @details Under this rule, at first admissible doses are found, which are those |
|
328 |
#' with toxicity probability to fall in `overdose` category and being below |
|
329 |
#' `max_overdose_prob`. Next, it picks (from the remaining admissible doses) the |
|
330 |
#' one that maximizes the probability to be in the `target` biomarker range. By |
|
331 |
#' default (`target_relative = TRUE`) the target is specified as relative to the |
|
332 |
#' maximum biomarker level across the dose grid or relative to the `Emax` |
|
333 |
#' parameter in case a parametric model was selected (i.e. [`DualEndpointBeta`], |
|
334 |
#' [`DualEndpointEmax`]). However, if `target_relative = FALSE`, then the |
|
335 |
#' absolute biomarker range can be used as a target. |
|
336 |
#' |
|
337 |
#' @slot target (`numeric`)\cr the biomarker target range that needs to be |
|
338 |
#' reached. For example, the target range \eqn{(0.8, 1.0)} and |
|
339 |
#' `target_relative = TRUE` means that we target a dose with at least |
|
340 |
#' \eqn{80\%} of maximum biomarker level. As an other example, |
|
341 |
#' \eqn{(0.5, 0.8)} would mean that we target a dose between \eqn{50\%} and |
|
342 |
#' \eqn{80\%} of the maximum biomarker level. |
|
343 |
#' @slot overdose (`numeric`)\cr the overdose toxicity interval (lower limit |
|
344 |
#' excluded, upper limit included). |
|
345 |
#' @slot max_overdose_prob (`proportion`)\cr maximum overdose probability that |
|
346 |
#' is allowed. |
|
347 |
#' @slot target_relative (`flag`)\cr is `target` specified as relative? If |
|
348 |
#' `TRUE`, then the `target` is interpreted relative to the maximum, so it |
|
349 |
#' must be a probability range. Otherwise, the `target` is interpreted as |
|
350 |
#' absolute biomarker range. |
|
351 |
#' @slot target_thresh (`proportion`)\cr a target probability threshold that |
|
352 |
#' needs to be fulfilled before the target probability will be used for |
|
353 |
#' deriving the next best dose (default to 0.01). |
|
354 |
#' |
|
355 |
#' @aliases NextBestDualEndpoint |
|
356 |
#' @export |
|
357 |
#' |
|
358 |
.NextBestDualEndpoint <- setClass( |
|
359 |
Class = "NextBestDualEndpoint", |
|
360 |
slots = c( |
|
361 |
target = "numeric", |
|
362 |
overdose = "numeric", |
|
363 |
max_overdose_prob = "numeric", |
|
364 |
target_relative = "logical", |
|
365 |
target_thresh = "numeric" |
|
366 |
), |
|
367 |
prototype = prototype( |
|
368 |
target = c(0.9, 1), |
|
369 |
overdose = c(0.35, 1), |
|
370 |
max_overdose_prob = 0.25, |
|
371 |
target_relative = TRUE, |
|
372 |
target_thresh = 0.01 |
|
373 |
), |
|
374 |
contains = "NextBest", |
|
375 |
validity = v_next_best_dual_endpoint |
|
376 |
) |
|
377 | ||
378 |
## constructor ---- |
|
379 | ||
380 |
#' @rdname NextBestDualEndpoint-class |
|
381 |
#' |
|
382 |
#' @param target (`numeric`)\cr see slot definition. |
|
383 |
#' @param overdose (`numeric`)\cr see slot definition. |
|
384 |
#' @param max_overdose_prob (`proportion`)\cr see slot definition. |
|
385 |
#' @param target_relative (`flag`)\cr see slot definition. |
|
386 |
#' @param target_thresh (`proportion`)\cr see slot definition. |
|
387 |
#' |
|
388 |
#' @export |
|
389 |
#' @example examples/Rules-class-NextBestDualEndpoint.R |
|
390 |
#' |
|
391 |
NextBestDualEndpoint <- function(target, |
|
392 |
overdose, |
|
393 |
max_overdose_prob, |
|
394 |
target_relative = TRUE, |
|
395 |
target_thresh = 0.01) { |
|
396 | 35x |
.NextBestDualEndpoint( |
397 | 35x |
target = target, |
398 | 35x |
overdose = overdose, |
399 | 35x |
max_overdose_prob = max_overdose_prob, |
400 | 35x |
target_relative = target_relative, |
401 | 35x |
target_thresh = target_thresh |
402 |
) |
|
403 |
} |
|
404 | ||
405 |
## default constructor ---- |
|
406 | ||
407 |
#' @rdname NextBestDualEndpoint-class |
|
408 |
#' @note Typically, end users will not use the `.DefaultNextBestDualEndpoint()` function. |
|
409 |
#' @export |
|
410 |
.DefaultNextBestDualEndpoint <- function() { |
|
411 | 8x |
NextBestDualEndpoint( |
412 | 8x |
target = c(200, 300), |
413 | 8x |
overdose = c(0.35, 1), |
414 | 8x |
max_overdose_prob = 0.25, |
415 | 8x |
target_relative = FALSE |
416 |
) |
|
417 |
} |
|
418 | ||
419 |
# NextBestMinDist ---- |
|
420 | ||
421 |
## class ---- |
|
422 | ||
423 |
#' `NextBestMinDist` |
|
424 |
#' |
|
425 |
#' @description `r lifecycle::badge("stable")` |
|
426 |
#' |
|
427 |
#' [`NextBestMinDist`] is the class for next best dose that is based on minimum |
|
428 |
#' distance to target probability. |
|
429 |
#' |
|
430 |
#' @slot target (`proportion`)\cr single target toxicity probability, except |
|
431 |
#' 0 or 1. |
|
432 |
#' |
|
433 |
#' @aliases NextBestMinDist |
|
434 |
#' @export |
|
435 |
#' |
|
436 |
.NextBestMinDist <- setClass( |
|
437 |
Class = "NextBestMinDist", |
|
438 |
slots = c( |
|
439 |
target = "numeric" |
|
440 |
), |
|
441 |
prototype = prototype( |
|
442 |
target = 0.3 |
|
443 |
), |
|
444 |
contains = "NextBest", |
|
445 |
validity = v_next_best_min_dist |
|
446 |
) |
|
447 | ||
448 |
## constructor ---- |
|
449 | ||
450 |
#' @rdname NextBestMinDist-class |
|
451 |
#' |
|
452 |
#' @param target (`proportion`)\cr see slot definition. |
|
453 |
#' |
|
454 |
#' @export |
|
455 |
#' @example examples/Rules-class-NextBestMinDist.R |
|
456 |
#' |
|
457 |
NextBestMinDist <- function(target) { |
|
458 | 16x |
.NextBestMinDist(target = target) |
459 |
} |
|
460 | ||
461 |
## default constructor ---- |
|
462 | ||
463 |
#' @rdname NextBestMinDist-class |
|
464 |
#' @note Typically, end users will not use the `.DefaultNextBestMinDist()` function. |
|
465 |
#' @export |
|
466 |
.DefaultNextBestMinDist <- function() { |
|
467 | 8x |
NextBestMinDist(target = 0.3) |
468 |
} |
|
469 | ||
470 |
# NextBestInfTheory ---- |
|
471 | ||
472 |
## class ---- |
|
473 | ||
474 |
#' `NextBestInfTheory` |
|
475 |
#' |
|
476 |
#' @description `r lifecycle::badge("stable")` |
|
477 |
#' |
|
478 |
#' [`NextBestInfTheory`] is the class for next best dose that is based on |
|
479 |
#' information theory as proposed in https://doi.org/10.1002/sim.8450. |
|
480 |
#' |
|
481 |
#' @slot target (`proportion`)\cr target toxicity probability, except 0 or 1. |
|
482 |
#' @slot asymmetry (`number`)\cr value of the asymmetry exponent in the |
|
483 |
#' divergence function that describes the rate of penalization for overly |
|
484 |
#' toxic does. It must be a value from \eqn{(0, 2)} interval. |
|
485 |
#' |
|
486 |
#' @aliases NextBestInfTheory |
|
487 |
#' @export |
|
488 |
#' |
|
489 |
.NextBestInfTheory <- setClass( |
|
490 |
Class = "NextBestInfTheory", |
|
491 |
slots = c( |
|
492 |
target = "numeric", |
|
493 |
asymmetry = "numeric" |
|
494 |
), |
|
495 |
prototype = prototype( |
|
496 |
target = 0.3, |
|
497 |
asymmetry = 1 |
|
498 |
), |
|
499 |
contains = "NextBest", |
|
500 |
validity = v_next_best_inf_theory |
|
501 |
) |
|
502 | ||
503 |
## constructor ---- |
|
504 | ||
505 |
#' @rdname NextBestInfTheory-class |
|
506 |
#' |
|
507 |
#' @param target (`proportion`)\cr see slot definition. |
|
508 |
#' @param asymmetry (`number`)\cr see slot definition. |
|
509 |
#' |
|
510 |
#' @export |
|
511 |
#' |
|
512 |
NextBestInfTheory <- function(target, asymmetry) { |
|
513 | 16x |
.NextBestInfTheory(target = target, asymmetry = asymmetry) |
514 |
} |
|
515 | ||
516 |
## default constructor ---- |
|
517 | ||
518 |
#' @rdname NextBestInfTheory-class |
|
519 |
#' @note Typically, end users will not use the `.DefaultNextBestInfTheory()` function. |
|
520 |
#' @export |
|
521 |
.DefaultNextBestInfTheory <- function() { |
|
522 | 8x |
NextBestInfTheory(0.33, 1.2) |
523 |
} |
|
524 | ||
525 |
# NextBestTD ---- |
|
526 | ||
527 |
## class ---- |
|
528 | ||
529 |
#' `NextBestTD` |
|
530 |
#' |
|
531 |
#' @description `r lifecycle::badge("stable")` |
|
532 |
#' |
|
533 |
#' [`NextBestTD`] is the class to find a next best dose based on pseudo |
|
534 |
#' DLT model without samples. Namely, it is to find two next best doses, one |
|
535 |
#' for allocation during the trial and the second for final recommendation at |
|
536 |
#' the end of a trial without involving any samples, i.e. only DLT responses |
|
537 |
#' will be incorporated for the dose-allocation. This is based solely on the |
|
538 |
#' probabilities of the occurrence of a DLT obtained by using the modal estimates |
|
539 |
#' of the model parameters. There are two target probabilities of the |
|
540 |
#' occurrence of a DLT that must be specified: target probability to be used |
|
541 |
#' during the trial and target probability to be used at the end of the trial. |
|
542 |
#' It is suitable to use it only with the [`ModelTox`] model class. |
|
543 |
#' |
|
544 |
#' @slot prob_target_drt (`proportion`)\cr the target probability (except 0 or 1) |
|
545 |
#' of the occurrence of a DLT to be used during the trial. |
|
546 |
#' @slot prob_target_eot (`proportion`)\cr the target probability (except 0 or 1) |
|
547 |
#' of the occurrence of a DLT to be used at the end of the trial. |
|
548 |
#' |
|
549 |
#' @aliases NextBestTD |
|
550 |
#' @export |
|
551 |
#' |
|
552 |
.NextBestTD <- setClass( |
|
553 |
Class = "NextBestTD", |
|
554 |
slots = c( |
|
555 |
prob_target_drt = "numeric", |
|
556 |
prob_target_eot = "numeric" |
|
557 |
), |
|
558 |
prototype = prototype( |
|
559 |
prob_target_drt = 0.35, |
|
560 |
prob_target_eot = 0.3 |
|
561 |
), |
|
562 |
contains = "NextBest", |
|
563 |
validity = v_next_best_td |
|
564 |
) |
|
565 | ||
566 |
## default constructor ---- |
|
567 | ||
568 |
#' @rdname NextBestTD-class |
|
569 |
#' @note Typically, end users will not use the `.DefaultNextBestTD()` function. |
|
570 |
#' @export |
|
571 |
.DefaultNextBestTD <- function() { |
|
572 | 8x |
NextBestTD(0.35, 0.3) |
573 |
} |
|
574 | ||
575 |
## constructor ---- |
|
576 | ||
577 |
#' @rdname NextBestTD-class |
|
578 |
#' |
|
579 |
#' @param prob_target_drt (`proportion`)\cr see slot definition. |
|
580 |
#' @param prob_target_eot (`proportion`)\cr see slot definition. |
|
581 |
#' |
|
582 |
#' @export |
|
583 |
#' @examples |
|
584 |
#' my_next_best <- NextBestTD(0.35, 0.3) |
|
585 |
NextBestTD <- function(prob_target_drt, prob_target_eot) { |
|
586 | 24x |
.NextBestTD( |
587 | 24x |
prob_target_drt = prob_target_drt, |
588 | 24x |
prob_target_eot = prob_target_eot |
589 |
) |
|
590 |
} |
|
591 | ||
592 |
# NextBestTDsamples ---- |
|
593 | ||
594 |
## class ---- |
|
595 | ||
596 |
#' `NextBestTDsamples` |
|
597 |
#' |
|
598 |
#' @description `r lifecycle::badge("stable")` |
|
599 |
#' |
|
600 |
#' [`NextBestTDsamples`] is the class to find a next best dose based on Pseudo |
|
601 |
#' DLT model with samples. Namely, it is to find two next best doses, one |
|
602 |
#' for allocation during the trial and the second for final recommendation at |
|
603 |
#' the end of a trial. Hence, there are two target probabilities of the |
|
604 |
#' occurrence of a DLT that must be specified: target probability to be used |
|
605 |
#' during the trial and target probability to be used at the end of the trial. |
|
606 |
#' |
|
607 |
#' @slot derive (`function`)\cr derives, based on a vector of posterior dose |
|
608 |
#' samples, the target dose that has the probability of the occurrence of |
|
609 |
#' DLT equals to either the `prob_target_drt` or `prob_target_eot`. It must |
|
610 |
#' therefore accept one and only one argument, which is a numeric vector, and |
|
611 |
#' return a number. |
|
612 |
#' |
|
613 |
#' @aliases NextBestTDsamples |
|
614 |
#' @export |
|
615 |
#' |
|
616 |
.NextBestTDsamples <- setClass( |
|
617 |
Class = "NextBestTDsamples", |
|
618 |
slots = c( |
|
619 |
derive = "function" |
|
620 |
), |
|
621 |
prototype = prototype( |
|
622 |
derive = function(dose_samples) { |
|
623 |
quantile(dose_samples, prob = 0.3) |
|
624 |
} |
|
625 |
), |
|
626 |
contains = "NextBestTD", |
|
627 |
validity = v_next_best_td_samples |
|
628 |
) |
|
629 | ||
630 |
## constructor ---- |
|
631 | ||
632 |
#' @rdname NextBestTDsamples-class |
|
633 |
#' |
|
634 |
#' @param prob_target_drt (`proportion`)\cr see slot definition in [`NextBestTD`]. |
|
635 |
#' @param prob_target_eot (`proportion`)\cr see slot definition in [`NextBestTD`]. |
|
636 |
#' @param derive (`function`)\cr see slot definition. |
|
637 |
#' |
|
638 |
#' @export |
|
639 |
#' @example examples/Rules-class-NextBestTDsamples.R |
|
640 |
#' |
|
641 |
NextBestTDsamples <- function(prob_target_drt, prob_target_eot, derive) { |
|
642 | 25x |
.NextBestTDsamples( |
643 | 25x |
prob_target_drt = prob_target_drt, |
644 | 25x |
prob_target_eot = prob_target_eot, |
645 | 25x |
derive = derive |
646 |
) |
|
647 |
} |
|
648 | ||
649 |
## default constructor ---- |
|
650 | ||
651 |
#' @rdname NextBestTDsamples-class |
|
652 |
#' @note Typically, end users will not use the `.DefaultNextBestTDsamples()` function. |
|
653 |
#' @export |
|
654 |
.DefaultNextBestTDsamples <- function() { |
|
655 | 8x |
NextBestTDsamples( |
656 | 8x |
prob_target_drt = 0.35, |
657 | 8x |
prob_target_eot = 0.3, |
658 | 8x |
derive = function(samples) { |
659 | 9x |
as.numeric(quantile(samples, probs = 0.3)) |
660 |
} |
|
661 |
) |
|
662 |
} |
|
663 | ||
664 | ||
665 |
# NextBestMaxGain ---- |
|
666 | ||
667 |
## class ---- |
|
668 | ||
669 |
#' `NextBestMaxGain` |
|
670 |
#' |
|
671 |
#' @description `r lifecycle::badge("stable")` |
|
672 |
#' |
|
673 |
#' [`NextBestMaxGain`] is the class to find a next best dose with maximum gain |
|
674 |
#' value based on a pseudo DLT and efficacy models without samples. It is based |
|
675 |
#' solely on the probabilities of the occurrence of a DLT and the values |
|
676 |
#' of the mean efficacy responses obtained by using the modal estimates of the |
|
677 |
#' DLT and efficacy model parameters. There are two target probabilities of the |
|
678 |
#' occurrence of a DLT that must be specified: target probability to be used |
|
679 |
#' during the trial and target probability to be used at the end of the trial. |
|
680 |
#' It is suitable to use it only with the [`ModelTox`] model and [`ModelEff`] |
|
681 |
#' classes (except [`EffFlexi`]). |
|
682 |
#' |
|
683 |
#' @slot prob_target_drt (`proportion`)\cr the target probability of the |
|
684 |
#' occurrence of a DLT to be used during the trial. |
|
685 |
#' @slot prob_target_eot (`proportion`)\cr the target probability of the |
|
686 |
#' occurrence of a DLT to be used at the end of the trial. |
|
687 |
#' |
|
688 |
#' @aliases NextBestMaxGain |
|
689 |
#' @export |
|
690 |
#' |
|
691 |
.NextBestMaxGain <- setClass( |
|
692 |
Class = "NextBestMaxGain", |
|
693 |
slots = c( |
|
694 |
prob_target_drt = "numeric", |
|
695 |
prob_target_eot = "numeric" |
|
696 |
), |
|
697 |
prototype = prototype( |
|
698 |
prob_target_drt = 0.35, |
|
699 |
prob_target_eot = 0.3 |
|
700 |
), |
|
701 |
contains = "NextBest", |
|
702 |
validity = v_next_best_td |
|
703 |
) |
|
704 | ||
705 |
## constructor ---- |
|
706 | ||
707 |
#' @rdname NextBestMaxGain-class |
|
708 |
#' |
|
709 |
#' @param prob_target_drt (`proportion`)\cr see slot definition. |
|
710 |
#' @param prob_target_eot (`proportion`)\cr see slot definition. |
|
711 |
#' |
|
712 |
#' @export |
|
713 |
#' @examples |
|
714 |
#' my_next_best <- NextBestMaxGain(0.35, 0.3) |
|
715 |
NextBestMaxGain <- function(prob_target_drt, prob_target_eot) { |
|
716 | 20x |
.NextBestMaxGain( |
717 | 20x |
prob_target_drt = prob_target_drt, |
718 | 20x |
prob_target_eot = prob_target_eot |
719 |
) |
|
720 |
} |
|
721 | ||
722 |
## default constructor ---- |
|
723 | ||
724 |
#' @rdname NextBestMaxGain-class |
|
725 |
#' @note Typically, end users will not use the `.DefaultNextBestMaxGain()` function. |
|
726 |
#' @export |
|
727 |
.DefaultNextBestMaxGain <- function() { |
|
728 | 8x |
NextBestMaxGain(0.35, 0.3) |
729 |
} |
|
730 | ||
731 |
# NextBestMaxGainSamples ---- |
|
732 | ||
733 |
## class ---- |
|
734 | ||
735 |
#' `NextBestMaxGainSamples` |
|
736 |
#' |
|
737 |
#' @description `r lifecycle::badge("stable")` |
|
738 |
#' |
|
739 |
#' [`NextBestMaxGainSamples`] is the class to find a next best dose with maximum |
|
740 |
#' gain value based on a pseudo DLT and efficacy models and DLT and efficacy |
|
741 |
#' samples. There are two target probabilities of the occurrence of a DLT that |
|
742 |
#' must be specified: target probability to be used during the trial and target |
|
743 |
#' probability to be used at the end of the trial. |
|
744 |
#' It is suitable to use it only with the [`ModelTox`] model and [`ModelEff`] |
|
745 |
#' classes. |
|
746 |
#' |
|
747 |
#' @slot derive (`function`)\cr derives, based on a vector of posterior dose |
|
748 |
#' samples, the target dose that has the probability of the occurrence of |
|
749 |
#' DLT equals to either the `prob_target_drt` or `prob_target_eot`. It must |
|
750 |
#' therefore accept one and only one argument, which is a numeric vector, and |
|
751 |
#' return a number. |
|
752 |
#' @slot mg_derive (`function`)\cr derives, based on a vector of posterior dose |
|
753 |
#' samples that give the maximum gain value, the final next best estimate of |
|
754 |
#' the dose that gives the maximum gain value. It must therefore accept one |
|
755 |
#' and only one argument, which is a numeric vector, and return a number. |
|
756 |
#' |
|
757 |
#' @aliases NextBestMaxGainSamples |
|
758 |
#' @export |
|
759 |
#' |
|
760 |
.NextBestMaxGainSamples <- setClass( |
|
761 |
Class = "NextBestMaxGainSamples", |
|
762 |
slots = c( |
|
763 |
derive = "function", |
|
764 |
mg_derive = "function" |
|
765 |
), |
|
766 |
prototype = prototype( |
|
767 |
prob_target_drt = 0.35, |
|
768 |
prob_target_eot = 0.3, |
|
769 |
derive = function(dose_samples) { |
|
770 |
as.numeric(quantile(dose_samples, prob = 0.3)) |
|
771 |
}, |
|
772 |
mg_derive = function(dose_samples) { |
|
773 |
as.numeric(quantile(dose_samples, prob = 0.5)) |
|
774 |
} |
|
775 |
), |
|
776 |
contains = "NextBestMaxGain", |
|
777 |
validity = v_next_best_max_gain_samples |
|
778 |
) |
|
779 | ||
780 |
## constructor ---- |
|
781 | ||
782 |
#' @rdname NextBestMaxGainSamples-class |
|
783 |
#' |
|
784 |
#' @param prob_target_drt (`proportion`)\cr see slot definition in [`NextBestMaxGain`]. |
|
785 |
#' @param prob_target_eot (`proportion`)\cr see slot definition in [`NextBestMaxGain`]. |
|
786 |
#' @param derive (`function`)\cr see slot definition. |
|
787 |
#' @param mg_derive (`function`)\cr see slot definition. |
|
788 |
#' |
|
789 |
#' @export |
|
790 |
#' @example examples/Rules-class-NextBestMaxGainSamples.R |
|
791 |
#' |
|
792 |
NextBestMaxGainSamples <- function(prob_target_drt, |
|
793 |
prob_target_eot, |
|
794 |
derive, |
|
795 |
mg_derive) { |
|
796 | 23x |
.NextBestMaxGainSamples( |
797 | 23x |
prob_target_drt = prob_target_drt, |
798 | 23x |
prob_target_eot = prob_target_eot, |
799 | 23x |
derive = derive, |
800 | 23x |
mg_derive = mg_derive |
801 |
) |
|
802 |
} |
|
803 | ||
804 |
## default constructor ---- |
|
805 | ||
806 |
#' @rdname NextBestMaxGainSamples-class |
|
807 |
#' @note Typically, end users will not use the `.DefaultNextBestMaxGainSamples()` function. |
|
808 |
#' @export |
|
809 |
.DefaultNextBestMaxGainSamples <- function() { |
|
810 | 8x |
NextBestMaxGainSamples( |
811 | 8x |
prob_target_drt = 0.35, |
812 | 8x |
prob_target_eot = 0.3, |
813 | 8x |
derive = function(samples) { |
814 | 10x |
as.numeric(quantile(samples, prob = 0.3)) |
815 |
}, |
|
816 | 8x |
mg_derive = function(mg_samples) { |
817 | 10x |
as.numeric(quantile(mg_samples, prob = 0.5)) |
818 |
} |
|
819 |
) |
|
820 |
} |
|
821 | ||
822 |
# NextBestProbMTDLTE ---- |
|
823 | ||
824 |
## class ---- |
|
825 | ||
826 |
#' `NextBestProbMTDLTE` |
|
827 |
#' |
|
828 |
#' @description `r lifecycle::badge("experimental")` |
|
829 |
#' |
|
830 |
#' [`NextBestProbMTDLTE`] is the class of finding a next best dose that selects |
|
831 |
#' the dose with the highest probability of having a toxicity rate less or equal |
|
832 |
#' to the toxicity target. |
|
833 |
#' The dose is determined by calculating the posterior toxicity probability |
|
834 |
#' for each dose per iteration and select the maximum dose that has a toxicity |
|
835 |
#' probability below or equal to the target. The dose with the highest frequency |
|
836 |
#' of being selected as MTD across iterations is the next best dose. Placebo |
|
837 |
#' is not considered in the calculation and removed from the dose grid for |
|
838 |
#' any calculations. |
|
839 |
#' |
|
840 |
#' @slot target (`numeric`)\cr the target toxicity probability. |
|
841 |
#' |
|
842 |
#' @aliases NextBestProbMTDLTE |
|
843 |
#' @export |
|
844 |
#' |
|
845 |
.NextBestProbMTDLTE <- setClass( |
|
846 |
Class = "NextBestProbMTDLTE", |
|
847 |
slots = c(target = "numeric"), |
|
848 |
prototype = prototype(target = 0.3), |
|
849 |
contains = "NextBest", |
|
850 |
validity = v_next_best_prob_mtd_lte |
|
851 |
) |
|
852 | ||
853 |
## constructor ---- |
|
854 | ||
855 |
#' @rdname NextBestProbMTDLTE-class |
|
856 |
#' |
|
857 |
#' @param target (`numeric`)\cr see slot definition. |
|
858 |
#' @export |
|
859 |
#' @example examples/Rules-class-NextBestProbMTDLTE.R |
|
860 |
#' |
|
861 |
NextBestProbMTDLTE <- function(target) { |
|
862 | 14x |
.NextBestProbMTDLTE(target = target) |
863 |
} |
|
864 | ||
865 |
## default constructor ---- |
|
866 | ||
867 |
#' @rdname NextBestProbMTDLTE-class |
|
868 |
#' @note Typically, end users will not use the `.DefaultNextBestProbMTDLTE()` function. |
|
869 |
#' @export |
|
870 |
.DefaultNextBestProbMTDLTE <- function() { |
|
871 | 8x |
NextBestProbMTDLTE(target = 0.3) |
872 |
} |
|
873 | ||
874 |
# NextBestProbMTDMinDist ---- |
|
875 | ||
876 |
## class ---- |
|
877 | ||
878 |
#' `NextBestProbMTDMinDist` |
|
879 |
#' |
|
880 |
#' @description `r lifecycle::badge("experimental")` |
|
881 |
#' |
|
882 |
#' [`NextBestProbMTDMinDist`] is the class of finding a next best dose that selects |
|
883 |
#' the dose with the highest probability of having a toxicity rate with the |
|
884 |
#' smallest distance to the toxicity target. |
|
885 |
#' The dose is determined by calculating the posterior toxicity probability |
|
886 |
#' for each dose per iteration and select the dose that has the smallest toxicity |
|
887 |
#' probability distance to the target. The dose with the highest frequency |
|
888 |
#' of being selected as MTD across iterations is the next best dose. Placebo |
|
889 |
#' is not considered as the next dose and for that reason not used in |
|
890 |
#' calculations. I.e. for placebo the toxicity probability distance to target |
|
891 |
#' is not calculated and taken into account for determination of the next dose. |
|
892 |
#' |
|
893 |
#' @slot target (`numeric`)\cr the target toxicity probability. |
|
894 |
#' |
|
895 |
#' @aliases NextBestProbMTDMinDist |
|
896 |
#' @export |
|
897 |
#' |
|
898 |
.NextBestProbMTDMinDist <- setClass( |
|
899 |
Class = "NextBestProbMTDMinDist", |
|
900 |
slots = c(target = "numeric"), |
|
901 |
prototype = prototype(target = 0.3), |
|
902 |
contains = "NextBest", |
|
903 |
validity = v_next_best_prob_mtd_min_dist |
|
904 |
) |
|
905 | ||
906 |
## constructor ---- |
|
907 | ||
908 |
#' @rdname NextBestProbMTDMinDist-class |
|
909 |
#' |
|
910 |
#' @param target (`numeric`)\cr see slot definition. |
|
911 |
#' @export |
|
912 |
#' @example examples/Rules-class-NextBestProbMTDMinDist.R |
|
913 |
#' |
|
914 |
NextBestProbMTDMinDist <- function(target) { |
|
915 | 14x |
.NextBestProbMTDMinDist(target = target) |
916 |
} |
|
917 | ||
918 |
## default constructor ---- |
|
919 | ||
920 |
#' @rdname NextBestProbMTDMinDist-class |
|
921 |
#' @note Typically, end users will not use the `.DefaultNextBestProbMTDMinDist()` function. |
|
922 |
#' @export |
|
923 |
.DefaultNextBestProbMTDMinDist <- function() { |
|
924 | 8x |
NextBestProbMTDMinDist(target = 0.3) |
925 |
} |
|
926 | ||
927 |
# NextBestOrdinal ---- |
|
928 | ||
929 |
## class ---- |
|
930 | ||
931 |
#' `NextBestOrdinal` |
|
932 |
#' |
|
933 |
#' @description `r lifecycle::badge("experimental")` |
|
934 |
#' |
|
935 |
#' [`NextBestOrdinal`] is the class for applying a standard `NextBest` rule to |
|
936 |
#' the results of an ordinal CRM trial. |
|
937 |
#' |
|
938 |
#' @slot grade (`integer`)\cr the toxicity grade to which the `rule` should be |
|
939 |
#' applied. |
|
940 |
#' @slot rule (`NextBest`)\cr the standard `NextBest` rule to be applied |
|
941 |
#' |
|
942 |
#' @aliases NextBestOrdinal |
|
943 |
#' @export |
|
944 |
#' |
|
945 |
.NextBestOrdinal <- setClass( |
|
946 |
Class = "NextBestOrdinal", |
|
947 |
slots = c(grade = "numeric", rule = "NextBest"), |
|
948 |
contains = "NextBest", |
|
949 |
validity = v_next_best_ordinal |
|
950 |
) |
|
951 | ||
952 |
## constructor ---- |
|
953 | ||
954 |
#' @rdname NextBestOrdinal-class |
|
955 |
#' |
|
956 |
#' @param grade (`numeric`)\cr see slot definition. |
|
957 |
#' @param rule (`NextBest`)\cr see slot definition. |
|
958 |
#' @export |
|
959 |
#' @example examples/Rules-class-NextBestOrdinal.R |
|
960 |
#' |
|
961 |
NextBestOrdinal <- function(grade, rule) { |
|
962 | 32x |
.NextBestOrdinal(grade = grade, rule = rule) |
963 |
} |
|
964 | ||
965 |
## default constructor ---- |
|
966 | ||
967 |
#' @rdname NextBestOrdinal-class |
|
968 |
#' @note Typically, end users will not use the `.DefaultNextBestOrdinal()` function. |
|
969 |
#' @export |
|
970 |
.DefaultNextBestOrdinal <- function() { |
|
971 | 12x |
NextBestOrdinal( |
972 | 12x |
grade = 1L, |
973 | 12x |
rule = NextBestMTD( |
974 | 12x |
0.25, |
975 | 12x |
function(mtd_samples) { |
976 | 14x |
quantile(mtd_samples, probs = 0.25) |
977 |
} |
|
978 |
) |
|
979 |
) |
|
980 |
} |
|
981 | ||
982 |
# Increments ---- |
|
983 | ||
984 |
## class ---- |
|
985 | ||
986 |
#' `Increments` |
|
987 |
#' |
|
988 |
#' @description `r lifecycle::badge("stable")` |
|
989 |
#' |
|
990 |
#' [`Increments`] is a virtual class for controlling increments, from which all |
|
991 |
#' other specific increments classes inherit. |
|
992 |
#' |
|
993 |
#' @seealso [`IncrementsRelative`], [`IncrementsRelativeDLT`], |
|
994 |
#' [`IncrementsDoseLevels`], [`IncrementsHSRBeta`], [`IncrementsMin`]. |
|
995 |
#' |
|
996 |
#' @aliases Increments |
|
997 |
#' @export |
|
998 |
#' |
|
999 |
setClass( |
|
1000 |
Class = "Increments", |
|
1001 |
contains = "CrmPackClass" |
|
1002 |
) |
|
1003 | ||
1004 |
## default constructor ---- |
|
1005 | ||
1006 |
#' @rdname Increments-class |
|
1007 |
#' @note Typically, end users will not use the `.DefaultIncrements()` function. |
|
1008 |
#' @export |
|
1009 |
.DefaultIncrements <- function() { |
|
1010 | 2x |
stop(paste0("Class Increments cannot be instantiated directly. Please use one of its subclasses instead.")) |
1011 |
} |
|
1012 | ||
1013 | ||
1014 |
# IncrementsRelative ---- |
|
1015 | ||
1016 |
## class ---- |
|
1017 | ||
1018 |
#' `IncrementsRelative` |
|
1019 |
#' |
|
1020 |
#' @description `r lifecycle::badge("stable")` |
|
1021 |
#' |
|
1022 |
#' [`IncrementsRelative`] is the class for increments control based on relative |
|
1023 |
#' differences in intervals. |
|
1024 |
#' |
|
1025 |
#' @slot intervals (`numeric`)\cr a vector with the left bounds of the relevant |
|
1026 |
#' intervals. For example, `intervals = c(0, 50, 100)` specifies three intervals: |
|
1027 |
#' \eqn{(0, 50)}, \eqn{[50, 100)} and \eqn{[100, +Inf)}. That means, the right |
|
1028 |
#' bound of the intervals are exclusive to the interval and the last interval |
|
1029 |
#' goes from the last value to infinity. |
|
1030 |
#' @slot increments (`numeric`)\cr a vector of the same length with the maximum |
|
1031 |
#' allowable relative increments in the `intervals`. |
|
1032 |
#' |
|
1033 |
#' @aliases IncrementsRelative |
|
1034 |
#' @export |
|
1035 |
#' |
|
1036 |
.IncrementsRelative <- setClass( |
|
1037 |
Class = "IncrementsRelative", |
|
1038 |
slots = c( |
|
1039 |
intervals = "numeric", |
|
1040 |
increments = "numeric" |
|
1041 |
), |
|
1042 |
prototype = prototype( |
|
1043 |
intervals = c(0, 2), |
|
1044 |
increments = c(2, 1) |
|
1045 |
), |
|
1046 |
contains = "Increments", |
|
1047 |
validity = v_increments_relative |
|
1048 |
) |
|
1049 | ||
1050 |
## constructor ---- |
|
1051 | ||
1052 |
#' @rdname IncrementsRelative-class |
|
1053 |
#' |
|
1054 |
#' @param intervals (`numeric`)\cr see slot definition. |
|
1055 |
#' @param increments (`numeric`)\cr see slot definition. |
|
1056 |
#' |
|
1057 |
#' @export |
|
1058 |
#' @example examples/Rules-class-IncrementsRelative.R |
|
1059 |
#' |
|
1060 |
IncrementsRelative <- function(intervals, increments) { |
|
1061 | 132x |
.IncrementsRelative( |
1062 | 132x |
intervals = intervals, |
1063 | 132x |
increments = increments |
1064 |
) |
|
1065 |
} |
|
1066 | ||
1067 |
## default constructor ---- |
|
1068 | ||
1069 |
#' @rdname IncrementsRelative-class |
|
1070 |
#' @note Typically, end users will not use the `.DefaultIncrementsRelative()` function. |
|
1071 |
#' @export |
|
1072 |
.DefaultIncrementsRelative <- function() { |
|
1073 | 9x |
IncrementsRelative(intervals = c(0, 20), increments = c(1, 0.33)) |
1074 |
} |
|
1075 | ||
1076 | ||
1077 |
# IncrementsRelativeDLT ---- |
|
1078 | ||
1079 |
## class ---- |
|
1080 | ||
1081 |
#' `IncrementsRelativeDLT` |
|
1082 |
#' |
|
1083 |
#' @description `r lifecycle::badge("stable")` |
|
1084 |
#' |
|
1085 |
#' [`IncrementsRelativeDLT`] is the class for increments control based on |
|
1086 |
#' relative differences in terms of DLTs. |
|
1087 |
#' |
|
1088 |
#' @slot intervals (`integer`)\cr a vector with the left bounds of the |
|
1089 |
#' relevant DLT intervals. For example, `intervals = c(0, 1, 3)` specifies |
|
1090 |
#' three intervals (sets of DLTs: first, 0 DLT; second 1 or 2 DLTs; and the third |
|
1091 |
#' one, at least 3 DLTs. That means, the right bound of the intervals are |
|
1092 |
#' exclusive to the interval and the last interval goes from the last value to |
|
1093 |
#' infinity. |
|
1094 |
#' @slot increments (`numeric`)\cr a vector of maximum allowable relative |
|
1095 |
#' increments corresponding to `intervals`. IT must be of the same length |
|
1096 |
#' as the length of `intervals`. |
|
1097 |
#' |
|
1098 |
#' @note This considers all DLTs across all cohorts observed so far. |
|
1099 |
#' |
|
1100 |
#' @seealso [IncrementsRelativeDLTCurrent] which only considers the DLTs |
|
1101 |
#' in the current cohort. |
|
1102 |
#' |
|
1103 |
#' @aliases IncrementsRelativeDLT |
|
1104 |
#' @export |
|
1105 |
#' |
|
1106 |
.IncrementsRelativeDLT <- setClass( |
|
1107 |
Class = "IncrementsRelativeDLT", |
|
1108 |
slots = representation( |
|
1109 |
intervals = "integer", |
|
1110 |
increments = "numeric" |
|
1111 |
), |
|
1112 |
prototype = prototype( |
|
1113 |
intervals = c(0L, 1L), |
|
1114 |
increments = c(2, 1) |
|
1115 |
), |
|
1116 |
contains = "Increments", |
|
1117 |
validity = v_increments_relative_dlt |
|
1118 |
) |
|
1119 | ||
1120 |
## constructor ---- |
|
1121 | ||
1122 |
#' @rdname IncrementsRelativeDLT-class |
|
1123 |
#' |
|
1124 |
#' @param intervals (`numeric`)\cr see slot definition. |
|
1125 |
#' @param increments (`numeric`)\cr see slot definition. |
|
1126 |
#' |
|
1127 |
#' @export |
|
1128 |
#' @example examples/Rules-class-IncrementsRelativeDLT.R |
|
1129 |
#' |
|
1130 |
IncrementsRelativeDLT <- function(intervals, increments) { |
|
1131 | 35x |
assert_integerish(intervals, lower = 0, any.missing = FALSE) |
1132 | 35x |
assert_numeric(increments, any.missing = FALSE, lower = 0) |
1133 | ||
1134 | 35x |
.IncrementsRelativeDLT( |
1135 | 35x |
intervals = as.integer(intervals), |
1136 | 35x |
increments = increments |
1137 |
) |
|
1138 |
} |
|
1139 | ||
1140 |
## default constructor ---- |
|
1141 | ||
1142 |
#' @rdname IncrementsRelativeDLT-class |
|
1143 |
#' @note Typically, end users will not use the `.DefaultIncrementsRelativeDLT()` function. |
|
1144 |
#' @export |
|
1145 |
.DefaultIncrementsRelativeDLT <- function() { |
|
1146 | 8x |
IncrementsRelativeDLT(intervals = c(0L, 1L, 3L), increments = c(1, 0.33, 0.2)) |
1147 |
} |
|
1148 | ||
1149 |
# IncrementsRelativeDLTCurrent ---- |
|
1150 | ||
1151 |
## class ---- |
|
1152 | ||
1153 |
#' `IncrementsRelativeDLTCurrent` |
|
1154 |
#' |
|
1155 |
#' @description `r lifecycle::badge("experimental")` |
|
1156 |
#' |
|
1157 |
#' [`IncrementsRelativeDLTCurrent`] is the class for increments control based on |
|
1158 |
#' relative differences and current DLTs. The class is based on the number of |
|
1159 |
#' DLTs observed in the current cohort, but not cumulatively over all cohorts |
|
1160 |
#' so far. |
|
1161 |
#' |
|
1162 |
#' @seealso [IncrementsRelativeDLT]. |
|
1163 |
#' |
|
1164 |
#' @aliases IncrementsRelativeDLTCurrent |
|
1165 |
#' @export |
|
1166 |
#' |
|
1167 |
.IncrementsRelativeDLTCurrent <- setClass( |
|
1168 |
Class = "IncrementsRelativeDLTCurrent", |
|
1169 |
contains = "IncrementsRelativeDLT" |
|
1170 |
) |
|
1171 | ||
1172 |
## constructor ---- |
|
1173 | ||
1174 |
#' @rdname IncrementsRelativeDLTCurrent-class |
|
1175 |
#' |
|
1176 |
#' @inheritParams IncrementsRelativeDLT |
|
1177 |
#' |
|
1178 |
#' @export |
|
1179 |
#' @example examples/Rules-class-IncrementsRelativeDLTCurrent.R |
|
1180 |
#' |
|
1181 |
IncrementsRelativeDLTCurrent <- function(intervals = c(0L, 1L), |
|
1182 |
increments = c(2L, 1L)) { |
|
1183 | 17x |
assert_integerish(intervals, lower = 0, any.missing = FALSE) |
1184 | 17x |
assert_numeric(increments, any.missing = FALSE, lower = 0) |
1185 | ||
1186 | 17x |
.IncrementsRelativeDLTCurrent( |
1187 | 17x |
intervals = as.integer(intervals), |
1188 | 17x |
increments = increments |
1189 |
) |
|
1190 |
} |
|
1191 | ||
1192 |
## default constructor ---- |
|
1193 | ||
1194 |
#' @rdname IncrementsRelativeDLTCurrent-class |
|
1195 |
#' @note Typically, end users will not use the `.DefaultIncrementsRelativeDLTCurrent()` function. |
|
1196 |
#' @export |
|
1197 |
.DefaultIncrementsRelativeDLTCurrent <- function() { # nolint |
|
1198 | 7x |
IncrementsRelativeDLTCurrent(intervals = c(0L, 1L, 3L), increments = c(1, 0.33, 0.2)) |
1199 |
} |
|
1200 | ||
1201 |
# IncrementsRelativeParts ---- |
|
1202 | ||
1203 |
## class ---- |
|
1204 | ||
1205 |
#' `IncrementsRelativeParts` |
|
1206 |
#' |
|
1207 |
#' @description `r lifecycle::badge("stable")` |
|
1208 |
#' |
|
1209 |
#' [`IncrementsRelativeParts`] is the class for increments control based on |
|
1210 |
#' relative differences in intervals, with special rules for part 1 and |
|
1211 |
#' beginning of part 2. |
|
1212 |
#' |
|
1213 |
#' @details This class works only in conjunction with [`DataParts`] objects. If |
|
1214 |
#' part 2 will just be started in the next cohort, then the next maximum dose |
|
1215 |
#' will be either `dlt_start` (e.g. -1) shift of the last part 1 dose in case |
|
1216 |
#' of a DLT in part 1, or `clean_start` shift (e.g. -1) in case of no DLTs in |
|
1217 |
#' part 1, given that `clean_start <= 0` (see description of `clean_start` |
|
1218 |
#' slot for more details). If part 1 will still be on in the next cohort, |
|
1219 |
#' then the next dose level will be the next higher dose level in the |
|
1220 |
#' `part1Ladder` slot of the data object. If part 2 has been started before, |
|
1221 |
#' the usual relative increment rules apply, see [`IncrementsRelative`]. |
|
1222 |
#' |
|
1223 |
#' @slot dlt_start (`integer`)\cr a scalar, the dose level increment for starting |
|
1224 |
#' part 2 in case of at least one DLT event in part 1. |
|
1225 |
#' @slot clean_start (`integer`)\cr a scalar, the dose level increment for |
|
1226 |
#' starting part 2 in case of no DLTs in part 1. If `clean_start <= 0`, |
|
1227 |
#' then the part 1 ladder will be used to find the maximum next dose. |
|
1228 |
#' Otherwise, the relative increment rules will be applied to find the next |
|
1229 |
#' maximum dose level. |
|
1230 |
#' |
|
1231 |
#' @note We require that `clean_start >= dlt_start`. However, this precondition |
|
1232 |
#' is not a prerequisite for any function (except of the class' validation |
|
1233 |
#' function) that works with objects of this class. It is rather motivated by |
|
1234 |
#' the semantics. That is, if we observe a DLT in part 1, we cannot be more |
|
1235 |
#' aggressive than in case of a clean part 1 without DLT. |
|
1236 |
#' |
|
1237 |
#' @aliases IncrementsRelativeParts |
|
1238 |
#' @export |
|
1239 |
#' |
|
1240 |
.IncrementsRelativeParts <- setClass( |
|
1241 |
Class = "IncrementsRelativeParts", |
|
1242 |
slots = representation( |
|
1243 |
dlt_start = "integer", |
|
1244 |
clean_start = "integer" |
|
1245 |
), |
|
1246 |
prototype = prototype( |
|
1247 |
dlt_start = -1L, |
|
1248 |
clean_start = 1L |
|
1249 |
), |
|
1250 |
contains = "IncrementsRelative", |
|
1251 |
validity = v_increments_relative_parts |
|
1252 |
) |
|
1253 | ||
1254 |
## constructor ---- |
|
1255 | ||
1256 |
#' @rdname IncrementsRelativeParts-class |
|
1257 |
#' |
|
1258 |
#' @param dlt_start (`count`)\cr see slot definition. |
|
1259 |
#' @param clean_start (`count`)\cr see slot definition. |
|
1260 |
#' @inheritDotParams IncrementsRelative |
|
1261 |
#' |
|
1262 |
#' @export |
|
1263 |
#' @example examples/Rules-class-IncrementsRelative-DataParts.R |
|
1264 |
#' |
|
1265 |
IncrementsRelativeParts <- function(dlt_start, clean_start, ...) { |
|
1266 | 29x |
assert_integerish(dlt_start) |
1267 | 29x |
assert_integerish(clean_start) |
1268 | ||
1269 | 29x |
.IncrementsRelativeParts( |
1270 | 29x |
dlt_start = as.integer(dlt_start), |
1271 | 29x |
clean_start = as.integer(clean_start), |
1272 |
... |
|
1273 |
) |
|
1274 |
} |
|
1275 | ||
1276 |
## default constructor ---- |
|
1277 | ||
1278 |
#' @rdname IncrementsRelativeParts-class |
|
1279 |
#' @note Typically, end users will not use the `.DefaultIncrementsRelativeParts()` function. |
|
1280 |
#' @export |
|
1281 |
.DefaultIncrementsRelativeParts <- function() { |
|
1282 | 9x |
IncrementsRelativeParts(dlt_start = 0L, clean_start = 1L) |
1283 |
} |
|
1284 | ||
1285 |
# IncrementsDoseLevels ---- |
|
1286 | ||
1287 |
## class ---- |
|
1288 | ||
1289 |
#' `IncrementsDoseLevels` |
|
1290 |
#' |
|
1291 |
#' @description `r lifecycle::badge("stable")` |
|
1292 |
#' |
|
1293 |
#' [`IncrementsDoseLevels`] is the class for increments control based on the |
|
1294 |
#' number of dose levels. |
|
1295 |
#' |
|
1296 |
#' @slot levels (`count`)\cr maximum number of dose levels to increment for |
|
1297 |
#' the next dose. It defaults to 1, which means that no dose skipping is |
|
1298 |
#' allowed, i.e. the next dose can be maximum one level higher than the current |
|
1299 |
#' base dose. The current base dose level is the dose level used to increment |
|
1300 |
#' from (see `basis_level` parameter). |
|
1301 |
#' @slot basis_level (`string`)\cr defines the current base dose level. It can |
|
1302 |
#' take one out of two possible values: `last` or `max`. |
|
1303 |
#' If `last` is specified (default), the current base dose level is set to the |
|
1304 |
#' last dose given. If `max` is specified, then the current base dose level is |
|
1305 |
#' set to the maximum dose level given. |
|
1306 |
#' |
|
1307 |
#' @aliases IncrementsDoseLevels |
|
1308 |
#' @export |
|
1309 |
#' |
|
1310 |
.IncrementsDoseLevels <- setClass( |
|
1311 |
Class = "IncrementsDoseLevels", |
|
1312 |
slots = representation( |
|
1313 |
levels = "integer", |
|
1314 |
basis_level = "character" |
|
1315 |
), |
|
1316 |
prototype = prototype( |
|
1317 |
levels = 1L, |
|
1318 |
basis_level = "last" |
|
1319 |
), |
|
1320 |
contains = "Increments", |
|
1321 |
validity = v_increments_dose_levels |
|
1322 |
) |
|
1323 | ||
1324 |
## constructor ---- |
|
1325 | ||
1326 |
#' @rdname IncrementsDoseLevels-class |
|
1327 |
#' |
|
1328 |
#' @param levels (`count`)\cr see slot definition. |
|
1329 |
#' @param basis_level (`string`)\cr see slot definition. |
|
1330 |
#' |
|
1331 |
#' @export |
|
1332 |
#' @example examples/Rules-class-IncrementsDoseLevels.R |
|
1333 |
#' |
|
1334 |
IncrementsDoseLevels <- function(levels = 1L, basis_level = "last") { |
|
1335 | 31x |
assert_count(levels, positive = TRUE) |
1336 | 31x |
assert_string(basis_level) |
1337 | 31x |
assert_subset(basis_level, c("last", "max")) |
1338 | ||
1339 | 31x |
.IncrementsDoseLevels( |
1340 | 31x |
levels = as.integer(levels), |
1341 | 31x |
basis_level = basis_level |
1342 |
) |
|
1343 |
} |
|
1344 | ||
1345 |
## default constructor ---- |
|
1346 | ||
1347 |
#' @rdname IncrementsDoseLevels-class |
|
1348 |
#' @note Typically, end users will not use the `.DefaultIncrementsDoseLevels()` function. |
|
1349 |
#' @export |
|
1350 |
.DefaultIncrementsDoseLevels <- function() { |
|
1351 | 7x |
IncrementsDoseLevels(levels = 2L, basis_level = "last") |
1352 |
} |
|
1353 | ||
1354 |
# IncrementsHSRBeta ---- |
|
1355 | ||
1356 |
## class ---- |
|
1357 | ||
1358 |
#' `IncrementsHSRBeta` |
|
1359 |
#' |
|
1360 |
#' @description `r lifecycle::badge("experimental")` |
|
1361 |
#' |
|
1362 |
#' [`IncrementsHSRBeta`] is a class for limiting further increments using |
|
1363 |
#' a Hard Safety Rule based on the Bin-Beta model. |
|
1364 |
#' Increment control is based on the number of observed DLTs and number of |
|
1365 |
#' subjects at each dose level. The probability of toxicity is calculated |
|
1366 |
#' using a Bin-Beta model with prior (a,b). If the probability exceeds |
|
1367 |
#' the threshold for a given dose, that dose and all doses above are excluded |
|
1368 |
#' from further escalation. |
|
1369 |
#' This is a hard safety rule that limits further escalation based on the |
|
1370 |
#' observed data per dose level, independent from the underlying model. |
|
1371 |
#' |
|
1372 |
#' @slot target (`proportion`)\cr the target toxicity, except |
|
1373 |
#' 0 or 1. |
|
1374 |
#' @slot prob (`proportion`)\cr the threshold probability (except 0 or 1) for |
|
1375 |
#' a dose being toxic. |
|
1376 |
#' @slot a (`number`)\cr shape parameter \eqn{a > 0} of probability distribution |
|
1377 |
#' Beta (a,b). |
|
1378 |
#' @slot b (`number`)\cr shape parameter \eqn{b > 0} of probability distribution |
|
1379 |
#' Beta (a,b). |
|
1380 |
#' |
|
1381 |
#' @aliases IncrementsHSRBeta |
|
1382 |
#' @export |
|
1383 |
#' |
|
1384 |
.IncrementsHSRBeta <- setClass( |
|
1385 |
Class = "IncrementsHSRBeta", |
|
1386 |
slots = c( |
|
1387 |
target = "numeric", |
|
1388 |
prob = "numeric", |
|
1389 |
a = "numeric", |
|
1390 |
b = "numeric" |
|
1391 |
), |
|
1392 |
prototype = prototype( |
|
1393 |
target = 0.3, |
|
1394 |
prob = 0.95, |
|
1395 |
a = 1, |
|
1396 |
b = 1 |
|
1397 |
), |
|
1398 |
contains = "Increments", |
|
1399 |
validity = v_increments_hsr_beta |
|
1400 |
) |
|
1401 | ||
1402 |
## constructor ---- |
|
1403 | ||
1404 |
#' @rdname IncrementsHSRBeta-class |
|
1405 |
#' |
|
1406 |
#' @param target (`proportion`)\cr see slot definition. |
|
1407 |
#' @param prob (`proportion`)\cr see slot definition. |
|
1408 |
#' @param a (`number`)\cr see slot definition. |
|
1409 |
#' @param b (`number`)\cr see slot definition. |
|
1410 |
#' |
|
1411 |
#' @example examples/Rules-class-IncrementsHSRBeta.R |
|
1412 |
#' @export |
|
1413 |
#' |
|
1414 |
IncrementsHSRBeta <- function(target = 0.3, |
|
1415 |
prob = 0.95, |
|
1416 |
a = 1, |
|
1417 |
b = 1) { |
|
1418 | 22x |
.IncrementsHSRBeta( |
1419 | 22x |
target = target, |
1420 | 22x |
prob = prob, |
1421 | 22x |
a = a, |
1422 | 22x |
b = b |
1423 |
) |
|
1424 |
} |
|
1425 | ||
1426 |
## default constructor ---- |
|
1427 | ||
1428 |
#' @rdname IncrementsHSRBeta-class |
|
1429 |
#' @note Typically, end users will not use the `.DefaultIncrementsHSRBeta()` function. |
|
1430 |
#' @export |
|
1431 |
.DefaultIncrementsHSRBeta <- function() { |
|
1432 | 7x |
IncrementsHSRBeta(target = 0.3, prob = 0.95) |
1433 |
} |
|
1434 | ||
1435 |
# IncrementsMin ---- |
|
1436 | ||
1437 |
## class ---- |
|
1438 | ||
1439 |
#' `IncrementsMin` |
|
1440 |
#' |
|
1441 |
#' @description `r lifecycle::badge("stable")` |
|
1442 |
#' |
|
1443 |
#' [`IncrementsMin`] is the class that combines multiple increment rules with |
|
1444 |
#' the `minimum` operation. Slot `increments_list` contains all increment rules, |
|
1445 |
#' which are itself the objects of class [`Increments`]. The minimum of these |
|
1446 |
#' individual increments is taken to give the final maximum increment. |
|
1447 |
#' |
|
1448 |
#' @slot increments_list (`list`)\cr list with increment rules. |
|
1449 |
#' |
|
1450 |
#' @aliases IncrementsMin |
|
1451 |
#' @export |
|
1452 |
#' |
|
1453 |
.IncrementsMin <- setClass( |
|
1454 |
Class = "IncrementsMin", |
|
1455 |
slots = c(increments_list = "list"), |
|
1456 |
prototype = prototype( |
|
1457 |
increments_list = list( |
|
1458 |
IncrementsRelativeDLT(intervals = c(0L, 1L), increments = c(2, 1)), |
|
1459 |
IncrementsRelative(intervals = c(0, 2), increments = c(2, 1)) |
|
1460 |
) |
|
1461 |
), |
|
1462 |
contains = "Increments", |
|
1463 |
validity = v_increments_min |
|
1464 |
) |
|
1465 | ||
1466 |
## constructor ---- |
|
1467 | ||
1468 |
#' @rdname IncrementsMin-class |
|
1469 |
#' |
|
1470 |
#' @param increments_list (`list`)\cr see slot definition. |
|
1471 |
#' |
|
1472 |
#' @example examples/Rules-class-IncrementsMin.R |
|
1473 |
#' @export |
|
1474 |
#' |
|
1475 |
IncrementsMin <- function(increments_list) { |
|
1476 | 16x |
.IncrementsMin(increments_list = increments_list) |
1477 |
} |
|
1478 |
## default constructor ---- |
|
1479 | ||
1480 |
#' @rdname IncrementsMin-class |
|
1481 |
#' @note Typically, end users will not use the `.DefaultIncrementsMin()` function. |
|
1482 |
#' @export |
|
1483 |
.DefaultIncrementsMin <- function() { |
|
1484 | 8x |
IncrementsMin( |
1485 | 8x |
increments_list = list( |
1486 | 8x |
IncrementsRelativeDLT(intervals = c(0, 1, 3), increments = c(1, 0.33, 0.2)), |
1487 | 8x |
IncrementsRelative(intervals = c(0, 20), increments = c(1, 0.33)) |
1488 |
) |
|
1489 |
) |
|
1490 |
} |
|
1491 | ||
1492 |
# IncrementsOrdinal ---- |
|
1493 | ||
1494 |
## class ---- |
|
1495 | ||
1496 |
#' `IncrementsOrdinal` |
|
1497 |
#' |
|
1498 |
#' @description `r lifecycle::badge("experimental")` |
|
1499 |
#' |
|
1500 |
#' [`IncrementsOrdinal`] is the class for applying a standard `Increments` rule to |
|
1501 |
#' the results of an ordinal CRM trial. |
|
1502 |
#' |
|
1503 |
#' @slot grade (`integer`)\cr the toxicity grade to which the `rule` should be |
|
1504 |
#' applied. |
|
1505 |
#' @slot rule (`Increments`)\cr the standard `Increments` rule to be applied |
|
1506 |
#' |
|
1507 |
#' @aliases IncrementsOrdinal |
|
1508 |
#' @export |
|
1509 |
#' |
|
1510 |
.IncrementsOrdinal <- setClass( |
|
1511 |
Class = "IncrementsOrdinal", |
|
1512 |
slots = c(grade = "numeric", rule = "Increments"), |
|
1513 |
contains = "Increments", |
|
1514 |
validity = v_increments_ordinal |
|
1515 |
) |
|
1516 | ||
1517 |
## constructor ---- |
|
1518 | ||
1519 |
#' @rdname IncrementsOrdinal-class |
|
1520 |
#' |
|
1521 |
#' @param grade (`numeric`)\cr see slot definition. |
|
1522 |
#' @param rule (`Increments`)\cr see slot definition. |
|
1523 |
#' @export |
|
1524 |
#' @example examples/Rules-class-IncrementsOrdinal.R |
|
1525 |
#' |
|
1526 |
IncrementsOrdinal <- function(grade, rule) { |
|
1527 | 21x |
.IncrementsOrdinal(grade = grade, rule = rule) |
1528 |
} |
|
1529 | ||
1530 |
## default constructor ---- |
|
1531 | ||
1532 |
#' @rdname IncrementsOrdinal-class |
|
1533 |
#' @note Typically, end users will not use the `.DefaultIncrementsOrdinal()` function. |
|
1534 |
#' @export |
|
1535 |
.DefaultIncrementsOrdinal <- function() { |
|
1536 | 10x |
IncrementsOrdinal( |
1537 | 10x |
grade = 1L, |
1538 | 10x |
rule = IncrementsRelative(intervals = c(0, 20), increments = c(1, 0.33)) |
1539 |
) |
|
1540 |
} |
|
1541 | ||
1542 |
# IncrementsMaxToxProb ---- |
|
1543 | ||
1544 |
## class ---- |
|
1545 | ||
1546 |
#' `IncrementsMaxToxProb` |
|
1547 |
#' |
|
1548 |
#' @description `r lifecycle::badge("experimental")` |
|
1549 |
#' |
|
1550 |
#' [`IncrementsMaxToxProb`] is the class for increments control based on |
|
1551 |
#' probability of toxicity |
|
1552 |
#' |
|
1553 |
#' @slot prob (`numeric`)\cr See Usage Notes below. |
|
1554 |
#' |
|
1555 |
#' @section Usage Notes: |
|
1556 |
#' For binary models, `prob` should be a scalar probability. |
|
1557 |
#' |
|
1558 |
#' For ordinal models, `prob` should be a named vector containing the maximum |
|
1559 |
#' permissible probability of toxicity by grade. The names should match the |
|
1560 |
#' names of the `yCategories` slot of the associated `DataOrdinal` object. |
|
1561 |
#' |
|
1562 |
#' @aliases IncrementsMaxToxProb |
|
1563 |
#' @export |
|
1564 |
#' |
|
1565 |
.IncrementsMaxToxProb <- setClass( |
|
1566 |
Class = "IncrementsMaxToxProb", |
|
1567 |
slots = c( |
|
1568 |
prob = "numeric" |
|
1569 |
), |
|
1570 |
prototype = prototype( |
|
1571 |
prob = c("DLAE" = 0.2, "DLT" = 0.05) |
|
1572 |
), |
|
1573 |
contains = "Increments", |
|
1574 |
validity = v_increments_maxtoxprob |
|
1575 |
) |
|
1576 | ||
1577 |
## constructor ---- |
|
1578 | ||
1579 |
#' @rdname IncrementsMaxToxProb-class |
|
1580 |
#' |
|
1581 |
#' @param prob (`numeric`)\cr see slot definition. |
|
1582 |
#' |
|
1583 |
#' @export |
|
1584 |
#' @example examples/Rules-class-IncrementsMaxToxProb.R |
|
1585 |
#' |
|
1586 |
IncrementsMaxToxProb <- function(prob) { |
|
1587 | 15x |
.IncrementsMaxToxProb( |
1588 | 15x |
prob = prob |
1589 |
) |
|
1590 |
} |
|
1591 | ||
1592 |
## default constructor ---- |
|
1593 | ||
1594 |
#' @rdname IncrementsMaxToxProb-class |
|
1595 |
#' @note Typically, end users will not use the `.DefaultIncrementsMaxToxProb()` function. |
|
1596 |
#' @export |
|
1597 |
.DefaultIncrementsMaxToxProb <- function() { |
|
1598 | 6x |
IncrementsMaxToxProb(prob = c("DLAE" = 0.2, "DLT" = 0.05)) |
1599 |
} |
|
1600 | ||
1601 |
# Stopping ---- |
|
1602 | ||
1603 |
## class ---- |
|
1604 | ||
1605 |
#' `Stopping` |
|
1606 |
#' |
|
1607 |
#' @description `r lifecycle::badge("stable")` |
|
1608 |
#' |
|
1609 |
#' [`Stopping`] is a class for stopping rules. |
|
1610 |
#' |
|
1611 |
#' @slot report_label (`string`)\cr a label for the stopping report. The meaning |
|
1612 |
#' of this parameter is twofold. If it is equal to `NA_character_` (default), |
|
1613 |
#' the `report_label` will not be used in the report at all. Otherwise, if it |
|
1614 |
#' is specified as an empty character (i.e. `character(0)`) in a user constructor, |
|
1615 |
#' then a default, class-specific label will be created for this slot. |
|
1616 |
#' Finally, for the remaining cases, a user can provide a custom label. |
|
1617 |
#' |
|
1618 |
#' @seealso [`StoppingList`], [`StoppingCohortsNearDose`], [`StoppingPatientsNearDose`], |
|
1619 |
#' [`StoppingMinCohorts`], [`StoppingMinPatients`], [`StoppingTargetProb`], |
|
1620 |
#' [`StoppingMTDdistribution`], [`StoppingTargetBiomarker`], [`StoppingHighestDose`] |
|
1621 |
#' [`StoppingMTDCV`], [`StoppingLowestDoseHSRBeta`], [`StoppingSpecificDose`]. |
|
1622 |
#' |
|
1623 |
#' @aliases Stopping |
|
1624 |
#' @export |
|
1625 |
#' |
|
1626 |
setClass( |
|
1627 |
Class = "Stopping", |
|
1628 |
contains = "CrmPackClass", |
|
1629 |
slots = c(report_label = "character"), |
|
1630 |
prototype = prototype(report_label = character(0)) |
|
1631 |
) |
|
1632 | ||
1633 | ||
1634 |
## default constructor ---- |
|
1635 | ||
1636 |
#' @rdname CohortSize-class |
|
1637 |
#' @note Typically, end users will not use the `DefaultCohortSize()` function. |
|
1638 |
#' @export |
|
1639 |
.DefaultCohortSize <- function() { |
|
1640 |
stop(paste0("Class CohortSize should not be instantiated directly. Please use one of its subclasses instead.")) |
|
1641 |
} |
|
1642 | ||
1643 |
# StoppingMissingDose ---- |
|
1644 | ||
1645 |
## class ---- |
|
1646 | ||
1647 |
#' `StoppingMissingDose` |
|
1648 |
#' |
|
1649 |
#' @description `r lifecycle::badge("experimental")` |
|
1650 |
#' |
|
1651 |
#' [`StoppingMissingDose`] is the class for stopping based on NA returned by |
|
1652 |
#' next best dose. |
|
1653 |
#' |
|
1654 |
#' @aliases StoppingMissingDose |
|
1655 |
#' @export |
|
1656 |
#' |
|
1657 |
.StoppingMissingDose <- setClass( |
|
1658 |
Class = "StoppingMissingDose", |
|
1659 |
contains = "Stopping" |
|
1660 |
) |
|
1661 | ||
1662 |
## constructor ---- |
|
1663 | ||
1664 |
#' @rdname StoppingMissingDose-class |
|
1665 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
1666 |
#' @example examples/Rules-class-StoppingMissingDose.R |
|
1667 |
#' @export |
|
1668 |
#' |
|
1669 |
StoppingMissingDose <- function(report_label = NA_character_) { |
|
1670 | 14x |
report_label <- h_default_if_empty( |
1671 | 14x |
as.character(report_label), |
1672 | 14x |
paste("Stopped because of missing dose") |
1673 |
) |
|
1674 | ||
1675 | 14x |
.StoppingMissingDose(report_label = report_label) |
1676 |
} |
|
1677 | ||
1678 |
## default constructor ---- |
|
1679 | ||
1680 |
#' @rdname StoppingMissingDose-class |
|
1681 |
#' @note Typically, end users will not use the `.DefaultStoppingMissingDose()` function. |
|
1682 |
#' @export |
|
1683 |
#' |
|
1684 |
.DefaultStoppingMissingDose <- function() { |
|
1685 | 7x |
StoppingMissingDose() |
1686 |
} |
|
1687 | ||
1688 |
# StoppingCohortsNearDose ---- |
|
1689 | ||
1690 |
## class ---- |
|
1691 | ||
1692 |
#' `StoppingCohortsNearDose` |
|
1693 |
#' |
|
1694 |
#' @description `r lifecycle::badge("stable")` |
|
1695 |
#' |
|
1696 |
#' [`StoppingCohortsNearDose`] is the class for stopping based on number of |
|
1697 |
#' cohorts near to next best dose. |
|
1698 |
#' |
|
1699 |
#' |
|
1700 |
#' @slot nCohorts (`number`)\cr number of required cohorts. |
|
1701 |
#' @slot percentage (`number`)\cr percentage (between and including 0 and 100) |
|
1702 |
#' within the next best dose the cohorts must lie. |
|
1703 |
#' |
|
1704 |
#' @aliases StoppingCohortsNearDose |
|
1705 |
#' @export |
|
1706 |
#' |
|
1707 |
.StoppingCohortsNearDose <- setClass( |
|
1708 |
Class = "StoppingCohortsNearDose", |
|
1709 |
slots = c( |
|
1710 |
nCohorts = "integer", |
|
1711 |
percentage = "numeric" |
|
1712 |
), |
|
1713 |
prototype = prototype( |
|
1714 |
nCohorts = 2L, |
|
1715 |
percentage = 50 |
|
1716 |
), |
|
1717 |
contains = "Stopping", |
|
1718 |
validity = v_stopping_cohorts_near_dose |
|
1719 |
) |
|
1720 | ||
1721 |
## constructor ---- |
|
1722 | ||
1723 |
#' @rdname StoppingCohortsNearDose-class |
|
1724 |
#' |
|
1725 |
#' @param nCohorts (`number`)\cr see slot definition. |
|
1726 |
#' @param percentage (`number`)\cr see slot definition. |
|
1727 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
1728 |
#' |
|
1729 |
#' @example examples/Rules-class-StoppingCohortsNearDose.R |
|
1730 |
#' @export |
|
1731 |
#' |
|
1732 |
StoppingCohortsNearDose <- function(nCohorts = 2L, |
|
1733 |
percentage = 50, |
|
1734 |
report_label = NA_character_) { |
|
1735 | 22x |
assert_count(nCohorts, positive = TRUE) |
1736 | 22x |
assert_numeric(percentage, lower = 0) |
1737 | ||
1738 | 22x |
report_label <- h_default_if_empty( |
1739 | 22x |
as.character(report_label), |
1740 | 22x |
paste("\u2265", nCohorts, "cohorts dosed in", percentage, "% dose range around NBD") |
1741 |
) |
|
1742 | ||
1743 | 22x |
.StoppingCohortsNearDose( |
1744 | 22x |
nCohorts = as.integer(nCohorts), |
1745 | 22x |
percentage = percentage, |
1746 | 22x |
report_label = report_label |
1747 |
) |
|
1748 |
} |
|
1749 | ||
1750 |
## default constructor ---- |
|
1751 | ||
1752 |
#' @rdname StoppingCohortsNearDose-class |
|
1753 |
#' @note Typically, end users will not use the `.DefaultStoppingCohortsNearDose()` function. |
|
1754 |
#' @export |
|
1755 |
.DefaultStoppingCohortsNearDose <- function() { # nolint |
|
1756 | 7x |
StoppingCohortsNearDose( |
1757 | 7x |
nCohorts = 3L, |
1758 | 7x |
percentage = 0.2 |
1759 |
) |
|
1760 |
} |
|
1761 | ||
1762 | ||
1763 |
# StoppingPatientsNearDose ---- |
|
1764 | ||
1765 |
## class ---- |
|
1766 | ||
1767 |
#' `StoppingPatientsNearDose` |
|
1768 |
#' |
|
1769 |
#' @description `r lifecycle::badge("stable")` |
|
1770 |
#' |
|
1771 |
#' [`StoppingPatientsNearDose`] is the class for stopping based on number of |
|
1772 |
#' patients near to next best dose. |
|
1773 |
#' |
|
1774 |
#' @slot nPatients (`number`)\cr number of required patients. |
|
1775 |
#' @slot percentage (`number`)\cr percentage (between and including 0 and 100) |
|
1776 |
#' within the next best dose the patients must lie. |
|
1777 |
#' |
|
1778 |
#' @aliases StoppingPatientsNearDose |
|
1779 |
#' @export |
|
1780 |
#' |
|
1781 |
.StoppingPatientsNearDose <- setClass( |
|
1782 |
Class = "StoppingPatientsNearDose", |
|
1783 |
slots = c( |
|
1784 |
nPatients = "integer", |
|
1785 |
percentage = "numeric" |
|
1786 |
), |
|
1787 |
prototype = prototype( |
|
1788 |
nPatients = 10L, |
|
1789 |
percentage = 50 |
|
1790 |
), |
|
1791 |
contains = "Stopping", |
|
1792 |
validity = v_stopping_patients_near_dose |
|
1793 |
) |
|
1794 | ||
1795 |
## constructor ---- |
|
1796 | ||
1797 |
#' @rdname StoppingPatientsNearDose-class |
|
1798 |
#' |
|
1799 |
#' @param nPatients (`number`)\cr see slot definition. |
|
1800 |
#' @param percentage (`number`)\cr see slot definition. |
|
1801 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
1802 |
#' |
|
1803 |
#' @example examples/Rules-class-StoppingPatientsNearDose.R |
|
1804 |
#' @export |
|
1805 |
#' |
|
1806 |
StoppingPatientsNearDose <- function(nPatients = 10L, |
|
1807 |
percentage = 50, |
|
1808 |
report_label = NA_character_) { |
|
1809 | 23x |
assert_count(nPatients, positive = TRUE) |
1810 | 23x |
assert_number(percentage, lower = 0, upper = 100) |
1811 | ||
1812 | 23x |
report_label <- h_default_if_empty( |
1813 | 23x |
as.character(report_label), |
1814 | 23x |
paste("\u2265", nPatients, "patients dosed in", percentage, "% dose range around NBD") |
1815 |
) |
|
1816 | ||
1817 | 23x |
.StoppingPatientsNearDose( |
1818 | 23x |
nPatients = as.integer(nPatients), |
1819 | 23x |
percentage = percentage, |
1820 | 23x |
report_label = report_label |
1821 |
) |
|
1822 |
} |
|
1823 | ||
1824 |
## default constructor ---- |
|
1825 | ||
1826 |
#' @rdname StoppingPatientsNearDose-class |
|
1827 |
#' @note Typically, end users will not use the `.DefaultStoppingPatientsNearDose()` function. |
|
1828 |
#' @export |
|
1829 |
.DefaultStoppingPatientsNearDose <- function() { # nolint |
|
1830 | 7x |
StoppingPatientsNearDose( |
1831 | 7x |
nPatients = 9L, |
1832 | 7x |
percentage = 20, |
1833 | 7x |
report_label = NA_character_ |
1834 |
) |
|
1835 |
} |
|
1836 | ||
1837 |
# StoppingMinCohorts ---- |
|
1838 | ||
1839 |
## class ---- |
|
1840 | ||
1841 |
#' `StoppingMinCohorts` |
|
1842 |
#' |
|
1843 |
#' @description `r lifecycle::badge("stable")` |
|
1844 |
#' |
|
1845 |
#' [`StoppingMinCohorts`] is the class for stopping based on minimum number of |
|
1846 |
#' cohorts. |
|
1847 |
#' |
|
1848 |
#' @slot nCohorts (`number`)\cr minimum required number of cohorts. |
|
1849 |
#' |
|
1850 |
#' @aliases StoppingMinCohorts |
|
1851 |
#' @export |
|
1852 |
#' |
|
1853 |
.StoppingMinCohorts <- setClass( |
|
1854 |
Class = "StoppingMinCohorts", |
|
1855 |
slots = c(nCohorts = "integer"), |
|
1856 |
prototype = prototype(nCohorts = 2L), |
|
1857 |
contains = "Stopping", |
|
1858 |
validity = v_stopping_min_cohorts |
|
1859 |
) |
|
1860 | ||
1861 |
## constructor ---- |
|
1862 | ||
1863 |
#' @rdname StoppingMinCohorts-class |
|
1864 |
#' |
|
1865 |
#' @param nCohorts (`number`)\cr see slot definition. |
|
1866 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
1867 |
#' |
|
1868 |
#' @example examples/Rules-class-StoppingMinCohorts.R |
|
1869 |
#' @export |
|
1870 |
#' |
|
1871 |
StoppingMinCohorts <- function(nCohorts = 2L, |
|
1872 |
report_label = NA_character_) { |
|
1873 | 82x |
assert_count(nCohorts, positive = TRUE) |
1874 | ||
1875 | 82x |
report_label <- h_default_if_empty( |
1876 | 82x |
as.character(report_label), |
1877 | 82x |
paste("\u2265", nCohorts, "cohorts dosed") |
1878 |
) |
|
1879 | ||
1880 | 82x |
.StoppingMinCohorts( |
1881 | 82x |
nCohorts = as.integer(nCohorts), |
1882 | 82x |
report_label = report_label |
1883 |
) |
|
1884 |
} |
|
1885 | ||
1886 |
## default constructor ---- |
|
1887 | ||
1888 |
#' @rdname StoppingMinCohorts-class |
|
1889 |
#' @note Typically, end users will not use the `.DefaultStoppingMinCohorts()` function. |
|
1890 |
#' @export |
|
1891 |
.DefaultStoppingMinCohorts <- function() { |
|
1892 | 7x |
StoppingMinCohorts( |
1893 | 7x |
nCohorts = 6L |
1894 |
) |
|
1895 |
} |
|
1896 | ||
1897 |
# StoppingMinPatients ---- |
|
1898 | ||
1899 |
## class ---- |
|
1900 | ||
1901 |
#' `StoppingMinPatients` |
|
1902 |
#' |
|
1903 |
#' @description `r lifecycle::badge("stable")` |
|
1904 |
#' |
|
1905 |
#' [`StoppingMinPatients`] is the class for stopping based on minimum number of |
|
1906 |
#' patients |
|
1907 |
#' |
|
1908 |
#' @slot nPatients (`number`)\cr minimum allowed number of patients. |
|
1909 |
#' |
|
1910 |
#' @aliases StoppingMinPatients |
|
1911 |
#' @export |
|
1912 |
#' |
|
1913 |
.StoppingMinPatients <- setClass( |
|
1914 |
Class = "StoppingMinPatients", |
|
1915 |
slots = c(nPatients = "integer"), |
|
1916 |
prototype = prototype(nPatients = 20L), |
|
1917 |
contains = "Stopping", |
|
1918 |
validity = v_stopping_min_patients |
|
1919 |
) |
|
1920 | ||
1921 |
## constructor ---- |
|
1922 | ||
1923 |
#' @rdname StoppingMinPatients-class |
|
1924 |
#' |
|
1925 |
#' @param nPatients (`number`)\cr see slot definition. |
|
1926 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
1927 |
#' |
|
1928 |
#' @example examples/Rules-class-StoppingMinPatients.R |
|
1929 |
#' @export |
|
1930 |
#' |
|
1931 |
StoppingMinPatients <- function(nPatients = 20L, |
|
1932 |
report_label = NA_character_) { |
|
1933 | 136x |
assert_count(nPatients, positive = TRUE) |
1934 | ||
1935 | 136x |
report_label <- h_default_if_empty( |
1936 | 136x |
as.character(report_label), |
1937 | 136x |
paste("\u2265", nPatients, "patients dosed") |
1938 |
) |
|
1939 | ||
1940 | 136x |
.StoppingMinPatients( |
1941 | 136x |
nPatients = as.integer(nPatients), |
1942 | 136x |
report_label = report_label |
1943 |
) |
|
1944 |
} |
|
1945 | ||
1946 |
## default constructor ---- |
|
1947 | ||
1948 |
#' @rdname StoppingMinPatients-class |
|
1949 |
#' @note Typically, end users will not use the `.DefaultStoppingMinPatients()` function. |
|
1950 |
#' @export |
|
1951 |
.DefaultStoppingMinPatients <- function() { |
|
1952 | 7x |
StoppingMinPatients( |
1953 | 7x |
nPatients = 20L |
1954 |
) |
|
1955 |
} |
|
1956 | ||
1957 |
# StoppingTargetProb ---- |
|
1958 | ||
1959 |
## class ---- |
|
1960 | ||
1961 |
#' `StoppingTargetProb` |
|
1962 |
#' |
|
1963 |
#' @description `r lifecycle::badge("stable")` |
|
1964 |
#' |
|
1965 |
#' [`StoppingTargetProb`] is the class for stopping based on the probability of |
|
1966 |
#' the DLT rate being in the target toxicity interval. |
|
1967 |
#' |
|
1968 |
#' @slot target (`number`)\cr the target toxicity interval, e.g. `c(0.2, 0.35)`. |
|
1969 |
#' @slot prob (`proportion`)\cr required target toxicity probability (except 0 or 1) |
|
1970 |
#' for reaching sufficient precision. |
|
1971 |
#' |
|
1972 |
#' @aliases StoppingTargetProb |
|
1973 |
#' @export |
|
1974 |
#' |
|
1975 |
.StoppingTargetProb <- setClass( |
|
1976 |
Class = "StoppingTargetProb", |
|
1977 |
slots = c( |
|
1978 |
target = "numeric", |
|
1979 |
prob = "numeric" |
|
1980 |
), |
|
1981 |
prototype = prototype( |
|
1982 |
target = c(0.2, 0.35), |
|
1983 |
prob = 0.4 |
|
1984 |
), |
|
1985 |
contains = "Stopping", |
|
1986 |
validity = v_stopping_target_prob |
|
1987 |
) |
|
1988 | ||
1989 |
## constructor ---- |
|
1990 | ||
1991 |
#' @rdname StoppingTargetProb-class |
|
1992 |
#' |
|
1993 |
#' @param target (`number`)\cr see slot definition. |
|
1994 |
#' @param prob (`proportion`)\cr see slot definition. |
|
1995 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
1996 |
#' |
|
1997 |
#' @example examples/Rules-class-StoppingTargetProb.R |
|
1998 |
#' @export |
|
1999 |
#' |
|
2000 |
StoppingTargetProb <- function(target = c(0.2, 0.35), |
|
2001 |
prob = 0.4, |
|
2002 |
report_label = NA_character_) { |
|
2003 | 114x |
assert_numeric(target, len = 2) |
2004 | 114x |
report_label <- h_default_if_empty( |
2005 | 114x |
as.character(report_label), |
2006 | 114x |
paste0("P(", target[1], " \u2264 prob(DLE | NBD) \u2264 ", target[2], ") \u2265 ", prob) |
2007 |
) |
|
2008 | ||
2009 | 114x |
.StoppingTargetProb( |
2010 | 114x |
target = target, |
2011 | 114x |
prob = prob, |
2012 | 114x |
report_label = report_label |
2013 |
) |
|
2014 |
} |
|
2015 | ||
2016 |
## default constructor ---- |
|
2017 | ||
2018 |
#' @rdname StoppingTargetProb-class |
|
2019 |
#' @note Typically, end users will not use the `.DefaultStoppingTargetProb()` function. |
|
2020 |
#' @export |
|
2021 |
.DefaultStoppingTargetProb <- function() { |
|
2022 | 7x |
StoppingTargetProb( |
2023 | 7x |
target = c(0.2, 0.35), |
2024 | 7x |
prob = 0.5 |
2025 |
) |
|
2026 |
} |
|
2027 | ||
2028 |
# StoppingMTDdistribution ---- |
|
2029 | ||
2030 |
## class ---- |
|
2031 | ||
2032 |
#' `StoppingMTDdistribution` |
|
2033 |
#' |
|
2034 |
#' @description `r lifecycle::badge("stable")` |
|
2035 |
#' |
|
2036 |
#' [`StoppingMTDdistribution`] is the class for stopping based on the posterior |
|
2037 |
#' distribution of the MTD. It is used for the cases where the stopping occurs |
|
2038 |
#' when the probability of `MTD > thresh * next_dose` is greater than or equal |
|
2039 |
#' to `prob`, where the `next_dose` is the recommended next best dose. |
|
2040 |
#' Here, the MTD is defined as the dose that reaches a specific `target` |
|
2041 |
#' probability of the occurrence of a DLT. |
|
2042 |
#' |
|
2043 |
#' @slot target (`proportion`)\cr the target toxicity probability (except 0 or 1) |
|
2044 |
#' defining the MTD. |
|
2045 |
#' @slot thresh (`proportion`)\cr the threshold (except 0 or 1) relative to the |
|
2046 |
#' recommended next best dose. |
|
2047 |
#' @slot prob (`proportion`)\cr required minimum probability, except 0 or 1. |
|
2048 |
#' |
|
2049 |
#' @aliases StoppingMTDdistribution |
|
2050 |
#' @export |
|
2051 |
#' |
|
2052 |
.StoppingMTDdistribution <- setClass( |
|
2053 |
Class = "StoppingMTDdistribution", |
|
2054 |
slots = c( |
|
2055 |
target = "numeric", |
|
2056 |
thresh = "numeric", |
|
2057 |
prob = "numeric" |
|
2058 |
), |
|
2059 |
prototype = prototype( |
|
2060 |
target = 0.33, |
|
2061 |
thresh = 0.5, |
|
2062 |
prob = 0.9 |
|
2063 |
), |
|
2064 |
contains = "Stopping", |
|
2065 |
validity = v_stopping_mtd_distribution |
|
2066 |
) |
|
2067 | ||
2068 |
## constructor ---- |
|
2069 | ||
2070 |
#' @rdname StoppingMTDdistribution-class |
|
2071 |
#' |
|
2072 |
#' @param target (`proportion`)\cr see slot definition. |
|
2073 |
#' @param thresh (`proportion`)\cr see slot definition. |
|
2074 |
#' @param prob (`proportion`)\cr see slot definition. |
|
2075 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
2076 |
#' |
|
2077 |
#' @example examples/Rules-class-StoppingMTDdistribution.R |
|
2078 |
#' @export |
|
2079 |
#' |
|
2080 |
StoppingMTDdistribution <- function(target = 0.33, |
|
2081 |
thresh = 0.5, |
|
2082 |
prob = 0.9, |
|
2083 |
report_label = NA_character_) { |
|
2084 | 768x |
report_label <- h_default_if_empty( |
2085 | 768x |
as.character(report_label), |
2086 | 768x |
paste0("P(MTD > ", thresh, " * NBD | P(DLE) = ", target, ") \u2265 ", prob) |
2087 |
) |
|
2088 | ||
2089 | 768x |
.StoppingMTDdistribution( |
2090 | 768x |
target = target, |
2091 | 768x |
thresh = thresh, |
2092 | 768x |
prob = prob, |
2093 | 768x |
report_label = report_label |
2094 |
) |
|
2095 |
} |
|
2096 | ||
2097 |
## default constructor ---- |
|
2098 | ||
2099 |
#' @rdname StoppingMTDdistribution-class |
|
2100 |
#' @note Typically, end users will not use the `.DefaultStoppingMTDDistribution()` function. |
|
2101 |
#' @export |
|
2102 |
.DefaultStoppingMTDdistribution <- function() { |
|
2103 | 7x |
StoppingMTDdistribution( |
2104 | 7x |
target = 0.33, |
2105 | 7x |
thresh = 0.5, |
2106 | 7x |
prob = 0.9 |
2107 |
) |
|
2108 |
} |
|
2109 | ||
2110 |
# StoppingMTDCV ---- |
|
2111 | ||
2112 |
## class ---- |
|
2113 | ||
2114 |
#' `StoppingMTDCV` |
|
2115 |
#' |
|
2116 |
#' @description `r lifecycle::badge("experimental")` |
|
2117 |
#' |
|
2118 |
#' [`StoppingMTDCV`] is a class for stopping rule based on precision of MTD |
|
2119 |
#' which is calculated as the coefficient of variation (CV) of the MTD. |
|
2120 |
#' Here, the MTD is defined as the dose that reaches a specific `target` |
|
2121 |
#' probability of the occurrence of a DLT. |
|
2122 |
#' |
|
2123 |
#' @slot target (`proportion`)\cr toxicity target of MTD (except 0 or 1). |
|
2124 |
#' @slot thresh_cv (`number`)\cr threshold (percentage > 0) for CV to be |
|
2125 |
#' considered accurate enough to stop the trial. The stopping occurs when the |
|
2126 |
#' CV is less than or equal to `tresh_cv`. |
|
2127 |
#' |
|
2128 |
#' @aliases StoppingMTDCV |
|
2129 |
#' @export |
|
2130 |
#' |
|
2131 |
.StoppingMTDCV <- setClass( |
|
2132 |
Class = "StoppingMTDCV", |
|
2133 |
slots = c( |
|
2134 |
target = "numeric", |
|
2135 |
thresh_cv = "numeric" |
|
2136 |
), |
|
2137 |
prototype = prototype( |
|
2138 |
target = 0.3, |
|
2139 |
thresh_cv = 40 |
|
2140 |
), |
|
2141 |
contains = "Stopping", |
|
2142 |
validity = v_stopping_mtd_cv |
|
2143 |
) |
|
2144 | ||
2145 |
## constructor ---- |
|
2146 | ||
2147 |
#' @rdname StoppingMTDCV-class |
|
2148 |
#' |
|
2149 |
#' @param target (`proportion`)\cr see slot definition. |
|
2150 |
#' @param thresh_cv (`number`)\cr see slot definition. |
|
2151 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
2152 |
#' |
|
2153 |
#' @export |
|
2154 |
#' @example examples/Rules-class-StoppingMTDCV.R |
|
2155 |
#' |
|
2156 |
StoppingMTDCV <- function(target = 0.3, |
|
2157 |
thresh_cv = 40, |
|
2158 |
report_label = NA_character_) { |
|
2159 | 20x |
report_label <- h_default_if_empty( |
2160 | 20x |
as.character(report_label), |
2161 | 20x |
paste("CV(MTD) >", target) |
2162 |
) |
|
2163 | ||
2164 | 20x |
.StoppingMTDCV( |
2165 | 20x |
target = target, |
2166 | 20x |
thresh_cv = thresh_cv, |
2167 | 20x |
report_label = report_label |
2168 |
) |
|
2169 |
} |
|
2170 | ||
2171 |
## default constructor ---- |
|
2172 | ||
2173 |
#' @rdname StoppingMTDCV-class |
|
2174 |
#' @note Typically, end users will not use the `.DefaultStoppingMTDCV()` function. |
|
2175 |
#' |
|
2176 |
#' @export |
|
2177 |
.DefaultStoppingMTDCV <- function() { |
|
2178 | 7x |
StoppingMTDCV( |
2179 | 7x |
target = 0.3, |
2180 | 7x |
thresh_cv = 40 |
2181 |
) |
|
2182 |
} |
|
2183 | ||
2184 |
# StoppingLowestDoseHSRBeta ---- |
|
2185 | ||
2186 |
## class ---- |
|
2187 | ||
2188 |
#' `StoppingLowestDoseHSRBeta` |
|
2189 |
#' |
|
2190 |
#' @description `r lifecycle::badge("experimental")` |
|
2191 |
#' |
|
2192 |
#' [`StoppingLowestDoseHSRBeta`] is a class for stopping based on a Hard Safety |
|
2193 |
#' Rule using the Beta posterior distribution with Beta(a,b) prior and a |
|
2194 |
#' Bin-Beta model based on the observed data at the lowest dose level. |
|
2195 |
#' The rule is triggered when the first dose is considered to be toxic |
|
2196 |
#' (i.e. above threshold probability) based on the observed data at the |
|
2197 |
#' lowest dose level and a Beta(a,b) prior distribution. |
|
2198 |
#' The default prior is Beta(1,1). |
|
2199 |
#' In case that placebo is used, the rule is evaluated at the second dose of the |
|
2200 |
#' dose grid, i.e. at the lowest non-placebo dose. |
|
2201 |
#' |
|
2202 |
#' @note This stopping rule is independent from the underlying model. |
|
2203 |
#' |
|
2204 |
#' @slot target (`proportion`)\cr the target toxicity. |
|
2205 |
#' @slot prob (`proportion`)\cr the threshold probability for the lowest dose |
|
2206 |
#' being toxic. |
|
2207 |
#' @slot a (`number`)\cr shape parameter \eqn{a > 0} of probability distribution |
|
2208 |
#' Beta (a,b). |
|
2209 |
#' @slot b (`number`)\cr shape parameter \eqn{b > 0} of probability distribution |
|
2210 |
#' Beta (a,b). |
|
2211 |
#' |
|
2212 |
#' @aliases StoppingLowestDoseHSRBeta |
|
2213 |
#' @export |
|
2214 |
#' |
|
2215 |
.StoppingLowestDoseHSRBeta <- setClass( |
|
2216 |
Class = "StoppingLowestDoseHSRBeta", |
|
2217 |
slots = c( |
|
2218 |
target = "numeric", |
|
2219 |
prob = "numeric", |
|
2220 |
a = "numeric", |
|
2221 |
b = "numeric" |
|
2222 |
), |
|
2223 |
prototype = prototype( |
|
2224 |
target = 0.3, |
|
2225 |
prob = 0.95, |
|
2226 |
a = 1, |
|
2227 |
b = 1 |
|
2228 |
), |
|
2229 |
contains = "Stopping", |
|
2230 |
validity = v_increments_hsr_beta |
|
2231 |
) |
|
2232 | ||
2233 |
## constructor ---- |
|
2234 | ||
2235 |
#' @rdname StoppingLowestDoseHSRBeta-class |
|
2236 |
#' |
|
2237 |
#' @param target (`proportion`)\cr see slot definition. |
|
2238 |
#' @param prob (`proportion`)\cr see slot definition. |
|
2239 |
#' @param a (`number`)\cr see slot definition. |
|
2240 |
#' @param b (`number`)\cr see slot definition. |
|
2241 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
2242 |
#' |
|
2243 |
#' @export |
|
2244 |
#' @example examples/Rules-class-StoppingLowestDoseHSRBeta.R |
|
2245 |
#' |
|
2246 |
StoppingLowestDoseHSRBeta <- function(target = 0.3, |
|
2247 |
prob = 0.95, |
|
2248 |
a = 1, |
|
2249 |
b = 1, |
|
2250 |
report_label = NA_character_) { |
|
2251 | 20x |
report_label <- h_default_if_empty( |
2252 | 20x |
as.character(report_label), |
2253 | 20x |
paste0("P\u03B2(lowest dose > P(DLE) = ", target, ") > ", prob) |
2254 |
) |
|
2255 | ||
2256 | 20x |
.StoppingLowestDoseHSRBeta( |
2257 | 20x |
target = target, |
2258 | 20x |
prob = prob, |
2259 | 20x |
a = a, |
2260 | 20x |
b = b, |
2261 | 20x |
report_label = report_label |
2262 |
) |
|
2263 |
} |
|
2264 | ||
2265 |
## default constructor ---- |
|
2266 | ||
2267 |
#' @rdname StoppingLowestDoseHSRBeta-class |
|
2268 |
#' @note Typically, end users will not use the `.DefaultStoppingLowestDoseHSRBeta()` function. |
|
2269 |
#' @export |
|
2270 |
.DefaultStoppingLowestDoseHSRBeta <- function() { # nolint |
|
2271 | 7x |
StoppingLowestDoseHSRBeta( |
2272 | 7x |
target = 0.3, |
2273 | 7x |
prob = 0.95, |
2274 | 7x |
a = 1, |
2275 | 7x |
b = 1 |
2276 |
) |
|
2277 |
} |
|
2278 | ||
2279 |
# StoppingTargetBiomarker ---- |
|
2280 | ||
2281 |
## class ---- |
|
2282 | ||
2283 |
#' `StoppingTargetBiomarker` |
|
2284 |
#' |
|
2285 |
#' @description `r lifecycle::badge("stable")` |
|
2286 |
#' |
|
2287 |
#' [`StoppingTargetBiomarker`] is a class for stopping based on probability of |
|
2288 |
#' target biomarker. |
|
2289 |
#' |
|
2290 |
#' @slot target (`numeric`)\cr the biomarker target range that needs to be |
|
2291 |
#' reached. For example, `target = c(0.8, 1.0)` with `is_relative = TRUE` |
|
2292 |
#' means that we target a dose with at least 80% of maximum biomarker level. |
|
2293 |
#' @slot is_relative (`flag`)\cr is target relative? If it so (default), then |
|
2294 |
#' the `target` is interpreted relative to the maximum, so it must be a |
|
2295 |
#' probability range. Otherwise, the `target` is interpreted as absolute |
|
2296 |
#' biomarker range. |
|
2297 |
#' @slot prob (`proportion`)\cr required target probability (except 0 or 1) for |
|
2298 |
#' reaching sufficient precision. |
|
2299 |
#' |
|
2300 |
#' @aliases StoppingTargetBiomarker |
|
2301 |
#' @export |
|
2302 |
#' |
|
2303 |
.StoppingTargetBiomarker <- setClass( |
|
2304 |
Class = "StoppingTargetBiomarker", |
|
2305 |
slots = c( |
|
2306 |
target = "numeric", |
|
2307 |
is_relative = "logical", |
|
2308 |
prob = "numeric" |
|
2309 |
), |
|
2310 |
prototype = prototype( |
|
2311 |
target = c(0.9, 1), |
|
2312 |
is_relative = TRUE, |
|
2313 |
prob = 0.3 |
|
2314 |
), |
|
2315 |
contains = "Stopping", |
|
2316 |
validity = v_stopping_target_biomarker |
|
2317 |
) |
|
2318 | ||
2319 |
## constructor ---- |
|
2320 | ||
2321 |
#' @rdname StoppingTargetBiomarker-class |
|
2322 |
#' |
|
2323 |
#' @param target (`numeric`)\cr see slot definition. |
|
2324 |
#' @param prob (`proportion`)\cr see slot definition. |
|
2325 |
#' @param is_relative (`flag`)\cr see slot definition. |
|
2326 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
2327 |
#' |
|
2328 |
#' @export |
|
2329 |
#' @example examples/Rules-class-StoppingTargetBiomarker.R |
|
2330 |
#' |
|
2331 |
StoppingTargetBiomarker <- function(target = c(0.9, 1), |
|
2332 |
prob = 0.3, |
|
2333 |
is_relative = TRUE, |
|
2334 |
report_label = NA_character_) { |
|
2335 | 31x |
assert_numeric(target, len = 2) |
2336 | 31x |
assert_flag(is_relative) |
2337 | ||
2338 | 31x |
report_label <- h_default_if_empty( |
2339 | 31x |
as.character(report_label), |
2340 | 31x |
paste0( |
2341 | 31x |
"P(", target[1], " \u2264 ", "Biomarker \u2264 ", target[2], ") \u2265 ", prob, |
2342 | 31x |
ifelse(is_relative, " (relative)", " (absolute)") |
2343 |
) |
|
2344 |
) |
|
2345 | ||
2346 | 31x |
.StoppingTargetBiomarker( |
2347 | 31x |
target = target, |
2348 | 31x |
is_relative = is_relative, |
2349 | 31x |
prob = prob, |
2350 | 31x |
report_label = report_label |
2351 |
) |
|
2352 |
} |
|
2353 | ||
2354 |
## default constructor ---- |
|
2355 | ||
2356 |
#' @rdname StoppingTargetBiomarker-class |
|
2357 |
#' @note Typically, end users will not use the `.DefaultStoppingTargetBiomarker()` function. |
|
2358 |
#' @export |
|
2359 |
.DefaultStoppingTargetBiomarker <- function() { |
|
2360 | 7x |
StoppingTargetBiomarker( |
2361 | 7x |
target = c(0.9, 1), |
2362 | 7x |
prob = 0.5, |
2363 | 7x |
is_relative = TRUE |
2364 |
) |
|
2365 |
} |
|
2366 | ||
2367 |
# StoppingSpecificDose ---- |
|
2368 | ||
2369 |
## class ---- |
|
2370 | ||
2371 |
#' `StoppingSpecificDose` |
|
2372 |
#' |
|
2373 |
#' @description `r lifecycle::badge("experimental")` |
|
2374 |
#' |
|
2375 |
#' [`StoppingSpecificDose`] is the class for testing a stopping rule at specific |
|
2376 |
#' dose of the dose grid and not at the next best dose. |
|
2377 |
#' |
|
2378 |
#' @slot rule (`Stopping`)\cr a stopping rule available in this package. |
|
2379 |
#' @slot dose (`positive_number`)\cr a dose that is defined as part of the dose |
|
2380 |
#' grid of the data. |
|
2381 |
#' |
|
2382 |
#' @aliases StoppingSpecificDose |
|
2383 |
#' @export |
|
2384 |
#' |
|
2385 |
.StoppingSpecificDose <- setClass( |
|
2386 |
Class = "StoppingSpecificDose", |
|
2387 |
slots = c( |
|
2388 |
rule = "Stopping", |
|
2389 |
dose = "positive_number" |
|
2390 |
), |
|
2391 |
contains = "Stopping" |
|
2392 |
) |
|
2393 | ||
2394 |
## constructor ---- |
|
2395 | ||
2396 |
#' @rdname StoppingSpecificDose-class |
|
2397 |
#' |
|
2398 |
#' @param rule (`Stopping`)\cr see slot definition. |
|
2399 |
#' @param dose (`number`)\cr see slot definition. |
|
2400 |
#' @param report_label (`string` or `NA`) \cr see slot definition. |
|
2401 |
#' |
|
2402 |
#' @export |
|
2403 |
#' @example examples/Rules-class-StoppingSpecificDose.R |
|
2404 |
#' |
|
2405 |
StoppingSpecificDose <- function(rule = StoppingTargetProb(target = c(0, 0.3), prob = 0.8), |
|
2406 |
dose = 80, |
|
2407 |
report_label = NA_character_) { |
|
2408 | 20x |
report_label <- h_default_if_empty( |
2409 | 20x |
as.character(report_label), |
2410 | 20x |
paste0("Dose ", dose, " used for testing a stopping rule") |
2411 |
) |
|
2412 | ||
2413 | 20x |
.StoppingSpecificDose( |
2414 | 20x |
rule = rule, |
2415 | 20x |
dose = positive_number(dose), |
2416 | 20x |
report_label = report_label |
2417 |
) |
|
2418 |
} |
|
2419 | ||
2420 |
## default constructor ---- |
|
2421 | ||
2422 |
#' @rdname StoppingSpecificDose-class |
|
2423 |
#' @note Typically, end users will not use the `.DefaultStoppingSpecificDose()` function. |
|
2424 |
#' @export |
|
2425 |
.DefaultStoppingSpecificDose <- function() { |
|
2426 | 7x |
StoppingSpecificDose( |
2427 | 7x |
rule = StoppingTargetProb(target = c(0, 0.3), prob = 0.8), |
2428 | 7x |
dose = positive_number(80) |
2429 |
) |
|
2430 |
} |
|
2431 | ||
2432 |
# StoppingHighestDose ---- |
|
2433 | ||
2434 |
## class ---- |
|
2435 | ||
2436 |
#' `StoppingHighestDose` |
|
2437 |
#' |
|
2438 |
#' @description `r lifecycle::badge("experimental")` |
|
2439 |
#' |
|
2440 |
#' [`StoppingHighestDose`] is the class for stopping based on the highest dose. |
|
2441 |
#' That is, the stopping occurs when the highest dose is reached. |
|
2442 |
#' |
|
2443 |
#' @aliases StoppingHighestDose |
|
2444 |
#' @export |
|
2445 |
#' |
|
2446 |
.StoppingHighestDose <- setClass( |
|
2447 |
Class = "StoppingHighestDose", |
|
2448 |
contains = "Stopping" |
|
2449 |
) |
|
2450 | ||
2451 |
## constructor ---- |
|
2452 | ||
2453 |
#' @rdname StoppingHighestDose-class |
|
2454 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
2455 |
#' |
|
2456 |
#' @export |
|
2457 |
#' @example examples/Rules-class-StoppingHighestDose.R |
|
2458 |
#' |
|
2459 |
StoppingHighestDose <- function(report_label = NA_character_) { |
|
2460 | 23x |
report_label <- h_default_if_empty( |
2461 | 23x |
as.character(report_label), |
2462 | 23x |
"NBD is the highest dose" |
2463 |
) |
|
2464 | ||
2465 | 23x |
.StoppingHighestDose(report_label = report_label) |
2466 |
} |
|
2467 | ||
2468 |
## default constructor ---- |
|
2469 | ||
2470 |
#' @rdname StoppingHighestDose-class |
|
2471 |
#' @note Typically, end users will not use the `.DefaultStoppingHighestDose()` function. |
|
2472 |
#' @export |
|
2473 |
.DefaultStoppingHighestDose <- function() { |
|
2474 | 7x |
StoppingHighestDose() |
2475 |
} |
|
2476 | ||
2477 |
# StoppingTDCIRatio ---- |
|
2478 | ||
2479 |
## class ---- |
|
2480 | ||
2481 |
#' `StoppingTDCIRatio` |
|
2482 |
#' |
|
2483 |
#' @description `r lifecycle::badge("stable")` |
|
2484 |
#' |
|
2485 |
#' [`StoppingTDCIRatio`] is the class for testing a stopping rule that is based |
|
2486 |
#' on a target ratio of the 95% credibility interval. Specifically, this is the |
|
2487 |
#' ratio of the upper to the lower bound of the 95% credibility interval's |
|
2488 |
#' estimate of the target dose (i.e. a dose that corresponds to a given target |
|
2489 |
#' probability of the occurrence of a DLT `prob_target`). |
|
2490 |
#' |
|
2491 |
#' @slot target_ratio (`numeric`)\cr target for the ratio of the 95% credibility |
|
2492 |
#' interval's estimate, that is required to stop a trial. |
|
2493 |
#' @slot prob_target (`proportion`)\cr the target probability of the occurrence |
|
2494 |
#' of a DLT. |
|
2495 |
#' |
|
2496 |
#' @aliases StoppingTDCIRatio |
|
2497 |
#' @export |
|
2498 |
#' |
|
2499 |
.StoppingTDCIRatio <- setClass( |
|
2500 |
Class = "StoppingTDCIRatio", |
|
2501 |
slots = c( |
|
2502 |
target_ratio = "numeric", |
|
2503 |
prob_target = "numeric" |
|
2504 |
), |
|
2505 |
prototype = prototype( |
|
2506 |
target_ratio = 5, |
|
2507 |
prob_target = 0.3 |
|
2508 |
), |
|
2509 |
contains = "Stopping", |
|
2510 |
validity = v_stopping_tdci_ratio |
|
2511 |
) |
|
2512 | ||
2513 |
## constructor ---- |
|
2514 | ||
2515 |
#' @rdname StoppingTDCIRatio-class |
|
2516 |
#' |
|
2517 |
#' @param target_ratio (`numeric`)\cr see slot definition. |
|
2518 |
#' @param prob_target (`proportion`)\cr see slot definition. |
|
2519 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
2520 |
#' |
|
2521 |
#' @export |
|
2522 |
#' @example examples/Rules-class-StoppingTDCIRatio.R |
|
2523 |
#' |
|
2524 |
StoppingTDCIRatio <- function(target_ratio = 5, |
|
2525 |
prob_target = 0.3, |
|
2526 |
report_label = NA_character_) { |
|
2527 | 979x |
report_label <- h_default_if_empty( |
2528 | 979x |
as.character(report_label), |
2529 | 979x |
paste("TD", target_ratio, "for", prob_target, "target prob") |
2530 |
) |
|
2531 | ||
2532 | 979x |
.StoppingTDCIRatio( |
2533 | 979x |
target_ratio = target_ratio, |
2534 | 979x |
prob_target = prob_target, |
2535 | 979x |
report_label = report_label |
2536 |
) |
|
2537 |
} |
|
2538 | ||
2539 |
## default constructor ---- |
|
2540 | ||
2541 |
#' @rdname StoppingTDCIRatio-class |
|
2542 |
#' @note Typically, end users will not use the `.DefaultStoppingTDCIRatio()` function. |
|
2543 |
#' @export |
|
2544 |
.DefaultStoppingTDCIRatio <- function() { |
|
2545 | 7x |
StoppingTDCIRatio( |
2546 | 7x |
target_ratio = 5, |
2547 | 7x |
prob_target = 0.3 |
2548 |
) |
|
2549 |
} |
|
2550 | ||
2551 |
# StoppingMaxGainCIRatio ---- |
|
2552 | ||
2553 |
## class ---- |
|
2554 | ||
2555 |
#' `StoppingMaxGainCIRatio` |
|
2556 |
#' |
|
2557 |
#' @description `r lifecycle::badge("stable")` |
|
2558 |
#' |
|
2559 |
#' [`StoppingMaxGainCIRatio`] is the class for testing a stopping rule that is based |
|
2560 |
#' on a target ratio of the 95% credibility interval. Specifically, this is the |
|
2561 |
#' ratio of the upper to the lower bound of the 95% credibility interval's |
|
2562 |
#' estimate of the: |
|
2563 |
#' (1) target dose (i.e. a dose that corresponds to a given target |
|
2564 |
#' probability of the occurrence of a DLT `prob_target`), or |
|
2565 |
#' (2) max gain dose (i.e. a dose which gives the maximum gain), |
|
2566 |
#' depending on which one out of these two is smaller. |
|
2567 |
#' |
|
2568 |
#' @slot target_ratio (`numeric`)\cr target for the ratio of the 95% credibility |
|
2569 |
#' interval's estimate, that is required to stop a trial. |
|
2570 |
#' @slot prob_target (`proportion`)\cr the target probability of the occurrence |
|
2571 |
#' of a DLT. |
|
2572 |
#' |
|
2573 |
#' @aliases StoppingMaxGainCIRatio |
|
2574 |
#' @export |
|
2575 |
#' |
|
2576 |
.StoppingMaxGainCIRatio <- setClass( |
|
2577 |
Class = "StoppingMaxGainCIRatio", |
|
2578 |
slots = c( |
|
2579 |
target_ratio = "numeric", |
|
2580 |
prob_target = "numeric" |
|
2581 |
), |
|
2582 |
prototype = prototype( |
|
2583 |
target_ratio = 5, |
|
2584 |
prob_target = 0.3 |
|
2585 |
), |
|
2586 |
contains = "Stopping", |
|
2587 |
validity = v_stopping_tdci_ratio |
|
2588 |
) |
|
2589 | ||
2590 |
## constructor ---- |
|
2591 | ||
2592 |
#' @rdname StoppingMaxGainCIRatio-class |
|
2593 |
#' |
|
2594 |
#' @param target_ratio (`numeric`)\cr see slot definition. |
|
2595 |
#' @param prob_target (`proportion`)\cr see slot definition. |
|
2596 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
2597 |
#' |
|
2598 |
#' @export |
|
2599 |
#' @example examples/Rules-class-StoppingMaxGainCIRatio.R |
|
2600 |
#' |
|
2601 |
StoppingMaxGainCIRatio <- function(target_ratio = 5, |
|
2602 |
prob_target = 0.3, |
|
2603 |
report_label = NA_character_) { |
|
2604 | 13x |
report_label <- h_default_if_empty( |
2605 | 13x |
as.character(report_label), |
2606 | 13x |
paste("GStar", target_ratio, "for", prob_target, "target prob") |
2607 |
) |
|
2608 | ||
2609 | 13x |
.StoppingMaxGainCIRatio( |
2610 | 13x |
target_ratio = target_ratio, |
2611 | 13x |
prob_target = prob_target, |
2612 | 13x |
report_label = report_label |
2613 |
) |
|
2614 |
} |
|
2615 | ||
2616 | ||
2617 |
## default constructor ---- |
|
2618 | ||
2619 |
#' @rdname StoppingMaxGainCIRatio-class |
|
2620 |
#' @examples |
|
2621 |
#' .DefaultStoppingMaxGainCIRatio() |
|
2622 |
#' @export |
|
2623 |
.DefaultStoppingMaxGainCIRatio <- function() { |
|
2624 | 7x |
StoppingMaxGainCIRatio( |
2625 | 7x |
target_ratio = 5, |
2626 | 7x |
prob_target = 0.3 |
2627 |
) |
|
2628 |
} |
|
2629 | ||
2630 |
# StoppingList ---- |
|
2631 | ||
2632 |
## class ---- |
|
2633 | ||
2634 |
#' `StoppingList` |
|
2635 |
#' |
|
2636 |
#' @description `r lifecycle::badge("stable")` |
|
2637 |
#' |
|
2638 |
#' [`StoppingList`] is the class for testing a stopping rule that consists of |
|
2639 |
#' many single stopping rules that are in turn the objects of class `Stopping`. |
|
2640 |
#' The `summary` slot stores a function that takes a logical vector of the size |
|
2641 |
#' of `stop_list` and returns a single logical value. For example, if the function |
|
2642 |
#' `all` is specified as a `summary` function, then that all stopping rules |
|
2643 |
#' defined in `stop_list` must be satisfied in order the result of this rule to |
|
2644 |
#' be `TRUE`. |
|
2645 |
#' |
|
2646 |
#' @slot stop_list (`list`)\cr list of stopping rules. |
|
2647 |
#' @slot summary (`function`)\cr a summary function to combine the results of |
|
2648 |
#' the stopping rules into a single result. |
|
2649 |
#' |
|
2650 |
#' @aliases StoppingList |
|
2651 |
#' @export |
|
2652 |
#' |
|
2653 |
.StoppingList <- setClass( |
|
2654 |
Class = "StoppingList", |
|
2655 |
slots = c( |
|
2656 |
stop_list = "list", |
|
2657 |
summary = "function" |
|
2658 |
), |
|
2659 |
prototype = prototype( |
|
2660 |
stop_list = list(StoppingMinPatients(50), StoppingMinCohorts(5)), |
|
2661 |
summary = all |
|
2662 |
), |
|
2663 |
contains = "Stopping", |
|
2664 |
validity = v_stopping_list |
|
2665 |
) |
|
2666 | ||
2667 |
## constructor ---- |
|
2668 | ||
2669 |
#' @rdname StoppingList-class |
|
2670 |
#' |
|
2671 |
#' @param stop_list (`list`)\cr see slot definition. |
|
2672 |
#' @param summary (`function`)\cr see slot definition. |
|
2673 |
#' |
|
2674 |
#' @export |
|
2675 |
#' @example examples/Rules-class-StoppingList.R |
|
2676 |
#' |
|
2677 |
StoppingList <- function(stop_list, summary) { |
|
2678 | 18x |
.StoppingList( |
2679 | 18x |
stop_list = stop_list, |
2680 | 18x |
summary = summary |
2681 |
) |
|
2682 |
} |
|
2683 | ||
2684 |
## default constructor ---- |
|
2685 | ||
2686 |
#' @rdname StoppingList-class |
|
2687 |
#' @note Typically, end users will not use the `.DefaultStoppingList()` function. |
|
2688 |
#' @export |
|
2689 |
.DefaultStoppingList <- function() { |
|
2690 | 8x |
StoppingList( |
2691 | 8x |
stop_list = c( |
2692 | 8x |
StoppingMinCohorts(nCohorts = 3L), |
2693 | 8x |
StoppingTargetProb(target = c(0.2, 0.35), prob = 0.5), |
2694 | 8x |
StoppingMinPatients(nPatients = 20L) |
2695 |
), |
|
2696 | 8x |
summary = any |
2697 |
) |
|
2698 |
} |
|
2699 | ||
2700 |
# StoppingAll ---- |
|
2701 | ||
2702 |
## class ---- |
|
2703 | ||
2704 |
#' `StoppingAll` |
|
2705 |
#' |
|
2706 |
#' @description `r lifecycle::badge("stable")` |
|
2707 |
#' |
|
2708 |
#' [`StoppingAll`] is the class for testing a stopping rule that consists of |
|
2709 |
#' many single stopping rules that are in turn the objects of class `Stopping`. |
|
2710 |
#' All single stopping rules must be satisfied in order the result of this rule |
|
2711 |
#' to be `TRUE`. |
|
2712 |
#' |
|
2713 |
#' @slot stop_list (`list`)\cr list of stopping rules. |
|
2714 |
#' @slot report_label label for reporting |
|
2715 |
#' @aliases StoppingAll |
|
2716 |
#' @export |
|
2717 |
#' |
|
2718 |
.StoppingAll <- setClass( |
|
2719 |
Class = "StoppingAll", |
|
2720 |
slots = c( |
|
2721 |
stop_list = "list" |
|
2722 |
), |
|
2723 |
prototype = prototype( |
|
2724 |
stop_list = list( |
|
2725 |
StoppingMinPatients(50), |
|
2726 |
StoppingMinCohorts(5) |
|
2727 |
) |
|
2728 |
), |
|
2729 |
contains = "Stopping", |
|
2730 |
validity = v_stopping_all |
|
2731 |
) |
|
2732 | ||
2733 |
## constructor ---- |
|
2734 | ||
2735 |
#' @rdname StoppingAll-class |
|
2736 |
#' |
|
2737 |
#' @param stop_list (`list`)\cr see slot definition. |
|
2738 |
#' @param report_label (`string`) \cr see slot definition. |
|
2739 |
#' @export |
|
2740 |
#' @example examples/Rules-class-StoppingAll.R |
|
2741 |
#' |
|
2742 |
StoppingAll <- function(stop_list, report_label = NA_character_) { |
|
2743 | 36x |
.StoppingAll( |
2744 | 36x |
stop_list = stop_list, |
2745 | 36x |
report_label = report_label |
2746 |
) |
|
2747 |
} |
|
2748 |
## default constructor ---- |
|
2749 | ||
2750 |
#' @rdname StoppingAll-class |
|
2751 |
#' @note Typically, end users will not use the `.DefaultStoppingAll()` function. |
|
2752 |
#' @export |
|
2753 |
.DefaultStoppingAll <- function() { |
|
2754 | 8x |
StoppingAll( |
2755 | 8x |
stop_list = c( |
2756 | 8x |
StoppingMinCohorts(nCohorts = 3L), |
2757 | 8x |
StoppingTargetProb(target = c(0.2, 0.35), prob = 0.5), |
2758 | 8x |
StoppingMinPatients(nPatients = 20L) |
2759 |
) |
|
2760 |
) |
|
2761 |
} |
|
2762 | ||
2763 |
# StoppingAny ---- |
|
2764 | ||
2765 |
## class ---- |
|
2766 | ||
2767 |
#' `StoppingAny` |
|
2768 |
#' |
|
2769 |
#' @description `r lifecycle::badge("stable")` |
|
2770 |
#' |
|
2771 |
#' [`StoppingAny`] is the class for testing a stopping rule that consists of |
|
2772 |
#' many single stopping rules that are in turn the objects of class `Stopping`. |
|
2773 |
#' At least one single stopping rule must be satisfied in order the result of |
|
2774 |
#' this rule to be `TRUE`. |
|
2775 |
#' |
|
2776 |
#' @slot stop_list (`list`)\cr list of stopping rules. |
|
2777 |
#' @slot report_label label for reporting |
|
2778 |
#' |
|
2779 |
#' @aliases StoppingAny |
|
2780 |
#' @export |
|
2781 |
#' |
|
2782 |
.StoppingAny <- setClass( |
|
2783 |
Class = "StoppingAny", |
|
2784 |
slots = c( |
|
2785 |
stop_list = "list" |
|
2786 |
), |
|
2787 |
prototype = prototype( |
|
2788 |
stop_list = list(StoppingMinPatients(50), StoppingMinCohorts(5)) |
|
2789 |
), |
|
2790 |
contains = "Stopping", |
|
2791 |
validity = v_stopping_all |
|
2792 |
) |
|
2793 | ||
2794 |
## constructor ---- |
|
2795 | ||
2796 |
#' @rdname StoppingAny-class |
|
2797 |
#' |
|
2798 |
#' @param stop_list (`list`)\cr see slot definition. |
|
2799 |
#' @param report_label (`string`)\cr see slot definition. |
|
2800 |
#' |
|
2801 |
#' @export |
|
2802 |
#' @example examples/Rules-class-StoppingAny.R |
|
2803 |
#' |
|
2804 |
StoppingAny <- function(stop_list, report_label = NA_character_) { |
|
2805 | 54x |
.StoppingAny( |
2806 | 54x |
stop_list = stop_list, |
2807 | 54x |
report_label = report_label |
2808 |
) |
|
2809 |
} |
|
2810 | ||
2811 |
## default constructor ---- |
|
2812 | ||
2813 |
#' @rdname StoppingAny-class |
|
2814 |
#' @note Typically, end users will not use the `.DefaultStoppingAny()` function. |
|
2815 |
#' @export |
|
2816 |
.DefaultStoppingAny <- function() { |
|
2817 | 8x |
StoppingAny( |
2818 | 8x |
stop_list = c( |
2819 | 8x |
StoppingMinCohorts(nCohorts = 3L), |
2820 | 8x |
StoppingTargetProb(target = c(0.2, 0.35), prob = 0.5), |
2821 | 8x |
StoppingMinPatients(nPatients = 20L) |
2822 |
) |
|
2823 |
) |
|
2824 |
} |
|
2825 | ||
2826 |
# StoppingOrdinal ---- |
|
2827 | ||
2828 |
## class ---- |
|
2829 | ||
2830 |
#' `StoppingOrdinal` |
|
2831 |
#' |
|
2832 |
#' @description `r lifecycle::badge("experimental")` |
|
2833 |
#' |
|
2834 |
#' [`StoppingOrdinal`] is the class for stopping based on a Stopping rule applied |
|
2835 |
#' to a specific toxicity grade in an ordinal CRM trial |
|
2836 |
#' |
|
2837 |
#' @slot grade (`integer`)\cr the grade to which the rule should be applied |
|
2838 |
#' @slot rule (`Stopping`)\cr the rule to apply |
|
2839 |
#' |
|
2840 |
#' @aliases StoppingOrdinal |
|
2841 |
#' @export |
|
2842 |
#' |
|
2843 |
.StoppingOrdinal <- setClass( |
|
2844 |
Class = "StoppingOrdinal", |
|
2845 |
slots = c(grade = "integer", rule = "Stopping"), |
|
2846 |
contains = "Stopping" |
|
2847 |
) |
|
2848 | ||
2849 |
## constructor ---- |
|
2850 | ||
2851 |
#' @rdname StoppingOrdinal-class |
|
2852 |
#' @param grade (`integer`)\cr see slot definition. |
|
2853 |
#' @param rule (`Stopping`)\cr see slot definition. |
|
2854 |
#' @example examples/Rules-class-StoppingOrdinal.R |
|
2855 |
#' @export |
|
2856 |
#' |
|
2857 |
StoppingOrdinal <- function(grade, rule) { |
|
2858 | 16x |
.StoppingOrdinal(grade = grade, rule = rule) |
2859 |
} |
|
2860 | ||
2861 |
## default constructor ---- |
|
2862 | ||
2863 |
#' @rdname StoppingOrdinal-class |
|
2864 |
#' @note Typically, end users will not use the `.DefaultStoppingOrdinal()` function. |
|
2865 |
#' @export |
|
2866 |
#' |
|
2867 |
.DefaultStoppingOrdinal <- function() { |
|
2868 | 11x |
StoppingOrdinal( |
2869 | 11x |
1L, |
2870 | 11x |
StoppingTargetProb(target = c(0.2, 0.35), prob = 0.6) |
2871 |
) |
|
2872 |
} |
|
2873 | ||
2874 |
# StoppingExternal ---- |
|
2875 | ||
2876 |
## class ---- |
|
2877 | ||
2878 |
#' `StoppingExternal` |
|
2879 |
#' |
|
2880 |
#' @description `r lifecycle::badge("experimental")` |
|
2881 |
#' |
|
2882 |
#' [`StoppingExternal`] is the class for stopping based on an external flag. |
|
2883 |
#' |
|
2884 |
#' @aliases StoppingExternal |
|
2885 |
#' @export |
|
2886 |
#' |
|
2887 |
.StoppingExternal <- setClass( |
|
2888 |
Class = "StoppingExternal", |
|
2889 |
contains = "Stopping" |
|
2890 |
) |
|
2891 | ||
2892 |
## constructor ---- |
|
2893 | ||
2894 |
#' @rdname StoppingExternal-class |
|
2895 |
#' @param report_label (`string` or `NA`)\cr see slot definition. |
|
2896 |
#' @example examples/Rules-class-StoppingExternal.R |
|
2897 |
#' @export |
|
2898 |
#' |
|
2899 |
StoppingExternal <- function(report_label = NA_character_) { |
|
2900 | 13x |
report_label <- h_default_if_empty( |
2901 | 13x |
as.character(report_label), |
2902 | 13x |
paste("Stopped because of external flag") |
2903 |
) |
|
2904 | 13x |
.StoppingExternal(report_label = report_label) |
2905 |
} |
|
2906 | ||
2907 |
## default constructor ---- |
|
2908 | ||
2909 |
#' @rdname StoppingExternal-class |
|
2910 |
#' @note Typically, end users will not use the `.DefaultStoppingExternal()` function. |
|
2911 |
#' @export |
|
2912 |
#' |
|
2913 |
.DefaultStoppingExternal <- StoppingExternal |
|
2914 | ||
2915 |
# CohortSize ---- |
|
2916 | ||
2917 |
## class ---- |
|
2918 | ||
2919 |
#' `CohortSize` |
|
2920 |
#' |
|
2921 |
#' @description `r lifecycle::badge("stable")` |
|
2922 |
#' |
|
2923 |
#' [`CohortSize`] is a class for cohort sizes. |
|
2924 |
#' |
|
2925 |
#' @seealso [`CohortSizeRange`], [`CohortSizeDLT`], [`CohortSizeConst`], |
|
2926 |
#' [`CohortSizeParts`], [`CohortSizeMin`], [`CohortSizeMin`]. |
|
2927 |
#' |
|
2928 |
#' @aliases CohortSize |
|
2929 |
#' @export |
|
2930 |
#' |
|
2931 |
setClass( |
|
2932 |
Class = "CohortSize", |
|
2933 |
contains = "CrmPackClass" |
|
2934 |
) |
|
2935 | ||
2936 |
## default constructor |
|
2937 | ||
2938 |
#' @rdname CohortSize-class |
|
2939 |
#' @note Typically, end users will not use the `DefaultCohortSize()` function. |
|
2940 |
#' @export |
|
2941 |
.DefaultCohortSize <- function() { |
|
2942 | 2x |
stop(paste0("Class CohortSize should not be instantiated directly. Please use one of its subclasses instead.")) |
2943 |
} |
|
2944 | ||
2945 |
# CohortSizeRange ---- |
|
2946 | ||
2947 |
## class ---- |
|
2948 | ||
2949 |
#' `CohortSizeRange` |
|
2950 |
#' |
|
2951 |
#' @description `r lifecycle::badge("stable")` |
|
2952 |
#' |
|
2953 |
#' [`CohortSizeRange`] is the class for cohort size based on dose range. |
|
2954 |
#' |
|
2955 |
#' @slot intervals (`numeric`)\cr a vector with the left bounds of the relevant |
|
2956 |
#' dose intervals. |
|
2957 |
#' @slot cohort_size (`integer`)\cr an integer vector with the cohort sizes |
|
2958 |
#' corresponding to the elements of `intervals`. |
|
2959 |
#' |
|
2960 |
#' @aliases CohortSizeRange |
|
2961 |
#' @export |
|
2962 |
#' |
|
2963 |
.CohortSizeRange <- setClass( |
|
2964 |
Class = "CohortSizeRange", |
|
2965 |
slots = c( |
|
2966 |
intervals = "numeric", |
|
2967 |
cohort_size = "integer" |
|
2968 |
), |
|
2969 |
prototype = prototype( |
|
2970 |
intervals = c(0, 20), |
|
2971 |
cohort_size = c(1L, 3L) |
|
2972 |
), |
|
2973 |
contains = "CohortSize", |
|
2974 |
validity = v_cohort_size_range |
|
2975 |
) |
|
2976 | ||
2977 |
## constructor ---- |
|
2978 | ||
2979 |
#' @rdname CohortSizeRange-class |
|
2980 |
#' |
|
2981 |
#' @param intervals (`numeric`)\cr see slot definition. |
|
2982 |
#' @param cohort_size (`numeric`)\cr see slot definition. |
|
2983 |
#' |
|
2984 |
#' @export |
|
2985 |
#' @example examples/Rules-class-CohortSizeRange.R |
|
2986 |
#' |
|
2987 |
CohortSizeRange <- function(intervals, cohort_size) { |
|
2988 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
2989 | 85x |
assert_integerish(cohort_size, lower = 0, any.missing = FALSE) |
2990 | ||
2991 | 85x |
.CohortSizeRange( |
2992 | 85x |
intervals = intervals, |
2993 | 85x |
cohort_size = as.integer(cohort_size) |
2994 |
) |
|
2995 |
} |
|
2996 | ||
2997 |
## default constructor ---- |
|
2998 | ||
2999 |
#' @rdname CohortSizeRange-class |
|
3000 |
#' @note Typically, end users will not use the `.DefaultCohortSizeRange()` function. |
|
3001 |
#' @export |
|
3002 |
.DefaultCohortSizeRange <- function() { |
|
3003 | 9x |
CohortSizeRange(intervals = c(0L, 30L), cohort_size = c(1L, 3L)) |
3004 |
} |
|
3005 | ||
3006 |
# CohortSizeDLT ---- |
|
3007 | ||
3008 |
## class ---- |
|
3009 | ||
3010 |
#' `CohortSizeDLT` |
|
3011 |
#' |
|
3012 |
#' @description `r lifecycle::badge("stable")` |
|
3013 |
#' |
|
3014 |
#' [`CohortSizeDLT`] is the class for cohort size based on number of DLTs. |
|
3015 | ||
3016 |
#' @slot intervals (`integer`)\cr a vector with the left bounds of the |
|
3017 |
#' relevant DLT intervals. |
|
3018 |
#' @slot cohort_size (`integer`)\cr a vector with the cohort sizes corresponding |
|
3019 |
#' to the elements of `intervals`. |
|
3020 |
#' |
|
3021 |
#' @aliases CohortSizeDLT |
|
3022 |
#' @export |
|
3023 |
#' |
|
3024 |
.CohortSizeDLT <- setClass( |
|
3025 |
Class = "CohortSizeDLT", |
|
3026 |
slots = c( |
|
3027 |
intervals = "integer", |
|
3028 |
cohort_size = "integer" |
|
3029 |
), |
|
3030 |
prototype = prototype( |
|
3031 |
intervals = c(0L, 1L), |
|
3032 |
cohort_size = c(1L, 3L) |
|
3033 |
), |
|
3034 |
contains = "CohortSize", |
|
3035 |
validity = v_cohort_size_dlt |
|
3036 |
) |
|
3037 | ||
3038 |
## constructor ---- |
|
3039 | ||
3040 |
#' @rdname CohortSizeDLT-class |
|
3041 |
#' |
|
3042 |
#' @param intervals (`numeric`)\cr see slot definition. |
|
3043 |
#' @param cohort_size (`numeric`)\cr see slot definition. |
|
3044 |
#' |
|
3045 |
#' @export |
|
3046 |
#' @example examples/Rules-class-CohortSizeDLT.R |
|
3047 |
#' |
|
3048 |
CohortSizeDLT <- function(intervals, cohort_size) { |
|
3049 | 71x |
assert_integerish(intervals, lower = 0, any.missing = FALSE) |
3050 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
3051 | 71x |
assert_integerish(cohort_size, lower = 0, any.missing = FALSE) |
3052 | ||
3053 | 71x |
.CohortSizeDLT( |
3054 | 71x |
intervals = as.integer(intervals), |
3055 | 71x |
cohort_size = as.integer(cohort_size) |
3056 |
) |
|
3057 |
} |
|
3058 | ||
3059 |
## default constructor ---- |
|
3060 | ||
3061 |
#' @rdname CohortSizeDLT-class |
|
3062 |
#' @note Typically, end users will not use the `.DefaultCohortSizeDLT()` function. |
|
3063 |
#' @export |
|
3064 |
.DefaultCohortSizeDLT <- function() { |
|
3065 | 7x |
CohortSizeDLT(intervals = c(0L, 1L), cohort_size = c(1L, 3L)) |
3066 |
} |
|
3067 | ||
3068 | ||
3069 |
# CohortSizeConst ---- |
|
3070 | ||
3071 |
## class ---- |
|
3072 | ||
3073 |
#' `CohortSizeConst` |
|
3074 |
#' |
|
3075 |
#' @description `r lifecycle::badge("stable")` |
|
3076 |
#' |
|
3077 |
#' [`CohortSizeConst`] is the class for fixed and constant size of cohort. |
|
3078 |
#' |
|
3079 |
#' @slot size (`integer`)\cr cohort size. |
|
3080 |
#' |
|
3081 |
#' @aliases CohortSizeConst |
|
3082 |
#' @export |
|
3083 |
#' |
|
3084 |
.CohortSizeConst <- setClass( |
|
3085 |
Class = "CohortSizeConst", |
|
3086 |
slots = c(size = "integer"), |
|
3087 |
prototype = prototype(size = 3L), |
|
3088 |
contains = "CohortSize", |
|
3089 |
validity = v_cohort_size_const |
|
3090 |
) |
|
3091 | ||
3092 |
## constructor ---- |
|
3093 | ||
3094 |
#' @rdname CohortSizeConst-class |
|
3095 |
#' |
|
3096 |
#' @param size (`number`)\cr see slot definition. |
|
3097 |
#' |
|
3098 |
#' @export |
|
3099 |
#' @example examples/Rules-class-CohortSizeConst.R |
|
3100 |
#' |
|
3101 |
CohortSizeConst <- function(size) { |
|
3102 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
3103 | 290x |
assert_integerish(size, lower = 0) |
3104 | 290x |
.CohortSizeConst(size = as.integer(size)) |
3105 |
} |
|
3106 | ||
3107 |
## default constructor ---- |
|
3108 | ||
3109 |
#' @rdname CohortSizeConst-class |
|
3110 |
#' @note Typically, end users will not use the `.DefaultCohortSizeConst()` function. |
|
3111 |
#' @export |
|
3112 |
.DefaultCohortSizeConst <- function() { |
|
3113 | 6x |
CohortSizeConst(size = 3L) |
3114 |
} |
|
3115 | ||
3116 |
# CohortSizeParts ---- |
|
3117 | ||
3118 |
## class ---- |
|
3119 | ||
3120 |
#' `CohortSizeParts` |
|
3121 |
#' |
|
3122 |
#' @description `r lifecycle::badge("stable")` |
|
3123 |
#' |
|
3124 |
#' [`CohortSizeParts`] is the class for cohort size that changes for the second |
|
3125 |
#' part of the dose escalation. It works only in conjunction with [`DataParts`] |
|
3126 |
#' objects. |
|
3127 |
#' |
|
3128 |
#' @slot cohort_sizes (`integer`)\cr a vector of length two with two sizes, one for |
|
3129 |
#' part 1, and one for part 2 respectively. |
|
3130 |
#' |
|
3131 |
#' @aliases CohortSizeParts |
|
3132 |
#' @export |
|
3133 |
#' |
|
3134 |
.CohortSizeParts <- setClass( |
|
3135 |
Class = "CohortSizeParts", |
|
3136 |
slots = c(cohort_sizes = "integer"), |
|
3137 |
prototype = prototype(cohort_sizes = c(1L, 3L)), |
|
3138 |
contains = "CohortSize", |
|
3139 |
validity = v_cohort_size_parts |
|
3140 |
) |
|
3141 | ||
3142 |
## constructor ---- |
|
3143 | ||
3144 |
#' @rdname CohortSizeParts-class |
|
3145 |
#' |
|
3146 |
#' @param cohort_sizes (`numeric`)\cr see slot definition. |
|
3147 |
#' |
|
3148 |
#' @export |
|
3149 |
#' @example examples/Rules-class-CohortSizeParts.R |
|
3150 |
#' |
|
3151 |
CohortSizeParts <- function(cohort_sizes) { |
|
3152 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
3153 | 16x |
assert_integerish(cohort_sizes, lower = 0, any.missing = FALSE) |
3154 | 16x |
.CohortSizeParts(cohort_sizes = as.integer(cohort_sizes)) |
3155 |
} |
|
3156 | ||
3157 |
## default constructor ---- |
|
3158 | ||
3159 |
#' @rdname CohortSizeParts-class |
|
3160 |
#' @note Typically, end users will not use the `.DefaultCohortSizeParts()` function. |
|
3161 |
#' @export |
|
3162 |
.DefaultCohortSizeParts <- function() { |
|
3163 | 7x |
CohortSizeParts(cohort_sizes = c(1L, 3L)) |
3164 |
} |
|
3165 | ||
3166 |
# CohortSizeMax ---- |
|
3167 | ||
3168 |
## class ---- |
|
3169 | ||
3170 |
#' `CohortSizeMax` |
|
3171 |
#' |
|
3172 |
#' @description `r lifecycle::badge("stable")` |
|
3173 |
#' |
|
3174 |
#' [`CohortSizeMax`] is the class for cohort size that is based on maximum of |
|
3175 |
#' multiple cohort size rules. The `cohort_sizes` slot stores a set of cohort |
|
3176 |
#' size rules, which are again the objects of class [`CohortSize`]. The maximum |
|
3177 |
#' of these individual cohort sizes is taken to give the final cohort size. |
|
3178 |
#' |
|
3179 |
#' @slot cohort_sizes (`list`)\cr a list of cohort size rules, i.e. objects |
|
3180 |
#' of class [`CohortSize`]. |
|
3181 |
#' |
|
3182 |
#' @aliases CohortSizeMax |
|
3183 |
#' @export |
|
3184 |
#' |
|
3185 |
.CohortSizeMax <- setClass( |
|
3186 |
Class = "CohortSizeMax", |
|
3187 |
slots = c(cohort_sizes = "list"), |
|
3188 |
prototype = prototype( |
|
3189 |
cohort_sizes = list( |
|
3190 |
CohortSizeRange(intervals = c(0, 30), cohort_size = c(1, 3)), |
|
3191 |
CohortSizeDLT(intervals = c(0, 1), cohort_size = c(1, 3)) |
|
3192 |
) |
|
3193 |
), |
|
3194 |
contains = "CohortSize", |
|
3195 |
validity = v_cohort_size_max |
|
3196 |
) |
|
3197 | ||
3198 |
## default constructor ---- |
|
3199 | ||
3200 |
#' @rdname CohortSizeMax-class |
|
3201 |
#' @note Typically, end users will not use the `.DefaultCohortSizeMax()` function. |
|
3202 |
#' |
|
3203 |
#' @export |
|
3204 |
.DefaultCohortSizeMax <- function() { |
|
3205 | 7x |
CohortSizeMax( |
3206 | 7x |
cohort_sizes = list( |
3207 | 7x |
CohortSizeRange(intervals = c(0, 10), cohort_size = c(1L, 3L)), |
3208 | 7x |
CohortSizeDLT(intervals = c(0L, 1L), cohort_size = c(1L, 3L)) |
3209 |
) |
|
3210 |
) |
|
3211 |
} |
|
3212 | ||
3213 |
## constructor ---- |
|
3214 | ||
3215 |
#' @rdname CohortSizeMax-class |
|
3216 |
#' |
|
3217 |
#' @param cohort_sizes (`list`)\cr see slot definition. |
|
3218 |
#' |
|
3219 |
#' @export |
|
3220 |
#' @example examples/Rules-class-CohortSizeMax.R |
|
3221 |
#' |
|
3222 |
CohortSizeMax <- function(cohort_sizes) { |
|
3223 | 46x |
.CohortSizeMax(cohort_sizes = cohort_sizes) |
3224 |
} |
|
3225 | ||
3226 |
# CohortSizeMin ---- |
|
3227 | ||
3228 |
## class ---- |
|
3229 | ||
3230 |
#' `CohortSizeMin` |
|
3231 |
#' |
|
3232 |
#' @description `r lifecycle::badge("stable")` |
|
3233 |
#' |
|
3234 |
#' [`CohortSizeMin`] is the class for cohort size that is based on minimum of |
|
3235 |
#' multiple cohort size rules. The `cohort_sizes` slot stores a set of cohort |
|
3236 |
#' size rules, which are again the objects of class [`CohortSize`]. The minimum |
|
3237 |
#' of these individual cohort sizes is taken to give the final cohort size. |
|
3238 |
#' |
|
3239 |
#' @slot cohort_sizes (`list`)\cr a list of cohort size rules, i.e. objects |
|
3240 |
#' of class [`CohortSize`]. |
|
3241 |
#' |
|
3242 |
#' @aliases CohortSizeMin |
|
3243 |
#' @export |
|
3244 |
#' |
|
3245 |
.CohortSizeMin <- setClass( |
|
3246 |
Class = "CohortSizeMin", |
|
3247 |
slots = c(cohort_sizes = "list"), |
|
3248 |
prototype = prototype( |
|
3249 |
cohort_sizes = |
|
3250 |
list( |
|
3251 |
CohortSizeRange(intervals = c(0, 30), cohort_size = c(1, 3)), |
|
3252 |
CohortSizeDLT(intervals = c(0, 1), cohort_size = c(1, 3)) |
|
3253 |
) |
|
3254 |
), |
|
3255 |
contains = "CohortSize", |
|
3256 |
validity = v_cohort_size_max |
|
3257 |
) |
|
3258 | ||
3259 |
## constructor ---- |
|
3260 | ||
3261 |
#' @rdname CohortSizeMin-class |
|
3262 |
#' |
|
3263 |
#' @param cohort_sizes (`list`)\cr see slot definition. |
|
3264 |
#' |
|
3265 |
#' @export |
|
3266 |
#' @example examples/Rules-class-CohortSizeMin.R |
|
3267 |
#' |
|
3268 |
CohortSizeMin <- function(cohort_sizes) { |
|
3269 | 13x |
.CohortSizeMin(cohort_sizes = cohort_sizes) |
3270 |
} |
|
3271 | ||
3272 |
## default constructor ---- |
|
3273 | ||
3274 |
#' @rdname CohortSizeMin-class |
|
3275 |
#' @note Typically, end users will not use the `.DefaultCohortSizeMin()` function. |
|
3276 |
#' @export |
|
3277 |
.DefaultCohortSizeMin <- function() { |
|
3278 | 7x |
CohortSizeMin( |
3279 | 7x |
cohort_sizes = list( |
3280 | 7x |
CohortSizeRange(intervals = c(0, 10), cohort_size = c(1L, 3L)), |
3281 | 7x |
CohortSizeDLT(intervals = c(0L, 1L), cohort_size = c(1L, 3L)) |
3282 |
) |
|
3283 |
) |
|
3284 |
} |
|
3285 | ||
3286 |
# CohortSizeOrdinal ---- |
|
3287 | ||
3288 |
## class ---- |
|
3289 | ||
3290 |
#' `CohortSizeOrdinal` |
|
3291 |
#' |
|
3292 |
#' @description `r lifecycle::badge("experimental")` |
|
3293 |
#' |
|
3294 |
#' [`CohortSizeOrdinal`] is the class for cohort size for an ordinal CRM trial. |
|
3295 |
#' |
|
3296 |
#' @slot grade (`integer`)\cr the grade at which the rule should be applied |
|
3297 |
#' @slot rule (`CohortSize`)\cr the `CohortSize` rule to apply. |
|
3298 |
#' |
|
3299 |
#' @aliases CohortSizeOrdinal |
|
3300 |
#' @export |
|
3301 |
#' |
|
3302 |
.CohortSizeOrdinal <- setClass( |
|
3303 |
Class = "CohortSizeOrdinal", |
|
3304 |
slots = c( |
|
3305 |
grade = "integer", |
|
3306 |
rule = "CohortSize" |
|
3307 |
), |
|
3308 |
prototype = prototype( |
|
3309 |
grade = 1L, |
|
3310 |
rule = CohortSizeRange(intervals = c(0, 30), cohort_size = c(1L, 3L)) |
|
3311 |
), |
|
3312 |
contains = "CohortSize", |
|
3313 |
validity = v_cohort_size_ordinal |
|
3314 |
) |
|
3315 | ||
3316 |
## constructor ---- |
|
3317 | ||
3318 |
#' @rdname CohortSizeOrdinal-class |
|
3319 |
#' |
|
3320 |
#' @param grade (`integer`)\cr see slot definition. |
|
3321 |
#' @param rule (`CohortSize`)\cr see slot definition. |
|
3322 |
#' |
|
3323 |
#' @export |
|
3324 |
#' @example examples/Rules-class-CohortSizeOrdinal.R |
|
3325 |
#' |
|
3326 |
CohortSizeOrdinal <- function(grade, rule) { |
|
3327 |
# Cohort size 0 is needed to allow for no-placebo designs |
|
3328 | 31x |
assert_integer(grade, lower = 1, len = 1) |
3329 | 31x |
assert_class(rule, "CohortSize") |
3330 | ||
3331 | 31x |
.CohortSizeOrdinal(grade = grade, rule = rule) |
3332 |
} |
|
3333 | ||
3334 |
## default constructor ---- |
|
3335 | ||
3336 |
#' @rdname CohortSizeOrdinal-class |
|
3337 |
#' @note Typically, end users will not use the `.DefaultCohortSizeOrdinal()` function. |
|
3338 |
#' @export |
|
3339 |
.DefaultCohortSizeOrdinal <- function() { |
|
3340 | 6x |
CohortSizeOrdinal( |
3341 | 6x |
grade = 1L, |
3342 | 6x |
rule = CohortSizeRange(intervals = c(0L, 30L), cohort_size = c(1L, 3L)) |
3343 |
) |
|
3344 |
} |
|
3345 | ||
3346 | ||
3347 |
# SafetyWindow ---- |
|
3348 | ||
3349 |
## class ---- |
|
3350 | ||
3351 |
#' `SafetyWindow` |
|
3352 |
#' |
|
3353 |
#' @description `r lifecycle::badge("stable")` |
|
3354 |
#' |
|
3355 |
#' [`SafetyWindow`] is a class for safety window. |
|
3356 |
#' |
|
3357 |
#' @seealso [`SafetyWindowSize`], [`SafetyWindowConst`]. |
|
3358 |
#' |
|
3359 |
#' @aliases SafetyWindow |
|
3360 |
#' @export |
|
3361 |
#' |
|
3362 |
setClass( |
|
3363 |
Class = "SafetyWindow", |
|
3364 |
contains = "CrmPackClass" |
|
3365 |
) |
|
3366 | ||
3367 |
## default constructor ---- |
|
3368 | ||
3369 |
#' @rdname SafetyWindow-class |
|
3370 |
#' @note Typically, end users will not use the `.DefaultSafetyWindow()` function. |
|
3371 |
#' @export |
|
3372 |
.DefaultSafetyWindow <- function() { |
|
3373 | 2x |
stop(paste0("Class SafetyWindow cannot be instantiated directly. Please use one of its subclasses instead.")) |
3374 |
} |
|
3375 | ||
3376 | ||
3377 |
# SafetyWindowSize ---- |
|
3378 | ||
3379 |
## class ---- |
|
3380 | ||
3381 |
#' `SafetyWindowSize` |
|
3382 |
#' |
|
3383 |
#' @description `r lifecycle::badge("stable")` |
|
3384 |
#' |
|
3385 |
#' [`SafetyWindowSize`] is the class for safety window length based on cohort |
|
3386 |
#' size. This class is used to decide the rolling rule from the clinical |
|
3387 |
#' perspective. |
|
3388 |
#' |
|
3389 |
#' @slot gap (`list`)\cr observed period of the previous patient before |
|
3390 |
#' the next patient can be dosed. This is used as follows. If for instance, |
|
3391 |
#' the cohort size is 4 and we want to specify three time intervals between |
|
3392 |
#' these four consecutive patients, i.e. 7 units of time between the 1st and |
|
3393 |
#' the 2nd patient, 5 units between the 2nd and the 3rd one, and finally 3 |
|
3394 |
#' units between the 3rd and the 4th one, then, |
|
3395 |
#' `gap` = `list(c(7L, 5L, 3L))`. Sometimes, we want that the interval |
|
3396 |
#' only between the 1st and 2nd patient should be increased for the |
|
3397 |
#' safety consideration and the rest time intervals should remain constant, |
|
3398 |
#' regardless of what the cohort size is. Then, `gap` = `list(c(7L, 3L))` |
|
3399 |
#' and the the package will automatically repeat the last element of the vector |
|
3400 |
#' for the remaining time intervals. |
|
3401 |
#' @slot size (`integer`)\cr a vector with the left bounds of the |
|
3402 |
#' relevant cohort size intervals. This is used as follows. For instance, when |
|
3403 |
#' we want to change the `gap` based on the cohort size, i.e. the time |
|
3404 |
#' interval between the 1st and 2nd patient = 9 units of time and the rest |
|
3405 |
#' time intervals are of 5 units of time when the cohort size is equal to or |
|
3406 |
#' larger than 4. And the time interval between the 1st and 2nd patient = 7 units |
|
3407 |
#' of time and the rest time intervals are 3 units of time when the cohort size |
|
3408 |
#' is smaller than 4, then we specify both `gap = list(c(7, 3), c(9, 5))` and |
|
3409 |
#' `size = c(0L, 4L)`. This means, the right bounds of the intervals are |
|
3410 |
#' excluded from the interval, and the last interval goes from the last value |
|
3411 |
#' to infinity. |
|
3412 |
#' @slot follow (`count`)\cr the period of time that each patient in the |
|
3413 |
#' cohort needs to be followed before the next cohort opens. |
|
3414 |
#' @slot follow_min (`count`)\cr at least one patient in the cohort needs |
|
3415 |
#' to be followed at the minimal follow up time. |
|
3416 |
#' |
|
3417 |
#' @aliases SafetyWindowSize |
|
3418 |
#' @export |
|
3419 |
#' |
|
3420 |
.SafetyWindowSize <- setClass( |
|
3421 |
Class = "SafetyWindowSize", |
|
3422 |
slots = c( |
|
3423 |
gap = "list", |
|
3424 |
size = "integer", |
|
3425 |
follow = "integer", |
|
3426 |
follow_min = "integer" |
|
3427 |
), |
|
3428 |
prototype = prototype( |
|
3429 |
gap = list(1:2, 1:2), |
|
3430 |
size = c(1L, 3L), |
|
3431 |
follow = 1L, |
|
3432 |
follow_min = 1L |
|
3433 |
), |
|
3434 |
contains = "SafetyWindow", |
|
3435 |
validity = v_safety_window_size |
|
3436 |
) |
|
3437 | ||
3438 |
## constructor ---- |
|
3439 | ||
3440 |
#' @rdname SafetyWindowSize-class |
|
3441 |
#' |
|
3442 |
#' @param gap see slot definition. |
|
3443 |
#' @param size see slot definition. |
|
3444 |
#' @param follow see slot definition. |
|
3445 |
#' @param follow_min see slot definition. |
|
3446 |
#' |
|
3447 |
#' @export |
|
3448 |
#' @example examples/Rules-class-SafetyWindowSize.R |
|
3449 |
#' |
|
3450 |
SafetyWindowSize <- function(gap, |
|
3451 |
size, |
|
3452 |
follow, |
|
3453 |
follow_min) { |
|
3454 | 17x |
assert_integerish(follow, lower = 0) |
3455 | 17x |
assert_integerish(follow_min, lower = 0) |
3456 | 17x |
for (g in gap) { |
3457 | 36x |
assert_integerish(g, lower = 0) |
3458 |
} |
|
3459 | 17x |
assert_integerish(size, lower = 0) |
3460 | 17x |
if (follow > follow_min) { |
3461 | 1x |
warning("The value of follow_min is typically larger than the value of follow") |
3462 |
} |
|
3463 | 17x |
gap <- lapply(gap, as.integer) |
3464 | 17x |
.SafetyWindowSize( |
3465 | 17x |
gap = gap, |
3466 | 17x |
size = as.integer(size), |
3467 | 17x |
follow = as.integer(follow), |
3468 | 17x |
follow_min = as.integer(follow_min) |
3469 |
) |
|
3470 |
} |
|
3471 | ||
3472 |
## default constructor ---- |
|
3473 | ||
3474 |
#' @rdname SafetyWindowSize-class |
|
3475 |
#' @note Typically, end users will not use the `.DefaultSafetyWindowSize()` function. |
|
3476 |
#' @export |
|
3477 |
.DefaultSafetyWindowSize <- function() { |
|
3478 | 7x |
SafetyWindowSize( |
3479 | 7x |
gap = list(c(7, 3), c(9, 5)), |
3480 | 7x |
size = c(1, 4), |
3481 | 7x |
follow = 7, |
3482 | 7x |
follow_min = 14 |
3483 |
) |
|
3484 |
} |
|
3485 | ||
3486 |
# SafetyWindowConst ---- |
|
3487 | ||
3488 |
## class ---- |
|
3489 | ||
3490 |
#' `SafetyWindowConst` |
|
3491 |
#' |
|
3492 |
#' @description `r lifecycle::badge("stable")` |
|
3493 |
#' |
|
3494 |
#' [`SafetyWindowConst`] is the class for safety window length and it is used |
|
3495 |
#' when the `gap` should be kept constant across cohorts (though it may vary |
|
3496 |
#' within a cohort). |
|
3497 |
#' |
|
3498 |
#' @slot gap (`integer`)\cr a vector, the constant gap between patients. |
|
3499 |
#' @slot follow (`count`)\cr how long to follow each patient. The period of time |
|
3500 |
#' that each patient in the cohort needs to be followed before the next cohort |
|
3501 |
#' opens. |
|
3502 |
#' @slot follow_min (`count`)\cr minimum follow up. At least one patient in the |
|
3503 |
#' cohort needs to be followed at the minimal follow up time. |
|
3504 |
#' |
|
3505 |
#' @aliases SafetyWindowConst |
|
3506 |
#' @export |
|
3507 |
#' |
|
3508 |
.SafetyWindowConst <- setClass( |
|
3509 |
Class = "SafetyWindowConst", |
|
3510 |
slots = c( |
|
3511 |
gap = "integer", |
|
3512 |
follow = "integer", |
|
3513 |
follow_min = "integer" |
|
3514 |
), |
|
3515 |
prototype = prototype( |
|
3516 |
gap = 0L, |
|
3517 |
follow = 1L, |
|
3518 |
follow_min = 1L |
|
3519 |
), |
|
3520 |
contains = "SafetyWindow", |
|
3521 |
validity = v_safety_window_const |
|
3522 |
) |
|
3523 | ||
3524 |
## constructor ---- |
|
3525 | ||
3526 |
#' @rdname SafetyWindowConst-class |
|
3527 |
#' |
|
3528 |
#' @param gap see slot definition. |
|
3529 |
#' @param follow see slot definition. |
|
3530 |
#' @param follow_min see slot definition. |
|
3531 |
#' |
|
3532 |
#' @export |
|
3533 |
#' @example examples/Rules-class-SafetyWindowConst.R |
|
3534 |
#' |
|
3535 |
SafetyWindowConst <- function(gap, |
|
3536 |
follow, |
|
3537 |
follow_min) { |
|
3538 | 26x |
assert_integerish(follow, lower = 0) |
3539 | 26x |
assert_integerish(follow_min, lower = 0) |
3540 | 26x |
assert_integerish(gap, lower = 0) |
3541 | ||
3542 | 26x |
if (follow > follow_min) { |
3543 | 1x |
warning("The value of follow_min is typically larger than the value of follow") |
3544 |
} |
|
3545 | 26x |
.SafetyWindowConst( |
3546 | 26x |
gap = as.integer(gap), |
3547 | 26x |
follow = as.integer(follow), |
3548 | 26x |
follow_min = as.integer(follow_min) |
3549 |
) |
|
3550 |
} |
|
3551 | ||
3552 |
## default constructor ---- |
|
3553 | ||
3554 |
#' @rdname SafetyWindowConst-class |
|
3555 |
#' @note Typically, end users will not use the `.DefaultSafetyWindowConst()` function. |
|
3556 |
#' @export |
|
3557 |
.DefaultSafetyWindowConst <- function() { |
|
3558 | 7x |
SafetyWindowConst( |
3559 | 7x |
gap = 7, |
3560 | 7x |
follow = 7, |
3561 | 7x |
follow_min = 14 |
3562 |
) |
|
3563 |
} |
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 | 69223x |
"Check whether the \\code{test} is \\code{TRUE}; if so, return \\code{NULL}. |
23 | 69223x |
Otherwise, add the \\code{string} message into the cumulative messages vector \\code{msg}." |
24 | 69223x |
assert_flag(test) |
25 | 69223x |
assert_string(string) |
26 | 69223x |
if (test) { |
27 | 68705x |
NULL |
28 |
} else { |
|
29 | 518x |
msg <<- c(msg, string) |
30 |
} |
|
31 |
}, |
|
32 |
result = function() { |
|
33 | 18049x |
"Return either cumulative messages vector \\code{msg} |
34 | 18049x |
(which contains the error messages from all the checks), |
35 | 18049x |
or \\code{NULL}, if \\code{msg} is empty (i.e. all the checks were successful)." |
36 | 18049x |
if (length(msg) > 0) { |
37 | 440x |
msg |
38 |
} else { |
|
39 | 17609x |
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 | 1499x |
if (length(table) == 0) { |
85 | 31x |
return(integer()) |
86 |
} |
|
87 | ||
88 | 1468x |
as.integer(sapply(x, function(.x) { |
89 | 6979x |
which(sapply(table, function(.table) { |
90 | 189634x |
isTRUE(all.equal(.x, .table, |
91 | 189634x |
tolerance = 1e-10, |
92 | 189634x |
check.names = FALSE, |
93 | 189634x |
check.attributes = FALSE |
94 |
)) |
|
95 | 6979x |
}))[1] |
96 |
})) |
|
97 |
} |
|
98 | ||
99 |
##' checks for whole numbers (integers) |
|
100 |
##' |
|
101 |
##' @param x the numeric vector |
|
102 |
##' @param tol the tolerance |
|
103 |
##' @return TRUE or FALSE for each element of x |
|
104 |
##' |
|
105 |
##' @keywords internal |
|
106 |
is.wholenumber <- function(x, tol = .Machine$double.eps^0.5) { |
|
107 | ! |
abs(x - round(x)) < tol |
108 |
} |
|
109 | ||
110 | ||
111 |
##' Safe conversion to integer vector |
|
112 |
##' |
|
113 |
##' @param x the numeric vector |
|
114 |
##' @return the integer vector |
|
115 |
##' |
|
116 |
##' @keywords internal |
|
117 |
safeInteger <- function(x) { |
|
118 | ! |
testres <- is.wholenumber(x) |
119 | ! |
if (!all(testres)) { |
120 | ! |
notInt <- which(!testres) |
121 | ! |
stop(paste( |
122 | ! |
"elements", |
123 | ! |
paste(notInt, sep = ", "), |
124 | ! |
"of vector are not integers!" |
125 |
)) |
|
126 |
} |
|
127 | ! |
as.integer(x) |
128 |
} |
|
129 | ||
130 |
##' Shorthand for logit function |
|
131 |
##' |
|
132 |
##' @param x the function argument |
|
133 |
##' @return the logit(x) |
|
134 |
##' |
|
135 |
##' @export |
|
136 |
##' @keywords programming |
|
137 |
logit <- function(x) { |
|
138 | 1815x |
qlogis(x) |
139 |
} |
|
140 | ||
141 |
##' Shorthand for probit function |
|
142 |
##' |
|
143 |
##' @param x the function argument |
|
144 |
##' @return the probit(x) |
|
145 |
##' |
|
146 |
##' @export |
|
147 |
##' @keywords programming |
|
148 |
probit <- function(x) { |
|
149 | 6x |
qnorm(x) |
150 |
} |
|
151 | ||
152 |
##' Open the example pdf for crmPack |
|
153 |
##' |
|
154 |
##' Calling this helper function should open the example.pdf document, |
|
155 |
##' residing in the doc subfolder of the package installation directory. |
|
156 |
##' |
|
157 |
##' @return nothing |
|
158 |
##' @export |
|
159 |
##' @keywords documentation |
|
160 |
##' @author Daniel Sabanes Bove \email{sabanesd@@rconis.com} |
|
161 |
crmPackExample <- function() { |
|
162 | ! |
crmPath <- system.file(package = "crmPack") |
163 | ! |
printVignette(list(PDF = "example.pdf", Dir = crmPath)) |
164 |
## instead of utils:::print.vignette |
|
165 |
} |
|
166 | ||
167 |
##' Open the browser with help pages for crmPack |
|
168 |
##' |
|
169 |
##' This convenience function opens your browser with the help pages for |
|
170 |
##' crmPack. |
|
171 |
##' |
|
172 |
##' @return nothing |
|
173 |
##' @export |
|
174 |
##' @importFrom utils help |
|
175 |
##' @keywords documentation |
|
176 |
##' @author Daniel Sabanes Bove \email{sabanesd@@rconis.com} |
|
177 |
crmPackHelp <- function() { |
|
178 | ! |
utils::help(package = "crmPack", help_type = "html") |
179 |
} |
|
180 | ||
181 |
#' Plot `gtable` Objects |
|
182 |
#' |
|
183 |
#' This is needed because `crmPack` uses [gridExtra::arrangeGrob()] to combine |
|
184 |
#' `ggplot2` plots, and the resulting `gtable` object is not plotted otherwise |
|
185 |
#' when implicitly printing it in the console, e.g. |
|
186 |
#' |
|
187 |
#' @method plot gtable |
|
188 |
#' @param x (`gtable`)\cr object to plot. |
|
189 |
#' @param ... additional parameters for [grid::grid.draw()]. |
|
190 |
#' |
|
191 |
#' @export |
|
192 |
plot.gtable <- function(x, ...) { |
|
193 | 1x |
grid::grid.draw(x, ...) |
194 |
} |
|
195 | ||
196 |
#' @method print gtable |
|
197 |
#' @rdname plot.gtable |
|
198 |
#' @export |
|
199 |
print.gtable <- function(x, ...) { |
|
200 | ! |
plot(x, ...) |
201 |
} |
|
202 | ||
203 |
##' Taken from utils package (print.vignette) |
|
204 |
##' |
|
205 |
##' @importFrom tools file_ext |
|
206 |
##' @importFrom utils browseURL |
|
207 |
##' @keywords internal |
|
208 |
printVignette <- function(x, ...) { |
|
209 | ! |
if (nzchar(out <- x$PDF)) { |
210 | ! |
ext <- tools::file_ext(out) |
211 | ! |
out <- file.path(x$Dir, "doc", out) |
212 | ! |
if (tolower(ext) == "pdf") { |
213 | ! |
pdfviewer <- getOption("pdfviewer") |
214 | ! |
if (identical(pdfviewer, "false")) { |
215 | ! |
} else if (.Platform$OS.type == "windows" && identical( |
216 | ! |
pdfviewer, |
217 | ! |
file.path(R.home("bin"), "open.exe") |
218 |
)) { |
|
219 | ! |
shell.exec(out) |
220 |
} else { |
|
221 | ! |
system2(pdfviewer, shQuote(out), wait = FALSE) |
222 |
} |
|
223 |
} else { |
|
224 | ! |
browseURL(out) |
225 |
} |
|
226 |
} else { |
|
227 | ! |
warning(gettextf("vignette %s has no PDF/HTML", sQuote(x$Topic)), |
228 | ! |
call. = FALSE, domain = NA |
229 |
) |
|
230 |
} |
|
231 | ! |
invisible(x) |
232 |
} |
|
233 | ||
234 |
##' Compute the density of Inverse gamma distribution |
|
235 |
##' @param x vector of quantiles |
|
236 |
##' @param a the shape parameter of the inverse gamma distribution |
|
237 |
##' @param b the scale parameter of the inverse gamma distribution |
|
238 |
##' @param log logical; if TRUE, probabilities p are given as log(p) |
|
239 |
##' @param normalize logical; if TRUE, the output will be normalized |
|
240 |
##' |
|
241 |
##' @keywords internal |
|
242 |
dinvGamma <- function(x, |
|
243 |
a, |
|
244 |
b, |
|
245 |
log = FALSE, |
|
246 |
normalize = TRUE) { |
|
247 | ! |
ret <- -(a + 1) * log(x) - b / x |
248 | ! |
if (normalize) { |
249 | ! |
ret <- ret + a * log(b) - lgamma(a) |
250 |
} |
|
251 | ! |
if (log) { |
252 | ! |
return(ret) |
253 |
} else { |
|
254 | ! |
return(exp(ret)) |
255 |
} |
|
256 |
} |
|
257 | ||
258 |
##' Compute the distribution function of Inverse gamma distribution |
|
259 |
##' |
|
260 |
##' @param q vector of quantiles |
|
261 |
##' @param a the shape parameter of the inverse gamma distribution |
|
262 |
##' @param b the scale parameter of the inverse gamma distribution |
|
263 |
##' @param lower.tail logical; if TRUE (default), probabilities are `P(X > x)`, |
|
264 |
##' otherwise, `P(X <= x)`. |
|
265 |
##' @param log.p if TRUE, probabilities/densities p are returned as `log(p)` |
|
266 |
##' |
|
267 |
##' @keywords internal |
|
268 |
pinvGamma <- function(q, |
|
269 |
a, |
|
270 |
b, |
|
271 |
lower.tail = TRUE, |
|
272 |
log.p = FALSE) { |
|
273 | ! |
pgamma( |
274 | ! |
q = 1 / q, |
275 | ! |
shape = a, |
276 | ! |
rate = b, |
277 | ! |
lower.tail = !lower.tail, |
278 | ! |
log.p = log.p |
279 |
) |
|
280 |
} |
|
281 | ||
282 |
##' Compute the quantile function of Inverse gamma distribution |
|
283 |
##' @param p vector of probabilities |
|
284 |
##' @param a the shape parameter of the inverse gamma distribution |
|
285 |
##' @param b the scale parameter of the inverse gamma distribution |
|
286 |
##' @param lower.tail logical; if TRUE (default), probabilities are `P(X > x)`, |
|
287 |
##' otherwise, `P(X <= x)`. |
|
288 |
##' @param log.p FALSE if TRUE, probabilities/densities p are returned as `log(p)` |
|
289 |
##' |
|
290 |
##' @keywords internal |
|
291 |
qinvGamma <- function(p, |
|
292 |
a, |
|
293 |
b, |
|
294 |
lower.tail = TRUE, |
|
295 |
log.p = FALSE) { |
|
296 | ! |
1 / qgamma( |
297 | ! |
p = p, |
298 | ! |
shape = a, |
299 | ! |
rate = b, |
300 | ! |
lower.tail = !lower.tail, |
301 | ! |
log.p = log.p |
302 |
) |
|
303 |
} |
|
304 |
##' The random generation of the Inverse gamma distribution |
|
305 |
##' @param n the number of observations |
|
306 |
##' @param a the shape parameter of the inverse gamma distribution |
|
307 |
##' @param b the scale parameter of the inverse gamma distribution |
|
308 |
##' |
|
309 |
##' @keywords internal |
|
310 |
rinvGamma <- function(n, |
|
311 |
a, |
|
312 |
b) { |
|
313 | 43464x |
1 / rgamma(n, |
314 | 43464x |
shape = a, |
315 | 43464x |
rate = b |
316 |
) |
|
317 |
} |
|
318 | ||
319 |
# nolint end |
|
320 | ||
321 |
#' Combining S4 Class Validation Results |
|
322 |
#' |
|
323 |
#' @description `r lifecycle::badge("experimental")` |
|
324 |
#' |
|
325 |
#' A simple helper function that combines two outputs from calls to `result()` |
|
326 |
#' function which is placed in a slot of [Validate()] reference class. |
|
327 |
#' |
|
328 |
#' @param v1 (`logical` or `character`)\cr an output from `result()` function from |
|
329 |
#' [Validate()] reference class, to be combined with `v2`. |
|
330 |
#' @param v2 (`logical` or `character`)\cr an output from `result()` function from |
|
331 |
#' [Validate()] reference class, to be combined with `v1`. |
|
332 |
#' |
|
333 |
#' @export |
|
334 |
#' @examples |
|
335 |
#' h_validate_combine_results(TRUE, "some_message") |
|
336 |
h_validate_combine_results <- function(v1, v2) { |
|
337 | ! |
assert_true(test_true(v1) || test_character(v1, any.missing = FALSE, min.len = 1L)) |
338 | ! |
assert_true(test_true(v2) || test_character(v2, any.missing = FALSE, min.len = 1L)) |
339 | ||
340 | ! |
isTRUEv2 <- isTRUE(v2) |
341 | ! |
if (isTRUE(v1)) { |
342 | ! |
if (isTRUEv2) { |
343 | ! |
TRUE |
344 |
} else { |
|
345 | ! |
v2 |
346 |
} |
|
347 |
} else { |
|
348 | ! |
if (isTRUEv2) { |
349 | ! |
v1 |
350 |
} else { |
|
351 | ! |
c(v1, v2) |
352 |
} |
|
353 |
} |
|
354 |
} |
|
355 | ||
356 |
#' Comparison with Numerical Tolerance and Without Name Comparison |
|
357 |
#' |
|
358 |
#' @description `r lifecycle::badge("experimental")` |
|
359 |
#' |
|
360 |
#' This helper function ensures a default tolerance level equal to `1e-10`, |
|
361 |
#' and ignores names and other attributes. |
|
362 |
#' In contrast to [all.equal()], it always returns a logical type object. |
|
363 |
#' |
|
364 |
#' @param target (`numeric`)\cr target values. |
|
365 |
#' @param current (`numeric`)\cr current values. |
|
366 |
#' @param tolerance (`number`) relative differences smaller than this are not |
|
367 |
#' reported. |
|
368 |
#' @return `TRUE` when `target` and `current` do not differ |
|
369 |
#' up to desired tolerance and without looking at names or other attributes, |
|
370 |
#' `FALSE` otherwise. |
|
371 |
#' |
|
372 |
#' @export |
|
373 |
#' |
|
374 |
h_all_equivalent <- function(target, |
|
375 |
current, |
|
376 |
tolerance = 1e-10) { |
|
377 | 1842x |
assert_numeric(target) |
378 | 1842x |
assert_numeric(current) |
379 | 1842x |
assert_number(tolerance) |
380 | ||
381 | 1842x |
tmp <- all.equal( |
382 | 1842x |
target = target, |
383 | 1842x |
current = current, |
384 | 1842x |
tolerance = tolerance, |
385 | 1842x |
check.names = FALSE, |
386 | 1842x |
check.attributes = FALSE |
387 |
) |
|
388 | 1842x |
isTRUE(tmp) |
389 |
} |
|
390 | ||
391 |
#' Preparing Data for Plotting |
|
392 |
#' |
|
393 |
#' @description `r lifecycle::badge("experimental")` |
|
394 |
#' |
|
395 |
#' This helper function prepares a `data.frame` object based on `Data` class |
|
396 |
#' object. The resulting data frame is used by the plot function for `Data` |
|
397 |
#' class objects. |
|
398 |
#' |
|
399 |
#' @param data (`Data`)\cr object from which data is extracted and converted |
|
400 |
#' into a data frame. |
|
401 |
#' @param blind (`flag`)\cr should data be blinded? |
|
402 |
#' If `TRUE`, then for each cohort, all DLTs are assigned to the first |
|
403 |
#' subjects in the cohort. In addition, the placebo (if any) is set to the |
|
404 |
#' active dose level for that cohort. |
|
405 |
#' @param ... further arguments passed to `data.frame` constructor. |
|
406 |
#' It can be e.g. an extra `column_name = value` pair based on a slot |
|
407 |
#' from `x` (which in this case might be a subclass of `Data`) |
|
408 |
#' which does not appear in `Data`. |
|
409 |
#' @return A [`data.frame`] object with values to plot. |
|
410 |
#' |
|
411 |
h_plot_data_df <- function(data, blind = FALSE, ...) { |
|
412 |
df <- data.frame( |
|
413 |
patient = seq_along(data@x), |
|
414 |
ID = paste(" ", data@ID), |
|
415 |
cohort = data@cohort, |
|
416 |
dose = data@x, |
|
417 |
toxicity = ifelse(data@y == 1, "Yes", "No"), |
|
418 |
... |
|
419 |
) |
|
420 | ||
421 |
if (blind) { |
|
422 |
# This is to blind the data. |
|
423 |
# For each cohort, all DLTs are assigned to the first subjects in the cohort. |
|
424 |
# In addition, the placebo (if any) is set to the active dose level for that |
|
425 |
# cohort. |
|
426 |
# Notice: dapply reorders records of df according to the lexicographic order |
|
427 |
# of cohort. |
|
428 |
df <- dapply(df, f = ~cohort, FUN = function(coh) { |
|
429 |
coh$toxicity <- sort(coh$toxicity, decreasing = TRUE) |
|
430 |
coh$dose <- max(coh$dose) |
|
431 |
coh |
|
432 |
}) |
|
433 |
} else if (data@placebo) { |
|
434 |
# Placebo will be plotted at y = 0 level. |
|
435 |
df$dose[df$dose == data@doseGrid[1]] <- 0 |
|
436 |
} |
|
437 | ||
438 |
df |
|
439 |
} |
|
440 | ||
441 |
#' Preparing Cohort Lines for Data Plot |
|
442 |
#' |
|
443 |
#' @description `r lifecycle::badge("experimental")` |
|
444 |
#' |
|
445 |
#' This helper function prepares a `ggplot` geom with reference lines |
|
446 |
#' separating different cohorts on the plot of `Data` class object. |
|
447 |
#' Lines are either vertical or horizontal of green color and longdash type. |
|
448 |
#' |
|
449 |
#' @details The geom object is returned if and only if `placebo` is equal to |
|
450 |
#' `TRUE` and there are more than one unique values in `cohort`. Otherwise, |
|
451 |
#' this function returns `NULL` object. |
|
452 |
#' |
|
453 |
#' @param cohort (`integer`)\cr the cohort indices. |
|
454 |
#' @param placebo (`flag`)\cr is placebo included in the doses? |
|
455 |
#' If it so, this function returns `NULL` object as in this case all doses |
|
456 |
#' in a given cohort are equal and there is no need to separate them. |
|
457 |
#' @param vertical (`flag`)\cr should the line be vertical? Otherwise it is |
|
458 |
#' horizontal. |
|
459 |
#' |
|
460 |
h_plot_data_cohort_lines <- function(cohort, |
|
461 |
placebo, |
|
462 |
vertical = TRUE) { |
|
463 | 15x |
assert_integer(cohort) |
464 | 15x |
assert_flag(placebo) |
465 | 15x |
assert_flag(vertical) |
466 | ||
467 |
# If feasible, add vertical or horizontal green lines separating sub-sequent |
|
468 |
# cohorts. |
|
469 | 15x |
if (placebo && length(unique(cohort)) > 1) { |
470 | 11x |
intercept <- head(cumsum(table(cohort)), n = -1) + 0.5 |
471 | 11x |
if (vertical) { |
472 | 9x |
geom_vline(xintercept = intercept, colour = "green", linetype = "longdash") # nolintr |
473 |
} else { |
|
474 | 2x |
geom_hline(yintercept = intercept, colour = "green", linetype = "longdash") # nolintr |
475 |
} |
|
476 |
} else { |
|
477 | 4x |
NULL |
478 |
} |
|
479 |
} |
|
480 | ||
481 |
#' Checking Formals of a Function |
|
482 |
#' |
|
483 |
#' @description `r lifecycle::badge("experimental")` |
|
484 |
#' |
|
485 |
#' This helper function checks whether a given function `fun` has required or |
|
486 |
#' allowed arguments. The argument check is based only on the names of the |
|
487 |
#' arguments. No any further logic is verified here. |
|
488 |
#' |
|
489 |
#' @param fun (`function`)\cr a function name whose argument names will be |
|
490 |
#' checked. |
|
491 |
#' @param mandatory (`character` or `NULL`)\cr the names of the arguments which |
|
492 |
#' must be present in `fun`. If `mandatory` is specified as `NULL` (default) |
|
493 |
#' this requirement is ignored. |
|
494 |
#' @param allowed (`character` or `NULL`)\cr the names of the arguments which |
|
495 |
#' are allowed in `fun`. Names that do not belong to `allowed` are simply not |
|
496 |
#' allowed. The `allowed` parameter is independent from the `mandatory`, in a |
|
497 |
#' sense that if `mandatory` is specified as a `character` vector, it does not |
|
498 |
#' have to be repeated in `allowed`. If `allowed` is specified as `NULL` |
|
499 |
#' (default), then it means that there must be no any arguments in `fun` |
|
500 |
#' (except these ones which are specified in `mandatory`). |
|
501 |
#' |
|
502 |
#' @export |
|
503 |
#' |
|
504 |
h_check_fun_formals <- function(fun, mandatory = NULL, allowed = NULL) { |
|
505 | 1311x |
assert_function(fun) |
506 | 1311x |
assert_character(mandatory, null.ok = TRUE) |
507 | 1311x |
assert_character(allowed, null.ok = TRUE) |
508 | ||
509 | 1311x |
arg_names <- names(formals(fun)) |
510 | 1311x |
mandatory_check <- all(mandatory %in% arg_names) |
511 | 1311x |
allowed_check <- all(arg_names %in% c(mandatory, allowed)) |
512 | ||
513 | 1311x |
mandatory_check && allowed_check |
514 |
} |
|
515 | ||
516 |
#' Getting the Slots from a S4 Object |
|
517 |
#' |
|
518 |
#' @description `r lifecycle::badge("experimental")` |
|
519 |
#' |
|
520 |
#' This helper function extracts requested slots from the S4 class object. |
|
521 |
#' It is a simple wrapper of [methods::slot()] function. |
|
522 |
#' |
|
523 |
#' @param object (`S4`)\cr an object from a formally defined S4 class. |
|
524 |
#' @param names (`character`)\cr a vector with names of slots to be fetched. |
|
525 |
#' This function assumes that for every element in `names`, there exists a |
|
526 |
#' slot of the same name in the `object`. |
|
527 |
#' @param simplify (`flag`)\cr should an output be simplified? This has an |
|
528 |
#' effect if and only if a single slot is about to be extracted, i.e. |
|
529 |
#' `names` is just a single string. |
|
530 |
#' |
|
531 |
#' @return `list` with the slots extracted from `object` according to `names`, |
|
532 |
#' or single slot if simplification is required and possible. |
|
533 |
#' |
|
534 |
#' @export |
|
535 |
#' |
|
536 |
h_slots <- function(object, names, simplify = FALSE) { |
|
537 | 3164x |
assert_true(isS4(object)) |
538 | 3164x |
assert_character(names, any.missing = FALSE, null.ok = TRUE) |
539 | 3164x |
assert_true(all(names %in% slotNames(object))) |
540 | ||
541 | 3163x |
if (is.null(names) || length(names) == 0L) { |
542 | 590x |
return(list()) |
543 |
} |
|
544 | ||
545 | 2573x |
slots_list <- sapply(names, function(n) slot(object, n), simplify = FALSE, USE.NAMES = TRUE) |
546 | ||
547 | 2573x |
if (simplify && length(names) == 1) { |
548 | 17x |
slots_list[[1]] |
549 |
} else { |
|
550 | 2556x |
slots_list |
551 |
} |
|
552 |
} |
|
553 | ||
554 |
#' Conditional Formatting Using C-style Formats |
|
555 |
#' |
|
556 |
#' @description `r lifecycle::badge("experimental")` |
|
557 |
#' |
|
558 |
#' This helper function conditionally formats a number with [formatC()] |
|
559 |
#' function using `"E"` format and specific number of digits as given by the |
|
560 |
#' user. A number is formatted if and only if its absolute value is less than |
|
561 |
#' `0.001` or greater than `10000`. Otherwise, the number is not formatted. |
|
562 |
#' Additionally, custom prefix or suffix can be appended to character string |
|
563 |
#' with formatted number, so that the changes are marked. |
|
564 |
#' |
|
565 |
#' @note This function was primarily designed as a helper for |
|
566 |
#' [h_jags_write_model()] function. |
|
567 |
#' |
|
568 |
#' @param x (`number`)\cr a number to be formatted. |
|
569 |
#' @param digits (`function`)\cr the desired number of significant digits. |
|
570 |
#' @param prefix (`string`)\cr a prefix to be added in front of the formatted |
|
571 |
#' number. |
|
572 |
#' @param suffix (`string`)\cr a suffix to be appended after the formatted |
|
573 |
#' number. |
|
574 |
#' |
|
575 |
#' @return Either formatted `x` as `string` or unchanged `x` if the |
|
576 |
#' formatting condition is not met. |
|
577 |
#' |
|
578 |
#' @export |
|
579 |
#' @examples |
|
580 |
#' h_format_number(50000) |
|
581 |
#' h_format_number(50000, prefix = "P", suffix = "S") |
|
582 |
h_format_number <- function(x, |
|
583 |
digits = 5, |
|
584 |
prefix = "", |
|
585 |
suffix = "") { |
|
586 | 2398x |
assert_number(x) |
587 | 2398x |
assert_int(digits) |
588 | 2398x |
assert_string(prefix) |
589 | 2398x |
assert_string(suffix) |
590 | ||
591 | 2398x |
if ((abs(x) < 1e-3) || (abs(x) > 1e+4)) { |
592 | 162x |
paste0(prefix, formatC(x, digits = digits, format = "E"), suffix) |
593 |
} else { |
|
594 | 2236x |
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 | 15350x |
assert_function(fun) |
638 | 15350x |
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 | 15350x |
force(x[[1]]) |
642 | ||
643 | 15350x |
for (i in seq_along(x)) { |
644 | 45993x |
if (class(x[[i]]) %in% classes) { |
645 | 2395x |
x[[i]] <- do.call(fun, c(list(x[[i]]), ...)) |
646 | 43598x |
} else if (length(x[[i]]) > 1L) { |
647 | 14956x |
x[[i]] <- h_rapply(x[[i]], fun, classes, ...) |
648 |
} |
|
649 |
} |
|
650 | 15350x |
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 | 727x |
assert_atomic(x, len = 1L) |
669 | ||
670 | 724x |
if (is.na(x)) { |
671 | 66x |
NULL |
672 |
} else { |
|
673 | 658x |
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 | 2282x |
if (length(x) == 0L || (length(x) == 1L && is.na(x))) { |
696 | 2242x |
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 | 2943x |
assert_number(tol) |
730 | ||
731 | 2943x |
is_matrix <- test_matrix( |
732 | 2943x |
x, |
733 | 2943x |
mode = "numeric", nrows = size, ncols = size, any.missing = FALSE |
734 |
) |
|
735 | ||
736 | 2943x |
if (is_matrix) { |
737 | 2932x |
is_symmetric <- all.equal(x, t(x), tolerance = tol) |
738 | 2932x |
if (isTRUE(is_symmetric)) { |
739 | 2926x |
ev <- eigen(x, only.values = TRUE)$values |
740 | 2926x |
all(ev > tol) |
741 |
} else { |
|
742 | 6x |
FALSE |
743 |
} |
|
744 |
} else { |
|
745 | 11x |
FALSE |
746 |
} |
|
747 |
} |
|
748 | ||
749 |
#' Check that an argument is a named vector of type numeric |
|
750 |
#' |
|
751 |
#' @description `r lifecycle::badge("stable")` |
|
752 |
#' |
|
753 |
#' A simple helper function that tests whether an object is a named numerical |
|
754 |
#' vector. |
|
755 |
#' |
|
756 |
#' @note This function is based on [`checkmate::test_numeric()`] and |
|
757 |
#' [`checkmate::test_names()`] functions. |
|
758 |
#' |
|
759 |
#' @param x (`any`)\cr object to check. |
|
760 |
#' @inheritParams checkmate::test_names |
|
761 |
#' @inheritParams checkmate::test_numeric |
|
762 |
#' @param ... further parameters passed to [`checkmate::test_numeric()`]. |
|
763 |
#' |
|
764 |
#' @return `TRUE` if `x` is a named vector of type numeric, otherwise `FALSE`. |
|
765 |
#' |
|
766 |
#' @export |
|
767 |
#' @examples |
|
768 |
#' h_test_named_numeric(1:2, permutation.of = c("a", "b")) |
|
769 |
#' h_test_named_numeric(c(a = 1, b = 2), permutation.of = c("a", "b")) |
|
770 |
#' h_test_named_numeric(c(a = 1, b = 2), permutation.of = c("b", "a")) |
|
771 |
h_test_named_numeric <- function(x, |
|
772 |
subset.of = NULL, # nolintr |
|
773 |
must.include = NULL, # nolintr |
|
774 |
permutation.of = NULL, # nolintr |
|
775 |
identical.to = NULL, # nolintr |
|
776 |
disjunct.from = NULL, # nolintr |
|
777 |
lower = 0 + .Machine$double.xmin, |
|
778 |
finite = TRUE, |
|
779 |
any.missing = FALSE, # nolintr |
|
780 |
len = 2, |
|
781 |
...) { |
|
782 | 908x |
is_valid_num <- test_numeric( |
783 | 908x |
x, |
784 | 908x |
lower = lower, |
785 | 908x |
finite = finite, |
786 | 908x |
any.missing = any.missing, |
787 | 908x |
len = len, |
788 |
..., |
|
789 | 908x |
names = "named" |
790 |
) |
|
791 | 908x |
are_names_valid <- test_names( |
792 | 908x |
names(x), |
793 | 908x |
subset.of = subset.of, |
794 | 908x |
must.include = must.include, |
795 | 908x |
permutation.of = permutation.of, |
796 | 908x |
identical.to = identical.to, |
797 | 908x |
disjunct.from = disjunct.from, |
798 |
) |
|
799 | 908x |
is_valid_num && are_names_valid |
800 |
} |
|
801 | ||
802 |
#' Check which elements are in a given range |
|
803 |
#' |
|
804 |
#' @description `r lifecycle::badge("stable")` |
|
805 |
#' |
|
806 |
#' A simple helper function that tests whether elements of a given vector or |
|
807 |
#' matrix are within specified interval. |
|
808 |
#' |
|
809 |
#' @param x (`numeric`)\cr vector or matrix with elements to test. |
|
810 |
#' @param range (`numeric`)\cr an interval, i.e. sorted two-elements vector. |
|
811 |
#' @param bounds_closed (`logical`)\cr should bounds in the `range` be treated |
|
812 |
#' as closed? This can be a scalar or vector of length two. If it is a scalar, |
|
813 |
#' then its value applies to lower bound `range[1]` and upper bound |
|
814 |
#' `range[2]`. If this is a vector with two flags, the first flag corresponds |
|
815 |
#' to the lower bound only, and the second to the upper bound only. |
|
816 |
#' |
|
817 |
#' @return A logical vector or matrix of length equal to the length of `x`, that |
|
818 |
#' for every element of `x`, indicates whether a given element of `x` is in |
|
819 |
#' the `range`. |
|
820 |
#' |
|
821 |
#' @export |
|
822 |
#' @examples |
|
823 |
#' x <- 1:4 |
|
824 |
#' h_in_range(x, range = c(1, 3)) |
|
825 |
#' h_in_range(x, range = c(1, 3), bounds_closed = FALSE) |
|
826 |
#' h_in_range(x, range = c(1, 3), bounds_closed = c(FALSE, TRUE)) |
|
827 |
#' mat <- matrix(c(2, 5, 3, 10, 4, 9, 1, 8, 7), nrow = 3) |
|
828 |
#' h_in_range(mat, range = c(1, 5)) |
|
829 |
h_in_range <- function(x, range = c(0, 1), bounds_closed = TRUE) { |
|
830 | 12323x |
assert_numeric(x) |
831 | 12322x |
assert_numeric(range, any.missing = FALSE, len = 2, sorted = TRUE) |
832 | 12318x |
assert_logical(bounds_closed, min.len = 1, max.len = 2, any.missing = FALSE) |
833 | ||
834 | 12316x |
above_lwr <- if (bounds_closed[1]) { |
835 | 7284x |
x >= range[1] |
836 |
} else { |
|
837 | 5032x |
x > range[1] |
838 |
} |
|
839 | ||
840 | 12316x |
below_upr <- if (tail(bounds_closed, 1)) { |
841 | 8745x |
x <= range[2] |
842 |
} else { |
|
843 | 3571x |
x < range[2] |
844 |
} |
|
845 | ||
846 | 12316x |
above_lwr & below_upr |
847 |
} |
|
848 | ||
849 |
#' Find Interval Numbers or Indices and Return Custom Number For 0. |
|
850 |
#' |
|
851 |
#' @description `r lifecycle::badge("stable")` |
|
852 |
#' |
|
853 |
#' A simple wrapper of [`findInterval()`] function that invokes |
|
854 |
#' [`findInterval()`], takes its output and replaces all the |
|
855 |
#' elements with \eqn{0} value to a custom number as specified in `replacement` |
|
856 |
#' argument. |
|
857 |
#' |
|
858 |
#' @inheritDotParams base::findInterval |
|
859 |
#' @param replacement (`number`)\cr a custom number to be used as a replacement |
|
860 |
#' for \eqn{0}. Default to `-Inf`. |
|
861 |
#' |
|
862 |
#' @export |
|
863 |
#' @examples |
|
864 |
#' h_find_interval(1, c(2, 4, 6)) |
|
865 |
#' h_find_interval(3, c(2, 4, 6)) |
|
866 |
#' h_find_interval(1, c(2, 4, 6), replacement = -1) |
|
867 |
h_find_interval <- function(..., replacement = -Inf) { |
|
868 | 178x |
assert_number(replacement) |
869 | ||
870 | 178x |
x <- findInterval(...) |
871 | 178x |
ifelse(x == 0, yes = replacement, no = x) |
872 |
} |
|
873 | ||
874 | ||
875 | ||
876 |
#' Group Together Mono and Combo Data |
|
877 |
#' |
|
878 |
#' This is only used in the simulation method for `DesignGrouped` to combine |
|
879 |
#' the separately generated data sets from mono and combo arms and to fit the |
|
880 |
#' combined logistic regression model. |
|
881 |
#' Hence the ID and cohort information is not relevant and will be |
|
882 |
#' arbitrarily assigned to avoid problems with the [`DataGrouped`] validation. |
|
883 |
#' |
|
884 |
#' @param mono_data (`Data`)\cr mono data. |
|
885 |
#' @param combo_data (`Data`)\cr combo data. |
|
886 |
#' |
|
887 |
#' @return A [`DataGrouped`] object containing both `mono_data` and `combo_data`, |
|
888 |
#' but with arbitrary ID and cohort slots. |
|
889 |
#' |
|
890 |
#' @keywords internal |
|
891 |
h_group_data <- function(mono_data, combo_data) { |
|
892 | 72x |
assert_class(mono_data, "Data") |
893 | 72x |
assert_class(combo_data, "Data") |
894 | ||
895 | 72x |
df <- data.frame( |
896 | 72x |
x = c(mono_data@x, combo_data@x), |
897 | 72x |
y = c(mono_data@y, combo_data@y), |
898 | 72x |
group = rep(c("mono", "combo"), c(length(mono_data@x), length(combo_data@x))) |
899 |
) |
|
900 | 72x |
df <- df[order(df$x), ] |
901 | ||
902 | 72x |
DataGrouped( |
903 | 72x |
x = df$x, |
904 | 72x |
y = df$y, |
905 | 72x |
ID = seq_along(df$x), |
906 | 72x |
cohort = as.integer(factor(df$x)), |
907 | 72x |
doseGrid = sort(unique(c(mono_data@doseGrid, combo_data@doseGrid))), |
908 | 72x |
group = df$group |
909 |
) |
|
910 |
} |
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 | ||
68 | 8x |
param[["x"]] <- tidy(x) |
69 | 8x |
rv <- kableExtra::add_header_above( |
70 | 8x |
do.call(knitr::kable, param), |
71 | 8x |
c("No DLTs" = 2, " " = 1) |
72 |
) |
|
73 | 8x |
rv <- paste0(rv, "\n\n") |
74 | ||
75 | 8x |
if (asis) { |
76 | 4x |
rv <- knitr::asis_output(rv) |
77 |
} |
|
78 | 8x |
rv |
79 |
} |
|
80 | ||
81 |
#' Render an `IncrementsDoseLevels` object |
|
82 |
#' |
|
83 |
#' @description `r lifecycle::badge("experimental")` |
|
84 |
#' @inherit knit_print.CohortSizeConst return |
|
85 |
#' @inheritParams knit_print.CohortSizeConst |
|
86 |
#' @export |
|
87 |
#' @rdname knit_print |
|
88 |
#' @method knit_print IncrementsDoseLevels |
|
89 |
knit_print.IncrementsDoseLevels <- function(x, ..., asis = TRUE) { |
|
90 | 6x |
assert_flag(asis) |
91 | ||
92 | 4x |
rv <- paste0( |
93 | 4x |
"The maximum increment between cohorts is ", |
94 | 4x |
x@levels, |
95 | 4x |
ifelse(x@levels == 1, " level", " levels"), |
96 | 4x |
" relative to the ", |
97 | 4x |
ifelse(x@basis_level == "last", "dose used in the previous cohort.", "highest dose used so far."), |
98 | 4x |
"\n\n" |
99 |
) |
|
100 | ||
101 | 4x |
if (asis) { |
102 | 2x |
rv <- knitr::asis_output(rv) |
103 |
} |
|
104 | 4x |
rv |
105 |
} |
|
106 | ||
107 |
#' Render an `IncrementsHSRBeta` object |
|
108 |
#' |
|
109 |
#' @description `r lifecycle::badge("experimental")` |
|
110 |
#' @inherit knit_print.CohortSizeConst return |
|
111 |
#' @inheritParams knit_print.CohortSizeConst |
|
112 |
#' @export |
|
113 |
#' @method knit_print IncrementsHSRBeta |
|
114 |
#' @rdname knit_print |
|
115 |
knit_print.IncrementsHSRBeta <- function(x, ..., asis = TRUE) { |
|
116 | 6x |
assert_flag(asis) |
117 | ||
118 | 4x |
rv <- paste0( |
119 | 4x |
"The maximum increment is defined by a hard safety rule, independent of the CRM model, ", |
120 | 4x |
" and based on a beta(", x@a, ", ", x@b, ") ", |
121 | 4x |
"prior with a target toxicity rate of ", |
122 | 4x |
x@target, |
123 | 4x |
" and a probability threshold of ", |
124 | 4x |
x@prob, |
125 | 4x |
".\n\n" |
126 |
) |
|
127 | ||
128 | 4x |
if (asis) { |
129 | 2x |
rv <- knitr::asis_output(rv) |
130 |
} |
|
131 | 4x |
rv |
132 |
} |
|
133 | ||
134 |
#' Render an `IncrementsMin` object |
|
135 |
#' |
|
136 |
#' @description `r lifecycle::badge("experimental")` |
|
137 |
#' @inherit knit_print.CohortSizeConst return |
|
138 |
#' @param ... passed through to the `knit_print` methods of the constituent |
|
139 |
#' rules |
|
140 |
#' @inheritParams knit_print.CohortSizeConst |
|
141 |
#' @export |
|
142 |
#' @method knit_print IncrementsMin |
|
143 |
#' @rdname knit_print |
|
144 |
knit_print.IncrementsMin <- function(x, ..., asis = TRUE) { |
|
145 | 6x |
assert_flag(asis) |
146 | ||
147 | 4x |
rv <- paste0( |
148 | 4x |
"The minimum of the increments defined in the following rules:", |
149 | 4x |
paste0( |
150 | 4x |
lapply( |
151 | 4x |
x@increments_list, |
152 | 4x |
function(x, ...) { |
153 | 8x |
knit_print(x, asis = asis, ...) |
154 |
} |
|
155 |
), |
|
156 | 4x |
collapse = "\n" |
157 |
), |
|
158 | 4x |
"\n\n", |
159 | 4x |
paste = "\n" |
160 |
) |
|
161 | ||
162 | 4x |
if (asis) { |
163 | 2x |
rv <- knitr::asis_output(rv) |
164 |
} |
|
165 | 4x |
rv |
166 |
} |
|
167 | ||
168 |
#' Render an `IncrementsOrdinal` object |
|
169 |
#' @inherit knit_print.CohortSizeConst return |
|
170 |
#' @param ... passed through to the `knit_print` method of the standard rule |
|
171 |
#' @inheritParams knit_print.CohortSizeConst |
|
172 |
#' @export |
|
173 |
#' @method knit_print IncrementsOrdinal |
|
174 |
#' @rdname knit_print |
|
175 |
knit_print.IncrementsOrdinal <- function(x, ..., asis = TRUE) { |
|
176 | 10x |
assert_flag(asis) |
177 | ||
178 | 8x |
rv <- paste0( |
179 | 8x |
"Based on a toxicity grade of ", |
180 | 8x |
x@grade, |
181 |
": ", |
|
182 | 8x |
paste0(knit_print(x@rule, asis = asis, ...), collapse = "\n"), |
183 | 8x |
"\n\n", |
184 | 8x |
paste = "\n" |
185 |
) |
|
186 | ||
187 | 8x |
if (asis) { |
188 | 2x |
rv <- knitr::asis_output(rv) |
189 |
} |
|
190 | 8x |
rv |
191 |
} |
|
192 | ||
193 |
#' Render an `IncrementsRelativeParts` object |
|
194 |
#' |
|
195 |
#' @inherit knit_print.CohortSizeConst return |
|
196 |
#' @param tox_label (`character`)\cr The word used to describe toxicities. See |
|
197 |
#' Usage Notes below. |
|
198 |
#' @inheritParams knit_print.CohortSizeConst |
|
199 |
#' @section Usage Notes: |
|
200 |
#' `label` defines how toxicities are described. |
|
201 |
#' |
|
202 |
#' It should be a character vector of length 1 or 2. If of length 2, the first |
|
203 |
#' element describes a single toxicity and the second describes all other |
|
204 |
#' toxicity counts. If of length 1, the character `s` is appended to the value |
|
205 |
#' describing a single toxicity. |
|
206 |
#' |
|
207 |
#' @export |
|
208 |
#' @method knit_print IncrementsRelativeParts |
|
209 |
#' @rdname knit_print |
|
210 |
knit_print.IncrementsRelativeParts <- function(x, ..., asis = TRUE, tox_label = c("toxicity", "toxicities")) { |
|
211 | 7x |
assert_flag(asis) |
212 | ||
213 | 5x |
tox_label <- h_prepare_labels(tox_label) |
214 | 5x |
rv <- paste0( |
215 | 5x |
"The maximum increment in Part 1 is defined by the `part1Ladder` slot of ", |
216 | 5x |
"the associated `DataParts` object.\n\n", |
217 | 5x |
"If no ", tox_label[2], " are reported in Part 1, the starting dose for Part 2 ", |
218 | 5x |
"will be ", |
219 | 5x |
ifelse( |
220 | 5x |
x@clean_start == 0, |
221 | 5x |
"the highest dose used in Part 1.\n\n", |
222 | 5x |
paste0( |
223 | 5x |
x@clean_start, |
224 | 5x |
ifelse(abs(x@clean_start) == 1, " dose ", " doses "), |
225 | 5x |
ifelse(x@clean_start < 0, "below ", "above "), |
226 | 5x |
"the highest dose used in Part 1.\n\n" |
227 |
) |
|
228 |
), |
|
229 | 5x |
"If one or more ", tox_label[2], " are reported in Part 1, the starting dose for Part 2 ", |
230 | 5x |
"will be ", |
231 | 5x |
ifelse( |
232 | 5x |
x@dlt_start == 0, |
233 | 5x |
"the highest dose used in Part 1.\n\n", |
234 | 5x |
paste0( |
235 | 5x |
abs(x@dlt_start), |
236 | 5x |
ifelse(abs(x@dlt_start) == 1, " dose ", " doses "), |
237 | 5x |
ifelse(x@dlt_start < 0, "below ", "above "), |
238 | 5x |
"the highest dose used in Part 1.\n\n" |
239 |
) |
|
240 |
), |
|
241 | 5x |
"Once Part 2 has started, the maximum increment in dose levels will be based ", |
242 | 5x |
"on the number of ", tox_label[2], " reported so far, as described in the ", |
243 | 5x |
"following table:" |
244 |
) |
|
245 | ||
246 | 5x |
param <- list(...) |
247 | 5x |
if (!("col.names" %in% names(param))) { |
248 | 5x |
param[["col.names"]] <- c("Lower", "Upper", "Increment") |
249 |
} |
|
250 | 5x |
if (!("caption" %in% names(param))) { |
251 | 5x |
param[["caption"]] <- paste0("Defined by the number of ", tox_label[2], " reported so far") |
252 |
} |
|
253 | 5x |
header <- c(2, 1) |
254 | 5x |
headerLabel <- tox_label[2] |
255 | 5x |
substr(headerLabel, 1, 1) <- toupper(substr(headerLabel, 1, 1)) |
256 | 5x |
names(header) <- c(headerLabel, " ") |
257 | 5x |
param[["x"]] <- tibble( |
258 | 5x |
intervals = x@intervals |
259 |
) %>% |
|
260 | 5x |
h_range_to_minmax(intervals) %>% |
261 | 5x |
tibble::add_column(increments = c(0, x@increments)) |
262 | 5x |
d_tab <- kableExtra::add_header_above( |
263 | 5x |
do.call(knitr::kable, param), |
264 | 5x |
header |
265 |
) |
|
266 | 5x |
rv <- paste(rv, d_tab, "\n\n") |
267 | 5x |
if (asis) { |
268 | 3x |
rv <- knitr::asis_output(rv) |
269 |
} |
|
270 | 5x |
rv |
271 |
} |
|
272 | ||
273 |
#' Render an `IncrementsRelativeDLTCurrent` object |
|
274 |
#' |
|
275 |
#' @description `r lifecycle::badge("experimental")` |
|
276 |
#' @inherit knit_print.CohortSizeConst return |
|
277 |
#' @param tox_label (`character`)\cr The word used to describe toxicities. See |
|
278 |
#' Usage Notes below. |
|
279 |
#' @param ... passed to [knitr::kable()] |
|
280 |
#' @inheritParams knit_print.CohortSizeConst |
|
281 |
#' @section Usage Notes: |
|
282 |
#' The default value of `col.names` is `c("Min", "Max", "Increment")` and that |
|
283 |
#' of `caption` is `"Defined by number of DLTs in the current cohort"`. These values |
|
284 |
#' can be overridden by passing `col.names` and `caption` in the function call. |
|
285 |
#' |
|
286 |
#' `tox_label` defines how toxicities are described. |
|
287 |
#' |
|
288 |
#' It should be a character vector of length 1 or 2. If of length 2, the first |
|
289 |
#' element describes a single toxicity and the second describes all other |
|
290 |
#' toxicity counts. If of length 1, the character `s` is appended to the value |
|
291 |
#' describing a single toxicity. |
|
292 |
#' |
|
293 |
#' @export |
|
294 |
#' @method knit_print IncrementsRelativeDLTCurrent |
|
295 |
#' @rdname knit_print |
|
296 |
knit_print.IncrementsRelativeDLTCurrent <- function( |
|
297 |
x, |
|
298 |
..., |
|
299 |
asis = TRUE, |
|
300 |
tox_label = c("DLT", "DLTs")) { |
|
301 | 6x |
assert_flag(asis) |
302 | 4x |
assert_character(tox_label, min.len = 1, max.len = 2, any.missing = FALSE) |
303 | ||
304 | 4x |
if (length(tox_label) == 1) { |
305 | ! |
tox_label[2] <- paste0(tox_label[1], "s") |
306 |
} |
|
307 | ||
308 | 4x |
param <- list(...) |
309 | 4x |
if (!("col.names" %in% names(param))) { |
310 | 4x |
param[["col.names"]] <- c("Min", "Max", "Increment") |
311 |
} |
|
312 | 4x |
if (!("caption" %in% names(param))) { |
313 | 4x |
param[["caption"]] <- paste0( |
314 | 4x |
"Defined by number of ", |
315 | 4x |
tox_label[2], |
316 | 4x |
" reported in the current cohort" |
317 |
) |
|
318 |
} |
|
319 | 4x |
param[["x"]] <- tidy(x) |
320 | 4x |
header_text <- c(2, 1) |
321 | 4x |
names(header_text) <- c(paste0("No ", tox_label[2]), " ") |
322 | 4x |
rv <- kableExtra::add_header_above( |
323 | 4x |
do.call(knitr::kable, param), |
324 | 4x |
header_text |
325 |
) |
|
326 | 4x |
rv <- paste0(rv, "\n\n") |
327 | ||
328 | 4x |
if (asis) { |
329 | 2x |
rv <- knitr::asis_output(rv) |
330 |
} |
|
331 | 4x |
rv |
332 |
} |
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(object@overdose, bounds_closed = TRUE) |
65 | 20x |
v$check(is_overdose_ok, "overdose has to be a probability range") |
66 | ||
67 | 20x |
is_unacceptable_ok <- test_probability_range(object@unacceptable, bounds_closed = TRUE) |
68 | 20x |
v$check(is_unacceptable_ok, "unacceptable has to be a probability range") |
69 | ||
70 | 20x |
if (is_overdose_ok && is_unacceptable_ok) { |
71 | 12x |
v$check( |
72 | 12x |
object@overdose[2] <= object@unacceptable[1], |
73 | 12x |
"lower bound of unacceptable has to be >= than upper bound of overdose" |
74 |
) |
|
75 |
} |
|
76 | 20x |
if (is_unacceptable_ok) { |
77 | 16x |
losses_len <- ifelse(all(object@unacceptable == c(1, 1)), 3L, 4L) |
78 | 16x |
v$check( |
79 | 16x |
test_numeric(object@losses, lower = 0, finite = TRUE, any.missing = FALSE, len = losses_len), |
80 | 16x |
"losses must be a vector of non-negative numbers of length 3 if unacceptable is c(1, 1), otherwise 4" |
81 |
) |
|
82 |
} |
|
83 | 20x |
v$result() |
84 |
} |
|
85 | ||
86 |
#' @describeIn v_next_best validates that the [`NextBestDualEndpoint`] object |
|
87 |
#' contains valid probability objects. |
|
88 |
v_next_best_dual_endpoint <- function(object) { |
|
89 | 22x |
v <- Validate() |
90 | 22x |
v$check( |
91 | 22x |
test_flag(object@target_relative), |
92 | 22x |
"target_relative must be a flag" |
93 |
) |
|
94 | 22x |
if (isTRUE(object@target_relative)) { |
95 | 17x |
v$check( |
96 | 17x |
test_probability_range(object@target), |
97 | 17x |
"target has to be a probability range when target_relative is TRUE" |
98 |
) |
|
99 |
} else { |
|
100 | 5x |
v$check( |
101 | 5x |
test_range(object@target), |
102 | 5x |
"target must be a numeric range" |
103 |
) |
|
104 |
} |
|
105 | 22x |
v$check( |
106 | 22x |
test_probability_range(object@overdose), |
107 | 22x |
"overdose has to be a probability range" |
108 |
) |
|
109 | 22x |
v$check( |
110 | 22x |
test_probability(object@max_overdose_prob, bounds_closed = FALSE), |
111 | 22x |
"max_overdose_prob must be a probability value from (0, 1) interval" |
112 |
) |
|
113 | 22x |
v$check( |
114 | 22x |
test_probability(object@target_thresh), |
115 | 22x |
"target_thresh must be a probability value from [0, 1] interval" |
116 |
) |
|
117 | 22x |
v$result() |
118 |
} |
|
119 | ||
120 |
#' @describeIn v_next_best validates that the [`NextBestMinDist`] object |
|
121 |
#' contains valid `target` object. |
|
122 |
v_next_best_min_dist <- function(object) { |
|
123 | 5x |
v <- Validate() |
124 | 5x |
v$check( |
125 | 5x |
test_probability(object@target, bounds_closed = FALSE), |
126 | 5x |
"target must be a probability value from (0, 1) interval" |
127 |
) |
|
128 | 5x |
v$result() |
129 |
} |
|
130 | ||
131 |
#' @describeIn v_next_best validates that the [`NextBestInfTheory`] object |
|
132 |
#' contains valid `target` and `asymmetry` objects. |
|
133 |
v_next_best_inf_theory <- function(object) { |
|
134 | 9x |
v <- Validate() |
135 | 9x |
v$check( |
136 | 9x |
test_probability(object@target, bounds_closed = FALSE), |
137 | 9x |
"target must be a probability value from (0, 1) interval" |
138 |
) |
|
139 | 9x |
v$check( |
140 | 9x |
test_number(object@asymmetry, finite = TRUE) && h_in_range(object@asymmetry, c(0, 2), FALSE), |
141 | 9x |
"asymmetry must be a number from (0, 2) interval" |
142 |
) |
|
143 | 9x |
v$result() |
144 |
} |
|
145 | ||
146 |
#' @describeIn v_next_best validates that the [`NextBestTD`] object |
|
147 |
#' contains valid `prob_target_drt` and `prob_target_eot` probabilities. |
|
148 |
v_next_best_td <- function(object) { |
|
149 | 9x |
v <- Validate() |
150 | 9x |
v$check( |
151 | 9x |
test_probability(object@prob_target_drt, bounds_closed = FALSE), |
152 | 9x |
"prob_target_drt must be a probability value from (0, 1) interval" |
153 |
) |
|
154 | 9x |
v$check( |
155 | 9x |
test_probability(object@prob_target_eot, bounds_closed = FALSE), |
156 | 9x |
"prob_target_eot must be a probability value from (0, 1) interval" |
157 |
) |
|
158 | 9x |
v$result() |
159 |
} |
|
160 | ||
161 |
#' @describeIn v_next_best validates that the [`NextBestTDsamples`] object |
|
162 |
#' contains valid `derive` function. |
|
163 |
v_next_best_td_samples <- function(object) { |
|
164 | 3x |
v <- Validate() |
165 | 3x |
v$check( |
166 | 3x |
test_function(object@derive, nargs = 1), |
167 | 3x |
"derive must have a single argument" |
168 |
) |
|
169 | 3x |
v$check( |
170 | 3x |
test_number(object@derive(1:5)), |
171 | 3x |
"derive must accept numerical vector as an argument and return a number" |
172 |
) |
|
173 | 3x |
v$result() |
174 |
} |
|
175 | ||
176 |
#' @describeIn v_next_best validates that the [`NextBestMaxGainSamples`] object |
|
177 |
#' contains valid `derive` and `mg_derive` functions. |
|
178 |
v_next_best_max_gain_samples <- function(object) { |
|
179 | 5x |
v <- Validate() |
180 | 5x |
v$check( |
181 | 5x |
test_function(object@derive, nargs = 1), |
182 | 5x |
"derive must have a single argument" |
183 |
) |
|
184 | 5x |
v$check( |
185 | 5x |
test_number(object@derive(1:5)), |
186 | 5x |
"derive must accept numerical vector as an argument and return a number" |
187 |
) |
|
188 | 5x |
v$check( |
189 | 5x |
test_function(object@mg_derive, nargs = 1), |
190 | 5x |
"mg_derive must have a single argument" |
191 |
) |
|
192 | 5x |
v$check( |
193 | 5x |
test_number(object@mg_derive(1:5)), |
194 | 5x |
"mg_derive must accept numerical vector as an argument and return a number" |
195 |
) |
|
196 | 5x |
v$result() |
197 |
} |
|
198 | ||
199 |
#' @describeIn v_next_best validates that the [`NextBestProbMTDLTE`] object |
|
200 |
#' contains valid `target` probability and `method` string value. |
|
201 |
v_next_best_prob_mtd_lte <- function(object) { |
|
202 | 5x |
v <- Validate() |
203 | 5x |
v$check( |
204 | 5x |
test_probability(object@target, bounds_closed = FALSE), |
205 | 5x |
"target must be a probability value from (0, 1) interval" |
206 |
) |
|
207 | 5x |
v$result() |
208 |
} |
|
209 | ||
210 |
#' @describeIn v_next_best validates that the [`NextBestProbMTDMinDist`] object |
|
211 |
#' contains valid `target` probability and `method` string value. |
|
212 |
v_next_best_prob_mtd_min_dist <- function(object) { |
|
213 | 5x |
v <- Validate() |
214 | 5x |
v$check( |
215 | 5x |
test_probability(object@target, bounds_closed = FALSE), |
216 | 5x |
"target must be a probability value from (0, 1) interval" |
217 |
) |
|
218 | 5x |
v$result() |
219 |
} |
|
220 | ||
221 |
# Increments ---- |
|
222 | ||
223 |
#' Internal Helper Functions for Validation of [`Increments`] Objects |
|
224 |
#' |
|
225 |
#' @description `r lifecycle::badge("stable")` |
|
226 |
#' |
|
227 |
#' These functions are only used internally to validate the format of an input |
|
228 |
#' [`Increments`] or inherited classes and therefore not exported. |
|
229 |
#' |
|
230 |
#' @name v_increments |
|
231 |
#' @param object (`Increments`)\cr object to validate. |
|
232 |
#' @return A `character` vector with the validation failure messages, |
|
233 |
#' or `TRUE` in case validation passes. |
|
234 |
NULL |
|
235 | ||
236 |
#' @describeIn v_increments validates that the [`IncrementsRelative`] object |
|
237 |
#' contains valid `intervals` and `increments` parameters. |
|
238 |
v_increments_relative <- function(object) { |
|
239 | 6x |
v <- Validate() |
240 | 6x |
v$check( |
241 | 6x |
test_numeric( |
242 | 6x |
object@intervals, |
243 | 6x |
lower = 0, finite = TRUE, any.missing = FALSE, unique = TRUE, sorted = TRUE |
244 |
), |
|
245 | 6x |
"intervals has to be a numerical vector with unique, finite, non-negative and sorted non-missing values" |
246 |
) |
|
247 | 6x |
v$check( |
248 | 6x |
test_numeric(object@increments, finite = TRUE, any.missing = FALSE, len = length(object@intervals)), |
249 | 6x |
"increments has to be a numerical vector of the same length as `intervals` with finite values" |
250 |
) |
|
251 | 6x |
v$result() |
252 |
} |
|
253 | ||
254 |
#' @describeIn v_increments validates that the [`IncrementsRelativeParts`] object |
|
255 |
#' contains valid `dlt_start` and `clean_start` parameters. |
|
256 |
v_increments_relative_parts <- function(object) { |
|
257 | 6x |
v <- Validate() |
258 | 6x |
is_dlt_start_ok <- test_int(object@dlt_start) |
259 | 6x |
v$check(is_dlt_start_ok, "dlt_start must be an integer number") |
260 | 6x |
if (is_dlt_start_ok) { |
261 | 4x |
v$check( |
262 | 4x |
test_int(object@clean_start, lower = object@dlt_start), |
263 | 4x |
"clean_start must be an integer number and it must be >= dlt_start" |
264 |
) |
|
265 |
} |
|
266 | 6x |
v$result() |
267 |
} |
|
268 | ||
269 |
#' @describeIn v_increments validates that the [`IncrementsRelativeDLT`] object |
|
270 |
#' contains valid `intervals` and `increments` parameters. |
|
271 |
v_increments_relative_dlt <- function(object) { |
|
272 | 6x |
v <- Validate() |
273 | 6x |
v$check( |
274 | 6x |
test_integer(object@intervals, lower = 0, any.missing = FALSE, unique = TRUE, sorted = TRUE), |
275 | 6x |
"intervals has to be an integer vector with unique, finite, non-negative and sorted non-missing values" |
276 |
) |
|
277 | 6x |
v$check( |
278 | 6x |
test_numeric(object@increments, finite = TRUE, any.missing = FALSE, len = length(object@intervals)), |
279 | 6x |
"increments has to be a numerical vector of the same length as `intervals` with finite values" |
280 |
) |
|
281 | 6x |
v$result() |
282 |
} |
|
283 | ||
284 |
#' @describeIn v_increments validates that the [`IncrementsDoseLevels`] object |
|
285 |
#' contains valid `levels` and `basis_level` option. |
|
286 |
v_increments_dose_levels <- function(object) { |
|
287 | 9x |
v <- Validate() |
288 | 9x |
v$check( |
289 | 9x |
test_int(object@levels, lower = .Machine$double.xmin), |
290 | 9x |
"levels must be scalar positive integer" |
291 |
) |
|
292 | 9x |
v$check( |
293 | 9x |
test_string(object@basis_level, pattern = "^last$|^max$"), |
294 | 9x |
"basis_level must be either 'last' or 'max'" |
295 |
) |
|
296 | 9x |
v$result() |
297 |
} |
|
298 | ||
299 |
#' @describeIn v_increments validates that the [`IncrementsHSRBeta`] |
|
300 |
#' object contains valid probability target, threshold and shape parameters. |
|
301 |
v_increments_hsr_beta <- function(object) { |
|
302 | 12x |
v <- Validate() |
303 | 12x |
v$check( |
304 | 12x |
test_probability(object@target, bounds_closed = FALSE), |
305 | 12x |
"target must be a probability value from (0, 1) interval" |
306 |
) |
|
307 | 12x |
v$check( |
308 | 12x |
test_probability(object@prob, bounds_closed = FALSE), |
309 | 12x |
"prob must be a probability value from (0, 1) interval" |
310 |
) |
|
311 | 12x |
v$check( |
312 | 12x |
test_number(object@a, lower = .Machine$double.xmin, finite = TRUE), |
313 | 12x |
"Beta distribution shape parameter a must be a positive scalar" |
314 |
) |
|
315 | 12x |
v$check( |
316 | 12x |
test_number(object@b, lower = .Machine$double.xmin, finite = TRUE), |
317 | 12x |
"Beta distribution shape parameter b must be a positive scalar" |
318 |
) |
|
319 | 12x |
v$result() |
320 |
} |
|
321 | ||
322 |
#' @describeIn v_increments validates that the [`IncrementsMin`] |
|
323 |
#' object contains a list with `Increments` objects. |
|
324 |
v_increments_min <- function(object) { |
|
325 | 2x |
v <- Validate() |
326 | 2x |
v$check( |
327 | 2x |
all(sapply(object@increments_list, test_class, "Increments")), |
328 | 2x |
"all elements in increments_list must be of Increments class" |
329 |
) |
|
330 | 2x |
v$result() |
331 |
} |
|
332 | ||
333 |
#' @describeIn v_increments validates the [`IncrementsMaxToxProb`] |
|
334 |
v_increments_maxtoxprob <- function(object) { |
|
335 | ! |
v <- Validate() |
336 | ! |
v$check( |
337 | ! |
test_probabilities(object@prob), |
338 | ! |
"prob must be a vector of probabilities with minimum length 1 and no missing values" |
339 |
) |
|
340 | ! |
v$result() |
341 |
} |
|
342 | ||
343 |
# Stopping ---- |
|
344 | ||
345 |
#' Internal Helper Functions for Validation of [`Stopping`] Objects |
|
346 |
#' |
|
347 |
#' @description `r lifecycle::badge("stable")` |
|
348 |
#' |
|
349 |
#' These functions are only used internally to validate the format of an input |
|
350 |
#' [`Stopping`] or inherited classes and therefore not exported. |
|
351 |
#' |
|
352 |
#' @name v_stopping |
|
353 |
#' @param object (`Stopping`)\cr object to validate. |
|
354 |
#' @return A `character` vector with the validation failure messages, |
|
355 |
#' or `TRUE` in case validation passes. |
|
356 |
NULL |
|
357 | ||
358 |
#' @describeIn v_stopping validates that the [`StoppingCohortsNearDose`] |
|
359 |
#' object contains valid `nCohorts` and `percentage` parameters. |
|
360 |
v_stopping_cohorts_near_dose <- function(object) { |
|
361 | 11x |
v <- Validate() |
362 | 11x |
v$check( |
363 | 11x |
test_int(object@nCohorts, lower = .Machine$double.xmin), |
364 | 11x |
"nCohorts must be positive integer scalar" |
365 |
) |
|
366 | 11x |
v$check( |
367 | 11x |
test_probability(object@percentage / 100), |
368 | 11x |
"percentage must be a number between 0 and 100" |
369 |
) |
|
370 | 11x |
v$result() |
371 |
} |
|
372 | ||
373 |
#' @describeIn v_stopping validates that the [`StoppingPatientsNearDose`] |
|
374 |
#' object contains valid `nPatients` and `percentage` parameters. |
|
375 |
v_stopping_patients_near_dose <- function(object) { |
|
376 | 11x |
v <- Validate() |
377 | 11x |
v$check( |
378 | 11x |
test_int(object@nPatients, lower = .Machine$double.xmin), |
379 | 11x |
"nPatients must be positive integer scalar" |
380 |
) |
|
381 | 11x |
v$check( |
382 | 11x |
test_probability(object@percentage / 100), |
383 | 11x |
"percentage must be a number between 0 and 100" |
384 |
) |
|
385 | 11x |
v$result() |
386 |
} |
|
387 | ||
388 |
#' @describeIn v_stopping validates that the [`StoppingMinCohorts`] |
|
389 |
#' object contains valid `nCohorts` parameter. |
|
390 |
v_stopping_min_cohorts <- function(object) { |
|
391 | 4x |
v <- Validate() |
392 | 4x |
v$check( |
393 | 4x |
test_int(object@nCohorts, lower = .Machine$double.xmin), |
394 | 4x |
"nCohorts must be positive integer scalar" |
395 |
) |
|
396 | 4x |
v$result() |
397 |
} |
|
398 | ||
399 |
#' @describeIn v_stopping validates that the [`StoppingMinPatients`] |
|
400 |
#' object contains valid `nPatients` parameter. |
|
401 |
v_stopping_min_patients <- function(object) { |
|
402 | 4x |
v <- Validate() |
403 | 4x |
v$check( |
404 | 4x |
test_int(object@nPatients, lower = .Machine$double.xmin), |
405 | 4x |
"nPatients must be positive integer scalar" |
406 |
) |
|
407 | 4x |
v$result() |
408 |
} |
|
409 | ||
410 |
#' @describeIn v_stopping validates that the [`StoppingTargetProb`] |
|
411 |
#' object contains valid `target` and `prob` parameters. |
|
412 |
v_stopping_target_prob <- function(object) { |
|
413 | 10x |
v <- Validate() |
414 | 10x |
v$check( |
415 | 10x |
test_probability_range(object@target), |
416 | 10x |
"target has to be a probability range" |
417 |
) |
|
418 | 10x |
v$check( |
419 | 10x |
test_probability(object@prob, bounds_closed = FALSE), |
420 | 10x |
"prob must be a probability value from (0, 1) interval" |
421 |
) |
|
422 | 10x |
v$result() |
423 |
} |
|
424 | ||
425 |
#' @describeIn v_stopping validates that the [`StoppingMTDdistribution`] |
|
426 |
#' object contains valid `target`, `thresh` and `prob` parameters. |
|
427 |
v_stopping_mtd_distribution <- function(object) { |
|
428 | 13x |
v <- Validate() |
429 | 13x |
v$check( |
430 | 13x |
test_probability(object@target, bounds_closed = FALSE), |
431 | 13x |
"target must be a probability value from (0, 1) interval" |
432 |
) |
|
433 | 13x |
v$check( |
434 | 13x |
test_probability(object@thresh, bounds_closed = FALSE), |
435 | 13x |
"thresh must be a probability value from (0, 1) interval" |
436 |
) |
|
437 | 13x |
v$check( |
438 | 13x |
test_probability(object@prob, bounds_closed = FALSE), |
439 | 13x |
"prob must be a probability value from (0, 1) interval" |
440 |
) |
|
441 | 13x |
v$result() |
442 |
} |
|
443 | ||
444 |
#' @describeIn v_stopping validates that the [`StoppingMTDCV`] object |
|
445 |
#' contains valid probability target and percentage threshold. |
|
446 |
v_stopping_mtd_cv <- function(object) { |
|
447 | 11x |
v <- Validate() |
448 | 11x |
v$check( |
449 | 11x |
test_probability(object@target, bounds_closed = FALSE), |
450 | 11x |
"target must be probability value from (0, 1) interval" |
451 |
) |
|
452 | 11x |
v$check( |
453 | 11x |
test_probability(object@thresh_cv / 100, bounds_closed = c(FALSE, TRUE)), |
454 | 11x |
"thresh_cv must be percentage > 0" |
455 |
) |
|
456 | 11x |
v$result() |
457 |
} |
|
458 | ||
459 |
#' @describeIn v_stopping validates that the [`StoppingTargetBiomarker`] object |
|
460 |
#' contains valid `target`, `is_relative` and `prob`slots. |
|
461 |
v_stopping_target_biomarker <- function(object) { |
|
462 | 16x |
v <- Validate() |
463 | 16x |
v$check( |
464 | 16x |
test_flag(object@is_relative), |
465 | 16x |
"is_relative must be a flag" |
466 |
) |
|
467 | 16x |
if (isTRUE(object@is_relative)) { |
468 | 10x |
v$check( |
469 | 10x |
test_probability_range(object@target), |
470 | 10x |
"target has to be a probability range when is_relative flag is 'TRUE'" |
471 |
) |
|
472 |
} else { |
|
473 | 6x |
v$check( |
474 | 6x |
test_range(object@target, finite = TRUE), |
475 | 6x |
"target must be a numeric range" |
476 |
) |
|
477 |
} |
|
478 | 16x |
v$check( |
479 | 16x |
test_probability(object@prob, bounds_closed = FALSE), |
480 | 16x |
"prob must be a probability value from (0, 1) interval" |
481 |
) |
|
482 | 16x |
v$result() |
483 |
} |
|
484 | ||
485 |
#' @describeIn v_stopping validates that the [`StoppingList`] object |
|
486 |
#' contains valid `stop_list`, `summary` slots. |
|
487 |
v_stopping_list <- function(object) { |
|
488 | 8x |
v <- Validate() |
489 | 8x |
v$check( |
490 | 8x |
all(sapply(object@stop_list, test_class, "Stopping")), |
491 | 8x |
"every stop_list element must be of class 'Stopping'" |
492 |
) |
|
493 | 8x |
is_summary_ok <- test_function(object@summary, nargs = 1) |
494 | 8x |
v$check( |
495 | 8x |
is_summary_ok, |
496 | 8x |
"summary must be a function that accepts a single argument, without ..." |
497 |
) |
|
498 | 8x |
if (is_summary_ok) { |
499 | 5x |
summary_res <- object@summary( |
500 | 5x |
rep(c(TRUE, FALSE), length.out = length(object@stop_list)) |
501 |
) |
|
502 | 5x |
v$check( |
503 | 5x |
test_flag(summary_res), |
504 | 5x |
"summary must accept a logical vector of the same length as 'stop_list' and return a boolean value" |
505 |
) |
|
506 |
} |
|
507 | 8x |
v$result() |
508 |
} |
|
509 | ||
510 |
#' @describeIn v_stopping validates that the [`StoppingAll`] object |
|
511 |
#' contains valid `stop_list` slot. |
|
512 |
v_stopping_all <- function(object) { |
|
513 | 3x |
v <- Validate() |
514 | 3x |
v$check( |
515 | 3x |
all(sapply(object@stop_list, test_class, "Stopping")), |
516 | 3x |
"every stop_list element must be of class 'Stopping'" |
517 |
) |
|
518 | 3x |
v$result() |
519 |
} |
|
520 | ||
521 |
#' @describeIn v_stopping validates that the [`StoppingTDCIRatio`] object |
|
522 |
#' contains valid `target_ratio` and `prob_target` slots. |
|
523 |
v_stopping_tdci_ratio <- function(object) { |
|
524 | 9x |
v <- Validate() |
525 | 9x |
v$check( |
526 | 9x |
test_number(object@target_ratio, lower = .Machine$double.xmin, finite = TRUE), |
527 | 9x |
"target_ratio must be a positive number" |
528 |
) |
|
529 | 9x |
v$check( |
530 | 9x |
test_probability(object@prob_target), |
531 | 9x |
"prob_target must be a probability value from [0, 1] interval" |
532 |
) |
|
533 | 9x |
v$result() |
534 |
} |
|
535 | ||
536 |
# CohortSize ---- |
|
537 | ||
538 |
#' Internal Helper Functions for Validation of [`CohortSize`] Objects |
|
539 |
#' |
|
540 |
#' @description `r lifecycle::badge("stable")` |
|
541 |
#' |
|
542 |
#' These functions are only used internally to validate the format of an input |
|
543 |
#' [`CohortSize`] or inherited classes and therefore not exported. |
|
544 |
#' |
|
545 |
#' @name v_cohort_size |
|
546 |
#' @param object (`CohortSize`)\cr object to validate. |
|
547 |
#' @return A `character` vector with the validation failure messages, |
|
548 |
#' or `TRUE` in case validation passes. |
|
549 |
NULL |
|
550 | ||
551 |
#' @describeIn v_cohort_size validates that the [`CohortSizeRange`] object |
|
552 |
#' contains valid `intervals` and `cohort_size` slots. |
|
553 |
v_cohort_size_range <- function(object) { |
|
554 | 13x |
v <- Validate() |
555 | 13x |
v$check( |
556 | 13x |
test_numeric( |
557 | 13x |
object@intervals, |
558 | 13x |
lower = 0, finite = TRUE, any.missing = FALSE, min.len = 1, unique = TRUE, sorted = TRUE |
559 |
), |
|
560 | 13x |
"intervals must be a numeric vector with non-negative, sorted (asc.) and unique values" |
561 |
) |
|
562 | 13x |
v$check( |
563 | 13x |
test_integer( |
564 | 13x |
object@cohort_size, |
565 | 13x |
lower = 0, any.missing = FALSE, len = length(object@intervals) |
566 |
), |
|
567 | 13x |
"cohort_size must be an integer vector of the same length as intervals, containing non-negative values only" |
568 |
) |
|
569 | 13x |
v$result() |
570 |
} |
|
571 | ||
572 |
#' @describeIn v_cohort_size validates that the [`CohortSizeDLT`] object |
|
573 |
#' contains valid `intervals` and `cohort_size` slots. |
|
574 |
v_cohort_size_dlt <- function(object) { |
|
575 | 12x |
v <- Validate() |
576 | 12x |
v$check( |
577 | 12x |
test_integer( |
578 | 12x |
object@intervals, |
579 | 12x |
lower = 0, any.missing = FALSE, min.len = 1, unique = TRUE, sorted = TRUE |
580 |
), |
|
581 | 12x |
"intervals must be an integer vector with non-negative, sorted (asc.) and unique values" |
582 |
) |
|
583 | 12x |
v$check( |
584 | 12x |
test_integer( |
585 | 12x |
object@cohort_size, |
586 | 12x |
lower = 0, any.missing = FALSE, len = length(object@intervals) |
587 |
), |
|
588 | 12x |
"cohort_size must be an integer vector of the same length as intervals, containing non-negative values only" |
589 |
) |
|
590 | 12x |
v$result() |
591 |
} |
|
592 | ||
593 |
#' @describeIn v_cohort_size validates that the [`CohortSizeConst`] object |
|
594 |
#' contains valid `size` slot. |
|
595 |
v_cohort_size_const <- function(object) { |
|
596 | 5x |
v <- Validate() |
597 | 5x |
v$check( |
598 | 5x |
test_int(object@size, lower = 0), |
599 | 5x |
"size needs to be a non-negative scalar" |
600 |
) |
|
601 | 5x |
v$result() |
602 |
} |
|
603 | ||
604 |
#' @describeIn v_cohort_size validates that the [`CohortSizeParts`] object |
|
605 |
#' contains valid `sizes` slot. |
|
606 |
v_cohort_size_parts <- function(object) { |
|
607 | 9x |
v <- Validate() |
608 | 9x |
v$check( |
609 | 9x |
test_integer(object@cohort_sizes, lower = .Machine$double.xmin, any.missing = FALSE, len = 2), |
610 | 9x |
"cohort_sizes needs to be an integer vector of length 2 with all elements positive" |
611 |
) |
|
612 | 9x |
v$result() |
613 |
} |
|
614 | ||
615 |
#' @describeIn v_cohort_size validates that the [`CohortSizeMax`] object |
|
616 |
#' contains valid `cohort_sizes` slot. |
|
617 |
v_cohort_size_max <- function(object) { |
|
618 | 8x |
v <- Validate() |
619 | 8x |
v$check( |
620 | 8x |
test_list(object@cohort_sizes, types = "CohortSize", any.missing = FALSE, min.len = 2, unique = TRUE), |
621 | 8x |
"cohort_sizes must be a list of CohortSize (unique) objects only and be of length >= 2" |
622 |
) |
|
623 | 8x |
v$result() |
624 |
} |
|
625 | ||
626 |
# SafetyWindow ---- |
|
627 | ||
628 |
#' Internal Helper Functions for Validation of [`SafetyWindow`] Objects |
|
629 |
#' |
|
630 |
#' @description `r lifecycle::badge("stable")` |
|
631 |
#' |
|
632 |
#' These functions are only used internally to validate the format of an input |
|
633 |
#' [`SafetyWindow`] or inherited classes and therefore not exported. |
|
634 |
#' |
|
635 |
#' @name v_safety_window |
|
636 |
#' @param object (`SafetyWindow`)\cr object to validate. |
|
637 |
#' @return A `character` vector with the validation failure messages, |
|
638 |
#' or `TRUE` in case validation passes. |
|
639 |
NULL |
|
640 | ||
641 |
#' @describeIn v_safety_window validates that the [`SafetyWindowSize`] object |
|
642 |
#' contains valid slots. |
|
643 |
v_safety_window_size <- function(object) { |
|
644 | 21x |
v <- Validate() |
645 | 21x |
v$check( |
646 | 21x |
test_list(object@gap, types = "integer", any.missing = FALSE, min.len = 1), |
647 | 21x |
"gap must be a list of length >= 1 with integer vectors only" |
648 |
) |
|
649 | 21x |
v$check( |
650 | 21x |
all(sapply(object@gap, test_integer, lower = 0, any.missing = FALSE, min.len = 1)), |
651 | 21x |
"every element in gap list has to be an integer vector with non-negative and non-missing values" |
652 |
) |
|
653 | 21x |
pg_len <- length(object@gap) |
654 | 21x |
v$check( |
655 | 21x |
test_integer( |
656 | 21x |
object@size, |
657 | 21x |
lower = .Machine$double.xmin, any.missing = FALSE, len = pg_len, unique = TRUE, sorted = TRUE |
658 |
), |
|
659 | 21x |
"size has to be an integer vector, of the same length as gap, with positive, unique and sorted non-missing values" |
660 |
) |
|
661 | 21x |
v$check( |
662 | 21x |
test_int(object@follow, lower = .Machine$double.xmin), |
663 | 21x |
"follow has to be a positive integer number" |
664 |
) |
|
665 | 21x |
v$check( |
666 | 21x |
test_int(object@follow_min, lower = .Machine$double.xmin), |
667 | 21x |
"follow_min has to be a positive integer number" |
668 |
) |
|
669 | 21x |
v$result() |
670 |
} |
|
671 | ||
672 |
#' @describeIn v_safety_window validates that the [`SafetyWindowConst`] object |
|
673 |
#' contains valid slots. |
|
674 |
v_safety_window_const <- function(object) { |
|
675 | 15x |
v <- Validate() |
676 | 15x |
v$check( |
677 | 15x |
test_integer(object@gap, lower = 0, any.missing = FALSE), |
678 | 15x |
"gap has to be an integer vector with non-negative and non-missing elements" |
679 |
) |
|
680 | 15x |
v$check( |
681 | 15x |
test_int(object@follow, lower = .Machine$double.xmin), |
682 | 15x |
"follow has to be a positive integer number" |
683 |
) |
|
684 | 15x |
v$check( |
685 | 15x |
test_int(object@follow_min, lower = .Machine$double.xmin), |
686 | 15x |
"follow_min has to be a positive integer number" |
687 |
) |
|
688 | 15x |
v$result() |
689 |
} |
|
690 | ||
691 |
#' @describeIn v_next_best validates that the [`NextBestOrdinal`] object |
|
692 |
#' contains valid `grade` and standard `NextBest` rule. |
|
693 |
v_next_best_ordinal <- function(object) { |
|
694 | ! |
v <- Validate() |
695 | ! |
v$check( |
696 | ! |
test_integer(object@grade, lower = 1), |
697 | ! |
"grade must be a positive integer" |
698 |
) |
|
699 | ! |
v$check( |
700 | ! |
test_class(object@rule, "NextBest"), |
701 | ! |
"rule must be a NextBest object" |
702 |
) |
|
703 | ! |
v$result() |
704 |
} |
|
705 | ||
706 |
#' @describeIn v_increments validates that the [`IncrementsOrdinal`] object |
|
707 |
#' contains valid `grade` and standard `Increments` rule. |
|
708 |
v_increments_ordinal <- function(object) { |
|
709 | ! |
v <- Validate() |
710 | ! |
v$check( |
711 | ! |
test_integer(object@grade, lower = 1), |
712 | ! |
"grade must be a positive integer" |
713 |
) |
|
714 | ! |
v$check( |
715 | ! |
test_class(object@rule, "Increments"), |
716 | ! |
"rule must be a Increments object" |
717 |
) |
|
718 | ! |
v$result() |
719 |
} |
|
720 | ||
721 |
#' @describeIn v_increments validates that the [`CohortSizeOrdinal`] object |
|
722 |
#' contains valid `grade` and standard `CohortSize` rule. |
|
723 |
v_cohort_size_ordinal <- function(object) { |
|
724 | ! |
v <- Validate() |
725 | ! |
v$check( |
726 | ! |
test_integer(object@grade, lower = 1), |
727 | ! |
"grade must be a positive integer" |
728 |
) |
|
729 | ! |
v$check( |
730 | ! |
test_class(object@rule, "CohortSize"), |
731 | ! |
"rule must be a CohortSize object" |
732 |
) |
|
733 | ! |
v$result() |
734 |
} |
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(grid, lower = 0, min.len = 2, unique = TRUE, finite = TRUE, sorted = TRUE, any.missing = FALSE) |
15 | 106x |
assert_character(units, len = 1) |
16 | ||
17 | 106x |
n <- length(grid) |
18 | 106x |
units <- h_prepare_units(units) |
19 | 106x |
formattedGrid <- if (is.na(fmt)) { |
20 | 104x |
as.character(grid) |
21 |
} else { |
|
22 | 2x |
sprintf(fmt, grid) |
23 |
} |
|
24 | 106x |
paste0( |
25 | 106x |
paste( |
26 | 106x |
lapply( |
27 | 106x |
formattedGrid[1:(n - 1)], |
28 | 106x |
paste0, |
29 | 106x |
sep = units |
30 |
), |
|
31 | 106x |
collapse = ", " |
32 |
), |
|
33 | 106x |
" and ", |
34 | 106x |
formattedGrid[n], |
35 | 106x |
paste0(units, ".\n\n") |
36 |
) |
|
37 |
} |
|
38 | ||
39 |
#' Set Column Headers in Custom `knit_print` Methods |
|
40 |
#' |
|
41 |
#' This is a helper method used `knit_print` for `crmPack` classes. |
|
42 |
#' |
|
43 |
#' @param x (`ANY`)\cr object that will be printed |
|
44 |
#' @param param (`list`)\cr A list of the `...` parameters passed to `knit_print` |
|
45 |
#' @param summarise (`flag`)\cr Is the object to be summarised (default) or listed? |
|
46 |
#' @return A character vector of column names. |
|
47 |
#' @noRd |
|
48 |
h_knit_print_set_headers <- function(x, param, summarise, ...) { |
|
49 | 97x |
UseMethod("h_knit_print_set_headers") |
50 |
} |
|
51 | ||
52 |
#' @description `r lifecycle::badge("experimental")` |
|
53 |
#' @rdname knit_print_set_headers |
|
54 |
#' @noRd |
|
55 |
h_knit_print_set_headers.GeneralData <- function(x, param, summarise, ...) { |
|
56 | 42x |
if (!("col.names" %in% names(param))) { |
57 | 42x |
if (summarise == "none") { |
58 | 40x |
param[["col.names"]] <- c("ID", "Cohort", "Dose", "DLT?") |
59 | 2x |
} else if (summarise == "dose") { |
60 | 1x |
param[["col.names"]] <- c("Dose", "Evaluable", "With Toxicities") |
61 |
} else { |
|
62 | 1x |
param[["col.names"]] <- c("Cohort", "Evaluable", "With Toxicities") |
63 |
} |
|
64 |
} |
|
65 | 42x |
param |
66 |
} |
|
67 | ||
68 |
#' @description `r lifecycle::badge("experimental")` |
|
69 |
#' @rdname knit_print_set_headers |
|
70 |
#' @noRd |
|
71 |
h_knit_print_set_headers.DataDA <- function(x, param, summarise, ...) { |
|
72 | 10x |
if (!("col.names" %in% names(param))) { |
73 | 10x |
if (summarise == "none") { |
74 | 10x |
param[["col.names"]] <- c("ID", "Cohort", "Dose", "Tox", "U", "T0", "TMax") |
75 | ! |
} else if (summarise == "dose") { |
76 | ! |
param[["col.names"]] <- c("Dose", "Evaluable", "With Toxicities") |
77 |
} else { |
|
78 | ! |
param[["col.names"]] <- c("Cohort", "Evaluable", "With Toxicities") |
79 |
} |
|
80 |
} |
|
81 | 10x |
param |
82 |
} |
|
83 | ||
84 |
#' @description `r lifecycle::badge("experimental")` |
|
85 |
#' @rdname knit_print_set_headers |
|
86 |
#' @noRd |
|
87 |
h_knit_print_set_headers.DataGrouped <- function(x, param, summarise, ...) { |
|
88 | 4x |
if (!("col.names" %in% names(param))) { |
89 | 4x |
if (summarise == "none") { |
90 | 4x |
param[["col.names"]] <- c("ID", "Cohort", "Dose", "Group", "Tox") |
91 | ! |
} else if (summarise == "dose") { |
92 | ! |
param[["col.names"]] <- c("Dose", "Group", "Evaluable", "With Toxicities") |
93 |
} else { |
|
94 | ! |
param[["col.names"]] <- c("Cohort", "Group", "Evaluable", "With Toxicities") |
95 |
} |
|
96 |
} |
|
97 | 4x |
param |
98 |
} |
|
99 | ||
100 |
#' @description `r lifecycle::badge("experimental")` |
|
101 |
#' @rdname knit_print_set_headers |
|
102 |
#' @noRd |
|
103 |
h_knit_print_set_headers.DataParts <- function(x, param, summarise, ...) { |
|
104 | 4x |
if (!("col.names" %in% names(param))) { |
105 | 4x |
if (summarise == "none") { |
106 | 4x |
param[["col.names"]] <- c("ID", "Part", "Cohort", "Dose", "Tox") |
107 | ! |
} else if (summarise == "dose") { |
108 | ! |
param[["col.names"]] <- c("Dose", "Evaluable", "With Toxicities") |
109 |
} else { |
|
110 | ! |
param[["col.names"]] <- c("Cohort", "Evaluable", "With Toxicities") |
111 |
} |
|
112 |
} |
|
113 | 4x |
param |
114 |
} |
|
115 | ||
116 |
#' @description `r lifecycle::badge("experimental")` |
|
117 |
#' @noRd |
|
118 |
h_knit_print_set_headers.DataOrdinal <- function(x, param, summarise, ...) { |
|
119 | 15x |
if (!("col.names" %in% names(param))) { |
120 | 15x |
if (summarise == "none") { |
121 | 15x |
param[["col.names"]] <- c("ID", "Cohort", "Dose", paste0("Cat", 0:(length(x@yCategories) - 1))) |
122 | ! |
} else if (summarise == "dose") { |
123 | ! |
param[["col.names"]] <- c("Dose", "Evaluable", names(x@yCategories)) |
124 |
} else { |
|
125 | ! |
param[["col.names"]] <- c("Cohort", "Evaluable", names(x@yCategories)) |
126 |
} |
|
127 |
} |
|
128 | 15x |
param |
129 |
} |
|
130 | ||
131 |
#' @description `r lifecycle::badge("experimental")` |
|
132 |
#' @rdname knit_print_set_headers |
|
133 |
#' @noRd |
|
134 |
h_knit_print_set_headers.DataDual <- function(x, param, summarise, ...) { |
|
135 | 22x |
if (!("col.names" %in% names(param))) { |
136 | 22x |
if (summarise == "none") { |
137 | 22x |
param[["col.names"]] <- c("ID", "Cohort", "Dose", "Tox", "W") |
138 | ! |
} else if (summarise == "dose") { |
139 | ! |
param[["col.names"]] <- c("Dose", "Evaluable", "With Toxicities") |
140 |
} else { |
|
141 | ! |
param[["col.names"]] <- c("Cohort", "Evaluable", "With Toxicities") |
142 |
} |
|
143 |
} |
|
144 | 22x |
param |
145 |
} |
|
146 | ||
147 |
#' Select Columns to Print in Custom `knit_print` Methods |
|
148 |
#' |
|
149 |
#' This is a helper method used `knit_print` for `crmPack` classes. |
|
150 |
#' |
|
151 |
#' @param x (`ANY`)\cr object that will be printed |
|
152 |
#' @param ... Not used at present. |
|
153 |
#' @return A tidied version of `x`, containing only the selected columns. |
|
154 |
#' @noRd |
|
155 |
h_knit_print_select_columns <- function(x, ...) { |
|
156 | 95x |
UseMethod("h_knit_print_select_columns") |
157 |
} |
|
158 | ||
159 |
#' @description `r lifecycle::badge("experimental")` |
|
160 |
#' @rdname knit_print_select_columns |
|
161 |
#' @noRd |
|
162 |
h_knit_print_select_columns.GeneralData <- function(x, ...) { |
|
163 | ! |
x %>% |
164 | ! |
tidy() %>% |
165 | ! |
dplyr::select("ID", "Cohort", "Dose", "Tox") |
166 |
} |
|
167 | ||
168 |
#' @description `r lifecycle::badge("experimental")` |
|
169 |
#' @rdname knit_print_select_columns |
|
170 |
#' @noRd |
|
171 |
h_knit_print_select_columns.Data <- function(x, ...) { |
|
172 | 40x |
x %>% |
173 | 40x |
tidy() %>% |
174 | 40x |
dplyr::select("ID", "Cohort", "Dose", "Tox") |
175 |
} |
|
176 | ||
177 |
#' @description `r lifecycle::badge("experimental")` |
|
178 |
#' @rdname knit_print_select_columns |
|
179 |
#' @noRd |
|
180 |
h_knit_print_select_columns.DataParts <- function(x, ...) { |
|
181 | 4x |
x %>% |
182 | 4x |
tidy() %>% |
183 | 4x |
dplyr::select("ID", "Part", "Cohort", "Dose", "Tox") |
184 |
} |
|
185 | ||
186 |
#' @description `r lifecycle::badge("experimental")` |
|
187 |
#' @rdname knit_print_select_columns |
|
188 |
#' @noRd |
|
189 |
h_knit_print_select_columns.DataOrdinal <- function(x, ...) { |
|
190 | 15x |
x %>% |
191 | 15x |
tidy() %>% |
192 | 15x |
dplyr::select("ID", "Cohort", "Dose", tidyselect::starts_with("Cat")) |
193 |
} |
|
194 | ||
195 |
#' @description `r lifecycle::badge("experimental")` |
|
196 |
#' @rdname knit_print_select_columns |
|
197 |
#' @noRd |
|
198 |
h_knit_print_select_columns.DataDA <- function(x, param, summarise, ...) { |
|
199 | 10x |
x %>% |
200 | 10x |
tidy() %>% |
201 | 10x |
dplyr::select("ID", "Cohort", "Dose", "Tox", "U", "T0", "TMax") |
202 |
} |
|
203 | ||
204 |
#' @description `r lifecycle::badge("experimental")` |
|
205 |
#' @rdname knit_print_select_columns |
|
206 |
#' @noRd |
|
207 |
h_knit_print_select_columns.DataGrouped <- function(x, param, summarise, ...) { |
|
208 | 4x |
x %>% |
209 | 4x |
tidy() %>% |
210 | 4x |
dplyr::select("ID", "Cohort", "Dose", "Group", "Tox") |
211 |
} |
|
212 | ||
213 |
#' @description `r lifecycle::badge("experimental")` |
|
214 |
#' @rdname knit_print_select_columns |
|
215 |
#' @noRd |
|
216 |
h_knit_print_select_columns.DataDual <- function(x, param, summarise, ...) { |
|
217 | 22x |
x %>% |
218 | 22x |
tidy() %>% |
219 | 22x |
dplyr::select("ID", "Cohort", "Dose", "Tox", "W") |
220 |
} |
|
221 | ||
222 |
#' Summarise a `Data` Object by Dose or Cohort for Display in Custom `knit_print` Methods |
|
223 |
#' |
|
224 |
#' This is a helper method used `knit_print` for `crmPack` classes. |
|
225 |
#' |
|
226 |
#' @param x (`ANY`)\cr object that will be printed |
|
227 |
#' @param full_grid (`flag`)\cr Should the full grid be included or only those |
|
228 |
#' doses with at least one evaluable participant? |
|
229 |
#' @param ... Not used at present. |
|
230 |
#' @return A tibble containing the summarised data |
|
231 |
#' @noRd |
|
232 |
h_knit_print_summarise <- function(x, summarise, full_grid, ...) { |
|
233 | 2x |
UseMethod("h_knit_print_summarise") |
234 |
} |
|
235 | ||
236 |
#' @description `r lifecycle::badge("experimental")` |
|
237 |
#' @rdname knit_print_summarise |
|
238 |
#' @noRd |
|
239 |
h_knit_print_summarise.GeneralData <- function(x, summarise, full_grid, ...) { |
|
240 | 2x |
xTidy <- x %>% tidy() |
241 | 2x |
xTidy <- xTidy %>% |
242 | 2x |
dplyr::group_by(.data[[stringr::str_to_title(summarise)]]) %>% |
243 | 2x |
dplyr::summarise( |
244 | 2x |
N = dplyr::n(), |
245 | 2x |
ToxCount = sum(Tox) |
246 |
) |
|
247 | 2x |
if (full_grid && summarise == "dose") { |
248 | ! |
xTidy <- xTidy %>% |
249 | ! |
tidyr::complete( |
250 | ! |
Dose = x@doseGrid, |
251 | ! |
fill = list(ToxCount = 0, N = 0) |
252 |
) |
|
253 |
} |
|
254 | 2x |
xTidy |
255 |
} |
|
256 | ||
257 |
#' @description `r lifecycle::badge("experimental")` |
|
258 |
#' @rdname knit_print_summarise |
|
259 |
#' @noRd |
|
260 |
h_knit_print_summarise.DataOrdinal <- function(x, summarise, full_grid, ...) { |
|
261 | ! |
xTidy <- x %>% tidy() |
262 | ! |
xTidy <- xTidy %>% |
263 | ! |
dplyr::group_by(.data$Dose) %>% |
264 | ! |
dplyr::summarise( |
265 | ! |
N = dplyr::n(), |
266 | ! |
dplyr::across(tidyselect::starts_with("Cat"), sum) |
267 |
) |
|
268 | ! |
if (full_grid && summarise == "dose") { |
269 | ! |
replace_list <- as.list( |
270 | ! |
c( |
271 | ! |
"N", |
272 | ! |
names(xTidy)[which(stringr::str_detect(names(xTidy), "Cat\\d+"))] |
273 |
) |
|
274 |
) |
|
275 |
# Create a list whose names are the columns in which we need to replace NAs |
|
276 |
# and whose values are 0 |
|
277 | ! |
names(replace_list) <- sapply(replace_list, \(x) x) |
278 | ! |
replace_list <- lapply(replace_list, \(x) 0) |
279 |
# Expand the tibble and do the replacement |
|
280 | ! |
xTidy <- tidyr::expand_grid(Dose = x@doseGrid) %>% |
281 | ! |
dplyr::left_join(xTidy, by = "Dose") %>% |
282 | ! |
tidyr::replace_na(replace_list) |
283 |
} |
|
284 | ! |
xTidy |
285 |
} |
|
286 | ||
287 |
#' @description `r lifecycle::badge("experimental")` |
|
288 |
#' @rdname knit_print_summarise |
|
289 |
#' @noRd |
|
290 |
h_knit_print_summarise.DataGrouped <- function(x, summarise, full_grid, ...) { |
|
291 | ! |
xTidy <- x %>% tidy() |
292 | ! |
xTidy <- xTidy %>% |
293 | ! |
dplyr::group_by(.data[[stringr::str_to_title(summarise)]], .data$Group) %>% |
294 | ! |
dplyr::summarise( |
295 | ! |
N = dplyr::n(), |
296 | ! |
ToxCount = sum(Tox) |
297 |
) |
|
298 | ! |
if (full_grid && summarise == "dose") { |
299 | ! |
xTidy <- tidyr::expand_grid( |
300 | ! |
Dose = x@doseGrid, |
301 | ! |
Group = c("mono", "combo") |
302 |
) %>% |
|
303 | ! |
dplyr::left_join(xTidy, by = c("Dose", "Group")) %>% |
304 | ! |
tidyr::replace_na(list(N = 0, ToxCount = 0)) |
305 |
} |
|
306 | ! |
xTidy |
307 |
} |
|
308 | ||
309 |
#' Print a `GeneralData` Object in a Markdown or Quarto Chunk |
|
310 |
#' |
|
311 |
#' @param label (`character`)\cr How to describe the participants in the trial. |
|
312 |
#' See Usage Notes below. |
|
313 |
#' @param full_grid (`flag`)\cr Should the full dose grid appear in the output table |
|
314 |
#' or simply those doses for whom at least one evaluable participant is available? |
|
315 |
#' Ignored unless `summarise == "dose"`. |
|
316 |
#' @param summarise (`character`)\cr How to summarise the observed data. The default, |
|
317 |
#' `"none"`, lists observed data at the participant level. `"dose"` presents |
|
318 |
#' participant counts by dose and `"cohort"` by cohort. |
|
319 |
#' @param summarize (`character`)\cr Synonym for `summarise` |
|
320 |
#' @param format_func (`function`)\cr The function used to format the participant table. |
|
321 |
#' The default applies no formatting. Obvious alternatives include `kableExtra::kable_styling`. |
|
322 |
#' @param ... passed to [knitr::kable()] |
|
323 |
#' @section Usage Notes: |
|
324 |
#' `label` describes the trial's participants. |
|
325 |
#' |
|
326 |
#' It should be a character vector of length 1 or 2. If of length 2, the first |
|
327 |
#' element describes a single participant and the second describes all other |
|
328 |
#' situations. If of length 1, the character `s` is appended to the value |
|
329 |
#' when the number of participants is not 1. |
|
330 |
#' The default values of `col.names` and `caption` vary depending on the summary |
|
331 |
#' requested. The default values can be overridden by passing `col.names` and |
|
332 |
#' `caption` in the function call. |
|
333 |
#' |
|
334 |
#' @inheritParams h_get_formatted_dosegrid |
|
335 |
#' @export |
|
336 |
#' @method knit_print GeneralData |
|
337 |
#' @rdname knit_print |
|
338 |
knit_print.GeneralData <- function( |
|
339 |
x, ..., asis = TRUE, |
|
340 |
label = c("participant", "participants"), |
|
341 |
full_grid = FALSE, |
|
342 |
summarise = c("none", "dose", "cohort"), |
|
343 |
summarize = summarise, |
|
344 |
units = NA, |
|
345 |
format_func = function(x) x) { |
|
346 |
# Validate |
|
347 | 111x |
assert_flag(asis) |
348 | 97x |
assert_flag(full_grid) |
349 | 97x |
assert_function(format_func) |
350 | 97x |
summarise <- match.arg(summarise) |
351 | 97x |
summarize <- match.arg(summarize) |
352 | ||
353 | 97x |
if (is.na(summarise) || is.null(summarise)) { |
354 | ! |
summarise <- summarize |
355 |
} |
|
356 | 97x |
assert_choice(summarise, c("none", "dose", "cohort")) |
357 |
# Initialise |
|
358 | 97x |
label <- h_prepare_labels(label) |
359 | 97x |
param <- list(...) |
360 | ||
361 |
# Execute |
|
362 | 97x |
param <- h_knit_print_set_headers(x, param, summarise, ...) |
363 | 97x |
if (summarise == "none") { |
364 | 95x |
if (!("caption" %in% names(param))) { |
365 | 95x |
param[["caption"]] <- paste("Evaluable", label[2], "to-date") |
366 |
} |
|
367 | 95x |
xTidy <- h_knit_print_select_columns(x) |
368 |
} else { |
|
369 | 2x |
xTidy <- h_knit_print_summarise(x, summarise, full_grid) |
370 | 2x |
if (!("caption" %in% names(param))) { |
371 | 2x |
param[["caption"]] <- paste0("Summarised by ", summarise) |
372 |
} |
|
373 |
} |
|
374 | 97x |
param[["x"]] <- xTidy |
375 | 97x |
rv <- if (length(x@x) > 0) { |
376 | 32x |
paste((do.call(knitr::kable, param)) %>% format_func(), collapse = "\n") |
377 |
} else { |
|
378 | 65x |
paste("No", label[2], "are yet evaluable.\n\n") |
379 |
} |
|
380 | 97x |
rv <- paste0( |
381 | 97x |
rv, |
382 | 97x |
paste0( |
383 | 97x |
"\n\nThe dose grid is ", |
384 | 97x |
h_get_formatted_dosegrid( |
385 | 97x |
grid = x@doseGrid, |
386 | 97x |
units = units, |
387 |
... |
|
388 |
), |
|
389 |
"" |
|
390 |
), |
|
391 | 97x |
"\n\n", |
392 | 97x |
collpase = "\n" |
393 |
) |
|
394 | 97x |
if (asis) { |
395 | 18x |
rv <- knitr::asis_output(rv) |
396 |
} |
|
397 | 97x |
rv |
398 |
} |
|
399 | ||
400 |
#' @export |
|
401 |
#' @method knit_print DataParts |
|
402 |
#' @rdname knit_print |
|
403 |
knit_print.DataParts <- function( |
|
404 |
x, ..., asis = TRUE, |
|
405 |
label = c("participant", "participants"), |
|
406 |
full_grid = FALSE, |
|
407 |
summarise = c("none", "dose", "cohort"), |
|
408 |
summarize = summarise, |
|
409 |
units = NA, |
|
410 |
format_func = function(x) x) { |
|
411 | 6x |
rv <- NextMethod() |
412 | 4x |
rv <- paste0( |
413 | 4x |
rv, |
414 | 4x |
paste0( |
415 | 4x |
"\n\nThe part 1 ladder is ", |
416 | 4x |
h_get_formatted_dosegrid(x@part1Ladder, units) |
417 |
), |
|
418 | 4x |
paste0("\n\nThe next part is Part ", x@nextPart, ".\n\n") |
419 |
) |
|
420 | 4x |
if (asis) { |
421 | 2x |
rv <- knitr::asis_output(rv) |
422 |
} |
|
423 | 4x |
rv |
424 |
} |
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(object@startingDose, choices = object@data@doseGrid, empty.ok = FALSE), |
26 | 10x |
"startingDose must be included in data@doseGrid" |
27 |
) |
|
28 | 10x |
v$result() |
29 |
} |
|
30 | ||
31 |
#' Internal Helper Functions for Validation of [`RuleDesignOrdinal`] Objects |
|
32 |
#' |
|
33 |
#' @description `r lifecycle::badge("experimental")` |
|
34 |
#' |
|
35 |
#' These functions are only used internally to validate the format of an input |
|
36 |
#' [`RuleDesignOrdinal`] or inherited classes and therefore not exported. |
|
37 |
#' |
|
38 |
#' @name v_design |
|
39 |
#' @param object (`RuleDesignOrdinal`)\cr object to validate. |
|
40 |
#' @return A `character` vector with the validation failure messages, |
|
41 |
#' or `TRUE` in case validation passes. |
|
42 |
NULL |
|
43 | ||
44 |
#' @describeIn v_design validates that the [`RuleDesignOrdinal`] object |
|
45 |
#' contains valid `starting_dose`. |
|
46 |
v_rule_design_ordinal <- function(object) { |
|
47 | ! |
v <- Validate() |
48 | ! |
v$check( |
49 | ! |
test_number(object@starting_dose, finite = TRUE), |
50 | ! |
"starting_dose must be a number" |
51 |
) |
|
52 | ! |
v$check( |
53 | ! |
test_subset(object@starting_dose, choices = object@data@doseGrid, empty.ok = FALSE), |
54 | ! |
"starting_dose must be included in data@doseGrid" |
55 |
) |
|
56 | ! |
v$result() |
57 |
} |
|
58 | ||
59 | ||
60 |
#' @describeIn v_design validates that the [`DesignGrouped`] object |
|
61 |
#' contains valid flags. |
|
62 |
v_design_grouped <- function(object) { |
|
63 | 2x |
v <- Validate() |
64 | 2x |
v$check( |
65 | 2x |
test_flag(object@first_cohort_mono_only), |
66 | 2x |
"first_cohort_mono_only must be a flag" |
67 |
) |
|
68 | 2x |
v$check( |
69 | 2x |
test_flag(object@same_dose_for_all), |
70 | 2x |
"same_dose_for_all must be a flag" |
71 |
) |
|
72 | 2x |
v$check( |
73 | 2x |
test_flag(object@same_dose_for_all), |
74 | 2x |
"same_dose_for_start must be a flag" |
75 |
) |
|
76 | 2x |
v$result() |
77 |
} |
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 | 52235x |
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 | 51097x |
if (!h_covr_active()) { |
48 | 2x |
return(expr) |
49 |
} |
|
50 | ||
51 | 51095x |
if (is.function(expr)) { |
52 | 9x |
body(expr) <- h_covr_detrace(body(expr)) |
53 | 9x |
return(expr) |
54 |
} |
|
55 | ||
56 | 51086x |
detrace <- function(x) { |
57 |
# returns "missing" expression to avoid errors with calls |
|
58 | 51086x |
if (identical(x, bquote())) { |
59 | 223x |
return(x) |
60 |
} |
|
61 | ||
62 | 50863x |
x <- h_covr_detrace_call(x) |
63 | 25108x |
if (is.call(x)) x[-1] <- lapply(x[-1], h_covr_detrace) |
64 | 50863x |
x |
65 |
} |
|
66 | ||
67 | 51086x |
detrace(expr) |
68 |
} |
|
69 | ||
70 |
#' @describeIn h_covr_helpers |
|
71 |
#' Determine whether the current expression is a `covr`-modified expression |
|
72 |
h_is_covr_trace <- function(expr) { |
|
73 |
# Matches `if (TRUE) { covr:::count(<trace>); <expr> }` (see covr:::trace_calls). |
|
74 | 50868x |
is.call(expr) && |
75 | 50868x |
expr[[1]] == "if" && |
76 | 50868x |
expr[[2]] == quote(TRUE) && |
77 | 50868x |
expr[[3]][[1]] == "{" && |
78 | 50868x |
length(expr[[3]]) >= 3 && |
79 | 50868x |
is.call(expr[[3]][[2]]) && |
80 | 50868x |
expr[[3]][[2]][[1]] == call(":::", as.symbol("covr"), as.symbol("count")) |
81 |
} |
|
82 | ||
83 |
#' @describeIn h_covr_helpers |
|
84 |
#' Extract the original expression from a `covr`-modified expression |
|
85 |
h_covr_detrace_call <- function(expr) { |
|
86 | 3777x |
if (h_is_covr_trace(expr)) expr[[3]][[3]] else expr |
87 |
} |
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 |
# 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 | 921x |
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 |
#' @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 | 81218x |
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 | 41690x |
iterations_relative <- object@iterations - object@burnin |
42 | 41690x |
if (iterations_relative <= 0) { |
43 | ! |
return(0L) |
44 |
} |
|
45 | 41690x |
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 | 21734x |
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 | 21734x |
iteration_relative <- iteration - object@burnin |
86 | 21734x |
iteration_relative > 0 && ((iteration_relative %% object@step) == 0) |
87 |
} |
|
88 |
) |
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 | 729x |
logger <- futile.logger::flog.logger(name = "crmPack") |
48 | 729x |
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 | 1118x |
assert_string(msg) |
61 | 1118x |
assert_flag(capture) |
62 | ||
63 | 1118x |
futile.logger::flog.trace(msg = msg, ..., name = "crmPack", capture = capture) |
64 |
} |
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 | 6x |
assert_number(xaxisround, lower = 0) |
14 | 4x |
assert_character(description, len = 1, any.missing = FALSE) |
15 | 2x |
assert_numeric(x) |
16 | ||
17 | 1x |
tabx <- table(x) / length(x) |
18 | 1x |
dat <- data.frame(x = as.numeric(names(tabx)), perc = as.numeric(tabx) * 100) |
19 | 1x |
ggplot() + |
20 | 1x |
geom_bar(aes(x = x, y = perc), |
21 | 1x |
data = dat, |
22 | 1x |
stat = "identity", |
23 | 1x |
position = "identity", |
24 | 1x |
width = ifelse(nrow(dat) > 1, min(diff(dat$x)) / 2, 1) |
25 |
) + |
|
26 | 1x |
xlab(description) + |
27 | 1x |
ylab("Percent") + |
28 | 1x |
scale_x_continuous( |
29 | 1x |
breaks = |
30 | 1x |
round(dat$x, xaxisround) |
31 |
) |
|
32 |
} |
|
33 | ||
34 | ||
35 | ||
36 |
#' Helper function to calculate percentage of true stopping rules for |
|
37 |
#' report label output |
|
38 |
#' calculates true column means and converts output into percentages |
|
39 |
#' before combining the output with the report label; output is passed |
|
40 |
#' to [`show()`] and output with cat to console |
|
41 |
#' |
|
42 |
#' @param stop_report object from summary method |
|
43 |
#' @return named list with label and percentage of rule activation |
|
44 | ||
45 | ||
46 |
h_calc_report_label_percentage <- function(stop_report) { |
|
47 | 2x |
stop_pct <- colMeans(stop_report) * 100 |
48 | 2x |
stop_pct_to_print <- stop_pct[!is.na(names(stop_pct))] |
49 | 2x |
return(stop_pct_to_print) |
50 |
} |
|
51 | ||
52 | ||
53 | ||
54 |
#' Helper function to calculate average across iterations for each additional |
|
55 |
#' reporting parameter |
|
56 |
#' extracts parameter names as specified by user and averaged the values |
|
57 |
#' for each specified parameter to [`show()`] and output with cat to console |
|
58 |
#' |
|
59 |
#' @param stats_list object from simulation with nested parameter values |
|
60 |
#' (sublist for each parameter) |
|
61 |
#' @return list of parameter names and averaged values for console output |
|
62 | ||
63 | ||
64 |
h_summarize_add_stats <- function(stats_list) { |
|
65 |
# Extract the parameter names |
|
66 | 2x |
param_names <- names(stats_list[[1]]) |
67 | ||
68 |
# Calculate the average for each parameter |
|
69 | 2x |
averages <- lapply(param_names, function(param) { |
70 | 4x |
values <- sapply(stats_list, function(x) x[[param]]) |
71 | 4x |
mean(values) |
72 |
}) |
|
73 | ||
74 | 2x |
return(list(param_names, averages)) |
75 |
} |
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(burnin = 1e4L, |
|
77 |
step = 2L, |
|
78 |
samples = 1e4L, |
|
79 |
rng_kind = NA_character_, |
|
80 |
rng_seed = NA_integer_) { |
|
81 | 2350x |
assert_count(burnin, positive = TRUE) |
82 | 2350x |
assert_count(step, positive = TRUE) |
83 | 2350x |
assert_count(samples, positive = TRUE) |
84 | 2350x |
assert_string(rng_kind, na.ok = TRUE) |
85 | 2350x |
assert_count(rng_seed, na.ok = TRUE) |
86 | ||
87 | 2350x |
if (!is.na(rng_kind)) { |
88 | 344x |
rng_kind <- paste0("base::", rng_kind) |
89 |
} else { |
|
90 | 2006x |
rng_kind <- NA_character_ |
91 |
} |
|
92 | 2350x |
if (!is.na(rng_seed)) { |
93 | 343x |
rng_seed <- as.integer(rng_seed) |
94 |
} else { |
|
95 | 2007x |
rng_seed <- NA_integer_ |
96 |
} |
|
97 | ||
98 | 2350x |
.McmcOptions( |
99 | 2350x |
iterations = as.integer(burnin + (step * samples)), |
100 | 2350x |
burnin = as.integer(burnin), |
101 | 2350x |
step = as.integer(step), |
102 | 2350x |
rng_kind = rng_kind, |
103 | 2350x |
rng_seed = as.integer(rng_seed) |
104 |
) |
|
105 |
} |
|
106 | ||
107 |
## default constructor ---- |
|
108 | ||
109 |
#' @rdname McmcOptions-class |
|
110 |
#' @note Typically, end users will not use the `.DefaultMcmcOptions()` function. |
|
111 |
#' @export |
|
112 |
.DefaultMcmcOptions <- function() { |
|
113 | 13x |
McmcOptions( |
114 | 13x |
burnin = 250, |
115 | 13x |
samples = 1000 |
116 |
) |
|
117 |
} |
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(object@cov %*% object@prec, diag(1, length(object@mean)), check.attributes = FALSE) == TRUE, |
37 | 5x |
"prec must be inverse of cov" |
38 |
) |
|
39 |
} |
|
40 | 9x |
v$result() |
41 |
} |
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 | 757x |
assert_matrix( |
52 | 757x |
cov, |
53 | 757x |
mode = "numeric", |
54 | 757x |
any.missing = FALSE, |
55 | 757x |
nrows = length(mean), |
56 | 757x |
ncols = length(mean) |
57 |
) |
|
58 |
# To ensure that `cov` is invertible: |
|
59 | 757x |
assert_true(h_is_positive_definite(cov, length(mean))) |
60 | ||
61 | 757x |
.ModelParamsNormal( |
62 | 757x |
mean = mean, |
63 | 757x |
cov = cov, |
64 | 757x |
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 |
#' @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 | 2594x |
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 |
# nolint start |
|
2 | ||
3 |
##################################################################################### |
|
4 |
## Author: Daniel Sabanes Bove [sabanesd *a*t* roche *.* com] |
|
5 |
## Project: Object-oriented implementation of CRM designs |
|
6 |
## |
|
7 |
## Time-stamp: <[crmPack-package.R] by DSB Mon 11/05/2015 17:47> |
|
8 |
## |
|
9 |
## Description: |
|
10 |
## Package description. |
|
11 |
## |
|
12 |
## History: |
|
13 |
## 29/01/2014 file creation |
|
14 |
##################################################################################### |
|
15 | ||
16 |
#' Object-oriented implementation of CRM designs |
|
17 |
#' |
|
18 |
#' @name crmPack-package |
|
19 |
#' @aliases crmPack |
|
20 |
#' @docType package |
|
21 |
#' @title Object-oriented implementation of CRM designs |
|
22 |
#' @import checkmate |
|
23 |
#' @import ggplot2 |
|
24 |
#' @import methods |
|
25 |
#' @import tibble |
|
26 |
#' @importFrom grid grid.draw |
|
27 |
#' @importFrom gridExtra arrangeGrob |
|
28 |
#' @importFrom graphics plot hist legend lines matlines matplot |
|
29 |
#' @importFrom stats binomial coef cov2cor gaussian glm lm median model.matrix |
|
30 |
#' optim pgamma plogis pnorm qgamma qlogis qnorm quantile rbinom rgamma |
|
31 |
#' approxfun rnorm runif uniroot var vcov step mad pbeta dbeta dgamma |
|
32 |
#' setNames |
|
33 |
#' @importFrom utils data head tail capture.output |
|
34 |
#' @importFrom lifecycle badge |
|
35 |
#' @importFrom rjags jags.model jags.samples |
|
36 |
#' @importFrom futile.logger flog.threshold flog.logger flog.trace TRACE FATAL |
|
37 |
#' @importFrom knitr knit_print |
|
38 |
#' @importFrom kableExtra kbl add_header_above column_spec collapse_rows |
|
39 |
#' kable_styling add_footnote kable |
|
40 |
#' |
|
41 |
#' @keywords package |
|
42 |
#' @references Sabanes Bove D, Yeung WY, Palermo G, Jaki T (2019). |
|
43 |
#' "Model-Based Dose Escalation Designs in R with crmPack." |
|
44 |
#' Journal of Statistical Software, 89(10), 1-22. |
|
45 |
#' doi:10.18637/jss.v089.i10 (URL: http://doi.org/10.18637/jss.v089.i10). |
|
46 |
{} |
|
47 | ||
48 |
##' @keywords internal |
|
49 |
.onAttach <- function(libname, pkgname) { |
|
50 | 3x |
packageStartupMessage( |
51 | 3x |
"Type crmPackHelp() to open help browser\n", |
52 | 3x |
"Type crmPackExample() to open example\n" |
53 |
) |
|
54 |
} |
|
55 | ||
56 |
## need to declare global variable / function |
|
57 |
## names in order to avoid R CMD check notes: |
|
58 |
globalVariables(c( |
|
59 |
"log.betaZ", |
|
60 |
"precW", |
|
61 |
"pow", |
|
62 |
"nObs", |
|
63 |
"betaZ", |
|
64 |
"x", |
|
65 |
"betaW", |
|
66 |
"xLevel", |
|
67 |
"precW", |
|
68 |
"z", |
|
69 |
"nGrid", |
|
70 |
"doseGrid", |
|
71 |
"betaWintercept", |
|
72 |
"delta", |
|
73 |
"deltaStart", |
|
74 |
"delta2", |
|
75 |
"Effsamples", |
|
76 |
"logit<-", |
|
77 |
"rho0", |
|
78 |
"alpha0", |
|
79 |
"delta0", |
|
80 |
"alpha1", |
|
81 |
"delta1", |
|
82 |
"inverse", |
|
83 |
"priorCov", |
|
84 |
"theta", |
|
85 |
"comp0", |
|
86 |
"w", |
|
87 |
"DLTs", |
|
88 |
"y", |
|
89 |
"group", |
|
90 |
"annotate", |
|
91 |
"probSamples", |
|
92 |
"prec", |
|
93 |
"nu", |
|
94 |
"samples", |
|
95 |
"Type", |
|
96 |
"patient", |
|
97 |
"toxicity", |
|
98 |
"ID", |
|
99 |
"biomarker", |
|
100 |
"traj", |
|
101 |
"Statistic", |
|
102 |
"perc", |
|
103 |
"..density..", |
|
104 |
"middle", |
|
105 |
"lower", |
|
106 |
"upper", |
|
107 |
"middleBiomarker", |
|
108 |
"lowerBiomarker", |
|
109 |
"upperBiomarker", |
|
110 |
"nObsshare", |
|
111 |
"xshare", |
|
112 |
"yshare", |
|
113 |
"thisProb.PL", |
|
114 |
"thisMeanEff.PL", |
|
115 |
"thisSize.PL", |
|
116 |
"probit<-", |
|
117 |
"refDose", |
|
118 |
"Tmax", |
|
119 |
"u", |
|
120 |
"eps", |
|
121 |
"h", |
|
122 |
"lambda", |
|
123 |
"cadj", |
|
124 |
"A", |
|
125 |
"lambda_p", |
|
126 |
"cond", |
|
127 |
"t0", |
|
128 |
"tend", |
|
129 |
"t0_case", |
|
130 |
"tend_case", |
|
131 |
"yhat", |
|
132 |
"ref_dose", |
|
133 |
"comp", |
|
134 |
"X", |
|
135 |
"skel_probs", |
|
136 |
"is_combo", |
|
137 |
"results", |
|
138 |
"k", |
|
139 |
"value", |
|
140 |
"Parameter", |
|
141 |
"intervals", |
|
142 |
"Group", |
|
143 |
"Tox", |
|
144 |
"MaxOverdoseProb", |
|
145 |
"DoseGrid", |
|
146 |
"NGrid", |
|
147 |
"NObs", |
|
148 |
"XLevel" |
|
149 |
)) |
|
150 | ||
151 |
# nolint end |