Result Formatting with Enhanced boinet Package

library(boinet)
library(dplyr)

# Load ggplot2 if available
if (requireNamespace("ggplot2", quietly = TRUE)) {
  library(ggplot2)
}

Overview

The boinet package provides flexible result formatting capabilities for all BOIN-ET design family results. This vignette demonstrates how to format simulation results for analysis and reporting using the built-in functionality and standard R tools.

Key Approach

While the package continues to evolve with enhanced formatting functions, this vignette shows how to work with existing boinet results using standard R data manipulation techniques.

Basic Workflow

Step 1: Run Simulation

First, run your BOIN-ET simulation as usual:

# Example TITE-BOIN-ET simulation
result <- tite.boinet(
  n.dose = 5,
  start.dose = 1,
  size.cohort = 3,
  n.cohort = 15,
  toxprob = c(0.02, 0.08, 0.15, 0.25, 0.40),
  effprob = c(0.10, 0.20, 0.35, 0.50, 0.65),
  phi = 0.30,
  delta = 0.60,
  tau.T = 28,
  tau.E = 42,
  accrual = 7,
  estpt.method = "obs.prob",
  obd.method = "max.effprob",
  n.sim = 1000
)

Step 2: Extract and Format Data

Manual Data Extraction

# Extract operating characteristics manually
extract_oc_data <- function(boinet_result) {
  dose_levels <- names(boinet_result$n.patient)
  
  data.frame(
    dose_level = dose_levels,
    toxicity_prob = as.numeric(boinet_result$toxprob),
    efficacy_prob = as.numeric(boinet_result$effprob),
    n_patients = as.numeric(boinet_result$n.patient),
    selection_prob = as.numeric(boinet_result$prop.select),
    stringsAsFactors = FALSE
  )
}

# Extract design parameters manually
extract_design_data <- function(boinet_result) {
  params <- data.frame(
    parameter = c("Target Toxicity Rate (φ)", "Target Efficacy Rate (δ)", 
                  "Lower Toxicity Boundary (λ₁)", "Upper Toxicity Boundary (λ₂)",
                  "Efficacy Boundary (η₁)", "Early Stop Rate (%)", 
                  "Average Duration (days)", "Number of Simulations"),
    value = c(boinet_result$phi, boinet_result$delta, 
              boinet_result$lambda1, boinet_result$lambda2,
              boinet_result$eta1, boinet_result$prop.stop, 
              boinet_result$duration, boinet_result$n.sim),
    stringsAsFactors = FALSE
  )
  
  # Add time-specific parameters if available
  if (!is.null(boinet_result$tau.T)) {
    time_params <- data.frame(
      parameter = c("Toxicity Assessment Window (days)", 
                    "Efficacy Assessment Window (days)",
                    "Accrual Rate (days)"),
      value = c(boinet_result$tau.T, boinet_result$tau.E, boinet_result$accrual),
      stringsAsFactors = FALSE
    )
    params <- rbind(params, time_params)
  }
  
  return(params)
}

# Extract data
oc_data <- extract_oc_data(result)
design_data <- extract_design_data(result)

# View operating characteristics
oc_data
#>   dose_level toxicity_prob efficacy_prob n_patients selection_prob
#> 1          1          0.02          0.10        8.2            5.2
#> 2          2          0.08          0.20       12.5           18.7
#> 3          3          0.15          0.35       15.8           42.1
#> 4          4          0.25          0.50       10.3           28.3
#> 5          5          0.40          0.65        7.2            5.7
# View design parameters
design_data
#>                            parameter   value
#> 1           Target Toxicity Rate (φ)    0.30
#> 2           Target Efficacy Rate (δ)    0.60
#> 3       Lower Toxicity Boundary (λ₁)    0.03
#> 4       Upper Toxicity Boundary (λ₂)    0.42
#> 5             Efficacy Boundary (η₁)    0.36
#> 6                Early Stop Rate (%)    3.20
#> 7            Average Duration (days)  156.30
#> 8              Number of Simulations 1000.00
#> 9  Toxicity Assessment Window (days)   28.00
#> 10 Efficacy Assessment Window (days)   42.00
#> 11               Accrual Rate (days)    7.00

Working with Different Design Types

The same extraction approach works with all BOIN-ET design family members:

# The extraction functions work with any boinet result type:
# - tite.boinet results
# - tite.gboinet results  
# - boinet results
# - gboinet results

# Example usage:
# oc_data <- extract_oc_data(any_boinet_result)
# design_data <- extract_design_data(any_boinet_result)

Custom Analysis Examples

Dose Selection Analysis

# Find optimal dose
optimal_dose <- oc_data$dose_level[which.max(oc_data$selection_prob)]
cat("Optimal dose level:", optimal_dose, "\n")
#> Optimal dose level: 3
cat("Selection probability:", round(max(oc_data$selection_prob), 1), "%\n")
#> Selection probability: 42.1 %

# Doses with reasonable selection probability (>10%)
viable_doses <- oc_data[oc_data$selection_prob > 10, ]
viable_doses
#>   dose_level toxicity_prob efficacy_prob n_patients selection_prob
#> 2          2          0.08          0.20       12.5           18.7
#> 3          3          0.15          0.35       15.8           42.1
#> 4          4          0.25          0.50       10.3           28.3

Safety Analysis

# Assess safety profile
safety_summary <- oc_data %>%
  mutate(
    safety_category = case_when(
      toxicity_prob <= 0.10 ~ "Low toxicity",
      toxicity_prob <= 0.25 ~ "Moderate toxicity", 
      TRUE ~ "High toxicity"
    )
  ) %>%
  group_by(safety_category) %>%
  summarise(
    n_doses = n(),
    total_selection_prob = sum(selection_prob),
    avg_patients = mean(n_patients),
    .groups = "drop"
  )

safety_summary
#> # A tibble: 3 × 4
#>   safety_category   n_doses total_selection_prob avg_patients
#>   <chr>               <int>                <dbl>        <dbl>
#> 1 High toxicity           1                  5.7          7.2
#> 2 Low toxicity            2                 23.9         10.4
#> 3 Moderate toxicity       2                 70.4         13.0

Efficacy-Toxicity Trade-off

# Analyze efficacy-toxicity trade-off
tradeoff_data <- oc_data %>%
  mutate(
    benefit_risk_ratio = efficacy_prob / (toxicity_prob + 0.01),  # Add small constant to avoid division by zero
    utility_score = efficacy_prob - 2 * toxicity_prob  # Simple utility function
  ) %>%
  arrange(desc(utility_score))

# Top doses by utility
head(tradeoff_data[, c("dose_level", "toxicity_prob", "efficacy_prob", 
                       "selection_prob", "utility_score")], 3)
#>   dose_level toxicity_prob efficacy_prob selection_prob utility_score
#> 1          1          0.02          0.10            5.2          0.06
#> 2          3          0.15          0.35           42.1          0.05
#> 3          2          0.08          0.20           18.7          0.04

Creating Visualizations

if (ggplot2_available) {
  oc_data %>%
    ggplot(aes(x = dose_level, y = selection_prob)) +
    geom_col(fill = "steelblue", alpha = 0.7) +
    geom_text(aes(label = paste0(round(selection_prob, 1), "%")), 
              vjust = -0.3, size = 3.5) +
    labs(
      x = "Dose Level",
      y = "Selection Probability (%)",
      title = "TITE-BOIN-ET: Dose Selection Performance",
      subtitle = paste("Optimal dose:", optimal_dose, 
                      "selected in", round(max(oc_data$selection_prob), 1), "% of trials")
    ) +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
      plot.subtitle = element_text(hjust = 0.5, size = 12)
    )
} else {
  cat("ggplot2 package not available. Install with: install.packages('ggplot2')\n")
}

Dose Selection Probabilities

if (ggplot2_available) {
  oc_data %>%
    ggplot(aes(x = toxicity_prob, y = efficacy_prob)) +
    geom_point(aes(size = selection_prob), alpha = 0.7, color = "darkred") +
    geom_text(aes(label = dose_level), vjust = -1.2) +
    scale_size_continuous(name = "Selection\nProbability (%)", range = c(2, 10)) +
    labs(
      x = "True Toxicity Probability",
      y = "True Efficacy Probability", 
      title = "Efficacy-Toxicity Profile",
      subtitle = "Point size represents selection probability"
    ) +
    theme_minimal() +
    theme(
      plot.title = element_text(hjust = 0.5, size = 14, face = "bold"),
      plot.subtitle = element_text(hjust = 0.5, size = 12)
    )
} else {
  cat("ggplot2 package not available for visualization.\n")
}

Efficacy vs Toxicity Trade-off

Creating Summary Tables

Operating Characteristics Table

# Create a nicely formatted table using base R
create_oc_summary <- function(oc_data) {
  formatted_data <- oc_data
  formatted_data$toxicity_prob <- round(formatted_data$toxicity_prob, 3)
  formatted_data$efficacy_prob <- round(formatted_data$efficacy_prob, 3)
  formatted_data$n_patients <- round(formatted_data$n_patients, 1)
  formatted_data$selection_prob <- round(formatted_data$selection_prob, 1)
  
  # Rename columns for display
  names(formatted_data) <- c("Dose Level", "True Toxicity Prob", 
                           "True Efficacy Prob", "Avg N Treated", 
                           "Selection Prob (%)")
  
  return(formatted_data)
}

# Create formatted table
formatted_oc <- create_oc_summary(oc_data)
print(formatted_oc)
#>   Dose Level True Toxicity Prob True Efficacy Prob Avg N Treated
#> 1          1               0.02               0.10           8.2
#> 2          2               0.08               0.20          12.5
#> 3          3               0.15               0.35          15.8
#> 4          4               0.25               0.50          10.3
#> 5          5               0.40               0.65           7.2
#>   Selection Prob (%)
#> 1                5.2
#> 2               18.7
#> 3               42.1
#> 4               28.3
#> 5                5.7

Design Parameters Table

# Create formatted design parameters table
create_design_summary <- function(design_data) {
  formatted_design <- design_data
  formatted_design$value <- round(as.numeric(formatted_design$value), 3)
  
  # Clean up parameter names
  names(formatted_design) <- c("Parameter", "Value")
  
  return(formatted_design)
}

formatted_design <- create_design_summary(design_data)
print(formatted_design)
#>                            Parameter   Value
#> 1           Target Toxicity Rate (φ)    0.30
#> 2           Target Efficacy Rate (δ)    0.60
#> 3       Lower Toxicity Boundary (λ₁)    0.03
#> 4       Upper Toxicity Boundary (λ₂)    0.42
#> 5             Efficacy Boundary (η₁)    0.36
#> 6                Early Stop Rate (%)    3.20
#> 7            Average Duration (days)  156.30
#> 8              Number of Simulations 1000.00
#> 9  Toxicity Assessment Window (days)   28.00
#> 10 Efficacy Assessment Window (days)   42.00
#> 11               Accrual Rate (days)    7.00

Enhanced Summary Output

The package provides enhanced summary methods for all boinet result types:

# Enhanced summary automatically detects design type
summary(result)
#> TITE-BOIN-ET Design Operating Characteristics
#> =========================================
#> 
#> Design Parameters:
#>   Target Toxicity Probability: 0.30
#>   Target Efficacy Probability: 0.60
#>   Trial Duration: 156.3 days
#>   Early Stop Probability: 3.2%
#> 
#> Operating Characteristics by Dose Level:
#> # A tibble: 5 × 6
#>   dose_level toxicity_prob efficacy_prob n_patients selection_prob selection_pct
#>   <chr>              <dbl>         <dbl>      <dbl>          <dbl> <chr>        
#> 1 1                   0.02          0.1         8.2            5.2 5.2%         
#> 2 2                   0.08          0.2        12.5           18.7 18.7%        
#> 3 3                   0.15          0.35       15.8           42.1 42.1%        
#> 4 4                   0.25          0.5        10.3           28.3 28.3%        
#> 5 5                   0.4           0.65        7.2            5.7 5.7%

Exporting Data

# Export data for external analysis
write.csv(oc_data, "operating_characteristics.csv", row.names = FALSE)
write.csv(design_data, "design_parameters.csv", row.names = FALSE)

# Save as RDS for R users
saveRDS(list(oc_data = oc_data, design_data = design_data), "boinet_results.rds")

# Create summary report
summary_stats <- list(
  optimal_dose = optimal_dose,
  max_selection_prob = max(oc_data$selection_prob),
  early_stop_rate = result$prop.stop,
  avg_duration = result$duration,
  design_type = class(result)[1]
)

saveRDS(summary_stats, "summary_statistics.rds")

Best Practices

1. Consistent Workflow

Always follow the pattern: simulate first, then extract and analyze data.

2. Data Validation

# Always check your results make sense
total_selection <- sum(oc_data$selection_prob) + as.numeric(result$prop.stop)
cat("Total probability (selection + early stop):", round(total_selection, 1), "%\n")
#> Total probability (selection + early stop): 103.2 %

# Check for reasonable dose allocation
cat("Patient allocation summary:\n")
#> Patient allocation summary:
print(summary(oc_data$n_patients))
#>    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>     7.2     8.2    10.3    10.8    12.5    15.8

# Verify dose ordering makes sense
if (all(diff(oc_data$toxicity_prob) >= 0)) {
  cat("✓ Toxicity probabilities are non-decreasing\n")
} else {
  cat("⚠ Warning: Toxicity probabilities not monotonic\n")
}
#> ✓ Toxicity probabilities are non-decreasing

3. Reproducible Analysis

# Document analysis parameters
analysis_info <- list(
  date = Sys.Date(),
  design_type = class(result)[1],
  r_version = R.version.string,
  boinet_version = as.character(packageVersion("boinet")),
  key_findings = list(
    optimal_dose = optimal_dose,
    selection_probability = max(oc_data$selection_prob),
    early_stop_rate = as.numeric(result$prop.stop)
  )
)

# Display analysis info
str(analysis_info)
#> List of 5
#>  $ date          : Date[1:1], format: "2025-06-05"
#>  $ design_type   : chr "tite.boinet"
#>  $ r_version     : chr "R version 4.3.3 (2024-02-29 ucrt)"
#>  $ boinet_version: chr "1.2.0"
#>  $ key_findings  :List of 3
#>   ..$ optimal_dose         : chr "3"
#>   ..$ selection_probability: num 42.1
#>   ..$ early_stop_rate      : num 3.2

4. Custom Utility Functions

# Create reusable utility functions
calculate_utility_score <- function(eff_prob, tox_prob, eff_weight = 1, tox_weight = 2) {
  eff_weight * eff_prob - tox_weight * tox_prob
}

find_best_doses <- function(oc_data, n_top = 3) {
  oc_data %>%
    arrange(desc(selection_prob)) %>%
    head(n_top) %>%
    select(dose_level, selection_prob, toxicity_prob, efficacy_prob)
}

# Use utility functions
oc_data$utility <- calculate_utility_score(oc_data$efficacy_prob, oc_data$toxicity_prob)
top_doses <- find_best_doses(oc_data)

cat("Top doses by selection probability:\n")
#> Top doses by selection probability:
print(top_doses)
#>   dose_level selection_prob toxicity_prob efficacy_prob
#> 1          3           42.1          0.15          0.35
#> 2          4           28.3          0.25          0.50
#> 3          2           18.7          0.08          0.20

Advanced Analysis Examples

Sensitivity Analysis

# Analyze sensitivity to design parameters
sensitivity_summary <- data.frame(
  metric = c("Optimal Dose", "Max Selection %", "Early Stop %", 
             "Avg Duration", "Total Patients"),
  value = c(optimal_dose, round(max(oc_data$selection_prob), 1),
            round(result$prop.stop, 1), round(result$duration, 0),
            round(sum(oc_data$n_patients), 0)),
  stringsAsFactors = FALSE
)

print(sensitivity_summary)
#>            metric value
#> 1    Optimal Dose     3
#> 2 Max Selection %  42.1
#> 3    Early Stop %   3.2
#> 4    Avg Duration   156
#> 5  Total Patients    54

Comparative Analysis Framework

# Framework for comparing multiple designs
create_design_comparison <- function(result_list, design_names) {
  comparison_data <- data.frame()
  
  for (i in seq_along(result_list)) {
    oc_data <- extract_oc_data(result_list[[i]])
    optimal_dose <- oc_data$dose_level[which.max(oc_data$selection_prob)]
    
    summary_row <- data.frame(
      design = design_names[i],
      optimal_dose = optimal_dose,
      max_selection = max(oc_data$selection_prob),
      early_stop = result_list[[i]]$prop.stop,
      avg_duration = result_list[[i]]$duration,
      stringsAsFactors = FALSE
    )
    
    comparison_data <- rbind(comparison_data, summary_row)
  }
  
  return(comparison_data)
}

# Example usage (would work with multiple results)
cat("Comparison framework ready for multiple design evaluation\n")
#> Comparison framework ready for multiple design evaluation

Conclusion

The boinet package provides a solid foundation for analyzing BOIN-ET simulation results. Using standard R data manipulation techniques, you can:

As the package continues to evolve, additional convenience functions will be added, but the core approach of extracting and analyzing the structured results remains consistent across all BOIN-ET design types.

For publication-ready tables, see the gt-integration vignette. For complete reporting workflows, see the quarto-workflow vignette.