Extract Informative Priors from a Fitted Bayesian IRT Model
Source:R/post_helpers.R
posterior_to_prior.RdTakes a fitted brmsfit object and constructs a
brmsprior object in which each item parameter
receives a normal(mean, sd) prior derived from its posterior
distribution. The person-level random effect SD prior is also
updated. The returned prior can be passed directly to
update (or brm) to refit
the model with empirical Bayes / informative priors — useful for
anchoring scales, warm-starting a model on new data, or
regularising estimation with small samples.
Usage
posterior_to_prior(
model,
item_var = item,
person_var = id,
mult = 1,
target_link = c("source", "logit", "probit")
)Arguments
- model
A fitted
brmsfitobject. Supported parameterisations:- Polytomous ordinal
e.g.,
family = acatwiththres(gr = item), producing item-specific thresholds.- Dichotomous Rasch (random items)
e.g.,
response ~ 1 + (1 | item) + (1 | id)withfamily = bernoulli().- Dichotomous 1PL (fixed items)
e.g.,
response ~ 0 + item + (1 | id)withfamily = bernoulli().
- item_var
An unquoted variable name identifying the item grouping variable in the model data. Default is
item.- person_var
An unquoted variable name identifying the person grouping variable in the model data. Default is
id.- mult
Numeric multiplier applied to each posterior SD before it is used as the prior SD. Values > 1 widen the priors (less informative); values < 1 tighten them. Default is 1 (use posterior SD directly).
- target_link
Character string specifying the link function of the model the priors will be used with. One of
"logit","probit", or"source"(the default). When"source", the link function of the fitted model is used and no transformation is applied. When different from the source model's link, all location and scale parameters are rescaled using the approximation \(\beta_{\text{probit}} \approx \beta_{\text{logit}} / 1.7\). This is useful when transferring priors from a logit-fitted model to a probit model or vice versa.
Details
The function extracts all posterior draws via
as_draws_df, computes the mean and SD of each
parameter's marginal posterior, and constructs
normal(mean, sd * mult) priors.
Polytomous ordinal models with grouped thresholds
(thres(gr = item)): each threshold receives its own prior
via brms::set_prior("normal(...)", class = "Intercept",
group = item, coef = threshold_index).
Dichotomous Rasch models parameterised as
response ~ 1 + (1 | item) + (1 | id): priors are set on
the global intercept (class = "Intercept"), the item-level
SD (class = "sd", group = item_var), and the person-level
SD.
Dichotomous 1PL models parameterised as
response ~ 0 + item + (1 | id): each item-specific fixed
effect (e.g., b_itemI1) receives its own
normal(mean, sd) prior via
brms::set_prior(..., class = "b", coef = "itemI1").
In all cases the person-level SD receives a
normal(mean, sd * mult) prior (brms applies the lower
bound of zero automatically for SD parameters).
Link function transformation: When target_link
differs from the source model's link function, all parameters
(means and SDs) are rescaled by a factor of approximately 1.7.
This uses the well-known approximation that
\(\Phi(x) \approx \text{logistic}(1.7 \, x)\), so
logit-scale parameters can be converted to probit-scale by
dividing by 1.7, and vice versa. The approximation is excellent
for parameters in the range \(|\beta| < 3\) and adequate
beyond that range.
Examples
# \donttest{
library(brms)
library(dplyr)
library(tidyr)
library(tibble)
# --- Partial Credit Model ---
df_pcm <- eRm::pcmdat2 %>%
mutate(across(everything(), ~ .x + 1)) %>%
rownames_to_column("id") %>%
pivot_longer(!id, names_to = "item", values_to = "response")
fit_pcm <- brm(
response | thres(gr = item) ~ 1 + (1 | id),
data = df_pcm,
family = acat,
chains = 4, cores = 2, iter = 1000 # use more iter (and cores if you have)
)
#> Compiling Stan program...
#> Error in .fun(model_code = .x1): Boost not found; call install.packages('BH')
# Extract posterior-informed priors (same link)
new_priors <- posterior_to_prior(fit_pcm)
#> Error: object 'fit_pcm' not found
new_priors
#> Error: object 'new_priors' not found
# Narrow the prior's sd by a factor of 0.5
wide_priors <- posterior_to_prior(fit_pcm, mult = 0.5)
#> Error: object 'fit_pcm' not found
# Extract priors for use with a probit model
probit_priors <- posterior_to_prior(fit_pcm, target_link = "probit")
#> Error: object 'fit_pcm' not found
# --- Dichotomous 1PL (fixed item effects) ---
df_rm <- eRm::raschdat3 %>%
as.data.frame() %>%
rownames_to_column("id") %>%
pivot_longer(!id, names_to = "item", values_to = "response")
fit_1pl <- brm(
response ~ 0 + item + (1 | id),
data = df_rm,
family = bernoulli(),
chains = 4, cores = 2, iter = 1000 # use more iter (and cores if you have)
)
#> Compiling Stan program...
#> Error in .fun(model_code = .x1): Boost not found; call install.packages('BH')
priors_1pl <- posterior_to_prior(fit_1pl)
#> Error: object 'fit_1pl' not found
priors_1pl
#> Error: object 'priors_1pl' not found
# Transfer logit priors to a probit refit
priors_probit <- posterior_to_prior(fit_1pl, target_link = "probit")
#> Error: object 'fit_1pl' not found
# }