This article fits the tutorial’s model and data with brms and Stan, then summarises the posterior and computes Bayes factors.
Bayesian fitting needs a C++ toolchain and either rstan (with
BH and RcppEigen) or cmdstanr with CmdStan.
The brms.backend option selects the backend. The default is
rstan and this article uses cmdstanr. Compiled models are cached for
later sessions.
We use the paired data exactly as the tutorial prepares them:
data_usdt <- usdt_data_tasks(
awareness = vadillo_awareness,
influence = vadillo_cuing,
subject_col = "subj",
condition_col = "condition",
condition_levels = c(signal = "old", noise = "new"),
response_col = list(awareness = "judged.old", influence = "rt"),
response_levels = list(awareness = c(signal = 1, noise = 0),
influence = c(signal = "faster", noise = "slower")),
dichotomize = list(awareness = FALSE, influence = TRUE)
)usdt_priors()
specifies the priors. Each prior is a string in Stan notation, and the
defaults are weakly informative on the probit scale:
── Priors ──────────────────────────────────────────────────────────────────────
Parameter Task Prior
mean d' awareness normal(0, 1)
mean d' influence normal(0, 1)
sd(d') awareness student_t(4, 0, 0.5)
sd(d') influence student_t(4, 0, 0.5)
cor(d') both lkj(1)
mean c awareness normal(0, 0.5)
mean c influence normal(0, 0.5)
sd(c) awareness student_t(4, 0, 0.5)
sd(c) influence student_t(4, 0, 0.5)
cor(c) both lkj(1)
sd(signal) awareness lognormal(0, 0.5)
sd(signal) influence lognormal(0, 0.5)
intercept both normal(0, 1)
Standard deviation priors are truncated at zero. cor(c) applies when
both criteria are estimated, and sd(signal) under unequal variances.
Under the latent regression, intercept replaces the influence mean d'.
normal(0, 1) for \(d'\) and normal(0, 0.5)
for \(c\).student_t(4, 0, 0.5).lkj(1), a uniform prior
on \((-1, 1)\). Larger values of its
argument concentrate the correlation around zero.Supported families are listed in usdt_priors().
Means need full real-line support. SDs need positive support or
truncation at zero.
The model only uses the priors of the parameters it estimates. Here the median split fixes the influence criterion at zero and both tasks have equal variances, so the influence criterion, the criterion correlation and the signal standard deviations play no role.
Before fitting anything, plot() simulates the priors to
show what they say about the data and the hypotheses:
plot of chunk prior-check
To change a prior, pass its new value to usdt_priors(),
either for both tasks or per task, and the result to
hsdt():
Setting estimation = "bayesian" in hsdt() fits the model
with brms:
Defaults are four chains, each with 1,000 warmup iterations and 5,000
kept draws. Inspect divergences, tree-depth warnings, R-hat and
effective sample size before interpreting the fit. Sampling options
include chains, iter (including warmup),
warmup, cores, seed and
control. backend selects the Stan
interface.
The summary has the same structure as the frequentist one:
── Model summary ───────────────────────────────────────────────────────────────
Subjects: 104
Observations: 416 aggregated rows (46,592 trials)
Family: binomial (probit), equal variances
Criteria: Awareness estimated, Influence fixed to 0 (Meyen split, mean |c| = 0.0000)
Estimation: brms via cmdstanr, 4 chains x 5,000 draws (warmup 1,000)
Diagnostics: max R-hat 1.002, min ESS 1,880, 0 divergent
Loading required namespace: rstan
── Fixed effects ───────────────────────────────────────────────────────────────
Parameter Task Mean SD 95% CrI R-hat ESS
criterion Awareness -0.0356 0.0289 [ -0.092, 0.021] 1.000 9,191
d' Awareness 0.2350 0.0330 [ 0.170, 0.300] 1.000 32,301
d' Influence 0.1283 0.0153 [ 0.098, 0.158] 1.000 26,275
── Random effects ──────────────────────────────────────────────────────────────
Parameter Task Mean SD 95% CrI R-hat ESS
sd(criterion) Awareness 0.2513 0.0249 [ 0.205, 0.303] 1.000 9,337
sd(d') Awareness 0.1073 0.0597 [ 0.006, 0.224] 1.000 4,354
sd(d') Influence 0.0864 0.0219 [ 0.039, 0.127] 1.000 5,186
cor(d') both 0.2902 0.4448 [ -0.740, 0.954] 1.002 1,880
── Hypotheses ──────────────────────────────────────────────────────────────────
H1: Group-level sensitivity difference (Δd' = Influence d' - Awareness d')
Parameter Mean SD 95% CrI p-value
Δd' (I - A) -0.1067 0.0358 [ -0.177, -0.036] .002
H2: Correlation between sensitivities across tasks
Parameter Mean SD 95% CrI p-value
rho 0.2902 0.4448 [ -0.740, 0.954] .484
H3: Latent regression of Influence d' on Awareness d'
Parameter Mean SD 95% CrI p-value
Intercept 0.0026 7.9765 [ -0.780, 0.761] .512
Slope 0.5406 34.1561 [ -2.712, 3.862] .484
── Priors ──────────────────────────────────────────────────────────────────────
Parameter Task Prior
mean d' Awareness normal(0, 1)
mean d' Influence normal(0, 1)
sd(d') Awareness student_t(4, 0, 0.5)
sd(d') Influence student_t(4, 0, 0.5)
cor(d') both lkj(1)
mean c Awareness normal(0, 0.5)
sd(c) Awareness student_t(4, 0, 0.5)
── Notes ───────────────────────────────────────────────────────────────────────
Mean, SD and CrI are the posterior mean, posterior SD and central
credible interval. The p-value is the two-sided posterior p-value.
Estimates are posterior means, with posterior SDs and central 95% credible intervals (CrIs). Mean sensitivities are \(d' = 0.24\) in the awareness task and \(d' = 0.13\) in the influence task.
fit_bayes$fit is a brmsfit. Its non-linear
parameters include dA and dI (sensitivities)
and cA (the classical awareness criterion). Inspect their
priors with:
prior class coef group resp dpar nlpar lb ub tag source
lkj_corr_cholesky(1) L subj user
normal(0, 0.5) b cA user
normal(0, 1) b dA user
normal(0, 1) b dI user
student_t(4, 0, 0.5) sd subj dA user
student_t(4, 0, 0.5) sd subj dI user
student_t(4, 0, 0.5) sd subj_c cA user
Criteria use a separate subject block, subj_c. Check
model adequacy with posterior predictive comparisons as well as sampler
diagnostics:
The p-value of each hypothesis is now the two-sided posterior p-value, \(2 \min\{P(\theta > 0 \mid y), P(\theta < 0 \mid y)\}\). It falls below .05 when the central 95% credible interval excludes zero (up to finite-draw rounding). This is posterior sign uncertainty, not a frequentist p-value or a Bayes factor.
A credible interval that covers zero does not show that an effect is
absent. usdt_bf()
weighs the evidence for and against each hypothesis. By default, it
tests the three point nulls with the Savage-Dickey density ratio and
draws the prior and posterior of each tested quantity:
plot of chunk bf
uSDT hypothesis ─ Savage-Dickey density ratio test
════════════════════════════════════════════════════════════════════════════════
Hypothesis (H1) : Δd' != 0
Hypothesis (H0) : Δd' = 0
Posterior mean : -0.107
Posterior SD : 0.036
95% CrI : [-0.177, -0.036]
log BF10 : 0.809
log BF01 : -0.809
Evidence : anecdotal for H1
────────────────────────────────────────────────────────────────────────────────
uSDT hypothesis ─ Savage-Dickey density ratio test
════════════════════════════════════════════════════════════════════════════════
Hypothesis (H1) : rho != 0
Hypothesis (H0) : rho = 0
Posterior mean : 0.290
Posterior SD : 0.445
95% CrI : [-0.740, 0.954]
log BF10 : -0.151
log BF01 : 0.151
Evidence : anecdotal for H0
────────────────────────────────────────────────────────────────────────────────
uSDT hypothesis ─ Savage-Dickey density ratio test
════════════════════════════════════════════════════════════════════════════════
Hypothesis (H1) : intercept != 0
Hypothesis (H0) : intercept = 0
Posterior mean : 0.003
Posterior SD : 7.976
95% CrI : [-0.780, 0.761]
log BF10 : -1.909
log BF01 : 1.909
Evidence : moderate for H0
────────────────────────────────────────────────────────────────────────────────
A point statement names H0. An inequality or region names H1. Reports give both log Bayes factors. Each plot panel reports evidence for the hypothesis in its title: BF01 for a point null, BF10 for a direction or region.
Solid curves show posteriors and dashed curves priors. For a point
null, BF01 is posterior density divided by prior density at the dotted
line. Use usdt_bf(..., plot = FALSE) to compute without
drawing, then plot(bf) to draw later.
caption = FALSE hides the caption.
You can also test directional hypotheses and regions, such as a region of practical equivalence around zero:
set.seed(2026)
bf_other <- usdt_bf(fit_bayes, c("intercept > 0", "diff in [-0.1, 0.1]"),
plot = FALSE)
bf_other uSDT hypothesis ─ Directional test
════════════════════════════════════════════════════════════════════════════════
Hypothesis (H1) : intercept > 0
Hypothesis (H0) : intercept <= 0
Posterior mean : 0.003
Posterior SD : 7.976
95% CrI : [-0.780, 0.761]
Posterior probability (H1) : 0.744
log BF10 : 1.067
log BF01 : -1.067
Evidence : anecdotal for H1
────────────────────────────────────────────────────────────────────────────────
uSDT hypothesis ─ Interval test
════════════════════════════════════════════════════════════════════════════════
Hypothesis (H1) : Δd' in [-0.1, 0.1]
Hypothesis (H0) : Δd' out [-0.1, 0.1]
Posterior mean : -0.107
Posterior SD : 0.036
95% CrI : [-0.177, -0.036]
Posterior probability (H1) : 0.421
log BF10 : 2.503
log BF01 : -2.503
Evidence : strong for H1
────────────────────────────────────────────────────────────────────────────────
These Bayes factors compare the posterior odds of H1, the share of posterior draws inside it, with its prior odds. A positive intercept has a posterior probability of .744 against .500 a priori, which is anecdotal evidence. The region \([-0.1, 0.1]\) holds 42% of the posterior but only about 6% of the prior, so the Bayes factor is strong (\(\log \text{BF}_{10} = 2.50\)) even though the posterior mean lies just outside it. Read interval Bayes factors together with the posterior probability.
Bayes factors depend on the priors. Report the priors and check sensitivity to other defensible choices by refitting. Changing prior width need not have the same effect for every hypothesis.
All the plots of the tutorial work with a Bayesian fit, and show posterior means with credible intervals.
plot of chunk plot-regression
The left panel is unchanged. In the right panel, the latent line uses the posterior means, and its 95% credible band fans out away from the group means because the slope is so uncertain. The vertical segment at \(d'_{\text{Awareness}} = 0\) is the credible interval of the intercept.
plot of chunk plot-caterpillar
The right panels show each participant’s posterior mean \(d'\) with its 95% credible interval.
Partial pooling pulls the estimates towards the group mean and narrows
their intervals. type = "shrinkage" and
type = "roc" work as in the tutorial.
The intercept of H3 has no prior of its own above: the model places
its priors on the means, standard deviations and correlation of the two
sensitivities, and the intercept prior they induce has very heavy tails.
Its Bayes factor depends on all of them. With
latent = "regression", the prior of the influence mean
moves to the intercept \(\beta_0\) of
the latent regression, the last row of usdt_priors(). The
influence mean follows from it as \(\mu_I =
\beta_0 + \rho \, (\sigma_I / \sigma_A) \, \mu_A\), and the
priors of the awareness mean, the standard deviations and the
correlation do not change.
When \(\sigma_A\) approaches zero,
the ratio \(\sigma_I/\sigma_A\) can
produce difficult posterior geometry. A prior that gives less weight
near zero can help, if substantively justified. Here we use
gamma(2, 4) for \(\sigma_A\) and retain the default for \(\sigma_I\):
priors_reg <- usdt_priors(sd_dprime = list(awareness = "gamma(2, 4)",
influence = "student_t(4, 0, 0.5)"))
plot(priors_reg, data = data_usdt, latent = "regression")plot of chunk prior-check-regression
H3 now has the specified intercept prior (default
normal(0, 1)), while H1 has the induced prior \(\Delta d' = \beta_0 +
(\rho\,\sigma_I/\sigma_A-1)\mu_A\). Inspect both the hypothesis
and predictive implications: this choice changes the prior model, even
though it reports the same quantities.
fit_reg <- hsdt(data_usdt, estimation = "bayesian", priors = priors_reg,
latent = "regression", seed = 2026)The model samples the same parameters as before, so the fit reports the same tables and hypotheses, and its header names the parameterization:
── Model summary ───────────────────────────────────────────────────────────────
Subjects: 104
Observations: 416 aggregated rows (46,592 trials)
Family: binomial (probit), equal variances
Model: latent regression of Influence d' on Awareness d'
Criteria: Awareness estimated, Influence fixed to 0 (Meyen split, mean |c| = 0.0000)
Estimation: brms via cmdstanr, 4 chains x 5,000 draws (warmup 1,000)
Diagnostics: max R-hat 1.002, min ESS 2,578, 0 divergent
── Fixed effects ───────────────────────────────────────────────────────────────
Parameter Task Mean SD 95% CrI R-hat ESS
criterion Awareness -0.0357 0.0294 [ -0.094, 0.021] 1.001 9,838
d' Awareness 0.2345 0.0343 [ 0.167, 0.301] 1.000 34,365
d' Influence 0.1282 0.0154 [ 0.098, 0.158] 1.001 25,396
── Random effects ──────────────────────────────────────────────────────────────
Parameter Task Mean SD 95% CrI R-hat ESS
sd(criterion) Awareness 0.2515 0.0254 [ 0.205, 0.305] 1.000 8,970
sd(d') Awareness 0.1330 0.0529 [ 0.034, 0.238] 1.001 6,784
sd(d') Influence 0.0866 0.0213 [ 0.041, 0.126] 1.000 6,732
cor(d') both 0.3342 0.4010 [ -0.560, 0.954] 1.002 2,578
── Hypotheses ──────────────────────────────────────────────────────────────────
H1: Group-level sensitivity difference (Δd' = Influence d' - Awareness d')
Parameter Mean SD 95% CrI p-value
Δd' (I - A) -0.1062 0.0368 [ -0.178, -0.034] .004
H2: Correlation between sensitivities across tasks
Parameter Mean SD 95% CrI p-value
rho 0.3342 0.4010 [ -0.560, 0.954] .402
H3: Latent regression of Influence d' on Awareness d'
Parameter Mean SD 95% CrI p-value
Intercept 0.0653 0.1236 [ -0.204, 0.266] .365
Slope 0.2695 0.5224 [ -0.568, 1.421] .402
── Priors ──────────────────────────────────────────────────────────────────────
Parameter Task Prior
mean d' Awareness normal(0, 1)
sd(d') Awareness gamma(2, 4)
sd(d') Influence student_t(4, 0, 0.5)
cor(d') both lkj(1)
mean c Awareness normal(0, 0.5)
sd(c) Awareness student_t(4, 0, 0.5)
intercept both normal(0, 1)
── Notes ───────────────────────────────────────────────────────────────────────
Mean, SD and CrI are the posterior mean, posterior SD and central
credible interval. The p-value is the two-sided posterior p-value.
\(\Delta d' = -0.11\) (95% CrI \([-0.18, -0.03]\)) as before. The intercept, with a prior of its own and \(\sigma_A\) kept away from zero, has the 95% credible interval \([-0.20, 0.27]\), against \([-0.78, 0.76]\) when its prior was induced by the others.
The interval of \(\rho\) is now \([-0.56, 0.95]\), against \([-0.74, 0.95]\) before. Both the intercept prior and the SD prior changed. This comparison does not isolate either one’s effect. The intercept prior also affects the correlation posterior and Bayes factor:
uSDT hypothesis ─ Savage-Dickey density ratio test
════════════════════════════════════════════════════════════════════════════════
Hypothesis (H1) : intercept != 0
Hypothesis (H0) : intercept = 0
Posterior mean : 0.065
Posterior SD : 0.124
95% CrI : [-0.204, 0.266]
log BF10 : -1.752
log BF01 : 1.752
Evidence : moderate for H0
────────────────────────────────────────────────────────────────────────────────
uSDT hypothesis ─ Savage-Dickey density ratio test
════════════════════════════════════════════════════════════════════════════════
Hypothesis (H1) : rho != 0
Hypothesis (H0) : rho = 0
Posterior mean : 0.334
Posterior SD : 0.401
95% CrI : [-0.560, 0.954]
log BF10 : -0.185
log BF01 : 0.185
Evidence : anecdotal for H0
────────────────────────────────────────────────────────────────────────────────
usdt_reliability()
returns the posterior of each task’s reliability:
── Reliability summary ─────────────────────────────────────────────────────────
Task Subjects Group mean SD 95% CrI By-subject mean median [min, max]
Awareness 104 .118 .096 [ 0.000, 0.331] .119 [.110, .120]
Influence 104 .311 .102 [ 0.086, 0.495] .311 [.310, .312]
Means, SDs and credible intervals summarise the posterior draws.
The posterior means (.118 and .311) are close to the frequentist estimates, and the credible intervals show how uncertain they are: the reliability of the awareness task could be anywhere between 0.00 and .331.
unequal_variances = TRUE is available for Bayesian
fits:
With one binary operating point per task, signal SDs are weakly
identified, so check prior sensitivity. H1–H3 then use the separation
\(d\) in noise-SD units. To compare
discriminability across different signal SDs, transform each draw to
\(d_a=d\sqrt{2/(1+\sigma_s^2)}\). See
hsdt(). The
equal-variance tutorial does not fit this extension.
When you need covariates, you can fit your own model with
brms and test it with the same functions. usdt_aggregate()
counts the trials first, which makes the fit much faster.
usdt_bf() reads the priors from the model, so the two
sensitivities need proper priors from the families of
usdt_priors():
trials <- rbind(
data.frame(vadillo_awareness[c("subj", "condition", "set.size")],
task = "A", resp = vadillo_awareness$judged.old),
data.frame(vadillo_cuing[c("subj", "condition", "set.size")],
task = "I", resp = meyen_split(vadillo_cuing$rt,
by = vadillo_cuing$subj))
)
trials <- within(trials, {
cond <- ifelse(condition == "old", 0.5, -0.5)
size <- ifelse(set.size == "set size 16", 0.5, -0.5)
awareness <- as.integer(task == "A")
})
counts <- usdt_aggregate(trials, response = "resp",
by = c("subj", "task", "cond", "size", "awareness"))
fit_brms <- brms::brm(
y | trials(n) ~ 0 + awareness + task:size + task:cond +
(0 + awareness | subj) + (0 + task:cond | subj),
data = counts, family = binomial("probit"),
prior = brms::prior(normal(0, 1), class = b)
)
usdt_tests(fit_brms, awareness = "taskA:cond", influence = "taskI:cond")
usdt_bf(fit_brms, awareness = "taskA:cond", influence = "taskI:cond")The examples of usdt_tests()
build counts from the Vadillo data with set size as a
covariate.
Vadillo, M. A., Malejka, S., & Shanks, D. R. (2025). Mapping the reliability multiverse of contextual cuing. Journal of Experimental Psychology: Learning, Memory, and Cognition, 51(6), 910-927. https://doi.org/10.1037/xlm0001410
Wagenmakers, E.-J., Lodewyckx, T., Kuriyal, H., & Grasman, R. (2010). Bayesian hypothesis testing for psychologists: A tutorial on the Savage-Dickey method. Cognitive Psychology, 60(3), 158-189. https://doi.org/10.1016/j.cogpsych.2009.12.001