WGCNA 模块-性状热图改用 BH 校正 p 值,并支持 job 置顶与优先级排序

This commit is contained in:
potaser
2026-08-30 00:17:25 +08:00
parent f712a657c0
commit abdd486ed8
4 changed files with 63 additions and 6 deletions
+15 -4
View File
@@ -360,23 +360,29 @@ colnames(condition_mm) <- sub("^condition", "", colnames(condition_mm))
traitData <- as.data.frame(condition_mm)
moduleTraitCor <- cor(MEs, traitData, use = "p")
moduleTraitPvalue <- corPvalueStudent(moduleTraitCor, nSamples = nrow(datExpr))
moduleTraitPadj <- matrix(
p.adjust(as.vector(moduleTraitPvalue), method = "BH"),
nrow = nrow(moduleTraitPvalue), ncol = ncol(moduleTraitPvalue),
dimnames = dimnames(moduleTraitPvalue)
)
write.table(moduleTraitCor, file = paste0(kv$out_prefix, ".module_trait_correlation.tsv"), sep = "\t", quote = FALSE, col.names = NA)
write.table(moduleTraitPvalue, file = paste0(kv$out_prefix, ".module_trait_pvalue.tsv"), sep = "\t", quote = FALSE, col.names = NA)
write.table(moduleTraitPadj, file = paste0(kv$out_prefix, ".module_trait_padj.tsv"), sep = "\t", quote = FALSE, col.names = NA)
write.table(MEs, file = paste0(kv$out_prefix, ".module_eigengenes.tsv"), sep = "\t", quote = FALSE, col.names = NA)
textMatrix <- paste(signif(moduleTraitCor, 2), "\n(", signif(moduleTraitPvalue, 1), ")", sep = "")
textMatrix <- paste(signif(moduleTraitCor, 2), "\n(FDR ", signif(moduleTraitPadj, 2), ")", sep = "")
dim(textMatrix) <- dim(moduleTraitCor)
png(paste0(kv$out_prefix, ".module_trait_heatmap.png"), width = 1800, height = 1600, res = 220)
labeledHeatmap(Matrix = moduleTraitCor, xLabels = colnames(traitData), yLabels = names(MEs),
ySymbols = names(MEs), colorLabels = FALSE, colors = blueWhiteRed(50),
textMatrix = textMatrix, setStdMargins = FALSE, cex.text = 0.7,
zlim = c(-1, 1), main = "Module-trait relationships")
zlim = c(-1, 1), main = "Module-trait relationships (BH-adjusted P values)")
dev.off()
pdf(paste0(kv$out_prefix, ".module_trait_heatmap.pdf"), width = 8, height = 7)
labeledHeatmap(Matrix = moduleTraitCor, xLabels = colnames(traitData), yLabels = names(MEs),
ySymbols = names(MEs), colorLabels = FALSE, colors = blueWhiteRed(50),
textMatrix = textMatrix, setStdMargins = FALSE, cex.text = 0.7,
zlim = c(-1, 1), main = "Module-trait relationships")
zlim = c(-1, 1), main = "Module-trait relationships (BH-adjusted P values)")
dev.off()
geneModule <- data.frame(gene_id = colnames(datExpr), module = moduleColors)
@@ -394,7 +400,8 @@ colnames(moduleSizes) <- c("module", "genes")
write.table(moduleSizes, file = paste0(kv$out_prefix, ".module_sizes.tsv"), sep = "\t", quote = FALSE, row.names = FALSE)
saveRDS(list(datExpr = datExpr, moduleColors = moduleColors, MEs = MEs, softPower = softPower,
moduleTraitCor = moduleTraitCor, moduleTraitPvalue = moduleTraitPvalue),
moduleTraitCor = moduleTraitCor, moduleTraitPvalue = moduleTraitPvalue,
moduleTraitPadj = moduleTraitPadj),
file = paste0(kv$out_prefix, ".wgcna_result.rds"))
summary <- data.frame(
@@ -475,6 +482,7 @@ def write_report(path: Path, manifest: dict[str, Any]) -> None:
"",
"- WGCNA is most reliable with larger sample sizes; interpret very small RNA-seq designs cautiously.",
"- Genes are filtered by expression and variance before TOM construction to keep the analysis tractable.",
"- Module-trait heatmap labels use Benjamini-Hochberg adjusted P values (FDR) across all displayed module-trait tests; raw P values are retained as a separate artifact.",
"",
"## Artifacts",
"",
@@ -581,6 +589,7 @@ def main(argv: list[str] | None = None) -> int:
"gene_dendrogram_modules_pdf": (Path(f"{out_prefix}.gene_dendrogram_modules.pdf"), "pdf"),
"module_trait_correlation": (Path(f"{out_prefix}.module_trait_correlation.tsv"), "tsv"),
"module_trait_pvalue": (Path(f"{out_prefix}.module_trait_pvalue.tsv"), "tsv"),
"module_trait_padj": (Path(f"{out_prefix}.module_trait_padj.tsv"), "tsv"),
"module_trait_heatmap_png": (Path(f"{out_prefix}.module_trait_heatmap.png"), "image"),
"module_trait_heatmap_pdf": (Path(f"{out_prefix}.module_trait_heatmap.pdf"), "pdf"),
"module_eigengenes": (Path(f"{out_prefix}.module_eigengenes.tsv"), "tsv"),
@@ -607,6 +616,7 @@ def main(argv: list[str] | None = None) -> int:
metrics = read_summary(Path(f"{out_prefix}.wgcna_summary.tsv"))
metrics["conditions_detail"] = {sample: groups[sample] for sample in samples}
metrics["matrix_type"] = matrix_type
metrics["module_trait_pvalue_adjustment"] = "BH_global"
manifest = {
"manifest_version": "1.0",
"stage": "rnaseq_wgcna",
@@ -637,6 +647,7 @@ def main(argv: list[str] | None = None) -> int:
"min_module_size": args.min_module_size,
"merge_cut_height": args.merge_cut_height,
"powers": args.powers,
"module_trait_pvalue_adjustment": "BH_global",
"cpu": threads,
"threads": threads,
},
+23 -2
View File
@@ -8970,9 +8970,28 @@ async def list_existing_jobs():
except OSError as exc:
skipped.append({"job_id": job_dir.name, "reason": str(exc)})
continue
job_entries.append((job_dir, stat_result.st_mtime))
pinned = False
priority = 0
priority_file = job_dir / ".plantai_job_priority.json"
if priority_file.is_file():
try:
priority_data = json.loads(priority_file.read_text(encoding="utf-8"))
pinned = priority_data.get("pinned") is True
priority = int(priority_data.get("priority", 0))
except (OSError, ValueError, TypeError, json.JSONDecodeError) as exc:
skipped.append({
"job_id": job_dir.name,
"reason": f"invalid_job_priority_metadata: {exc}",
})
job_entries.append((job_dir, stat_result.st_mtime, pinned, priority))
for job_dir, st_mtime in sorted(job_entries, key=lambda item: item[1], reverse=True):
# Pinned jobs come first, then explicit priority, while mtime remains
# the stable default ordering for jobs without metadata.
for job_dir, st_mtime, pinned, priority in sorted(
job_entries,
key=lambda item: (item[2], item[3], item[1]),
reverse=True,
):
if job_dir.is_dir() and job_dir.name.startswith("job_"):
# 检查目录内容
final_dir = job_dir / "final"
@@ -8990,6 +9009,8 @@ async def list_existing_jobs():
jobs.append({
"job_id": job_dir.name,
"created_at": mtime.strftime("%Y-%m-%d %H:%M:%S"),
"pinned": pinned,
"priority": priority,
"completed_stages": completed_stages,
"has_input": (INPUT_ROOT / job_dir.name).exists(),
"workspace_dir": str(job_dir)
@@ -46,6 +46,7 @@
- `{prefix}.rnaseq_wgcna.module_eigengenes.tsv`
- `{prefix}.rnaseq_wgcna.module_trait_correlation.tsv`
- `{prefix}.rnaseq_wgcna.module_trait_pvalue.tsv`
- `{prefix}.rnaseq_wgcna.module_trait_padj.tsv`
- `{prefix}.rnaseq_wgcna.wgcna_summary.tsv`
### final/ 图形产物
@@ -103,8 +104,16 @@ moduleColors <- labels2colors(dynamicMods)
merge <- mergeCloseModules(datExpr, moduleColors, cutHeight = mergeCutHeight)
MEs <- merge$newMEs
moduleTraitCor <- cor(MEs, traits, use = "p")
moduleTraitPvalue <- corPvalueStudent(moduleTraitCor, nSamples = nrow(datExpr))
moduleTraitPadj <- matrix(
p.adjust(as.vector(moduleTraitPvalue), method = "BH"),
nrow = nrow(moduleTraitPvalue), ncol = ncol(moduleTraitPvalue),
dimnames = dimnames(moduleTraitPvalue)
)
```
模块-性状热图单元格显示相关系数与 `moduleTraitPadj`。校正范围为当前热图中的全部模块×性状检验,方法为 Benjamini-Hochberg;原始 P 值继续写入 `module_trait_pvalue.tsv` 供追溯和兼容旧下游。
## 7. 解释限制
WGCNA 对样本数量敏感。少样本数据可以完成计算,但模块稳定性和模块-性状相关性需要谨慎解释,建议后续进行模块稳定性或重采样验证。
@@ -0,0 +1,16 @@
from pathlib import Path
from apps.cli.run_rnaseq_wgcna import write_r_script
def test_wgcna_heatmap_uses_bh_adjusted_pvalues(tmp_path: Path) -> None:
script_path = tmp_path / "run_wgcna.R"
write_r_script(script_path, threads=2)
script = script_path.read_text(encoding="utf-8")
assert 'p.adjust(as.vector(moduleTraitPvalue), method = "BH")' in script
assert '".module_trait_pvalue.tsv"' in script
assert '".module_trait_padj.tsv"' in script
assert '"\\n(FDR ", signif(moduleTraitPadj, 2)' in script
assert "Module-trait relationships (BH-adjusted P values)" in script
assert "moduleTraitPadj = moduleTraitPadj" in script