NMF-Based Super-Level Analysis and ECMSMA

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.

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.
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^-. \]

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

all(unlist(fit_snmf$ssX) >= 0)
#> [1] TRUE
all(unlist(fit_snmf$wsX) >= 0)
#> [1] TRUE
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)
#> [1] TRUE
all.equal(fit_snmf$wsX, fit_snmf_2$wsX)
#> [1] TRUE

Clustering solutions

A simple cluster assignment is obtained from the largest super-score value for each observation. Each root component supplies one clustering solution.

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)
#>      comp1 comp2
#> [1,]     2     2
#> [2,]     2     2
#> [3,]     1     1
#> [4,]     1     2
#> [5,]     1     1
#> [6,]     1     2
apply(cluster_matrix, 2, table)
#>   comp1 comp2
#> 1    28    23
#> 2    17    22

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.

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
#> [[1]]
#>         clinical   biomarker
#> [1,] 0.004986875 0.004354675
#> 
#> [[2]]
#>      clinical  biomarker
#> [1,] 0.036251 0.04306789

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.

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))
#> [1] TRUE

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

sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] msma_4.0       rmarkdown_2.32
#> 
#> loaded via a namespace (and not attached):
#>  [1] digest_0.6.39    R6_2.6.1         fastmap_1.2.0    xfun_0.61       
#>  [5] maketools_1.3.2  cachem_1.1.0     knitr_1.52       htmltools_0.5.9 
#>  [9] buildtools_1.0.0 lifecycle_1.0.5  cli_3.6.6        sass_0.4.10     
#> [13] jquerylib_0.1.4  compiler_4.6.1   sys_3.4.3        tools_4.6.1     
#> [17] evaluate_1.0.5   bslib_0.12.0     yaml_2.3.12      otel_0.2.0      
#> [21] jsonlite_2.0.0   rlang_1.3.0