---
title: "Fitting a hierarchical SDT model"
description: >
  A complete pass over the uSDT workflow, from two trial-level data frames to
  the three hypotheses of the unconscious-processing design, using the
  contextual cuing data of Vadillo, Malejka and Shanks (2025).
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Fitting a hierarchical SDT model}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r}
#| label: setup
#| include: false
knitr::opts_chunk$set(
  collapse   = FALSE,
  comment    = "",
  fig.width  = 8,
  fig.height = 5.2,
  fig.align  = "center",
  out.width  = "100%",
  dpi        = 150
)

# The printed output rules the tables with box characters in a UTF-8 locale
# and falls back to ASCII when there is none, so the vignette asks for UTF-8
# to read the same wherever it is built. It only asks when the session is not
# already there, and the last chunk puts the width and the locale back as they
# were.
old_options <- options(width = 80)
old_ctype   <- Sys.getlocale("LC_CTYPE")

if (!isTRUE(l10n_info()[["UTF-8"]])) {
  for (loc in c("en_US.UTF-8", "C.UTF-8", "English_United States.utf8")) {
    suppressWarnings(Sys.setlocale("LC_CTYPE", loc))
    if (isTRUE(l10n_info()[["UTF-8"]])) break
  }
  # Windows accepts locale names it does not actually have, so trust
  # l10n_info() to verify the locale, and undo the attempts when none
  # of the names produced a UTF-8 session.
  if (!isTRUE(l10n_info()[["UTF-8"]])) {
    invisible(suppressWarnings(Sys.setlocale("LC_CTYPE", old_ctype)))
  }
}
```

```{r}
#| label: library
library(uSDT)
```


## What is uSDT for?

uSDT models paired **awareness** and **influence** measures from the same
participants. It estimates their mean sensitivities, between-subject variation
and latent association. The latent regression estimates influence sensitivity
at zero awareness sensitivity. Interpreting this as unconscious processing
requires a suitable model and an adequate awareness measure.

Prepare the data with `usdt_data_tasks()`, fit with `hsdt()`, then inspect
the tests, plots and reliability.

## The data

The package includes Experiment 2 of
[Vadillo, Malejka, and Shanks (2025)](https://doi.org/10.1037/xlm0001410).
`vadillo_awareness` contains old/new recognition judgements.
`vadillo_cuing` contains search response times. Contextual cuing predicts
faster responses to repeated (`old`) displays.

```{r}
#| label: data-awareness
head(vadillo_awareness)
```

```{r}
#| label: data-influence
head(vadillo_cuing)
```

Subject IDs link participants across tasks. Individual trials need not be
paired. Column names and condition labels may differ between tasks.

This example uses all supplied trials, including search errors, across blocks
and display conditions. It demonstrates the package workflow using the source
data. Define exclusions and select the relevant trials before dichotomizing.
The package does not remove errors or RT outliers
automatically.

## Preparing the data

[`usdt_data_tasks()`](../reference/usdt_data.html) takes one data frame per
task. Give shared arguments once and task-specific arguments as
`list(awareness = ..., influence = ...)`.

```{r}
#| label: prepare
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)
)
```

The median split is computed **within subject, pooling conditions** (Meyen
et al., 2022). RTs below the median become signal responses. Ties become noise
responses by default. A hit is an old display searched faster than the median.
A false alarm is a new display searched faster than the median. The resulting
$d'$ measures discrimination by this binary classifier.

With balanced signal/noise trials and an exact half split, the classical
criterion is zero under the default deviation coding. Ties, odd trial counts
or unequal condition counts can change this. `hsdt()` fixes a dichotomized
task's criterion to zero when its mean absolute subject criterion is below
0.02. `fix_criteria = "none"` estimates all criteria.

`coding = "deviation"` codes signal/noise as $+0.5/-0.5$, so the probit
intercept is $-c$. Treatment coding uses $1/0$, giving the intercept
$z(FAR)=-\lambda$, where $\lambda=c+d'/2$. Both define sensitivity as
$d'=z(HR)-z(FAR)$. They specify different random-effects models when
criteria and sensitivities are constrained to independent blocks, so fitted
sensitivities need not be identical.

```{r}
#| label: prepare-print
data_usdt
```

Check subject overlap, role mappings, trial counts and extreme rates.
The model uses two binomial rows per task and subject (signal and noise),
four rows per subject here. Extreme rates are fitted directly. Descriptive
$d'$ values use the Hautus correction where needed.

For a single long-format frame, use
[`usdt_data_long()`](../reference/usdt_data.html) with `task_col` and
`task_levels = c(awareness = "A", influence = "I")`.

## Fitting the model

[`hsdt()`](../reference/hsdt.html) fits a binomial probit mixed model by
maximum likelihood with the Laplace approximation in lme4.

```{r}
#| label: fit
fit_uSDT <- hsdt(data_usdt)
summary(fit_uSDT)
```

```{r}
#| label: helpers
#| include: false
h <- function(term) fit_uSDT$tests[match(term, fit_uSDT$tests$term), ]
fmt <- function(x, digits = 2) formatC(x, format = "f", digits = digits)
ci <- function(term) {
  r <- h(term)
  sprintf("[%s, %s]", fmt(r$conf.low), fmt(r$conf.high))
}
```

Fixed sensitivity effects are task means. Random effects describe subject
departures. `sd(d')` measures between-subject variation and `cor(d')`
the latent correlation. Subject estimates combine the fixed effects with
conditional random-effect modes. Inspect convergence and singularity before
interpreting the tests.

### The three hypotheses

The default tests are two-sided against zero, without multiplicity adjustment.

- **H1, mean difference:** $\Delta d'=\mu_I-\mu_A$ (influence minus awareness).
  Here it is `r fmt(h("d'(influence) - d'(awareness)")$estimate)`,
  95% CI `r ci("d'(influence) - d'(awareness)")`.
  The interval is below zero, favouring awareness sensitivity. An influence
  advantage would require a positive difference. Its interpretation also
  depends on how the tasks measure conscious information.
- **H2, latent correlation:** $\rho=$
  `r fmt(h("correlation")$estimate)`, 95% CI `r ci("correlation")`.
  This describes the association between latent subject sensitivities. The wide interval provides little precision. A nonsignificant test
  does not establish independence.
- **H3, latent regression:** the slope is
  `r fmt(h("slope")$estimate)` and the intercept is
  `r fmt(h("intercept")$estimate)`, 95% CI `r ci("intercept")`.
  The intercept is expected influence sensitivity at **latent awareness
  sensitivity zero**, not the mean of participants selected for low observed
  scores. Its interval includes zero, leaving both zero and positive influence
  sensitivity compatible with these data.

Writing the awareness variance as $\tau_A^2$ and the covariance as $\tau_{AI}$,

$$
E(d'_I\mid d'_A=x)=\beta_0+\beta_1 x,\qquad
\beta_1=\tau_{AI}/\tau_A^2,\qquad
\beta_0=\mu_I-\beta_1\mu_A.
$$

A positive intercept requires an adequate awareness measure and a suitable
latent model to support an unconscious-processing interpretation. It may
involve extrapolation to zero. The analytical tests of H2 and the H3 slope
share a covariance test, so they provide the same test of association.

### Equivalence and one-sided tests

[`usdt_equivalence()`](../reference/usdt_equivalence.html) tests specified
bounds. Choose them from theory or previous studies. These only illustrate
the syntax.

```{r}
#| label: equivalence
usdt_equivalence(fit_uSDT, c("diff in [-0.2, 0.2]", "intercept < 0.2",
                            "rho in [-0.3, 0.3]"))
```

At $\alpha=.05$, equivalence and one-sided tests report 90% intervals.
Here the sensitivity difference is equivalent within $\pm0.2$ despite being
different from zero. The intercept and correlation tests do not establish
their proposed bounds. Correlation tests use the Fisher-$z$ scale, so their
p-values can differ from the covariance test in `usdt_tests()`.

## Reliability of individual differences

[`usdt_reliability()`](../reference/usdt_reliability.html) estimates
$\tau^2/(\tau^2+\overline v)$ for each task: between-subject sensitivity
variance divided by total variance, with measurement variance $\overline v$
from binomial-probit information at the fitted probabilities.

```{r}
#| label: reliability
reliability <- usdt_reliability(fit_uSDT)
reliability
```

Reliability is about .13 for awareness and .32 for influence. Low reliability
can reflect little between-subject variation, substantial trial noise, or both.
It limits precision for individual differences even when the mean effect
is clear. The latent model accounts for trial noise under its assumptions.
It does not guarantee unbiased or precise estimates in every sample.

```{r}
#| label: reliability-subjects
head(reliability$subjects)
```

## Visualizing the model

### Latent regression and shrinkage

```{r}
#| label: plot-regression
plot(fit_uSDT)
```

The left panel regresses observed influence $d'$ on observed awareness $d'$.
Measurement error in the predictor can flatten the slope. The right panel
draws the latent regression implied by the fitted covariance, with segments
connecting observed pairs to their conditional model estimates. The line is
not fitted through those partially pooled points. Its band is a pointwise
confidence interval for the regression mean.

Use `nrow = 2` to stack the panels and `caption = FALSE` to hide the
caption. The shrinkage plot isolates the movement of the subject estimates:

```{r}
#| label: plot-shrinkage
plot(fit_uSDT, type = "shrinkage")
```

### Subject intervals

```{r}
#| label: plot-caterpillar
#| fig.height: 7
plot(fit_uSDT, type = "caterpillar")
```

Observed intervals use Miller standard errors and a normal approximation.
Model intervals include uncertainty in the means, variance components and
subject effects when the joint covariance is available. The percentages
describe intervals containing zero. They do not classify participants as
aware or unaware.

### ROC curves

```{r}
#| label: plot-roc
#| fig.width: 5.8
#| fig.height: 6
plot(fit_uSDT, type = "roc")
```

These equal-variance SDT curves describe a subject whose random effects are
set to zero. Points mark fitted criteria and bands
transform the sensitivity intervals. Binary responses identify one operating
point per task. The full curves follow from the model.

```{r}
#| label: plot-roc-subject
#| fig.width: 5.8
#| fig.height: 6.4
plot(fit_uSDT, type = "roc", subject_id = 2040)
```

Subject curves use partially pooled estimates, with population curves dashed
behind them. Their bands condition on the fitted population parameters.

## Further analyses

[`usdt_boot()`](../reference/usdt_boot.html) adds parametric bootstrap
inference to a frequentist fit:

```{r}
#| label: bootstrap
#| eval: false
fit_boot <- usdt_boot(fit_uSDT, nsim = 1000, ncores = 2, seed = 2026)
summary(fit_boot)
fit_boot$tests
```

At least 500 usable replicates are required before bootstrap inference
replaces the analytical results. Converged boundary refits are retained when
all hypothesis estimates are finite. Inspect failures and retained boundary
counts in `fit_boot$boot`. The bootstrap simulates binary responses at the
original cell sizes. It does not repeat the RT median split or resolve a lack
of information in the design. Read the returned `$tests` table to retain
bootstrap results. `usdt_tests()` recomputes analytical tests.

[Bayesian estimation](uSDT-bayesian.html) covers priors, posterior summaries
and Bayes factors with `hsdt(estimation = "bayesian")`.
[Planning a study](uSDT-power.html) covers power, sample sizes and detectable
effects. For covariates, see the custom lme4 and brms examples in
[`usdt_tests()`](../reference/usdt_hypotheses.html).

## References

Meyen, S., Zerweck, I. A., Amado, C., von Luxburg, U., & Franz, V. H. (2022).
Advancing research on unconscious priming: When can scientists claim an
indirect task advantage? *Journal of Experimental Psychology: General*, 151(1),
65-81. <https://doi.org/10.1037/xge0001065>

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>

```{r}
#| label: cleanup
#| include: false
options(old_options)
invisible(suppressWarnings(Sys.setlocale("LC_CTYPE", old_ctype)))
```
