Multiblock and Nested Component Analysis

Multiblock data

Multiblock data are represented by a list of matrices. Rows are observations, columns are variables, and every block must have the same number of rows.

dat <- simdata(
  n = 40, rho = 0.8,
  Xps = c(4, 5), Yps = c(3, 4),
  seed = 2
)
X <- dat$X
Y <- dat$Y
names(X) <- c("X_block_1", "X_block_2")
names(Y) <- c("Y_block_1", "Y_block_2")
lapply(X, dim)
#> $X_block_1
#> [1] 40  4
#> 
#> $X_block_2
#> [1] 40  5
lapply(Y, dim)
#> $Y_block_1
#> [1] 40  3
#> 
#> $Y_block_2
#> [1] 40  4

Multiblock PCA

fit_mb_pca <- msma(X = X, comp = 2, intseed = 1)
fit_mb_pca
#> Call:
#> .msma_default_legacy(X = X, Y = Y, Z = Z_legacy, comp = comp, 
#>     lambdaX = lambdaX, lambdaY = lambdaY, lambdaXsup = lambdaXsup, 
#>     lambdaYsup = lambdaYsup, eta = eta, type = type, inX = inX, 
#>     inY = inY, inXsup = inXsup, inYsup = inYsup, muX = muX, muY = muY, 
#>     defmethod = defmethod, scaling = scaling, verbose = verbose, 
#>     intseed = intseed, ceps = ceps)
#> 
#> Numbers of non-zeros for X block: 
#>        comp1 comp2
#> block1     4     4
#> block2     5     5
#> 
#> Numbers of non-zeros for X super: 
#>         comp1 comp2
#> comp1-1     2     2

For X, the fitted object contains block-level weights and scores (wbX, sbX) and super-level weights and scores (wsX, ssX).

lapply(fit_mb_pca$wbX, dim)
#> $block1
#> [1] 4 2
#> 
#> $block2
#> [1] 5 2
lapply(fit_mb_pca$sbX, dim)
#> $block1
#> [1] 40  2
#> 
#> $block2
#> [1] 40  2
lapply(fit_mb_pca$wsX, dim)
#> $comp1
#> [1] 2 1
#> 
#> $comp2
#> [1] 2 1
lapply(fit_mb_pca$ssX, dim)
#> [[1]]
#> [1] 40  1
#> 
#> [[2]]
#> [1] 40  1
plot(fit_mb_pca, axes = 1, plottype = "bar", block = "block", las = 2)
plot(fit_mb_pca, axes = 1, plottype = "bar", block = "super")

Sparse multiblock PCA

lambdaX has one value per X block. lambdaXsup controls sparsity at the super level.

fit_sparse <- msma(
  X = X, comp = 2,
  lambdaX = c(0.10, 0.15),
  lambdaXsup = 0.05,
  intseed = 1
)
fit_sparse$nzwbX
#>        comp1 comp2
#> block1     3     4
#> block2     5     5
fit_sparse$nzwsX
#> $comp1
#> comp1-1 
#>       2 
#> 
#> $comp2
#> comp2-1 
#>       2

Nested components

A two-element comp specifies the numbers of root and super components:

comp = c(number_of_root_components, number_of_super_components)

For example, comp = c(2, 3) estimates three super components for each of two root components.

fit_nested <- msma(X = X, comp = c(2, 3), intseed = 1)
lapply(fit_nested$wsX, dim)
#> $comp1
#> [1] 2 3
#> 
#> $comp2
#> [1] 2 3
lapply(fit_nested$ssX, dim)
#> [[1]]
#> [1] 40  3
#> 
#> [[2]]
#> [1] 40  3
plot(fit_nested, axes = 1, axes2 = 1, plottype = "bar", block = "super")
plot(fit_nested, axes = 1, axes2 = 2, plottype = "bar", block = "super")

Supervised multiblock PCA

set.seed(2)
Z <- rnorm(nrow(X[[1]]))
fit_supervised <- msma(
  X = X, Z = Z, comp = 2,
  lambdaX = c(0.10, 0.10),
  muX = 0.20,
  intseed = 1
)
fit_supervised$predictiv
#> [1] 0.02620760 0.02331129

Multiblock PLS

fit_mb_pls <- msma(
  X = X, Y = Y, comp = 2,
  lambdaX = c(0.10, 0.10),
  lambdaY = c(0.10, 0.10),
  intseed = 1
)
fit_mb_pls
#> Call:
#> .msma_default_legacy(X = X, Y = Y, Z = Z_legacy, comp = comp, 
#>     lambdaX = lambdaX, lambdaY = lambdaY, lambdaXsup = lambdaXsup, 
#>     lambdaYsup = lambdaYsup, eta = eta, type = type, inX = inX, 
#>     inY = inY, inXsup = inXsup, inYsup = inYsup, muX = muX, muY = muY, 
#>     defmethod = defmethod, scaling = scaling, verbose = verbose, 
#>     intseed = intseed, ceps = ceps)
#> 
#> Numbers of non-zeros for X block: 
#>        comp1 comp2
#> block1     4     3
#> block2     4     5
#> 
#> Numbers of non-zeros for X super: 
#>         comp1 comp2
#> comp1-1     2     2
#> 
#> Numbers of non-zeros for Y block: 
#>        comp1 comp2
#> block1     3     3
#> block2     4     4
#> 
#> Numbers of non-zeros for Y super: 
#>         comp1 comp2
#> comp1-1     2     2

The four regularization arguments have distinct roles:

  • lambdaX and lambdaY: block-level weights;
  • lambdaXsup and lambdaYsup: super-level weights.
fit_nested_pls <- msma(
  X = X, Y = Y, comp = c(2, 2),
  lambdaX = c(0.10, 0.10),
  lambdaY = c(0.10, 0.10),
  lambdaXsup = 0.05,
  lambdaYsup = 0.05,
  intseed = 1
)
lapply(fit_nested_pls$ssX, dim)
#> [[1]]
#> [1] 40  2
#> 
#> [[2]]
#> [1] 40  2
lapply(fit_nested_pls$ssY, dim)
#> [[1]]
#> [1] 40  2
#> 
#> [[2]]
#> [1] 40  2

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