---
title: "D-studies and within-cell replicates"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{D-studies and within-cell replicates}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

```{r setup}
library(intraclass)
```

Two things you can do once you have a fitted `icc()`. The first is to project its
reliability to a different number of raters, a [**decision
study**](glossary.html#d-study-decision-study). The second, available when each cell
holds more than one rating, is to separate the subject-by-rater interaction from
pure error (**within-cell replicates**). This article also shows the `autoplot()`
methods that visualize a fit. (Unfamiliar terms are defined in the
[*Glossary*](glossary.html).)

## How many raters do I need? A D-study

`ICC(*,1)` is the reliability of a *single* rater and `ICC(*,k)` the reliability
of the mean of the `k` raters you actually used. A **decision (D-) study** asks a
forward-looking question: *how reliable would the mean of some other number of
raters `m` be?* In generalizability theory the absolute-agreement ICC is the
[dependability coefficient](glossary.html#dependability-coefficient), and projecting it
to `m` raters is just a change of the
averaging divisor,

$$\Phi(m) = \frac{\sigma^2_s}{\sigma^2_s + (\sigma^2_r + \sigma^2_{res}) / m},$$

so `d_study()` reuses the fit you already have, with no refitting.

```{r dstudy, eval = requireNamespace("glmmTMB", quietly = TRUE)}
fit <- icc(ratings, score, subject, rater, type = "agreement", seed = 1)
proj <- d_study(fit, m = 1:8, seed = 1)
proj
```

Reliability climbs with more raters, but with diminishing returns. The projection
is anchored to what you observed: at `m = 4`, the number of raters in `ratings`,
`Φ(m)` is exactly the `ICC(A,k)` you would get from `icc()` directly.

The projection has its own interval settings: `conf_level`, `mc_samples` and
`seed`. Each defaults to the fit's own whenever the fit carries them, which a
default Monte-Carlo fit does. Pass any of them to `d_study()` to override it for
this call alone. The `seed = 1` above is the fit's own seed passed explicitly. That
is why the `m = 4` row reproduces the fit's `ICC(A,k)` *interval* exactly, and not
merely its estimate.

`tidy()` turns the projection into one row per projected point, and `glance()`
summarizes the projection as a whole:

```{r dstudy-tidy, eval = requireNamespace("glmmTMB", quietly = TRUE)}
tidy(proj)

glance(proj)
```

The one column here that `tidy()` on the *fit* does not also give you is **`m`**,
the rater-count column. That column is the whole point of a D-study: the same
coefficient, indexed by the rater count it is projected to. `glance()` describes
the projection rather than the model, reporting the swept range (`n_m`, `m_min`,
`m_max`) alongside the design and the interval settings.

We fit with `type = "agreement"` here because the dependability coefficient
\eqn{\Phi(m)} above *is* the absolute-agreement projection. A default `icc()` reports
both agreement and consistency, and `d_study()` then projects **one curve per error
definition**, told apart by the `type` column. That is handy when you want both, but for
this walkthrough one curve keeps the picture clear.

Read as a curve, this is the classic "how many raters?" picture. Plot it with
`autoplot()`, which needs **ggplot2**:

```{r dstudy-plot, eval = requireNamespace("ggplot2", quietly = TRUE) && requireNamespace("glmmTMB", quietly = TRUE), fig.alt = "Projected reliability rising with the number of raters, with a Monte-Carlo interval band."}
library(ggplot2)
autoplot(d_study(fit, m = 1:12))
```

There is also a `plot()` method, for the console habit of drawing a picture as a
side effect. `plot()` on a projection prints the very same ggplot that
`autoplot()` builds from it, then returns the projection invisibly. We show the
call rather than run it, since running it would render another reliability curve
just like the one above:

```{r dstudy-plot-wrapper, eval = FALSE}
plot(proj)
```

### One projected value, without a projection object

When you want a single projected rater count rather than a whole curve, `icc()`
will report it inline. `unit` takes the keywords `"single"` and `"average"`, which
give the `ICC(*,1)` and `ICC(*,k)` you always get. Alongside them it also takes any
number `m >= 1`:

```{r dstudy-unit, eval = requireNamespace("glmmTMB", quietly = TRUE)}
icc(ratings, score, subject, rater,
  type = "agreement", unit = c("single", "average", 6), seed = 1
)
```

The extra row is labeled `ICC(A,6)`, and it is the same quantity `d_study()`
projects: the `m = 6` row of `proj` above carries the same estimate. The
*interval* matches too, but only because both calls pass the same `seed`. The
interval is Monte-Carlo, so two unseeded runs of the same projection agree on
the estimate and differ in the last digits of the endpoints. Which one you reach
for is a matter of shape, not of arithmetic: `unit` adds a row to a coefficient
table, while `d_study()` returns a projection object you can `tidy()`, `glance()`
and plot.

**Fixed raters cannot be projected to absolute agreement.** The rater term for
[*fixed* raters](glossary.html#fixed-vs--random-raters) is the variance of
exactly the raters you observed. There is no wider pool for a hypothetical sixth
rater to be drawn from, so `icc()` refuses rather than returning a number:

```{r dstudy-unit-fixed, error = TRUE, eval = requireNamespace("glmmTMB", quietly = TRUE)}
icc(ratings, score, subject, rater,
  type = "agreement", raters = "fixed", unit = c("single", "average", 6),
  seed = 1
)
```

Consistency has no such problem, because the rater term drops out of it. That is
why the two remedies the message names are to project with `raters = "random"`, or
to ask for a fixed-rater `type = "consistency"` projection instead. The refusal is
raised only when absolute agreement is *all* you asked for. A default `icc()` call
reports both error definitions, and there the consistency projection is kept and
you are told the agreement one was dropped.

**Projection is extrapolation.** The rater variance $\sigma^2_r$ is estimated from
only as many raters as you observed, so projecting far beyond that design leans
hard on that estimate. The Monte-Carlo interval widens honestly to reflect this,
rather than pretending to a precision it lacks. And projecting absolute agreement
is refused for [*fixed* raters](glossary.html#fixed-vs--random-raters), where there is
no wider rater universe to
generalize to (use `raters = "random"`). The [D-study also works on a multilevel
fit](multilevel-designs.html#how-many-raters-a-multilevel-d-study), projecting the
rater count at each level.

## Within-cell replicates: interaction vs. pure error

So far every subject-by-rater cell holds a single rating. When each rater rates each
subject **more than once**, a design with *within-cell replicates*, you can separate
two things that a single rating confounds. The first is the **subject-by-rater
interaction**: does a rater systematically score a particular subject high or low, a
*stable* disagreement? The second is **pure error**, meaning how much a rater's
repeat ratings of the same subject wobble. `icc()` detects the replicates and fits
the interaction model automatically:

```{r replicates-data, eval = requireNamespace("glmmTMB", quietly = TRUE)}
set.seed(2025)
ns <- 20
nr <- 4
no <- 3
grid <- expand.grid(subject = seq_len(ns), rater = seq_len(nr), occ = seq_len(no))
subj <- rnorm(ns, sd = 1.1)[grid$subject]
rater <- rnorm(nr, sd = 0.8)[grid$rater]
sr <- rnorm(ns * nr, sd = 0.6)[(grid$rater - 1) * ns + grid$subject]
reps <- data.frame(
  subject = factor(grid$subject),
  rater = factor(grid$rater),
  score = 10 + subj + rater + sr + rnorm(nrow(grid), sd = 0.7)
)

icc(reps, score, subject, rater, type = "agreement", occasions = c("single", "average"))
```

The variance-components line now shows `subject:rater` (the interaction) and
`residual` (pure error) as separate terms. The **single-occasion** rows, whose
`occasions` column reads 1, are the ordinary ICCs, since a single rating's
error still includes the interaction. But they are now fit correctly, rather
than folding the interaction into the residual. The **occasion-averaged** rows,
whose `occasions` column reads 3 here, divide pure error by three occasions per
rater. `ICC(A,1)` is then the reliability of one rater's *mean of three
ratings*. `ICC(A,k)` is the reliability of a mean over all four raters, each at
that same three. That 3 is the fitted per-cell replicate count. Averaging
cuts pure error but not the interaction, so those coefficients are higher.

Within-cell replicates extend beyond this balanced two-way random example: **fixed**
raters (balanced), **multilevel** designs (crossed Design 1 and nested Design 2,
balanced), and **ragged** replicates at a single occasion are all supported. What
remains open is the occasion-*averaged* coefficient on ragged replicates, where
unequal per-cell counts leave no single effective-occasion divisor with a
validated oracle. The compound fixed-by-ragged and multilevel-by-ragged corners
are open too.

## How many occasions do I need? A D-study on the occasion facet

Just as `d_study(m = ...)` projects the number of *raters*, `d_study(n_o = ...)`
projects the number of *occasions* off a replicate fit. It holds the raters fixed
and asks "how reliable would each rater's mean of `n_o` ratings be?". Supply exactly
one axis per call (`m` **or** `n_o`).

```{r occasion-dstudy, eval = requireNamespace("glmmTMB", quietly = TRUE)}
fit_rep <- icc(reps, score, subject, rater, type = "agreement", occasions = "average")
d_study(fit_rep, n_o = 1:6)
```

Notice the curve **flattens**. Averaging more occasions only cancels *pure error*
(`residual`). It never touches the rater or `subject:rater` variance. So the occasion
curve climbs to a **ceiling below 1**, rather than approaching 1 the way a rater
projection does. That ceiling is the reliability you would reach with perfectly
repeatable ratings but the same raters. Read it as "how much does re-rating help?",
which saturates.

Because occasions are a **random** facet however the raters are treated, the occasion
projection is defined even where a rater projection is not: **fixed-rater absolute
agreement projects on the occasion axis**. It is only the *rater* axis that is
undefined for fixed absolute agreement, having no "freshly sampled rater" to add. On a
**multilevel** replicate fit the subject-level curve rises with `n_o` while the
cluster-level curve is **flat**. The cluster-level error set has no pure-error term,
so occasions cannot change it, and `d_study()` says so with a note.

## Visualizing a fit

Every `icc()` fit carries an `autoplot()` method (with a `plot()` wrapper), so you
can *see* the coefficients and the variance components behind them without building
a plot by hand. Both read straight off the fitted object, so the picture can never
disagree with the printed table. They need **ggplot2**, an optional dependency.

The default, `what = "coefficients"`, is a **forest plot**: each ICC index as a
point estimate with its Monte-Carlo interval. Reusing the two-way `ratings` fit from
the D-study section above:

```{r plot-coef, eval = requireNamespace("ggplot2", quietly = TRUE) && requireNamespace("glmmTMB", quietly = TRUE), fig.alt = "Forest plot of ICC(A,1) and ICC(A,k) for the ratings data, each a point estimate with a horizontal Monte-Carlo interval, ICC(A,k) higher than ICC(A,1), and its interval slightly wider."}
library(ggplot2)
autoplot(fit) # `fit <- icc(ratings, score, subject, rater, type = "agreement", seed = 1)`
```

`what = "components"` shows the other half of the story: the estimated **variance
components** the ratio is built from. It makes plain *why* absolute agreement is so
much lower than the averaged coefficient on `ratings`. The **rater** component is
large, and only absolute agreement counts between-rater differences as error:

```{r plot-comp, eval = requireNamespace("ggplot2", quietly = TRUE) && requireNamespace("glmmTMB", quietly = TRUE), fig.alt = "Bar chart of the estimated variance components for the ratings fit: subject, rater, and residual, with the rater component the largest."}
autoplot(fit, what = "components")
```

For a **multilevel** fit the forest plot facets by level. See [*Multilevel
designs*](multilevel-designs.html#visualizing-the-levels) for that example.
