---
title: "Getting started with simFastBOIN"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Getting started with simFastBOIN}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
library(simFastBOIN)
```

## What the package does

The Bayesian optimal interval (BOIN) design assigns a cohort of patients to a
dose, counts how many experienced a dose-limiting toxicity, and compares the
observed rate against two boundaries. If the rate is at or below the lower
boundary the next cohort is escalated, if it reaches the upper boundary the next
cohort is de-escalated, and otherwise the dose is repeated. The rule is as simple
to run as a 3+3 design, and Liu and Yuan report average performance comparable to
that of the continual reassessment method.

This package provides the boundaries, a fast simulation engine for operating
characteristics, and the MTD selection rule applied at the end of a trial. The
traditional 3+3 design is included as a comparator, so the two can be tabulated
side by side.

## Decision boundaries

`boin_lambda()` returns the two interval boundaries, which depend only on the
target rate and the two thresholds around it.

```{r}
boin_lambda(target = 0.30)
```

In practice the boundaries are used as whole numbers of toxicities.
`boin_boundary()` tabulates them, together with the elimination boundary, for
every attainable sample size at a dose.

```{r}
bd <- boin_boundary(target = 0.30, max_n = 18, extrasafe = TRUE)
print(bd, cohort_size = 3)
```

Reading the first column: with three patients treated, escalate on zero
toxicities, de-escalate on two or more, and stay on exactly one.

The same information can be read as a table indexed by both counts. Printing it
with a cohort size shows only the sample sizes that are actually reached.

```{r}
decisions <- boin_decision_table(target = 0.30, max_n = 18)

print(decisions, cohort_size = 3)
```

For a protocol the table is usually easier to read as a picture. Every cell
carries its decision letter, so the figure survives grayscale printing.

```{r decision-plot, fig.width = 8, fig.height = 6, eval = requireNamespace("ggplot2", quietly = TRUE)}
plot(decisions)
```

When `extrasafe = TRUE` there is a second, stricter rule that applies at the
lowest dose only. `boin_stopping_table()` lays it out the same way.

```{r}
boin_stopping_table(bd, cohort_size = 3)
```

## Operating characteristics for one scenario

`sim_boin()` simulates the design many times under an assumed dose-toxicity
curve and summarizes the result.

```{r}
oc <- sim_boin(
  target = 0.30,
  p_true = c(0.05, 0.15, 0.30, 0.45, 0.60),
  n_cohort = 10,
  cohort_size = 3,
  n_trials = 2000,
  seed = 123
)

oc
```

The third dose is the true MTD here, and it is the dose selected most often. The
components of the result are available individually.

```{r}
oc$sel_percent
oc$percent_no_mtd
oc$overdose$pct_patients
```

## Comparing scenarios

A design is judged across a range of plausible curves rather than one.
`sim_boin_multi()` runs the same design under several scenarios and collects the
results into one table.

```{r}
scenarios <- list(
  "MTD at dose 2" = c(0.15, 0.30, 0.45, 0.60, 0.75),
  "MTD at dose 4" = c(0.02, 0.06, 0.15, 0.30, 0.50),
  "All doses toxic" = c(0.40, 0.55, 0.65, 0.75, 0.85)
)

sim_boin_multi(
  target = 0.30,
  scenarios = scenarios,
  n_cohort = 10,
  cohort_size = 3,
  n_trials = 2000,
  seed = 123
)
```

## Design options

Five options change how the design behaves.

`titration = TRUE` treats one patient per dose until the first toxicity, which
moves through safe doses quickly.

```{r}
safe_curve <- c(0.01, 0.02, 0.05, 0.12, 0.30)

plain <- sim_boin(target = 0.30, p_true = safe_curve, n_cohort = 12,
                  cohort_size = 3, n_trials = 1000, seed = 1)
titrated <- sim_boin(target = 0.30, p_true = safe_curve, n_cohort = 12,
                     cohort_size = 3, n_trials = 1000, titration = TRUE, seed = 1)

rbind(plain = plain$n_pts_dose, titrated = titrated$n_pts_dose)
```

`extrasafe = TRUE` adds a stopping rule at the lowest dose that is easier to
trigger than elimination, and `bound_mtd = TRUE` refuses to select a dose whose
estimated toxicity exceeds the de-escalation boundary.

```{r}
toxic_curve <- c(0.35, 0.45, 0.55, 0.65, 0.75)

c(
  plain = sim_boin(target = 0.30, p_true = toxic_curve, n_cohort = 12,
                   cohort_size = 3, n_trials = 1000, seed = 2)$percent_no_mtd,
  extrasafe = sim_boin(target = 0.30, p_true = toxic_curve, n_cohort = 12,
                       cohort_size = 3, n_trials = 1000, extrasafe = TRUE,
                       seed = 2)$percent_no_mtd
)
```

`n_earlystop` stops a trial once the current dose has accrued this many patients
and the design would stay there, which is taken as a sign that the algorithm has
settled. It defaults to 18. Note that the reference
implementation in the BOIN package defaults to 100, which effectively switches
the rule off, so a comparison between the two should set it explicitly.

For some target rates one DLT out of three patients de-escalates the dose, where
the 3+3 design would stay. `stay_on_1_of_3 = TRUE` aligns the design with that
practice by raising the de-escalation boundary at three patients, which changes
one cell of the table and nothing else.

```{r}
c(default = boin_decision_table(0.25, 9)["1", "3"],
  modified = boin_decision_table(0.25, 9, stay_on_1_of_3 = TRUE)["1", "3"])
```

## Working with the trial data

`boin_simulate()` returns the raw counts, which is useful when a summary other
than the built-in one is needed.

```{r}
trials <- boin_simulate(
  target = 0.30,
  p_true = c(0.05, 0.15, 0.30, 0.45, 0.60),
  n_cohort = 10,
  cohort_size = 3,
  n_trials = 1000,
  seed = 123
)

trials
```

The MTD selection rule can then be applied separately, which makes it possible to
compare selection rules on one set of simulated trials.

```{r}
free <- boin_select_mtd(trials$n_pts, trials$n_tox, target = 0.30)
bounded <- boin_select_mtd(trials$n_pts, trials$n_tox, target = 0.30,
                           bound_mtd = TRUE)

table(free$mtd, bounded$mtd, useNA = "ifany")
```

The estimated dose-toxicity curve behind those decisions is available on its own.

```{r}
round(boin_isotonic(trials$n_pts[1:5, ], trials$n_tox[1:5, ]), 3)
```

## Comparing with the 3+3 design

`oc_3p3()` gives the operating characteristics of the traditional 3+3 design.
The probability of every path the design can take is evaluated in closed form, so
the result carries no Monte Carlo error and takes no seed.

```{r}
oc_3p3(p_true = c(0.05, 0.15, 0.25, 0.45, 0.60))
```

Two definitions of the MTD are in common use and `mtd_rule` chooses between
them. The default, `"previous"`, names the dose below the one declared too toxic.
The other takes the MTD to be the highest dose at which at most one of six
patients had a DLT, expanding a dose that has only three patients before
assessing it.

```{r}
expanded <- oc_3p3(p_true = c(0.05, 0.15, 0.25, 0.45, 0.60),
                   mtd_rule = "expand")

round(expanded$sel_percent, 1)
round(expanded$total_n_pts, 2)
```

The components of the result carry the same names as those of `sim_boin()`, so
the same code can tabulate either design. `sim_3p3()` simulates the same design
and exists to confirm the closed form rather than to obtain the numbers.

## Agreement with the BOIN package

The engine draws one uniform variate per patient, in enrollment order, and
applies the decision rules in the order used by `BOIN::get.oc()`. With the
same seed and matching arguments the two implementations agree trial by trial,
not merely on average. The package's test suite checks this directly whenever the
BOIN package is installed, and `inst/validation/compare-with-BOIN.R` runs the
same comparison over 200 configurations covering every combination of the options
the two packages share.

```{r, eval = FALSE}
reference <- BOIN::get.oc(
  target = 0.30, p.true = c(0.05, 0.15, 0.25, 0.45, 0.60),
  ncohort = 20, cohortsize = 3, n.earlystop = 18,
  ntrial = 1000, seed = 6
)

ours <- sim_boin(
  target = 0.30, p_true = c(0.05, 0.15, 0.25, 0.45, 0.60),
  n_cohort = 20, cohort_size = 3, n_earlystop = 18,
  n_trials = 1000, seed = 6
)

all.equal(unname(ours$sel_percent), reference$selpercent)
all.equal(unname(ours$n_pts_dose), reference$npatients)
```

## References

Liu S. and Yuan, Y. (2015). Bayesian Optimal Interval Designs for Phase I
Clinical Trials. *Journal of the Royal Statistical Society: Series C*, 64,
507-523.

Yan, F., Zhang, L., Zhou, Y., Pan, H., Liu, S. and Yuan, Y. (2020). BOIN: An R
Package for Designing Single-Agent and Drug-Combination Dose-Finding Trials Using
Bayesian Optimal Interval Designs. *Journal of Statistical Software*, 94(13),
1-32.
