openpharma / crmPack

Object-Oriented Implementation of CRM Designs
https://openpharma.github.io/crmPack/
20 stars 10 forks source link

Add support for "smallest/largest next best" dose #855

Open Puzzled-Face opened 1 month ago

Puzzled-Face commented 1 month ago

Some trials, most obviously those using ordinal models, may need to recommend a dose based on the minimum/maximum of different NextBest rules.

For example

The dose recommended for the next cohort will be the dose that maximises the probability that the chance of a grade 1 toxicity is in the range [0.2, 0.33] amongst those doses that have a probability of a grade 2 toxicity is less than 0.05.

This is not currently supported, but would be by the creation of NextBestMin (and NextBestMax) classes with behaviour analogous to CohortSizeMin and CohortSizeMax.

danielinteractive commented 1 month ago

Interesting idea, thanks @Puzzled-Face - can you give a code example for the situation mentioned above? Just so that I can better imagine this.

Puzzled-Face commented 1 month ago

For example

data <- Data(
  doseGrid = c(1, 3, 9, 18, 36, 54, 80, 100),
  x = c(1, 1, 1, 3, 3, 3, 9, 9, 9),
  y = c(rep(0, 8), 1),
  cohort = rep(1L:3L, each = 3),
  ID = 1L:9L
)

model <- .DefaultLogisticLogNormal()
samples <- mcmc(data, model, .DefaultMcmcOptions())

next_best_mtd <- NextBestMTD(
  0.25,
  function(mtd_samples) quantile(mtd_samples, probs = 0.25)
)

next_best_min_dist <- .DefaultNextBestMinDist()

nextBest(next_best_mtd, Inf, samples, model, data)
nextBest(next_best_min_dist, Inf, samples, model, data)

next_best_min <- NextBestMin(list(next_best_mtd, next_best_min_dist))
nextBest(next_best_min, Inf, samples, model, data)

next_best_max <- NextBestMax(list(next_best_mtd, next_best_min_dist))
nextBest(next_best_max, Inf, samples, model, data)

More realistically (and this is the type of use case that prompted the issue):

ordinal_data <- DataOrdinal(
  doseGrid = c(1, 3, 9, 18, 36, 54, 80, 100),
  x = c(1, 1, 1, 3, 3, 3, 9, 9, 9),
  y = c(rep(0, 6), 0, 0, 1, 0, 0, 2),
  cohort = rep(1L:3L, each = 3),
  ID = 1L:9L,
  yCategories = c("No Tox" = 0L, "DLT" = 1L, "CRS" = 2L)
)

ordinal_model <- .DefaultLogisticLogNormalOrdinal()
samples <- mcmc(ordinal_data, ordinal_model, .DefaultMcmcOptions())

# We want separate constrants on p(DLT) and p(CRS).  The next best dose must satisfy both.
next_best_trial <- NextBestMin(
  list(
    #  Limit p(DLT) to 33%
    NextBestOrdinal(1L, .DefaultNextBestMinDist(0.33)), 
    # Limit p(CRS) to 5%
    NextBestOrdinal(2L, .DefaultNextBestMinDist(0.05))
  )
)
nextBest(next_best_trial, Inf, samples, ordinal_model, ordinal_data)

Though this code will currently fail since nextBest(<NextBest>, <numeric>, <Samples>, ordinal_model, ordinal_data) will fail because <NextBest> must be an instance of NextBestOrdinal, which operates on only one toxicity grade.

The PR associated with this issue is the set up for #856, which will provide support for this situation. The tests included in the PR demonstrate the correct function of the new class(es) for Data and GeneralModel. (Though note the addition of a new dependency on patchwork. This (or similar) is required to get a sensible value in the plot element of the return value of nextBest().)

danielinteractive commented 1 month ago

I think it would generally be good for this kind of larger feature to have a design doc first :-)

But for the content, I would like to check whether this cannot be equally well achieved by additional Increments rules? Because there we already have the restrictions on the dose range, and NextBest is then for optimizing a certain criterion subject to the restrictions from Increments. Note that we also have NextBest rules which are combining multiple outcomes (e.g. safety and efficacy)

Puzzled-Face commented 1 month ago

Good points. Will think about whether Increments will suffice. Though I am doubtful. Increments tend to impose limits relative to the current dose and are based on the trial design. (For example, 200% increment from the highest dose administered so far.) What I am proposing here is a limit based on the fitted model, which seems to fit more naturally with NextBest. The additional functionality is simply the ability to apply more than one rule.

I don't think dual endpoints present any fundamental difficulties, though we may need different generics to handle them.

danielinteractive commented 1 month ago

We also have already Increments rules with models, e.g. https://github.com/openpharma/crmPack/blob/main/R/Rules-class.R#L1362

I would see the dual endpoints NextBest rules as examples where multiple models are combined into a single rule, rather than defining them separately and the combining with min or max. For example, if the ordinal model needs something specific here, it could be an interesting alternative to just define a special rule for this case.

Puzzled-Face commented 1 month ago

Hmmm. I think it is possible to do what I propose with an Increments rule, but I still think it fits better as a NextBest. The reason is simple: the Increments rules are based on the design of the trial (ratio of this dose level to the next etc), whereas the NextBest rules may be based on the results of the trial. Even the IncrementsHSRBeta rule you link to above uses a fixed prior when evaluating the likelihood of toxicity rather than the current posterior obtained from the trial to date.

The point is made clear by the signatures of the relevant generics. For maxDose, used with Increments, the signature is (Increments, Data, ...), whereas for nextBest, used with NextBest it is (NextBest, numeric, Samples, GeneralModel, Data, ...).

So we could create (say) an IncrementsMaxToxProb, but it would need a Samples object passed to it. This would make it look like a NextBest class even though it wasn't.

Equally, I could make a NextBestMaxToxProb class specific for DataOrdinal data and LogisiticLogNormalOrdinal models, but that would lose the generality the other options provide.

I can see where the philosophical conflict arises. Conceptually, we determine which doses are safe/eligible (with an Increments rule) and then determine which of the eligible/safe doses is optimal for the next cohort (with a NextBest rule). As I wrote earlier, determining which doses are eligible is typically a design decision whereas determining which is optimal is based on results. This is the first example where an eligibility/safety questions requires results rather than simply design data.

The solution based on an ordinal-specific NextBest rule is probably minimum effort/minimum risk, but it does break the assumption I made in designing the wrapper classes for ordinal rules that only one toxicity grade is relevant. I would say that using a set of new NextBest rules is safe and more consistent with the way the package is currently written. But creating a new Increments rule that takes a Samples argument is most consistent with the idea of first choosing which doses are safe and then selecting the best dose amongst the safe doses.

It's not an easy decision!

danielinteractive commented 1 month ago

Thanks @Puzzled-Face for the analysis, much appreciated! I just think that normally this won't be needed, so am trying to keep the changes to a minimum. How about a compromise where you have a special NextBest class for ordinal models and that takes multiple NextBest objects itself? So nothing that needs to work with all possible models etc, but just tailored for this problem.