Modern studies rarely describe their samples with a single table. A cohort may be profiled by several assays at once — transcriptomics, proteomics, metabolomics, methylation, microbiome — or a set of objects may be described by several heterogeneous data sources. MosaiClusteR is a framework for discovering the sample structure that these sources agree on: it takes a collection of data matrices measured over the same objects and returns an integrated clustering.
The package is deliberately a catalogue, not a single algorithm. Multi-source clustering can be approached in many ways, and no one method is best across all data regimes, so MosaiClusteR gathers a broad set of methods behind one consistent interface and adds everything you need to choose between them: preprocessing, cluster-number selection, agreement metrics, visual comparison, cluster characterisation, and — for omics — pathway and gene-set interpretation.
The methods are organised into five integration paradigms:
| Paradigm | Idea | Examples in this package |
|---|---|---|
| Direct | Concatenate the sources, cluster once | ADC, ADEC |
| Similarity-based | Combine per-source object similarities | WeightedClust, WonM, SNF,
NEMO |
| Graph-based | Partition a graph built from many clusterings | EnsembleClustering, HBGF,
ClusteringAggregation |
| Voting / consensus | Let many partitions vote on co-membership | CEC, CVAA,
ConsensusClustering, EvidenceAccumulation,
LinkBasedClustering, the ABC family |
| Hierarchy-based | Merge per-source hierarchies | HierarchicalEnsembleClustering, EHC |
| Factor / low-rank | Cluster a shared latent representation | intNMF, spectral_clustering,
LUCID |
Every method in MosaiClusteR speaks the same language.
List of numeric matrices,
one per source, all measured over the same objects in the same
order. The convention is objects in rows,
features in columns (n objects x m_k features
for source k). Sources may have different numbers of
features; they only need to share the objects.DistM (an object-by-object dissimilarity,
n x n) and Clust (a fitted hierarchical
clustering). Some methods additionally return a $cluster
label vector.type = "dist"; otherwise type = "data" (the
default) starts from the raw matrices.Because the interface is uniform, swapping one method for another is a one-line change, and the evaluation / visualisation tools work with any of them.
The package ships a small toy data set — two sources over 60 shared objects with three planted groups — used throughout this vignette.
data(mosaic_toy)
L <- mosaic_toy$List # a list of two matrices, objects in rows
truth <- mosaic_toy$truth # the known group of each object (for illustration)
S <- length(L) # number of sources
K <- 3 # number of groups
vapply(L, dim, integer(2)) # 60 objects x (120, 150) features
#> source1 source2
#> [1,] 60 60
#> [2,] 120 150You can also simulate structured multi-source data with
mosaic_sim():
A typical analysis moves through four layers: preprocess -> baseline -> integrate -> evaluate.
Make heterogeneous sources comparable and turn a source into a dissimilarity.
Xn <- Normalization(L[[1]], method = "Quantile") # optional per-source scaling
D1 <- Distance(L[[1]], distmeasure = "euclidean") # object-by-object distance
round(as.matrix(D1)[1:4, 1:4], 2)
#> obj01 obj02 obj03 obj04
#> obj01 0.00 0.22 0.21 0.23
#> obj02 0.22 0.00 0.23 0.22
#> obj03 0.21 0.23 0.00 0.24
#> obj04 0.23 0.22 0.24 0.00Distance() supports many measures —
"euclidean", "manhattan",
"tanimoto"/"jaccard" (for binary
fingerprints), and correlation-based distances among them — and
Normalization() offers quantile, standardisation and range
scaling. Binary sources (e.g. molecular fingerprints, presence/absence)
are handled by choosing an appropriate distmeasure.
Cluster each source on its own to see what it recovers in isolation. Weak or disagreeing baselines are exactly what motivates integration.
b1 <- Cluster(L[[1]], distmeasure = "euclidean", normalize = FALSE, nrclusters = 3)
b2 <- Cluster(L[[2]], distmeasure = "euclidean", normalize = FALSE, nrclusters = 3)
c(source1 = cluster_agreement(mosaic_labels(b1, 3), truth)[["ARI"]],
source2 = cluster_agreement(mosaic_labels(b2, 3), truth)[["ARI"]])
#> source1 source2
#> 1 1The whole point of the uniform interface is that you can run
many integration methods with almost identical calls and score
them the same way. Here we run one method from each paradigm and compare
how well each recovers the planted groups with the Adjusted Rand Index
(cluster_agreement() also returns NMI, Jaccard and purity).
mosaic_labels(fit, k) cuts any fitted result into
k clusters.
DM <- rep("euclidean", S); NM <- rep(FALSE, S); ME <- rep(list(NULL), S)
methods <- list(
"ADC (direct)" = function() mosaic_labels(
ADC(L, distmeasure = "euclidean", normalize = FALSE), K),
"WeightedClust (simil.)" = function() mosaic_labels(
WeightedClust(L, type = "data", distmeasure = DM, normalize = NM,
method = ME, weight = seq(1, 0, -0.25)), K),
"WonM (similarity)" = function() mosaic_labels(
WonM(L, type = "data", distmeasure = DM, normalize = NM, method = ME,
nrclusters = rep(list(2:6), S), linkage = rep("ward", S)), K),
"SNF (graph fusion)" = function() mosaic_labels(
SNF(L, type = "data", distmeasure = DM, normalize = NM, method = ME,
NN = 20, T = 20), K),
"NEMO (neighbourhood)" = function() NEMO(L, k = K, NN = 20)$cluster,
"CEC (consensus)" = function() mosaic_labels(
CEC(L, distmeasure = DM, normalize = NM, method = ME,
r = vapply(L, ncol, 1L) %/% 2L, nrclusters = as.list(rep(K, S)),
linkage = rep("ward", S)), K),
"intNMF (factor)" = function() intNMF(L, k = K, nstart = 4)$cluster,
"Data nuggets" = function() nugget_cluster(
do.call(cbind, L), k = K, max_nuggets = 40)$cluster,
"ABC (voting)" = function() mosaic_labels(
M_ABCpp(L, numsim = 60, NC = K), K)
)
ari <- vapply(methods, function(f)
tryCatch(round(cluster_agreement(f(), truth)[["ARI"]], 2),
error = function(e) NA_real_), numeric(1))
sort(ari, decreasing = TRUE)
#> ADC (direct) WeightedClust (simil.) WonM (similarity)
#> 1 1 1
#> SNF (graph fusion) NEMO (neighbourhood) CEC (consensus)
#> 1 1 1
#> intNMF (factor) Data nuggets ABC (voting)
#> 1 1 1On this toy set most methods recover the planted structure; on real data they diverge, which is why running several and comparing them is the recommended workflow rather than trusting a single algorithm.
This section describes each integration method, grouped by paradigm.
All take the same List input; the calls below are complete
and ready to adapt.
The sources are combined into one representation and clustered once.
ADC — Aggregated Data Clustering:
concatenate the sources (after optional per-source normalisation), form
a single distance and one hierarchy.ADEC — Aggregated Data Ensemble
Clustering: repeatedly subsample / reduce the concatenated data and
aggregate the resulting partitions.Each source contributes an object-by-object similarity; the similarities are combined before clustering.
WeightedClust — convex (weighted)
combination of per-source distances over a grid of weights; good when
you want to control each source’s influence.WonM — Weighting on Membership:
consensus co-membership accumulated over many cut heights, so no single
k has to be fixed up front.SNF — Similarity Network Fusion:
iteratively cross-diffuses per-source similarity networks into a single
fused network (needs SNFtool).NEMO — NEighborhood based Multi-Omics
clustering: averages neighbourhood affinities across sources and
tolerates partially missing samples.WeightedClust(L, type = "data", distmeasure = DM, normalize = NM, method = ME,
weight = seq(1, 0, -0.25))
WonM(L, type = "data", distmeasure = DM, normalize = NM, method = ME,
nrclusters = rep(list(2:6), S), linkage = rep("ward", S))
SNF(L, type = "data", distmeasure = DM, normalize = NM, method = ME, NN = 20, T = 20)
NEMO(L, k = K, NN = 20)$clusterMany base clusterings are encoded as a graph (of objects and/or clusters) that is then partitioned.
EnsembleClustering — CSPA / HGPA /
MCLA hyper-graph consensus.HBGF — Hybrid Bipartite Graph
Formulation over objects and clusters.ClusteringAggregation — aggregate many
partitions by a distance between clusterings.HBGF(L, type = "data", distmeasure = DM, normalize = NM, method = ME, nrclusters = 3)
ClusteringAggregation(L, type = "data", distmeasure = DM, normalize = NM,
method = ME, nrclusters = 3)
# EnsembleClustering() / EHC() call an external graph-partitioning executable
# (e.g. METIS); install it and pass its path via 'executable' to use them.Many partitions “vote” on whether each pair of objects belongs together; a consensus co-membership is then clustered.
CEC — (Consensus) Ensemble Clustering
by incidence accumulation across sources and cut points;
weight-aware.CVAA — Cumulative Voting Adjusted
Aggregation, aligning partitions to a reference before merging.ConsensusClustering — iterative
(probabilistic) voting consensus.EvidenceAccumulation — co-association
matrix across partitions.LinkBasedClustering — refines the
co-association with link-based similarity (CTS / SRS / ASRS).M_ABCpp,
M_ABCdist and the deep-learning variant
M_ABCdeep build a consensus by repeatedly bootstrapping
objects and feature bundles per source; M_ABCdist.WC fuses
per-source distances with an optimally weighted combination.CEC(L, distmeasure = DM, normalize = NM, method = ME,
r = vapply(L, ncol, 1L) %/% 2L, nrclusters = as.list(rep(K, S)),
linkage = rep("ward", S))
ConsensusClustering(L, type = "data", distmeasure = DM, normalize = NM,
method = ME, nrclusters = 3)
EvidenceAccumulation(L, type = "data", distmeasure = DM, normalize = NM,
method = ME, nrclusters = 3)
LinkBasedClustering(L, type = "data", distmeasure = DM, normalize = NM,
method = ME, nrclusters = 3, linkBasedMethod = "SRS")
M_ABCpp(L, numsim = 200, NC = 3) # co-clustering consensus
M_ABCdist(L, numsim = 200, NC = 3) # distance-accumulating variant
M_ABCdeep(L, numsim = 200, NC = 3, ae_epochs = 60) # autoencoder embeddingPer-source hierarchies are merged into a single dendrogram.
HierarchicalEnsembleClustering —
combine per-source hierarchical clusterings into a consensus
hierarchy.EHC — Ensemble Hierarchical Clustering
(uses an external partitioner).Cluster a shared latent representation of the sources.
intNMF — integrative non-negative
matrix factorisation: a shared basis plus per-source coefficients;
cluster the shared factors.spectral_clustering — spectral
clustering of an affinity matrix (build one from a fused similarity,
e.g. via SimilarityMeasure() or SNF()).LUCID — a wrapper for latent-cluster
models that use an outcome (supervised integration); supply
exposures G, omics Z and outcome
Y.Not all features are equally informative. Several methods can weight
features by their importance, and MosaiClusteR provides a robust,
big-data-friendly weighting based on data nuggets — a
compression of the objects into weighted representatives that resists
outliers and scales to large n.
dn <- create_data_nuggets(L[[1]], max_nuggets = 25) # compress objects
dn
#> <data_nugget>
#> engine : native
#> observations: 60 compressed into 25 nuggets
#> reduction : 58.3%
#> weight range: 1 - 5 (obs per nugget)
w <- nugget_feature_weights(dn, type = "between") # feature importance
head(sort(w, decreasing = TRUE))
#> src1_f008 src1_f004 src1_f012 src1_f006 src1_f019 src1_f003
#> 2.449098 2.352650 2.284118 2.165043 2.103505 2.007162Data nuggets can also be clustered directly, and used as the feature
weighting in the ABC family (weighting = "nugget"):
SelectnrClusters() scores a range of k
(silhouette / gap-style criteria) so you do not have to guess.
ChooseCluster() helps pick a cut of a fitted tree.
| Function | Purpose |
|---|---|
cluster_agreement(a, b) |
ARI, NMI, Jaccard, purity between two labellings |
compare_clusterings(D1, D2, NC) |
agreement between two integrated results |
mosaic_labels(fit, k) |
cut any fitted result into k labels |
CompareSilCluster, CompareSvsM |
silhouette-based comparison of solutions |
DetermineWeight_SilClust,
DetermineWeight_SimClust |
data-driven source weights |
fit_a <- ADC(L, distmeasure = "euclidean", normalize = FALSE)
fit_b <- SNF(L, type = "data", distmeasure = DM, normalize = NM, method = ME)
compare_clusterings(fit_a$DistM, fit_b$DistM, NC = 3)$ARI
#> NC = 3, linkage = ward.D2, n = 60
#> Adjusted Rand Index : 1.0000
#> Normalized Mut. Info: 1.0000
#> Pair-Jaccard (same) : 1.0000
#> D2_c1 D2_c2 D2_c3
#> D1_c1 20 0 0
#> D1_c2 0 20 0
#> D1_c3 0 0 20
#> ARI
#> 1
cluster_agreement(mosaic_labels(fit_b, 3), truth)
#> ARI NMI Jaccard
#> 1 1 1MosaiClusteR includes a plotting suite for inspecting and comparing solutions. A fitted result carries a hierarchy you can draw directly:
fit <- SNF(L, type = "data", distmeasure = DM, normalize = NM, method = ME)
hc <- stats::as.hclust(fit$Clust)
plot(hc, labels = FALSE, main = "SNF consensus", xlab = "", sub = "")
stats::rect.hclust(hc, k = 3, border = 2:4)Other visualisations (all take fitted results or label vectors):
| Function | Shows |
|---|---|
ComparePlot |
several solutions aligned side-by-side (needs circlize,
plotrix) |
ClusterPlot, Cyclogram |
a single solution as a coloured tree / cyclogram |
SimilarityHeatmap, HeatmapPlot |
object-by-object similarity / feature heatmaps |
ProfilePlot, ContFeaturesPlot,
BinFeaturesPlot_* |
per-cluster feature profiles |
BoxPlotDistance |
within- vs between-cluster distances |
ColorPalette, ColorsNames,
ClusterCols, LabelCols |
consistent colour helpers |
Once you have clusters, describe why they differ and follow them across solutions.
| Function | Purpose |
|---|---|
CharacteristicFeatures, FeatSelection |
features that define each cluster |
FeaturesOfCluster |
the driving features of one cluster |
FindCluster, FindElement |
locate a cluster / object in a result |
TrackCluster |
follow a cluster across several solutions |
ReorderToReference |
align labels of one solution to a reference (Gale-Shapley) |
SharedComps, SimilarityMeasure |
shared members / similarity between solutions |
For omics data, MosaiClusteR connects clusters to genes and pathways.
These functions rely on Bioconductor packages (limma,
MLP, biomaRt, org.Hs.eg.db, …),
declared under Suggests; install them to enable this layer. All
examples here are illustrative.
| Function | Purpose |
|---|---|
DiffGenes, DiffGenesSelection,
FindGenes |
differential features per cluster |
PathwayAnalysis, Pathways,
PathwaysIter, PathwaysSelection |
pathway enrichment per cluster |
PreparePathway, PlotPathways |
prepare / visualise pathway results |
Geneset.intersect, SharedGenesPathsFeat,
SharedSelection* |
shared genes / pathways across solutions |
A complete, reproducible analysis is short because every step shares the interface:
L <- my_sources # list of matrices, objects in rows
k <- SelectnrClusters(L, type = "data", distmeasure = rep("euclidean", length(L)),
normalize = rep(FALSE, length(L)),
method = rep(list(NULL), length(L)),
nrclusters = 2:8)$Optimal_Nr_of_CLusters
fit <- SNF(L, type = "data", distmeasure = rep("euclidean", length(L)),
normalize = rep(FALSE, length(L)), method = rep(list(NULL), length(L)))
lab <- mosaic_labels(fit, k)
CharacteristicFeatures(L, lab) # what defines the clustersSwap SNF for any other method above to compare paradigms
on your own data.
#> R version 4.6.0 (2026-04-24 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26200)
#>
#> Matrix products: default
#> LAPACK version 3.12.1
#>
#> locale:
#> [1] LC_COLLATE=C
#> [2] LC_CTYPE=English_United Kingdom.utf8
#> [3] LC_MONETARY=English_United Kingdom.utf8
#> [4] LC_NUMERIC=C
#> [5] LC_TIME=English_United Kingdom.utf8
#>
#> time zone: Europe/Paris
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] MosaiClusteR_0.1.0
#>
#> loaded via a namespace (and not attached):
#> [1] ade4_1.7-24 Matrix_1.7-5 jsonlite_2.0.0 vegan_2.7-5
#> [5] compiler_4.6.0 gtools_3.9.5 Rcpp_1.1.1-1.1 alluvial_0.1-2
#> [9] parallel_4.6.0 cluster_2.1.8.2 jquerylib_0.1.4 splines_4.6.0
#> [13] SNFtool_2.3.1 yaml_2.3.12 fastmap_1.2.0 lattice_0.22-9
#> [17] R6_2.6.1 geometry_0.5.2 ExPosition_2.11.0 knitr_1.51
#> [21] prettyGraphs_2.2.0 rbibutils_2.4.1 MASS_7.3-65 magic_1.6-1
#> [25] bslib_0.11.0 rlang_1.2.0 cachem_1.1.0 xfun_0.57
#> [29] sass_0.4.10 otel_0.2.0 cli_3.6.6 mgcv_1.9-4
#> [33] Rdpack_2.6.6 digest_0.6.39 grid_4.6.0 permute_0.9-10
#> [37] fastcluster_1.3.0 lifecycle_1.0.5 nlme_3.1-169 FD_1.0-12.5
#> [41] evaluate_1.0.5 abind_1.4-8 ape_5.8-1 rmarkdown_2.31
#> [45] matrixStats_1.5.0 tools_4.6.0 htmltools_0.5.9