1 Introduction

This guide provides an overview of the PIUMA1 PIUMA is the italian word for feather package, a comprehensive R package for performing Topological Data Analysis on high-dimensional datasets, such as -omics data.

1.1 Motivation

Phenotyping is a process of characterizing and classifying individuals based on observable traits or phenotypic characteristics. In the context of medicine and biology, phenotyping involves the systematic analysis and measurement of various physical, physiological, and behavioral features of individuals, such as height, weight, blood pressure, biochemical markers, imaging data, and more. Phenotyping plays a crucial role in precision medicine as it provides essential data for understanding individual health characteristics and disease manifestations, by combining data from different sources to gain comprehensive insights into an individual’s health status and disease risk. This integrated approach allows for more accurate disease diagnosis, prognosis, and treatment selection. The same considerations could be also be extended in omics research, in which the expression values of thousands of genes and proteins, or the incidence of somatic and germline polymorphic variants are usually assessed to link molecular activities with the onset or the progression of diseases. In this field, phenotyping is needed to identify patterns and associations between phenotypic traits and a huge amount of available features. These analyses can uncover novel disease subtypes, identify predictive markers, and facilitate the development of personalized treatment strategies. In this context, the application of unsupervised learning methodologies could help the identification of specific phenotypes in huge heterogeneous cohorts, such as clinical or -omics data. Among them, the Topological Data Analysis (TDA) is a rapidly growing field that combines concepts from algebraic topology and computational geometry to analyze and extract meaningful information from complex and high-dimensional data sets (Carlsson 2009). Moreover, TDA is a robust and effective methodology that preserves the intrinsic characteristics of data and the mutual relationships among observations, by presenting complex data in a graph-based representation. Indeed, building topological models as networks, TDA allows complex diseases to be inspected in a continuous space, where subjects can ‘fluctuate’ over the graph, sharing, at the same time, more than one adjacent node of the network (Dagliati et al. 2020). Overall, TDA offers a powerful set of tools to capture the underlying topological features of data, revealing essential patterns and relationships that might be hidden from traditional statistical techniques (Casaclang-Verzosa et al. 2019).

2 Installation

PIUMA can be installed by:

if (!require("BiocManager", quietly = TRUE)) {
    install.packages("BiocManager")
}

BiocManager::install("PIUMA")

3 Tutorial

3.1 The testing dataset

We tested PIUMA on a subset of the single-cell RNA Sequencing dataset (GSE:GSE193346 generated and published by Feng et al. (2022) to demonstrate that distinct transcriptional profiles are present in specific cell types of each heart chambers, which were attributed to have roles in cardiac development (Feng et al. 2022). In this tutorial, our aim will be to exploit PIUMA for identifying sub-population of vascular endothelial cells, which can be associated with specific heart developmental stages. The original dataset consisted of three layers of heterogeneity: cell type, stage and zone (i.e., heart chamber). Our test dataset was obtained by subsetting vascular endothelial cells (cell type) by Seurat object, extracting raw counts and metadata. Thus, we filtered low expressed genes and normalized data by DaMiRseq :

#############################################
############# NOT TO EXECUTE ################
########## please skip this chunk ###########
#############################################


dataset_seu <- readRDS("./GSE193346_CD1_seurat_object.rds")

# subset vascular endothelial cells
vascularEC_seuobj <- subset(x = dataset_seu,
                            subset = markFinal == "vascular_ec")
df_data_counts <- vascularEC_seuobj@assays$RNA@counts
df_cl <- as.data.frame(df_data_counts)
meta_cl <- vascularEC_seuobj@meta.data[, c(10,13,14,15)]
meta_cl[sapply(meta_cl, is.character)] <- lapply(meta_cl[sapply(meta_cl,
                                                                is.character)],
                                                 as.factor)

## Filtering and normalization
colnames(meta_cl)[4] <- "class"
SE <- DaMiR.makeSE(df_cl, meta_cl)
data_norm <- DaMiR.normalization(SE,
                                 type = "vst",
                                 minCounts = 3,
                                 fSample = 0.4,
                                 hyper = "no")
vascEC_norm <- round(t(assay(data_norm)), 2)
vascEC_meta <- meta_cl[, c(3,4), drop=FALSE]
df_TDA <- cbind(vascEC_meta, vascEC_norm)

At the end, the dataset was composed of 1180 cells (observations) and 838 expressed genes (features). Moreover, 2 additional features are present in the metadata: ‘stage’ and ‘zone’. The first one describes the stage of heart development, while the second one refers to the heart chamber.

Users can directly import the testing dataset by:

library(PIUMA)
#> Loading required package: ggplot2
library(ggplot2)
data(vascEC_norm)
data(vascEC_meta)

df_TDA <- cbind(vascEC_meta, vascEC_norm)

dim(df_TDA)
#> [1] 1180  840
head(df_TDA[1:5, 1:7])
#>                       stage zone Rpl7  Dst Cox5b Eif5b Rpl31
#> AACCAACGTGGTACAG-a5k1 E15.5   RV 5.08 2.95  2.51  2.20  3.42
#> AACCATGAGGAAGTCC-a5k1    P3   LV 4.84 2.40  3.40  1.68  3.40
#> AACGAAAAGACCATAA-a5k1    P0   RV 4.42 2.43  2.61  2.33  2.61
#> AAGCGAGGTAGGAGGG-a5k1    P0   LV 3.74 2.52  2.52  2.01  2.52
#> AAGCGTTGTCTCGGAC-a5k1 E16.5   LV 4.99 3.31  2.33  2.33  3.62

3.2 The TDA object

The PIUMA package comes with a dedicated data structure to easily store the information gathered from all the steps performed by a Topological Data Analysis. As shown in the following cartoon, this object, called TDAobj, is an S4 class containing 9 slots:

  • orig_data: data.frame with the original data (without outcomes)
  • scaled_data: data.frame with re-scaled data (without outcomes)
  • outcomeFact: data.frame with the original outcomes
  • outcome: data.frame with original outcomes converted as numeric
  • comp: data.frame containing the components of projected data
  • dist_mat: data.frame containing the computed distance matrix
  • dfMapper: data.frame containing the nodes, with their elements,
  • jacc: matrix of Jaccard indexes between each pair of dfMapper nodes
  • node_data_mat: data.frame with the node size and the average value

The makeTDAobj function allows users to 1) generate the TDAobj from a data.frame, 2) select one or more variables to be considered as outcome, and 3) perform the 0-1 scaling on the remaining dataset:

TDA_obj <- makeTDAobj(df_TDA, c("stage","zone"))

For genomic data, such as RNA-Seq or scRNA-Seq, we have also developed a custom function to import a SummarizedExperiment object into PIUMA:

data("vascEC_meta")
data("vascEC_norm")

dataSE <- SummarizedExperiment(assays=as.matrix(t(vascEC_norm)),
                               colData=as.data.frame(vascEC_meta))
TDA_obj <- makeTDAobjFromSE(dataSE, c("stage","zone"))

3.3 Preparing data for Mapper

To perform TDA, some preliminary preprocessing steps have to be carried out; specifically, the scaled data stored in TDA_obj@scaled_data, called point-cloud in TDA jargon, has to be projected in a low dimensional space and transformed in distance matrix, exploiting the dfToProjection and dfToDistance functions, respectively. In this example, we will use the umap as projection strategy, to obtain the first 2 reduced dimensions (nComp = 2) and the Euclidean distance (distMethod = "euclidean") as distance metrics. PIUMA allows setting 6 different projection strategies with their specific arguments: UMAP, TSNE, PCA, MDS, KPCA, and ISOMAP and 3 types of well-known distance metrics are available: Euclidean, Pearson’s correlation and the Gower’s distance (to be preferred in case of categorical features are present). Users can also use standard external functions both to implement the low-dimensional reduction (e.g., the built-in princomp function) and to calculate distances (e.g., the built-in dist function).

set.seed(1)

# calculate the distance matrix
TDA_obj <- dfToDistance(TDA_obj, distMethod = "euclidean")

# calculate the projections (lenses)
TDA_obj <- dfToProjection(TDA_obj,
                      "UMAP",
                      nComp = 2,
                      umapNNeigh = 25,
                      umapMinDist = 0.3,
                      showPlot = FALSE)

# plot point-cloud based on stage and zone
df_plot <- as.data.frame(cbind(getOutcomeFact(TDA_obj),
                                getComp(TDA_obj)), 
                         stringAsFactor = TRUE)

ggplot(data= df_plot, aes(x=comp1, y=comp2, color=stage))+
  geom_point(size=3)+
  facet_wrap(~zone)
Scatterplot from UMAP. Four scatter plots are drawn, using the first 2 components identified by UMAP. Each panel represents cells belonging to a specific heart chamber, while colors refer to the development stage.

Figure 1: Scatterplot from UMAP
Four scatter plots are drawn, using the first 2 components identified by UMAP. Each panel represents cells belonging to a specific heart chamber, while colors refer to the development stage.

As shown in Figure 1, the most of vascular endothelial cells are located in ventricles where, in turn, it is possible to more easily appreciate cell groups based on developmental stages.

3.4 TDA Mapper

One of the core algorithms in TDA is the TDA Mapper, which is designed to provide a simplified representation of the data’s topological structure, making it easier to interpret and analyze. The fundamental idea behind TDA Mapper is to divide the data into overlapping subsets called ‘clusters’ and, then, build a simplicial complex that captures the relationships between these clusters. This simplicial complex can be thought of as a network of points, edges, triangles, and higher-dimensional shapes that approximate the underlying topology of the data. The TDA Mapper algorithm proceeds through several consecutive steps:

  • Data Partitioning: the data is partitioned into overlapping subsets, called ‘bins’, where each bin corresponds to a neighborhood of points;
  • Lensing: a filter function, called ‘lens’, is chosen to assign a value to each data point;
  • Clustering: the overlapping bins are clustered based on the values assigned by the filter function. Clusters are formed by grouping together data points with similar filter function values;
  • Simplicial Complex: a simplicial complex is constructed to represent the relationships between the clusters. Each cluster corresponds to a vertex in the complex, and edges are created to connect overlapping clusters;
  • Visualization: the resulting simplicial complex can be visualized, and the topological features of interest can be easily identified and studied.

TDA Mapper has been successfully applied to various domains, including biology, neuroscience, materials science, and more. Its ability to capture the underlying topological structure of data while being robust to noise and dimensionality makes it a valuable tool for gaining insights from complex datasets. PIUMA is thought to implement a 2-dimensional lens function and then apply one of the 4 well-known clustering algorithm: ‘k-means’, ‘hierarchical clustering’, DBSCAN or OPTICS.

TDA_obj <- mapperCore(TDA_obj,
                       nBins = 15,
                       overlap = 0.3,
                       clustMeth = "kmeans")

# number of clusters (nodes)
dim(getDfMapper(TDA_obj))
#> [1] 367   1

# content of two overlapping clusters
getDfMapper(TDA_obj)["node_102_cl_1", 1]
#> [1] "GCGGAAACAGAGTTGG-a5k1 GTGCAGCAGCTGGAGT-a25k TCAGTGACAGCTTTGA-a25k TGTTCTATCAAGCCGC-a25k"
getDfMapper(TDA_obj)["node_117_cl_1", 1]
#> [1] "CCACCATGTTGAGTCT-a5k1 GCCCAGAGTTGCTCAA-a5k1 GCGGAAACAGAGTTGG-a5k1 TCCAGAAGTGTTCCTC-a5k1 GTCCTCATCGGCTGAC-a25k TGCTCGTAGCCTGTGC-a25k"

Here, we decided to generated 15 bins (for each dimension), each one overlapping by 30% with the adjacent ones. The k-means algorithm is, then, applied on the sample belonging to each ‘squared’ bin. In this example, the Mapper aggregated samples in 369 partially overlapping clusters. Indeed, as shown in the previous code chunk, the nodes node_102_cl_1 and node_117_cl_1 shared 2 out of 4 cells.

3.5 Nodes Similarity and Enrichment

The output of mapper is a data.frame, stored in the dfMapper slot, in which each row represents a group of samples (here, a group of cells), called ’node’ in network theory jargon. PIUMA allows the users to also generate a matrix that specifies the similarity between nodes ‘edge’ allowing to represent the data as a network. Since the similarity, in this context, consists of the number of samples, shared by nodes, PIUMA implements a function (jaccardMatrix) to calculate the Jaccard’s index between each pairs of nodes.

# Jaccard Matrix
TDA_obj <- jaccardMatrix(TDA_obj)
head(round(getJacc(TDA_obj)[1:5,1:5],3))
#>             node_3_cl_1 node_4_cl_1 node_4_cl_2 node_5_cl_1 node_5_cl_2
#> node_3_cl_1          NA          NA       0.167          NA          NA
#> node_4_cl_1          NA          NA          NA          NA          NA
#> node_4_cl_2       0.167          NA          NA          NA       0.308
#> node_5_cl_1          NA          NA          NA          NA          NA
#> node_5_cl_2          NA          NA       0.308          NA          NA
round(getJacc(TDA_obj)["node_102_cl_1","node_117_cl_1"],3)
#> [1] 0.111

Regarding the similarity matrix, we obtained a Jaccard matrix where each clusters’ pair was compared; looking, for example, at the Jaccard Index for nodes node_102_cl_1 and node_117_cl_1, we correctly got 0.5 (2/4 cells). Moreover, the tdaDfEnrichment function allows inferring the features values for the generated nodes, by returning the averaged variables values of samples belonging to specific nodes. Generally, this step is called ‘Node Enrichment’. In addition the size of each node is also appended to the output data.frame (the last column name is ‘size’).

TDA_obj <- tdaDfEnrichment(TDA_obj,
                           cbind(getScaledData(TDA_obj),
                                 getOutcome(TDA_obj)))
head(getNodeDataMat(TDA_obj)[1:5, tail(names(getNodeDataMat(TDA_obj)), 5)])
#>             mt.Nd5 mt.Cytb  stage  zone size
#> node_3_cl_1  0.195   0.536 11.000 3.000    1
#> node_4_cl_1  0.240   0.720 13.000 1.000    1
#> node_4_cl_2  0.257   0.634 14.167 1.667    6
#> node_5_cl_1  0.988   0.999 13.000 1.000    1
#> node_5_cl_2  0.279   0.654 15.273 1.545   11

Printing the last 5 columns of the data.frame returned by tdaDfEnrichment (node_data_mat slot), we can show the averaged expression values of each nodes for 4 mitochondrial genes as well as the number of samples belonging to the nodes.

3.6 Network assessment

TDA requires several parameters to be set, such as the type of projection algorithm, the distance metrics, the number of overlapping bins, the percentage of overlap, and the clustering techniques. Moreover, often, specific algorithm parameters need to be chosen, such as the number of UMAP neighbors. This means that TDA analysis should be repeated several times, varying the hyperparameters to find the suitable combination (‘grid search’ approach) and an evaluation metrics is needed to assess each result. PIUMA implements two different strategies to ass

  • supervised approach, usually called ‘anchoring’, in which the entropy of the network generated by TDA is calculated averaging the entropies of each node using one single outcome as class (i.e., ‘anchor’). The lower the entropy, the better the network.
  • unsupervised approach that exploits a topological measurement to force the network to be scale-free. Scale-free networks are characterized by few highly connected nodes (hub nodes) and many poorly connected nodes (leaf nodes). Scale-free networks follows a power-law degree distribution in which the probability that a node has k links follows \[P(k) \sim k^{-\gamma}\], where \(k\) is a node degree (i.e., the number of its connections), \(\gamma\) is a degree exponent, and \(P(k)\) is the frequency of nodes with a specific degree.Degree exponents between \(2 < \gamma < 3\) have been observed in most biological and social networks. Forcing our network to be scale-free ensures to unveil communities in our data. The higher the correlation between P(k) and k, in log-log scale, the better the network.
# Anchoring (supervised)
entropy <-  checkNetEntropy(getNodeDataMat(TDA_obj)[, "zone"])
entropy

# Scale free network (unsupervised)
netModel <- checkScaleFreeModel(TDA_obj, showPlot = "yes")
netModel

In this example, we tested both the approaches even if the unsupervised one is preferable as no prior knowledge (i.e., outcome) is needed to assess the network. We got a global entropy of 1.3 and correlations between P(k) and k equal to -0.75 and -0.58, with data in linear scale or log-log scale, respectively. PIUMA provides users also with the \(\gamma\) value, so that it is easy to assess if the network is scale-free (\(2 < \gamma < 3\)) or not. In this case, \(\gamma\) is equal to 2.09, meaning that the network can be considered scale-free. Overall, the entropy, the correlation between P(k) and k (linear or log-log scale), and/or the \(\gamma\) value, provided by checkNetEntropy and checkScaleFreeModel, could be used to compare different sets of hyper-parameters, such as different lenses, space reduction algorithms and Mapper arguments.

3.7 Export data for Cytoscape

Cytoscape is a well-known tool to handle, process and analyze networks (Shannon et al. 2003). Two files are needed to generate and enrich network in Cytoscape: the jaccard Matrix (TDA_obj@jacc), to generate the structure of the network (nodes and edges) and a data.frame with additional nodes information to enrich the network (TDA_obj@node_data_mat):

write.table(x = round(getJacc(TDA_obj),3),
            file = "./jaccard.matrix.txt",
            sep = "\t",
            quote = FALSE,
            na = "",
            col.names = NA)

write.table(x = getNodeDataMat(TDA_obj),
            file = "./nodeEnrichment.txt",
            sep = "\t",
            quote = FALSE,
            col.names = NA)

To explore the network resulted following the PIUMA framework, we imported jaccard.matrix.txt in Cytoscape by the aMatReader plugin (Settle et al. 2018) (PlugIn -> aMatReader -> Import Matrix file) while nodeEnrichment.txt by File -> Import -> Table from File. Then, we identified network communities by the GLay cluster function from the ‘clustermaker2’ plugin (Utriainen and Morris 2023).

As shown in Figure 3, using the transcriptome of vascular endothelial cells, it is possible to identify 11 communities of cells (top-right). Interestingly, some of them are in the same developmental stage (top-left). Moreover, there are clusters showing similar expression for some genes but different expression for other genes, suggesting that the sub-population could have a different biological function.For example, orange and yellow clusters have a similar average expression of Igfpb7 (bottom-right) but different expression level of Aprt (bottom-left).

Session Info

sessionInfo()
#> R version 4.4.0 beta (2024-04-15 r86425)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 22.04.4 LTS
#> 
#> Matrix products: default
#> BLAS:   /home/biocbuild/bbs-3.19-bioc/R/lib/libRblas.so 
#> LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.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: America/New_York
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] PIUMA_1.0.0      ggplot2_3.5.1    BiocStyle_2.32.0
#> 
#> loaded via a namespace (and not attached):
#>   [1] tidyselect_1.2.1            farver_2.1.1               
#>   [3] dplyr_1.1.4                 fastmap_1.1.1              
#>   [5] digest_0.6.35               rpart_4.1.23               
#>   [7] lifecycle_1.0.4             cluster_2.1.6              
#>   [9] magrittr_2.0.3              kernlab_0.9-32             
#>  [11] dbscan_1.1-12               compiler_4.4.0             
#>  [13] rlang_1.1.3                 Hmisc_5.1-2                
#>  [15] sass_0.4.9                  tools_4.4.0                
#>  [17] igraph_2.0.3                utf8_1.2.4                 
#>  [19] yaml_2.3.8                  data.table_1.15.4          
#>  [21] knitr_1.46                  labeling_0.4.3             
#>  [23] S4Arrays_1.4.0              askpass_1.2.0              
#>  [25] htmlwidgets_1.6.4           DelayedArray_0.30.0        
#>  [27] reticulate_1.36.1           abind_1.4-5                
#>  [29] withr_3.0.0                 foreign_0.8-86             
#>  [31] BiocGenerics_0.50.0         nnet_7.3-19                
#>  [33] grid_4.4.0                  stats4_4.4.0               
#>  [35] fansi_1.0.6                 colorspace_2.1-0           
#>  [37] scales_1.3.0                MASS_7.3-60.2              
#>  [39] tinytex_0.50                SummarizedExperiment_1.34.0
#>  [41] cli_3.6.2                   crayon_1.5.2               
#>  [43] rmarkdown_2.26              vegan_2.6-4                
#>  [45] generics_0.1.3              umap_0.2.10.0              
#>  [47] rstudioapi_0.16.0           RSpectra_0.16-1            
#>  [49] httr_1.4.7                  cachem_1.0.8               
#>  [51] stringr_1.5.1               zlibbioc_1.50.0            
#>  [53] splines_4.4.0               parallel_4.4.0             
#>  [55] XVector_0.44.0              BiocManager_1.30.22        
#>  [57] matrixStats_1.3.0           base64enc_0.1-3            
#>  [59] vctrs_0.6.5                 Matrix_1.7-0               
#>  [61] jsonlite_1.8.8              bookdown_0.39              
#>  [63] IRanges_2.38.0              patchwork_1.2.0            
#>  [65] S4Vectors_0.42.0            Formula_1.2-5              
#>  [67] htmlTable_2.4.2             magick_2.8.3               
#>  [69] jquerylib_0.1.4             glue_1.7.0                 
#>  [71] tsne_0.1-3.1                stringi_1.8.3              
#>  [73] gtable_0.3.5                GenomeInfoDb_1.40.0        
#>  [75] GenomicRanges_1.56.0        UCSC.utils_1.0.0           
#>  [77] munsell_0.5.1               tibble_3.2.1               
#>  [79] pillar_1.9.0                htmltools_0.5.8.1          
#>  [81] openssl_2.1.2               GenomeInfoDbData_1.2.12    
#>  [83] R6_2.5.1                    Biobase_2.64.0             
#>  [85] evaluate_0.23               lattice_0.22-6             
#>  [87] highr_0.10                  png_0.1-8                  
#>  [89] backports_1.4.1             bslib_0.7.0                
#>  [91] Rcpp_1.0.12                 SparseArray_1.4.0          
#>  [93] gridExtra_2.3               nlme_3.1-164               
#>  [95] permute_0.9-7               checkmate_2.3.1            
#>  [97] mgcv_1.9-1                  xfun_0.43                  
#>  [99] MatrixGenerics_1.16.0       pkgconfig_2.0.3

References

Carlsson, Gunnar. 2009. “Topology and Data.” Bulletin of the American Mathematical Society 46 (2): 255–308.

Casaclang-Verzosa, Grace, Sirish Shrestha, Muhammad Jahanzeb Khalil, Jung Sun Cho, Márton Tokodi, Sudarshan Balla, Mohamad Alkhouli, et al. 2019. “Network Tomography for Understanding Phenotypic Presentations in Aortic Stenosis.” JACC: Cardiovascular Imaging 12 (2): 236–48.

Dagliati, Arianna, Nophar Geifman, Niels Peek, John H Holmes, Lucia Sacchi, Riccardo Bellazzi, Seyed Erfan Sajjadi, and Allan Tucker. 2020. “Using Topological Data Analysis and Pseudo Time Series to Infer Temporal Phenotypes from Electronic Health Records.” Artificial Intelligence in Medicine 108: 101930.

Feng, Wei, Abha Bais, Haoting He, Cassandra Rios, Shan Jiang, Juan Xu, Cindy Chang, Dennis Kostka, and Guang Li. 2022. “Single-Cell Transcriptomic Analysis Identifies Murine Heart Molecular Features at Embryonic and Neonatal Stages.” Nature Communications 13 (1): 7960.

Settle, Brett, David Otasek, John H Morris, and Barry Demchak. 2018. “AMatReader: Importing Adjacency Matrices via Cytoscape Automation.” F1000Research 7.

Shannon, Paul, Andrew Markiel, Owen Ozier, Nitin S Baliga, Jonathan T Wang, Daniel Ramage, Nada Amin, Benno Schwikowski, and Trey Ideker. 2003. “Cytoscape: A Software Environment for Integrated Models of Biomolecular Interaction Networks.” Genome Research 13 (11): 2498–2504.

Utriainen, Maija, and John H Morris. 2023. “ClusterMaker2: A Major Update to clusterMaker, a Multi-Algorithm Clustering App for Cytoscape.” BMC Bioinformatics 24 (1): 134.