Introduction to bmco

Overview

The bmco package provides Bayesian methods for analyzing multiple binary outcomes simultaneously. It addresses a common problem in clinical trials, educational research, and other fields: how to compare groups when success is defined by multiple endpoints.

The Problem

Suppose you’re evaluating a new drug and want to know if it improves both symptom relief and quality of life.

bmco provides a principled Bayesian framework that:

Quick Start Example

library(bmco)
set.seed(2024)

# Simulate a clinical trial with two binary outcomes
trial_data <- data.frame(
  treatment = rep(c("placebo", "drug"), each = 50),
  symptom_relief = rbinom(100, 1, prob = rep(c(0.4, 0.6), each = 50)),
  quality_of_life = rbinom(100, 1, prob = rep(c(0.5, 0.7), each = 50))
)

# Test if drug improves BOTH outcomes
result <- bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  test = "right_sided",
  rule = "All",
  n_it = 1000
)

print(result)
#> 
#> Multivariate Bernoulli Analysis
#> =================================
#> 
#>    Group mean y1 mean y2
#>  placebo   0.520   0.481
#>     drug   0.616   0.639
#>   n(placebo) = 50    n(drug) = 50
#> 
#> Posterior probability P(drug > placebo) [All rule]: 0.795
#> 
#> Use summary() for credible intervals and ESS.
summary(result)
#> 
#> Multivariate Bernoulli Analysis
#> =================================
#> 
#> Group Estimates:
#>    Group mean y1 sd y1 mean y2 sd y2
#>  placebo   0.520 0.067   0.481 0.072
#>     drug   0.616 0.068   0.639 0.067
#> 
#>   n(placebo) = 50    n(drug) = 50
#> 
#> Treatment Effect:
#>   Delta mean (y1, y2): 0.096, 0.157
#>   Delta SE   (y1, y2): 0.003, 0.003
#>   Posterior probability P(drug > placebo): 0.795
#> 
#> Test Information:
#>   Decision rule: All
#>   Hypothesis: P(drug > placebo)
#> 
#>   (Run bmvb() with return_samples = TRUE for credible intervals and ESS.)

The posterior probability (pop) of this example tells us: “What is the probability that the drug is better than placebo on BOTH outcomes?”

Three Analysis Functions

The package provides three main functions for different scenarios:

1. bmvb(): Basic Comparison

Use when: Comparing two groups on multiple binary outcomes. Currently supported for two outcomes only.

# Compare two teaching methods on two outcomes
bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  rule = "All",
  n_it = 1000
)
#> 
#> Multivariate Bernoulli Analysis
#> =================================
#> 
#>    Group mean y1 mean y2
#>  placebo   0.521   0.482
#>     drug   0.616   0.631
#>   n(placebo) = 50    n(drug) = 50
#> 
#> Posterior probability P(drug > placebo) [All rule]: 0.791
#> 
#> Use summary() for credible intervals and ESS.

For more details on statistical methodology or when using this function, please cite:

[1] X. Kavelaars, J. Mulder, and M. Kaptein. “Decision-making with, multiple correlated binary outcomes in clinical trials”. In:, Statistical Methods in Medical Research 29.11 (2020), pp. 3265-3277., DOI: 10.1177/0962280220922256.

2. bglm(): Subgroup analysis

Use when: Comparing two subgroups on multiple binary outcomes (e.g., age range, particular baseline severity). Currently supported for two outcomes only.

# Add age covariate
trial_data$age <- rnorm(100, mean = 50, sd = 10)

result_bglm <- bglm(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  x_var = "age",
  y_vars = c("symptom_relief", "quality_of_life"),
  x_method = "Empirical",
  x_def = c(-Inf, Inf),
  rule = "All",
  n_burn = 200,
  n_it = 500
)

print(result_bglm)
#> 
#> Bayesian Multivariate Logistic Regression
#> ==========================================
#> 
#>    Group mean y1 mean y2
#>  placebo   0.518   0.479
#>     drug   0.637   0.653
#>   n(placebo) = 50    n(drug) = 50
#> 
#> Posterior probability P(drug > placebo) [All rule]: 0.859
#> Marginalization: Empirical over [-Inf, Inf]
#> 
#> Use summary() for regression coefficients, priors and MCMC diagnostics.
summary(result_bglm) 
#> 
#> Bayesian Multivariate Logistic Regression
#> ==========================================
#> 
#> Group Estimates:
#>    Group mean y1 sd y1 mean y2 sd y2
#>  placebo   0.518 0.069   0.479 0.068
#>     drug   0.637 0.077   0.653 0.073
#> 
#>   n(placebo) = 50    n(drug) = 50
#> 
#> Treatment Effect:
#>   Delta mean (y1, y2): 0.119, 0.173
#>   Delta SE   (y1, y2): 0.003, 0.003
#>   Posterior probability P(drug > placebo): 0.859
#> 
#> Regression Coefficients (mean [SD]):
#>                          b11            b10            b01
#> Intercept     -0.731 [1.667] -0.093 [1.727] -0.502 [1.771]
#> treatment     -0.717 [2.296] -1.080 [2.280] -0.290 [2.350]
#> age            0.014 [0.033] -0.005 [0.034] -0.001 [0.035]
#> treatment:age  0.119 [0.226]  0.124 [0.224]  0.114 [0.226]
#> 
#> Prior Specification (regression coefficients):
#>   Mean:
#>   Intercept treatment age treatment:age
#> 1         0         0   0             0
#>   Variance (diagonal of inverse precision):
#>   Intercept treatment age treatment:age
#> 1        10        10  10            10
#> 
#> Marginalization:
#>   Method: Empirical
#>   (Sub)population: [-Inf, Inf]
#>   Decision rule: All
#> 
#> MCMC Diagnostics (regression coefficients):
#>   Multivariate PSRF (MPSRF): 1.0352
#>             Parameter   ESS   Rhat
#>      b11_Intercept[1] 460.8 0.9985
#>      b11_treatment[2] 393.8 1.0084
#>            b11_age[3] 445.6 0.9997
#>  b11_treatment_age[4] 199.7 1.0040
#>      b10_Intercept[1] 502.1 1.0002
#>      b10_treatment[2] 363.6 1.0004
#>            b10_age[3] 486.9 0.9992
#>  b10_treatment_age[4] 163.7 0.9997
#>      b01_Intercept[1] 394.3 1.0015
#>      b01_treatment[2] 326.3 1.0176
#>            b01_age[3] 440.9 1.0021
#>  b01_treatment_age[4] 260.4 1.0140

For more details on statistical methodology or when using this function, please cite:

[1] X. Kavelaars, J. Mulder, and M. Kaptein. “Bayesian Multivariate, Logistic Regression for Superiority and Inferiority Decision-Making, under Observable Treatment Heterogeneity”. In: Multivariate Behavioral, Research 59.4 (2024), pp. 859-882. DOI: 10.1080/00273171.2024.2337340.

Also see the “Subgroup analysis” vignette for details.

3. bglmm(): With Clustering

Use when: Comparing two (sub)groups on multiple binary outcomes, when data are clustered (patients within hospitals, students within schools).

# Add cluster variable
trial_data$hospital <- rep(1:10, each = 10)

result_bglmm <- suppressWarnings( # Warnings due to small number of iterations suppressed
  bglmm(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  id_var = "hospital",
  x_var = "age",
  y_vars = c("symptom_relief", "quality_of_life"),
  x_method = "Empirical",
  x_def = c(-Inf, Inf),
  rule = "All",
  n_burn = 200,
  n_it = 500,
  n_thin = 3
)
)

print(result_bglmm)
#> 
#> Bayesian Multilevel Multivariate Logistic Regression
#> ======================================================
#> 
#>    Group mean y1 mean y2
#>  placebo   0.539   0.487
#>     drug   0.623   0.634
#>   J = 10 clusters    n(placebo) = 50    n(drug) = 50
#> 
#> Posterior probability P(drug > placebo) [All rule]: 0.765
#> Marginalization: Empirical over [-Inf, Inf]
#> MPSRF: fixed = 4.7581    random = 7.3981    variance = 1.2611
#> 
#> Use summary() for full coefficient tables, priors and MCMC diagnostics.
summary(result_bglmm)
#> 
#> Bayesian Multilevel Multivariate Logistic Regression
#> ======================================================
#> 
#> Multilevel Structure:  J = 10 clusters    n(placebo) = 50    n(drug) = 50
#> 
#> Group Estimates:
#>    Group mean y1 sd y1 mean y2 sd y2
#>  placebo   0.539 0.069   0.487 0.070
#>     drug   0.623 0.062   0.634 0.059
#> 
#> Treatment Effect:
#>   Delta mean (y1, y2): 0.084, 0.147
#>   Delta SE   (y1, y2): 0.007, 0.007
#>   Posterior probability P(drug > placebo): 0.765
#> 
#> Fixed Effects (mean [SD]):
#>                         b11           b10           b01
#> age           0.008 [0.022] 0.014 [0.030] 0.034 [0.023]
#> treatment_age 0.118 [0.202] 0.077 [0.206] 0.070 [0.225]
#> 
#> Random Effects (population mean [SD]):
#>                      g11            g10            g01
#> Intercept -0.365 [1.074] -1.025 [1.497] -2.321 [1.154]
#> treatment -0.854 [1.385]  1.226 [1.021]  1.910 [1.576]
#> 
#> Variance Components (posterior mean):
#>   y1=1, y2=1 (b11):
#>           Intercept treatment
#> Intercept     0.133    -0.008
#> treatment    -0.008     0.225
#>   y1=1, y2=0 (b10):
#>           Intercept treatment
#> Intercept     0.182    -0.075
#> treatment    -0.075     0.244
#>   y1=0, y2=1 (b01):
#>           Intercept treatment
#> Intercept     0.208    -0.083
#> treatment    -0.083     0.211
#> 
#> Prior Specification:
#>   Fixed effects -- Normal prior:
#>     Mean:      0, 0 
#>     Variance:  10, 10 
#>   Random effects -- Normal prior:
#>     Mean:      0, 0 
#>     Variance:  10, 10 
#>   Covariance -- Inverse-Wishart: df = 2
#> 
#> Marginalization:
#>   Method: Empirical    (Sub)population: [-Inf, Inf]
#>   Decision rule: All
#> 
#> MCMC Convergence Diagnostics:
#>   Fixed effects -- MPSRF: 4.7581
#>             Parameter  ESS   Rhat
#>            b11_age[1] 21.2 4.6628
#>  b11_treatment_age[2]  6.2 1.7603
#>            b10_age[1] 23.0 5.2521
#>  b10_treatment_age[2] 17.1 1.1535
#>            b01_age[1] 12.2 1.8520
#>  b01_treatment_age[2]  6.5 1.0558
#> 
#>   Random effects -- MPSRF: 7.3981
#>         Parameter  ESS   Rhat
#>  g11_Intercept[1] 14.3 5.2441
#>  g11_treatment[2]  4.5 1.9938
#>  g10_Intercept[1]  9.2 5.8404
#>  g10_treatment[2] 11.2 1.2483
#>  g01_Intercept[1]  6.9 1.9428
#>  g01_treatment[2]  5.5 1.0851
#> 
#>   Variance components -- MPSRF: 1.2611
#>               Parameter   ESS   Rhat
#>  g11_InterceptIntercept 164.4 1.2415
#>  g11_treatmentIntercept 106.1 1.4007
#>  g11_treatmenttreatment 113.9 1.0590
#>  g10_InterceptIntercept 136.3 1.1900
#>  g10_treatmentIntercept 124.5 0.9952
#>  g10_treatmenttreatment 108.0 1.1740
#>  g01_InterceptIntercept  74.9 1.0785
#>  g01_treatmentIntercept  49.6 1.1052
#>  g01_treatmenttreatment  85.3 1.1856

For more details on statistical methodology or when using this function, please cite:

[1] X. Kavelaars, J. Mulder, and M. Kaptein. “Bayesian multilevel, multivariate logistic regression for superiority decision-making under, observable treatment heterogeneity”. In: BMC Medical Research, Methodology 23.1 (2023). DOI: 10.1186/s12874-023-02034-z.

Also see the “Multilevel Models” vignette for details.

Decision Rules

Choose how to define “treatment success”:

All Rule (Conjunctive)

Treatment must improve ALL outcomes:

result_all <- bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  rule = "All",  # BOTH outcomes must improve
  n_it = 5000
)

cat("P(drug better on BOTH outcomes):", result_all$delta$pop, "\n")
#> P(drug better on BOTH outcomes): 0.793

Any Rule (Disjunctive)

Treatment must improve AT LEAST ONE outcome:

result_any <- bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  rule = "Any",  # At least one outcome must improve
  n_it = 5000
)

cat("P(drug better on AT LEAST ONE outcome):", result_any$delta$pop, "\n")
#> P(drug better on AT LEAST ONE outcome): 0.992

Compensatory Rule (Weighted)

Weight outcomes by importance:

result_comp <- bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  rule = "Comp",
  w = c(0.7, 0.3),  # Symptom relief weighted 70%, QoL 30%
  n_it = 5000
)

cat("P(weighted improvement):", result_comp$delta$pop, "\n")
#> P(weighted improvement): 0.936
cat("Weighted effect:", result_comp$delta$w_delta, "\n")
#> Weighted effect: 0.114

Test Directions

Right-sided Test

Test if group B is better than group A:

bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",      
  grp_b = "drug", # Put the group you expect to be better here
  y_vars = c("symptom_relief", "quality_of_life"),
  test = "right_sided",  # P(drug > placebo)
  n_it = 5000
)
#> 
#> Multivariate Bernoulli Analysis
#> =================================
#> 
#>    Group mean y1 mean y2
#>  placebo   0.519   0.481
#>     drug   0.617   0.635
#>   n(placebo) = 50    n(drug) = 50
#> 
#> Posterior probability P(drug > placebo) [All rule]: 0.798
#> 
#> Use summary() for credible intervals and ESS.

Left-sided Test

Test if group A is better than group B:

bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  test = "left_sided",  # P(placebo > drug)
  n_it = 5000
)
#> 
#> Multivariate Bernoulli Analysis
#> =================================
#> 
#>    Group mean y1 mean y2
#>  placebo   0.521   0.481
#>     drug   0.615   0.634
#>   n(placebo) = 50    n(drug) = 50
#> 
#> Posterior probability P(placebo > drug) [All rule]: 0.012
#> 
#> Use summary() for credible intervals and ESS.

Note: Left-sided with (A, B) is equivalent to right-sided with (B, A).

Understanding the Output

result <- bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  n_it = 5000
)

# Key components
names(result)
#> [1] "estimates"    "sample_sizes" "delta"        "info"

# Posterior estimates for each group
result$estimates
#> $mean_a
#> [1] 0.520 0.481
#> 
#> $mean_b
#> [1] 0.615 0.635
#> 
#> $sd_a
#> [1] 0.069 0.069
#> 
#> $sd_b
#> [1] 0.066 0.066

# Sample sizes
result$sample_sizes
#> $n_a
#> [1] 50
#> 
#> $n_b
#> [1] 50

# Treatment effect
result$delta
#> $mean_delta
#> [1] 0.095 0.155
#> 
#> $se_delta
#> [1] 0.001 0.001
#> 
#> $pop
#> [1] 0.795

# Test information
result$info
#> $rule
#> [1] "All"
#> 
#> $test
#> [1] "right_sided"
#> 
#> $test_label
#> [1] "P(drug > placebo)"
#> 
#> $grp_a
#> [1] "placebo"
#> 
#> $grp_b
#> [1] "drug"

Key Output Elements

Posterior Samples

For custom analyses, extract posterior samples:

result_samples <- bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  n_it = 5000,
  return_samples = TRUE
)

# Access posterior samples
str(result_samples$samples)
#> List of 3
#>  $ theta_a: num [1:5000, 1:2] 0.379 0.541 0.596 0.592 0.468 ...
#>  $ theta_b: num [1:5000, 1:2] 0.743 0.514 0.722 0.634 0.696 ...
#>  $ delta  : num [1:5000, 1:2] 0.3641 -0.0268 0.1256 0.0416 0.2285 ...

# Custom probability calculations
samples <- result_samples$samples$delta

# P(effect on symptom relief > 0.1)
cat("P(delta[symptom] > 0.1):", mean(samples[, 1] > 0.1), "\n")
#> P(delta[symptom] > 0.1): 0.4832

# P(effect on QoL > 0.15)
cat("P(delta[QoL] > 0.15):", mean(samples[, 2] > 0.15), "\n")
#> P(delta[QoL] > 0.15): 0.5162

# Credible intervals
cat("\n95% Credible Intervals:\n")
#> 
#> 95% Credible Intervals:
print(apply(samples, 2, quantile, probs = c(0.025, 0.975)))
#>              [,1]        [,2]
#> 2.5%  -0.09942862 -0.03655631
#> 97.5%  0.28142777  0.34018042

Practical Considerations

Sample Size

For basic comparison: see @Kavelaars2020. For subgroup analysis: see @Kavelaars2024.

MCMC Settings

Default settings usually work, but you can adjust:

bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  n_it = 10000  # More iterations for smoother estimates
)
#> 
#> Multivariate Bernoulli Analysis
#> =================================
#> 
#>    Group mean y1 mean y2
#>  placebo   0.520   0.481
#>     drug   0.616   0.634
#>   n(placebo) = 50    n(drug) = 50
#> 
#> Posterior probability P(drug > placebo) [All rule]: 0.791
#> 
#> Use summary() for credible intervals and ESS.

For bglm() and bglmm():

bglm(
  # ... arguments ...
  n_burn = 2000,  # Burn-in / warm-up iterations
  n_it = 5000,    # Sampling iterations
  n_thin = 1      # Keep every nth sample (reduces memory)
)

Missing Data

Missing values are automatically handled via complete-case analysis:

# Introduce missing data
trial_data_missing <- trial_data
trial_data_missing$symptom_relief[1:5] <- NA

result_missing <- bmvb(
  data = trial_data_missing,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  n_it = 1000
)
#> Warning in check_input(data = data, grp = grp, grp_a = grp_a, grp_b = grp_b, :
#> Missing data detected in group placebo (5 rows). Rows with missing data will be
#> removed.

# Sample sizes are reduced
result_missing$sample_sizes
#> $n_a
#> [1] 45
#> 
#> $n_b
#> [1] 50

Comparison with Frequentist Approaches

Traditional approach

# Separate tests for each outcome
chisq_symptom <- chisq.test(table(trial_data$treatment, trial_data$symptom_relief))
chisq_qol <- chisq.test(table(trial_data$treatment, trial_data$quality_of_life))

cat("Frequentist p-values:\n")
#> Frequentist p-values:
cat("  Symptom relief:", chisq_symptom$p.value, "\n")
#>   Symptom relief: 0.4191152
cat("  Quality of life:", chisq_qol$p.value, "\n")
#>   Quality of life: 0.1584835
cat("  Bonferroni-adjusted alpha: 0.025 (needed for Any-rule only; not needed for All-rule)\n\n")
#>   Bonferroni-adjusted alpha: 0.025 (needed for Any-rule only; not needed for All-rule)

Bayesian approach

result_bayes <- bmvb(
  data = trial_data,
  grp = "treatment",
  grp_a = "placebo",
  grp_b = "drug",
  y_vars = c("symptom_relief", "quality_of_life"),
  rule = "All",
  n_it = 1000
)

cat("Bayesian result:\n")
#> Bayesian result:
cat("  P(drug better on BOTH):", result_bayes$delta$pop, "\n")
#>   P(drug better on BOTH): 0.787

Advantages of Bayesian approach

  1. Direct probability statements: “95% probability drug is better” vs. “reject null at alpha=0.05”
  2. Flexible decision rules: All/Any/Weighted combinations
  3. Coherent uncertainty: Full sample of posterior distribution available

Next Steps

Explore the other vignettes:

References

Statistical methodology: More details about statistical methodology can be found here in the papers below.

When using the bmvb() function, please cite: Kavelaars, X., J. Mulder, and M. Kaptein (2020). “Decision-making with, multiple correlated binary outcomes in clinical trials”. In:, Statistical Methods in Medical Research 29.11, pp. 3265-3277. DOI:, 10.1177/0962280220922256.

When using the bglm() function, please cite: Kavelaars, X., J. Mulder, and M. Kaptein (2024). “Bayesian Multivariate, Logistic Regression for Superiority and Inferiority Decision-Making, under Observable Treatment Heterogeneity”. In: Multivariate Behavioral, Research 59.4, pp. 859-882. DOI:, 10.1080/00273171.2024.2337340.

When using the bglmm() function, please cite: Kavelaars, X., J. Mulder, and M. Kaptein (2023). “Bayesian multilevel, multivariate logistic regression for superiority decision-making under, observable treatment heterogeneity”. In: BMC Medical Research, Methodology 23.1. DOI:, 10.1186/s12874-023-02034-z.