A toolkit for constructing, validating, and justifying Bayesian priors
in clinical trial settings. Implements expert elicitation via quantile
matching, the roulette method, and moment matching across six distribution
families, linear and logarithmic expert pooling, meta-analytic-predictive
(MAP) priors derived from historical trials, prior-data conflict
diagnostics including the Box p-value (computed from the exact prior
predictive distribution where one has a closed form), an exploratory
surprisal (S-value) companion to it, surprise index, information
divergence, and Mahalanobis distance, sensitivity analyses with tornado
and influence heatmap plots, sceptical, robust, and power priors, and
automated prior justification reports. Includes a fully modular 'Shiny'
application for interactive use.
Methods based on O'Hagan et al. (2006, ISBN:9780470029886),
Box (1980)

Structured Bayesian prior elicitation, conflict diagnostics, and regulatory reporting.
bayprior is an advanced interactive R package and Shiny application designed for biostatisticians and clinical researchers to implement Bayesian Prior Elicitation, Conflict Diagnostics, and Sensitivity Analysis for clinical trials.
bayprior focuses on a specific stage of the Bayesian trial workflow: how do you construct, validate, and justify your prior to a regulator? It brings structured elicitation, prior-data conflict diagnostics, and regulatory report generation together in one integrated workflow. The FDA's 2026 draft guidance on Bayesian methods makes this a growing area of focus.
bayprior enables users to:
| Module | Detail | Primary Output | Goal |
|---|---|---|---|
| Prior Elicitation | Quantile matching, moment matching, SHELF roulette for Beta / Normal / Gamma / Log-Normal / Exponential / Weibull | Fitted density plot + parameter table | Structured expert prior elicitation |
| Expert Pooling | Linear and logarithmic opinion pooling with support compatibility validation | Consensus density overlay + Bhattacharyya matrix | Aggregate multi-expert beliefs |
| MAP Prior (Historical) | Meta-analytic-predictive prior via random-effects meta-analysis, with an explicit prior on between-trial heterogeneity (tau) | Fitted prior + tau posterior plot | Derive an informative prior from historical trials |
| Conflict Diagnostics | Box p-value (exact prior predictive where available, otherwise Normal approximation), S-value (surprisal), surprise index, KL divergence, Bhattacharyya overlap; binary, continuous, Poisson, and survival data | Prior-Likelihood-Posterior overlay | Detect prior misspecification |
| Mahalanobis Check | Two-endpoint multivariate conflict test | Chi-sq p-value + per-parameter z-scores | Co-primary endpoint trials |
| Sensitivity Analysis | Hyperparameter grid over posterior mean, SD, CrI width, Pr(efficacy); independent data entry | Tornado plot + influence heatmap | Demonstrate robustness to regulators |
| Sceptical Prior | Spiegelhalter-Freedman centred-at-null prior | Prior density + summary statistics | Conservative regulatory sensitivity |
| Robust Mixture | Schmidli et al. MAP robust mixture prior | Robust vs informative density overlay | Protection against misspecification |
| Power Prior | Ibrahim-Chen calibrated borrowing weight via Bayes Factor | Calibration curves + optimal delta | Principled historical data borrowing |
| Export Report | HTML / PDF / Word (.docx) prior justification document via Quarto | Regulatory-ready self-contained report | Submission documentation |
Three structured elicitation approaches are implemented. Quantile matching fits a parametric distribution to expert-specified probability-value pairs via numerical optimisation. Moment matching derives hyperparameters analytically from an expert-supplied mean and SD. The SHELF roulette method (Oakley & O'Hagan, 2010) lets the expert allocate chips across histogram bins, fitting a parametric family to the chip allocation in real time.
All three methods support six distribution families:
| Family | Support | Typical use |
|---|---|---|
| Beta | (0, 1) | Response rates, proportions |
| Normal | (-Inf, Inf) | Mean differences, log odds ratios |
| Gamma | (0, Inf) | Event rates, variances, survival times |
| Log-Normal | (0, Inf) | Hazard ratios, PK parameters |
| Exponential | (0, Inf) | Constant hazard rates, Poisson rate priors |
| Weibull | (0, Inf) | Non-constant hazard survival times (OS, PFS) |
The meta-analytic-predictive (MAP) approach (Schmidli et al., 2014) derives
an informative prior directly from multiple historical trials via a
random-effects meta-analysis, with an explicit prior on the between-trial
heterogeneity parameter tau -- how much heterogeneity is assumed among
historical trials directly affects how much borrowing is justified, so
map_prior() requires this to be stated rather than left as an unstated
default (see ?resolve_tau_prior for outcome-specific defaults). It
returns an ordinary prior object that can be used directly, or passed into
robust_prior() for an additional vague-mixture component.
y/se per historical trial can be supplied directly, or derived from raw
trial-level summary statistics via the optional convenience function
historical_effect_sizes(), a thin wrapper around metafor::escalc()
(metafor is Suggests-only and not required to use map_prior() itself).
Conflict detection follows Box (1980). Complementary metrics are computed:
-log2(p): an exploratory companion to the p-value above, not a thresholded decision rule, since prior-data conflict checks are typically not powered for a binary accept/reject call.The Box p-value uses the exact prior predictive distribution wherever one has
a closed form: Beta-Binomial (Beta prior, binary data) and Gamma-Poisson /
Negative Binomial (Gamma prior, Poisson or survival data). Other combinations
use a Normal approximation to the prior and the likelihood, and
$box_pvalue_method reports which was used. exact = FALSE forces the
approximation, which can flag conflict too often for informative priors with
small samples.
Four data types are supported:
| Data type | Conjugate update | Typical endpoint |
|---|---|---|
| Binary (x events / n) | Beta-Binomial | Response rate, ORR |
| Continuous (mean, SD, n) | Normal-Normal | Mean difference |
| Poisson / count (events / exposure) | Gamma-Poisson | Adverse event rate |
| Survival (events / follow-up time) | Gamma-Exponential | Hazard rate, OS, PFS |
The sensitivity module is fully independent of conflict diagnostics — users
can enter observed data directly without running conflict diagnostics first.
Results are visualised as tornado plots and influence heatmaps.
A dedicated sensitivity_cri() function tracks credible interval width
specifically — a key regulatory quantity.
The robust mixture prior (Schmidli et al., 2014) mixes the informative prior with a vague Normal component. The sceptical prior (Spiegelhalter & Freedman, 1994) is centred at the null treatment effect. The power prior (Ibrahim & Chen, 2000) down-weights historical data by delta in (0, 1], calibrated to achieve a target Bayes Factor.
run_app() launches the full workflow as an interactive app (also available
as a live demo). The
Welcome page shows the analytical workflow, tracks which steps you have
completed, and can load a synthetic Phase II example (TRIAL-001) so you can
try the conflict check straight away.
| Type | Source | Command |
|---|---|---|
| Release | CRAN | install.packages("bayprior") |
| Development | GitHub | pak::pak("ndohpenngit/bayprior") |
PDF reports require Quarto CLI and a LaTeX installation:
install.packages("tinytex") tinytex::install_tinytex()
library(bayprior)
# Elicit a Beta prior
prior <- elicit_beta(mean = 0.35, sd = 0.10, method = "moments",
label = "Response rate", expert_id = "Expert_1")
plot(prior)
# Pool two experts
e1 <- elicit_beta(mean = 0.30, sd = 0.10, method = "moments", expert_id = "E1")
e2 <- elicit_beta(mean = 0.42, sd = 0.12, method = "moments", expert_id = "E2")
agg <- aggregate_experts(list(E1 = e1, E2 = e2), weights = c(0.6, 0.4))
# Conflict diagnostics (exact prior predictive p-value where available; exact = FALSE forces the approximation)
cd <- prior_conflict(prior, list(type = "binary", x = 18, n = 40))
print(cd)
# Derive a MAP prior from historical trials (log-odds scale)
hist_prior <- map_prior(
y = qlogis(c(0.28, 0.35, 0.31)), # historical response rates, as log-odds
se = c(0.30, 0.26, 0.34), # their standard errors
outcome_type = "single_arm_log_odds"
)
plot(hist_prior)
# Sensitivity analysis
sa <- sensitivity_grid(
prior,
data_summary = list(type = "binary", x = 18, n = 40),
param_grid = list(alpha = seq(1, 8, 0.5), beta = seq(2, 20, 1)),
target = c("posterior_mean", "prob_efficacy"),
threshold = 0.30
)
plot_tornado(sa)
# Robust and sceptical priors
rob <- robust_prior(prior, vague_weight = 0.20)
scep <- sceptical_prior(null_value = 0.20, family = "beta",
strength = "moderate")
# Generate regulatory report
prior_report(
prior = prior,
conflict = cd,
sensitivity = sa,
robust_prior = rob,
sceptical_prior = scep,
output_format = "html",
trial_name = "TRIAL-001",
sponsor = "Example Pharma Ltd",
author = "N.P., Biostatistician"
)
# Launch the Shiny app
run_app()
| Vignette | Covers |
|---|---|
bayprior-introduction |
Full end-to-end workflow overview |
prior-elicitation |
All six families and three elicitation methods |
conflict-diagnostics |
All four data types; univariate and multivariate |
sensitivity-analysis |
Grid sensitivity, tornado plots, CrI tracking |
robust-priors |
MAP priors from historical trials, robust mixture, sceptical, and power priors |
regulatory-reporting |
Report generation and compliance checklist |
using-with-rstanarm-brms |
Passing bayprior priors to rstanarm and brms models |
browseVignettes("bayprior")
Full documentation — vignettes, changelog, and cheat sheet. Function help is available in R via help(package = "bayprior") or ?bayprior.
Please note that the bayprior project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.