BOIN, CRM and 3+3

BOIN

  • Define trial parameters (target DLT, cohort size, number of cohorts)
  • Generate BOIN decision boundaries
  • Extract escalation and de-escalation cutoffs
  • Input current cohort toxicity data
  • Calculate observed toxicity rate
  • Compare toxicity rate with BOIN boundaries
  • Make dose decision: escalate, stay, or de-escalate
# =========================================================
# Simple BOIN Example in R
# Single-Agent Phase I Dose Escalation Trial
# =========================================================

library(BOIN)

# =========================================================
# Step 1: Define Trial Design Parameters
# =========================================================

target <- 0.30
# Target DLT (Dose Limiting Toxicity) rate = 30%

ncohort <- 10
# Maximum number of cohorts planned in the trial

cohortsize <- 3
# Number of patients enrolled per cohort

# =========================================================
# Step 2: Generate BOIN Decision Boundaries
# =========================================================

bound <- get.boundary(
  target = target,
  ncohort = ncohort,
  cohortsize = cohortsize
)

# Print BOIN design summary and boundaries
print(bound)
## $lambda_e
## [1] 0.2364907
## 
## $lambda_d
## [1] 0.3585195
## 
## $boundary_tab
##                                                      
## Number of patients treated 3 6 9 12 15 18 21 24 27 30
## Escalate if # of DLT <=    0 1 2  2  3  4  4  5  6  7
## Deescalate if # of DLT >=  2 3 4  5  6  7  8  9 10 11
## Eliminate if # of DLT >=   3 4 5  7  8  9 10 11 12 14
## 
## $full_boundary_tab
##                                                                                
## Number of patients treated  1  2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## Escalate if # of DLT <=     0  0 0 0 1 1 1 1 2  2  2  2  3  3  3  3  4  4  4  4
## Deescalate if # of DLT >=   1  1 2 2 2 3 3 3 4  4  4  5  5  6  6  6  7  7  7  8
## Eliminate if # of DLT >=   NA NA 3 3 4 4 5 5 5  6  6  7  7  8  8  8  9  9  9 10
##                                                         
## Number of patients treated 21 22 23 24 25 26 27 28 29 30
## Escalate if # of DLT <=     4  5  5  5  5  6  6  6  6  7
## Deescalate if # of DLT >=   8  8  9  9  9 10 10 11 11 11
## Eliminate if # of DLT >=   10 11 11 11 12 12 12 13 13 14
## 
## attr(,"class")
## [1] "boin"
# =========================================================
# Step 3: Extract Escalation / De-escalation Boundaries
# =========================================================

lambda_e <- bound$lambda_e
# Escalation boundary
# If observed toxicity rate is BELOW this value,
# escalate to the next higher dose

lambda_d <- bound$lambda_d
# De-escalation boundary
# If observed toxicity rate is ABOVE this value,
# de-escalate to the next lower dose

# Print boundaries
cat("Escalation Boundary (lambda_e):", lambda_e, "\n")
## Escalation Boundary (lambda_e): 0.2364907
cat("De-escalation Boundary (lambda_d):", lambda_d, "\n")
## De-escalation Boundary (lambda_d): 0.3585195
# =========================================================
# Step 4: Current Cohort Data
# =========================================================

current_n <- 3
# Number of patients treated at current dose

current_tox <- 0
# Number of DLTs observed at current dose

# Calculate observed toxicity rate
obs_rate <- current_tox / current_n

cat("Observed Toxicity Rate:", obs_rate, "\n")
## Observed Toxicity Rate: 0
# =========================================================
# Step 5: BOIN Dose Escalation Decision
# =========================================================

if (obs_rate < lambda_e) {
  
  decision <- "Escalate"
  
} else if (obs_rate > lambda_d) {
  
  decision <- "De-escalate"
  
} else {
  
  decision <- "Stay at Current Dose"
  
}

# Print final decision
cat("BOIN Decision:", decision, "\n")
## BOIN Decision: Escalate

CRM

  • Define CRM design parameters. Set the target DLT rate, dose levels, and prior toxicity skeleton.
  • Create observed trial data. Enter patient-level dose assignment and DLT outcomes.
  • Fit the CRM model. Use the observed data to update toxicity probabilities for all dose levels.
  • Summarize estimated toxicity. Compare prior skeleton values with posterior estimated toxicity probabilities.
  • Select next recommended dose. Choose the dose with estimated toxicity closest to the target DLT rate.
  • Output recommendation. Print the recommended dose level for the next cohort.
# =========================================================
# Simple CRM Example in R
# Single-Agent Phase I Dose Escalation Trial
# =========================================================

# install.packages("dfcrm")
library(dfcrm)

# =========================================================
# Step 1: Define CRM Design Parameters
# =========================================================

target <- 0.30
# Target DLT rate = 30%

dose_level <- 1:5
# Five dose levels

prior <- c(0.05, 0.10, 0.20, 0.35, 0.50)
# CRM skeleton:
# Initial guesses of DLT probability for each dose level
# Must be increasing with dose

# =========================================================
# Step 2: Create Observed Trial Data
# =========================================================
# Example:
# Cohort 1: Dose 1, 0/3 DLT
# Cohort 2: Dose 2, 0/3 DLT
# Cohort 3: Dose 3, 1/3 DLT
# Cohort 4: Dose 3, 1/3 DLT

level <- c(
  rep(1, 3),
  rep(2, 3),
  rep(3, 3),
  rep(3, 3)
)

tox <- c(
  0, 0, 0,   # Dose 1: 0/3 DLT
  0, 0, 0,   # Dose 2: 0/3 DLT
  0, 0, 1,   # Dose 3: 1/3 DLT
  0, 1, 0    # Dose 3: 1/3 DLT
)

trial_data <- data.frame(
  patient_id = 1:length(tox),
  dose_level = level,
  dlt = tox
)

print(trial_data)
##    patient_id dose_level dlt
## 1           1          1   0
## 2           2          1   0
## 3           3          1   0
## 4           4          2   0
## 5           5          2   0
## 6           6          2   0
## 7           7          3   0
## 8           8          3   0
## 9           9          3   1
## 10         10          3   0
## 11         11          3   1
## 12         12          3   0
# =========================================================
# Step 3: Fit CRM Model
# =========================================================

fit <- crm(
  prior = prior,
  target = target,
  tox = tox,
  level = level,
  model = "empiric",
  method = "bayes"
)

print(fit)
## Today:  Tue Jun  2 12:10:51 2026 
## DATA SUMMARY (CRM) 
## PID   Level   Toxicity    Included 
## 1     1   0       1 
## 2     1   0       1 
## 3     1   0       1 
## 4     2   0       1 
## 5     2   0       1 
## 6     2   0       1 
## 7     3   0       1 
## 8     3   0       1 
## 9     3   1       1 
## 10    3   0       1 
## 11    3   1       1 
## 12    3   0       1 
## 
## Toxicity probability update (with 90 percent probability interval): 
## Level     Prior   n   total.wts   total.tox   Ptox    LoLmt   UpLmt 
## 1     0.05    3   3       0       0.061   0.006   0.216 
## 2     0.1     3   3       0       0.116   0.02    0.308 
## 3     0.2     6   6       2       0.222   0.064   0.439 
## 4     0.35    0   0       0       0.375   0.167   0.584 
## 5     0.5     0   0       0       0.523   0.307   0.701 
## Next recommended dose level: 4 
## Recommendation is based on a target toxicity probability of 0.3 
## 
## Estimation details:
## Empiric dose-toxicity model: p = dose^{exp(beta)}
## dose = 0.05 0.1 0.2 0.35 0.5 
## Normal prior on beta with mean 0 and variance 1.34 
## Posterior mean of beta: -0.068 
## Posterior variance of beta: 0.134
# =========================================================
# Step 4: Extract CRM Estimated Toxicity Probabilities
# =========================================================

estimated_tox <- fit$ptox

crm_summary <- data.frame(
  dose_level = dose_level,
  skeleton = prior,
  estimated_toxicity = estimated_tox
)

print(crm_summary)
##   dose_level skeleton estimated_toxicity
## 1          1     0.05         0.06093446
## 2          2     0.10         0.11641775
## 3          3     0.20         0.22242082
## 4          4     0.35         0.37511834
## 5          5     0.50         0.52341210
# =========================================================
# Step 5: Recommended Dose for Next Cohort
# =========================================================

recommended_dose <- fit$mtd

cat("Recommended dose level for next cohort:", recommended_dose, "\n")
## Recommended dose level for next cohort: 4

3+3

  • Define patient dose levels and DLT outcomes
  • Create trial dataset
  • Summarize number of patients and DLTs at each dose
  • Apply 3+3 rules sequentially:
    0/3 DLT → escalate 1/3 DLT → expand to 6 patients ≤1/6 DLT → escalate ≥2/3 or ≥2/6 DLT → de-escalate/stop
  • Select the previous safe dose as the MTD
# =========================================================
# Simple 3+3 Design Example in R
# Single-Agent Phase I Dose Escalation Trial
# =========================================================

# =========================================================
# Step 1: Define Observed Trial Data
# =========================================================

# Example:
# Cohort 1: Dose 1, 0/3 DLT  -> escalate
# Cohort 2: Dose 2, 0/3 DLT  -> escalate
# Cohort 3: Dose 3, 1/3 DLT  -> expand dose 3
# Cohort 4: Dose 3, 0/3 DLT  -> dose 3 has 1/6 DLT, escalate
# Cohort 5: Dose 4, 2/3 DLT  -> de-escalate, stop
# Final MTD = Dose 3

dose_level <- c(
  rep(1, 3),
  rep(2, 3),
  rep(3, 3),
  rep(3, 3),
  rep(4, 3)
)

dlt <- c(
  0, 0, 0,   # Dose 1: 0/3 DLT
  0, 0, 0,   # Dose 2: 0/3 DLT
  0, 0, 1,   # Dose 3: 1/3 DLT
  0, 0, 0,   # Dose 3: total 1/6 DLT
  1, 1, 0    # Dose 4: 2/3 DLT
)

trial_data <- data.frame(
  patient_id = 1:length(dlt),
  dose_level = dose_level,
  dlt = dlt
)

print(trial_data)
##    patient_id dose_level dlt
## 1           1          1   0
## 2           2          1   0
## 3           3          1   0
## 4           4          2   0
## 5           5          2   0
## 6           6          2   0
## 7           7          3   0
## 8           8          3   0
## 9           9          3   1
## 10         10          3   0
## 11         11          3   0
## 12         12          3   0
## 13         13          4   1
## 14         14          4   1
## 15         15          4   0
# =========================================================
# Step 2: Summarize DLT by Dose
# =========================================================

summary_by_dose <- aggregate(
  dlt ~ dose_level,
  data = trial_data,
  FUN = function(x) c(n = length(x), tox = sum(x), rate = mean(x))
)

summary_by_dose <- do.call(data.frame, summary_by_dose)
colnames(summary_by_dose) <- c("dose_level", "n", "dlt", "dlt_rate")

print(summary_by_dose)
##   dose_level n dlt  dlt_rate
## 1          1 3   0 0.0000000
## 2          2 3   0 0.0000000
## 3          3 6   1 0.1666667
## 4          4 3   2 0.6666667
# =========================================================
# Step 3: Apply Simple 3+3 Rule Manually
# =========================================================

cat("3+3 decision path:\n")
## 3+3 decision path:
cat("Dose 1: 0/3 DLT -> Escalate to Dose 2\n")
## Dose 1: 0/3 DLT -> Escalate to Dose 2
cat("Dose 2: 0/3 DLT -> Escalate to Dose 3\n")
## Dose 2: 0/3 DLT -> Escalate to Dose 3
cat("Dose 3: 1/3 DLT -> Expand Dose 3 to 6 patients\n")
## Dose 3: 1/3 DLT -> Expand Dose 3 to 6 patients
cat("Dose 3: 1/6 DLT -> Escalate to Dose 4\n")
## Dose 3: 1/6 DLT -> Escalate to Dose 4
cat("Dose 4: 2/3 DLT -> Stop and de-escalate\n")
## Dose 4: 2/3 DLT -> Stop and de-escalate
cat("Final MTD: Dose 3\n")
## Final MTD: Dose 3