# Suppress title check warning options(rmarkdown.html_vignette.check_title = FALSE) knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
The American Gut data provided by the SpiecEasi
package are used for almost all NetCoMi tutorials.
We begin by constructing a single network, which we analyze using quantitative and graphical methods. Later, we will compare the networks of two groups: Individuals with and without seasonal allergies.
NetCoMi's main functions are netConstruct()
for network construction, netAnalyze()
for network analysis, and netCompare()
for network comparison. As should become clear from the following examples, these three functions must be executed in the aforementioned order. A further function is diffnet()
for constructing a differential association network. diffnet()
must be applied to the object returned by netConstruct()
.
First of all, we load NetCoMi and the data from American Gut Project (provided
by SpiecEasi
, which is automatically
loaded together with NetCoMi).
library(NetCoMi) data("amgut1.filt") data("amgut2.filt.phy")
We use the SPRING package for estimating associations (conditional dependence) between OTUs.
The data are filtered within netConstruct()
as follows:
filtSamp
). filtTax
).measure
defines the association or dissimilarity measure, which is "spring"
in our case. Additional arguments are passed to SPRING()
via measurePar
.
nlambda
and rep.num
are set to 10 for a decreased execution time, but should
be higher for real data. Rmethod
is set to “approx” to estimate the correlations using a hybrid multi-linear interpolation approach proposed by @yoon2020fast. This method considerably reduces the runtime while controlling the approximation error.
Normalization as well as zero handling is performed internally in SPRING()
.
Hence, we set normMethod
and zeroMethod
to "none"
.
We furthermore set sparsMethod
to "none"
because SPRING
returns a sparse
network where no additional sparsification step is necessary.
We use the "signed" method for transforming associations into dissimilarities
(argument dissFunc
). In doing so, strongly negatively associated taxa have a
high dissimilarity and, in turn, a low similarity, which corresponds to edge
weights in the network plot.
The verbose
argument is set to 3 so that all messages generated by
netConstruct()
as well as messages of external functions are printed.
net_spring <- netConstruct(amgut1.filt, filtTax = "highestFreq", filtTaxPar = list(highestFreq = 50), filtSamp = "totalReads", filtSampPar = list(totalReads = 1000), measure = "spring", measurePar = list(nlambda=10, rep.num=10, Rmethod = "approx"), normMethod = "none", zeroMethod = "none", sparsMethod = "none", dissFunc = "signed", verbose = 2, seed = 123456)
NetCoMi's netAnalyze()
function is used for analyzing the constructed
network(s).
Here, centrLCC
is set to TRUE
meaning that centralities are calculated only
for nodes in the largest connected component (LCC).
Clusters are identified using greedy modularity optimization
(by cluster_fast_greedy()
from igraph
package).
Hubs are nodes with an eigenvector centrality value above the empirical
95% quantile of all eigenvector centralities in the network (argument hubPar
).
weightDeg
and normDeg
are set to FALSE
so that the degree of a node is
simply defined as number of nodes that are adjacent to the node.
By default, a heatmap of the Graphlet Correlation Matrix (GCM) is returned (with
graphlet correlations in the upper triangle and significance codes resulting
from Student's t-test in the lower triangle).
See ?calcGCM
and ?testGCM
for details.
props_spring <- netAnalyze(net_spring, centrLCC = TRUE, clustMethod = "cluster_fast_greedy", hubPar = "eigenvector", weightDeg = FALSE, normDeg = FALSE) #?summary.microNetProps summary(props_spring, numbNodes = 5L)
plotHeat(mat = props_spring$graphletLCC$gcm1, pmat = props_spring$graphletLCC$pAdjust1, type = "mixed", title = "GCM", colorLim = c(-1, 1), mar = c(2, 0, 2, 0)) # Add rectangles highlighting the four types of orbits graphics::rect(xleft = c( 0.5, 1.5, 4.5, 7.5), ybottom = c(11.5, 7.5, 4.5, 0.5), xright = c( 1.5, 4.5, 7.5, 11.5), ytop = c(10.5, 10.5, 7.5, 4.5), lwd = 2, xpd = NA) text(6, -0.2, xpd = NA, "Significance codes: ***: 0.001; **: 0.01; *: 0.05")
We use the determined clusters as node colors and scale the node sizes according to the node's eigenvector centrality.
# help page ?plot.microNetProps
p <- plot(props_spring, nodeColor = "cluster", nodeSize = "eigenvector", title1 = "Network on OTU level with SPRING associations", showTitle = TRUE, cexTitle = 2.3) legend(0.7, 1.1, cex = 2.2, title = "estimated association:", legend = c("+","-"), lty = 1, lwd = 3, col = c("#009900","red"), bty = "n", horiz = TRUE)
Note that edge weights are (non-negative) similarities, however, the edges
belonging to negative estimated associations are colored in red by default
(negDiffCol = TRUE
).
By default, a different transparency value is added to edges with an absolute
weight below and above the cut
value (arguments edgeTranspLow
and
edgeTranspHigh
). The determined cut
value can be read out as
follows:
p$q1$Arguments$cut
Some users may be interested in how to export the network to Gephi. Here's an example:
# For Gephi, we have to generate an edge list with IDs. # The corresponding labels (and also further node features) are stored as node list. # Create edge object from the edge list exported by netConstruct() edges <- dplyr::select(net_spring$edgelist1, v1, v2) # Add Source and Target variables (as IDs) edges$Source <- as.numeric(factor(edges$v1)) edges$Target <- as.numeric(factor(edges$v2)) edges$Type <- "Undirected" edges$Weight <- net_spring$edgelist1$adja nodes <- unique(edges[,c('v1','Source')]) colnames(nodes) <- c("Label", "Id") # Add category with clusters (can be used as node colors in Gephi) nodes$Category <- props_spring$clustering$clust1[nodes$Label] edges <- dplyr::select(edges, Source, Target, Type, Weight) write.csv(nodes, file = "nodes.csv", row.names = FALSE) write.csv(edges, file = "edges.csv", row.names = FALSE)
The exported .csv files can then be imported into Gephi.
Let's construct another network using Pearson's correlation coefficient
as association measure. The input is now a phyloseq
object.
Since Pearson correlations may lead to compositional effects when applied to sequencing data, we use the clr transformation as normalization method. Zero treatment is necessary in this case.
A threshold of 0.3 is used as sparsification method, so that only OTUs with an absolute correlation greater than or equal to 0.3 are connected.
net_pears <- netConstruct(amgut2.filt.phy, measure = "pearson", normMethod = "clr", zeroMethod = "multRepl", sparsMethod = "threshold", thresh = 0.3, verbose = 3)
Network analysis and plotting:
props_pears <- netAnalyze(net_pears, clustMethod = "cluster_fast_greedy")
plot(props_pears, nodeColor = "cluster", nodeSize = "eigenvector", title1 = "Network on OTU level with Pearson correlations", showTitle = TRUE, cexTitle = 2.3) legend(0.7, 1.1, cex = 2.2, title = "estimated correlation:", legend = c("+","-"), lty = 1, lwd = 3, col = c("#009900","red"), bty = "n", horiz = TRUE)
Let's improve the visualization by changing the following arguments:
repulsion = 0.8
: Place the nodes further apart.rmSingles = TRUE
: Single nodes are removed.labelScale = FALSE
and cexLabels = 1.6
: All labels have equal size and are
enlarged to improve readability of small node's labels.nodeSizeSpread = 3
(default is 4): Node sizes are more similar if the value
is decreased. This argument (in combination with cexNodes
) is useful to enlarge
small nodes while keeping the size of big nodes.hubBorderCol = "darkgray"
: Change border color for a better readability of
the node labels.plot(props_pears, nodeColor = "cluster", nodeSize = "eigenvector", repulsion = 0.8, rmSingles = TRUE, labelScale = FALSE, cexLabels = 1.6, nodeSizeSpread = 3, cexNodes = 2, hubBorderCol = "darkgray", title1 = "Network on OTU level with Pearson correlations", showTitle = TRUE, cexTitle = 2.3) legend(0.7, 1.1, cex = 2.2, title = "estimated correlation:", legend = c("+","-"), lty = 1, lwd = 3, col = c("#009900","red"), bty = "n", horiz = TRUE)
The network can be sparsified further using the arguments edgeFilter
(edges
are filtered before the layout is computed) and edgeInvisFilter
(edges are
removed after the layout is computed and thus just made "invisible").
plot(props_pears, edgeInvisFilter = "threshold", edgeInvisPar = 0.4, nodeColor = "cluster", nodeSize = "eigenvector", repulsion = 0.8, rmSingles = TRUE, labelScale = FALSE, cexLabels = 1.6, nodeSizeSpread = 3, cexNodes = 2, hubBorderCol = "darkgray", title1 = paste0("Network on OTU level with Pearson correlations", "\n(edge filter: threshold = 0.4)"), showTitle = TRUE, cexTitle = 2.3) legend(0.7, 1.1, cex = 2.2, title = "estimated correlation:", legend = c("+","-"), lty = 1, lwd = 3, col = c("#009900","red"), bty = "n", horiz = TRUE)
In the above network, the "signed" transformation was used to transform the estimated associations into dissimilarities. This leads to a network where strongly positive correlated taxa have a high edge weight (1 if the correlation equals 1) and strongly negative correlated taxa have a low edge weight (0 if the correlation equals -1).
We now use the "unsigned" transformation so that the edge weight between strongly correlated taxa is high, no matter of the sign. Hence, a correlation of -1 and 1 would lead to an edge weight of 1.
We can pass the network object from before to netConstruct()
to save runtime.
net_pears_unsigned <- netConstruct(data = net_pears$assoEst1, dataType = "correlation", sparsMethod = "threshold", thresh = 0.3, dissFunc = "unsigned", verbose = 3)
The following histograms demonstrate how the estimated correlations are transformed into adjacencies (= sparsified similarities for weighted networks).
Sparsified estimated correlations:
hist(net_pears$assoMat1, 100, xlim = c(-1, 1), ylim = c(0, 400), xlab = "Estimated correlation", main = "Estimated correlations after sparsification")
Adjacency values computed using the "signed" transformation (values different from 0 and 1 will be edges in the network):
hist(net_pears$adjaMat1, 100, ylim = c(0, 400), xlab = "Adjacency values", main = "Adjacencies (with \"signed\" transformation)")
Adjacency values computed using the "unsigned" transformation:
hist(net_pears_unsigned$adjaMat1, 100, ylim = c(0, 400), xlab = "Adjacency values", main = "Adjacencies (with \"unsigned\" transformation)")
props_pears_unsigned <- netAnalyze(net_pears_unsigned, clustMethod = "cluster_fast_greedy", gcmHeat = FALSE)
plot(props_pears_unsigned, nodeColor = "cluster", nodeSize = "eigenvector", repulsion = 0.9, rmSingles = TRUE, labelScale = FALSE, cexLabels = 1.6, nodeSizeSpread = 3, cexNodes = 2, hubBorderCol = "darkgray", title1 = "Network with Pearson correlations and \"unsigned\" transformation", showTitle = TRUE, cexTitle = 2.3) legend(0.7, 1.1, cex = 2.2, title = "estimated correlation:", legend = c("+","-"), lty = 1, lwd = 3, col = c("#009900","red"), bty = "n", horiz = TRUE)
While with the "signed" transformation, positive correlated taxa are likely to belong to the same cluster, with the "unsigned" transformation clusters contain strongly positive and negative correlated taxa.
We now construct a further network, where OTUs are agglomerated to genera.
library(phyloseq) data("amgut2.filt.phy") # Agglomerate to genus level amgut_genus <- tax_glom(amgut2.filt.phy, taxrank = "Rank6") # Taxonomic table taxtab <- as(tax_table(amgut_genus), "matrix") # Rename taxonomic table and make Rank6 (genus) unique amgut_genus_renamed <- renameTaxa(amgut_genus, pat = "<name>", substPat = "<name>_<subst_name>(<subst_R>)", numDupli = "Rank6") # Network construction and analysis net_genus <- netConstruct(amgut_genus_renamed, taxRank = "Rank6", measure = "pearson", zeroMethod = "multRepl", normMethod = "clr", sparsMethod = "threshold", thresh = 0.3, verbose = 3) props_genus <- netAnalyze(net_genus, clustMethod = "cluster_fast_greedy")
Modifications:
igraph
package used (passed to
plot
as matrix)# Compute layout graph3 <- igraph::graph_from_adjacency_matrix(net_genus$adjaMat1, weighted = TRUE) set.seed(123456) lay_fr <- igraph::layout_with_fr(graph3) # Row names of the layout matrix must match the node names rownames(lay_fr) <- rownames(net_genus$adjaMat1) plot(props_genus, layout = lay_fr, shortenLabels = "intelligent", labelLength = 10, labelPattern = c(5, "'", 3, "'", 3), nodeSize = "fix", nodeColor = "gray", cexNodes = 0.8, cexHubs = 1.1, cexLabels = 1.2, title1 = "Network on genus level with Pearson correlations", showTitle = TRUE, cexTitle = 2.3) legend(0.7, 1.1, cex = 2.2, title = "estimated correlation:", legend = c("+","-"), lty = 1, lwd = 3, col = c("#009900","red"), bty = "n", horiz = TRUE)
Since the above visualization is obviously not optimal, we make further adjustments:
set.seed(123456) plot(props_genus, layout = "layout_with_fr", shortenLabels = "intelligent", labelLength = 10, labelPattern = c(5, "'", 3, "'", 3), labelScale = FALSE, rmSingles = TRUE, nodeSize = "clr", nodeColor = "cluster", hubBorderCol = "darkgray", cexNodes = 2, cexLabels = 1.5, cexHubLabels = 2, title1 = "Network on genus level with Pearson correlations", showTitle = TRUE, cexTitle = 2.3) legend(0.7, 1.1, cex = 2.2, title = "estimated correlation:", legend = c("+","-"), lty = 1, lwd = 3, col = c("#009900","red"), bty = "n", horiz = TRUE)
Let's check whether the largest nodes are actually those with highest
column sums in the matrix with normalized counts returned by netConstruct()
.
sort(colSums(net_genus$normCounts1), decreasing = TRUE)[1:10]
In order to further improve our plot, we use the following modifications:
qgraph()
(the function
is generally used for network plotting in NetCoMi)# Get phyla names taxtab <- as(tax_table(amgut_genus_renamed), "matrix") phyla <- as.factor(gsub("p__", "", taxtab[, "Rank2"])) names(phyla) <- taxtab[, "Rank6"] #table(phyla) # Define phylum colors phylcol <- c("cyan", "blue3", "red", "lawngreen", "yellow", "deeppink") plot(props_genus, layout = "spring", repulsion = 0.84, shortenLabels = "none", charToRm = "g__", labelScale = FALSE, rmSingles = TRUE, nodeSize = "clr", nodeSizeSpread = 4, nodeColor = "feature", featVecCol = phyla, colorVec = phylcol, posCol = "darkturquoise", negCol = "orange", edgeTranspLow = 0, edgeTranspHigh = 40, cexNodes = 2, cexLabels = 2, cexHubLabels = 2.5, title1 = "Network on genus level with Pearson correlations", showTitle = TRUE, cexTitle = 2.3) # Colors used in the legend should be equally transparent as in the plot phylcol_transp <- colToTransp(phylcol, 60) legend(-1.2, 1.2, cex = 2, pt.cex = 2.5, title = "Phylum:", legend=levels(phyla), col = phylcol_transp, bty = "n", pch = 16) legend(0.7, 1.1, cex = 2.2, title = "estimated correlation:", legend = c("+","-"), lty = 1, lwd = 3, col = c("darkturquoise","orange"), bty = "n", horiz = TRUE)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.