Title: | A Modular Approach to Dose-Finding Clinical Trials |
---|---|
Description: | Methods for working with dose-finding clinical trials. We provide implementations of many dose-finding clinical trial designs, including the continual reassessment method (CRM) by O'Quigley et al. (1990) <doi:10.2307/2531628>, the toxicity probability interval (TPI) design by Ji et al. (2007) <doi:10.1177/1740774507079442>, the modified TPI (mTPI) design by Ji et al. (2010) <doi:10.1177/1740774510382799>, the Bayesian optimal interval design (BOIN) by Liu & Yuan (2015) <doi:10.1111/rssc.12089>, EffTox by Thall & Cook (2004) <doi:10.1111/j.0006-341X.2004.00218.x>; the design of Wages & Tait (2015) <doi:10.1080/10543406.2014.920873>, and the 3+3 described by Korn et al. (1994) <doi:10.1002/sim.4780131802>. All designs are implemented with a common interface. We also offer optional additional classes to tailor the behaviour of all designs, including avoiding skipping doses, stopping after n patients have been treated at the recommended dose, stopping when a toxicity condition is met, or demanding that n patients are treated before stopping is allowed. By daisy-chaining together these classes using the pipe operator from 'magrittr', it is simple to tailor the behaviour of a dose-finding design so it behaves how the trialist wants. Having provided a flexible interface for specifying designs, we then provide functions to run simulations and calculate dose-paths for future cohorts of patients. |
Authors: | Kristian Brock [aut, cre] , Daniel Slade [aut] , Michael Sweeting [aut] |
Maintainer: | Kristian Brock <[email protected]> |
License: | GPL (>=3) |
Version: | 0.1.10 |
Built: | 2024-10-25 05:20:45 UTC |
Source: | https://github.com/brockk/escalation |
escalation provides methods for working with dose-finding clinical trials. We provide implementations of many dose-finding clinical trial designs, ncluding the continual reassessment method (CRM) by O'Quigley et al. (1990) <doi:10.2307/2531628>, the toxicity probability interval (TPI) design by Ji et al. (2007) <doi:10.1177/1740774507079442>, the modified TPI (mTPI) design by Ji et al. (2010) <doi:10.1177/1740774510382799>, the Bayesian optimal interval design (BOIN) by Liu & Yuan (2015) <doi:10.1111/rssc.12089>, EffTox by Thall & Cook (2004) <doi:10.1111/j.0006-341X.2004.00218.x>; the design of Wages & Tait (2015) <doi:10.1080/10543406.2014.920873>, and the 3+3 described by Korn et al. (1994) <doi:10.1002/sim.4780131802>. All designs are implemented with a common interface. We also offer optional additional classes to tailor the behaviour of all designs, including avoiding skipping doses, stopping after n patients have been treated at the recommended dose, stopping when a toxicity condition is met, or demanding that n patients are treated before stopping is allowed. By daisy-chaining together these classes using the pipe operator from 'magrittr', it is simple to tailor the behaviour of a dose-finding design so it behaves how the trialist wants. Having provided a flexible interface for specifying designs, we then provide functions to run simulations and calculate dose-paths for future cohorts of patients.
dose_paths
object to tibble
.Cast dose_paths
object to tibble
.
## S3 method for class 'dose_paths' as_tibble(x, ...)
## S3 method for class 'dose_paths' as_tibble(x, ...)
x |
Object of class |
... |
Extra args passed onwards. |
Object of class tibble
dose_selector
object to tibble
.Cast dose_selector
object to tibble
.
## S3 method for class 'selector' as_tibble(x, ...)
## S3 method for class 'selector' as_tibble(x, ...)
x |
Object of class |
... |
Extra args passed onwards. |
Object of class tibble
Cumulative statistics are shown to gauge how the simulations converge.
## S3 method for class 'simulations_collection' as_tibble(x, target_dose = NULL, alpha = 0.05, ...)
## S3 method for class 'simulations_collection' as_tibble(x, target_dose = NULL, alpha = 0.05, ...)
x |
object of type |
target_dose |
numerical dose index, or NULL (default) for all doses |
alpha |
significance level for symmetrical confidence intervals |
... |
extra args are ignored |
a tibble with cols:
dose
, the dose-level
n
, cumulative inference using the first n simulated iterations
design.x
, The first design in the comparison, aka design X
hit.x
, logical showing if design X recommended dose in iterate n
design.y
, The second design in the comparison, aka design Y
hit.x
, logical showing if design Y recommended dose in iterate n
X
, cumulative sum of hit.x within dose, i.e. count of recommendations
X2
, cumulative sum of hit.x^2 within dose
Y
, cumulative sum of hit.y within dose, i.e. count of recommendations
Y2
, cumulative sum of hit.y^2 within dose
XY
, cumulative sum of hit.x * hit.y within dose
psi1
, X / n
psi2
, Y / n
v_psi1
, variance of psi1
v_psi2
, variance of psi2
cov_psi12
, covariance of psi1 and psi2
delta
, psi1 - psi2
v_delta
, variance of delta
se_delta
, standard error of delta
delta_l
, delta - q * se_delta, where q is alpha / 2 normal quantile
delta_u
, delta + q * se_delta, where q is alpha / 2 normal quantile
comparison
, Label of design.x vs design.y, using design names
Crystallise a set of dose_paths
with probabilities to calculate
how likely each path is. Once probabilised in this way, the probabilities of
the terminal nodes in this set of paths will sum to 1. This allows users to
calculate operating characteristics.
calculate_probabilities(dose_paths, true_prob_tox, true_prob_eff = NULL, ...)
calculate_probabilities(dose_paths, true_prob_tox, true_prob_eff = NULL, ...)
dose_paths |
Object of type |
true_prob_tox |
Numeric vector, true probability of toxicity. |
true_prob_eff |
vector of true efficacy probabilities, optionally NULL if efficacy not analysed. |
... |
Extra parameters |
# Phase 1 example. # Calculate dose paths for the first three cohorts in a 3+3 trial of 5 doses: paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3, 3)) # Set the true probabilities of toxicity true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) # And calculate exact operating performance x <- paths %>% calculate_probabilities(true_prob_tox) prob_recommend(x) # Phase 1/2 example. prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) selector_factory <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) paths <- selector_factory %>% get_dose_paths(cohort_sizes = c(2, 2)) true_prob_eff <- c(0.27, 0.35, 0.41, 0.44, 0.45) x <- paths %>% calculate_probabilities(true_prob_tox = true_prob_tox, true_prob_eff = true_prob_eff) prob_recommend(x)
# Phase 1 example. # Calculate dose paths for the first three cohorts in a 3+3 trial of 5 doses: paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3, 3)) # Set the true probabilities of toxicity true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) # And calculate exact operating performance x <- paths %>% calculate_probabilities(true_prob_tox) prob_recommend(x) # Phase 1/2 example. prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) selector_factory <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) paths <- selector_factory %>% get_dose_paths(cohort_sizes = c(2, 2)) true_prob_eff <- c(0.27, 0.35, 0.41, 0.44, 0.45) x <- paths %>% calculate_probabilities(true_prob_tox = true_prob_tox, true_prob_eff = true_prob_eff) prob_recommend(x)
Check the consistency of a dose_selector instance
check_dose_selector_consistency(x)
check_dose_selector_consistency(x)
x |
dose_selector |
boin_fitter <- get_boin(num_doses = 5, target = 0.3) x <- fit(boin_fitter, "1NNN") check_dose_selector_consistency(x)
boin_fitter <- get_boin(num_doses = 5, target = 0.3) x <- fit(boin_fitter, "1NNN") check_dose_selector_consistency(x)
Get a vector of integers that reflect the cohorts to which the evaluated patients belong.
cohort(x, ...)
cohort(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
an integer vector
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% cohort()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% cohort()
Sample times between patient arrivals using the exponential distribution.
cohorts_of_n(n = 3, mean_time_delta = 1)
cohorts_of_n(n = 3, mean_time_delta = 1)
n |
integer, sample arrival times for this many patients. |
mean_time_delta |
the average gap between patient arrival times. I.e. the reciprocal of the rate parameter in an Exponential distribution. |
data.frame
with column time_delta containing durations of time
between patient arrivals.
cohorts_of_n() cohorts_of_n(n = 10, mean_time_delta = 5)
cohorts_of_n() cohorts_of_n(n = 10, mean_time_delta = 5)
Should this dose-finding experiment continue? Or have circumstances prevailed
that dictate this trial should stop? This method is critical to the automatic
calculation of statistical operating characteristics and dose-pathways. You
add stopping behaviours to designs using calls like stop_at_n
and stop_when_too_toxic
.
continue(x, ...)
continue(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
logical
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model1 <- get_dfcrm(skeleton = skeleton, target = target) fit1 <- model1 %>% fit('1NNN 2NTN') fit1 %>% continue() model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 6) fit2 <- model2 %>% fit('1NNN 2NTN') fit2 %>% continue()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model1 <- get_dfcrm(skeleton = skeleton, target = target) fit1 <- model1 %>% fit('1NNN 2NTN') fit1 %>% continue() model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 6) fit2 <- model2 %>% fit('1NNN 2NTN') fit2 %>% continue()
Plot the convergence processes from a collection of simulations.
convergence_plot(x, ...)
convergence_plot(x, ...)
x |
object of type |
... |
extra args are passed onwards to stack_sims_vert |
a ggplot2 plot
## Not run: # See ? simulate_compare ## End(Not run)
## Not run: # See ? simulate_compare ## End(Not run)
dose_paths
reflect all possible paths a dose-finding trial may
take. When the probability of those paths is calculated using an assumed set
of true dose-event probabilities, in this package those paths are said to be
crysallised. Once crystallised, operating charactersitics can be calculated.
crystallised_dose_paths( dose_paths, true_prob_tox, true_prob_eff = NULL, terminal_nodes )
crystallised_dose_paths( dose_paths, true_prob_tox, true_prob_eff = NULL, terminal_nodes )
dose_paths |
Object of type |
true_prob_tox |
vector of toxicity probabilities at doses 1..n |
true_prob_eff |
vector of efficacy probabilities at doses 1..n, optionally NULL if efficacy not evaluated. |
terminal_nodes |
tibble of terminal nodes on the dose-paths |
An object of type crystallised_dose_paths
# Calculate dose paths for the first three cohorts in a 3+3 trial of 5 doses: paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3, 3)) # Set the true probabilities of toxicity true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) # Crytallise the paths with the probabilities of toxicity x <- paths %>% calculate_probabilities(true_prob_tox) # And then examine, for example, the probabilities of recommending each dose # at the terminal nodes of these paths: prob_recommend(x)
# Calculate dose paths for the first three cohorts in a 3+3 trial of 5 doses: paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3, 3)) # Set the true probabilities of toxicity true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) # Crytallise the paths with the probabilities of toxicity x <- paths %>% calculate_probabilities(true_prob_tox) # And then examine, for example, the probabilities of recommending each dose # at the terminal nodes of these paths: prob_recommend(x)
This method continues a dose-finding trial until there are n patients at a dose. Once that condition is met, it delegates stopping responsibility to its parent dose selector, whatever that might be. This class is greedy in that it meets its own needs before asking any other selectors in a chain what they want. Thus, different behaviours may be achieved by nesting dose selectors in different orders. See examples.
demand_n_at_dose(parent_selector_factory, n, dose)
demand_n_at_dose(parent_selector_factory, n, dose)
parent_selector_factory |
Object of type |
n |
Continue at least until there are n at a dose. |
dose |
|
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # This model will demand 9 at any dose before it countenances stopping. model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% demand_n_at_dose(n = 9, dose = 'any') # This model will recommend continuing: model1 %>% fit('1NNT 1NNN 2TNN 2NNN') %>% continue() # It tells you to continue because there is no selector considering when # you should stop - dfcrm implements no stopping rule by default. # In contrast, we can add a stopping selector to discern the behaviour of # demand_n_at_dose. We will demand 9 are seen at the recommended dose before # stopping is permitted in model3: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% demand_n_at_dose(n = 9, dose = 'recommended') # This model advocates stopping because 12 patients are seen in total: model2 %>% fit('1NNN 1NNN 2TNN 2NNN') %>% continue() # But this model advocates continuing because 9 patients have not been seen # at any dose yet: model3 %>% fit('1NNN 1NNN 2TNN 2NNN') %>% continue() # This shows how demand_n_at_dose overrides stopping behaviours that come # before it in the daisychain. # Once 9 are seen at the recommended dose, the decision to stop is made: fit <- model3 %>% fit('1NNN 1NNN 2TNN 2NNN 2TTN') fit %>% continue() fit %>% recommended_dose()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # This model will demand 9 at any dose before it countenances stopping. model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% demand_n_at_dose(n = 9, dose = 'any') # This model will recommend continuing: model1 %>% fit('1NNT 1NNN 2TNN 2NNN') %>% continue() # It tells you to continue because there is no selector considering when # you should stop - dfcrm implements no stopping rule by default. # In contrast, we can add a stopping selector to discern the behaviour of # demand_n_at_dose. We will demand 9 are seen at the recommended dose before # stopping is permitted in model3: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% demand_n_at_dose(n = 9, dose = 'recommended') # This model advocates stopping because 12 patients are seen in total: model2 %>% fit('1NNN 1NNN 2TNN 2NNN') %>% continue() # But this model advocates continuing because 9 patients have not been seen # at any dose yet: model3 %>% fit('1NNN 1NNN 2TNN 2NNN') %>% continue() # This shows how demand_n_at_dose overrides stopping behaviours that come # before it in the daisychain. # Once 9 are seen at the recommended dose, the decision to stop is made: fit <- model3 %>% fit('1NNN 1NNN 2TNN 2NNN 2TTN') fit %>% continue() fit %>% recommended_dose()
This method optionally prevents dose selectors from skipping doses when escalating and / or deescalating. The default is that skipping when escalating is prevented but skipping when deescalating is permitted, but both of these behaviours can be altered.
dont_skip_doses( parent_selector_factory, when_escalating = TRUE, when_deescalating = FALSE )
dont_skip_doses( parent_selector_factory, when_escalating = TRUE, when_deescalating = FALSE )
parent_selector_factory |
Object of type |
when_escalating |
TRUE to prevent skipping when attempting to escalate. |
when_deescalating |
TRUE to prevent skipping when attempting to deescalate. |
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% dont_skip_doses() fit1 <- model1 %>% fit('1NNN') model2 <- get_dfcrm(skeleton = skeleton, target = target) fit2 <- model2 %>% fit('1NNN') # fit1 will not skip doses fit1 %>% recommended_dose() # But fit2 will: fit2 %>% recommended_dose() # Similar demonstration for de-escalation model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% dont_skip_doses(when_deescalating = TRUE) fit1 <- model1 %>% fit('1NNN 2N 3TTT') model2 <- get_dfcrm(skeleton = skeleton, target = target) fit2 <- model2 %>% fit('1NNN 2N 3TTT') # fit1 will not skip doses fit1 %>% recommended_dose() # But fit2 will: fit2 %>% recommended_dose()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% dont_skip_doses() fit1 <- model1 %>% fit('1NNN') model2 <- get_dfcrm(skeleton = skeleton, target = target) fit2 <- model2 %>% fit('1NNN') # fit1 will not skip doses fit1 %>% recommended_dose() # But fit2 will: fit2 %>% recommended_dose() # Similar demonstration for de-escalation model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% dont_skip_doses(when_deescalating = TRUE) fit1 <- model1 %>% fit('1NNN 2N 3TTT') model2 <- get_dfcrm(skeleton = skeleton, target = target) fit2 <- model2 %>% fit('1NNN 2N 3TTT') # fit1 will not skip doses fit1 %>% recommended_dose() # But fit2 will: fit2 %>% recommended_dose()
Get a vector of logical values reflecting whether each dose is admissible. Admissibility is defined in different ways for different models, and may not be defined at all in some models. For instance, in the TPI method, doses are inadmissible when the posterior probability is high that the toxicity rate exceeds the target value. In contrast, admissibility is not defined in the general CRM model (but it can be added with auxiliary classes). In this latter case, doses are implicitly considered to be admissible, by default.
dose_admissible(x, ...)
dose_admissible(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a logical vector
outcomes <- '1NNN 2TTT' # TPI example. This method defines admissibility. fit1 <- get_tpi(num_doses = 5, target = 0.3, k1 = 1, k2 = 1.5, exclusion_certainty = 0.95) %>% fit(outcomes) fit1 %>% dose_admissible() # Ordinary CRM example with no admissibility function. skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 fit2 <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit2 %>% dose_admissible() # Same CRM example with added admissibility function fit3 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_too_toxic(dose = 1, tox_threshold = target, confidence = 0.8) %>% fit(outcomes) fit3 %>% dose_admissible()
outcomes <- '1NNN 2TTT' # TPI example. This method defines admissibility. fit1 <- get_tpi(num_doses = 5, target = 0.3, k1 = 1, k2 = 1.5, exclusion_certainty = 0.95) %>% fit(outcomes) fit1 %>% dose_admissible() # Ordinary CRM example with no admissibility function. skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 fit2 <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit2 %>% dose_admissible() # Same CRM example with added admissibility function fit3 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_too_toxic(dose = 1, tox_threshold = target, confidence = 0.8) %>% fit(outcomes) fit3 %>% dose_admissible()
Get the integers from 1 to the number of doses under investigation.
dose_indices(x, ...)
dose_indices(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
an integer vector
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% dose_indices()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% dose_indices()
A dose-escalation design exists to select doses in response to observed
outcomes. The entire space of possible responses can be calculated to show
the behaviour of a design in response to all feasible outcomes. The
get_dose_paths
function performs that task and returns an
instance of this object.
dose_paths()
dose_paths()
# Calculate dose-paths for the 3+3 design: paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3))
# Calculate dose-paths for the 3+3 design: paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3))
This function does not need to be called by users. It is used internally.
dose_paths_function(selector_factory)
dose_paths_function(selector_factory)
selector_factory |
Object of type |
A function.
Get a vector of the dose-levels that have been administered to patients.
doses_given(x, ...)
doses_given(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
an integer vector
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% doses_given()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% doses_given()
Get a vector of the binary efficacy outcomes for evaluated patients.
eff(x, ...)
eff(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
an integer vector
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB') eff(x)
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB') eff(x)
Get the number of toxicities seen at each dose under investigation.
eff_at_dose(x, ...)
eff_at_dose(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
an integer vector
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB') eff_at_dose(x)
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB') eff_at_dose(x)
Get the minimum permissible efficacy rate, if supported. NULL if not.
eff_limit(x, ...)
eff_limit(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
numeric
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') eff_limit(x)
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') eff_limit(x)
Get the empirical or observed efficacy rate seen at each dose under investigation. This is simply the number of efficacies divded by the number of patients evaluated.
empiric_eff_rate(x, ...)
empiric_eff_rate(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a numerical vector
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB') empiric_tox_rate(x)
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB') empiric_tox_rate(x)
Get the empirical or observed toxicity rate seen at each dose under investigation. This is simply the number of toxicities divded by the number of patients evaluated.
empiric_tox_rate(x, ...)
empiric_tox_rate(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a numerical vector
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% empiric_tox_rate()
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% empiric_tox_rate()
This function stops with en error if it detects that outcomes describing a trial path have diverged from that advocated by the 3+3 method.
enforce_three_plus_three(outcomes, allow_deescalate = FALSE)
enforce_three_plus_three(outcomes, allow_deescalate = FALSE)
outcomes |
Outcomes observed. See |
allow_deescalate |
TRUE to allow de-escalation, as described by Korn et al. Default is FALSE. |
Nothing. Function stops if problem detected.
## Not run: enforce_three_plus_three('1NNN 2NTN 2NNN') # OK enforce_three_plus_three('1NNN 2NTN 2N') # OK too, albeit in-progress cohort enforce_three_plus_three('1NNN 1N') # Not OK because should have escalated ## End(Not run)
## Not run: enforce_three_plus_three('1NNN 2NTN 2NNN') # OK enforce_three_plus_three('1NNN 2NTN 2N') # OK too, albeit in-progress cohort enforce_three_plus_three('1NNN 1N') # Not OK because should have escalated ## End(Not run)
Fit a dose-finding model to some outcomes.
fit(selector_factory, outcomes, ...)
fit(selector_factory, outcomes, ...)
selector_factory |
Object of type |
outcomes |
Outcome string. See |
... |
Extra args are passed onwards. |
Object of generic type selector
.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% recommended_dose() # Etc
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% recommended_dose() # Etc
This method creates a dose selector that will follow a pre-specified trial path. Whilst the trial path is matched by realised outcomes, the selector will recommend the next dose in the desired sequence. As soon as the observed outcomes diverge from the desired path, the selector stops giving dose recommendations. This makes it possible, for instance, to specify a fixed escalation plan that should be followed until the first toxicity is seen. This tactic is used by some model-based designs to get rapidly to the doses where the action is. See, for example, the dfcrm package and Cheung (2011).
follow_path(path)
follow_path(path)
path |
Follow this outcome path. See |
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
Cheung. Dose Finding by the Continual Reassessment Method. 2011. Chapman and Hall/CRC. ISBN 9781420091519
model1 <- follow_path(path = '1NNN 2NNN 3NNN 4NNN') fit1 <- model1 %>% fit('1NNN 2N') fit1 %>% recommended_dose() fit1 %>% continue() # The model recommends continuing at dose 2 because the observed outcomes # perfectly match the desired escalation path. fit2 <- model1 %>% fit('1NNN 2NT') fit2 %>% recommended_dose() fit2 %>% continue() # Uh oh. Toxicity has now been seen, the outcomes diverge from the sought # path, hence this class recommends no dose now. # At this point, we can hand over dose selection decisions to another class # by chaining them together, like: model2 <- follow_path(path = '1NNN 2NNN 3NNN 4NNN') %>% get_dfcrm(skeleton = c(0.05, 0.1, 0.25, 0.4, 0.6), target = 0.25) fit3 <- model2 %>% fit('1NNN 2NT') # Now the CRM model is using all of the outcomes to calculate the next dose: fit3 %>% recommended_dose() fit3 %>% continue()
model1 <- follow_path(path = '1NNN 2NNN 3NNN 4NNN') fit1 <- model1 %>% fit('1NNN 2N') fit1 %>% recommended_dose() fit1 %>% continue() # The model recommends continuing at dose 2 because the observed outcomes # perfectly match the desired escalation path. fit2 <- model1 %>% fit('1NNN 2NT') fit2 %>% recommended_dose() fit2 %>% continue() # Uh oh. Toxicity has now been seen, the outcomes diverge from the sought # path, hence this class recommends no dose now. # At this point, we can hand over dose selection decisions to another class # by chaining them together, like: model2 <- follow_path(path = '1NNN 2NNN 3NNN 4NNN') %>% get_dfcrm(skeleton = c(0.05, 0.1, 0.25, 0.4, 0.6), target = 0.25) fit3 <- model2 %>% fit('1NNN 2NT') # Now the CRM model is using all of the outcomes to calculate the next dose: fit3 %>% recommended_dose() fit3 %>% continue()
Get an object to fit the BOIN model using the BOIN package.
get_boin(num_doses, target, use_stopping_rule = TRUE, ...)
get_boin(num_doses, target, use_stopping_rule = TRUE, ...)
num_doses |
Number of doses under investigation. |
target |
We seek a dose with this probability of toxicity. |
use_stopping_rule |
TRUE to use the toxicity stopping rule described in Yan et al. (2019). FALSE to suppress the authors' stopping rule, with the assumption being that you will test the necessity to stop early in some other way. |
... |
Extra args are passed to |
an object of type selector_factory
that can fit the
BOIN model to outcomes.
Yan, F., Pan, H., Zhang, L., Liu, S., & Yuan, Y. (2019). BOIN: An R Package for Designing Single-Agent and Drug-Combination Dose-Finding Trials Using Bayesian Optimal Interval Designs. Journal of Statistical Software, 27(November 2017), 0–35. https://doi.org/10.18637/jss.v000.i00
Liu, S., & Yuan, Y. (2015). Bayesian optimal designs for Phase I clinical trials. J. R. Stat. Soc. C, 64, 507–523. https://doi.org/10.1111/rssc.12089
target <- 0.25 model1 <- get_boin(num_doses = 5, target = target) outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose()
target <- 0.25 model1 <- get_boin(num_doses = 5, target = target) outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose()
This function returns an object that can be used to fit the BOIN12 model for phase I/II dose-finding, i.e. it selects doses according to efficacy and toxicity outcomes.
get_boin12( num_doses, phi_t, phi_e, u1 = 100, u2, u3, u4 = 0, n_star = 6, c_t = 0.95, c_e = 0.9, start_dose = 1, prior_alpha = 1, prior_beta = 1, ... )
get_boin12( num_doses, phi_t, phi_e, u1 = 100, u2, u3, u4 = 0, n_star = 6, c_t = 0.95, c_e = 0.9, start_dose = 1, prior_alpha = 1, prior_beta = 1, ... )
num_doses |
integer, num of doses under investigation |
phi_t |
Probability of toxicity threshold |
phi_e |
Probability of efficacy threshold |
u1 |
utility of efficacy without toxicity, 100 by default |
u2 |
utility of no efficacy and no toxicity, between u1 and u4 |
u3 |
utility of efficacy and toxicity, between u1 and u4 |
u4 |
utility of toxicity without efficacy , 0 by default |
n_star |
when tox is within bounds, stop exploring higher doses when n at dose is greater than or equal to this value. 6 by default. |
c_t |
certainty required to flag excess toxicity, 0.95 by default |
c_e |
certainty required to flag deficient efficacy, 0.9 by default |
start_dose |
index of starting dose, 1 by default (i.e. lowest dose) |
prior_alpha |
first shape param for prior on beta prior, 1 by default |
prior_beta |
second shape param for prior on beta prior, 1 by default |
... |
Extra args are passed onwards. |
an object of type selector_factory
that can fit the
BOIN12 model to outcomes.
Lin, R., Zhou, Y., Yan, F., Li, D., & Yuan, Y. (2020). BOIN12: Bayesian optimal interval phase I/II trial design for utility-based dose finding in immunotherapy and targeted therapies. JCO precision oncology, 4, 1393-1402.
# Examples in Lin et al. model <- get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, n_star = 6) fit <- model %>% fit('1NNN 2ENT 3ETT 2EEN') fit %>% recommended_dose() fit %>% continue() fit %>% is_randomising() fit %>% dose_admissible() fit %>% prob_administer()
# Examples in Lin et al. model <- get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, n_star = 6) fit <- model %>% fit('1NNN 2ENT 3ETT 2EEN') fit %>% recommended_dose() fit %>% continue() fit %>% is_randomising() fit %>% dose_admissible() fit %>% prob_administer()
This function returns an object that can be used to fit a CRM model using methods provided by the dfcrm package.
Dose selectors are designed to be daisy-chained together to achieve different
behaviours. This class is a **resumptive** selector, meaning it carries on
when the previous dose selector, where present, has elected not to continue.
For example, this allows instances of this class to be preceded by a selector
that follows a fixed path in an initial escalation plan, such as that
provided by follow_path
. In this example, when the observed
trial outcomes deviate from that initial plan, the selector following the
fixed path elects not to continue and responsibility passes to this class.
See Examples.
The time-to-event variant, TITE-CRM, is used via the
dfcrm::titecrm
function when you specify tite = TRUE
. This
weights the observations to allow dose-selections based on partially observed
outcomes.
get_dfcrm(parent_selector_factory = NULL, skeleton, target, tite = FALSE, ...)
get_dfcrm(parent_selector_factory = NULL, skeleton, target, tite = FALSE, ...)
parent_selector_factory |
optional object of type
|
skeleton |
Dose-toxicity skeleton, a non-decreasing vector of probabilities. |
target |
We seek a dose with this probability of toxicity. |
tite |
FALSE to use regular CRM; TRUE to use TITE-CRM. See Description. |
... |
Extra args are passed to |
an object of type selector_factory
that can fit the
CRM model to outcomes.
Cheung, K. 2019. dfcrm: Dose-Finding by the Continual Reassessment Method. R package version 0.2-2.1. https://CRAN.R-project.org/package=dfcrm
Cheung, K. 2011. Dose Finding by the Continual Reassessment Method. Chapman and Hall/CRC. ISBN 9781420091519
O’Quigley J, Pepe M, Fisher L. Continual reassessment method: a practical design for phase 1 clinical trials in cancer. Biometrics. 1990;46(1):33-48. doi:10.2307/2531628
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model1 <- get_dfcrm(skeleton = skeleton, target = target) # By default, dfcrm fits the empiric model: outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose() # But we can provide extra args to get_dfcrm that are than passed onwards to # the call to dfcrm::crm to override the defaults. For example, if we want # the one-parameter logistic model: model2 <- get_dfcrm(skeleton = skeleton, target = target, model = 'logistic') model2 %>% fit(outcomes) %>% recommended_dose() # dfcrm does not offer a two-parameter logistic model but other classes do. # We can use an initial dose-escalation plan, a pre-specified path that # should be followed until trial outcomes deviate, at which point the CRM # model takes over. For instance, if we want to use two patients at each of # the first three doses in the absence of toxicity, irrespective the model's # advice, we would run: model1 <- follow_path('1NN 2NN 3NN') %>% get_dfcrm(skeleton = skeleton, target = target) # If outcomes match the desired path, the path is followed further: model1 %>% fit('1NN 2N') %>% recommended_dose() # But when the outcomes diverge: model1 %>% fit('1NN 2T') %>% recommended_dose() # Or the pre-specified path comes to an end: model1 %>% fit('1NN 2NN 3NN') %>% recommended_dose() # The CRM model takes over.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model1 <- get_dfcrm(skeleton = skeleton, target = target) # By default, dfcrm fits the empiric model: outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose() # But we can provide extra args to get_dfcrm that are than passed onwards to # the call to dfcrm::crm to override the defaults. For example, if we want # the one-parameter logistic model: model2 <- get_dfcrm(skeleton = skeleton, target = target, model = 'logistic') model2 %>% fit(outcomes) %>% recommended_dose() # dfcrm does not offer a two-parameter logistic model but other classes do. # We can use an initial dose-escalation plan, a pre-specified path that # should be followed until trial outcomes deviate, at which point the CRM # model takes over. For instance, if we want to use two patients at each of # the first three doses in the absence of toxicity, irrespective the model's # advice, we would run: model1 <- follow_path('1NN 2NN 3NN') %>% get_dfcrm(skeleton = skeleton, target = target) # If outcomes match the desired path, the path is followed further: model1 %>% fit('1NN 2N') %>% recommended_dose() # But when the outcomes diverge: model1 %>% fit('1NN 2T') %>% recommended_dose() # Or the pre-specified path comes to an end: model1 %>% fit('1NN 2NN 3NN') %>% recommended_dose() # The CRM model takes over.
Get an object to fit the TITE-CRM model using the dfcrm package.
get_dfcrm_tite(parent_selector_factory = NULL, skeleton, target, ...)
get_dfcrm_tite(parent_selector_factory = NULL, skeleton, target, ...)
parent_selector_factory |
optional object of type
|
skeleton |
Dose-toxicity skeleton, a non-decreasing vector of probabilities. |
target |
We seek a dose with this probability of toxicity. |
... |
Extra args are passed to |
This function is a short-cut to get_dfcrm(tite = TRUE)
. See
get_dfcrm
for full details.
an object of type selector_factory
that can fit the
CRM model to outcomes.
Cheung, K. 2019. dfcrm: Dose-Finding by the Continual Reassessment Method. R package version 0.2-2.1. https://CRAN.R-project.org/package=dfcrm
Cheung, K. 2011. Dose Finding by the Continual Reassessment Method. Chapman and Hall/CRC. ISBN 9781420091519
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model1 <- get_dfcrm_tite(skeleton = skeleton, target = target) outcomes <- data.frame( dose = c(1, 1, 2, 2, 3, 3), tox = c(0, 0, 0, 0, 1, 0), weight = c(1, 1, 1, 0.9, 1, 0.5), cohort = c(1, 2, 3, 4, 5, 6) ) fit <- model1 %>% fit(outcomes)
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model1 <- get_dfcrm_tite(skeleton = skeleton, target = target) outcomes <- data.frame( dose = c(1, 1, 2, 2, 3, 3), tox = c(0, 0, 0, 0, 1, 0), weight = c(1, 1, 1, 0.9, 1, 0.5), cohort = c(1, 2, 3, 4, 5, 6) ) fit <- model1 %>% fit(outcomes)
A dose-escalation design exists to select doses in response to observed outcomes. The entire space of possible responses can be calculated to show the behaviour of a design in response to all feasible outcomes. This function performs that task.
get_dose_paths(selector_factory, cohort_sizes, ...)
get_dose_paths(selector_factory, cohort_sizes, ...)
selector_factory |
Object of type |
cohort_sizes |
Integer vector representing sizes of |
... |
Extra args are passed onwards. |
Object of type dose_paths
.
# Calculate paths for a 3+3 design for the next two cohorts of three patients paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3))
# Calculate paths for a 3+3 design for the next two cohorts of three patients paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3))
Get posterior model weights for several empiric CRM skeletons, assuming a normal prior on the beta model parameter
get_empiric_crm_skeleton_weights( skeletons, events_at_dose, n_at_dose, prior = rep(1, nrow(skeletons)) )
get_empiric_crm_skeleton_weights( skeletons, events_at_dose, n_at_dose, prior = rep(1, nrow(skeletons)) )
skeletons |
matrix with one skeleton per row, so that the number of columns is the number of doses under investigation. |
events_at_dose |
integer vector of number of events at doses |
n_at_dose |
integer vector of number of patients at doses |
prior |
vector of prior model weights. Length should be same as number
of rows in |
numerical vector, posterior weights of the skeletons.
The modified toxicity probability interval (mTPI)is a dose-escalation design by Ji et al. As the name suggests, it is an adaptation of the TPI design.
get_mtpi( parent_selector_factory = NULL, num_doses, target, epsilon1, epsilon2, exclusion_certainty, alpha = 1, beta = 1, ... )
get_mtpi( parent_selector_factory = NULL, num_doses, target, epsilon1, epsilon2, exclusion_certainty, alpha = 1, beta = 1, ... )
parent_selector_factory |
Object of type |
num_doses |
Number of doses under investigation. |
target |
We seek a dose with this probability of toxicity. |
epsilon1 |
This parameter determines the lower bound of the equivalence interval. See Details. |
epsilon2 |
This parameter determines the upper bound of the equivalence interval. See Details. |
exclusion_certainty |
Numeric, threshold posterior certainty required to exclude a dose for being excessively toxic. The authors discuss values in the range 0.7 - 0.95. Set to a value > 1 to suppress the dose exclusion mechanism. The authors use the Greek letter xi for this parameter. |
alpha |
First shape parameter of the beta prior distribution on the probability of toxicity. |
beta |
Second shape parameter of the beta prior distribution on the probability of toxicity. |
... |
Extra args are passed onwards. |
an object of type selector_factory
that can fit the
TPI model to outcomes.
The design seeks a dose with probability of toxicity
close to a target probability
by iteratively calculating the
interval
In this model, and
are specified
constants.
is estimated by a Bayesian beta-binomial conjugate
model
where is the number of toxicities observed and
is the
number of patients treated at dose
, and
and
are hyperparameters for the beta prior on
.
A dose is excluded as inadmissible if
The trial commences at a starting dose, possibly dose 1. If dose
has just been evaluated in patient(s), dose selection decisions proceed by
calculating the unit probability mass of the true toxicity rate at dose
using the partition of the probability space
,
, and
.
The unit probability mass (UPM) of an interval is the posterior probability
that the true toxicity rate belongs to the interval divided by the width of
the interval. The interval with maximal UPM determines the recommendation for
the next patient(s), with the intervals corresponding to decisions tp
escalate, stay, and de-escalate dose, respectively. Further to this are rules
that prevent escalation to an inadmissible dose.
In their paper, the authors demonstrate acceptable operating performance
using
,
,
and
.
See the publications for full details.
Ji, Y., Liu, P., Li, Y., & Bekele, B. N. (2010). A modified toxicity probability interval method for dose-finding trials. Clinical Trials, 7(6), 653-663. https://doi.org/10.1177/1740774510382799
Ji, Y., & Yang, S. (2017). On the Interval-Based Dose-Finding Designs, 1-26. Retrieved from https://arxiv.org/abs/1706.03277
target <- 0.25 model1 <- get_mtpi(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose()
target <- 0.25 model1 <- get_mtpi(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose()
The modified toxicity probability interval 2 (mTPI-2) is a dose-escalation design by Guo et al. As the name suggests, it is an adaptation of the mTPI design.
get_mtpi2( parent_selector_factory = NULL, num_doses, target, epsilon1, epsilon2, exclusion_certainty, alpha = 1, beta = 1, ... )
get_mtpi2( parent_selector_factory = NULL, num_doses, target, epsilon1, epsilon2, exclusion_certainty, alpha = 1, beta = 1, ... )
parent_selector_factory |
Object of type |
num_doses |
Number of doses under investigation. |
target |
We seek a dose with this probability of toxicity. |
epsilon1 |
This parameter determines the lower bound of the equivalence interval. See Details. |
epsilon2 |
This parameter determines the upper bound of the equivalence interval. See Details. |
exclusion_certainty |
Numeric, threshold posterior certainty required to exclude a dose for being excessively toxic. The authors discuss values in the range 0.7 - 0.95. Set to a value > 1 to suppress the dose exclusion mechanism. The authors use the Greek letter xi for this parameter. |
alpha |
First shape parameter of the beta prior distribution on the probability of toxicity. |
beta |
Second shape parameter of the beta prior distribution on the probability of toxicity. |
... |
Extra args are passed onwards. |
an object of type selector_factory
that can fit the
mTPI-2 model to outcomes.
The design seeks a dose with probability of toxicity
close to a target probability
by iteratively calculating the
interval
In this model, and
are specified
constants.
is estimated by a Bayesian beta-binomial conjugate
model
where is the number of toxicities observed and
is the
number of patients treated at dose
, and
and
are hyperparameters for the beta prior on
.
A dose is excluded as inadmissible if
The trial commences at a starting dose, possibly dose 1. If dose
has just been evaluated in patient(s), dose selection decisions proceed by
calculating the unit probability mass of the true toxicity rate at dose
using the partition of the probability space into subintervals with
equal length given by
.
is the
equivalence interval
, with
the set of all intervals below, and
the set of all
intervals above.
The unit probability mass (UPM) of an interval is the posterior probability
that the true toxicity rate belongs to the interval divided by the width of
the interval. The interval with maximal UPM determines the recommendation for
the next patient(s), with the intervals corresponding to decisions to
escalate, stay, and de-escalate dose, respectively. Further to this are rules
that prevent escalation to an inadmissible dose.
In the original mTPI paper, the authors demonstrate acceptable operating
performance using
,
,
and
.
The authors of the mTPI-2 approach show desirable performance as compared
to the original mTPI method, under particular parameter choices.
See the publications for full details.
Ji, Y., Liu, P., Li, Y., & Bekele, B. N. (2010). A modified toxicity probability interval method for dose-finding trials. Clinical Trials, 7(6), 653–663. https://doi.org/10.1177/1740774510382799
Ji, Y., & Yang, S. (2017). On the Interval-Based Dose-Finding Designs, 1–26. Retrieved from https://arxiv.org/abs/1706.03277
Guo, W., Wang, SJ., Yang, S., Lynn, H., Ji, Y. (2017). A Bayesian Interval Dose-Finding Design Addressing Ockham's Razor: mTPI-2. https://doi.org/10.1016/j.cct.2017.04.006
target <- 0.25 model1 <- get_mtpi2(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose()
target <- 0.25 model1 <- get_mtpi2(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose()
An instance of PatientSample
, or one of its subclasses like
CorrelatedPatientSample
, reflects one particular state of the
world, where patient i would reliably experience a toxicity or efficacy
event if treated at a particular dose. This function, given true toxicity and
efficacy probabilities at doses 1, ..., num_doses, calculates 0/1 matrices to
reflect whether the patients in those samples would have experienced toxicity
and efficacy at the doses, had they been dosed as such. Using the
vernacular of causal inference, these are _potential outcomes_. At any single
instant, a patient can only be dosed at one dose, so only one of the
outcomes for a patient would in reality have been observed; the rest are
counterfactual.
get_potential_outcomes(patient_samples, true_prob_tox, true_prob_eff)
get_potential_outcomes(patient_samples, true_prob_tox, true_prob_eff)
patient_samples |
list of |
true_prob_tox |
vector of probabilities of toxicity outcomes at doses |
true_prob_eff |
vector of probabilities of efficacy outcomes at doses |
a list of lists, with names tox and eff, each mapping to a matrix of the potential outcomes.
num_sims <- 10 ps <- lapply(1:num_sims, function(x) PatientSample$new()) # Set tox_u and eff_u for each simulation set.seed(2024) lapply(1:num_sims, function(x) { tox_u_new <- runif(n = 20) eff_u_new <- runif(n = 20) ps[[x]]$set_eff_and_tox(tox_u = tox_u_new, eff_u = eff_u_new) }) true_prob_tox <- c(0.05, 0.10, 0.15, 0.18, 0.45) true_prob_eff <- c(0.40, 0.50, 0.52, 0.53, 0.53) get_potential_outcomes( patient_samples = ps, true_prob_tox = true_prob_tox, true_prob_eff = true_prob_eff )
num_sims <- 10 ps <- lapply(1:num_sims, function(x) PatientSample$new()) # Set tox_u and eff_u for each simulation set.seed(2024) lapply(1:num_sims, function(x) { tox_u_new <- runif(n = 20) eff_u_new <- runif(n = 20) ps[[x]]$set_eff_and_tox(tox_u = tox_u_new, eff_u = eff_u_new) }) true_prob_tox <- c(0.05, 0.10, 0.15, 0.18, 0.45) true_prob_eff <- c(0.40, 0.50, 0.52, 0.53, 0.53) get_potential_outcomes( patient_samples = ps, true_prob_tox = true_prob_tox, true_prob_eff = true_prob_eff )
Get an object to fit a dose-selector that randomly selects doses. Whilst this design is unlikely to pass the ethical hurdles when investigating truly experimental treatments, this class is useful for illustrating methods and can be useful for benchmarking.
get_random_selector( parent_selector_factory = NULL, prob_select, supports_efficacy = FALSE, ... )
get_random_selector( parent_selector_factory = NULL, prob_select, supports_efficacy = FALSE, ... )
parent_selector_factory |
optional object of type
|
prob_select |
vector of probabilities, the probability of selecting dose 1...n |
supports_efficacy |
TRUE to monitor toxicity and efficacy outcomes; FALSE (by default) to just monitor toxicity outcomes. |
... |
Extra args are ignored. |
an object of type selector_factory
.
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select) fit <- model %>% fit('1NTN') fit %>% recommended_dose() # This is random # We could also precede this selector with a set path: model <- follow_path('1NN 2NN 3NN') %>% get_random_selector(prob_select = prob_select) fit <- model %>% fit('1NN') fit %>% recommended_dose() # This is not-random; it comes from the path. fit <- model %>% fit('1NN 2NT') fit %>% recommended_dose() # This is random; the path is discarded.
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select) fit <- model %>% fit('1NTN') fit %>% recommended_dose() # This is random # We could also precede this selector with a set path: model <- follow_path('1NN 2NN 3NN') %>% get_random_selector(prob_select = prob_select) fit <- model %>% fit('1NN') fit %>% recommended_dose() # This is not-random; it comes from the path. fit <- model %>% fit('1NN 2NT') fit %>% recommended_dose() # This is random; the path is discarded.
Get an object to fit the 3+3 model.
get_three_plus_three(num_doses, allow_deescalate = FALSE, ...)
get_three_plus_three(num_doses, allow_deescalate = FALSE, ...)
num_doses |
Number of doses under investigation. |
allow_deescalate |
TRUE to allow de-escalation, as described by Korn et al. Default is FALSE. |
... |
Extra args are not currently used. |
an object of type selector_factory
that can fit the
3+3 model to outcomes.
Storer BE. Design and Analysis of Phase I Clinical Trials. Biometrics. 1989;45(3):925-937. doi:10.2307/2531693
Korn EL, Midthune D, Chen TT, Rubinstein LV, Christian MC, Simon RM. A comparison of two phase I trial designs. Statistics in Medicine. 1994;13(18):1799-1806. doi:10.1002/sim.4780131802
model <- get_three_plus_three(num_doses = 5) fit1 <- model %>% fit('1NNN 2NTN') fit1 %>% recommended_dose() fit1 %>% continue() fit2 <- model %>% fit('1NNN 2NTN 2NNT') fit2 %>% recommended_dose() fit2 %>% continue()
model <- get_three_plus_three(num_doses = 5) fit1 <- model %>% fit('1NNN 2NTN') fit1 %>% recommended_dose() fit1 %>% continue() fit2 <- model %>% fit('1NNN 2NTN 2NNT') fit2 %>% recommended_dose() fit2 %>% continue()
The toxicity probability interval (TPI)is a dose-escalation design by Ji et al.
get_tpi( num_doses, target, k1, k2, exclusion_certainty, alpha = 0.005, beta = 0.005, ... )
get_tpi( num_doses, target, k1, k2, exclusion_certainty, alpha = 0.005, beta = 0.005, ... )
num_doses |
Number of doses under investigation. |
target |
We seek a dose with this probability of toxicity. |
k1 |
The K1 parameter in TPI determines the upper bound of the equivalence interval. See Details. |
k2 |
The K2 parameter in TPI determines the lower bound of the equivalence interval. See Details. |
exclusion_certainty |
Numeric, threshold posterior certainty required to exclude a dose for being excessively toxic. The authors discuss values in the range 0.7 - 0.95. Set to a value > 1 to suppress the dose exclusion mechanism. The authors use the Greek letter xi for this parameter. |
alpha |
First shape parameter of the beta prior distribution on the probability of toxicity. |
beta |
Second shape parameter of the beta prior distribution on the probability of toxicity. |
... |
Extra args are passed onwards. |
an object of type selector_factory
that can fit the
TPI model to outcomes.
The design seeks a dose with probability of toxicity
close to a target probability
by iteratively calculating the
interval
In this model, and
are specified constants and
is the standard deviation of
arising from a
Bayesian beta-binomial conjugate model
where is the number of toxicities observed and
is the
number of patients treated at dose
, and
and
are hyperparameters for the beta prior on
.
A dose is excluded as inadmissible if
The trial commences at a starting dose, possibly dose 1. If dose
has just been evaluated in patient(s), dose selection decisions proceed by
calculating the posterior probability that the true toxicity rate at dose
belongs to the three partition regions
,
, and
, corresponding to decisions escalate,
stay, and de-escalate dose, respectively. Further to this are rules that
prevent escalation to an inadmissible dose.
In their paper, the authors demonstrate acceptable operating performance
using
,
,
and
.
See the publications for full details.
Ji, Y., Li, Y., & Bekele, B. N. (2007). Dose-finding in phase I clinical trials based on toxicity probability intervals. Clinical Trials, 4(3), 235–244. https://doi.org/10.1177/1740774507079442
Ji, Y., & Yang, S. (2017). On the Interval-Based Dose-Finding Designs, 1–26. Retrieved from https://arxiv.org/abs/1706.03277
target <- 0.25 model1 <- get_tpi(num_doses = 5, target = target, k1 = 1, k2 = 1.5, exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose()
target <- 0.25 model1 <- get_tpi(num_doses = 5, target = target, k1 = 1, k2 = 1.5, exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose()
This function returns an object that can be used to fit a CRM model using methods provided by the trialr package.
Dose selectors are designed to be daisy-chained together to achieve different
behaviours. This class is a **resumptive** selector, meaning it carries on
when the previous dose selector, where present, has elected not to continue.
For example, this allows instances of this class to be preceded by a selector
that follows a fixed path in an initial escalation plan, such as that
provided by follow_path
. In this example, when the observed
trial outcomes deviate from that initial plan, the selector following the
fixed path elects not to continue and responsibility passes to this class.
See Examples.
The time-to-event variant, TITE-CRM, is used when you specify
tite = TRUE
. This weights the observations to allow dose-selections
based on partially observed outcomes.
get_trialr_crm( parent_selector_factory = NULL, skeleton, target, model, tite = FALSE, ... )
get_trialr_crm( parent_selector_factory = NULL, skeleton, target, model, tite = FALSE, ... )
parent_selector_factory |
optional object of type
|
skeleton |
Dose-toxicity skeleton, a non-decreasing vector of probabilities. |
target |
We seek a dose with this probability of toxicity. |
model |
character string identifying which model form to use. Options
include empiric, logistic, logistic2. The model form chosen determines which
prior hyperparameters are required. See |
tite |
FALSE to use regular CRM; TRUE to use TITE-CRM. See Description. |
... |
Extra args are passed to |
an object of type selector_factory
that can fit the
CRM model to outcomes.
Kristian Brock (2020). trialr: Clinical Trial Designs in 'rstan'. R package version 0.1.5. https://github.com/brockk/trialr
Kristian Brock (2019). trialr: Bayesian Clinical Trial Designs in R and Stan. arXiv preprint arXiv:1907.00161.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # The model to use must be specified in trialr: model1 <- get_trialr_crm(skeleton = skeleton, target = target, model = 'empiric', beta_sd = 1.34) # Refer to the trialr documentation for more details on model forms. outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose() # But we can provide extra args to trialr that are than passed onwards to # the call to trialr::stan_crm to override the defaults. # For example, if we want the one-parameter logistic model, we run: model2 <- get_trialr_crm(skeleton = skeleton, target = target, model = 'logistic', a0 = 3, beta_mean = 0, beta_sd = 1) model2 %>% fit(outcomes) %>% recommended_dose() # And, if we want the two-parameter logistic model, we run: model3 <- get_trialr_crm(skeleton = skeleton, target = target, model = 'logistic2', alpha_mean = 0, alpha_sd = 2, beta_mean = 0, beta_sd = 1) model3 %>% fit(outcomes) %>% recommended_dose() # We can use an initial dose-escalation plan, a pre-specified path that # should be followed until trial outcomes deviate, at which point the CRM # model takes over. For instance, if we want to use two patients at each of # the first three doses in the absence of toxicity, irrespective the model's # advice, we would run: model1 <- follow_path('1NN 2NN 3NN') %>% get_trialr_crm(skeleton = skeleton, target = target, model = 'empiric', beta_sd = 1.34) # If outcomes match the desired path, the path is followed further: model1 %>% fit('1NN 2N') %>% recommended_dose() # But when the outcomes diverge: model1 %>% fit('1NN 2T') %>% recommended_dose() # Or the pre-specified path comes to an end: model1 %>% fit('1NN 2NN 3NN') %>% recommended_dose() # ...the CRM model takes over.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # The model to use must be specified in trialr: model1 <- get_trialr_crm(skeleton = skeleton, target = target, model = 'empiric', beta_sd = 1.34) # Refer to the trialr documentation for more details on model forms. outcomes <- '1NNN 2NTN' model1 %>% fit(outcomes) %>% recommended_dose() # But we can provide extra args to trialr that are than passed onwards to # the call to trialr::stan_crm to override the defaults. # For example, if we want the one-parameter logistic model, we run: model2 <- get_trialr_crm(skeleton = skeleton, target = target, model = 'logistic', a0 = 3, beta_mean = 0, beta_sd = 1) model2 %>% fit(outcomes) %>% recommended_dose() # And, if we want the two-parameter logistic model, we run: model3 <- get_trialr_crm(skeleton = skeleton, target = target, model = 'logistic2', alpha_mean = 0, alpha_sd = 2, beta_mean = 0, beta_sd = 1) model3 %>% fit(outcomes) %>% recommended_dose() # We can use an initial dose-escalation plan, a pre-specified path that # should be followed until trial outcomes deviate, at which point the CRM # model takes over. For instance, if we want to use two patients at each of # the first three doses in the absence of toxicity, irrespective the model's # advice, we would run: model1 <- follow_path('1NN 2NN 3NN') %>% get_trialr_crm(skeleton = skeleton, target = target, model = 'empiric', beta_sd = 1.34) # If outcomes match the desired path, the path is followed further: model1 %>% fit('1NN 2N') %>% recommended_dose() # But when the outcomes diverge: model1 %>% fit('1NN 2T') %>% recommended_dose() # Or the pre-specified path comes to an end: model1 %>% fit('1NN 2NN 3NN') %>% recommended_dose() # ...the CRM model takes over.
Get an object to fit the TITE-CRM model using the trialr package.
get_trialr_crm_tite( parent_selector_factory = NULL, skeleton, target, model, ... )
get_trialr_crm_tite( parent_selector_factory = NULL, skeleton, target, model, ... )
parent_selector_factory |
optional object of type
|
skeleton |
Dose-toxicity skeleton, a non-decreasing vector of probabilities. |
target |
We seek a dose with this probability of toxicity. |
model |
character string identifying which model form to use. Options
include empiric, logistic, logistic2. The model form chosen determines which
prior hyperparameters are required. See |
... |
Extra args are passed to |
This function is a short-cut to get_trialr_crm(tite = TRUE)
. See
get_trialr_crm
for full details.
an object of type selector_factory
that can fit the
CRM model to outcomes.
# TODO
# TODO
This function returns an object that can be used to fit the EffTox model for phase I/II dose-finding using methods provided by the trialr package.
get_trialr_efftox( parent_selector_factory = NULL, real_doses, efficacy_hurdle, toxicity_hurdle, p_e, p_t, eff0, tox1, eff_star, tox_star, priors, ... )
get_trialr_efftox( parent_selector_factory = NULL, real_doses, efficacy_hurdle, toxicity_hurdle, p_e, p_t, eff0, tox1, eff_star, tox_star, priors, ... )
parent_selector_factory |
optional object of type
|
real_doses |
A vector of numbers, the doses under investigation. They should be ordered from lowest to highest and be in consistent units. E.g. to conduct a dose-finding trial of doses 10mg, 20mg and 50mg, use c(10, 20, 50). |
efficacy_hurdle |
Minimum acceptable efficacy probability. A number between 0 and 1. |
toxicity_hurdle |
Maximum acceptable toxicity probability. A number between 0 and 1. |
p_e |
Certainty required to infer a dose is acceptable with regards to being probably efficacious; a number between 0 and 1. |
p_t |
Certainty required to infer a dose is acceptable with regards to being probably tolerable; a number between 0 and 1. |
eff0 |
Efficacy probability required when toxicity is impossible; a number between 0 and 1 (see Details). |
tox1 |
Toxicity probability permitted when efficacy is guaranteed; a number between 0 and 1 (see Details). |
eff_star |
Efficacy probability of an equi-utility third point (see Details). |
tox_star |
Toxicity probability of an equi-utility third point (see Details). |
priors |
instance of class |
... |
Extra args are passed to |
an object of type selector_factory
that can fit the
EffTox model to outcomes.
Thall, P., & Cook, J. (2004). Dose-Finding Based on Efficacy-Toxicity Trade-Offs. Biometrics, 60(3), 684-693. https://doi.org/10.1111/j.0006-341X.2004.00218.x
Thall, P., Herrick, R., Nguyen, H., Venier, J., & Norris, J. (2014). Effective sample size for computing prior hyperparameters in Bayesian phase I-II dose-finding. Clinical Trials, 11(6), 657-666. https://doi.org/10.1177/1740774514547397
Brock, K. (2020). trialr: Clinical Trial Designs in 'rstan'. R package version 0.1.5. https://github.com/brockk/trialr
Brock, K. (2019). trialr: Bayesian Clinical Trial Designs in R and Stan. arXiv preprint arXiv:1907.00161.
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020)
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020)
This function returns an object that can be used to fit a Neuenschwander, Branson and Gsponer (NBG) model for dose-finding using methods provided by the trialr package.
get_trialr_nbg( parent_selector_factory = NULL, real_doses, d_star, target, alpha_mean, alpha_sd, beta_mean, beta_sd, tite = FALSE, ... )
get_trialr_nbg( parent_selector_factory = NULL, real_doses, d_star, target, alpha_mean, alpha_sd, beta_mean, beta_sd, tite = FALSE, ... )
parent_selector_factory |
optional object of type
|
real_doses |
Doses under investigation, a non-decreasing vector of numbers. |
d_star |
Numeric, reference dose for calculating the covariate
|
target |
We seek a dose with this probability of toxicity. |
alpha_mean |
Prior mean of intercept variable for normal prior. See Details. Also see documentation for trialr package for further details. |
alpha_sd |
Prior standard deviation of intercept variable for normal prior. See Details. Also see documentation for trialr package for further details. |
beta_mean |
Prior mean of gradient variable for normal prior. See Details. Also see documentation for trialr package for further details. |
beta_sd |
Prior standard deviation of slope variable for normal prior. See Details. Also see documentation for trialr package for further details. |
tite |
FALSE to use regular model; TRUE to use TITE version See Description. |
... |
Extra args are passed to |
The model form implemented in trialr is:
with normal priors on alpha and beta.
Dose selectors are designed to be daisy-chained together to achieve different
behaviours. This class is a **resumptive** selector, meaning it carries on
when the previous dose selector, where present, has elected not to continue.
For example, this allows instances of this class to be preceded by a selector
that follows a fixed path in an initial escalation plan, such as that
provided by follow_path
. In this example, when the observed
trial outcomes deviate from that initial plan, the selector following the
fixed path elects not to continue and responsibility passes to this class.
See examples under get_dfcrm
.
A time-to-event variant, like TITE-CRM, is used when you specify
tite = TRUE
. This weights the observations to allow dose-selections
based on partially observed outcomes.
an object of type selector_factory
that can fit the
NBG model to outcomes.
Neuenschwander, B., Branson, M., & Gsponer, T. (2008). Critical aspects of the Bayesian approach to phase I cancer trials. Statistics in Medicine, 27, 2420–2439. https://doi.org/10.1002/sim.3230
Brock, K. (2020). trialr: Clinical Trial Designs in 'rstan'. R package version 0.1.5. https://github.com/brockk/trialr
Brock, K. (2019). trialr: Bayesian Clinical Trial Designs in R and Stan. arXiv preprint arXiv:1907.00161.
real_doses <- c(5, 10, 25, 40, 60) d_star <- 60 target <- 0.25 model <- get_trialr_nbg(real_doses = real_doses, d_star = d_star, target = target, alpha_mean = 2, alpha_sd = 1, beta_mean = 0.5, beta_sd = 1) # Refer to the trialr documentation for more details on model & priors. outcomes <- '1NNN 2NTN' fit <- model %>% fit(outcomes) fit %>% recommended_dose() fit %>% mean_prob_tox()
real_doses <- c(5, 10, 25, 40, 60) d_star <- 60 target <- 0.25 model <- get_trialr_nbg(real_doses = real_doses, d_star = d_star, target = target, alpha_mean = 2, alpha_sd = 1, beta_mean = 0.5, beta_sd = 1) # Refer to the trialr documentation for more details on model & priors. outcomes <- '1NNN 2NTN' fit <- model %>% fit(outcomes) fit %>% recommended_dose() fit %>% mean_prob_tox()
Get an object to fit a TITE version of the NBG dose-finding model using trialr
get_trialr_nbg_tite( parent_selector_factory = NULL, real_doses, d_star, target, alpha_mean, alpha_sd, beta_mean, beta_sd, ... )
get_trialr_nbg_tite( parent_selector_factory = NULL, real_doses, d_star, target, alpha_mean, alpha_sd, beta_mean, beta_sd, ... )
parent_selector_factory |
optional object of type
|
real_doses |
Doses under investigation, a non-decreasing vector of numbers. |
d_star |
Numeric, reference dose for calculating the covariate
|
target |
We seek a dose with this probability of toxicity. |
alpha_mean |
Prior mean of intercept variable for normal prior. See Details. Also see documentation for trialr package for further details. |
alpha_sd |
Prior standard deviation of intercept variable for normal prior. See Details. Also see documentation for trialr package for further details. |
beta_mean |
Prior mean of gradient variable for normal prior. See Details. Also see documentation for trialr package for further details. |
beta_sd |
Prior standard deviation of slope variable for normal prior. See Details. Also see documentation for trialr package for further details. |
... |
Extra args are passed to |
an object of type selector_factory
that can fit the
NBG model to outcomes.
# TODO
# TODO
This function returns an object that can be used to fit Wages & Taits model for phase I/II dose-finding, i.e. it selects doses according to efficacy and toxicity outcomes. This function delegates prior-to-posterior calculations to the dfcrm package.
get_wages_and_tait( parent_selector_factory = NULL, tox_skeleton, eff_skeletons, eff_skeleton_weights = rep(1, nrow(eff_skeletons)), tox_limit, eff_limit, num_randomise, ... )
get_wages_and_tait( parent_selector_factory = NULL, tox_skeleton, eff_skeletons, eff_skeleton_weights = rep(1, nrow(eff_skeletons)), tox_limit, eff_limit, num_randomise, ... )
parent_selector_factory |
optional object of type
|
tox_skeleton |
Dose-toxicity skeleton, a non-decreasing vector of probabilities. |
eff_skeletons |
Matrix of dose-efficacy skeletons, with the skeletons in rows. I.e. number of cols is equal to number of doses, and number of rows is equal to number of efficacy skeletons under consideration. |
eff_skeleton_weights |
numerical vector, prior weights to efficacy
skeletons. Should have length equal to number of rows in
|
tox_limit |
We seek a dose with probability of toxicity no greater than this. Value determines the admissible set. See Wages & Tait (2015). |
eff_limit |
We seek a dose with probability of efficacy no less than this. |
num_randomise |
integer, maximum number of patients to use in the adaptive randomisation phase of the trial. |
... |
Extra args are passed onwards. |
an object of type selector_factory
.
Wages, N. A., & Tait, C. (2015). Seamless Phase I/II Adaptive Design for Oncology Trials of Molecularly Targeted Agents. Journal of Biopharmaceutical Statistics, 25(5), 903–920. https://doi.org/10.1080/10543406.2014.920873
# Example in Wages & Tait (2015) tox_skeleton = c(0.01, 0.08, 0.15, 0.22, 0.29, 0.36) eff_skeletons = matrix(nrow=11, ncol=6) eff_skeletons[1,] <- c(0.60, 0.50, 0.40, 0.30, 0.20, 0.10) eff_skeletons[2,] <- c(0.50, 0.60, 0.50, 0.40, 0.30, 0.20) eff_skeletons[3,] <- c(0.40, 0.50, 0.60, 0.50, 0.40, 0.30) eff_skeletons[4,] <- c(0.30, 0.40, 0.50, 0.60, 0.50, 0.40) eff_skeletons[5,] <- c(0.20, 0.30, 0.40, 0.50, 0.60, 0.50) eff_skeletons[6,] <- c(0.10, 0.20, 0.30, 0.40, 0.50, 0.60) eff_skeletons[7,] <- c(0.20, 0.30, 0.40, 0.50, 0.60, 0.60) eff_skeletons[8,] <- c(0.30, 0.40, 0.50, 0.60, 0.60, 0.60) eff_skeletons[9,] <- c(0.40, 0.50, 0.60, 0.60, 0.60, 0.60) eff_skeletons[10,] <- c(0.50, 0.60, 0.60, 0.60, 0.60, 0.60) eff_skeletons[11,] <- c(rep(0.60, 6)) eff_skeleton_weights = rep(1, nrow(eff_skeletons)) tox_limit = 0.33 eff_limit = 0.05 model <- get_wages_and_tait(tox_skeleton = tox_skeleton, eff_skeletons = eff_skeletons, tox_limit = tox_limit, eff_limit = eff_limit, num_randomise = 20) fit <- model %>% fit('1NN 2EN 3BE') fit %>% recommended_dose() fit %>% is_randomising() fit %>% dose_admissible() fit %>% prob_administer()
# Example in Wages & Tait (2015) tox_skeleton = c(0.01, 0.08, 0.15, 0.22, 0.29, 0.36) eff_skeletons = matrix(nrow=11, ncol=6) eff_skeletons[1,] <- c(0.60, 0.50, 0.40, 0.30, 0.20, 0.10) eff_skeletons[2,] <- c(0.50, 0.60, 0.50, 0.40, 0.30, 0.20) eff_skeletons[3,] <- c(0.40, 0.50, 0.60, 0.50, 0.40, 0.30) eff_skeletons[4,] <- c(0.30, 0.40, 0.50, 0.60, 0.50, 0.40) eff_skeletons[5,] <- c(0.20, 0.30, 0.40, 0.50, 0.60, 0.50) eff_skeletons[6,] <- c(0.10, 0.20, 0.30, 0.40, 0.50, 0.60) eff_skeletons[7,] <- c(0.20, 0.30, 0.40, 0.50, 0.60, 0.60) eff_skeletons[8,] <- c(0.30, 0.40, 0.50, 0.60, 0.60, 0.60) eff_skeletons[9,] <- c(0.40, 0.50, 0.60, 0.60, 0.60, 0.60) eff_skeletons[10,] <- c(0.50, 0.60, 0.60, 0.60, 0.60, 0.60) eff_skeletons[11,] <- c(rep(0.60, 6)) eff_skeleton_weights = rep(1, nrow(eff_skeletons)) tox_limit = 0.33 eff_limit = 0.05 model <- get_wages_and_tait(tox_skeleton = tox_skeleton, eff_skeletons = eff_skeletons, tox_limit = tox_limit, eff_limit = eff_limit, num_randomise = 20) fit <- model %>% fit('1NN 2EN 3BE') fit %>% recommended_dose() fit %>% is_randomising() fit %>% dose_admissible() fit %>% prob_administer()
Visualise dose-paths as a graph
graph_paths(paths, viridis_palette = "viridis", RColorBrewer_palette = NULL)
graph_paths(paths, viridis_palette = "viridis", RColorBrewer_palette = NULL)
paths |
Object of type |
viridis_palette |
optional name of a colour palette in the viridis package. |
RColorBrewer_palette |
optional name of a colour palette in the RColorBrewer package. |
The viridis package supports palettes: viridis, magma, plasma, inferno, and cividis. The RColorBrewer package supports many palettes. Refer to those packages on CRAN for more details.
paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3, 3)) ## Not run: graph_paths(paths) graph_paths(paths, viridis_palette = 'plasma') graph_paths(paths, RColorBrewer_palette = 'YlOrRd') ## End(Not run)
paths <- get_three_plus_three(num_doses = 5) %>% get_dose_paths(cohort_sizes = c(3, 3, 3)) ## Not run: graph_paths(paths) graph_paths(paths, viridis_palette = 'plasma') graph_paths(paths, RColorBrewer_palette = 'YlOrRd') ## End(Not run)
Get the percentage of patients evaluated at each dose under investigation.
is_randomising(x, ...)
is_randomising(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a logical value
outcomes <- '1NNN 2NTN' fit <- get_random_selector(prob_select = c(0.1, 0.6, 0.3)) %>% fit(outcomes) fit %>% is_randomising()
outcomes <- '1NNN 2NTN' fit <- get_random_selector(prob_select = c(0.1, 0.6, 0.3)) %>% fit(outcomes) fit %>% is_randomising()
Weights for tolerance and toxicity events using linear function of time
linear_follow_up_weight( now_time, recruited_time, tox, max_time, tox_has_weight_1 = TRUE )
linear_follow_up_weight( now_time, recruited_time, tox, max_time, tox_has_weight_1 = TRUE )
now_time |
the time now |
recruited_time |
vector of recruitment times for patients |
tox |
integer vector of toxicity variables for patients, 1 means tox |
max_time |
the maximum window of evaluation for |
tox_has_weight_1 |
logical, TRUE to set the weight for tox to 1 identically |
numerical vector of weights
linear_follow_up_weight( now_time = 10, recruited_time = 4:7, tox = c(0, 0, 0, 1), max_time = 6, tox_has_weight_1 = TRUE ) linear_follow_up_weight( now_time = 10, recruited_time = 4:7, tox = c(0, 0, 0, 1), max_time = 6, tox_has_weight_1 = FALSE )
linear_follow_up_weight( now_time = 10, recruited_time = 4:7, tox = c(0, 0, 0, 1), max_time = 6, tox_has_weight_1 = TRUE ) linear_follow_up_weight( now_time = 10, recruited_time = 4:7, tox = c(0, 0, 0, 1), max_time = 6, tox_has_weight_1 = FALSE )
Get the estimated mean efficacy rate at each dose under investigation. This is a set of modelled statistics. The underlying models estimate efficacy probabilities in different ways. If no model-based estimate of the mean is available, this function will return a vector of NAs.
mean_prob_eff(x, ...)
mean_prob_eff(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a numerical vector
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') mean_prob_eff(x)
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') mean_prob_eff(x)
Get the estimated mean toxicity rate at each dose under investigation. This is a set of modelled statistics. The underlying models estimate toxicity probabilities in different ways. If no model-based estimate of the mean is available, this function will return a vector of NAs.
mean_prob_tox(x, ...)
mean_prob_tox(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a numerical vector
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% mean_prob_tox()
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% mean_prob_tox()
Get the estimated median efficacy rate at each dose under investigation. This is a set of modelled statistics. The underlying models estimate efficacy probabilities in different ways. If no model-based estimate of the median is available, this function will return a vector of NAs.
median_prob_eff(x, ...)
median_prob_eff(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a numerical vector
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') median_prob_eff(x)
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') median_prob_eff(x)
Get the estimated median toxicity rate at each dose under investigation. This is a set of modelled statistics. The underlying models estimate toxicity probabilities in different ways. If no model-based estimate of the median is available, this function will return a vector of NAs.
median_prob_tox(x, ...)
median_prob_tox(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a numerical vector
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% median_prob_tox()
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% median_prob_tox()
Get the model data-frame for a dose-finding analysis, inlcuding columns for patient id, cohort id, dose administered, and toxicity outcome. In some scenarios, further columns are provided.
model_frame(x, ...)
model_frame(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
tibble
, which acts like a data.frame
.
# In a toxicity-only setting: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% model_frame() # In an efficacy-toxicity setting prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB', supports_efficacy = TRUE) fit %>% model_frame()
# In a toxicity-only setting: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% model_frame() # In an efficacy-toxicity setting prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB', supports_efficacy = TRUE) fit %>% model_frame()
Get the number of patients evaluated at each dose under investigation.
n_at_dose(x, ...)
n_at_dose(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
an integer vector
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% n_at_dose()
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% n_at_dose()
Get the number of patients evaluated at the recommended dose.
n_at_recommended_dose(x, ...)
n_at_recommended_dose(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
an integer
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% n_at_recommended_dose()
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% n_at_recommended_dose()
Number of different possible outcomes for a cohort of patients, each of which will experience one of a number of discrete outcomes. For instance, in a typical phase I dose-finding trial, each patient will experience: no-toxicity (N); or toxicity (T). The number of possible outcomes per patient is two. For a cohort of three patients, the number of cohort outcomes is four: NNN, NNT, NTT, TTT. Consider a more complex example: in a seamless phase I/II trial with efficacy and toxicity outcomes, an individual patient will experience one of four distinct outcomes: efficacy only (E); toxicity only (T); both efficacy and toxicity (B) or neither. How many different outcomes are there for a cohort of three patients? The answer is 20 but it is non-trivial to see why. This convenience function calculates that number using the formula for the number of combinations with replacement,
num_cohort_outcomes(num_patient_outcomes, cohort_size)
num_cohort_outcomes(num_patient_outcomes, cohort_size)
num_patient_outcomes |
integer, number of distinct possible outcomes for each single patient |
cohort_size |
integer, number of patients in the cohort |
integer, number of distinct possible cohort outcomes
# As described in example, N or T in a cohort of three: num_cohort_outcomes(num_patient_outcomes = 2, cohort_size = 3) # Also described in example, E, T, B or N in a cohort of three: num_cohort_outcomes(num_patient_outcomes = 4, cohort_size = 3)
# As described in example, N or T in a cohort of three: num_cohort_outcomes(num_patient_outcomes = 2, cohort_size = 3) # Also described in example, E, T, B or N in a cohort of three: num_cohort_outcomes(num_patient_outcomes = 4, cohort_size = 3)
Number of possible nodes in an exhaustive analysis of dose-paths in a dose-finding trial. The number of nodes at depth i is the the number of nodes at depth i-1 multiplied by the number of possible cohort outcomes at depth i. For instance, if there were 16 nodes at the previous depth and four possible cohort outcomes at the current depth, then there are 64 possible nodes at the current depth. Knowing the number of nodes in a dose-paths analysis helps the analyst decide whether simulation or dose-paths are a better tool for assessing operating characteristics of a dose-finding design.
num_dose_path_nodes(num_patient_outcomes, cohort_sizes)
num_dose_path_nodes(num_patient_outcomes, cohort_sizes)
num_patient_outcomes |
integer, number of distinct possible outcomes for each single patient |
cohort_sizes |
integer vector of cohort sizes |
integer vector, number of nodes at increasing depths. The total number of nodes is the sum of this vector.
# In a 3+3 design, there are two possible outcomes for each patient and # patients are evaluated in cohorts of three. In an analysis of dose-paths in # the first two cohorts of three, how many nodes are there? num_dose_path_nodes(num_patient_outcomes = 2, cohort_sizes = rep(3, 2)) # In contrast, using an EffTox design there are four possible outcomes for # each patient. In a similar analysis of dose-paths in the first two cohorts # of three, how many nodes are there now? num_dose_path_nodes(num_patient_outcomes = 4, cohort_sizes = rep(3, 2))
# In a 3+3 design, there are two possible outcomes for each patient and # patients are evaluated in cohorts of three. In an analysis of dose-paths in # the first two cohorts of three, how many nodes are there? num_dose_path_nodes(num_patient_outcomes = 2, cohort_sizes = rep(3, 2)) # In contrast, using an EffTox design there are four possible outcomes for # each patient. In a similar analysis of dose-paths in the first two cohorts # of three, how many nodes are there now? num_dose_path_nodes(num_patient_outcomes = 4, cohort_sizes = rep(3, 2))
Get the number of doses under investigation in a dose-finding trial.
num_doses(x, ...)
num_doses(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
integer
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% num_doses()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% num_doses()
Get the number of efficacies seen in a dose-finding trial.
num_eff(x, ...)
num_eff(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
integer
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB') num_eff(x)
prob_select = c(0.1, 0.3, 0.5, 0.07, 0.03) model <- get_random_selector(prob_select = prob_select, supports_efficacy = TRUE) x <- model %>% fit('1NTN 2EN 5BB') num_eff(x)
Get the number of patients evaluated in a dose-finding trial.
num_patients(x, ...)
num_patients(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
integer
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% num_patients()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% num_patients()
Get the number of toxicities seen in a dose-finding trial.
num_tox(x, ...)
num_tox(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
integer
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% num_tox()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% num_tox()
Parse a string of phase I/II dose-finding outcomes to a binary vector notation necessary for model invocation.
The outcome string describes the doses given, outcomes observed and groups patients into cohorts. The format of the string is described in Brock et al. (2017). See Examples.
The letters E, T, N and B are used to represents patients that
experienced (E)fficacy only, (T)oxicity only, (B)oth efficacy and toxicity,
and (N)either. These letters are concatenated after numerical dose-levels to
convey the outcomes of cohorts of patients. For instance, 2ETB
represents a cohort of three patients that were treated at dose-level 2, and
experienced efficacy, toxicity and both events, respectively. The results of
cohorts are separated by spaces. Thus, 2ETB 1NN
extends our previous
example, where the next cohort of two were treated at dose-level 1 and both
patients experienced neither efficacy nor toxicity. See Examples.
parse_phase1_2_outcomes(outcomes, as_list = TRUE)
parse_phase1_2_outcomes(outcomes, as_list = TRUE)
outcomes |
character string, conveying doses given and outcomes observed. |
as_list |
TRUE (the default) to return a |
If as_list == TRUE
, a list with elements eff
,
tox
, dose
and num_patients
. If as_list == FALSE
,
a data.frame with columns eff
, tox
and dose
.
Brock, K., Billingham, L., Copland, M., Siddique, S., Sirovica, M., & Yap, C. (2017). Implementing the EffTox dose-finding design in the Matchpoint trial. BMC Medical Research Methodology, 17(1), 112. https://doi.org/10.1186/s12874-017-0381-x
x = parse_phase1_2_outcomes('1NNE 2EEN 3TBB') # Three cohorts of three patients. The first cohort was treated at dose 1 and # had no toxicity with one efficacy, etc. x$num_patients # 9 x$dose # c(1, 1, 1, 2, 2, 2, 3, 3, 3) x$eff # c(0, 0, 1, 1, 1, 0, 0, 1, 1) sum(x$eff) # 5 x$tox # c(0, 0, 0, 0, 0, 0, 1, 1, 1) sum(x$tox) # 3 # The same information can be parsed to a data-frame: y = parse_phase1_2_outcomes('1NNE 2EEN 3TBB', as_list = FALSE) y
x = parse_phase1_2_outcomes('1NNE 2EEN 3TBB') # Three cohorts of three patients. The first cohort was treated at dose 1 and # had no toxicity with one efficacy, etc. x$num_patients # 9 x$dose # c(1, 1, 1, 2, 2, 2, 3, 3, 3) x$eff # c(0, 0, 1, 1, 1, 0, 0, 1, 1) sum(x$eff) # 5 x$tox # c(0, 0, 0, 0, 0, 0, 1, 1, 1) sum(x$tox) # 3 # The same information can be parsed to a data-frame: y = parse_phase1_2_outcomes('1NNE 2EEN 3TBB', as_list = FALSE) y
Parse a string of phase I dose-finding outcomes to a binary vector notation necessary for model invocation.
The outcome string describes the doses given, outcomes observed and groups patients into cohorts. The format of the string is described in Brock (2019), and that itself is the phase I analogue of the similar idea described in Brock et al. (2017). See Examples.
The letters T and N are used to represents patients that experienced
(T)oxicity and (N)o toxicity. These letters are concatenated after numerical
dose-levels to convey the outcomes of cohorts of patients.
For instance, 2NNT
represents a cohort of three patients that were
treated at dose-level 2, one of whom experienced toxicity, and two that did
not. The results of cohorts are separated by spaces. Thus, 2NNT 1NN
extends our previous example, where the next cohort of two were treated at
dose-level 1 and neither experienced toxicity. See examples.
parse_phase1_outcomes(outcomes, as_list = TRUE)
parse_phase1_outcomes(outcomes, as_list = TRUE)
outcomes |
character string, conveying doses given and outcomes observed. |
as_list |
TRUE (the default) to return a |
If as_list == TRUE
, a list with elements tox
,
doses
and num_patients
. If as_list == FALSE
, a
data.frame with columns tox
and doses
.
Brock, K. (2019). trialr: Bayesian Clinical Trial Designs in R and Stan. arXiv:1907.00161 [stat.CO]
Brock, K., Billingham, L., Copland, M., Siddique, S., Sirovica, M., & Yap, C. (2017). Implementing the EffTox dose-finding design in the Matchpoint trial. BMC Medical Research Methodology, 17(1), 112. https://doi.org/10.1186/s12874-017-0381-x
x = parse_phase1_outcomes('1NNN 2NTN 3TTT') # Three cohorts of three patients. The first cohort was treated at dose 1 and # none had toxicity. The second cohort was treated at dose 2 and one of the # three had toxicity. Finally, cohort three was treated at dose 3 and all # patients had toxicity. x$num_patients # 9 x$doses # c(1, 1, 1, 2, 2, 2, 3, 3, 3) x$tox # c(0, 0, 0, 0, 1, 0, 1, 1, 1) sum(x$tox) # 4 # The same information can be parsed to a data-frame: y = parse_phase1_outcomes('1NNN 2NTN 3TTT', as_list = FALSE) y
x = parse_phase1_outcomes('1NNN 2NTN 3TTT') # Three cohorts of three patients. The first cohort was treated at dose 1 and # none had toxicity. The second cohort was treated at dose 2 and one of the # three had toxicity. Finally, cohort three was treated at dose 3 and all # patients had toxicity. x$num_patients # 9 x$doses # c(1, 1, 1, 2, 2, 2, 3, 3, 3) x$tox # c(0, 0, 0, 0, 1, 0, 1, 1, 1) sum(x$tox) # 4 # The same information can be parsed to a data-frame: y = parse_phase1_outcomes('1NNN 2NTN 3TTT', as_list = FALSE) y
Class to house the latent random variables that govern toxicity and efficacy events in patients. Instances of this class can be used in simulation-like tasks to effectively use the same simulated individuals in different designs, thus supporting reduced Monte Carlo error and more efficient comparison.
num_patients
('integer(1)')
tox_u
('numeric(num_patients)')
time_to_tox_func
('function')
tox_time
('numeric(num_patients)')
eff_u
('numeric(num_patients)')
time_to_eff_func
('function')
eff_time
('numeric(num_patients)')
can_grow
('logical(1)')
new()
Creator.
PatientSample$new( num_patients = 0, time_to_tox_func = function() runif(n = 1), time_to_eff_func = function() runif(n = 1) )
num_patients
('integer(1)') Number of patients.
time_to_tox_func
('function') function taking no args that returns a single time of toxicity, given that toxicity occurs.
time_to_eff_func
('function') function taking no args that returns a single time of efficacy, given that efficacy occurs.
[PatientSample].
set_eff_and_tox()
Set the toxicity and efficacy latent variables that govern occurrence of
toxicity and efficacy events. By default, instances of this class
automatically grow these latent variables to accommodate arbitrarily high
sample sizes. However, when you set these latent variables manually via
this function, you override the ability of the class to self-manage, so
its ability to grow is turned off by setting the internal variable
self$can_grow <- FALSE
.
PatientSample$set_eff_and_tox( tox_u, eff_u, tox_time = rep(0, length(tox_u)), eff_time = rep(0, length(eff_u)) )
tox_u
('numeric()') Patient-level toxicity propensities.
eff_u
('numeric()') Patient-level efficacy propensities.
tox_time
('numeric()') Patient-level toxicity times, given that toxicity occurs.
eff_time
('numeric()') Patient-level efficacy times, given that efficacy occurs.
expand_to()
Expand sample to size at least num_patients
PatientSample$expand_to(num_patients)
num_patients
('integer(1)').
get_tox_u()
Get toxicity latent variable for patient i
PatientSample$get_tox_u(i)
i
('integer(1)') patient index
get_patient_tox()
Get 0 or 1 event marker for whether toxicity occurred in patient i
PatientSample$get_patient_tox(i, prob_tox, time = Inf)
i
('integer(1)') patient index
prob_tox
('numeric(1)') probability of toxicity
time
('numeric(1)') at time
get_eff_u()
Get efficacy latent variable for patient i
PatientSample$get_eff_u(i)
i
('integer(1)') patient index
get_patient_eff()
Get 0 or 1 event marker for whether efficacy occurred in patient i
PatientSample$get_patient_eff(i, prob_eff, time = Inf)
i
('integer(1)') patient index
prob_eff
('numeric(1)') probability of efficacy
time
('numeric(1)') at time
clone()
The objects of this class are cloneable with this method.
PatientSample$clone(deep = FALSE)
deep
Whether to make a deep clone.
Sweeting, M., Slade, D., Jackson, D., & Brock, K. (2024). Potential outcome simulation for efficient head-to-head comparison of adaptive dose-finding designs. arXiv preprint arXiv:2402.15460
Break a phase I/II outcome string into a list of cohort parts.
Break a phase I/II outcome string into a list of cohort parts.
The outcome string describes the doses given, outcomes observed and the timing of analyses that recommend a dose. The format of the string is described in Brock _et al_. (2017).
The letters E, T, N & B are used to represents patients that experienced
(E)fficacy, (T)oxicity, (N)either and (B)oth. These letters are concatenated
after numerical dose-levels to convey the outcomes of cohorts of patients.
For instance, 2NET
represents a cohort of three patients that were
treated at dose-level 2, one of whom experienced toxicity only, one that
experienced efficacy only, and one that had neither.
The results of cohorts are separated by spaces and it is assumed that a
dose-finding decision takes place at the end of a cohort. Thus,
2NET 1NN
builds on our previous example, where the next cohort of two
were treated at dose-level 1 and neither of these patients experienced
either event See examples.
phase1_2_outcomes_to_cohorts(outcomes)
phase1_2_outcomes_to_cohorts(outcomes)
outcomes |
character string representing the doses given, outcomes observed, and timing of analyses. See Description. |
a list with a slot for each cohort. Each cohort slot is itself a
list, containing elements:
* dose
, the integer dose delivered to the cohort;
* outcomes
, a character string representing the E
, T
N
or B
outcomes for the patients in this cohort.
Brock, K., Billingham, L., Copland, M., Siddique, S., Sirovica, M., & Yap, C. (2017). Implementing the EffTox dose-finding design in the Matchpoint trial. BMC Medical Research Methodology, 17(1), 112. https://doi.org/10.1186/s12874-017-0381-x
x = phase1_2_outcomes_to_cohorts('1NEN 2ENT 3TB') length(x) x[[1]]$dose x[[1]]$outcomes x[[2]]$dose x[[2]]$outcomes x[[3]]$dose x[[3]]$outcomes
x = phase1_2_outcomes_to_cohorts('1NEN 2ENT 3TB') length(x) x[[1]]$dose x[[1]]$outcomes x[[2]]$dose x[[2]]$outcomes x[[3]]$dose x[[3]]$outcomes
Break a phase I outcome string into a list of cohort parts.
Break a phase I outcome string into a list of cohort parts.
The outcome string describes the doses given, outcomes observed and the timing of analyses that recommend a dose. The format of the string is described in Brock (2019), and that itself is the phase I analogue of the similar idea described in Brock _et al_. (2017).
The letters T and N are used to represents patients that experienced
(T)oxicity and (N)o toxicity. These letters are concatenated after numerical
dose-levels to convey the outcomes of cohorts of patients.
For instance, 2NNT
represents a cohort of three patients that were
treated at dose-level 2, one of whom experienced toxicity, and two that did
not. The results of cohorts are separated by spaces and it is assumed that a
dose-finding decision takes place at the end of a cohort. Thus,
2NNT 1NN
builds on our previous example, where the next cohort of two
were treated at dose-level 1 and neither of these patients experienced
toxicity. See examples.
phase1_outcomes_to_cohorts(outcomes)
phase1_outcomes_to_cohorts(outcomes)
outcomes |
character string representing the doses given, outcomes observed, and timing of analyses. See Description. |
a list with a slot for each cohort. Each cohort slot is itself a
list, containing elements:
* dose
, the integer dose delivered to the cohort;
* outcomes
, a character string representing the T
or N
outcomes for the patients in this cohort.
Brock, K. (2019). trialr: Bayesian Clinical Trial Designs in R and Stan. arXiv:1907.00161 [stat.CO]
Brock, K., Billingham, L., Copland, M., Siddique, S., Sirovica, M., & Yap, C. (2017). Implementing the EffTox dose-finding design in the Matchpoint trial. BMC Medical Research Methodology, 17(1), 112. https://doi.org/10.1186/s12874-017-0381-x
x = phase1_outcomes_to_cohorts('1NNN 2NNT 3TT') length(x) x[[1]]$dose x[[1]]$outcomes x[[2]]$dose x[[2]]$outcomes x[[3]]$dose x[[3]]$outcomes
x = phase1_outcomes_to_cohorts('1NNN 2NNT 3TT') length(x) x[[1]]$dose x[[1]]$outcomes x[[2]]$dose x[[2]]$outcomes x[[3]]$dose x[[3]]$outcomes
Get the percentage of patients evaluated at each dose under investigation.
prob_administer(x, ...)
prob_administer(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a numerical vector
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% prob_administer()
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% prob_administer()
Get the estimated quantile of the efficacy rate at each dose under investigation. This is a set of modelled statistics. The underlying models estimate efficacy probabilities in different ways. If no model-based estimate of the median is available, this function will return a vector of NAs.
prob_eff_quantile(x, p, ...)
prob_eff_quantile(x, p, ...)
x |
Object of class |
p |
quantile probability, decimal value between 0 and 1 |
... |
arguments passed to other methods |
a numerical vector
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') prob_tox_quantile(x, p = 0.9)
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') prob_tox_quantile(x, p = 0.9)
Get the probabilities that each of the doses under investigation is recommended.
prob_recommend(x, ...)
prob_recommend(x, ...)
x |
Object of type |
... |
arguments passed to other methods |
vector of probabilities
true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 50, true_prob_tox = true_prob_tox) sims %>% prob_recommend
true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 50, true_prob_tox = true_prob_tox) sims %>% prob_recommend
Get the probability that the toxicity rate at each dose exceeds some threshold.
Get the probability that the efficacy rate at each dose exceeds some threshold.
prob_tox_exceeds(x, threshold, ...) prob_eff_exceeds(x, threshold, ...)
prob_tox_exceeds(x, threshold, ...) prob_eff_exceeds(x, threshold, ...)
x |
Object of type |
threshold |
Probability that efficacy rate exceeds what? |
... |
arguments passed to other methods |
numerical vector of probabilities
numerical vector of probabilities
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) # What is probability that tox rate at each dose exceeds target by >= 10%? fit %>% prob_tox_exceeds(threshold = target + 0.1) efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') prob_tox_exceeds(x, threshold = 0.45)
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) # What is probability that tox rate at each dose exceeds target by >= 10%? fit %>% prob_tox_exceeds(threshold = target + 0.1) efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') prob_tox_exceeds(x, threshold = 0.45)
Get the estimated quantile of the toxicity rate at each dose under investigation. This is a set of modelled statistics. The underlying models estimate toxicity probabilities in different ways. If no model-based estimate of the median is available, this function will return a vector of NAs.
prob_tox_quantile(x, p, ...)
prob_tox_quantile(x, p, ...)
x |
Object of class |
p |
quantile probability, decimal value between 0 and 1 |
... |
arguments passed to other methods |
a numerical vector
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% prob_tox_quantile(p = 0.9)
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% prob_tox_quantile(p = 0.9)
Get samples of the probability of toxicity. For instance, a Bayesian approach
that supports sampling would be expected to return posterior samples of the
probability of toxicity. If this class does not support sampling, this
function will raise an error. You can check whether this class supports
sampling by calling supports_sampling
.
Get samples of the probability of efficacy For instance, a Bayesian approach
that supports sampling would be expected to return posterior samples of the
probability of toxicity. If this class does not support sampling, this
function will raise an error. You can check whether this class supports
sampling by calling supports_sampling
.
prob_tox_samples(x, tall = FALSE, ...) prob_eff_samples(x, tall = FALSE, ...)
prob_tox_samples(x, tall = FALSE, ...) prob_eff_samples(x, tall = FALSE, ...)
x |
Object of type |
tall |
logical, if FALSE, a wide data-frame is returned with columns
pertaining to the doses and column names the dose indices.
If TRUE, a tall data-frame is returned with data for all doses stacked
vertically. In this mode, column names will include |
... |
arguments passed to other methods |
data-frame like object
data-frame like object
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% prob_tox_samples() fit %>% prob_tox_samples(tall = TRUE) efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') prob_tox_samples(x, tall = TRUE)
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% prob_tox_samples() fit %>% prob_tox_samples(tall = TRUE) efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') prob_tox_samples(x, tall = TRUE)
Get the dose recommended for the next patient or cohort in a dose-finding trial.
recommended_dose(x, ...)
recommended_dose(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
integer
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% recommended_dose()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% recommended_dose()
This method selects dose by the algorithm for identifying the maximum
tolerable dose (MTD) described in Yan et al. (2019). This class is intended
to be used when a BOIN trial has reached its maximum sample size. Thus, it
intends to make the final dose recommendation after the regular BOIN dose
selection algorithm, as implemented by get_boin
, including any
additional behaviours that govern stopping (etc), has gracefully concluded a
dose-finding trial. However, the class can be used in any scenario where
there is a target toxicity rate. See Examples. Note - this class will not
override the parent dose selector when the parent is advocating no dose. Thus
this class will not reinstate a dangerous dose.
select_boin_mtd( parent_selector_factory, when = c("finally", "always"), target = NULL, ... )
select_boin_mtd( parent_selector_factory, when = c("finally", "always"), target = NULL, ... )
parent_selector_factory |
Object of type |
when |
Either of: 'finally' to select dose only when the parent dose-selector has finished, by returning continue() == FALSE; or 'always' to use this dose-selection algorithm for every dose decision. As per the authors' original intentions, the default is 'finally'. |
target |
We seek a dose with this probability of toxicity. If not provided, the value will be sought from the parent dose-selector. |
... |
Extra args are passed to |
an object of type selector_factory
.
Yan, F., Pan, H., Zhang, L., Liu, S., & Yuan, Y. (2019). BOIN: An R Package for Designing Single-Agent and Drug-Combination Dose-Finding Trials Using Bayesian Optimal Interval Designs. Journal of Statistical Software, 27(November 2017), 0–35. https://doi.org/10.18637/jss.v000.i00
# This class is intended to make the final dose selection in a BOIN trial: target <- 0.25 model <- get_boin(num_doses = 5, target = target) %>% stop_at_n(n = 12) %>% select_boin_mtd() outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision if we wanted: model2 <- get_boin(num_doses = 5, target = target) %>% select_boin_mtd(when = 'always') model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose() # and with any underlying model: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_boin_mtd(when = 'always') model3 %>% fit('1NNT') %>% recommended_dose() model3 %>% fit('1NNN 2NNT') %>% recommended_dose()
# This class is intended to make the final dose selection in a BOIN trial: target <- 0.25 model <- get_boin(num_doses = 5, target = target) %>% stop_at_n(n = 12) %>% select_boin_mtd() outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision if we wanted: model2 <- get_boin(num_doses = 5, target = target) %>% select_boin_mtd(when = 'always') model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose() # and with any underlying model: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_boin_mtd(when = 'always') model3 %>% fit('1NNT') %>% recommended_dose() model3 %>% fit('1NNN 2NNT') %>% recommended_dose()
This method selects dose by the algorithm for identifying the optimal
biological dose (OBD) described in Lin et al. (2020). This class is intended
to be used when a BOIN12 trial has reached its maximum sample size. Thus, it
intends to make the final dose recommendation after the regular BOIN12 dose
selection algorithm, as implemented by get_boin12
, has
gracefully concluded a dose-finding trial. However, the class can be used in
any scenario where there is a limit toxicity rate. See Examples.
Note - this class will not override the parent dose selector when the parent
is advocating no dose. Thus this class will not reinstate a dangerous dose.
select_boin12_obd( parent_selector_factory, when = c("finally", "always"), tox_limit = NULL, ... )
select_boin12_obd( parent_selector_factory, when = c("finally", "always"), tox_limit = NULL, ... )
parent_selector_factory |
Object of type |
when |
Either of: 'finally' to select dose only when the parent dose-selector has finished, by returning continue() == FALSE; or 'always' to use this dose-selection algorithm for every dose decision. As per the authors' original intentions, the default is 'finally'. |
tox_limit |
We seek a dose with toxicity probability no greater than. If not provided, the value will be sought from the parent dose-selector. |
... |
Extra args are ignored. |
an object of type selector_factory
.
Lin, R., Zhou, Y., Yan, F., Li, D., & Yuan, Y. (2020). BOIN12: Bayesian optimal interval phase I/II trial design for utility-based dose finding in immunotherapy and targeted therapies. JCO precision oncology, 4, 1393-1402.
# This class is intended to make the final dose selection in a BOIN12 trial: tox_limit <- 0.35 model <- get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, n_star = 6) %>% stop_at_n(n = 12) %>% select_boin12_obd() outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision: model2 <- get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, n_star = 6) %>% select_boin12_obd(when = 'always') model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose()
# This class is intended to make the final dose selection in a BOIN12 trial: tox_limit <- 0.35 model <- get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, n_star = 6) %>% stop_at_n(n = 12) %>% select_boin12_obd() outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision: model2 <- get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, n_star = 6) %>% select_boin12_obd(when = 'always') model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose()
This method selects dose by the convex infinite bounds penalisation (CIBP) criterion of Mozgunov & Jaki. Their method is mindful of the uncertainty in the estimates of the probability of toxicity and uses an asymmetry parameter to penalise escalation to risky doses.
select_dose_by_cibp(parent_selector_factory, a, target = NULL)
select_dose_by_cibp(parent_selector_factory, a, target = NULL)
parent_selector_factory |
Object of type |
a |
Number between 0 and 2, the asymmetry parameter. See References. |
target |
We seek a dose with this probability of toxicity. If not provided, the value will be sought from the parent dose-selector. |
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
Mozgunov P, Jaki T. Improving safety of the continual reassessment method via a modified allocation rule. Statistics in Medicine.1-17. doi:10.1002/sim.8450
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.33 # Let's compare escalation behaviour of a CRM model without CIBP criterion: model1 <- get_dfcrm(skeleton = skeleton, target = target) # To one with the CIBP criterion: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_dose_by_cibp(a = 0.3) # Despite one-in-three tox at first dose, regular model is ready to escalate: model1 %>% fit('1NTN') %>% recommended_dose() # But the model using CIBP is more risk averse: model2 %>% fit('1NTN') %>% recommended_dose()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.33 # Let's compare escalation behaviour of a CRM model without CIBP criterion: model1 <- get_dfcrm(skeleton = skeleton, target = target) # To one with the CIBP criterion: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_dose_by_cibp(a = 0.3) # Despite one-in-three tox at first dose, regular model is ready to escalate: model1 %>% fit('1NTN') %>% recommended_dose() # But the model using CIBP is more risk averse: model2 %>% fit('1NTN') %>% recommended_dose()
This method selects dose by the algorithm for identifying the maximum
tolerable dose (MTD) described in Ji et al. (2010). This class is intended
to be used when a mTPI trial has reached its maximum sample size. Thus, it
intends to make the final dose recommendation after the regular mTPI dose
selection algorithm, as implemented by get_mtpi
, including any
additional behaviours that govern stopping (etc), has gracefully concluded a
dose-finding trial. However, the class can be used in any scenario where
there is a target toxicity rate. See Examples. Note - this class will not
override the parent dose selector when the parent is advocating no dose. Thus
this class will not reinstate a dangerous dose.
select_mtpi_mtd( parent_selector_factory, when = c("finally", "always"), target = NULL, exclusion_certainty, alpha = 1, beta = 1, ... )
select_mtpi_mtd( parent_selector_factory, when = c("finally", "always"), target = NULL, exclusion_certainty, alpha = 1, beta = 1, ... )
parent_selector_factory |
Object of type |
when |
Either of: 'finally' to select dose only when the parent dose-selector has finished, by returning continue() == FALSE; or 'always' to use this dose-selection algorithm for every dose decision. As per the authors' original intentions, the default is 'finally'. |
target |
We seek a dose with this probability of toxicity. If not provided, the value will be sought from the parent dose-selector. |
exclusion_certainty |
Numeric, threshold posterior certainty required to exclude a dose for being excessively toxic. The authors discuss values in the range 0.7 - 0.95. Set to a value > 1 to suppress the dose exclusion mechanism. The authors use the Greek letter xi for this parameter. |
alpha |
First shape parameter of the beta prior distribution on the probability of toxicity. |
beta |
Second shape parameter of the beta prior distribution on the probability of toxicity. |
... |
Extra args are passed onwards. |
an object of type selector_factory
.
Ji, Y., Liu, P., Li, Y., & Bekele, B. N. (2010). A modified toxicity probability interval method for dose-finding trials. Clinical Trials, 7(6), 653-663. https://doi.org/10.1177/1740774510382799
Ji, Y., & Yang, S. (2017). On the Interval-Based Dose-Finding Designs, 1-26. Retrieved from https://arxiv.org/abs/1706.03277
# This class is intended to make the final dose selection in a mTPI trial: target <- 0.25 model <- get_mtpi(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) %>% stop_at_n(n = 12) %>% select_mtpi_mtd(exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision if we wanted: model2 <- get_mtpi(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) %>% select_mtpi_mtd(when = 'always', exclusion_certainty = 0.95) model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose() # and with any underlying model: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_mtpi_mtd(when = 'always', exclusion_certainty = 0.95) model3 %>% fit('1NNT') %>% recommended_dose() model3 %>% fit('1NNN 2NNT') %>% recommended_dose()
# This class is intended to make the final dose selection in a mTPI trial: target <- 0.25 model <- get_mtpi(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) %>% stop_at_n(n = 12) %>% select_mtpi_mtd(exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision if we wanted: model2 <- get_mtpi(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) %>% select_mtpi_mtd(when = 'always', exclusion_certainty = 0.95) model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose() # and with any underlying model: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_mtpi_mtd(when = 'always', exclusion_certainty = 0.95) model3 %>% fit('1NNT') %>% recommended_dose() model3 %>% fit('1NNN 2NNT') %>% recommended_dose()
This method selects dose by the algorithm for identifying the maximum
tolerable dose (MTD) described in Guo et al. (2017). This class is intended
to be used when a mTPI2 trial has reached its maximum sample size. Thus, it
intends to make the final dose recommendation after the regular mTPI2 dose
selection algorithm, as implemented by get_mtpi2
, including any
additional behaviours that govern stopping (etc), has gracefully concluded a
dose-finding trial. However, the class can be used in any scenario where
there is a target toxicity rate. See Examples. Note - this class will not
override the parent dose selector when the parent is advocating no dose. Thus
this class will not reinstate a dangerous dose.
select_mtpi2_mtd( parent_selector_factory, when = c("finally", "always"), target = NULL, exclusion_certainty, alpha = 1, beta = 1, ... )
select_mtpi2_mtd( parent_selector_factory, when = c("finally", "always"), target = NULL, exclusion_certainty, alpha = 1, beta = 1, ... )
parent_selector_factory |
Object of type |
when |
Either of: 'finally' to select dose only when the parent dose-selector has finished, by returning continue() == FALSE; or 'always' to use this dose-selection algorithm for every dose decision. As per the authors' original intentions, the default is 'finally'. |
target |
We seek a dose with this probability of toxicity. If not provided, the value will be sought from the parent dose-selector. |
exclusion_certainty |
Numeric, threshold posterior certainty required to exclude a dose for being excessively toxic. The authors discuss values in the range 0.7 - 0.95. Set to a value > 1 to suppress the dose exclusion mechanism. The authors use the Greek letter xi for this parameter. |
alpha |
First shape parameter of the beta prior distribution on the probability of toxicity. |
beta |
Second shape parameter of the beta prior distribution on the probability of toxicity. |
... |
Extra args are passed onwards. |
an object of type selector_factory
.
Guo, W., Wang, SJ., Yang, S., Lynn, H., Ji, Y. (2017). A Bayesian Interval Dose-Finding Design Addressing Ockham's Razor: mTPI-2. https://doi.org/10.1016/j.cct.2017.04.006
# This class is intended to make the final dose selection in a mTPI2 trial: target <- 0.25 model <- get_mtpi2(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) %>% stop_at_n(n = 12) %>% select_mtpi2_mtd(exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision if we wanted: model2 <- get_mtpi2(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) %>% select_mtpi2_mtd(when = 'always', exclusion_certainty = 0.95) model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose() # and with any underlying model: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_mtpi2_mtd(when = 'always', exclusion_certainty = 0.95) model3 %>% fit('1NNT') %>% recommended_dose() model3 %>% fit('1NNN 2NNT') %>% recommended_dose()
# This class is intended to make the final dose selection in a mTPI2 trial: target <- 0.25 model <- get_mtpi2(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) %>% stop_at_n(n = 12) %>% select_mtpi2_mtd(exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision if we wanted: model2 <- get_mtpi2(num_doses = 5, target = target, epsilon1 = 0.05, epsilon2 = 0.05, exclusion_certainty = 0.95) %>% select_mtpi2_mtd(when = 'always', exclusion_certainty = 0.95) model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose() # and with any underlying model: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_mtpi2_mtd(when = 'always', exclusion_certainty = 0.95) model3 %>% fit('1NNT') %>% recommended_dose() model3 %>% fit('1NNN 2NNT') %>% recommended_dose()
This method selects dose by the algorithm for identifying the maximum
tolerable dose (MTD) described in Ji et al. (2007). This class is intended
to be used when a TPI trial has reached its maximum sample size. Thus, it
intends to make the final dose recommendation after the regular TPI dose
selection algorithm, as implemented by get_tpi
, including any
additional behaviours that govern stopping (etc), has gracefully concluded a
dose-finding trial. However, the class can be used in any scenario where
there is a target toxicity rate. See Examples. Note - this class will not
override the parent dose selector when the parent is advocating no dose. Thus
this class will not reinstate a dangerous dose.
select_tpi_mtd( parent_selector_factory, when = c("finally", "always"), target = NULL, exclusion_certainty, alpha = 1, beta = 1, ... )
select_tpi_mtd( parent_selector_factory, when = c("finally", "always"), target = NULL, exclusion_certainty, alpha = 1, beta = 1, ... )
parent_selector_factory |
Object of type |
when |
Either of: 'finally' to select dose only when the parent dose-selector has finished, by returning continue() == FALSE; or 'always' to use this dose-selection algorithm for every dose decision. As per the authors' original intentions, the default is 'finally'. |
target |
We seek a dose with this probability of toxicity. If not provided, the value will be sought from the parent dose-selector. |
exclusion_certainty |
Numeric, threshold posterior certainty required to exclude a dose for being excessively toxic. The authors discuss values in the range 0.7 - 0.95. Set to a value > 1 to suppress the dose exclusion mechanism. The authors use the Greek letter xi for this parameter. |
alpha |
First shape parameter of the beta prior distribution on the probability of toxicity. |
beta |
Second shape parameter of the beta prior distribution on the probability of toxicity. |
... |
Extra args are passed onwards. |
an object of type selector_factory
.
Ji, Y., Li, Y., & Bekele, B. N. (2007). Dose-finding in phase I clinical trials based on toxicity probability intervals. Clinical Trials, 4(3), 235–244. https://doi.org/10.1177/1740774507079442
# This class is intended to make the final dose selection in a mTPI2 trial: target <- 0.25 model <- get_tpi(num_doses = 5, target = target, k1 = 1, k2 = 1.5, exclusion_certainty = 0.95) %>% stop_at_n(n = 12) %>% select_tpi_mtd(exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision if we wanted: model2 <- get_tpi(num_doses = 5, target = target, k1 = 1, k2 = 1.5, exclusion_certainty = 0.95) %>% select_tpi_mtd(when = 'always', exclusion_certainty = 0.95) model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose() # and with any underlying model: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_tpi_mtd(when = 'always', exclusion_certainty = 0.95) model3 %>% fit('1NNT') %>% recommended_dose() model3 %>% fit('1NNN 2NNT') %>% recommended_dose()
# This class is intended to make the final dose selection in a mTPI2 trial: target <- 0.25 model <- get_tpi(num_doses = 5, target = target, k1 = 1, k2 = 1.5, exclusion_certainty = 0.95) %>% stop_at_n(n = 12) %>% select_tpi_mtd(exclusion_certainty = 0.95) outcomes <- '1NNN 2NTN 2NNN 3NTT' model %>% fit(outcomes) %>% recommended_dose() # However, since behaviour is modular in this package, we can use this method # to select dose at every dose decision if we wanted: model2 <- get_tpi(num_doses = 5, target = target, k1 = 1, k2 = 1.5, exclusion_certainty = 0.95) %>% select_tpi_mtd(when = 'always', exclusion_certainty = 0.95) model2 %>% fit('1NNT') %>% recommended_dose() model2 %>% fit('1NNN 2NNT') %>% recommended_dose() # and with any underlying model: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% select_tpi_mtd(when = 'always', exclusion_certainty = 0.95) model3 %>% fit('1NNT') %>% recommended_dose() model3 %>% fit('1NNN 2NNT') %>% recommended_dose()
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()
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() fit1 %>% continue() fit2 %>% recommended_dose() fit2 %>% continue() fit3 %>% recommended_dose() fit3 %>% continue() # 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() fit1 %>% continue() fit2 %>% recommended_dose() fit2 %>% continue() # 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() fit4 %>% continue() # Full selector interface: fit <- fit2 fit %>% tox_target() fit %>% num_patients() fit %>% cohort() fit %>% doses_given() fit %>% tox() fit %>% weight() fit %>% num_tox() fit %>% model_frame() fit %>% num_doses() fit %>% dose_indices() fit %>% recommended_dose() fit %>% continue() fit %>% n_at_dose() fit %>% n_at_recommended_dose() fit %>% is_randomising() fit %>% prob_administer() fit %>% tox_at_dose() fit %>% empiric_tox_rate() fit %>% mean_prob_tox() fit %>% median_prob_tox() fit %>% dose_admissible() fit %>% prob_tox_quantile(0.9) fit %>% prob_tox_exceeds(0.5) fit %>% supports_sampling() fit %>% prob_tox_samples()
# 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() fit1 %>% continue() fit2 %>% recommended_dose() fit2 %>% continue() fit3 %>% recommended_dose() fit3 %>% continue() # 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() fit1 %>% continue() fit2 %>% recommended_dose() fit2 %>% continue() # 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() fit4 %>% continue() # Full selector interface: fit <- fit2 fit %>% tox_target() fit %>% num_patients() fit %>% cohort() fit %>% doses_given() fit %>% tox() fit %>% weight() fit %>% num_tox() fit %>% model_frame() fit %>% num_doses() fit %>% dose_indices() fit %>% recommended_dose() fit %>% continue() fit %>% n_at_dose() fit %>% n_at_recommended_dose() fit %>% is_randomising() fit %>% prob_administer() fit %>% tox_at_dose() fit %>% empiric_tox_rate() fit %>% mean_prob_tox() fit %>% median_prob_tox() fit %>% dose_admissible() fit %>% prob_tox_quantile(0.9) fit %>% prob_tox_exceeds(0.5) fit %>% supports_sampling() fit %>% prob_tox_samples()
Along with selector
, this is the second core class in the
escalation
package. It exists to do one thing: fit outcomes from
dose-finding trials to the models we use to select doses.
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
.
selector
objects are obtained by calling the fit
function on a selector_factory
object. Refer to examples to see
how this works.
selector_factory()
selector_factory()
# 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() fit1 %>% continue() fit2 %>% recommended_dose() fit2 %>% continue() fit3 %>% recommended_dose() fit3 %>% continue() # 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() fit1 %>% continue() fit2 %>% recommended_dose() fit2 %>% continue() # 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() fit4 %>% continue()
# 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() fit1 %>% continue() fit2 %>% recommended_dose() fit2 %>% continue() fit3 %>% recommended_dose() fit3 %>% continue() # 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() fit1 %>% continue() fit2 %>% recommended_dose() fit2 %>% continue() # 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() fit4 %>% continue()
This function takes a list of several
selector_factory
s, such as those returned by
get_dfcrm
, get_boin
or
get_three_plus_three
, and conducts many notional clinical
trials. The simulated patients in the trials are common across designs. For
example, in a comparison of the three designs mentioned above, the first
simulated CRM trial uses the same notional patients as the first simulated
BOIN trial, etc. Using common patients within iterate across designs
reduces MCMC errors of comparisons, so this method is efficient for
comparing designs. See Sweeting et al. for full details.
simulate_compare( designs, num_sims, true_prob_tox, true_prob_eff = NULL, patient_samples = NULL, rho = NULL, return_patient_samples = FALSE, ... )
simulate_compare( designs, num_sims, true_prob_tox, true_prob_eff = NULL, patient_samples = NULL, rho = NULL, return_patient_samples = FALSE, ... )
designs |
list, mapping design names to objects of type
|
num_sims |
integer, number of trial iterations to simulate. |
true_prob_tox |
numeric vector of true but unknown toxicity probabilities |
true_prob_eff |
numeric vector of true but unknown efficacy probabilities. NULL if efficacy not analysed. |
patient_samples |
Optional list of length |
rho |
Optional correlation between -1 and 1 for the latent uniform variables that determine toxicity and efficacy events. Non-correlated events is the default. |
return_patient_samples |
TRUE to get the list of patient sample objects returned in the patient_samples attribute of the retured object. |
... |
Extra args are passed onwards. |
By default, dose decisions in simulated trials are made after each
cohort of 3 patients. This can be changed by providing a function by the
sample_patient_arrivals
parameter that simulates the arrival of new
patients. The new patients will be added to the existing patients and the
model will be fit to the set of all patients. The function that simulates
patient arrivals should take as a single parameter a data-frame with one
row for each existing patient and columns including cohort, patient, dose,
tox, time (and possibly also eff and weight, if a phase I/II or
time-to-event method is used). The provision of data on the existing
patients allows the patient sampling function to be adaptive. The function
should return a data-frame with a row for each new patient and a column for
time_delta, the time between the arrival of this patient and the previous,
as in cohorts_of_n
. See Examples.
This method can simulate the culmination of trials that are partly
completed. We just have to specify the outcomes already observed via the
previous_outcomes
parameter. Each simulated trial will commence from
those outcomes seen thus far. See Examples.
We can specify the immediate next dose by specifying next_dose
. If
omitted, the next dose is calculated by invoking the model on the outcomes
seen thus far.
Designs must eventually choose to stop the trial. Some designs, like 3+3,
have intrinsic stopping rules. However, some selectors like those derived
from get_dfcrm
offer no default stopping method. You may need
to append stopping behaviour to your selector via something like
stop_at_n
or stop_when_n_at_dose
, etc. To
safeguard against simulating runaway trials that never end, the function
will halt a simulated trial after 30 invocations of the dose-selection
decision. To breach this limit, specify i_like_big_trials = TRUE
in
the function call. However, when you forego the safety net, the onus is on
you to write selectors that will eventually stop the trial! See Examples.
The model is fit to the prevailing data at each dose selection point. By
default, only the final model fit for each simulated trial is retained.
This is done to conserve memory. With a high number of simulated trials,
storing many model fits per trial may cause the executing machine to run
out of memory. However, you can force this method to retain all model fits
by specifying return_all_fits = TRUE
. See Examples.
object of type simulations_collection
Sweeting, M., Slade, D., Jackson, D., & Brock, K. (2024). Potential outcome simulation for efficient head-to-head comparison of adaptive dose-finding designs. arXiv preprint arXiv:2402.15460
## Not run: # Don't run on build because they exceed CRAN time limit # In a five-dose scenario, we have assumed probabilities for Prob(tox): true_prob_tox <- c(0.05, 0.10, 0.15, 0.18, 0.45) # and Prob(eff): true_prob_eff <- c(0.40, 0.50, 0.52, 0.53, 0.53) # Let us compare two BOIN12 variants that differ in their stopping params: designs <- list( "BOIN12 v1" = get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, c_t = 0.95, c_e = 0.9) %>% stop_at_n(n = 36), "BOIN12 v2" = get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, c_t = 0.5, c_e = 0.5) %>% stop_at_n(n = 36) ) # For illustration we run only 10 iterates: x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff ) # To compare toxicity-only designs like CRM etc, we would omit true_prob_eff. # We might be interested in the absolute dose recommendation probabilities: convergence_plot(x) library(dplyr) library(ggplot2) # and, perhaps more importantly, how they compare: as_tibble(x) %>% ggplot(aes(x = n, y = delta)) + geom_point(size = 0.4) + geom_linerange(aes(ymin = delta_l, ymax = delta_u)) + geom_hline(yintercept = 0, linetype = "dashed", col = "red") + facet_grid(comparison ~ dose, labeller = labeller( .rows = label_both, .cols = label_both) ) # Simulations for each design are available by name: sims <- x$`BOIN12 v1` # And the usual functions are available on the sims objects: sims %>% num_patients() sims %>% num_doses() sims %>% dose_indices() sims %>% n_at_dose() # etc # See ? simulate_trials # As with simulate_trials, which examines one design, we also have options to # tweak the simulation process. # By default, dose decisions are made after each cohort of 3 patients. To # override, specify an alternative function via the sample_patient_arrivals # parameter. E.g. to use cohorts of 2, we run: patient_arrivals_func <- function(current_data) cohorts_of_n(n = 2) x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, sample_patient_arrivals = patient_arrivals_func ) # To simulate the culmination of trials that are partly completed, specify # the outcomes already observed via the previous_outcomes parameter. Imagine # one cohort has already been evaluated, returning outcomes 1NTN. We can # simulate the remaining part of that trial with: x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, previous_outcomes = '1NTN' ) # Outcomes can be described by the above outcome string method or data-frame: previous_outcomes <- data.frame( patient = 1:3, cohort = c(1, 1, 1), tox = c(0, 1, 0), eff = c(1, 1, 0), dose = c(1, 1, 1) ) x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, previous_outcomes = previous_outcomes ) # We can specify the immediate next dose: x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, next_dose = 5 ) # By default, the method will stop simulated trials after 30 dose selections. # To suppress this, specify i_like_big_trials = TRUE. However, please take # care to specify selectors that will eventually stop! Our designs above use # stop_at_n so they will not proceed ad infinitum. x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, i_like_big_trials = TRUE ) # By default, only the final model fit is retained for each simulated trial. # To retain all interim model fits, specify return_all_fits = TRUE. x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, return_all_fits = TRUE ) ## End(Not run)
## Not run: # Don't run on build because they exceed CRAN time limit # In a five-dose scenario, we have assumed probabilities for Prob(tox): true_prob_tox <- c(0.05, 0.10, 0.15, 0.18, 0.45) # and Prob(eff): true_prob_eff <- c(0.40, 0.50, 0.52, 0.53, 0.53) # Let us compare two BOIN12 variants that differ in their stopping params: designs <- list( "BOIN12 v1" = get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, c_t = 0.95, c_e = 0.9) %>% stop_at_n(n = 36), "BOIN12 v2" = get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, c_t = 0.5, c_e = 0.5) %>% stop_at_n(n = 36) ) # For illustration we run only 10 iterates: x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff ) # To compare toxicity-only designs like CRM etc, we would omit true_prob_eff. # We might be interested in the absolute dose recommendation probabilities: convergence_plot(x) library(dplyr) library(ggplot2) # and, perhaps more importantly, how they compare: as_tibble(x) %>% ggplot(aes(x = n, y = delta)) + geom_point(size = 0.4) + geom_linerange(aes(ymin = delta_l, ymax = delta_u)) + geom_hline(yintercept = 0, linetype = "dashed", col = "red") + facet_grid(comparison ~ dose, labeller = labeller( .rows = label_both, .cols = label_both) ) # Simulations for each design are available by name: sims <- x$`BOIN12 v1` # And the usual functions are available on the sims objects: sims %>% num_patients() sims %>% num_doses() sims %>% dose_indices() sims %>% n_at_dose() # etc # See ? simulate_trials # As with simulate_trials, which examines one design, we also have options to # tweak the simulation process. # By default, dose decisions are made after each cohort of 3 patients. To # override, specify an alternative function via the sample_patient_arrivals # parameter. E.g. to use cohorts of 2, we run: patient_arrivals_func <- function(current_data) cohorts_of_n(n = 2) x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, sample_patient_arrivals = patient_arrivals_func ) # To simulate the culmination of trials that are partly completed, specify # the outcomes already observed via the previous_outcomes parameter. Imagine # one cohort has already been evaluated, returning outcomes 1NTN. We can # simulate the remaining part of that trial with: x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, previous_outcomes = '1NTN' ) # Outcomes can be described by the above outcome string method or data-frame: previous_outcomes <- data.frame( patient = 1:3, cohort = c(1, 1, 1), tox = c(0, 1, 0), eff = c(1, 1, 0), dose = c(1, 1, 1) ) x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, previous_outcomes = previous_outcomes ) # We can specify the immediate next dose: x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, next_dose = 5 ) # By default, the method will stop simulated trials after 30 dose selections. # To suppress this, specify i_like_big_trials = TRUE. However, please take # care to specify selectors that will eventually stop! Our designs above use # stop_at_n so they will not proceed ad infinitum. x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, i_like_big_trials = TRUE ) # By default, only the final model fit is retained for each simulated trial. # To retain all interim model fits, specify return_all_fits = TRUE. x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff, return_all_fits = TRUE ) ## End(Not run)
This function takes a selector_factory
, such as
that returned by get_dfcrm
, get_boin
or
get_three_plus_three
, and conducts many notional clinical
trials. We conduct simulations to learn about the operating characteristics
of adaptive trial designs.
simulate_trials( selector_factory, num_sims, true_prob_tox, true_prob_eff = NULL, ... )
simulate_trials( selector_factory, num_sims, true_prob_tox, true_prob_eff = NULL, ... )
selector_factory |
Object of type |
num_sims |
integer, number of trial iterations to simulate. |
true_prob_tox |
numeric vector of true but unknown toxicity probabilities |
true_prob_eff |
numeric vector of true but unknown efficacy probabilities. NULL if efficacy not analysed. |
... |
Extra args are passed onwards. |
By default, dose decisions in simulated trials are made after each
cohort of 3 patients. This can be changed by providing a function by the
sample_patient_arrivals
parameter that simulates the arrival of new
patients. The new patients will be added to the existing patients and the
model will be fit to the set of all patients. The function that simulates
patient arrivals should take as a single parameter a data-frame with one
row for each existing patient and columns including cohort, patient, dose,
tox, time (and possibly also eff and weight, if a phase I/II or
time-to-event method is used). The provision of data on the existing
patients allows the patient sampling function to be adaptive. The function
should return a data-frame with a row for each new patient and a column for
time_delta, the time between the arrival of this patient and the previous,
as in cohorts_of_n
. See Examples.
This method can simulate the culmination of trials that are partly
completed. We just have to specify the outcomes already observed via the
previous_outcomes
parameter. Each simulated trial will commence from
those outcomes seen thus far. See Examples.
We can specify the immediate next dose by specifying next_dose
. If
omitted, the next dose is calculated by invoking the model on the outcomes
seen thus far.
Designs must eventually choose to stop the trial. Some designs, like 3+3,
have intrinsic stopping rules. However, some selectors like those derived
from get_dfcrm
offer no default stopping method. You may need
to append stopping behaviour to your selector via something like
stop_at_n
or stop_when_n_at_dose
, etc. To
safeguard against simulating runaway trials that never end, the function
will halt a simulated trial after 30 invocations of the dose-selection
decision. To breach this limit, specify i_like_big_trials = TRUE
in
the function call. However, when you forego the safety net, the onus is on
you to write selectors that will eventually stop the trial! See Examples.
The model is fit to the prevailing data at each dose selection point. By
default, only the final model fit for each simulated trial is retained.
This is done to conserve memory. With a high number of simulated trials,
storing many model fits per trial may cause the executing machine to run
out of memory. However, you can force this method to retain all model fits
by specifying return_all_fits = TRUE
. See Examples.
Object of type simulations
.
# In a five-dose scenario, we have assumed probabilities for Prob(tox): true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) # Simulate ten 3+3 trials: sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox) # Likewise, simulate 10 trials using a continual reassessment method: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox) # Lots of useful information is contained in the returned object: sims %>% num_patients() sims %>% num_doses() sims %>% dose_indices() sims %>% n_at_dose() sims %>% n_at_recommended_dose() sims %>% tox_at_dose() sims %>% num_tox() sims %>% recommended_dose() sims %>% prob_administer() sims %>% prob_recommend() sims %>% trial_duration() # By default, dose decisions are made after each cohort of 3 patients. See # Details. To override, specify an alternative function via the # sample_patient_arrivals parameter. E.g. to use cohorts of 2, we run: patient_arrivals_func <- function(current_data) cohorts_of_n(n = 2) sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, sample_patient_arrivals = patient_arrivals_func) # To simulate the culmination of trials that are partly completed, specify # the outcomes already observed via the previous_outcomes parameter. Imagine # one cohort has already been evaluated, returning outcomes 1NTN. We can # simulate the remaining part of the trial with: sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, previous_outcomes = '1NTN') # Outcomes can be described by the above outcome string method or data-frame: previous_outcomes <- data.frame( patient = 1:3, cohort = c(1, 1, 1), tox = c(0, 1, 0), dose = c(1, 1, 1) ) sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, previous_outcomes = previous_outcomes) # We can specify the immediate next dose: sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, next_dose = 5) # By default, the method will stop simulated trials after 30 dose selections. # To suppress this, specify i_like_big_trials = TRUE. However, please take # care to specify selectors that will eventually stop! sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 99) %>% simulate_trials(num_sims = 1, true_prob_tox = true_prob_tox, i_like_big_trials = TRUE) # By default, only the final model fit is retained for each simulated trial. # To retain all interim model fits, specify return_all_fits = TRUE. sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, return_all_fits = TRUE) # Verify that there are now many analyses per trial with: sapply(sims$fits, length)
# In a five-dose scenario, we have assumed probabilities for Prob(tox): true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) # Simulate ten 3+3 trials: sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox) # Likewise, simulate 10 trials using a continual reassessment method: skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox) # Lots of useful information is contained in the returned object: sims %>% num_patients() sims %>% num_doses() sims %>% dose_indices() sims %>% n_at_dose() sims %>% n_at_recommended_dose() sims %>% tox_at_dose() sims %>% num_tox() sims %>% recommended_dose() sims %>% prob_administer() sims %>% prob_recommend() sims %>% trial_duration() # By default, dose decisions are made after each cohort of 3 patients. See # Details. To override, specify an alternative function via the # sample_patient_arrivals parameter. E.g. to use cohorts of 2, we run: patient_arrivals_func <- function(current_data) cohorts_of_n(n = 2) sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, sample_patient_arrivals = patient_arrivals_func) # To simulate the culmination of trials that are partly completed, specify # the outcomes already observed via the previous_outcomes parameter. Imagine # one cohort has already been evaluated, returning outcomes 1NTN. We can # simulate the remaining part of the trial with: sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, previous_outcomes = '1NTN') # Outcomes can be described by the above outcome string method or data-frame: previous_outcomes <- data.frame( patient = 1:3, cohort = c(1, 1, 1), tox = c(0, 1, 0), dose = c(1, 1, 1) ) sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, previous_outcomes = previous_outcomes) # We can specify the immediate next dose: sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, next_dose = 5) # By default, the method will stop simulated trials after 30 dose selections. # To suppress this, specify i_like_big_trials = TRUE. However, please take # care to specify selectors that will eventually stop! sims <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 99) %>% simulate_trials(num_sims = 1, true_prob_tox = true_prob_tox, i_like_big_trials = TRUE) # By default, only the final model fit is retained for each simulated trial. # To retain all interim model fits, specify return_all_fits = TRUE. sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox, return_all_fits = TRUE) # Verify that there are now many analyses per trial with: sapply(sims$fits, length)
This function does not need to be called by users. It is used internally.
simulation_function(selector_factory)
simulation_function(selector_factory)
selector_factory |
Object of type |
A function.
This class encapsulates that many notional or virtual trials can be simulated. Each recommends a dose (or doses), keeps track of how many patients have been treated at what doses, what toxicity outcomes have been seen, and whether a trial advocates continuing, etc. We run simulations to learn about the operating characteristics of a trial design.
Computationally, the simulations
class supports much of the same
interface as selector
, and a little more.
Thus, many of the same generic functions are supported - see Examples.
However, compared to selector
s, the returned objects reflect
that there are many trials instead of one, e.g. num_patients(sims)
,
returns as an integer vector the number of patients used in the simulated
trials.
simulations(fits, true_prob_tox, true_prob_eff = NULL, ...)
simulations(fits, true_prob_tox, true_prob_eff = NULL, ...)
fits |
Simulated model fits, arranged as list of lists. |
true_prob_tox |
vector of true toxicity probabilities |
true_prob_eff |
vector of true efficacy probabilities, optionally NULL if efficacy not analysed. |
... |
Extra args |
The simulations
object implements the following functions:
list with slots: fits
containing model fits;
and true_prob_tox
, contianing the assumed true probability of
toxicity.
# Simulate performance of the 3+3 design: true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox) # The returned object has type 'simulations'. The supported interface is: sims %>% num_patients() sims %>% num_doses() sims %>% dose_indices() sims %>% n_at_dose() sims %>% tox_at_dose() sims %>% num_tox() sims %>% recommended_dose() sims %>% prob_administer() sims %>% prob_recommend() sims %>% trial_duration() # Access the list of model fits for the ith simulated trial using: i <- 1 sims$fits[[i]] # and the jth model fit for the ith simulated trial using: j <- 1 sims$fits[[i]][[j]] # and so on.
# Simulate performance of the 3+3 design: true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 10, true_prob_tox = true_prob_tox) # The returned object has type 'simulations'. The supported interface is: sims %>% num_patients() sims %>% num_doses() sims %>% dose_indices() sims %>% n_at_dose() sims %>% tox_at_dose() sims %>% num_tox() sims %>% recommended_dose() sims %>% prob_administer() sims %>% prob_recommend() sims %>% trial_duration() # Access the list of model fits for the ith simulated trial using: i <- 1 sims$fits[[i]] # and the jth model fit for the ith simulated trial using: j <- 1 sims$fits[[i]][[j]] # and so on.
simulations_collection
This object can be cast to a tibble with as_tibble
to generate useful
pairwise comparisons of the probability of recommending each dose for each
pair of designs investigated. See
as_tibble.simulations_collection
for a description.
simulations_collection(sim_map)
simulations_collection(sim_map)
sim_map |
list, character -> |
object of class simulations_collection
, inheriting from list
Sweeting, M., Slade, D., Jackson, D., & Brock, K. (2024). Potential outcome simulation for efficient head-to-head comparison of adaptive dose-finding designs. arXiv preprint arXiv:2402.15460
Spread the information in dose_finding_paths object to a wide data.frame format.
spread_paths(df = NULL, dose_finding_paths = NULL, max_depth = NULL)
spread_paths(df = NULL, dose_finding_paths = NULL, max_depth = NULL)
df |
Optional |
dose_finding_paths |
Optional instance of dose_finding_paths. Required if 'df' is null. |
max_depth |
integer, maximum depth of paths to traverse. |
A data.frame
## Not run: # Calculate paths for the first two cohorts of three patients a CRM trial skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 cohort_sizes <- c(3, 3) paths <- get_dfcrm(skeleton = skeleton, target = target) %>% get_dose_paths(cohort_sizes = cohort_sizes) ## End(Not run)
## Not run: # Calculate paths for the first two cohorts of three patients a CRM trial skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 cohort_sizes <- c(3, 3) paths <- get_dfcrm(skeleton = skeleton, target = target) %>% get_dose_paths(cohort_sizes = cohort_sizes) ## End(Not run)
simulations_collection
results verticallyStack simulations_collection
results vertically
stack_sims_vert(sim_map, target_dose = NULL, alpha = 0.05)
stack_sims_vert(sim_map, target_dose = NULL, alpha = 0.05)
sim_map |
object of type |
target_dose |
optional integer vector, the dose of interest. All doses are analysed if omitted, which is the default. |
alpha |
confidence level for asymptotic normal confidence intervals. The default value is 0.05 to get 95 percent confidence intervals. |
a data.frame
# In a five-dose scenario, we have assumed probabilities for Prob(tox): true_prob_tox <- c(0.05, 0.10, 0.15, 0.18, 0.45) # and Prob(eff): true_prob_eff <- c(0.40, 0.50, 0.52, 0.53, 0.53) # Let us compare two BOIN12 variants that differ in their stopping params: designs <- list( "BOIN12 v1" = get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, c_t = 0.95, c_e = 0.9) %>% stop_at_n(n = 36), "BOIN12 v2" = get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, c_t = 0.5, c_e = 0.5) %>% stop_at_n(n = 36) ) # For illustration we run only 10 iterates: x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff ) stack_sims_vert(x)
# In a five-dose scenario, we have assumed probabilities for Prob(tox): true_prob_tox <- c(0.05, 0.10, 0.15, 0.18, 0.45) # and Prob(eff): true_prob_eff <- c(0.40, 0.50, 0.52, 0.53, 0.53) # Let us compare two BOIN12 variants that differ in their stopping params: designs <- list( "BOIN12 v1" = get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, c_t = 0.95, c_e = 0.9) %>% stop_at_n(n = 36), "BOIN12 v2" = get_boin12(num_doses = 5, phi_t = 0.35, phi_e = 0.25, u2 = 40, u3 = 60, c_t = 0.5, c_e = 0.5) %>% stop_at_n(n = 36) ) # For illustration we run only 10 iterates: x <- simulate_compare( designs, num_sims = 10, true_prob_tox, true_prob_eff ) stack_sims_vert(x)
This function adds a restriction to stop a trial when n patients have been evaluated. It does this by adding together the number of patients treated at all doses and stopping when that total exceeds n.
Dose selectors are designed to be daisy-chained together to achieve different behaviours. This class is a **greedy** selector, meaning that it prioritises its own behaviour over the behaviour of other selectors in the chain. That is, it will advocate stopping when the condition has been met, even if the selectors further up the chain would advocate to keep going. In can be interpreted as an overriding selector. This allows the decision to stop to be executed as soon as it is warranted. Be aware though, that there are other selectors that can be placed after this class that will override the stopping behaviour. See Examples.
stop_at_n(parent_selector_factory, n)
stop_at_n(parent_selector_factory, n)
parent_selector_factory |
Object of type |
n |
Stop when there are this many patients. |
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # Create CRM model that will stop when 15 patients are evaluated: model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 15) # With 12 patients, this trial should not stop: fit1 <- model1 %>% fit('1NNN 2NTN 2TNN 2NNN') fit1 %>% recommended_dose() fit1 %>% continue() # With 15 patients, this trial should stop: fit2 <- model1 %>% fit('1NNN 2NTN 2TNN 2NNN 2NTT') fit2 %>% recommended_dose() fit2 %>% continue() # The stopping behaviour can be overruled by the order of selectors. # In model2, demanding 9 at recommended dose will trump stopping at 12: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% demand_n_at_dose(dose = 'recommended', n = 9) # In model3, stopping at 12 will trump demanding 9 at recommended dose: model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% demand_n_at_dose(dose = 'recommended', n = 9) %>% stop_at_n(n = 12) # This model will continue because 9 have not been seen at recommended dose. fit3 <- model2 %>% fit('1NNN 2NNN 2NNN 3NNN') fit3 %>% recommended_dose() fit3 %>% continue() # This model will stop because 12 have been seen. fit4 <- model3 %>% fit('1NNN 2NNN 2NNN 3NNN') fit4 %>% recommended_dose() fit4 %>% continue() # With enough observations though, both models will advise stopping because # both conditions have been met: fit5 <- model2 %>% fit('1NNN 2NNN 2NNN 5NNN 5NNN 5NNN') fit5 %>% recommended_dose() fit5 %>% continue() fit6 <- model3 %>% fit('1NNN 2NNN 2NNN 5NNN 5NNN 5NNN') fit6 %>% recommended_dose() fit6 %>% continue()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # Create CRM model that will stop when 15 patients are evaluated: model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 15) # With 12 patients, this trial should not stop: fit1 <- model1 %>% fit('1NNN 2NTN 2TNN 2NNN') fit1 %>% recommended_dose() fit1 %>% continue() # With 15 patients, this trial should stop: fit2 <- model1 %>% fit('1NNN 2NTN 2TNN 2NNN 2NTT') fit2 %>% recommended_dose() fit2 %>% continue() # The stopping behaviour can be overruled by the order of selectors. # In model2, demanding 9 at recommended dose will trump stopping at 12: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_at_n(n = 12) %>% demand_n_at_dose(dose = 'recommended', n = 9) # In model3, stopping at 12 will trump demanding 9 at recommended dose: model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% demand_n_at_dose(dose = 'recommended', n = 9) %>% stop_at_n(n = 12) # This model will continue because 9 have not been seen at recommended dose. fit3 <- model2 %>% fit('1NNN 2NNN 2NNN 3NNN') fit3 %>% recommended_dose() fit3 %>% continue() # This model will stop because 12 have been seen. fit4 <- model3 %>% fit('1NNN 2NNN 2NNN 3NNN') fit4 %>% recommended_dose() fit4 %>% continue() # With enough observations though, both models will advise stopping because # both conditions have been met: fit5 <- model2 %>% fit('1NNN 2NNN 2NNN 5NNN 5NNN 5NNN') fit5 %>% recommended_dose() fit5 %>% continue() fit6 <- model3 %>% fit('1NNN 2NNN 2NNN 5NNN 5NNN 5NNN') fit6 %>% recommended_dose() fit6 %>% continue()
This method stops a dose-finding trial when there are n patients at a dose. It can stop when the rule is triggered at the recommended dose, at a particular dose, or at any dose.
stop_when_n_at_dose(parent_selector_factory, n, dose)
stop_when_n_at_dose(parent_selector_factory, n, dose)
parent_selector_factory |
Object of type |
n |
Stop when there are n at a dose. |
dose |
|
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # This model will stop when 12 are seen at any dose: model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_n_at_dose(n = 12, dose = 'any') # This model fit will not stop: model1 %>% fit('1NNN 2NTN 2TNN 2NNN') %>% continue() # But this model fit will stop: model1 %>% fit('1NNN 2NTN 2TNN 2NNN 2NTT') %>% continue() # This model will stop when 12 are seen at the recommended dose: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_n_at_dose(n = 12, dose = 'recommended') # This model fit will not stop: fit2 <- model2 %>% fit('1NNN 2NTN 2TNN 2NNN') fit2 %>% recommended_dose() fit2 %>% continue() # But this model fit will stop: fit3 <- model2 %>% fit('1NNN 2NTN 2TNN 2NNN 2NNT') fit3 %>% recommended_dose() fit3 %>% continue()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # This model will stop when 12 are seen at any dose: model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_n_at_dose(n = 12, dose = 'any') # This model fit will not stop: model1 %>% fit('1NNN 2NTN 2TNN 2NNN') %>% continue() # But this model fit will stop: model1 %>% fit('1NNN 2NTN 2TNN 2NNN 2NTT') %>% continue() # This model will stop when 12 are seen at the recommended dose: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_n_at_dose(n = 12, dose = 'recommended') # This model fit will not stop: fit2 <- model2 %>% fit('1NNN 2NTN 2TNN 2NNN') fit2 %>% recommended_dose() fit2 %>% continue() # But this model fit will stop: fit3 <- model2 %>% fit('1NNN 2NTN 2TNN 2NNN 2NNT') fit3 %>% recommended_dose() fit3 %>% continue()
This method stops a dose-finding trial and recommends no dose when sufficient probabilistic confidence is reached that the rate of toxicity at a dose exceeds some threshold. In other words, it stops when it is likely that a dose is too toxic. It can stop when the rule is triggered at the recommended dose, at a particular dose, or at any dose. See Details.
stop_when_too_toxic(parent_selector_factory, dose, tox_threshold, confidence)
stop_when_too_toxic(parent_selector_factory, dose, tox_threshold, confidence)
parent_selector_factory |
Object of type |
dose |
|
tox_threshold |
We are interested in toxicity probabilities greater than this threshold. |
confidence |
Stop when there is this much total probability mass supporting that the toxicity rate exceeds the threshold. |
The method for calculating probability mass for toxicity rates will
ultimately be determined by the dose-finding model used and the attendant
inferential mechanism. For instance, the crm
function in
the dfcrm package calculates the posterior expected mean and variance of the
slope parameter in a CRM model. It does not use MCMC to draw samples from the
posterior distribution. Thus, to perform inference on the posterior
probability of toxicity, this package assumes the dfcrm slope parameter
follows a normal distribution with the mean and variance calculated by dfcrm.
In contrast, the stan_crm
function in the trialr
package needs
no such assumption because it samples from the posterior parameter
distribution and uses those samples to infer on the posterior probability of
toxicity at each dose, dependent on the chosen model for the dose-toxicity
curve.
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # We compare a CRM model without a toxicity stopping rule to one with it: model1 <- get_dfcrm(skeleton = skeleton, target = target) model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_too_toxic(dose = 'any', tox_threshold = 0.5, confidence = 0.7) outcomes <- '1NNN 2NNN 3NNT 3NNN 3TNT 2NNN' fit1 <- model1 %>% fit(outcomes) fit2 <- model2 %>% fit(outcomes) # Naturally the first does not advocate stopping: fit1 %>% recommended_dose() fit1 %>% continue() # However, after the material toxicity at dose 3, ithe rule is fired: fit2 %>% recommended_dose() fit2 %>% continue() # To verify the requirement to stop, let's calculate the probability that the # toxicity rate exceeds 50% fit2 %>% prob_tox_exceeds(0.5)
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # We compare a CRM model without a toxicity stopping rule to one with it: model1 <- get_dfcrm(skeleton = skeleton, target = target) model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_too_toxic(dose = 'any', tox_threshold = 0.5, confidence = 0.7) outcomes <- '1NNN 2NNN 3NNT 3NNN 3TNT 2NNN' fit1 <- model1 %>% fit(outcomes) fit2 <- model2 %>% fit(outcomes) # Naturally the first does not advocate stopping: fit1 %>% recommended_dose() fit1 %>% continue() # However, after the material toxicity at dose 3, ithe rule is fired: fit2 %>% recommended_dose() fit2 %>% continue() # To verify the requirement to stop, let's calculate the probability that the # toxicity rate exceeds 50% fit2 %>% prob_tox_exceeds(0.5)
This method stops a dose-finding trial when the symmetric uncertainty interval for the probability of toxicity falls within a range. This allows trials to be stopped when sufficient precision on the pobability of toxicity has been achieved. See Details.
stop_when_tox_ci_covered( parent_selector_factory, dose, lower, upper, width = 0.9 )
stop_when_tox_ci_covered( parent_selector_factory, dose, lower, upper, width = 0.9 )
parent_selector_factory |
Object of type |
dose |
|
lower |
Stop when lower interval bound exceeds this value |
upper |
Stop when upper interval bound is less than this value |
width |
Width of the uncertainty interval. Default is 0.9, i.e. a range from the 5th to the 95th percentiles. |
The method for calculating probability mass for toxicity rates will
ultimately be determined by the dose-finding model used and the attendant
inferential mechanism. For instance, the crm
function in
the dfcrm package calculates the posterior expected mean and variance of the
slope parameter in a CRM model. It does not use MCMC to draw samples from the
posterior distribution. Thus, to perform inference on the posterior
probability of toxicity, this package assumes the dfcrm slope parameter
follows a normal distribution with the mean and variance calculated by dfcrm.
In contrast, the stan_crm
function in the trialr
package needs no such assumption because it samples from the posterior
parameter distribution and uses those samples to infer on the posterior
probability of toxicity at each dose, dependent on the chosen model for the
dose-toxicity curve.
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # We compare a CRM model without this stopping rule: model1 <- get_dfcrm(skeleton = skeleton, target = target) # To two with it, the first demanding a relatively tight CI: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_tox_ci_covered(dose = 'recommended', lower = 0.15, upper = 0.35) # and the second demanding a relatively loose CI: model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_tox_ci_covered(dose = 'recommended', lower = 0.05, upper = 0.45) outcomes <- '1NNN 2NNN 3NNT 3NNN 3TNT 2NNN' fit1 <- model1 %>% fit(outcomes) fit2 <- model2 %>% fit(outcomes) fit3 <- model3 %>% fit(outcomes) # Naturally the first does not advocate stopping: fit1 %>% recommended_dose() fit1 %>% continue() # The second does not advocate stopping either: fit2 %>% recommended_dose() fit2 %>% continue() # This is because the CI is too wide: fit2 %>% prob_tox_quantile(p = 0.05) fit2 %>% prob_tox_quantile(p = 0.95) # However, the third design advocates stopping because the CI at the # recommended dose is covered: fit3 %>% recommended_dose() fit3 %>% continue() # To verify the veracity, inspect the quantiles: fit3 %>% prob_tox_quantile(p = 0.05) fit3 %>% prob_tox_quantile(p = 0.95)
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # We compare a CRM model without this stopping rule: model1 <- get_dfcrm(skeleton = skeleton, target = target) # To two with it, the first demanding a relatively tight CI: model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_tox_ci_covered(dose = 'recommended', lower = 0.15, upper = 0.35) # and the second demanding a relatively loose CI: model3 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_tox_ci_covered(dose = 'recommended', lower = 0.05, upper = 0.45) outcomes <- '1NNN 2NNN 3NNT 3NNN 3TNT 2NNN' fit1 <- model1 %>% fit(outcomes) fit2 <- model2 %>% fit(outcomes) fit3 <- model3 %>% fit(outcomes) # Naturally the first does not advocate stopping: fit1 %>% recommended_dose() fit1 %>% continue() # The second does not advocate stopping either: fit2 %>% recommended_dose() fit2 %>% continue() # This is because the CI is too wide: fit2 %>% prob_tox_quantile(p = 0.05) fit2 %>% prob_tox_quantile(p = 0.95) # However, the third design advocates stopping because the CI at the # recommended dose is covered: fit3 %>% recommended_dose() fit3 %>% continue() # To verify the veracity, inspect the quantiles: fit3 %>% prob_tox_quantile(p = 0.05) fit3 %>% prob_tox_quantile(p = 0.95)
Learn whether this selector supports sampling of outcomes. For instance, is it possible to get posterior samples of the probability of toxicity at each dose? If true, prob_tox_samples will return a data-frame of samples.
supports_sampling(x, ...)
supports_sampling(x, ...)
x |
Object of type |
... |
arguments passed to other methods |
logical
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% supports_sampling()
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% supports_sampling()
Fit the 3+3 model to some outcomes.
three_plus_three( outcomes, num_doses, allow_deescalate = FALSE, strict_mode = TRUE )
three_plus_three( outcomes, num_doses, allow_deescalate = FALSE, strict_mode = TRUE )
outcomes |
Outcomes observed. See |
num_doses |
Number of doses under investigation. |
allow_deescalate |
TRUE to allow de-escalation, as described by Korn et al. Default is FALSE. |
strict_mode |
TRUE to raise errors if it is detected that the 3+3 algorithm has not been followed. |
lits containing recommended_dose and a logical value continue saying whether the trial should continue.
Storer BE. Design and Analysis of Phase I Clinical Trials. Biometrics. 1989;45(3):925-937. doi:10.2307/2531693
Korn EL, Midthune D, Chen TT, Rubinstein LV, Christian MC, Simon RM. A comparison of two phase I trial designs. Statistics in Medicine. 1994;13(18):1799-1806. doi:10.1002/sim.4780131802
three_plus_three('2NNN 3NNT', num_doses = 7)
three_plus_three('2NNN 3NNT', num_doses = 7)
Get a vector of the binary toxicity outcomes for evaluated patients.
tox(x, ...)
tox(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
an integer vector
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% tox()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% tox()
Get the number of toxicities seen at each dose under investigation.
tox_at_dose(x, ...)
tox_at_dose(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
an integer vector
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% tox_at_dose()
# CRM example skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 outcomes <- '1NNN 2NTN' fit <- get_dfcrm(skeleton = skeleton, target = target) %>% fit(outcomes) fit %>% tox_at_dose()
Get the maximum permissible toxicity rate, if supported. NULL if not.
tox_limit(x, ...)
tox_limit(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
numeric
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') tox_limit(x)
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') tox_limit(x)
Get the target toxicity rate, if supported. NULL if not.
tox_target(x, ...)
tox_target(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
numeric
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% tox_target()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% tox_target()
Get the length of time that trials take to recruit all patients.
trial_duration(x, ...)
trial_duration(x, ...)
x |
Object of type |
... |
arguments passed to other methods |
vector of numerical times
true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 50, true_prob_tox = true_prob_tox) sims %>% trial_duration
true_prob_tox <- c(0.12, 0.27, 0.44, 0.53, 0.57) sims <- get_three_plus_three(num_doses = 5) %>% simulate_trials(num_sims = 50, true_prob_tox = true_prob_tox) sims %>% trial_duration
This method continues a dose-finding trial until a safety dose has been given to n patients. Once that condition is met, it delegates dose selelcting and stopping responsibility to its parent dose selector, whatever that might be. This class is greedy in that it meets its own needs before asking any other selectors higher in the chain what they want. Thus, different behaviours may be achieved by nesting dose selectors in different orders. See examples.
try_rescue_dose(parent_selector_factory, n, dose)
try_rescue_dose(parent_selector_factory, n, dose)
parent_selector_factory |
Object of type |
n |
Continue at least until there are n at a dose. |
dose |
an integer to identify the sought rescue dose-level. |
an object of type selector_factory
that can fit a
dose-finding model to outcomes.
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # This model will demand the lowest dose is tried in at least two patients # before the trial is stopped for excess toxicity model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_too_toxic(dose = 1, tox_threshold = 0.35, confidence = 0.8) %>% try_rescue_dose(dose = 1, n = 2) # In contrast, this model will stop for excess toxicity without trying dose 1 model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_too_toxic(dose = 1, tox_threshold = 0.35, confidence = 0.8) # For non-toxic outcomes, both designs will continue at sensible doses: fit1 <- model1 %>% fit('2NNN') fit1 %>% recommended_dose() fit1 %>% continue() fit2 <- model2 %>% fit('2NNN') fit2 %>% recommended_dose() fit2 %>% continue() # For toxic outcomes, the design 1 will use dose 1 before stopping is allowed fit1 <- model1 %>% fit('2TTT') fit1 %>% recommended_dose() fit1 %>% continue() # For toxic outcomes, however, design 2 will stop despite dose 1 being # untested: fit2 <- model2 %>% fit('2TTT') fit2 %>% recommended_dose() fit2 %>% continue() # After dose 1 is given the requisite number of times, dose recommendation # and stopping revert to being determined by the underlying dose selector: fit1 <- model1 %>% fit('2TTT 1T') fit1 %>% recommended_dose() fit1 %>% continue() fit1 <- model1 %>% fit('2TTT 1TT') fit1 %>% recommended_dose() fit1 %>% continue()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 # This model will demand the lowest dose is tried in at least two patients # before the trial is stopped for excess toxicity model1 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_too_toxic(dose = 1, tox_threshold = 0.35, confidence = 0.8) %>% try_rescue_dose(dose = 1, n = 2) # In contrast, this model will stop for excess toxicity without trying dose 1 model2 <- get_dfcrm(skeleton = skeleton, target = target) %>% stop_when_too_toxic(dose = 1, tox_threshold = 0.35, confidence = 0.8) # For non-toxic outcomes, both designs will continue at sensible doses: fit1 <- model1 %>% fit('2NNN') fit1 %>% recommended_dose() fit1 %>% continue() fit2 <- model2 %>% fit('2NNN') fit2 %>% recommended_dose() fit2 %>% continue() # For toxic outcomes, the design 1 will use dose 1 before stopping is allowed fit1 <- model1 %>% fit('2TTT') fit1 %>% recommended_dose() fit1 %>% continue() # For toxic outcomes, however, design 2 will stop despite dose 1 being # untested: fit2 <- model2 %>% fit('2TTT') fit2 %>% recommended_dose() fit2 %>% continue() # After dose 1 is given the requisite number of times, dose recommendation # and stopping revert to being determined by the underlying dose selector: fit1 <- model1 %>% fit('2TTT 1T') fit1 %>% recommended_dose() fit1 %>% continue() fit1 <- model1 %>% fit('2TTT 1TT') fit1 %>% recommended_dose() fit1 %>% continue()
Get the derived utility score of each dose under investigation. Some models, particularly phase I/II models or efficacy-toxicity designs, specify algorithms to calculate utility. If no utility algorithm is specified for a design, this function will return a vector of NAs.
utility(x, ...)
utility(x, ...)
x |
Object of class |
... |
arguments passed to other methods |
a numerical vector
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') utility(x)
efftox_priors <- trialr::efftox_priors p <- efftox_priors(alpha_mean = -7.9593, alpha_sd = 3.5487, beta_mean = 1.5482, beta_sd = 3.5018, gamma_mean = 0.7367, gamma_sd = 2.5423, zeta_mean = 3.4181, zeta_sd = 2.4406, eta_mean = 0, eta_sd = 0.2, psi_mean = 0, psi_sd = 1) real_doses = c(1.0, 2.0, 4.0, 6.6, 10.0) model <- get_trialr_efftox(real_doses = real_doses, efficacy_hurdle = 0.5, toxicity_hurdle = 0.3, p_e = 0.1, p_t = 0.1, eff0 = 0.5, tox1 = 0.65, eff_star = 0.7, tox_star = 0.25, priors = p, iter = 1000, chains = 1, seed = 2020) x <- model %>% fit('1N 2E 3B') utility(x)
Get a vector of the weights attached to outcomes for evaluated patients.
weight(x, ...)
weight(x, ...)
x |
Object of type |
... |
Extra args are passed onwards. |
a numerical vector
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% weight()
skeleton <- c(0.05, 0.1, 0.25, 0.4, 0.6) target <- 0.25 model <- get_dfcrm(skeleton = skeleton, target = target) fit <- model %>% fit('1NNN 2NTN') fit %>% weight()