---
title: "Reporting MAIHDA results: tidy output and publication tables"
author: "Hamid Bulut"
date: "`r Sys.Date()`"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Reporting MAIHDA results: tidy output and publication tables}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

## From a fitted model to a manuscript

This vignette covers the three reporting helpers that
turn a `maihda()` analysis into publication-ready output without you recomputing
anything: **`glance()`** (the one-row headline), **`tidy()`** (estimates as a tidy
tibble), and **`maihda_table()`** (the two canonical write-up tables). They read
the quantities `summary()` already computed, so the tables always agree with
`print()`/`plot()`.

```{r model}
library(MAIHDA)
data("maihda_health_data")

a <- maihda(
  BMI ~ Age + Gender + Race + Education + (1 | Gender:Race:Education),
  data = maihda_health_data
)
```

## `glance()` -- the one-row headline

`glance()` returns the whole MAIHDA headline as a **one-row tibble**: the VPC/ICC
(with its interval when bootstrapped), the PCV, the additive/interaction shares,
the discriminatory accuracy (AUC/MOR) for a binary outcome, and the bookkeeping
(`n_strata`, `nobs`, `engine`, `family`, `mode`).

```{r glance}
glance(a)
```

This is the row no generic mixed-model tool emits, because the **PCV needs the
null + adjusted pair** that only a `maihda()` analysis holds. It is ideal for
`rbind()`-ing many analyses into a comparison table, or for pulling a single
number into inline text -- e.g. the VPC is
`r round(100 * glance(a)$vpc, 1)`% and the additive share (PCV) is
`r round(100 * glance(a)$pcv, 1)`%.

`tidy()` and `glance()` come from the lightweight **`generics`** package -- the
same generics `broom`/`broom.mixed` re-export -- so they dispatch whether you have
`broom`, `generics`, or just `MAIHDA` loaded. There is no hard `broom` dependency.

## `tidy()` -- estimates as a tidy tibble

`tidy()` returns MAIHDA estimates in broom's `estimate`/`std.error`/`conf.low`/
`conf.high` shape, ready for `dplyr`, `ggplot2`, or a table package. Three
`component`s are available.

The **stratum** estimates (the default) -- one row per intersection, with the
human-readable label:

```{r tidy-strata}
strata <- tidy(a, component = "strata")
head(strata)
```

The **variance components**:

```{r tidy-variance}
tidy(a, component = "variance")
```

And the **fixed effects** (`component = "fixed"`). For a two-model analysis,
`which = "adjusted"` reads the adjusted model instead of the default null:

```{r tidy-fixed}
tidy(a, component = "fixed", which = "adjusted")
```

Because the output is a plain tibble, you can build your *own* figure instead of
using the built-in `plot()` -- here a caterpillar of the stratum estimates,
ordered by size. The default (`which = "null"`) estimates plotted below are the
null model's *total* between-stratum deviations (additive + interaction); for a
caterpillar of the pure interaction estimates, tidy the adjusted model instead:
`tidy(a, component = "strata", which = "adjusted")`.

```{r tidy-plot, fig.height = 5}
library(ggplot2)

strata_ord <- strata[order(strata$estimate), ]
strata_ord$label <- factor(strata_ord$label, levels = strata_ord$label)

ggplot(strata_ord, aes(x = estimate, y = label)) +
  geom_vline(xintercept = 0, linetype = "dashed", colour = "grey60") +
  geom_pointrange(aes(xmin = conf.low, xmax = conf.high)) +
  labs(x = "Stratum random effect (BLUP)", y = NULL,
       title = "Intersectional strata, ordered by estimated deviation") +
  theme_minimal()
```

## `maihda_table()` -- the two canonical write-up tables

`maihda_table()` assembles the two deliverables a MAIHDA paper reports (cf. Evans
et al. 2024): a **model-results table** contrasting the null (Model 1) and
adjusted (Model 2) fits, and a **ranked-strata table** ordering the intersections
by predicted outcome. The `print()` method shows both in the familiar
"estimate [low, high]" layout:

```{r table-print}
tab <- maihda_table(a)
tab
```

The `$models` element is a **numeric, export-ready** data frame (statistics in
rows, a column per model with `*_lower`/`*_upper` interval columns), so it drops
straight into `knitr::kable()` or `write.csv()`:

```{r table-models}
knitr::kable(tab$models, digits = 3,
             caption = "MAIHDA model-results table (null vs. adjusted).")
```

```{r table-csv, eval = FALSE}
write.csv(tab$models, "maihda_results.csv", row.names = FALSE)
```

The `$strata` element holds every stratum ranked by predicted BMI (the data
behind the printed top/bottom rows):

```{r table-strata}
head(tab$strata)
```

If you use **`gt`** or **`flextable`** for formatted tables, the same numeric frame
feeds them directly (shown only if `gt` is installed):

```{r table-gt, eval = requireNamespace("gt", quietly = TRUE)}
gt::gt(tab$models)
```

## Choosing a model structure with `maihda_ic()`

The VPC and PCV do not tell you whether a different model
specification (another covariate set, strata definition, or family) fits better.
`maihda_ic()` answers that with information criteria -- `AIC`/`BIC` for the
likelihood engines, `WAIC`/`LOOIC` for brms -- and a `delta` column (gap from the
best model):

```{r ic}
maihda_ic(a)
```

## See also

- [Introduction to MAIHDA](introduction.html) -- the end-to-end workflow.
- [Finding interaction patterns](finding_interactions.html) -- which strata show
  the clearest residual deviations from additive expectations.
- [Interpreting MAIHDA plots and diagnostics](interpreting_plots.html) -- and how to
  restyle the built-in plots.

## References

- Evans, C. R., Leckie, G., Subramanian, S. V., Bell, A., & Merlo, J. (2024). A
  tutorial for conducting intersectional multilevel analysis of individual
  heterogeneity and discriminatory accuracy (MAIHDA). *SSM - Population Health*, 26,
  101664.
