Skip to content

图表清单与解读

跑完流水线会出 11 类图(每张都是 600 dpi PNG + 矢量 PDF 双份)。 这一页讲:每张图在哪、怎么读、能下什么结论、以及哪些坑不能踩。 数字列表见 结果怎么看,方法原理见 从零读懂 MR


一、图在哪:一张总表

文件出自
火山图(分面)results/screen_<rel>/figures/volcano_all12_figure_screen.R
效应热图results/screen_<rel>/figures/effect_heatmap12_figure_screen.R
UpSet 重叠图results/screen_<rel>/figures/upset02_visualize.R
共享蛋白森林图results/screen_<rel>/figures/forest_shared02_visualize.R
蛋白-疾病网络results/screen_<rel>/figures/network02_visualize.R
整合靶点网络results/network_<rel>/network_integrated06_network.R
区域共定位图results/hyprcoloc_<rel>/figures/locus_<蛋白>13_figure_locus.R
PheWAS 多效性负担results/phewas/figures/phewas_burden_<rel>14_figure_downstream.R
PheWAS top 性状results/phewas/figures/phewas_top_traits_<rel>14_figure_downstream.R
LDSC 遗传相关热图 / 遗传力results/ldsc<_rel>/figures/ldsc_rg_heatmapldsc_h214_figure_downstream.R
单细胞 UMAP / feature / 富集热图results/singlecell/figures/15_figure_singlecell.py

为什么每张图都出两份

PNG 600 dpi 给你看和投稿初审;PDF 是矢量的,投稿要改字号、改配色、拼版,用 Illustrator 打开 PDF 改,不会糊。 统一由 analysis/_viz_common.Rsave_both() 落盘,所有图配色一致(风险红 #d73027 / 保护蓝 #4575b4)。

作图规范(字体与语言统一口径)

全课题所有结果图(MR 流水线 / 多组学 / 单细胞 / 靶点验证 / deCODE 平台复制)统一以下口径,保证跨模块一致、达发表标准:

维度现行统一口径说明
语言全英文(标题、坐标轴、图例、注释)符合高分 SCI;中文只出现在 docs/PPT 的正文与图注文字框,不进图片内部
字体DejaVu SansR 的 theme_minimal 默认 sans = DejaVu Sans;matplotlib 默认字体也是它 → R 图与 Python 图像素级统一
主题theme_minimal(base_size=12)(R)/ 等价的白底浅网格(matplotlib)中心助手 analysis/_viz_common.R::save_both()
输出PNG(≥200 dpi 看/初审)+ 矢量 PDF(投稿改版)双份PDF 文字可编辑,Illustrator 拼版不糊
配色风险红 #d73027 / 保护蓝 #4575b4(语义色)全套一致

关于 Arial(当前不改,留待终稿)

顶刊作图规范多点名 Arial / Helvetica,但 DejaVu Sans 是完全合规的开源无衬线体、现阶段(汇报/预印本)足够。决定:现在统一保持 DejaVu Sans,不换。 若终稿投稿阶段需要,再做一次专门的字体统一 pass——届时优先用 Arimo(开源、与 Arial 度量/外观几乎一致、嵌 PDF 无授权顾虑),从中心助手 _viz_common.R + matplotlib rcParams 两处统一切换,并只重绘、不重算


二、火山图:一眼看完整个筛查

volcano_all 按结局分面,横轴 log₂(OR),纵轴 −log₁₀(P)。

怎么读

  • 越靠上 = 越显著;越靠两边 = 效应越强
  • 红点=风险(OR>1),蓝点=保护(OR<1),灰点=没过 FDR<0.05
  • 只有过了 FDR 的点才标基因名(每个结局最多标 8 个)

能下什么结论

  • 某个结局整体"有没有信号":AMD 那几个面板点云高高拱起,NeovascGlaucoma 一片扁平(病例才 1576,功效不足)——扁平不等于没关系,可能只是样本不够
  • 复合体基因扎堆(CFH/CFB/CFHR2/CFHR5/CFI)同时冒头 = AMD 补体通路的经典信号,说明流水线没跑歪

三、效应热图:跨结局比较同一个蛋白

effect_heatmap,行=蛋白,列=结局,填充 log₂(OR),* 标 FDR<0.05。

怎么读

  • 整行都红/都蓝 = 这个蛋白在多个病里方向一致(如 AGER 一片蓝=跨并发症保护)
  • 一行里红蓝都有 = 方向不一致,要警惕是不是多效性位点

色标是截断的

色标按 |log₂(OR)| 的 98 分位截断(图注里写了具体数值)。不截断的话少数极端值会把其余格子全压成白色。看颜色深浅只能比大小顺序,不能当精确读数——精确值查 table_S_significant_associations.csv


四、区域共定位图(最该看的一张)

locus_<蛋白>上面一条轨道是血浆 pQTL,下面每条是一个疾病,横轴是同一个 ±500 kb 窗口。

怎么读

  • 各条轨道的峰对齐在同一个位置 → 支持"同一个因果变异同时影响蛋白和疾病"
  • 红色虚线 + 红菱形 = HyPrColoc 判定的候选因果变异,它在每条轨道上的实际取值都被高亮出来
  • 峰错开 → 很可能只是 LD 巧合,MR 的关联不可信

实例

  • locus_APOE(R13):pQTL、AMD、DryAMD、Retinopathy、WetAMD 五条轨道齐刷刷在同一点冒峰,PP=0.956——这是最漂亮的一张
  • locus_TGFB1:pQTL 峰很高(−log₁₀P 到 180),三个 AMD 亚型的峰都在同一位置但矮得多(4–6),PP=0.760——信号真,但疾病侧证据弱

点的颜色 = LD r²(2026-07-20 起)

按 LocusZoom 惯例,点按与候选因果变异的 分档着色(1000 Genomes EUR 面板):

颜色
🔴 红0.8–1.0
🟠 橙0.6–0.8
🟢 绿0.4–0.6
🔵 浅蓝0.2–0.4
🔷 深蓝<0.2
⬜ 灰unknown(面板里没有这个变异)

红点扎堆在主导变异附近 = 这一簇强连锁的变异共同支撑信号,正常且是好现象。

灰点是"不知道",不是"r² 低"

每张图的 r² 覆盖率不同(本课题实测 34%–46%,个别位点 0%),图注里逐张写明了具体百分比。 覆盖不满的原因:r² 只能对有 rsID 且在 1000G 面板里的变异算,而 UKB-PPP 的区域文件 ID 列是 7:27916:T:C:imp:v1 这种格式、不带 rsID,只能靠 FinnGen 的 rsids 列建 位置→rsID 映射反查;两边都覆盖到的才有颜色。 深蓝(<0.2) 和 灰(unknown) 是完全不同的含义,别混。

面板是 hg19,数据是 hg38 —— 必须按 rsID 匹配

1000G 面板实测是 GRCh37rs429358EUR.bim 里是 19:45411941,GRCh38 应为 19:44908684)。 本课题的 pQTL/GWAS 数据都是 GRCh38。所以代码里按 rsID 匹配、绝不按位置匹配——r² 是单倍型属性,与坐标系无关,这样做是对的;一旦改成按位置匹配,结果会全错。图注也写明了这一点。 个别位点(如 R9 的 CLPS,主导变异 rs71540127)在面板里不存在,整张图全灰,脚本会自动改写图注说明。


五、PheWAS 两张图:查多效性(安全性)

  • phewas_burden_<rel>:每个候选蛋白的工具变量,关联了多少个不相干性状(Bonferroni 显著)。数字越大,这个位点越"到处都显著",MR 结论越可能被多效性污染。APOE 是典型反面教材(几百个性状)。
  • phewas_top_traits_<rel>:每个蛋白最强的 8 个关联性状,红=正向、蓝=负向。用来判断"它到底在管什么"。

两个必须注意的点

  1. P 值有下溢:源数据里很多 P 直接是 0(双精度下溢),图里统一按 1e-320 画,图注写明了。别去解读那一档的高低
  2. PheWAS 查的是 OpenGWAS 实时库,不可逐位复现。2026-07-16 查得 224 SNP/20688 关联,07-18 再查得 243/22473。投稿必须注明查询日期

六、LDSC 两张图:疾病之间什么关系

  • ldsc_rg_heatmap:下三角遗传相关矩阵,格子里直接标数值。糖网↔肾病 rg 极高(同一套糖尿病微血管病变),AMD 与糖尿病并发症基本独立。
  • ldsc_h2:各结局的 SNP 遗传力。

2026-07-20 起改用有效样本量,h² 已可跨结局比较

此前 munge 传的是总人数 ncase+ncontrol,而 LDSC 的 h² 随 N 缩放——本课题各结局 病例占比从 2.2% 到 19.7%,Neff/N 在 0.086–0.633 之间(相差 7.4 倍),等于每个性状 被一个各不相同的因子压低,跨结局比大小是没有意义的

现已改为二分类的有效样本量 Neff = 4/(1/ncase + 1/ncontrol)。改后数值不再被病例占比牵着走: 病例占比最高的 Retinopathy(19.7%)h² 反而最低(0.051),T1D_wide(2.7%)最高(0.238) ——说明差异开始由性状本身而非抽样结构决定。新图可以跨结局比较(仍是观察尺度; 要与文献中的 liability-scale 数值对比,仍需按人群患病率转换)。

⚠️ 两条使用提醒:① 病例数少的结局(如 Maculopathy 4,766 例)LDSC 估计噪声大, 报告时必须带标准误,不能只给点估计;② rg 只是近似不受该改动影响——实测 AMD–WetAMD 0.925→0.931、WetAMD–DryAMD 0.851→0.865(最大变动 0.014,因为 LDSC 的 回归权重本身依赖 N),定性结论不变,但论文里的 rg 数值要用新表。详见 方法学审计

rg 高不等于生物学同源——共享对照会把它抬上去

已用官方 FinnGen R13 manifest 核对:Retinopathy 与 Nephropathy 的对照数完全相同(62,519), 它们共用同一批对照;AMD / WetAMD / DryAMD 之间同样大量共用样本。所以这些高 rg 不能当作"疾病之间生物学同源"的独立证据,写作时须限定为"同一生物库、共享对照下的遗传相关"。

这两张图的真正用途:解释"为什么一个蛋白会在好几个病里同时显著"——遗传相关高的病本来就会一起显著。所以跨结局一致性只是支持性观察,不是验证支柱


七、单细胞三张图:靶点在视网膜哪类细胞里

  • umap_celltypes:26.6 万细胞 / 18 个细胞类型的全景,图例带各类细胞数
  • umap_features:每个候选基因的表达叠在 UMAP 上,标题给"表达细胞占比"
  • enrichment_heatmap:基因 × 细胞类型,填充 AUC(该类 vs 其余,0.5=无差异),* = Mann-Whitney BH-FDR<0.05 且 AUC>0.5

读出来的结论

基因定位
SPRY2Müller 细胞 / astrocyte / RPE — 最像"在视网膜内起作用"
WARS1、NUDT5神经节细胞(60–76% 细胞表达)
TGFB1、PILRA小胶质细胞 → 神经炎症线索
CSF2、TNFRSF10A、IL7R、CLPS视网膜里几乎不表达 → 机制大概率是系统性/循环免疫,不在视网膜内

三条硬限制,写作时必须交代

  1. HRCA 全是正常视网膜disease 列只有 normal 一个水平)→ 做不了 AMD vs 正常的差异表达,只能做表达定位。
  2. RPE 只有 1619 个细胞(0.6%)。低表达基因在这么少的细胞里本就测不到。"0% 表达"不等于"不表达"——富集热图把细胞数标在横轴上、图注写了"无星≠无表达",就是为了防止误读。
  3. 26 万细胞下 P 值必然极小,真正有解释力的是 AUC。别去比谁的 P 更小。

和 eQTL 的互相印证

CSF2 / TNFRSF10A 在单细胞里几乎不表达,同时在 Strunz 视网膜 eQTL 里不是 eGene、在公开 eQTL 里连区域数据都没有——两条独立证据指向同一结论。这也正是申请 METR-GT 的 RPE eQTL 的理由:正常视网膜单细胞 + RPE 采样稀少,看不清的部分需要 RPE 分离的深度 bulk eQTL 来补。


八、有意不做的图

为什么不做
漏斗图 / leave-one-out / MR-Egger每个蛋白只有 1 个 cis 工具变量,用的是 Wald ratio。异质性和多效性检验在数学上无从谈起,硬画出来是假图。验证靠共定位 + PheWAS/Steiger 两根支柱
研究设计流程图那是排版图不是数据图,用代码画又难看又难改。建议在 Illustrator / PPT 里画。

九、重跑与缓存

bash
cd ~/mr-pipeline
./run_pipeline.sh 12     # 只重出筛查层的图
./run_pipeline.sh 13     # 只重出区域共定位图

LD 面板/mnt/d/mrdata/ldpanel/EUR.{bed,bim,fam}(1000G EUR,GRCh37,1.1 G),plink1.9 由 apt 安装。面板缺失时脚本不会崩,会自动退回全灰并在图注说明。

区域共定位图第一次跑很慢(要解 tar + 扫 FinnGen 大文件,约 20 s/基因),之后区域数据缓存在 results/_region_cache/*.rds再跑是秒出。想强制重新提取就删这个目录。

缓存文件名带版本号,改了读取逻辑必须升版

疾病区域缓存是 dis_<结局>_chr<N>_<pos>_500kb_v3.rds,末尾 _v3读取逻辑的版本。 一旦改动 _region_io.R 的提取方式(比如新增一列),必须把版本号 +1,否则会读到按旧格式存的缓存,静默给出错误结果。 旧版本缓存键名对不上、不会被读,确认无误后可以删:

bash
# 只删旧版疾病缓存;prot_*.rds 没有版本后缀、当前仍在用,不要动
find results/_region_cache -name 'dis_*kb.rds' ! -name '*_v3.rds' -delete

别在脚本跑着的时候改它

Rscript 是边读边解析的。运行中覆盖 .R 文件会让它中途报 unexpected symbol 崩掉(本项目实际踩过:热替换 13_figure_locus.R 把正在跑的 R9 任务弄崩,只出了 3/5 张图)。改代码前先 pgrep -af Rscript 确认没在跑。

个人科研与运维文档 · 内容持续修订