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,
  ...
)

Arguments

selector_factory

Object of type selector_factory.

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.

Value

Object of type simulations.

Details

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.

Examples

# 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()
#>  [1] 12 12 12 12 12 12 12 12 12 12
sims %>% num_doses()
#> [1] 5
sims %>% dose_indices()
#> [1] 1 2 3 4 5
sims %>% n_at_dose()
#> # A tibble: 10 × 5
#>      `1`   `2`   `3`   `4`   `5`
#>    <int> <int> <int> <int> <int>
#>  1     3     3     3     3     0
#>  2     3     3     3     3     0
#>  3     3     3     3     3     0
#>  4     3     3     3     3     0
#>  5     3     3     3     3     0
#>  6     3     0     0     3     6
#>  7     9     0     0     3     0
#>  8     9     0     0     3     0
#>  9     3     0     6     3     0
#> 10     3     0     0     3     6
sims %>% n_at_recommended_dose()
#>  [1] 3 3 3 3 3 3 9 9 6 3
sims %>% tox_at_dose()
#> # A tibble: 10 × 5
#>      `1`   `2`   `3`   `4`   `5`
#>    <int> <int> <int> <int> <int>
#>  1     0     0     3     1     0
#>  2     0     0     1     2     0
#>  3     0     1     2     1     0
#>  4     0     0     2     2     0
#>  5     0     0     0     2     0
#>  6     0     0     0     0     3
#>  7     2     0     0     3     0
#>  8     2     0     0     3     0
#>  9     0     0     2     1     0
#> 10     0     0     0     0     3
sims %>% num_tox()
#>  [1] 4 3 4 4 2 3 5 5 3 3
sims %>% recommended_dose()
#>  [1] 2 3 2 2 3 4 1 1 3 4
sims %>% prob_administer()
#>     1     2     3     4     5 
#> 0.350 0.125 0.175 0.250 0.100 
sims %>% prob_recommend()
#> NoDose      1      2      3      4      5 
#>    0.0    0.2    0.3    0.3    0.2    0.0 
sims %>% trial_duration()
#>  [1] 11.5  7.9 10.8 10.1 13.3 10.3 12.6 19.0 13.2 18.5

# 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)
#>  [1] 5 3 6 3 4 5 4 5 8 4