3 Random Matrix Theory (RMT)–based random network
When a numerical matrix is used as input, it is necessary to filter and screen the resulting correlation matrix. Empirical thresholds are commonly adopted, usually between 0.6 to 0.85; however, these thresholds are largely subjective.
Compared with empirical thresholding, random matrix theory (RMT) provides a data-driven and objective approach for determining correlation thresholds, effectively separating true biological signals from random noise and improving the robustness and comparability of ecological networks.
3.1 Random Matrix Theory (RMT)
Load R Package
library(tidyverse) # data manipulation / piping utilities
library(ggNetView) # graph builders + info-extraction helpers3.2 Example data
Example data
# Access built-in example datasets in ggNetView
# Relative abundance table of rarefied ASVs or OTUs
data("otu_rare_relative")
dim(otu_rare_relative)## [1] 2859 18
otu_rare_relative[1:5, 1:5]## KO1 KO2 KO3 KO4 KO5
## ASV_1 0.03306667 0.05453333 0.02013333 0.03613333 0.02686667
## ASV_2 0.05750000 0.03393333 0.06046667 0.05810000 0.07320000
## ASV_3 0.01733333 0.01296667 0.02290000 0.02336667 0.03106667
## ASV_4 0.04266667 0.01093333 0.01416667 0.01933333 0.03346667
## ASV_6 0.02646667 0.01856667 0.02110000 0.02353333 0.03806667
## [1] 2859 8
tax_tab[1:5, 1:5]## # A tibble: 5 × 5
## OTUID Kingdom Phylum Class Order
## <chr> <chr> <chr> <chr> <chr>
## 1 ASV_2 Archaea Thaumarchaeota Unassigned Nitrososphaerales
## 2 ASV_3 Bacteria Verrucomicrobia Spartobacteria Unassigned
## 3 ASV_31 Bacteria Actinobacteria Actinobacteria Actinomycetales
## 4 ASV_27 Archaea Thaumarchaeota Unassigned Nitrososphaerales
## 5 ASV_9 Bacteria Unassigned Unassigned Unassigned
3.3 Compute RMT threshold
# ---- RMT threshold scan #1: WGCNA correlation backend ----
# `ggNetView_RMT()` scans a series of candidate |r| thresholds, builds the
# thresholded correlation matrix at each, computes its eigenvalue spacing
# distribution (NNSD), and picks the threshold at which the NNSD transitions
# from Gaussian Orthogonal Ensemble (signal + noise) to Poisson (noise-free,
# modular network) — this is the RMT-recommended cutoff.
out <- ggNetView_RMT(
mat = otu_rare_relative, # input matrix (variables x samples)
transfrom.method = "none", # already relative-abundance, no extra transform
method = "WGCNA", # use WGCNA::corAndPvalue for correlation
cor.method = "pearson", # Pearson correlation
unfold.method = "gaussian", # Gaussian KDE unfolding of eigenvalues
bandwidth = "nrd0", # KDE bandwidth rule (stats::density default)
nr.fit.points = 20, # support points (only used if unfold.method = "spline")
discard.outliers = TRUE, # IQR-trim extreme eigenvalues before unfolding
discard.zeros = TRUE, # drop all-zero rows/cols after thresholding
min.mat.dim = 40, # early-stop if effective dim drops below 40
max.ev.spacing = 3, # truncate NNSD tail at spacing = 3
save_plots = FALSE, # don't write diagnostic PNGs to disk
out_dir = "RMT_plots", # (only used when save_plots = TRUE)
verbose = TRUE, # print scan progress to console
seed = 1115 # fix RNG for reproducibility
)## [Info] Matrix dimension: 2859 x 2859
## [Info] #non-zeros: 8173751 | Sparseness: 0.0000
## [Scan] 1/51 | threshold = 0
## Effective dimension: 2859
## [Scan] 2/51 | threshold = 0.02
## Effective dimension: 2859
## [Scan] 3/51 | threshold = 0.04
## Effective dimension: 2859
## [Scan] 4/51 | threshold = 0.06
## Effective dimension: 2859
## [Scan] 5/51 | threshold = 0.08
## Effective dimension: 2859
## [Scan] 6/51 | threshold = 0.1
## Effective dimension: 2859
## [Scan] 7/51 | threshold = 0.12
## Effective dimension: 2859
## [Scan] 8/51 | threshold = 0.14
## Effective dimension: 2859
## [Scan] 9/51 | threshold = 0.16
## Effective dimension: 2859
## [Scan] 10/51 | threshold = 0.18
## Effective dimension: 2859
## [Scan] 11/51 | threshold = 0.2
## Effective dimension: 2859
## [Scan] 12/51 | threshold = 0.22
## Effective dimension: 2859
## [Scan] 13/51 | threshold = 0.24
## Effective dimension: 2859
## [Scan] 14/51 | threshold = 0.26
## Effective dimension: 2859
## [Scan] 15/51 | threshold = 0.28
## Effective dimension: 2859
## [Scan] 16/51 | threshold = 0.3
## Effective dimension: 2859
## [Scan] 17/51 | threshold = 0.32
## Effective dimension: 2859
## [Scan] 18/51 | threshold = 0.34
## Effective dimension: 2859
## [Scan] 19/51 | threshold = 0.36
## Effective dimension: 2859
## [Scan] 20/51 | threshold = 0.38
## Effective dimension: 2859
## [Scan] 21/51 | threshold = 0.4
## Effective dimension: 2859
## [Scan] 22/51 | threshold = 0.42
## Effective dimension: 2859
## [Scan] 23/51 | threshold = 0.44
## Effective dimension: 2859
## [Scan] 24/51 | threshold = 0.46
## Effective dimension: 2859
## [Scan] 25/51 | threshold = 0.48
## Effective dimension: 2859
## [Scan] 26/51 | threshold = 0.5
## Effective dimension: 2859
## [Scan] 27/51 | threshold = 0.52
## Effective dimension: 2859
## [Scan] 28/51 | threshold = 0.54
## Effective dimension: 2859
## [Scan] 29/51 | threshold = 0.56
## Effective dimension: 2859
## [Scan] 30/51 | threshold = 0.58
## Effective dimension: 2859
## [Scan] 31/51 | threshold = 0.6
## Effective dimension: 2859
## [Scan] 32/51 | threshold = 0.62
## Effective dimension: 2859
## [Scan] 33/51 | threshold = 0.64
## Effective dimension: 2857
## [Scan] 34/51 | threshold = 0.66
## Effective dimension: 2855
## [Scan] 35/51 | threshold = 0.68
## Effective dimension: 2842
## [Scan] 36/51 | threshold = 0.7
## Effective dimension: 2800
## [Scan] 37/51 | threshold = 0.72
## Effective dimension: 2705
## [Scan] 38/51 | threshold = 0.74
## Effective dimension: 2546
## [Scan] 39/51 | threshold = 0.76
## Effective dimension: 2333
## [Scan] 40/51 | threshold = 0.78
## Effective dimension: 2049
## [Scan] 41/51 | threshold = 0.8
## Effective dimension: 1735
## [Scan] 42/51 | threshold = 0.82
## Effective dimension: 1396
## [Scan] 43/51 | threshold = 0.84
## Effective dimension: 1082
## [Scan] 44/51 | threshold = 0.86
## Effective dimension: 825
## [Scan] 45/51 | threshold = 0.88
## Effective dimension: 600
## [Scan] 46/51 | threshold = 0.9
## Effective dimension: 454
## [Scan] 47/51 | threshold = 0.92
## Effective dimension: 324
## [Scan] 48/51 | threshold = 0.94
## Effective dimension: 215
## [Scan] 49/51 | threshold = 0.96
## Effective dimension: 157
## [Scan] 50/51 | threshold = 0.98
## Effective dimension: 79
## [Scan] 51/51 | threshold = 1
## Effective dimension: 2
## Too small. Stop scanning.
# The chosen threshold to feed downstream into build_graph_from_mat(r.threshold = ...)
out$chosen_threshold## [1] 0.82
# ---- RMT threshold scan #2: plain `cor()` backend for comparison ----
# Same parameters as `out` above, but with `method = "cor"` (base stats::cor /
# psych::corr.test) instead of WGCNA. Useful as a sanity check — the chosen
# threshold should be very close across the two backends
out2 <- ggNetView_RMT(
mat = otu_rare_relative,
transfrom.method = "none",
method = "cor", # <-- only difference vs. `out`
cor.method = "pearson",
unfold.method = "gaussian",
bandwidth = "nrd0",
nr.fit.points = 20,
discard.outliers = TRUE,
discard.zeros = TRUE,
min.mat.dim = 40,
max.ev.spacing = 3,
save_plots = FALSE,
out_dir = "RMT_plots",
verbose = TRUE,
seed = 1115
)## [Info] Matrix dimension: 2859 x 2859
## [Info] #non-zeros: 8173761 | Sparseness: 0.0000
## [Scan] 1/51 | threshold = 0
## Effective dimension: 2859
## [Scan] 2/51 | threshold = 0.02
## Effective dimension: 2859
## [Scan] 3/51 | threshold = 0.04
## Effective dimension: 2859
## [Scan] 4/51 | threshold = 0.06
## Effective dimension: 2859
## [Scan] 5/51 | threshold = 0.08
## Effective dimension: 2859
## [Scan] 6/51 | threshold = 0.1
## Effective dimension: 2859
## [Scan] 7/51 | threshold = 0.12
## Effective dimension: 2859
## [Scan] 8/51 | threshold = 0.14
## Effective dimension: 2859
## [Scan] 9/51 | threshold = 0.16
## Effective dimension: 2859
## [Scan] 10/51 | threshold = 0.18
## Effective dimension: 2859
## [Scan] 11/51 | threshold = 0.2
## Effective dimension: 2859
## [Scan] 12/51 | threshold = 0.22
## Effective dimension: 2859
## [Scan] 13/51 | threshold = 0.24
## Effective dimension: 2859
## [Scan] 14/51 | threshold = 0.26
## Effective dimension: 2859
## [Scan] 15/51 | threshold = 0.28
## Effective dimension: 2859
## [Scan] 16/51 | threshold = 0.3
## Effective dimension: 2859
## [Scan] 17/51 | threshold = 0.32
## Effective dimension: 2859
## [Scan] 18/51 | threshold = 0.34
## Effective dimension: 2859
## [Scan] 19/51 | threshold = 0.36
## Effective dimension: 2859
## [Scan] 20/51 | threshold = 0.38
## Effective dimension: 2859
## [Scan] 21/51 | threshold = 0.4
## Effective dimension: 2859
## [Scan] 22/51 | threshold = 0.42
## Effective dimension: 2859
## [Scan] 23/51 | threshold = 0.44
## Effective dimension: 2859
## [Scan] 24/51 | threshold = 0.46
## Effective dimension: 2859
## [Scan] 25/51 | threshold = 0.48
## Effective dimension: 2859
## [Scan] 26/51 | threshold = 0.5
## Effective dimension: 2859
## [Scan] 27/51 | threshold = 0.52
## Effective dimension: 2859
## [Scan] 28/51 | threshold = 0.54
## Effective dimension: 2859
## [Scan] 29/51 | threshold = 0.56
## Effective dimension: 2859
## [Scan] 30/51 | threshold = 0.58
## Effective dimension: 2859
## [Scan] 31/51 | threshold = 0.6
## Effective dimension: 2859
## [Scan] 32/51 | threshold = 0.62
## Effective dimension: 2859
## [Scan] 33/51 | threshold = 0.64
## Effective dimension: 2857
## [Scan] 34/51 | threshold = 0.66
## Effective dimension: 2855
## [Scan] 35/51 | threshold = 0.68
## Effective dimension: 2842
## [Scan] 36/51 | threshold = 0.7
## Effective dimension: 2800
## [Scan] 37/51 | threshold = 0.72
## Effective dimension: 2705
## [Scan] 38/51 | threshold = 0.74
## Effective dimension: 2546
## [Scan] 39/51 | threshold = 0.76
## Effective dimension: 2333
## [Scan] 40/51 | threshold = 0.78
## Effective dimension: 2049
## [Scan] 41/51 | threshold = 0.8
## Effective dimension: 1735
## [Scan] 42/51 | threshold = 0.82
## Effective dimension: 1396
## [Scan] 43/51 | threshold = 0.84
## Effective dimension: 1082
## [Scan] 44/51 | threshold = 0.86
## Effective dimension: 825
## [Scan] 45/51 | threshold = 0.88
## Effective dimension: 600
## [Scan] 46/51 | threshold = 0.9
## Effective dimension: 454
## [Scan] 47/51 | threshold = 0.92
## Effective dimension: 324
## [Scan] 48/51 | threshold = 0.94
## Effective dimension: 215
## [Scan] 49/51 | threshold = 0.96
## Effective dimension: 157
## [Scan] 50/51 | threshold = 0.98
## Effective dimension: 79
## [Scan] 51/51 | threshold = 1
## Effective dimension: 10
## Too small. Stop scanning.
# Compare with out$chosen_threshold — they should be in the same neighborhood.
out2$chosen_threshold## [1] 0.82
3.4 Build graph from matrix based on RMT-threshold
Build graph object
# ---- Construct the tidygraph network using the RMT-selected r threshold ----
# Replaces the usual subjective r-cutoff (e.g. 0.6–0.85) with the data-driven
# value `out$chosen_threshold` returned above.
graph_obj <- build_graph_from_mat(
mat = otu_rare_relative, # variable x sample matrix
transfrom.method = "none", # match the transform used for RMT scan
r.threshold = out$chosen_threshold, # RMT-derived |r| cutoff (objective)
p.threshold = 0.05, # significance cutoff on adjusted p-values
method = "WGCNA", # correlation backend (must match RMT scan)
cor.method = "pearson", # correlation type
proc = "bonferroni", # multiple-testing correction for p-values
module.method = "Fast_greedy", # community detection algorithm
node_annotation = tax_tab, # taxonomy joined onto each node (by name)
top_modules = 15, # keep top-15 modules; smaller ones -> "Others"
seed = 1115 # reproducibility
)
# Print to inspect node/edge counts and the columns now available on nodes
# (modularity, modularity2, modularity3, Modularity, Degree, Strength + taxonomy)
graph_obj## # A tbl_graph: 213 nodes and 844 edges
## #
## # An undirected simple graph with 29 components
## #
## # Node Data: 213 × 14 (active)
## name modularity modularity2 modularity3 Modularity Degree Strength Kingdom
## <chr> <fct> <ord> <chr> <ord> <dbl> <dbl> <chr>
## 1 ASV_649 5 5 5 5 27 26.5 Bacter…
## 2 ASV_705 5 5 5 5 27 26.5 Bacter…
## 3 ASV_12… 5 5 5 5 27 26.5 Bacter…
## 4 ASV_13… 5 5 5 5 27 26.5 Bacter…
## 5 ASV_14… 5 5 5 5 27 26.5 Bacter…
## 6 ASV_14… 5 5 5 5 27 26.5 Bacter…
## 7 ASV_24… 5 5 5 5 27 26.5 Bacter…
## 8 ASV_25… 5 5 5 5 27 26.4 Bacter…
## 9 ASV_28… 5 5 5 5 27 26.5 Bacter…
## 10 ASV_28… 5 5 5 5 27 26.5 Bacter…
## # ℹ 203 more rows
## # ℹ 6 more variables: Phylum <chr>, Class <chr>, Order <chr>, Family <chr>,
## # Genus <chr>, Species <chr>
## #
## # Edge Data: 844 × 5
## from to weight correlation corr_direction
## <int> <int> <dbl> <dbl> <chr>
## 1 194 195 0.959 0.959 Positive
## 2 185 208 0.954 0.954 Positive
## 3 185 213 0.957 0.957 Positive
## # ℹ 841 more rows