--- title: "NMF-Based Super-Level Analysis and ECMSMA" author: "Atsushi Kawaguchi" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: true number_sections: true vignette: > %\VignetteIndexEntry{NMF-Based Super-Level Analysis and ECMSMA} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5) library(msma) ``` # Overview Version 3.2 adds NMF and sparse NMF (sNMF) as super-level decomposition methods. They support clustering-oriented analyses of multiblock data while the default `sprmethod = "PCA"` preserves the version 3.1 computational path. ```{r data} dat <- simdata(n = 45, rho = 0.8, Xps = c(5, 5, 5), Yps = 3, seed = 3) X <- dat$X names(X) <- paste0("block", seq_along(X)) ``` # Super-level methods The principal options are: - `sprmethod = "PCA"`: conventional super-level PCA; - `sprmethod = "NMF"`: non-negative matrix factorization; - `sprmethod = "sNMF"`: sparse non-negative matrix factorization. ```{r compare-methods} fit_pca <- msma(X, comp = c(2, 2), sprmethod = "PCA", intseed = 1) fit_nmf <- msma(X, comp = c(2, 2), sprmethod = "NMF", intseed = 1) fit_snmf <- msma( X, comp = c(2, 2), sprmethod = "sNMF", lambdaXsup = 0.05, intseed = 1 ) ``` # Non-negative transformations NMF requires non-negative input. The `nneg` argument controls transformation of the block scores: - `"posneg"`: separates positive and negative parts; - `"absolute"`: uses absolute values; - `"min"`: shifts values to a non-negative range. For a score vector \(s\), positive-negative decomposition is \[ s^+ = \max(s,0), \qquad s^- = \max(-s,0), \qquad s=s^+-s^-. \] ```{r nneg-options, eval=FALSE} fit_posneg <- msma(X, comp = c(2, 2), sprmethod = "NMF", nneg = "posneg") fit_absolute <- msma(X, comp = c(2, 2), sprmethod = "NMF", nneg = "absolute") fit_min <- msma(X, comp = c(2, 2), sprmethod = "NMF", nneg = "min") ``` # Non-negativity and reproducibility ```{r nonnegative} all(unlist(fit_snmf$ssX) >= 0) all(unlist(fit_snmf$wsX) >= 0) ``` ```{r reproducibility} fit_snmf_2 <- msma( X, comp = c(2, 2), sprmethod = "sNMF", lambdaXsup = 0.05, intseed = 1 ) all.equal(fit_snmf$ssX, fit_snmf_2$ssX) all.equal(fit_snmf$wsX, fit_snmf_2$wsX) ``` # Clustering solutions A simple cluster assignment is obtained from the largest super-score value for each observation. Each root component supplies one clustering solution. ```{r clustering} cluster_matrix <- vapply( fit_snmf$ssX, function(score) max.col(score, ties.method = "first"), integer(nrow(fit_snmf$ssX[[1]])) ) colnames(cluster_matrix) <- names(fit_snmf$ssX) head(cluster_matrix) apply(cluster_matrix, 2, table) ``` The resulting matrix may be supplied to a separate consensus or ensemble clustering procedure. The consensus step is not performed automatically by `msma()`. # Multiple supervision variables `Z` may be a numeric matrix. `con4spv` specifies weights used to combine its columns. This is a composite-supervision model rather than a multi-task model with a separate loading for every outcome. ```{r multivariate-z} set.seed(3) z1 <- rnorm(nrow(X[[1]])) z2 <- 0.5 * z1 + rnorm(nrow(X[[1]]), sd = 0.5) Z <- cbind(clinical = z1, biomarker = z2) fit_multi_z <- msma( X = X, Z = Z, con4spv = c(0.7, 0.3), comp = 2, muX = 0.20, intseed = 1 ) fit_multi_z$predictiv ``` Conceptually, the combined supervision score is \[ z_c = Zc, \] where the supplied weights are normalized internally as required by the implementation. # One-column matrix compatibility A vector and a one-column matrix produce the same result. ```{r one-column-z} fit_z_vector <- msma(X, Z = z1, comp = 1, muX = 0.20, intseed = 1) fit_z_matrix <- msma(X, Z = cbind(z1), comp = 1, muX = 0.20, intseed = 1) fit_z_vector$call <- NULL fit_z_matrix$call <- NULL isTRUE(all.equal(fit_z_vector, fit_z_matrix, tolerance = 1e-8)) ``` # Practical recommendations - Fix `intseed` for reproducible NMF and sNMF results. - Inspect both `ssX` and `wsX` when interpreting clusters. - Compare several values of `lambdaXsup` for sNMF. - Treat clustering from different root components as distinct candidate solutions before ensemble integration. # Session information ```{r session-info} sessionInfo() ```