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.7228449Because 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,] -2.546 -0.723
#> [2,] 1.594 -3.584
#> [3,] 0.815 0.912
#> [4,] -0.329 -0.648
#> [5,] -2.223 1.394
#> [6,] -2.628 0.972If 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_4.1-10 Matrix_1.7-3 knitr_1.51 tibble_3.3.1
#> [5] dplyr_1.1.4 ggplot2_4.0.1 multivarious_0.3.1
#>
#> loaded via a namespace (and not attached):
#> [1] GPArotation_2025.3-1 utf8_1.2.6 sass_0.4.10
#> [4] future_1.68.0 generics_0.1.4 shape_1.4.6.1
#> [7] lattice_0.22-7 listenv_0.10.0 digest_0.6.39
#> [10] magrittr_2.0.4 evaluate_1.0.5 grid_4.5.1
#> [13] RColorBrewer_1.1-3 iterators_1.0.14 fastmap_1.2.0
#> [16] foreach_1.5.2 jsonlite_2.0.0 ggrepel_0.9.6
#> [19] RSpectra_0.16-2 survival_3.8-3 scales_1.4.0
#> [22] pls_2.8-5 codetools_0.2-20 jquerylib_0.1.4
#> [25] cli_3.6.5 crayon_1.5.3 rlang_1.1.7
#> [28] chk_0.10.0 parallelly_1.45.1 future.apply_1.20.0
#> [31] splines_4.5.1 withr_3.0.2 cachem_1.1.0
#> [34] yaml_2.3.12 otel_0.2.0 tools_4.5.1
#> [37] parallel_4.5.1 corpcor_1.6.10 globals_0.18.0
#> [40] rsvd_1.0.5 assertthat_0.2.1 vctrs_0.7.0
#> [43] R6_2.6.1 matrixStats_1.5.0 proxy_0.4-27
#> [46] lifecycle_1.0.5 MASS_7.3-65 irlba_2.3.5.1
#> [49] pkgconfig_2.0.3 pillar_1.11.1 bslib_0.9.0
#> [52] geigen_2.3 gtable_0.3.6 glue_1.8.0
#> [55] Rcpp_1.1.1 xfun_0.55 tidyselect_1.2.1
#> [58] svd_0.5.8 farver_2.1.2 htmltools_0.5.9
#> [61] labeling_0.4.3 rmarkdown_2.30 compiler_4.5.1
#> [64] S7_0.2.1