---
title: "Multilevel Descriptive Statistics"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Multilevel Descriptive Statistics}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

```{r setup, message = FALSE}
library(mlstats)
library(dplyr)
```

This vignette covers `within_between_correlations()` and `mldesc()` in detail,
including all available options and guidance on interpreting the results. For a
brief introduction to both functions see `vignette("mlstats")`.

## Example Data

We use `media_diary`, a simulated daily diary dataset included with **mlstats**
(100 participants over 14 days; *N* = 100 persons, *T* = 1,400 daily observations). The variables are:

- **`person`**: person identifier
- **`self_control`**: trait self-control (stable between-person characteristic;
  ICC ≈ 1)
- **`wellbeing`**: daily positive wellbeing (1–7)
- **`screen_time`**: minutes of entertainment media consumed that day
- **`stress`**: daily perceived stress (1–7)
- **`enjoyment`**: enjoyment of the media watched that day (1–7)

```{r data}
data("media_diary")
vars <- c("self_control", "wellbeing", "screen_time", "stress")
```

## Why a Single Correlation Is Not Enough

When observations are nested within groups, the overall (pooled) correlation
between two variables is a weighted mix of two conceptually different
relationships: how variables co-vary within groups over time, and whether
groups that score higher on one variable also score higher on the other. These
two relationships can differ substantially in magnitude or can even point in
opposite directions. 

## Within- and Between-Group Correlations

`within_between_correlations()` returns a single correlation matrix in which
within-group correlations appear **above** the diagonal and between-group
correlations appear **below**. In our example dataset, observations are nested
within persons, so the function computes within-person correlations (upper triangle)
and between-person correlations (lower triangle).

```{r correlations, warning = FALSE}
within_between_correlations(
  data  = media_diary,
  group = "person",
  vars  = vars
)
```

### Reading the Matrix

**Upper triangle — within-person correlations:**
Each coefficient answers: *for a given person, on days when they score higher
than their own average on variable X, do they also tend to score higher than
their own average on variable Y?* For example, the cell where `stress` meets
`wellbeing` in the upper triangle tells us that days with higher stress than
usual also tend to be days with lower wellbeing for the same person. For
time-invariant variables (e.g., trait self-control), within-group correlations
cannot be estimated and appear as `NA` in the upper triangle.

**Lower triangle — between-person correlations:**
Each coefficient answers: *do persons who score higher on variable X on average
also tend to score higher on variable Y on average?* For example, the cell where
`stress` meets `wellbeing` in the lower triangle tells us that people who are more
stressed on average also tend to have worse wellbeing overall.

### When Within and Between Diverge

Importantly, within-group and between-group correlations can sometimes differ in direction:

```{r diverging, warning = FALSE}
within_between_correlations(
  data  = media_diary,
  group = "person",
  vars  = c("wellbeing", "screen_time")
)
```

The two correlations for `screen_time` and `wellbeing` tell different stories:

- **Within-person (upper triangle):** Positive. On days when a person watches
  more entertainment media than they typically do, their wellbeing tends to be
  slightly higher than their own average — consistent with short-term escapism
  or mood repair.
- **Between-person (lower triangle):** Negative. People who watch more
  entertainment media on average tend to have lower average wellbeing — perhaps
  because chronic heavy media use reflects lower trait self-control.

Now look at what a naive pooled correlation gives:

```{r naive-cor}
cor(media_diary$screen_time, media_diary$wellbeing)
```

The pooled correlation is near zero. A researcher relying only on this number
would conclude that entertainment media use has no relationship with wellbeing —
missing a real positive within-person effect *and* a real negative
between-person association that point in opposite directions.

### Estimation Method and Weighting

Three estimation methods for within- and between-group correlations are available via
the `method` argument:

- **`method = "decomposition"` (default)**: A fast, closed-form
  variance-decomposition approach (Pedhazur, 1997). Within-group correlations
  are computed from group-mean-centred scores; between-group correlations
  are computed from the group means.
- **`method = "sem"`**: Fits a two-level structural equation model using robust
maximum likelihood. This handles very unequal group sizes more rigorously for significance testing.
- **`method = "bayes"`**: Fits Bayesian multivariate models via `brms`,
  reporting credible intervals instead of p-values. See
  `vignette("correlation-methods")` for the statistical rationale and
  "Bayesian Estimation" below for the additional arguments it requires.

```{r method-options-1, warning = FALSE, eval = FALSE}
within_between_correlations(
  data   = media_diary,
  group  = "person",
  vars   = vars,
  method = "sem" # or "bayes"
)
```

The `weight` argument (decomposition method only) controls how much influence
each group has on the between-group estimate:

- **`weight = TRUE` (default)**: Between-group correlations and descriptive
  statistics are weighted by group size. Recommended for unbalanced data to
  recover the correct population-level estimate (Snijders & Bosker, 2012).
- **`weight = FALSE`**: Every group counts equally regardless of size.

Note that significance tests for between-group correlations always use
unweighted group means regardless of the `weight` setting — see
`vignette("correlation-methods")` for the statistical rationale.

```{r method-options-2, warning = FALSE, eval = FALSE}
within_between_correlations(
  data   = media_diary,
  group  = "person",
  vars   = vars,
  weight = FALSE
)
```

## Multilevel Descriptive Statistics

`mldesc()` extends `within_between_correlations()` by adding means, standard
deviations, ranges, and ICCs to the output:

```{r mldesc, warning = FALSE}
result <- mldesc(
  data  = media_diary,
  group = "person",
  vars  = vars
)

result
```

### Customising the Output

Several options control the appearance:

```{r mldesc-options, warning = FALSE}
mldesc(
  data                = media_diary,
  group               = "person",
  vars                = vars,
  significance        = "detailed",  # *, **, *** for p < .05, .01, .001
  flip                = TRUE,        # between above diagonal, within below
  remove_leading_zero = FALSE        # keep "0.45" instead of ".45"
)
```

- **`significance = "detailed"`**: Adds one star for *p* < .05, two for
  *p* < .01, and three for *p* < .001. The default (`"basic"`) marks only
  *p* < .05.
- **`flip = TRUE`**: Swaps the positions of within- and between-person
  correlations (between above the diagonal, within below).
- **`remove_leading_zero = FALSE`**: Keeps the leading zero in decimal numbers.
  The default removes it for APA formatting (`.45` instead of `0.45`).

The `method` and `weight` arguments can also be used and work exactly as for
`within_between_correlations()` (see above).

### Pipe-Friendly Output

Although the main purpose is to produce publication-ready tables, the output of
both `mldesc()` and `within_between_correlations()` are ordinary tibbles. The
correlation and ICC columns use the class `mlstats_stat` (abbreviated `mls`) to
store formatted strings. To use these values in further calculations, cast them
to `numeric`, which strips formatting and significance stars:

```{r pipe-friendly}
result_num <- result[c(1, 6:10)]
result_num[-1] <- lapply(result[6:10], as.numeric)
as_tibble(result_num)
```

## Bayesian Estimation

`method = "bayes"` (available for both `within_between_correlations()` and
`mldesc()`) requires the **brms** package, which is not installed with
**mlstats** by default (`install.packages("brms")`), and two additional
arguments:

- **`folder`**: a directory where fitted `brms` models are cached. Re-running
  the same call with the same data and settings reloads the cached model
  instead of refitting.
- **`ci`**: the width of the credible interval used to mark correlations
  (default `0.9`).

```{r bayes-example, warning = FALSE, eval = FALSE}
mldesc(
  data   = media_diary,
  group  = "person",
  vars   = vars,
  method = "bayes",
  folder = "brms_models",
  ci     = 0.95
)
```

MCMC sampling settings (iterations, chains) can be adjusted globally via
`options(mlstats.brms_iter = ..., mlstats.brms_chains = ...)`. See
`vignette("correlation-methods")` for the statistical rationale, the
weighting/credible-interval mechanics, and how many models a given call
fits.

## References

Bürkner, P.-C. (2017). brms: An R package for Bayesian multilevel models using
Stan. *Journal of Statistical Software, 80*(1), 1–28.
https://doi.org/10.18637/jss.v080.i01

Enders, C. K., & Tofighi, D. (2007). Centering predictor variables in
cross-sectional multilevel models: A new look at an old issue.
*Psychological Methods, 12*(2), 121–138.
https://doi.org/10.1037/1082-989X.12.2.121

Pedhazur, E. J. (1997). *Multiple regression in behavioral research: Explanation
and prediction* (3rd ed.). Harcourt Brace.

Snijders, T. A. B., & Bosker, R. J. (2012). *Multilevel analysis: An
introduction to basic and advanced multilevel modelling* (2nd ed.). SAGE.
