--- 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)) ```