Chapter 5: Network Construction, Topological Overlap Matrix (TOM) & Dynamic Module Detection
Chapter 5: Network Construction, Topological Overlap Matrix (TOM) & Dynamic Module Detection
Constructing co-expression networks with blockwiseModules; calculating signed Topological Overlap Matrices (TOM); dynamic tree cutting; multi-block handling; converting numeric labels to colors; and merging similar modules at mergeCutHeight = 0.25.
1. The Mathematical Anatomy of Topological Overlap (TOM)
While the co-expression adjacency matrix $a_{ij}$ measures direct correlation between gene $i$ and gene $j$, direct correlation is notoriously vulnerable to transitive noise (e.g., genes $i$ and $j$ may both correlate with an omnipresent stress signal without sharing any specific biological functional relationship).
To construct robust, biologically stable modules, WGCNA transforms adjacency into the Topological Overlap Measure (TOM):
Here, $l_{ij}$ quantifies the shared neighborhood connectivity between genes $i$ and $j$ across all other network nodes $u$, and $k_i = \sum_u a_{iu}$ is the node connectivity. Two genes achieve a high $TOM_{ij}$ value if and only if they are not only directly co-expressed ($a_{ij}$), but also share a tightly knit cohort of mutual network neighbors ($l_{ij}$).
The topological dissimilarity matrix is then computed as:
Hierarchical average-linkage clustering is subsequently performed on $d_{TOM}$, and modules are identified using the Dynamic Tree Cut algorithm.
2. One-Step Blockwise Network Construction with blockwiseModules()
The blockwiseModules() function encapsulates network construction, TOM calculation, hierarchical clustering, dynamic tree pruning, and module merging into a unified, memory-aware execution pipeline:
# Execute blockwise network construction and module detection
net <- blockwiseModules(
expr_mat,
power = soft_power,
networkType = "signed",
TOMType = "signed",
minModuleSize = 30, # Minimum genes per module to avoid unstable micro-clusters
mergeCutHeight = 0.25, # Merge modules with eigengene correlation >= 0.75 (1 - 0.25)
numericLabels = TRUE, # Returns numeric module IDs (0 = grey)
pamRespectsDendro = FALSE, # Prevents PAM stage from overriding dendrogram tree cut
maxBlockSize = 30000, # Partition size based on available host RAM
verbose = 3
)
# Extract numeric module labels and convert to standard WGCNA color conventions
module_labels <- net$colors
names(module_labels) <- colnames(expr_mat)
module_colors <- labels2colors(module_labels)
names(module_colors) <- names(module_labels)
# Print module size distribution
cat("=== Detected Co-expression Modules ===\n")
print(sort(table(module_colors), decreasing = TRUE))
# Diagnostic: Calculate Grey Module Fraction
grey_fraction <- mean(module_colors == "grey")
cat("\nGrey module fraction:", round(grey_fraction, 3), "(", round(grey_fraction * 100, 1), "%)\n")
if (grey_fraction > 0.50) {
message("⚠ More than 50% of genes are in the grey module. Review filtering and sample outliers.")
}3. Multi-Block Partitioning & Block-Specific Dendrogram Plotting
When processing large plant genomes with $> 30,000$ expressed transcripts on standard workstations ($16\text{ GB}$ RAM), blockwiseModules() automatically partitions genes into discrete blocks to prevent memory crashes.
module_colors vector against a single block dendrogram. If multiple blocks are generated, each block's dendrogram must be rendered strictly against the module colors assigned to the genes inside that specific block:# Correct multi-block dendrogram visualization loop
for (b in seq_along(net$dendrograms)) {
block_genes <- net$blockGenes[[b]]
block_colors <- module_colors[block_genes]
plotDendroAndColors(
net$dendrograms[[b]],
block_colors,
groupLabels = paste("Block", b, "Colors"),
dendroLabels = FALSE,
hang = 0.03,
addGuide = TRUE,
guideHang = 0.05,
main = paste("Gene Dendrogram & Module Colors (Block", b, ")")
)
}Common Pitfalls & Troubleshooting Checklist
- Java Heap Exhaustion (GATK / Picard): If processes terminate with
java.lang.OutOfMemoryError, set your environment variable explicitly:export _JAVA_OPTIONS="-Xmx16g -Djava.io.tmpdir=/tmp". - Index Timestamp Mismatch: If tools complain that a BAM index is missing or out of date, regenerate with
samtools index -@ 4 sample.bam. - Conda Channel Conflicts: Avoid ABI incompatibilities between bioconda and conda-forge by setting strict priority:
conda config --set channel_priority strict. - Chromosome Naming Contamination: Always verify whether reference FASTA headers use
chr1or1; divergent naming causes silent alignment drops.