---
title: "Winter 2-HT Model"
subtitle: "Modeling Full Lineup Outcomes"
author:
- "Colin G. Tredoux"
- "Tamsyn M. Naylor"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Winter 2-HT Model}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

> **Acknowledgement.** Large language models supplied by Anthropic and OpenAI
> were used in preparing this vignette, to assist with checking code, testing
> the veracity of the approaches described, and on occasion editing the text.
> Responsibility for the content rests with the authors.

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

## Introduction

This vignette demonstrates the **two-high-threshold (2-HT) model** for lineup
data introduced and validated by Winter, Menne, Bell and Buchner (2022).

**Attribution.** The model, its parameterisation and its validation are theirs;
they report four experiments, one targeting each parameter. The implementation
here was written from the published model equations and adapted into this
package's data conventions. No code was reused.

### The general approach

The model is a **multinomial processing tree** (MPT). MPT models differ from
signal detection theory, and the two are easily confused.

Signal detection theory posits a continuous evidence variable and a criterion
placed on it. An MPT instead posits a small number of **discrete latent
states**, each reached with some probability, with the observable response
determined by which state the witness lands in. The parameters are the branching
probabilities of that tree. Neither is "the" right description; they answer
different questions, and the MPT has the practical advantage that its parameters
map onto processes one can manipulate experimentally — which is what Winter et
al. did to validate them.

For a lineup the tree runs as follows. A witness either **detects** the state of
the lineup or does not. If they do not detect, they either select someone
because the suspect stands out, or they guess, or they reject.

Formally, for a lineup of size \(L\):

*Target-present lineups*

\[
\begin{aligned}
P(\text{suspect ID}) &= d_P + (1-d_P)\bigl[b + (1-b)\,g\,\tfrac{1}{L}\bigr] \\
P(\text{filler ID})  &= (1-d_P)(1-b)\,g\,\tfrac{L-1}{L} \\
P(\text{reject})     &= (1-d_P)(1-b)(1-g)
\end{aligned}
\]

*Target-absent lineups*

\[
\begin{aligned}
P(\text{suspect ID}) &= (1-d_A)\bigl[b + (1-b)\,g\,\tfrac{1}{L}\bigr] \\
P(\text{reject})     &= d_A + (1-d_A)(1-b)(1-g)
\end{aligned}
\]

### The four parameters

**dP — detection of culprit presence.** The probability that a witness viewing a
target-present lineup recognises the culprit. This is the memory parameter for
guilty lineups: a witness in this state identifies the suspect for the right
reason.

**dA — detection of culprit absence.** The probability that a witness viewing a
target-absent lineup recognises that the culprit is not there. Note this is a
*separate* process from dP, which is what makes the model "two-high-threshold":
detecting presence and detecting absence are not two ends of one continuum but
two distinct states, either of which may fail. A witness may fail to detect
absence without thereby detecting presence.

**b — biased suspect selection.** Given that no detection occurred, the
probability of choosing the suspect *because the suspect stands out* — not
because of memory, and not by chance. This is the lineup fairness parameter, and
it is treated in detail [below](#what-b-tells-you-about-lineup-fairness).

**g — guessing-based selection.** Given no detection and no bias, the
probability of choosing someone anyway rather than rejecting. This is
willingness to choose, and in a fair lineup the choice falls on the suspect with
probability \(1/L\) and on a foil with probability \((L-1)/L\) — which is why
those fractions appear in the equations.

Read together: dP and dA are about **memory**, g is about **willingness to
choose**, and b is about **the lineup itself**. Separating those three is the
point of the exercise, since an identification rate confounds all of them.

The implementation in **r4lineups** accepts either raw data (data frame) or
aggregated counts.

## Data format

We use the built-in `lineup_example` dataset, which already contains the required columns:

```{r}
library(r4lineups)
data(lineup_example)
head(lineup_example)
```

## 1. Fit the model using counts

First, convert the data to the 2 × 3 count format expected by `fit_winter_2ht()`:

```{r}
counts <- c(
  n_tp_suspect = sum(lineup_example$target_present & lineup_example$identification == "suspect"),
  n_tp_filler = sum(lineup_example$target_present & lineup_example$identification == "filler"),
  n_tp_reject = sum(lineup_example$target_present & lineup_example$identification == "reject"),
  n_ta_suspect = sum(!lineup_example$target_present & lineup_example$identification == "suspect"),
  n_ta_filler = sum(!lineup_example$target_present & lineup_example$identification == "filler"),
  n_ta_reject = sum(!lineup_example$target_present & lineup_example$identification == "reject")
)

fit <- fit_winter_2ht(counts, lineup_size = 6)
fit$parameters
```

## 2. Model diagnostics and plots

```{r plot_params}
plot_2ht_parameters(fit)
```

```{r plot_fit}
plot_2ht_fit(fit)
```

The second plot is the one to check before interpreting anything: it compares
observed response proportions against those the fitted model predicts. Visible
discrepancies mean the model is not describing the data, and the parameters
should not be read as though it were.

## 3. Bootstrap confidence intervals

```{r}
set.seed(123)
boot_fit <- boot_winter_2ht(
  counts,
  lineup_size = 6,
  nboot = 200,
  conf_level = 0.95,
  method = "percentile"
)

boot_fit$ci
```

The percentile intervals stay inside the parameter space, and
`plot_2ht_parameters()` uses them when given a bootstrap object:

```{r plot_boot}
plot_2ht_parameters(boot_fit)
```

All four parameters are probabilities bounded in [0, 1]. At a boundary —
`b` here — regular Hessian standard errors are unavailable. Percentile intervals
respect the parameter space, but this alone does not establish nominal coverage
at a boundary.

For larger datasets, increase `nboot` to 1000 or more for stable intervals.

## 4. What `b` tells you about lineup fairness {#what-b-tells-you-about-lineup-fairness}

This is arguably the most useful thing the model does.

Classical lineup fairness measures — Tredoux's E′, functional size, the
proportion of mock witnesses choosing the suspect — are computed from
**mock-witness data**: people who never saw the culprit, choosing from a
description. They ask whether the suspect stands out.

The 2-HT model answers the same question **from the witness data themselves**,
with no mock-witness study required. Look again at where `b` sits in the tree: it
is the probability of choosing the suspect *given that no detection occurred and
before any guessing*. A witness in that state has no memorial basis for choosing
the suspect, so if they do, it is because the lineup pointed them there.

In a perfectly fair lineup, `b = 0`: a non-detecting witness who chooses lands on
the suspect with probability \(1/L\), purely by chance, and that is already
captured by the `g/L` term. **Any `b` above zero is a measure of unfairness**,
on a probability scale, with a confidence interval.

### Does it work?

The claim can be checked against Tredoux's (1998) E′, computed with `esize_T()`
as \(1/\sum p_i^2\) over choices across all six members (it is defined in the
main vignette). E′ measures the **effective number of functioning
alternatives**.

Note where E′ comes from here: the **target-absent lineups**. Their choice
distribution across members gives a *resultant* effective size, as used by
Fitzgerald, Tredoux and Juncu (2023), without running a separate mock-witness
study. It is not the same measurement as a mock-witness E′: real witnesses have
a memory of the culprit and may reject the lineup, whereas mock witnesses choose
from a description, and the two families of measures can differ (Lee, Mansour
and Penrod, 2024). See the main vignette on the small-sample
properties of the estimate, which are not negligible.

```{r fairness_demo}
scenarios <- list(
  "Fair: foils match suspect"      = rep( 0.0, 5),
  "Uniformly weak foils"           = rep(-1.6, 5),
  "Uneven foils, suspect typical"  = c( 1.6,  0.9, -1.5, -2.5, -3.5),
  "Uneven foils, one dominant"     = c( 2.5, -3.0, -3.0, -3.0, -3.0)
)

fairness <- do.call(rbind, lapply(names(scenarios), function(nm) {
  d <- as.data.frame(simulate_lineup_data(
    n_tp = 1500, n_ta = 1500, d_prime = 1.4, c_criterion = 0.9,
    lineup_size = 6, conf_levels = 5,
    foil_bias = scenarios[[nm]], seed = 101
  ))
  fit_s <- fit_winter_2ht(d, lineup_size = 6,
                          target_present = "target_present",
                          identification = "identification")

  ta     <- d[!d$target_present, ]
  chosen <- ta$lineup_member[ta$lineup_member > 0]
  E      <- esize_T(table(factor(chosen, levels = 1:6)))

  data.frame(
    `Lineup`              = nm,
    `b (2-HT)`            = round(fit_s$parameters[["b"]], 3),
    `E' (Tredoux)`        = round(E, 2),
    `dP (2-HT)`           = round(fit_s$parameters[["dP"]], 3),
    check.names = FALSE
  )
}))

knitr::kable(
  fairness, row.names = FALSE,
  caption = "The 2-HT bias parameter against effective size (Tredoux, 1998), on the same simulated data (1500 target-present and 1500 target-absent lineups, nominal size 6)."
)
```

Weakening all the foils uniformly — the first two rows — makes `b` rise and E′
fall together, which is the expected behaviour.

**But `b` and E′ are not the same measure, and the last two rows show it.** With
uneven foils, E′ collapses to 2.00 and then 1.06, because a single dominant foil
absorbs most of the choices and the lineup has almost no functioning
alternatives. `b` stays at zero throughout — correctly, because in those lineups
the suspect is chosen *below* chance and is, if anything, protected by the
arrangement. `b` measures whether the **suspect** stands out; E′ measures how
many **alternatives** are functioning. Report both.

The practical value of `b` is that it is available **when mock-witness data are
not**: a lineup already administered to real witnesses can be assessed for
suspect bias retrospectively.

### A caution that comes with it

Notice the `dP` column across the first two rows. Apparent detection of culprit
presence *rises* as the foils are weakened uniformly. That is not better memory.
When the suspect stands out, the culprit in a target-present lineup stands out
too, so more witnesses land on them — and the model, which has no way of knowing
why, partly books this as detection.

So an unfair lineup does not merely inflate false identifications; it inflates
the apparent memory performance of the procedure that produced them.
**Report `b` alongside `dP`, never `dP` alone.** A high `dP` from a lineup with a
high `b` is not evidence of a good procedure.

## Technical Details

### Estimation

Parameters are estimated by maximum likelihood over the six observed cell counts
(three responses by two lineup types), using the multinomial likelihood implied
by the equations above. The target-present and target-absent totals are each
fixed, so each three-category multinomial contributes two independent
proportions: four data degrees of freedom against four free parameters, leaving
**zero residual degrees of freedom**. A single-condition fit is therefore
saturated (just identified), as Winter et al. (2022, Figure 1) state and as
`summary()` reports. The model reproduces the observed counts by construction,
so its absolute fit cannot be tested from one condition. Testing it needs
several conditions with parameters constrained across them. Standard errors come from the numerically differentiated Hessian at the
optimum, and `fit_winter_2ht()` reports AIC, BIC and the log-likelihood, all of
which are well defined here because this is a genuine maximum-likelihood fit.

Note the contrast with `fit_lineup_models()` in
`vignette("lineup_memory_models")`, which minimises Pearson chi-square rather
than maximising a likelihood, and for which AIC and BIC are therefore returned
as `NA`. The two should not be compared by information criteria.

### Identifiability

`dA` is identified almost entirely by rejections: it is the probability of
correctly concluding the culprit is absent, and a witness in that state rejects.
The amount of information depends on both the response probabilities and sample
size. A low rejection percentage alone does not establish nonidentifiability.
Inspect sparse cells, boundary estimates and convergence; regular Hessian
uncertainty is not available at a boundary. In simulated data a liberal criterion
can produce few rejections — see `vignette("simulation_power_analysis")` on why
`c_criterion = 0` is not a neutral value.

### Confidence intervals

`fit_winter_2ht()` returns regular Hessian standard errors for interior fits;
they are unavailable at a parameter boundary. `boot_winter_2ht()` gives
approximate percentile or BCa intervals while keeping TP and TA totals fixed.
Respecting the parameter space is useful, but does not establish coverage near
a boundary. The plotting function uses the supplied intervals.

### Assumptions

1. Each witness contributes one lineup decision, so the six cells partition the
   sample.
2. Target-present and target-absent lineups are independent samples.
3. The detection processes are all-or-none: a witness either detects or does
   not, with no partial information. This is the "high threshold" assumption,
   and it is what distinguishes the model from a signal detection account.
4. `lineup_size` is the number of **functioning** alternatives. In a lineup with
   null foils the nominal size overstates this, and since \(1/L\) appears
   directly in the equations, `g` and `b` will be biased. Check
   `lineup_table()` for null foils before fitting.

## References

::: {.hanging-indent}

Fitzgerald, R. J., Tredoux, C. G., & Juncu, S. (2023). Estimation of eyewitness error rates in fair and biased lineups. *Law and Human Behavior, 47*(4), 463-483.

Lee, J., Mansour, J. K., & Penrod, S. D. (2024). How to measure lineup fairness: Concurrent and predictive validity of lineup-fairness measures. *Psychology, Crime & Law*. https://doi.org/10.1080/1068316X.2024.2307358

Malpass, R. S. (1981). Effective size and defendant bias in eyewitness identification lineups. *Law and Human Behavior, 5*(4), 299-309.

Tredoux, C. G. (1998). Statistical inference on measures of lineup fairness. *Law and Human Behavior, 22*(2), 217-237.

Winter, K., Menne, N. M., Bell, R., & Buchner, A. (2022). Experimental validation of a multinomial processing tree model for analyzing eyewitness identification decisions. *Scientific Reports, 12*, 15571.

:::

## Further Reading

- `?fit_winter_2ht`, `?boot_winter_2ht`, `?plot_2ht_parameters`
- `vignette("model_comparison")` for fitting this model alongside EIG and full
  ROC
- The main vignette for the classical fairness measures that `b` recovers
