Many studies collect several tables on the same samples – e.g.
transcriptomics + metabolomics, or multiple sensor blocks. Most
single-table reductions (PCA, ICA, NMF, …) ignore that structure.
multiblock_projector is a thin wrapper that keeps track of
which original columns belong to which block, so you can
We demonstrate with a minimal two-block toy-set.
# 2-component centred PCA (using base SVD for brevity)
preproc_fitted <- fit(center(), X)
Xc <- transform(preproc_fitted, X) # Centered data
svd_res <- svd(Xc, nu = 0, nv = 2) # only V (loadings)
mb <- multiblock_projector(
v = svd_res$v, # p × k loadings
preproc = preproc_fitted, # remembers centering
block_indices = blk_idx
)
print(mb)
#> Projector object:
#> Input dimension: 12
#> Output dimension: 2
#> With pre-processing:
#> A finalized pre-processing pipeline:
#> Step 1: center# Project using only data from block A (requires original columns)
scores_A <- project_block(mb, XA, block = 1)
# Project using only data from block B
scores_B <- project_block(mb, XB, block = 2)
cor(scores_all[,1], scores_A[,1]) # high (they coincide)
#> [1] 0.7971026Because the global PCA treats all columns jointly, projecting only block A gives exactly the same latent coordinates as when the whole matrix is available – useful when a block is missing at prediction time.
Need to use just three variables from block B?
# Get the global indices for the first 3 columns of block B
sel_cols_global <- blk_idx[["B"]][1:3]
# Extract the corresponding data columns from the full matrix or block B
part_XB_data <- X[, sel_cols_global, drop = FALSE] # Data must match global indices
scores_part <- partial_project(mb, part_XB_data,
colind = sel_cols_global) # Use global indices
head(round(scores_part, 3))
#> [,1] [,2]
#> [1,] -1.045 -0.888
#> [2,] -0.142 -1.597
#> [3,] 0.469 0.647
#> [4,] -0.244 -0.410
#> [5,] -0.513 0.305
#> [6,] -0.740 0.000If you also keep the sample scores (from the original fit) you get two-way functionality: re-construct data, measure error, run permutation tests, etc. That is one extra line when creating the object:
bi <- multiblock_biprojector(
v = svd_res$v,
s = Xc %*% svd_res$v, # Calculate scores: Xc %*% V
sdev = svd_res$d[1:2] / sqrt(n-1), # SVD d are related to sdev
preproc = preproc_fitted,
block_indices = blk_idx
)
print(bi)
#> Multiblock Bi-Projector object:
#> Projection matrix dimensions: 12 x 2
#> Block indices:
#> Block 1: 1,2,3,4,5,6,7
#> Block 2: 8,9,10,11,12Now you can, for instance, test whether component-wise consensus between blocks is stronger than by chance.
# Quick permutation test (use more permutations for real analyses)
# use_rspectra=FALSE needed for this 2-block example; larger problems can use TRUE
perm_res <- perm_test(bi, Xlist = list(A = XA, B = XB), nperm = 99, use_rspectra = FALSE)
print(perm_res$component_results)
#> comp observed pval lower_ci upper_ci
#> 1 1 84.25129 0.1 78.70594 88.96802The perm_test method for
multiblock_biprojector uses an eigen-based score consensus
statistic to assess whether blocks share more variance than expected by
chance.
v (and
optionally scores s) can become multiblock-aware by
supplying block_indices.| Verb | What it does in multiblock context |
|---|---|
project() |
whole-matrix projection (uses preprocessing) |
project_block() |
scores based on one block’s data |
partial_project() |
scores from an arbitrary subset of global columns |
coef(..., block=) |
retrieve loadings for a specific block |
perm_test() |
permutation test for block consensus (biprojector) |
This light infrastructure lets you prototype block-aware analyses
quickly, while still tapping into the entire multiblock
toolkit (cross-validation, reconstruction metrics, composition with
compose_projector, etc.).
sessionInfo()
#> R version 4.5.1 (2025-06-13)
#> Platform: aarch64-apple-darwin20
#> Running under: macOS Sonoma 14.3
#>
#> Matrix products: default
#> BLAS: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib
#> LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
#>
#> locale:
#> [1] C/en_CA.UTF-8/en_CA.UTF-8/C/en_CA.UTF-8/en_CA.UTF-8
#>
#> time zone: America/Toronto
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] glmnet_5.0 Matrix_1.7-3 knitr_1.51 tibble_3.3.1
#> [5] dplyr_1.2.1 ggplot2_4.0.3 multivarious_0.3.2
#>
#> loaded via a namespace (and not attached):
#> [1] sass_0.4.10 utf8_1.2.6 generics_0.1.4
#> [4] shape_1.4.6.1 lattice_0.22-7 digest_0.6.39
#> [7] magrittr_2.0.5 evaluate_1.0.5 grid_4.5.1
#> [10] RColorBrewer_1.1-3 iterators_1.0.14 fastmap_1.2.0
#> [13] foreach_1.5.2 jsonlite_2.0.0 RSpectra_0.16-2
#> [16] survival_3.8-3 scales_1.4.0 codetools_0.2-20
#> [19] jquerylib_0.1.4 Rdpack_2.6.6 reformulas_0.4.4
#> [22] cli_3.6.6 rlang_1.2.0 chk_0.10.0
#> [25] rbibutils_2.4.1 splines_4.5.1 withr_3.0.2
#> [28] cachem_1.1.0 yaml_2.3.12 otel_0.2.0
#> [31] tools_4.5.1 corpcor_1.6.10 vctrs_0.7.3
#> [34] R6_2.6.1 matrixStats_1.5.0 proxy_0.4-29
#> [37] lifecycle_1.0.5 randomForest_4.7-1.2 MASS_7.3-65
#> [40] irlba_2.3.7 pkgconfig_2.0.3 pillar_1.11.1
#> [43] bslib_0.10.0 geigen_2.3 gtable_0.3.6
#> [46] glue_1.8.1 Rcpp_1.1.1-1.1 xfun_0.57
#> [49] tidyselect_1.2.1 farver_2.1.2 htmltools_0.5.9
#> [52] rmarkdown_2.31 labeling_0.4.3 compiler_4.5.1
#> [55] S7_0.2.2