The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Using bayprior Priors with rstanarm and brms

Ndoh Penn

2026-10-09

1 Overview

bayprior handles prior specification, elicitation, pooling, and diagnostics; it does not fit posterior models itself. This vignette shows how to take a bayprior prior object’s fitted hyperparameters and hand them to rstanarm or brms for the model-fitting step, using the same historical-trial data as the MAP Priors example in the package’s main vignette and companion paper.

Both rstanarm and brms are Suggested, not Imported, dependencies: this vignette’s code only runs if both are installed.

have_rstanarm <- requireNamespace("rstanarm", quietly = TRUE)
have_brms     <- requireNamespace("brms",     quietly = TRUE)
have_deps     <- have_rstanarm && have_brms

2 Deriving the prior

library(bayprior)

map <- map_prior(
  y            = c(-0.85, -0.62, -1.10),
  se           = c(0.25, 0.30, 0.28),
  outcome_type = "single_arm_log_odds",
  label        = "Historical control (3 trials)"
)

mu    <- map$fit_summary$mean
sigma <- map$fit_summary$sd
c(mu = mu, sigma = sigma)
#>         mu      sigma 
#> -0.8618678  0.3349206

map$dist is "normal", and mu/sigma are its mean and SD on the log-odds scale – exactly the scale a logistic-regression intercept uses. This is what makes the handoff below possible without any distributional-family conversion: both packages already expect a Normal prior on that scale for an intercept term.

3 Handoff to rstanarm

rstanarm::normal() evaluates its location/scale arguments as ordinary R expressions, so mu and sigma can be passed directly. Setting autoscale = FALSE is important: rstanarm otherwise may rescale the SD you supplied relative to the data before fitting, which would silently change the prior bayprior computed.

library(rstanarm)

# A toy current-trial dataset, sized only to keep this vignette's build
# time short -- substitute your own trial data in practice.
set.seed(1)
n_trial  <- 40
p_trial  <- 0.32
dat <- data.frame(y = rbinom(n_trial, 1, p_trial))

# Wrapped in tryCatch so an incomplete local C++/Stan toolchain degrades
# this vignette gracefully (a build failure here would otherwise fail
# R CMD check on such a machine) rather than as a claim this code is
# untested -- it has been run successfully end to end where the toolchain
# is complete.
fit_rstanarm <- tryCatch(
  stan_glm(
    y ~ 1,
    data            = dat,
    family          = binomial(link = "logit"),
    prior_intercept = normal(location = mu, scale = sigma, autoscale = FALSE),
    chains          = 1,
    iter            = 500,
    refresh         = 0,
    seed            = 1
  ),
  error = function(e) {
    message("Model fit skipped (local Stan toolchain issue): ", conditionMessage(e))
    NULL
  }
)

if (!is.null(fit_rstanarm)) print(fit_rstanarm, digits = 3)
#> stan_glm
#>  family:       binomial [logit]
#>  formula:      y ~ 1
#>  observations: 40
#>  predictors:   1
#> ------
#>             Median MAD_SD
#> (Intercept) -0.794  0.211
#> 
#> ------
#> * For help interpreting the printed output see ?print.stanreg
#> * For info on the priors used see ?prior_summary.stanreg
cat("rstanarm not installed -- skipping this demo.\n")

4 Handoff to brms

brms::prior() is not a drop-in replacement here: it captures its argument by non-standard evaluation and stores it as literal text, so brms::prior(normal(mu, sigma), class = "Intercept") would store the string "normal(mu, sigma)" – the variable names, not their values – and fail at fit time. brms::prior_string() builds the prior specification from an already-evaluated R string instead, and is the reliable way to pass numeric values programmatically:

library(brms)

prior_spec <- prior_string(
  paste0("normal(", mu, ",", sigma, ")"),
  class = "Intercept"
)
prior_spec
#> Intercept ~ normal(-0.861867808691721,0.334920636951119)

# See the rstanarm chunk above for why this is wrapped in tryCatch.
fit_brms <- tryCatch(
  brm(
    y ~ 1,
    data    = dat,
    family  = bernoulli(link = "logit"),
    prior   = prior_spec,
    chains  = 1,
    iter    = 500,
    refresh = 0,
    seed    = 1,
    silent  = 2
  ),
  error = function(e) {
    message("Model fit skipped (local Stan toolchain issue): ", conditionMessage(e))
    NULL
  }
)

if (!is.null(fit_brms)) summary(fit_brms)
#>  Family: bernoulli 
#>   Links: mu = logit 
#> Formula: y ~ 1 
#>    Data: dat (Number of observations: 40) 
#>   Draws: 1 chains, each with iter = 500; warmup = 250; thin = 1;
#>          total post-warmup draws = 250
#> 
#> Regression Coefficients:
#>           Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
#> Intercept    -0.84      0.23    -1.32    -0.45 1.01       97       66
#> 
#> Draws were sampled using sampling(NUTS). For each parameter, Bulk_ESS
#> and Tail_ESS are effective sample size measures, and Rhat is the potential
#> scale reduction factor on split chains (at convergence, Rhat = 1).
cat("brms not installed -- skipping this demo.\n")

5 Summary

Package Correct call Common mistake
rstanarm normal(location = mu, scale = sigma, autoscale = FALSE) Omitting autoscale = FALSE lets rstanarm silently rescale the SD
brms prior_string(paste0("normal(", mu, ",", sigma, ")"), class = "Intercept") prior(normal(mu, sigma), ...) stores the variable names as text, not their values

Both handoffs apply specifically to an intercept term representing the same quantity map_prior() (or an elicited prior on the log-odds scale) was fit to – not to slope/coefficient priors (class = "b" in brms, prior = in rstanarm), which are on a different parameter and should not receive these values unmodified.

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.