---
title: "Using bayprior Priors with rstanarm and brms"
author: "Ndoh Penn"
date: "`r Sys.Date()`"
output:
  rmarkdown::html_vignette:
    toc: true
    toc_depth: 3
    number_sections: true
vignette: >
  %\VignetteIndexEntry{Using bayprior Priors with rstanarm and brms}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse   = TRUE,
  comment    = "#>",
  warning    = FALSE,
  message    = FALSE
)
```

# 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.

```{r check-deps}
have_rstanarm <- requireNamespace("rstanarm", quietly = TRUE)
have_brms     <- requireNamespace("brms",     quietly = TRUE)
have_deps     <- have_rstanarm && have_brms
```

# Deriving the prior

```{r map-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)
```

`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.

# 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.

```{r rstanarm-demo, eval = have_rstanarm}
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)
```

```{r rstanarm-skip, eval = !have_rstanarm}
cat("rstanarm not installed -- skipping this demo.\n")
```

# 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:

```{r brms-demo, eval = have_brms}
library(brms)

prior_spec <- prior_string(
  paste0("normal(", mu, ",", sigma, ")"),
  class = "Intercept"
)
prior_spec

# 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)
```

```{r brms-skip, eval = !have_brms}
cat("brms not installed -- skipping this demo.\n")
```

# 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.
