基因共表达网络分析(WGCNA)¶
一句话概述¶
WGCNA(Weighted Gene Co-expression Network Analysis)通过构建基因间加权相关网络、识别共表达模块、关联模块与表型性状,系统发现生物学功能相关的基因集群和潜在hub基因。
核心知识点表格¶
| 知识点 | 说明 |
|---|---|
| 共表达网络 | 基因间表达量的相关性构建的网络 |
| 软阈值(soft threshold) | 用幂函数加权相关性使网络近似无标度拓扑 |
| 无标度网络(scale-free) | 节点连通度服从幂律分布(少数hub基因连接极多) |
| TOM (Topological Overlap Matrix) | 考虑共享邻居的相似性度量,比相关系数更稳健 |
| 模块(Module) | 高度共表达的基因簇,用颜色命名 |
| 模块特征基因(Module Eigengene, ME) | 模块第一主成分,代表模块整体表达模式 |
| Hub基因 | 模块内连通度最高的基因,通常是关键调控因子 |
| GS (Gene Significance) | 基因表达与性状的相关系数 |
| MM (Module Membership) | 基因表达与模块特征基因的相关系数 |
| 性状关联 | 将模块与临床/表型信息关联,找功能相关模块 |
各步骤详解¶
第一步:数据准备与预处理¶
白话解释: WGCNA需要一个基因表达矩阵(行=样本,列=基因),样本量不能太少(至少15个,推荐>25个)。数据需要先做标准的归一化和过滤,去除低表达基因和离群样本。
技术细节: - 输入:归一化后的表达矩阵(如RNA-seq的VST/rlog数据或log2(TPM+1)) - 样本量要求:最少15个样本,理想>25个 - 基因过滤:去除低表达和低变异基因(保留top 25-50%变异系数的基因,或MAD top 5000-10000) - 异常样本检测:层次聚类+cutreeStatic - 不要使用差异表达基因作为输入(WGCNA需要无偏的表达谱)
代码示例:
library(WGCNA)
options(stringsAsFactors = FALSE)
enableWGCNAThreads(nThreads = 8)
# 读取表达矩阵(行=样本,列=基因)
# 如果原始矩阵是基因×样本,需要转置
expr_data <- read.csv("expression_matrix.csv", row.names = 1)
datExpr <- as.data.frame(t(expr_data)) # 转置:行=样本,列=基因
# 读取性状/临床信息
traitData <- read.csv("clinical_traits.csv", row.names = 1)
# === 基因过滤 ===
# 方法1:去除低表达基因(至少在一定比例样本中有表达)
# 方法2:保留变异最大的前N个基因
library(matrixStats)
gene_var <- colVars(as.matrix(datExpr))
names(gene_var) <- colnames(datExpr)
top_genes <- names(sort(gene_var, decreasing = TRUE))[1:5000] # top 5000变异基因
datExpr <- datExpr[, top_genes]
# === 样本质控 ===
# 检查缺失值
gsg <- goodSamplesGenes(datExpr, verbose = 3)
if (!gsg$allOK) {
datExpr <- datExpr[gsg$goodSamples, gsg$goodGenes]
}
# 样本层次聚类检测离群
sampleTree <- hclust(dist(datExpr), method = "average")
plot(sampleTree, main = "Sample Dendrogram", sub = "", xlab = "", cex.lab = 1.5)
# 去除离群样本(手动设定切割高度)
abline(h = 15, col = "red") # 根据图形调整高度
clust <- cutreeStatic(sampleTree, cutHeight = 15, minSize = 10)
keepSamples <- (clust == 1)
datExpr <- datExpr[keepSamples, ]
traitData <- traitData[keepSamples, ]
cat("Samples:", nrow(datExpr), " Genes:", ncol(datExpr), "\n")
第二步:软阈值选择¶
白话解释: WGCNA用"软阈值"(power β)把基因间的相关系数提升为幂次方,让强相关更强、弱相关更弱。选择β的标准是让网络的连通度分布近似"无标度"(power-law)——自然中的生物网络大多是无标度的。
技术细节: - 软阈值公式:a_ij = |cor(x_i, x_j)|^β - 选择标准:R² > 0.85(Scale-free topology fit)且mean connectivity不能太低 - 通常范围:无符号/signed hybrid网络β=6左右(合理范围<15),有符号(signed)网络β=12左右(合理范围<30) - 如果R²怎么都达不到0.85,说明数据可能不适合WGCNA或需要进一步清理
代码示例:
# 选择软阈值
powers <- c(c(1:10), seq(12, 20, by = 2))
sft <- pickSoftThreshold(datExpr,
powerVector = powers,
networkType = "signed", # 推荐signed网络
verbose = 5)
# 可视化
par(mfrow = c(1, 2))
# Scale-free topology fit (R²)
plot(sft$fitIndices[, 1], -sign(sft$fitIndices[, 3]) * sft$fitIndices[, 2],
xlab = "Soft Threshold (power)",
ylab = "Scale Free Topology Model Fit (R²)",
type = "n",
main = "Scale Independence")
text(sft$fitIndices[, 1], -sign(sft$fitIndices[, 3]) * sft$fitIndices[, 2],
labels = powers, cex = 0.9, col = "red")
abline(h = 0.85, col = "red")
# Mean connectivity
plot(sft$fitIndices[, 1], sft$fitIndices[, 5],
xlab = "Soft Threshold (power)",
ylab = "Mean Connectivity",
type = "n",
main = "Mean Connectivity")
text(sft$fitIndices[, 1], sft$fitIndices[, 5],
labels = powers, cex = 0.9, col = "red")
# 选择power(通常选R²首次>0.85的最小power)
softPower <- sft$powerEstimate
cat("Selected soft threshold:", softPower, "\n")
# 如果自动选择不理想,手动设置(如6或8)
if (is.na(softPower)) softPower <- 8
第三步:网络构建与模块检测¶
白话解释: 有了软阈值后,WGCNA计算所有基因对之间的TOM(拓扑重叠矩阵),然后对TOM做层次聚类,切割聚类树得到"模块"——每个模块是一群高度共表达的基因,用颜色标记。
技术细节: - 一步法:blockwiseModules()(适合大数据集,自动分块) - 分步法:手动计算adjacency→TOM→聚类→切割(更灵活) - TOM考虑了共享邻居信息,比纯相关系数更稳健 - Dynamic Tree Cut用于自适应切割聚类树 - mergeCutHeight控制相似模块合并的阈值
代码示例:
# === 一步法(推荐初学者)===
net <- blockwiseModules(datExpr,
power = softPower,
networkType = "signed",
TOMType = "signed",
minModuleSize = 30, # 最小模块基因数
reassignThreshold = 0,
mergeCutHeight = 0.25, # 模块合并阈值(越小合并越少)
numericLabels = TRUE,
pamRespectsDendro = FALSE,
saveTOMs = TRUE,
saveTOMFileBase = "TOM",
verbose = 3,
maxBlockSize = 20000 # 内存限制相关
)
# 模块颜色
moduleColors <- labels2colors(net$colors)
table(moduleColors)
# 可视化模块聚类树
plotDendroAndColors(net$dendrograms[[1]],
moduleColors[net$blockGenes[[1]]],
"Module Colors",
dendroLabels = FALSE,
hang = 0.03,
addGuide = TRUE,
guideHang = 0.05)
# === 分步法(更灵活)===
# 1. 计算邻接矩阵
adjacency <- adjacency(datExpr, power = softPower, type = "signed")
# 2. 计算TOM
TOM <- TOMsimilarity(adjacency, TOMType = "signed")
dissTOM <- 1 - TOM
# 3. 层次聚类
geneTree <- hclust(as.dist(dissTOM), method = "average")
# 4. Dynamic Tree Cut
dynamicMods <- cutreeDynamic(dendro = geneTree,
distM = dissTOM,
deepSplit = 2,
pamRespectsDendro = FALSE,
minClusterSize = 30)
# 5. 合并相似模块
MEList <- moduleEigengenes(datExpr, colors = labels2colors(dynamicMods))
MEs <- MEList$eigengenes
MEDiss <- 1 - cor(MEs)
METree <- hclust(as.dist(MEDiss), method = "average")
plot(METree, main = "Clustering of module eigengenes")
abline(h = 0.25, col = "red")
merge <- mergeCloseModules(datExpr, labels2colors(dynamicMods), cutHeight = 0.25)
moduleColors <- merge$colors
MEs <- merge$newMEs
第四步:模块-性状关联¶
白话解释: 每个模块有一个"代表"——模块特征基因(ME),它是该模块所有基因表达量的第一主成分。把ME和你关心的表型/临床信息(如疾病状态、年龄、生存时间)做相关分析,找出与特定性状显著相关的模块。
代码示例:
# 准备性状数据(数值化)
# 分类变量需要编码为0/1
nSamples <- nrow(datExpr)
# 计算模块特征基因
MEs0 <- moduleEigengenes(datExpr, moduleColors)$eigengenes
MEs <- orderMEs(MEs0)
# 计算模块-性状相关性
moduleTraitCor <- cor(MEs, traitData, use = "p")
moduleTraitPvalue <- corPvalueStudent(moduleTraitCor, nSamples)
# 热图可视化
textMatrix <- paste(signif(moduleTraitCor, 2), "\n(",
signif(moduleTraitPvalue, 1), ")", sep = "")
dim(textMatrix) <- dim(moduleTraitCor)
par(mar = c(6, 8.5, 3, 3))
labeledHeatmap(Matrix = moduleTraitCor,
xLabels = names(traitData),
yLabels = names(MEs),
ySymbols = names(MEs),
colorLabels = FALSE,
colors = blueWhiteRed(50),
textMatrix = textMatrix,
setStdMargins = FALSE,
cex.text = 0.5,
zlim = c(-1, 1),
main = "Module-Trait Relationships")
# 找显著关联的模块
sig_modules <- which(moduleTraitPvalue[, "Disease"] < 0.05)
cat("Significant modules for Disease:\n")
print(names(MEs)[sig_modules])
第五步:Hub基因识别¶
白话解释: Hub基因是模块内最重要的基因——它和模块内其他基因的关联性最强(高MM),同时和目标性状的关联也强(高GS)。这些hub基因通常是模块功能的关键驱动者。
技术细节: - MM (Module Membership):基因表达与模块ME的相关性 - GS (Gene Significance):基因表达与感兴趣性状的相关性 - Hub基因标准:|MM| > 0.8 且 |GS| > 0.2(或自定义阈值) - kME等同于MM(模块内连通度) - Hub基因可导出到Cytoscape可视化网络
代码示例:
# 计算Gene Significance和Module Membership
# 以"Disease"性状为例
trait_of_interest <- as.data.frame(traitData$Disease)
names(trait_of_interest) <- "Disease"
# GS: 基因与性状的相关性
GS <- as.data.frame(cor(datExpr, trait_of_interest, use = "p"))
names(GS) <- paste("GS.", names(trait_of_interest), sep = "")
GSPvalue <- as.data.frame(corPvalueStudent(as.matrix(GS), nSamples))
# MM: 基因与各模块ME的相关性
MM <- as.data.frame(cor(datExpr, MEs, use = "p"))
names(MM) <- paste("MM", substring(names(MEs), 3), sep = "")
MMPvalue <- as.data.frame(corPvalueStudent(as.matrix(MM), nSamples))
# 选择感兴趣的模块(如与Disease最相关的blue模块)
target_module <- "blue"
# 模块内基因
module_genes <- colnames(datExpr)[moduleColors == target_module]
cat("Genes in", target_module, "module:", length(module_genes), "\n")
# 筛选Hub基因
mm_col <- paste("MM", target_module, sep = "")
gs_col <- paste("GS.", "Disease", sep = "")
hub_criteria <- abs(MM[module_genes, mm_col]) > 0.8 &
abs(GS[module_genes, gs_col]) > 0.2
hub_genes <- module_genes[hub_criteria]
cat("Hub genes:", length(hub_genes), "\n")
print(hub_genes)
# GS vs MM散点图
verboseScatterplot(abs(MM[module_genes, mm_col]),
abs(GS[module_genes, gs_col]),
xlab = paste("Module Membership in", target_module, "module"),
ylab = paste("Gene significance for Disease"),
main = paste("MM vs. GS\n"),
cex.main = 1.2, cex.lab = 1.2, cex.axis = 1.2,
col = target_module)
# 导出网络到Cytoscape
# 提取模块内top连接
TOM_module <- TOM[moduleColors == target_module, moduleColors == target_module]
dimnames(TOM_module) <- list(module_genes, module_genes)
# 导出top connections
cyt <- exportNetworkToCytoscape(TOM_module,
edgeFile = paste("CytoscapeInput-edges-", target_module, ".txt", sep = ""),
nodeFile = paste("CytoscapeInput-nodes-", target_module, ".txt", sep = ""),
weighted = TRUE,
threshold = 0.02, # 连接权重阈值
nodeNames = module_genes,
nodeAttr = moduleColors[moduleColors == target_module])
第六步:模块功能富集分析¶
白话解释: 确定了感兴趣的模块后,对模块内的基因做GO/KEGG富集分析,看这些共表达的基因到底参与了什么生物过程。
代码示例:
library(clusterProfiler)
library(org.Hs.eg.db)
# 对目标模块做GO富集
module_genes_entrez <- bitr(hub_genes,
fromType = "SYMBOL",
toType = "ENTREZID",
OrgDb = org.Hs.eg.db)
# GO富集
ego <- enrichGO(gene = module_genes_entrez$ENTREZID,
OrgDb = org.Hs.eg.db,
ont = "BP",
pAdjustMethod = "BH",
pvalueCutoff = 0.05,
readable = TRUE)
dotplot(ego, showCategory = 20, title = paste(target_module, "module GO enrichment"))
# KEGG富集
ekegg <- enrichKEGG(gene = module_genes_entrez$ENTREZID,
organism = "hsa",
pvalueCutoff = 0.05)
dotplot(ekegg, showCategory = 15, title = paste(target_module, "module KEGG enrichment"))
# 对所有模块批量做富集
all_modules <- unique(moduleColors)
module_enrichment <- list()
for (mod in all_modules) {
mod_genes <- colnames(datExpr)[moduleColors == mod]
mod_entrez <- bitr(mod_genes, fromType = "SYMBOL", toType = "ENTREZID", OrgDb = org.Hs.eg.db)
if (nrow(mod_entrez) > 10) {
module_enrichment[[mod]] <- enrichGO(gene = mod_entrez$ENTREZID,
OrgDb = org.Hs.eg.db, ont = "BP",
pvalueCutoff = 0.05, readable = TRUE)
}
}
实战命令(可复制)¶
# ===== WGCNA完整流程(可直接运行)=====
library(WGCNA)
options(stringsAsFactors = FALSE)
enableWGCNAThreads(nThreads = 8)
# 1. 读取数据(行=样本, 列=基因)
datExpr <- read.csv("expr_matrix_samples_as_rows.csv", row.names = 1)
traitData <- read.csv("clinical_data.csv", row.names = 1)
# 2. 质控
gsg <- goodSamplesGenes(datExpr, verbose = 3)
if (!gsg$allOK) datExpr <- datExpr[gsg$goodSamples, gsg$goodGenes]
# 3. 软阈值
powers <- c(1:10, seq(12, 20, 2))
sft <- pickSoftThreshold(datExpr, powerVector = powers, networkType = "signed", verbose = 5)
softPower <- sft$powerEstimate
if (is.na(softPower)) softPower <- 8
# 4. 网络构建
net <- blockwiseModules(datExpr, power = softPower, networkType = "signed",
TOMType = "signed", minModuleSize = 30, mergeCutHeight = 0.25,
numericLabels = TRUE, saveTOMs = TRUE, verbose = 3, maxBlockSize = 20000)
moduleColors <- labels2colors(net$colors)
# 5. 模块-性状关联
MEs <- moduleEigengenes(datExpr, moduleColors)$eigengenes
MEs <- orderMEs(MEs)
moduleTraitCor <- cor(MEs, traitData, use = "p")
moduleTraitPvalue <- corPvalueStudent(moduleTraitCor, nrow(datExpr))
# 6. Hub基因
# (选择与目标性状最相关的模块)
target_module <- "blue" # 根据热图结果选择
module_genes <- colnames(datExpr)[moduleColors == target_module]
MM <- cor(datExpr[, module_genes], MEs[, paste0("ME", target_module)], use = "p")
GS <- cor(datExpr[, module_genes], traitData$Disease, use = "p")
hub_genes <- module_genes[abs(MM) > 0.8 & abs(GS) > 0.2]
面试常问点¶
Q1: WGCNA的软阈值为什么要让网络满足无标度特征?¶
A: 生物网络(蛋白互作网络、代谢网络等)通常具有无标度特征:大多数节点连接度低,少数hub节点连接度极高。通过调整软阈值β使网络的连通度分布近似幂律分布(log-log线性),可以让构建的共表达网络更接近真实生物网络的拓扑结构,从而更可靠地识别hub基因。
Q2: WGCNA需要多少样本?¶
A: 官方建议至少15个样本,实际经验表明>25个效果较好,>50个最佳。样本量影响:(1) 相关系数估计的稳定性;(2) 模块检测的稳健性;(3) 模块-性状关联的统计力。小样本(<15)时WGCNA结果不稳定,建议使用其他方法(如加权GCN但不做WGCNA整套流程)。
Q3: signed和unsigned网络的区别?¶
A: Unsigned:只看相关性绝对值,正相关和负相关的基因可能在同一模块。Signed:区分正负相关,只有正相关的基因聚在一起。推荐用signed——生物学上共表达基因(正相关)更可能参与相同通路;负相关基因应分属不同模块。Signed hybrid是折中方案。
Q4: 为什么不能用差异表达基因作为WGCNA输入?¶
A: (1) 预筛选差异基因引入了人为偏差——只保留了与特定条件相关的基因,丢失了其他生物学关系;(2) WGCNA设计是对无偏的全转录组数据做无监督聚类;(3) 差异基因间的"共表达"可能只是因为它们都和同一个条件相关,而非真正的共调控关系;(4) 正确做法是先WGCNA再看模块与性状的关联。
Q5: Module Membership和Gene Significance如何解读?¶
A: MM衡量一个基因"多像这个模块的成员"——MM=0.9说明它和模块整体表达模式高度一致,是模块核心基因。GS衡量一个基因"多与目标性状相关"——GS=0.5说明它可能是疾病相关基因。Hub基因同时具有高MM和高GS,既是模块核心又与性状强相关,是最值得实验验证的候选。
Q6: 模块合并阈值(mergeCutHeight)如何选择?¶
A: mergeCutHeight是合并ME相关性>1-阈值的模块。默认0.25意味着相关性>0.75的模块会合并。选择策略:(1) 画ME聚类树看分支高度;(2) 太高(如0.4)导致过多合并,可能丢失生物学差异;(3) 太低(如0.1)导致模块过多且碎片化。通常0.2-0.3是合理范围。
Q7: WGCNA结果如何验证?¶
A: (1) 模块保存性检验:在独立数据集中检验模块是否存在(modulePreservation函数);(2) 功能富集:模块基因是否富集于已知通路;(3) Hub基因验证:文献支持或实验验证(qPCR/Western/knockdown);(4) 与已知调控关系比较:hub基因是否已知为该通路的调控因子。
Q8: WGCNA的局限性有哪些?¶
A: (1) 需要较大样本量(>15,最好>25);(2) 假设线性相关关系,非线性共调控可能被遗漏;(3) 结果依赖参数选择(power、minModuleSize、mergeCutHeight);(4) 计算量大,基因数>20000时内存需求高;(5) 识别的是相关而非因果关系;(6) 对batch effect敏感。
易错点¶
1. 输入数据方向错误(应为行=样本,列=基因)¶
问题: 多数表达矩阵是行=基因、列=样本。直接输入WGCNA会把样本当作基因处理。 正确做法: 使用t()转置表达矩阵,确认nrow=样本数、ncol=基因数。
2. 使用原始count而非归一化数据¶
问题: raw count有文库大小偏差,影响相关系数计算。 正确做法: 使用DESeq2的VST/rlog变换、或log2(TPM+1)、或log2(FPKM+1)。注意不要用DESeq2的normalized counts(仍是count空间)。
3. 软阈值R²达不到0.85就用默认值¶
问题: 有时数据因为batch effect、异常样本等原因无法满足scale-free,强行选power会得到无意义结果。 正确做法: (1) 检查并去除异常样本/batch effect;(2) 确认数据适合WGCNA;(3) 如果R²最高只到0.7-0.8,选择该范围内connectivity不太低的power(通常mean connectivity > 50-100)。
4. 基因数过多导致内存溢出¶
问题: >20000基因时TOM计算需要大量内存(N^2矩阵)。 正确做法: (1) 用blockwiseModules()自动分块计算;(2) 预先过滤到5000-10000高变异基因;(3) 使用高内存服务器(>64GB RAM)。
5. 性状数据包含非数值型变量¶
问题: WGCNA的cor()需要数值型数据,字符型变量(如"Male"/"Female")会报错。 正确做法: 将分类变量转为0/1编码。多水平分类变量(如A/B/C组)做dummy编码或序数编码。
6. 过度解读单个hub基因¶
问题: 将WGCNA找到的hub基因直接当作"关键驱动基因"或"治疗靶点"。 正确做法: hub基因只是统计意义上的"网络中心",需要结合文献、功能注释和实验验证。高MM可能只是表达量高或变异大。
7. 忽略模块保存性验证¶
问题: 在单一数据集上找到的模块可能是过拟合,换一个数据集就不存在了。 正确做法: 使用modulePreservation()在独立数据集中验证模块的保存性(Zsummary > 10为高度保存)。
补充知识¶
适用场景¶
| 场景 | 适合WGCNA | 不适合WGCNA |
|---|---|---|
| 样本量 | >15(最好>25) | <10 |
| 实验设计 | 多条件/渐变/时间序列 | 仅2组对照 |
| 目标 | 发现共调控模块、hub基因 | 仅找差异基因 |
| 数据类型 | RNA-seq、microarray | 单细胞(需pseudobulk) |
单细胞WGCNA变体¶
- hdWGCNA:专为单细胞设计的WGCNA,使用metacells/pseudocells减少稀疏性
- Monocle3 + WGCNA:在pseudotime bin中做WGCNA
- scWGCNA:直接在单细胞数据上运行的改进版本
与其他网络方法的对比¶
| 方法 | 特点 | 适用场景 |
|---|---|---|
| WGCNA | 加权无标度共表达网络,模块化 | 模块发现、hub基因 |
| ARACNe | 互信息+数据处理不等式 | 转录调控网络推断 |
| GENIE3 | 随机森林推断调控关系 | GRN构建 |
| SCENIC | TF调控子推断(与scRNA-seq结合) | 单细胞调控网络 |
| CEMiTool | 自动化WGCNA(简化参数选择) | 快速模块分析 |
本教程系统覆盖了WGCNA从数据准备到hub基因识别的完整流程,适合转录组共表达分析和生物标志物发现。