--- title: "Co-clustering two variable blocks with NMF-RRR (nmf.rrr)" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Co-clustering two variable blocks with NMF-RRR (nmf.rrr)} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} # Data-dependent chunks run only when ade4 (source of the Doubs data) is # installed, so the vignette still builds on machines without it. has_ade4 <- requireNamespace("ade4", quietly = TRUE) knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5, eval = has_ade4 ) ``` ## Introduction Many studies measure **two blocks of variables on the same individuals** -- a block of *covariates* (inputs) and a block of *responses* (outputs) -- and ask how *groups* of covariates relate to *groups* of responses. `nmf.rrr()` answers this by starting from the multivariate linear regression \eqn{Y_1 \approx M Y_2} of the responses on the covariates and giving its non-negative regression coefficient matrix a **tri-factorization** \deqn{Y_1 \;\approx\; X_1\,\Theta\,X_2\,Y_2,\qquad X_1,X_2\ge 0,\ \Theta\ge 0,} where \eqn{X_1} (each column summing to one) softly clusters the **response variables**, \eqn{X_2} (each row summing to one) softly clusters the **covariate variables**, and \eqn{\Theta} is a tested \eqn{Q\times R} matrix of **block correspondences**. Because \eqn{M = X_1\Theta X_2} has rank \eqn{\le \min(Q,R)}, this is the non-negative, parts-based member of the reduced-rank regression (RRR) family -- hence *NMF-RRR* -- related to RRR as NMF is to PCA. `nmf.rrr()` and its `nmf.rrr.*` helpers are the canonical interface; the names emphasise the reduced-rank-regression reading. The former `nmfae()` family names are retained as deprecated aliases for backward compatibility. This vignette reproduces the **Doubs** example (community ecology), where a single dominant upstream--downstream gradient aligns both blocks, so the correspondence \eqn{\Theta} is a clean *permutation*. ```{r lib} library(nmfkc) ``` ```{r no-ade4, eval = !has_ade4, echo = FALSE, results = "asis"} cat("> **Note:** this vignette needs the `ade4` package for the Doubs data.", "Install it with `install.packages(\"ade4\")` to run the code below.\n") ``` ## The Doubs data The Doubs data (Verneaux, 1973) record `27` fish species and `11` environmental variables at `30` sites along a French river -- a classic illustration of canonical (correspondence) analysis. We take the fish abundances as the **response** block \eqn{Y_1} and the environmental variables as the **covariate** block \eqn{Y_2}. Each variable is mapped to \eqn{[0,1]} by a per-variable min--max transform (`nmfkc.normalize()`), which makes the sign-free environmental variables non-negative, and both blocks are laid out as *variables* \eqn{\times} *sites* (\eqn{P \times N}). ```{r data} data(doubs, package = "ade4") # per-variable min-max to [0,1], then transpose to (variables x sites) nz <- function(M) t(nmfkc.normalize(as.matrix(M))) Y1 <- nz(doubs$fish) # responses: 27 fish species x 30 sites Y2 <- nz(doubs$env) # covariates: 11 environment x 30 sites dim(Y1) dim(Y2) ``` ## Fitting NMF-RRR Element-wise cross-validation (below) selects \eqn{Q=R=2}. Signed models and inference benefit from several k-means restarts, so we set `nstart = 20` and a tight tolerance. ```{r fit} fit <- nmf.rrr(Y1, Y2, rank1 = 2, rank2 = 2, epsilon = 1e-8, nstart = 20, seed = 1) # in-sample, column-centered R^2 Y1hat <- fit$X1 %*% fit$C %*% fit$X2 %*% Y2 R2 <- 1 - sum((Y1 - Y1hat)^2) / sum((Y1 - rowMeans(Y1))^2) round(R2, 3) ``` The fit is `R2 = 0.435`, reproducing the classical longitudinal zonation of the river. ### Response groups (fish guilds) Each column of \eqn{X_1} is a probability vector over the fish species; the top species per column name the guild. ```{r resp-groups} for (q in 1:ncol(fit$X1)) cat(sprintf("Resp%d: %s\n", q, paste(rownames(Y1)[order(-fit$X1[, q])[1:6]], collapse = ", "))) ``` `Resp1` is a **cold-water upstream guild** (brown trout *Satr*, *Phph*, *Neba*, *Cogo*, grayling *Thth*) and `Resp2` a **warm-water downstream guild** (roach *Ruru*, *Gogo*, barbel *Baba*, *Alal*). ### Covariate groups (environmental gradients) Each row of \eqn{X_2} is a probability vector over the environmental variables. ```{r cov-groups} for (r in 1:nrow(fit$X2)) cat(sprintf("Cov%d: %s\n", r, paste(rownames(Y2)[order(-fit$X2[r, ])[1:5]], collapse = ", "))) ``` `Cov1` is a **nutrient / downstream gradient** (distance from source `dfs`, flow `flo`, nitrate `nit`, BOD `bdo`) and `Cov2` an **oxic / upstream gradient** (dissolved oxygen `oxy`, altitude `alt`, pH, slope `slo`). ## Choosing the two ranks Because the attainable fit is bounded by \eqn{\min(Q,R)}, the *in-sample* fit cannot choose the ranks; we use **element-wise cross-validation** (`nmf.rrr.ecv()`), which holds out entries of \eqn{Y_1} and predicts them. ```{r ecv} ecv <- nmf.rrr.ecv(Y1, Y2, rank1 = 1:2, rank2 = 1:2, nfolds = 5, seed = 123) round(ecv$sigma, 4) ``` The smallest hold-out error is at \eqn{Q=R=2}. ## Inference for the correspondence matrix \eqn{\Theta} The entries of \eqn{\Theta} say how strongly each covariate group drives each response group. `nmf.rrr.inference()` attaches standard errors (Fisher + wild bootstrap) and a **one-sided boundary test** \eqn{H_0:\theta_{qr}=0} (each \eqn{\theta_{qr}\ge 0}). ```{r inference} inf <- nmf.rrr.inference(fit, Y1, Y2) co <- inf$coefficients print(format(co[order(co$p_value), c("Basis","Covariate","Estimate","SE","z_value","p_value")], digits = 3)) ``` \eqn{\Theta} is a **near-permutation**: the upstream guild is driven by the oxic gradient and the downstream guild by the nutrient gradient (both \eqn{p<0.001}), while the two off-diagonal paths are essentially zero (\eqn{p=0.5}). ```{r theta} round(fit$C, 3) ``` ## Visualising \eqn{\Theta} and the two co-clusterings `nmf.rrr.heatmap()` shows the response basis \eqn{X_1}, the correspondence \eqn{\Theta}, and the covariate basis \eqn{X_2} together. ```{r heatmap, fig.width = 7.5, fig.height = 4} nmf.rrr.heatmap(fit) ``` ## Relation to other methods Dropping non-negativity, \eqn{Y_1\approx M Y_2} at rank \eqn{\min(Q,R)} is ordinary **reduced-rank regression** (RRR): it attains a higher in-sample fit (\eqn{R^2\approx 0.66} on Doubs) but returns *signed* loadings and no clusters. On these data the two share the dominant fitted direction (leading principal-angle cosine \eqn{\approx 0.99}); they differ in the *basis* of that subspace -- non-negative parts versus signed singular directions -- exactly as NMF relates to PCA. An unsupervised **tri-NMF** of the association \eqn{Y_1 Y_2^\top} recovers the *same* guilds and gradients here (the gradient is so dominant that supervised and unsupervised co-clusterings coincide), but, unlike NMF-RRR, cannot predict the community at a new site through \eqn{\hat y_1 = X_1\Theta X_2\, y_2}. When within-block and cross-block structure disagree -- e.g. under \eqn{p>n} -- the non-negative, normalized parameterization of NMF-RRR stays well-behaved and exposes *cross-structure* (one response group driven by several covariate groups) that these baselines miss; see the paper for the nutrimouse and microbiome--metabolome examples. ## References - Satoh, K. & Tokuda, Y. Co-clustering of Response and Covariate Variables by Tri-Factorizing Their Non-negative Regression Coefficient Matrix (manuscript). - Ding, C., Li, T., Peng, W. & Park, H. (2006). Orthogonal nonnegative matrix tri-factorizations for clustering. *KDD*. - Verneaux, J. (1973). *Cours d'eau de Franche-Comté*. PhD thesis. (Doubs data, R package `ade4`.) ```