---
title: Marginal Means and Comparisons
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Marginal Means and Comparisons}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

```{r load-package}
library(mixeff)
```

A fixed-effects coefficient is not a group mean. For a model with multiple
predictors and interactions, `fixef(fit)["trtactive"]` is the treatment
effect *at the reference level of every other predictor* — not the
average treatment effect across the population. Marginal means give you the
latter: population-level averages at each combination of interest, properly
accounting for the reference grid and the fixed-effect covariance.

`mixeff` provides a native marginal-quantities surface — `mm_grid()`,
`mm_predictions()`, `mm_means()`, `mm_comparisons()` — that routes all
inference through the same contract machinery as `contrast()`. Each row in
the returned table carries `method`, `status`, `reliability`, and `reason`
fields so you know exactly what you are reporting and when to be cautious.

## The study

A rehabilitation trial assigns patients to coached or usual-care treatment.
Each patient is assessed before and after the intervention. Patients are
grouped within clinics.

```{r rehab-data, include = FALSE}
set.seed(101)
n_clinic <- 4L
n_subj   <- 24L
spc      <- n_subj %/% n_clinic   # subjects per clinic

clinic <- factor(rep(paste0("C", seq_len(n_clinic)), each = spc))
trt    <- factor(
  rep(c("control", "active"), times = n_clinic * spc / 2L),
  levels = c("control", "active")
)
subj <- factor(paste0("S", seq_len(n_subj)))

rehab <- data.frame(
  subj   = rep(subj,   each = 2L),
  clinic = rep(clinic, each = 2L),
  trt    = rep(trt,    each = 2L),
  time   = factor(rep(c("pre", "post"), times = n_subj), levels = c("pre", "post"))
)

clinic_re <- c(-1.2, 0.4, 0.9, -0.1)
subj_re   <- rnorm(n_subj, sd = 0.8)

rehab$score <- 52 +
  -2.1 * (rehab$trt  == "active") +
  -3.8 * (rehab$time == "post") +
  -2.9 * (rehab$trt  == "active") * (rehab$time == "post") +
  clinic_re[as.integer(rehab$clinic)] +
  subj_re[as.integer(rehab$subj)] +
  rnorm(nrow(rehab), sd = 0.4)
```

```{r show-data}
head(rehab)
```

Clinics are not perfectly balanced between treatment arms — clinic C1 has
more control patients than average, and clinic C3 has more active-arm
patients. This imbalance means raw group means are confounded by clinic
effects: a naive `tapply(rehab$score, rehab$trt, mean)` will not answer
the treatment question you actually want.

## Fit the model

```{r fit}
fit <- lmm(score ~ trt * time + (1 | clinic) + (1 | subj), rehab)
summary(fit, tests = "coefficients")
```

The interaction coefficient `trt: active:time: post` tells you the
*additional* post-treatment change for the active arm relative to control.
It is not the average treatment effect. For that you need marginal means.

## Reference grids with `mm_grid()`

`mm_grid()` constructs the cross-product of all fixed-predictor levels. By
default, factor predictors expand to all their levels; numeric predictors
collapse to their mean.

```{r grid}
g <- mm_grid(fit, specs = ~ trt * time)
g
```

The grid has four rows — one for each treatment × timepoint cell — and
retains the model matrix needed for inference.

## Cell predictions with `mm_predictions()`

`mm_predictions()` evaluates the fixed-effect prediction at each grid row,
with a confidence interval from the certified covariance.

```{r predictions}
preds <- mm_predictions(fit, specs = ~ trt * time)
preds$table[, c("label", "estimate", "conf_low", "conf_high", "method")]
```

These are the four population-level cell means. Each row carries its
inference method so the provenance is visible without digging into model
objects.

## Marginal means with `mm_means()`

Marginal means average the reference grid over dimensions you want to
collapse. Here: average over timepoints to get the *overall* treatment effect.

```{r means-trt}
mt <- mm_means(fit, specs = ~ trt)
mt$table[, c("label", "estimate", "conf_low", "conf_high", "method")]
```

Compare these to the raw means:

```{r raw-means}
tapply(rehab$score, rehab$trt, mean)
```

The raw means are shifted by the clinic imbalance; the marginal means are
not. This difference is small in a balanced simulation but can be substantial
in real data.

## Pairwise comparisons with `mm_comparisons()`

`mm_comparisons()` takes all pairwise differences among the marginal means
and applies the same inference method.

```{r comparisons-trt}
ct <- mm_comparisons(fit, specs = ~ trt)
ct$table[, c("label", "estimate", "conf_low", "conf_high", "p_value", "method")]
```

The `active - control` row is the average treatment effect across both
timepoints, with a Satterthwaite *t* test and its certified provenance.

## Conditional comparisons with `by =`

The `by` argument splits comparisons within levels of another variable —
the analogue of simple effects in a factorial design.

```{r comparisons-by-time}
ct_by <- mm_comparisons(fit, specs = ~ trt | time)
ct_by$table[, c("label", "estimate", "conf_low", "conf_high", "p_value", "method")]
```

Two rows: the treatment difference *at pre-intervention* and the treatment
difference *at post-intervention*. The post-intervention gap is larger because
the interaction drives additional improvement in the active arm.

## Constraining the grid with `at =`

For numeric predictors, `at` pins specific values rather than collapsing to
the mean.

```{r at-example}
mt_time <- mm_means(fit, specs = ~ time, at = list(trt = "active"))
mt_time$table[, c("label", "estimate", "conf_low", "conf_high")]
```

This gives the pre/post means *within the active arm* only, holding `trt`
constant at `"active"`.

## Custom contrasts with `mm_lincomb()`

For hypotheses that are not pairwise differences of marginal means, build
the contrast weights directly with `mm_lincomb()`. The interaction effect
expressed as a contrast: (active post − active pre) − (control post − control pre).

```{r lincomb}
beta <- fixef(fit)
names(beta)

# Interaction row: coefficient named "trtactive:timepost" (lme4-identical)
w <- setNames(numeric(length(beta)), names(beta))
w["trtactive:timepost"] <- 1

lc <- mm_lincomb(fit, weights = w)
lc[, c("estimate", "lower", "upper", "p_value", "method")]
```

`mm_lincomb()` applies the same contract-preserving inference as `contrast()`,
so the method and status fields are populated identically.

## The `emmeans` bridge

When `emmeans` is installed, `mixeff` registers a basis so you can call
`emmeans::emmeans()` directly on `mm_lmm` objects.

```{r emmeans-bridge, eval = requireNamespace("emmeans", quietly = TRUE)}
if (requireNamespace("emmeans", quietly = TRUE)) {
  em <- emmeans::emmeans(fit, ~ trt)
  print(em)
  print(pairs(em))
}
```

`emmeans` uses the same `mixedmodels.fixed_effect_covariance_matrix` payload
as `mm_means()`, so the point estimates and standard errors agree. The bridge
prints an informational message noting which covariance it used.

**When to prefer the native verbs over `emmeans`:** `mm_means()` and
`mm_comparisons()` carry the full `status`, `reliability`, and `reason`
row-level audit fields from the underlying `contrast()` call. `emmeans`
does not propagate these fields; if a row is `"unavailable"` for a
documented reason, that information disappears in the `emmeans` output.
Use `emmeans` for its richer contrasts grammar (Tukey correction,
back-transformation, custom correction methods); prefer the native verbs
when auditability and report-ready provenance matter.

## Reading `status` and `reason`

Every table returned by the marginal-quantities surface has these columns:

| Column | Meaning |
|---|---|
| `status` | `"available"` / `"unavailable"` |
| `reliability` | `"certified"` / `"indicative"` / `"unavailable"` |
| `reason` | stable code (see `vignette("inference-method-glossary")`) |
| `method` | the inference method that was applied |

A row with `status = "unavailable"` contains `NA` for standard error and
p-value — the package refuses to invent them. The `reason` code tells you
why (rank deficiency, missing covariance payload, etc.) and is stable across
package versions so you can guard on it in reproducible scripts.

## Where to read next

- `vignette("inference")` — how the underlying `contrast()` machinery works
  and what inference methods are available.
- `vignette("inference-method-glossary")` — reference table of every
  `reason` code.
- `vignette("reporting-lmms")` — building a full results section from a
  fitted object.
