---
title: Mixed Effect Operators with mixed_regress()
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Mixed Effect Operators with mixed_regress()}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
params:
family: red
css: albers.css
resource_files:
- albers.css
- albers.js
includes:
in_header: |-
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4)
library(multivarious)
```
# 1. Operator-valued ANOVA
`mixed_regress()` treats each named fixed-effect term as a multivariate object rather than a scalar test. The package-level idea is:
- fit the row-side geometry once,
- extract a named effect,
- analyze that effect with the existing `multivarious` verbs.
For a term `H`, the effect matrix is
$$
M_H = P_H^{(\Omega)} Y B
$$
where:
- `Y` is the stacked observation-by-feature response matrix,
- `P_H^{(\Omega)}` is the covariance-weighted projector for the term,
- `B` is an optional shared feature basis.
The returned `effect_operator` behaves like a `bi_projector`, so you can call `components()`, `scores()`, `truncate()`, `reconstruct()`, `perm_test()`, and `bootstrap()` directly.
---
# 2. Simulate a repeated-measures design
We generate a simple low/mid/high repeated-measures dataset with a between-subject group factor, a random intercept, and a random slope on the ordered within-subject effect.
```{r simulate_data}
set.seed(1)
n_subject <- 18
levels_within <- c("low", "mid", "high")
design <- expand.grid(
subject = factor(seq_len(n_subject)),
level = factor(levels_within, levels = levels_within),
KEEP.OUT.ATTRS = FALSE
)
subject_group <- rep(c("A", "B"), length.out = n_subject)
design$group <- factor(subject_group[as.integer(design$subject)])
level_num <- c(low = -1, mid = 0, high = 1)[as.character(design$level)]
group_num <- ifelse(design$group == "B", 1, 0)
subj_idx <- as.integer(design$subject)
b0 <- rnorm(n_subject, sd = 0.7)
b1 <- rnorm(n_subject, sd = 0.3)
n <- nrow(design)
p <- 8
Y <- cbind(
b0[subj_idx] + 1.2 * level_num + rnorm(n, sd = 0.2),
0.8 * group_num + rnorm(n, sd = 0.2),
1.4 * level_num * group_num + rnorm(n, sd = 0.2),
-0.9 * level_num + rnorm(n, sd = 0.2),
b1[subj_idx] * level_num + rnorm(n, sd = 0.2),
rnorm(n, sd = 0.2),
rnorm(n, sd = 0.2),
rnorm(n, sd = 0.2)
)
dim(Y)
```
---
# 3. Fit the model
The current implementation supports one grouping variable and a shared row covariance across features. You can supply either a single random-effects bar such as `~ 1 + level | subject` or multiple bars that collapse to the same grouping variable, such as `~ (1 | subject) + (0 + level | subject)`.
```{r fit_model}
fit <- mixed_regress(
Y,
design = design,
fixed = ~ group * level,
random = ~ 1 + level | subject,
basis = shared_pca(ncomp = 4),
preproc = center()
)
print(fit)
summary(fit)
```
The fit stores:
- the design matrix,
- the grouped row metric,
- the shared feature basis,
- metadata for named fixed-effect terms.
---
# 4. Extract a named effect
Now extract the interaction effect as a first-class multivariate object.
```{r extract_effect}
E <- effect(fit, "group:level")
print(E)
ncomp(E)
components(E)[1:8, ]
```
Because `E` is an `effect_operator` and inherits from `bi_projector`, the familiar decomposition grammar carries over:
- `components(E)` gives feature directions,
- `scores(E)` gives observation-side effect scores,
- `truncate(E, k)` keeps only the first `k` axes.
---
# 5. Reconstruct the effect
You can reconstruct the fitted contribution of the effect on different scales.
```{r reconstruct_effect}
E_proc <- reconstruct(E, scale = "processed")
E_orig <- reconstruct(E, scale = "original")
dim(E_proc)
dim(E_orig)
round(E_orig[1:6, 1:4], 3)
```
Typical choices:
- `scale = "whitened"` for the covariance-adjusted effect geometry,
- `scale = "processed"` for the response scale after preprocessing,
- `scale = "original"` for the final effect contribution on the original variables.
---
# 6. Omnibus and rank inference
`perm_test()` works directly on the extracted effect object.
```{r permutation_test}
set.seed(2)
pt <- perm_test(E, nperm = 99, alpha = 0.10)
print(pt)
pt$component_results
```
The permutation result provides:
- an omnibus trace test,
- a sequential rank test based on relative singular-value statistics,
- `ncomp(pt)` as the selected number of significant effect axes.
```{r truncate_to_selected_rank}
k <- ncomp(pt)
E_sig <- truncate(E, k)
k
ncomp(E_sig)
```
---
# 7. Stability by bootstrap
Permutation asks whether an effect exists. Bootstrap asks whether the geometry is stable under subject resampling.
```{r bootstrap_effect}
set.seed(3)
bres <- bootstrap(E, nboot = 49, resample = "subject")
print(bres)
bres$singular_values_mean
```
The bootstrap result contains means and standard deviations for:
- singular values,
- feature loadings,
- full loading arrays across resamples.
---
# 8. Array input
Repeated-measures arrays can be supplied directly. Internally they are normalized to the same stacked representation.
```{r array_input}
Y_array <- array(NA_real_, dim = c(n_subject, length(levels_within), p))
idx <- 1
for (i in seq_len(n_subject)) {
for (j in seq_along(levels_within)) {
Y_array[i, j, ] <- Y[idx, ]
idx <- idx + 1
}
}
fit_array <- mixed_regress(
Y_array,
design = design,
fixed = ~ group * level,
random = ~ 1 | subject,
basis = shared_pca(ncomp = 4),
preproc = center()
)
effect(fit_array, "level")$term
```
---
# 9. Current scope
The present implementation is intentionally narrow:
- Gaussian multivariate responses,
- one grouping variable,
- random intercepts and random slopes,
- grouped permutation and subject bootstrap,
- shared feature basis or identity basis.
The calibration harness used for the empirical checks in this vignette lives in `experimental/mixed_effect_operator_calibration.R`. Batch outputs can be written to `experimental/results/` for larger Monte Carlo runs outside the package test suite.
Still to come:
- richer exchangeability schemes for repeated-measures contrasts,
- broader support beyond one grouping variable,
- more extensive mixed-model examples across design families.
---
```{r internal_checks, eval=nzchar(Sys.getenv("_MULTIVARIOUS_DEV_COVERAGE")), include=FALSE}
set.seed(4)
chk_fit <- mixed_regress(
Y,
design = design,
fixed = ~ group * level,
random = ~ 1 | subject,
basis = shared_pca(ncomp = 3),
preproc = pass()
)
chk_E <- effect(chk_fit, "group:level")
chk_pt <- perm_test(chk_E, nperm = 19, alpha = 0.2)
stopifnot(inherits(chk_E, "effect_operator"))
stopifnot(is.numeric(chk_pt$omnibus_p_value))
```