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


3.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
# Taxonomic annotation table of ASVs or OTUs
data("tax_tab")
dim(tax_tab)
## [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