---
title: Inference You Can Report
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Inference You Can Report}
  %\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)
```

Mixed-model users want p-values. They also want to know what those
p-values mean: which method produced them, and how trustworthy that
method is for this particular fit. `lme4` reports the number;
`mixeff` reports the number alongside its provenance.

When a coefficient, contrast, or term test has an available method,
the p-value is printed with the method name. When the requested
method is unavailable on this fit, the row says so, with a stable
[reason](inference-method-glossary.html) rather than an apologetic substitute.

## What model are we fitting?

```{r clinic-data, include = FALSE}
set.seed(42)
n_clinic <- 12L
n_per_clinic <- 6L
clinic <- factor(rep(seq_len(n_clinic), each = n_per_clinic))
week <- rep(0:(n_per_clinic - 1L), n_clinic)
treatment <- factor(
  rep(rep(c("usual", "coached"), each = n_per_clinic), length.out = length(week)),
  levels = c("usual", "coached")
)
clinic_shift <- rnorm(n_clinic, sd = 0.45)
score <- 7.2 - 0.28 * week - 0.55 * (treatment == "coached") +
  clinic_shift[as.integer(clinic)] + rnorm(length(week), sd = 0.35)
clinic_visits <- data.frame(score, week, treatment, clinic)
```

```{r fit-model}
fit <- lmm(
  score ~ week + treatment + (1 | clinic),
  clinic_visits,
  control = mm_control(verbose = -1)
)
```

```{r fit-checks, include = FALSE}
stopifnot(inherits(fit, "mm_lmm"))
stopifnot(all(is.finite(fixef(fit))))
```

The fixed effects ask whether scores change across weeks and whether the
coached program differs from usual care, while clinic baselines are allowed to
vary.

## Coefficient p-values

Start with the ordinary coefficient table.

```{r coefficient-summary}
coef_table <- summary(fit, tests = "coefficients", method = "auto")$coefficients
knitr::kable(coef_table, digits = 4)
```

The last column tells you the method used for each available p-value.

```{r inference-table}
inference_table(fit)
```

```{r inference-checks, include = FALSE}
inf <- inference_table(fit)$table
stopifnot(any(inf$status == "available"))
stopifnot(all(is.finite(inf$p_value[inf$status == "available"])))
```

## Contrasts

`contrast()` is the direct route when you know the fixed-effect comparison you
want. This contrast asks whether the coached program differs from usual care.

```{r contrast-test}
L <- c(0, 0, 1)
names(L) <- names(fixef(fit))

contrast(fit, L, method = "satterthwaite")
```

```{r contrast-checks, include = FALSE}
ct <- contrast(fit, L, method = "satterthwaite")
stopifnot(identical(ct$table$status, "available"))
stopifnot(is.finite(ct$table$p_value))
```

## Term tests

Use `test_effect()` for a named fixed-effect term.

```{r term-test}
test_effect(fit, "treatment", method = "kenward_roger")
```

Single-model `anova()` gives the same kind of term-level table.

```{r anova-test}
anova(fit, type = "III", method = "kenward_roger")
```

```{r term-checks, include = FALSE}
te <- test_effect(fit, "treatment", method = "kenward_roger")
av <- anova(fit, type = "III", method = "kenward_roger")
stopifnot(identical(te$table$status, "available"))
stopifnot(any(av$table$status == "available"))
stopifnot(is.finite(te$table$p_value))
```

## Model comparisons

For nested fixed-effect comparisons, fit the reduced model and compare it with
the full model.

```{r model-comparison}
reduced <- lmm(
  score ~ week + (1 | clinic),
  clinic_visits,
  control = mm_control(verbose = -1)
)

compare(reduced, fit)
```

`compare()` records that likelihood-ratio p-values are asymptotic. If you want
a simulation-based check for a small example, use the bootstrap path.

```{r bootstrap-comparison}
compare(reduced, fit, method = "bootstrap", nsim = 10, seed = 7)
```

```{r comparison-checks, include = FALSE}
cmp <- compare(reduced, fit)
boot <- compare(reduced, fit, method = "bootstrap", nsim = 10, seed = 7)
stopifnot(is.finite(cmp$table$p_value[[2L]]))
stopifnot(identical(boot$table$method[[2L]], "parametric_bootstrap_lrt"))
stopifnot(boot$table$status[[2L]] %in% c("available", "not_assessed"))
if (identical(boot$table$status[[2L]], "available")) {
  stopifnot(is.finite(boot$table$p_value[[2L]]))
}
```

## Unavailable is still useful information

Population-level prediction standard errors and intervals are available via
`re.form = NA` (the Wald SE of the fixed-effect linear predictor):

```{r prediction-se-population}
pop <- predict(fit, re.form = NA, se.fit = TRUE)
head(pop$se.fit)
head(predict(fit, re.form = NA, interval = "confidence"))
```

*Conditional* prediction standard errors (the default, `re.form = NULL`) come
from the engine's prediction-variance payload, which adds the random-effect
(BLUP) variance and the fixed/random covariance to the fixed-effect Wald
variance — a surface `lme4::predict()` does not offer at all. Conditional
confidence and prediction intervals come from the same payload.

```{r prediction-se}
pred <- predict(fit, se.fit = TRUE)
head(pred$fit)
head(pred$se.fit)
head(predict(fit, interval = "confidence"))
```

The boundary has moved, not vanished: rows the engine cannot certify — for
example an unseen grouping level predicted with `allow.new.levels = TRUE`,
where there is no posterior variance for the missing level — still return
`NA` with the engine's reason in the `mm_reason` attribute rather than a
fabricated number.

```{r unavailable-checks, include = FALSE}
stopifnot(all(is.finite(pop$se.fit)))
stopifnot(all(is.finite(pred$se.fit)), all(pred$se.fit > 0))
```
