This is a core class in this package. It encapsulates that an object (e.g. a CRM model, a 3+3 model) is able to recommend doses, keep track of how many patients have been treated at what doses, what toxicity outcomes have been seen, and whether a trial should continue. It offers a consistent interface to many dose-finding methods, including CRM, TPI, mTPI, BOIN, EffTox, 3+3, and more.
Once you have a standardised interface, modularisation offers a powerful way
to adorn dose-finding methods with extra desirable behaviour. selector
objects can be daisy-chained togther using magrittr
's pipe operator.
For instance, the CRM fitting method in dfcrm
is fantastic because it
runs quickly and is simple to call. However, it does not recommend that a
trial stops if a dose is too toxic or if n patients have already been treated
at the recommended dose. Each of these behaviours can be bolted on via
additional selectors. Furthermore, those behaviours and more can be bolted
on to any dose selector because of the modular approach implemented in
escalation
. See Examples.
selector
objects are obtained by calling the fit
function on a selector_factory
object.
A selector_factory
object is obtained by initially calling a
function like get_dfcrm
, get_three_plus_three
or
get_boin
. Users may then add desired extra behaviour with
subsequent calls to functions like stop_when_n_at_dose
or
stop_when_too_toxic
.
The selector
class also supports that an object will be able to
perform inferential calculations on the rates of toxicity via functions like
mean_prob_tox
, median_prob_tox
, and
prob_tox_exceeds
. However, naturally the sophistication of
those calculations will vary by model implementation. For example, a full
MCMC method will be able to quantify any probability you like by working with
posterior samples. In contrast, a method like the crm
function in dfcrm
that uses the plug-in method to estimate posterior
dose-toxicity curves cannot natively estimate the median probability of tox.
selector()
Every selector
object implements the following functions:
Some selectors also add:
# Start with a simple CRM model
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6)
target <- 0.25
model1 <- get_dfcrm(skeleton = skeleton, target = target)
# Add a rule to stop when 9 patients are treated at the recommended dose
model2 <- get_dfcrm(skeleton = skeleton, target = target) %>%
stop_when_n_at_dose(n = 9, dose = 'recommended')
# Add a rule to stop if toxicity rate at lowest dose likely exceeds target
model3 <- get_dfcrm(skeleton = skeleton, target = target) %>%
stop_when_n_at_dose(n = 9, dose = 'recommended') %>%
stop_when_too_toxic(dose = 1, tox_threshold = target, confidence = 0.5)
# We now have three CRM models that differ in their stopping behaviour.
# Let's fit each to some outcomes to see those differences:
outcomes <- '1NNN 2NTT 1NNT'
fit1 <- model1 %>% fit(outcomes)
fit2 <- model2 %>% fit(outcomes)
fit3 <- model3 %>% fit(outcomes)
fit1 %>% recommended_dose()
#> [1] 1
fit1 %>% continue()
#> [1] TRUE
fit2 %>% recommended_dose()
#> [1] 1
fit2 %>% continue()
#> [1] TRUE
fit3 %>% recommended_dose()
#> [1] NA
fit3 %>% continue()
#> [1] FALSE
# Already model3 wants to stop because of excessive toxicity.
# Let's carry on with models 1 and 2 by adding another cohort:
outcomes <- '1NNN 2NTT 1NNT 1NNN'
fit1 <- model1 %>% fit(outcomes)
fit2 <- model2 %>% fit(outcomes)
fit1 %>% recommended_dose()
#> [1] 1
fit1 %>% continue()
#> [1] TRUE
fit2 %>% recommended_dose()
#> [1] 1
fit2 %>% continue()
#> [1] FALSE
# Model1 wants to continue - in fact it will never stop.
# In contrast, model2 has seen 9 at dose 1 so, rather than suggest dose 1
# again, it suggests the trial should stop.
# For contrast, let us consider a BOIN model on the same outcomes
boin_fitter <- get_boin(num_doses = length(skeleton), target = target)
fit4 <- boin_fitter %>% fit(outcomes)
fit4 %>% recommended_dose()
#> [1] 2
fit4 %>% continue()
#> [1] TRUE
# Full selector interface:
fit <- fit2
fit %>% tox_target()
#> [1] 0.25
fit %>% num_patients()
#> [1] 12
fit %>% cohort()
#> [1] 1 1 1 2 2 2 3 3 3 4 4 4
fit %>% doses_given()
#> [1] 1 1 1 2 2 2 1 1 1 1 1 1
fit %>% tox()
#> [1] 0 0 0 0 1 1 0 0 1 0 0 0
fit %>% weight()
#> [1] 1 1 1 1 1 1 1 1 1 1 1 1
fit %>% num_tox()
#> [1] 3
fit %>% model_frame()
#> # A tibble: 12 × 5
#> patient cohort dose tox weight
#> <int> <int> <int> <int> <dbl>
#> 1 1 1 1 0 1
#> 2 2 1 1 0 1
#> 3 3 1 1 0 1
#> 4 4 2 2 0 1
#> 5 5 2 2 1 1
#> 6 6 2 2 1 1
#> 7 7 3 1 0 1
#> 8 8 3 1 0 1
#> 9 9 3 1 1 1
#> 10 10 4 1 0 1
#> 11 11 4 1 0 1
#> 12 12 4 1 0 1
fit %>% num_doses()
#> [1] 5
fit %>% dose_indices()
#> [1] 1 2 3 4 5
fit %>% dose_strings()
#> [1] "1" "2" "3" "4" "5"
fit %>% recommended_dose()
#> [1] 1
fit %>% continue()
#> [1] FALSE
fit %>% n_at_dose()
#> [1] 9 3 0 0 0
fit %>% n_at_recommended_dose()
#> [1] 9
fit %>% is_randomising()
#> [1] FALSE
fit %>% prob_administer()
#> 1 2 3 4 5
#> 0.75 0.25 0.00 0.00 0.00
fit %>% tox_at_dose()
#> [1] 1 2 0 0 0
fit %>% empiric_tox_rate()
#> [1] 0.1111111 0.6666667 NaN NaN NaN
fit %>% mean_prob_tox()
#> [1] 0.2069561 0.2979677 0.4824122 0.6176620 0.7644430
fit %>% median_prob_tox()
#> [1] 0.2069561 0.2979677 0.4824122 0.6176620 0.7644430
fit %>% dose_admissible()
#> [1] TRUE TRUE TRUE TRUE TRUE
fit %>% prob_tox_quantile(0.9)
#> [1] 0.3649670 0.4608277 0.6272346 0.7346974 0.8420860
fit %>% prob_tox_exceeds(0.5)
#> [1] 0.009230096 0.054695783 0.442521884 0.851725376 0.996745367
fit %>% supports_sampling()
#> [1] TRUE
fit %>% prob_tox_samples()
#> # A tibble: 4,000 × 6
#> .draw `1` `2` `3` `4` `5`
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 1 0.224 0.317 0.501 0.633 0.775
#> 2 2 0.256 0.351 0.532 0.659 0.793
#> 3 3 0.181 0.269 0.454 0.593 0.747
#> 4 4 0.292 0.389 0.566 0.687 0.811
#> 5 5 0.220 0.312 0.496 0.629 0.772
#> 6 6 0.212 0.304 0.488 0.622 0.768
#> 7 7 0.0916 0.159 0.331 0.481 0.665
#> 8 8 0.329 0.426 0.598 0.712 0.827
#> 9 9 0.224 0.316 0.500 0.632 0.775
#> 10 10 0.303 0.399 0.575 0.694 0.816
#> # ℹ 3,990 more rows