| name | hdwgcna |
| description | Co-expression network analysis for single-cell and spatial transcriptomics using hdWGCNA (R/Seurat). Covers the full pipeline — metacell construction, soft-power selection, network construction, module identification, module eigengenes (MEs/hMEs), hub gene scoring — plus three downstream analyses: differential module eigengenes between groups (FindDMEs / FindAllDMEs), module-trait correlation (ModuleTraitCorrelation), and enrichment via Enrichr / fgsea. Operates on Seurat objects. |
hdWGCNA: High-Dimensional Co-Expression Network Analysis
Overview
hdWGCNA is the modern adaptation of WGCNA for high-dimensional transcriptomics — single-cell RNA-seq, single-nucleus RNA-seq, and spatial transcriptomics. It solves WGCNA's two biggest problems with single-cell data:
- Sparsity — single cells have many zeros, breaking WGCNA's correlation structure. hdWGCNA aggregates similar cells into "metacells" first.
- Batch effects — module eigengenes (MEs) can be confounded by donor/sample. hdWGCNA computes batch-corrected harmonized MEs (hMEs) via Harmony.
The package operates on Seurat objects: every step adds data to seurat_obj@misc[[wgcna_name]], so you can run multiple parallel analyses (e.g. one network per cell type) on the same Seurat object.
When to Use This Skill
- Identifying co-expressed gene modules within a cell type (e.g. "neuronal modules in inhibitory neurons")
- Finding hub genes (genes most central to a module) for downstream wet-lab validation
- Testing module activity differences between conditions (case vs control, treated vs untreated)
- Correlating module eigengenes with continuous traits (age, disease severity, cell type proportion)
- Functional enrichment of modules (GO, KEGG, custom gene sets) via Enrichr or fgsea
- Module preservation testing across datasets (cross-condition / cross-species)
Not for: bulk RNA-seq (use vanilla WGCNA), trajectory analysis (use scVelo / Slingshot), differential expression at the gene level (use Seurat / DESeq2).
Prerequisites
- R ≥ 4.2, Seurat ≥ 4.x, WGCNA, harmony, UCell, igraph
- A Seurat object with: cell type / cluster labels, sample/donor identifiers, PCA + Harmony reductions if you want hMEs
- Recommend ≥ 5,000 cells per group of interest after QC
install.packages("BiocManager")
BiocManager::install(c("WGCNA", "GeneOverlap", "UCell"))
devtools::install_github('smorabit/hdWGCNA', ref='dev')
Quick Start — Full Pipeline
library(Seurat)
library(hdWGCNA)
library(tidyverse)
library(cowplot)
library(patchwork)
library(WGCNA)
allowWGCNAThreads(nThreads = 8)
seurat_obj <- readRDS('your_annotated_seurat.rds')
seurat_obj <- SetupForWGCNA(
seurat_obj,
gene_select = "fraction",
fraction = 0.05,
wgcna_name = "INH"
)
seurat_obj <- MetacellsByGroups(
seurat_obj,
group.by = c("cell_type",
reduction
k
max_shared
ident.group
seurat_obj NormalizeMetacellsseurat_obj
seurat_obj SetDatExpr
seurat_obj
group_name
group.by
assay
layer
seurat_obj TestSoftPowersseurat_obj networkType
plot_list PlotSoftPowersseurat_obj
wrap_plotsplot_list ncol
seurat_obj ConstructNetwork
seurat_obj
tom_name
overwrite_tom
PlotDendrogramseurat_obj main
seurat_obj ScaleDataseurat_obj features VariableFeaturesseurat_obj
seurat_obj ModuleEigengenes
seurat_obj
group.by.vars
seurat_obj ModuleConnectivity
seurat_obj
group.by
group_name
seurat_obj ResetModuleNamesseurat_obj new_name
hub_df GetHubGenesseurat_obj n_hubs
libraryUCell
seurat_obj ModuleExprScoreseurat_obj n_genes method
saveRDSseurat_obj
The result object is the same Seurat object, with the entire hdWGCNA experiment stored in seurat_obj@misc[["INH"]]. Inspect the three key outputs:
modules <- GetModules(seurat_obj) %>% subset(module != 'grey')
head(modules)
hMEs <- GetMEs(seurat_obj)
MEs <- GetMEs(seurat_obj, harmonized = FALSE)
hub_df <- GetHubGenes(seurat_obj, n_hubs = 25)
Convenience runner: Rscript scripts/build_network.R --rds annotated.rds --cell-type INH --out seurat_hdwgcna.rds. See references/network_construction.md for parameter tuning, metacell diagnostics, and gotchas.
Downstream 1 — Differential Module Eigengenes (DMEs)
The question: does module-N activity differ between condition A and condition B (e.g. AD vs control, treated vs untreated)? This is the most common follow-up after network construction.
Two-group test — FindDMEs
group1 <- seurat_obj@meta.data %>%
filter(cell_type == 'INH', condition == 'AD') %>% rownames()
group2 <- seurat_obj@meta.data %>%
filter(cell_type == 'INH', condition == 'Ctrl') %>% rownames()
DMEs <- FindDMEs(
seurat_obj,
barcodes1 = group1,
barcodes2 = group2,
test.use = 'wilcox',
pseudocount.use = 0.01,
wgcna_name = 'INH'
)
Returns a dataframe with one row per module: p_val, avg_log2FC, pct.1, pct.2, p_val_adj, module. The pct.* columns are the fraction of cells in each group whose ME is non-zero — useful when a module is "active" in one condition but silent in the other.
Visualize
PlotDMEsLollipop(seurat_obj, DMEs, group.by = 'cell_type', wgcna_name = 'INH')
PlotDMEsVolcano (seurat_obj, DMEs, plot_labels = TRUE, wgcna_name = 'INH')
The lollipop is for a single comparison (great for figures). The volcano shows effect-size × significance at a glance — useful when you have many modules and want to spot the outliers.
One-vs-all — FindAllDMEs
Across each level of a grouping variable, runs that level vs all other cells:
DMEs_all <- FindAllDMEs(
seurat_obj,
group.by = 'cell_type',
wgcna_name = 'INH'
)
Module signature scores instead of MEs
If your modules don't have clean eigengenes (e.g. small modules with noisy first PC), test on UCell-based hub-gene signatures instead:
seurat_obj <- ModuleExprScore(seurat_obj, n_genes = 25, method = 'UCell')
DMEs_scores <- FindDMEs(
seurat_obj,
barcodes1 = group1, barcodes2 = group2,
features = 'ModuleScores',
wgcna_name = 'INH'
)
Convenience: Rscript scripts/downstream_analyses.R --rds seurat_hdwgcna.rds --task dme --group-by condition --group1 AD --group2 Ctrl --cell-type INH
Source: hdWGCNA Differential MEs tutorial.
Downstream 2 — Module–Trait Correlation
The question: which modules correlate with continuous or ordered phenotypes (age, Braak stage, disease severity, % tumor cells per spot)?
cur_traits <- c('age', 'Braak_stage', 'sex_binary', 'cell_density')
seurat_obj <- ModuleTraitCorrelation(
seurat_obj,
traits = cur_traits,
group.by = 'cell_type',
wgcna_name = 'INH'
)
mt_cor <- GetModuleTraitCorrelation(seurat_obj)
PlotModuleTraitCorrelation(
seurat_obj,
label = 'fdr',
label_symbol = 'stars',
text_size = 2.5,
high_color = 'yellow',
mid_color = 'black',
low_color =
plot_max
combine
Trait type compatibility
| Trait type | Supported? | Notes |
|---|
| Continuous (numeric) | ✅ | Use as-is |
| Binary categorical | ✅ | Convert to 0/1 before passing |
| Ordered categorical | ✅ | Convert to factor with explicit levels = c('low', 'mid', 'high'), then as.numeric() |
| Unordered categorical (>2 levels) | ❌ | The correlation is meaningless. Either dummy-encode or skip. |
Sample ID, mouse strain, batch — these are unordered categorical; if you want to test for batch effects on modules, use ANOVA via FindDMEs between groups instead.
Convenience: Rscript scripts/downstream_analyses.R --rds seurat_hdwgcna.rds --task trait --traits age,Braak_stage
Source: hdWGCNA Module-Trait Correlation tutorial.
Downstream 3 — Functional Enrichment
The question: what biological processes / pathways does each module represent?
Enrichr (web-based, fast, comprehensive)
library(enrichR)
dbs <- c(
'GO_Biological_Process_2023',
'GO_Cellular_Component_2023',
'GO_Molecular_Function_2023',
'KEGG_2021_Human',
'Reactome_2022',
'WikiPathway_2023_Human'
)
seurat_obj <- RunEnrichr(
seurat_obj,
dbs = dbs,
max_genes = 100,
wgcna_name = 'INH'
)
enrich_df <- GetEnrichrTable(seurat_obj)
Visualize
EnrichrBarPlot(
seurat_obj,
outdir = 'figures/enrichr/',
n_terms = 10,
plot_size = c(5, 7),
logscale = TRUE,
wgcna_name = 'INH'
)
EnrichrDotPlot(
seurat_obj,
mods = 'all',
database = 'GO_Biological_Process_2023',
n_terms = 2,
term_size = 8,
p_adj = FALSE,
wgcna_name = 'INH'
)
GSEA via fgsea (when you have a ranked gene list)
For a continuous test (not over-representation), rank genes by kME within a module and use fgsea against any MSigDB collection:
library(fgsea)
library(msigdbr)
modules <- GetModules(seurat_obj) %>% filter(module != 'grey')
target_mod <- 'INH-M3'
ranks <- modules %>%
arrange(desc(.data[[paste0('kME_', target_mod)]])) %>%
pull(.data[[paste0('kME_', target_mod)]], name = gene_name)
pathways <- msigdbr(species = 'Homo sapiens', category = 'H') %>%
split(x = .$gene_symbol, f = .gs_name
gsea_res fgseapathways ranks minSize maxSize
headgsea_resorderpadj
Convenience: Rscript scripts/downstream_analyses.R --rds seurat_hdwgcna.rds --task enrich --dbs GO_Biological_Process_2023,KEGG_2021_Human
Source: hdWGCNA Enrichment tutorial.
Visualization Cookbook
Module feature plot (UMAP × module)
plot_list <- ModuleFeaturePlot(seurat_obj, features = 'hMEs', order = TRUE)
wrap_plots(plot_list, ncol = 6)
Module dot plot across cell types
hMEs <- GetMEs(seurat_obj, harmonized = TRUE)
modules <- GetModules(seurat_obj)
mods <- levels(modules$module); mods <- mods[mods != 'grey']
seurat_obj@meta.data <- cbind(seurat_obj@meta.data, hMEs)
DotPlot(seurat_obj, features = mods, group.by = 'cell_type') +
RotatedAxis() +
scale_color_gradient2(high = 'red', mid = 'grey95', low = 'blue')
Hub gene network (top hubs from each module)
ModuleNetworkPlot(seurat_obj, n_hubs = 5)
HubGeneNetworkPlot(seurat_obj, n_hubs = 5, n_other = 10)
Module–module correlation
ModuleCorrelogram(seurat_obj, features = 'hMEs')
Module radar plot across cell subtypes
ModuleRadarPlot(
seurat_obj,
group.by = 'cluster_label',
barcodes = seurat_obj@meta.data %>% filter(cell_type == 'INH') %>% rownames(),
axis.label.size = 4
)
More patterns + custom layouts in references/plotting_guide.md.
Key Parameters to Adjust
Metacell construction (MetacellsByGroups)
k (default 25): cells per metacell. Lower → finer resolution but noisier. For small datasets (< 2000 cells/type), drop to 10–15.
max_shared (default 10): caps cell sharing across metacells. Higher → fewer metacells but more redundancy.
group.by: always include both cell type and sample — otherwise metacells will mix donors and you'll lose biology.
Soft power
- The plot from
PlotSoftPowers should show R² climbing past 0.8. Pick the lowest power that crosses this threshold.
- Default
networkType='signed' (recommended). 'unsigned' collapses positive and negative correlations — usually wrong.
Network construction (ConstructNetwork)
minModuleSize (default 50): smallest allowed module. Reduce for small datasets.
mergeCutHeight (default 0.2): merges similar modules. Increase (0.3–0.4) to consolidate noisy modules.
deepSplit (0–4): how aggressively to split. 2 is default; 4 produces many tiny modules.
Module eigengenes (ModuleEigengenes)
group.by.vars: pass your batch/donor variable. Without this you get raw MEs (MEs) — use harmonized (hMEs) for downstream stats.
kME / hub genes
n_hubs (default 25 for ModuleExprScore): how many top-kME genes count as "the module's signature". 25 is a good default; for sparse single-cell data, 50 makes the signature more stable.
Best Practices
- Run one network per major cell type, not one big network across all cells. WGCNA's TOM is dominated by cell-type identity otherwise — the "modules" will just be marker-gene signatures for each cell type rather than meaningful within-cell-type co-expression.
- Save the Seurat object after
ConstructNetwork. It's the slow step — minutes to hours depending on n cells and n genes. Everything downstream (MEs, kME, hubs, DMEs, enrichment) is fast.
- The grey module is the dumping ground for unassigned genes. Always
subset(module != 'grey') before downstream analyses.
- Inspect
PlotDendrogram BEFORE downstream analysis. If the dendrogram has one giant module and a tiny grey blob, your soft power is too low. If it has 30+ skinny modules, soft power is too high or minModuleSize is too small.
- For module-trait correlation with categorical traits, dummy-encode unordered factors instead of relying on alphabetical order. Otherwise the correlation is meaningless.
- Cross-condition module preservation matters. A module discovered in controls may not exist in cases. Test with
ModulePreservation before trusting cross-condition DME results.
End-to-End Template
assets/hdwgcna_template.R is a single parameterized script — set the variables at the top (input .rds, cell type, sample column, condition column) and run end-to-end through DME + module-trait correlation + Enrichr.
Rscript assets/hdwgcna_template.R
Convenience Scripts
scripts/build_network.R — network construction only (the slow step). Saves an annotated Seurat object.
scripts/downstream_analyses.R — DME / module-trait / enrichment. Subtask selected via --task dme|trait|enrich|all.
References