library(boinet)
library(dplyr)
# Load ggplot2 if available
if (requireNamespace("ggplot2", quietly = TRUE)) {
library(ggplot2)
}
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.
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.
First, run your BOIN-ET simulation as usual:
# Example TITE-BOIN-ET simulation
<- tite.boinet(
result 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
)
# Extract operating characteristics manually
<- function(boinet_result) {
extract_oc_data <- names(boinet_result$n.patient)
dose_levels
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
<- function(boinet_result) {
extract_design_data <- data.frame(
params 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,
$lambda1, boinet_result$lambda2,
boinet_result$eta1, boinet_result$prop.stop,
boinet_result$duration, boinet_result$n.sim),
boinet_resultstringsAsFactors = FALSE
)
# Add time-specific parameters if available
if (!is.null(boinet_result$tau.T)) {
<- data.frame(
time_params 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
)<- rbind(params, time_params)
params
}
return(params)
}
# Extract data
<- extract_oc_data(result)
oc_data <- extract_design_data(result)
design_data
# 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
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)
# Find optimal dose
<- oc_data$dose_level[which.max(oc_data$selection_prob)]
optimal_dose 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%)
<- oc_data[oc_data$selection_prob > 10, ]
viable_doses
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
# Assess safety profile
<- oc_data %>%
safety_summary mutate(
safety_category = case_when(
<= 0.10 ~ "Low toxicity",
toxicity_prob <= 0.25 ~ "Moderate toxicity",
toxicity_prob 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
# Analyze efficacy-toxicity trade-off
<- oc_data %>%
tradeoff_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
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
# Create a nicely formatted table using base R
<- function(oc_data) {
create_oc_summary <- 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)
formatted_data
# 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
<- create_oc_summary(oc_data)
formatted_oc 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
# Create formatted design parameters table
<- function(design_data) {
create_design_summary <- design_data
formatted_design $value <- round(as.numeric(formatted_design$value), 3)
formatted_design
# Clean up parameter names
names(formatted_design) <- c("Parameter", "Value")
return(formatted_design)
}
<- create_design_summary(design_data)
formatted_design 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
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%
# 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
<- list(
summary_stats 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")
Always follow the pattern: simulate first, then extract and analyze data.
# Always check your results make sense
<- sum(oc_data$selection_prob) + as.numeric(result$prop.stop)
total_selection 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
# Document analysis parameters
<- list(
analysis_info 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
# Create reusable utility functions
<- function(eff_prob, tox_prob, eff_weight = 1, tox_weight = 2) {
calculate_utility_score * eff_prob - tox_weight * tox_prob
eff_weight
}
<- function(oc_data, n_top = 3) {
find_best_doses %>%
oc_data arrange(desc(selection_prob)) %>%
head(n_top) %>%
select(dose_level, selection_prob, toxicity_prob, efficacy_prob)
}
# Use utility functions
$utility <- calculate_utility_score(oc_data$efficacy_prob, oc_data$toxicity_prob)
oc_data<- find_best_doses(oc_data)
top_doses
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
# Analyze sensitivity to design parameters
<- data.frame(
sensitivity_summary 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
# Framework for comparing multiple designs
<- function(result_list, design_names) {
create_design_comparison <- data.frame()
comparison_data
for (i in seq_along(result_list)) {
<- extract_oc_data(result_list[[i]])
oc_data <- oc_data$dose_level[which.max(oc_data$selection_prob)]
optimal_dose
<- data.frame(
summary_row 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
)
<- rbind(comparison_data, summary_row)
comparison_data
}
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
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.