主题
01 · 新建目录与代码改造(检查点 B)
执行日期:2026-08-07 状态:⚠️ 主体完成,两处待决(见 §7)
这一步不产生科学结果,但它决定后面所有结果是不是在正确的代码上算出来的。 本步查出一个足以让整个"干净重跑"作废的问题(§3.1)。
★ 本页是「当天在流程 v3 下做了什么」的历史记录,不整页改写
本页所有 "v3" 字样保持原样——它们记录的是改代码那一刻依据的是哪版流程, 改成 "v4" 等于篡改记录。但其中一处的语义已被 v4 作废,必须点名:
| 本页写的 | v4 之后 | 影响 |
|---|---|---|
§3.4 的 step4_survivor 列 | ❌ 语义作废 —— v4 第 4 步不判存活、只分流,否决移到 4X | ⬜ 需改名(如 repl_flag_to_4X),32 / 29 的引用跟着改。尚未做 |
§3.4 的三分法本身(repl_class / repl_strength) | ✅ v4 沿用 | 无 |
| §3.5 富集用「第 4 步存活集」 | ⚠️ 改为用 4X 之后的集合 | 输入集要跟着换,尚未做 |
| §3.6 共定位=措辞升级关卡 | ✅ v4 沿用 | 无 |
§7 tier 的三个选项 | ✅ 用户已拍板 ①彻底删 | 十文件 47 处,尚未开始 |
现行流程一律以 分析流程定稿 v4 为准。
一、新目录
| 项 | 值 |
|---|---|
| 运行目录(WSL) | /home/research/mr-pipeline-r9v3,属主 research,1.5 M |
| 编辑目录(F 盘) | F:\project\mr-pipeline-r9v3,独立 git 仓(不 fork 旧仓历史) |
| 来源 | 从 mr-pipeline-r9_full_20260807.tar.zst 解出,不 cp 现场 |
results/ | 空(解包时 --exclude='*/results/*') |
data/ | 两个软链原样保留,指向 /mnt/d/mrdata/,与旧目录共用只读原始数据 |
| 校验 | analysis/ 脚本数 61 = 旧目录;F↔WSL 逐字节 137 相同 / 0 不同 |
1.1 ★ 编辑与运行分离(本轮新立的规矩)
WSL 默认用户是 lovejj,而流水线在 research 名下。由此定死单向规则:
只在 F 盘编辑,只在 WSL 运行。反向拷贝一律视为事故。
固化成 _sync_to_wsl.sh:rsync --delete 后自动逐字节 cmp 校验,不通过则非零退出。 理由是 2026-07-31 的教训:F 盘副本比 WSL 落后,回灌前跑出的结果是旧代码算的, 而这种错误在结果上看不出来。
1.2 行尾:查出 4 个混合行尾文件并统一
git init 时 Git 警告要把 LF 转成 CRLF。查证后发现两件事:
- 立即关闭转换:
core.autocrlf=false+.gitattributes写* -text(字节原样存取)。 不关的话,将来任何一次git checkout都会把 R 脚本变成 CRLF,同步回 WSL 就出事 (历史教训:CRLF 致锚点 0 命中)。 - 实测原仓 87 个脚本里 4 个本来就是 CRLF:
analysis/01_screen.R、analysis/17_replicate_external.R、tools/verify_input_sanity.R、config.yaml—— 恰好是 2026-08-05 T1D EAF 修复时在 Windows 侧编辑过的那批。 因为本步要编辑其中两个,LF 的新内容插进 CRLF 文件会造成文件内混合行尾, 所以先统一为 LF(仅删 CR,其余字节不动),并以research身份验证 R/YAML 解析通过。
二、instrument_definition 按原文重写
config.yaml 原文案自注「未直接读到原文全文」,并把 Sun 2023 的 "excluding the HLA region" 转述成「排除 HLA」——容易被读成 HLA 蛋白被删掉了。
已按 00 §6 核实到的原文重写,新增: 原文原句、cis 判定依据、以及本数据 14,287 / 1,955 / 1,954 三处与原文逐位吻合的校验。
三、代码改造
3.1 ★★ 最重要的一处:setwd 硬编码(计划外,但不修则整轮作废)
12 个脚本硬编码 setwd("/home/research/mr-pipeline-r9"), tools/stamp_cache_fingerprints.R 更指向更早的 /home/research/mr-pipeline。
后果:在 v3 新目录里跑,这些脚本会切回旧目录、读旧结果、把新结果写进旧目录。 所谓"干净重跑"会变成一半新一半旧,而且日志里看不出来。
改法:统一为三级解析,全 16 处文本完全相同(便于一次 grep 核验):
r
# 优先级:环境变量 MRPIPE_ROOT > 本脚本所在目录的父目录 > 当前工作目录
setwd(local({
r <- Sys.getenv("MRPIPE_ROOT", ""); if (nzchar(r)) return(r)
a <- commandArgs(trailingOnly = FALSE); f <- grep("^--file=", a, value = TRUE)
if (!length(f)) return(getwd())
d <- dirname(normalizePath(sub("^--file=", "", f[1])))
if (basename(d) %in% c("analysis", "tools")) dirname(d) else d
}))实测验证:从 /tmp 调用 analysis/ 下的脚本,解析到 /home/research/mr-pipeline-r9v3。
未改动的:36_control_definition_sensitivity.R(R13 线,按约定不碰)、 tools/ 下若干针对 R13/AMD 的一次性核验脚本(不在 R9 主链上)。
3.2 in_MHC:四处口径统一为单一真值源
新增 analysis/_mhc.R,取 UKB-PPP 工具表自带的 MHC 列 (= Sun 2023 原文 25.5–34.0 Mb,实测双向差集为空)。不自己按坐标划线—— 那是数据提供方按其实际施加的规则打的标记,是事实。
替换掉的四处:
| 位置 | 原来 | 问题 |
|---|---|---|
29_enrichment.R | 手写 20 个蛋白名 | 是死代码,分组其实用的是 32 产出的 in_MHC 列 |
32_targets_table.R | 同一份手写列表 | 含 5 个不在 cis 池里的(C4A/C4B/NOTCH4/SKIV2L/VARS2),漏 12 个在池里的 |
35_reverse_mr.R | 坐标 25–34 Mb | 判定与"剔长程 LD 块"混用同一组常量 |
37_drug_target_phewas.R | 坐标 25–34 Mb | 只覆盖有 PheWAS 的蛋白 |
★ 手写列表对旧的 31 蛋白显著集恰好无影响(命中的 11 个全在列表内)—— 旧结果在这一点上没错,但机制脆弱,换数据即错。
★ 35_reverse_mr.R 里刻意保留两个不同的区间并写明理由: 判定用 25.5–34.0(定义),剔长程 LD 块仍用 25–34(更宽更保守,目的不同)。
3.3 写死断言:assert_mhc_consistency()
接入 32 / 35 / 37,启动即校验,不符立刻失败:
cis 1955 行 / 1954 蛋白(= Sun 2023 正文)
作者 MHC 列 ≡ 25.5–34.0 Mb 坐标(双向差集为空)→ 27 个
Zheng 口径 26–34 Mb → 26 个;两者差集恰为 SCGN写成断言而不是注释,因为注释不会在数据换掉时报警,而本项目已有两次 "改了上游、下游静默沿用旧口径"的教训。
3.4 ★ 第 4 步:外部复制三分法
step4_survivor 这个列名在 v4 下已经不成立
当时按 v3 写的,v3 说第 4 步有否决权,所以「存活」这个词是对的。 v4 把否决权收回到 4X,第 4 步只分流不判真伪——一个叫 "survivor" 的列 会让下一个读代码的人以为「不在这个集合里 = 已被淘汰」,而 v4 下它们只是被送去 4X。
★ 这正是本项目反复踩的那类坑:列名承载了一个已经改掉的判定语义,而代码照跑不报错。 待改名为 repl_flag_to_4X 或类似,尚未做。
17_replicate_external.R 新增 repl_class / repl_strength / step4_survivor。
旧的二值 replicated 把两件不同的事混在一起:
b_repl缺失(NA)在下游常被当成"没复制上"= 阴性。 但没测过不等于测了没有——tools/decode_platform_check_r9dm.R:71的注释 早就写着「跨平台复制属未做,不是阴性」,只是从没落到判定逻辑里。- 方向一致但 p ≥ 0.05 被判 FALSE。v3 规定方向一致即通过,显著与否只作强度标注—— 复制队列样本量通常远小于发现队列,拿 p < 0.05 当门槛等于用功效不足去否定一个方向正确的结果。
3.5 富集改用第 4 步存活集
29_enrichment.R 输入集由 MR 显著集改为第 4 步存活集(依据 C1R 预印本的 "the validated proteins")。缺 step4_survivor 列时直接 stop(), 而不是静默退回旧口径。保留 MR 显著集作对照组; 含 MHC vs 不含 MHC 的对比必须保留——那是本课题"MHC 需要处理"的核心证据。
3.6 共定位=措辞升级关卡(新增列)
32 新增 coloc_upgrade / mhc_verification_done / coloc_upgrade_note。 升级条件 = PP.H4 > 0.8 且 跨病符号一致 且(非 MHC 或已补 MHC 专用核验)。
mhc_verification_done 写成显式列而非隐含 FALSE:将来补做时改一处即可, 且报告里能看出是「未做」而不是「未通过」。
3.7 缓存与断点
01_screen.R缓存指纹v2→v3config.yamlresume: true→false(从零重算,供可复现性证明; 该文件原注释里就写着"要从零验证有效性时改回 false")
3.8 跨病符号一致性:已存在,无需新增(仅验证)
计划里列为"新增",实测已经实现且正确(32_targets_table.R): 按蛋白在 5 个并发症上 n_pos == 0 | n_neg == 0 判定, 且 n_disease < 2 标「单病(不适用)」而非「未通过」——与 v3 要求一致。
★ 计划里的担心「名字像但定义不同」完全成立,两者确是两回事:
| 位置 | 算什么 | 是什么 |
|---|---|---|
27_diabetes_contrast.R:61 | sign(bc) != sign(bd) | 蛋白对并发症 vs 对糖尿病 → 方向背离 |
32_targets_table.R | n_pos==0 | n_neg==0 | 同一蛋白跨多个并发症的符号 → 跨病符号一致性 |
四、用旧结果做的预演(只读,不写)
改完的判定逻辑套到旧结果上,看会得出什么。这是第 4 步的跑前预期。
4.1 三分法(并发症 57 对)
| 类别 | 对数 | 蛋白数 |
|---|---|---|
concordant(方向一致) | 32 | 24 |
not_assessable(不可评估) | 19 | 19 |
opposite(方向相反) | 6 | 6 |
旧二值口径 replicated == TRUE 只有 12 对。
★ 19 个 not_assessable 恰好等于 Maculopathy 的全部 19 对—— 该结局的 repl_dataset 是「无外部队列(FinnGen 单源)」,压根没有外部队列可判, 旧口径却把它们全算作"没复制上"。
4.2 蛋白层存活
| 数 | |
|---|---|
| MR 显著蛋白 | 31 |
| 第 4 步存活蛋白 | 31 |
| 被第 4 步整体否决 | 0 |
6 个有 opposite 记录的蛋白(AGER · HCG22 · LTB · TRIM40 · APOE · SIGLEC5) 各只有 1 对,在其他并发症上仍有存活对,故蛋白层全部存活。
推论(写进跑前预期):富集的输入集在本数据上与旧口径相同(31 个蛋白), 所以富集结果预期不变;变的是对数记账(57 → 51)与措辞。
五、验证记录
| 检查 | 结果 |
|---|---|
| 改过的 R 脚本语法解析 | 17 / 17 通过(以 research 身份) |
config.yaml YAML 解析 | 通过;resume = FALSE |
根目录解析(从 /tmp 调用) | 解析到 /home/research/mr-pipeline-r9v3 ✅ |
assert_mhc_consistency() | 通过:cis 1955/1954;MHC 27;Zheng 26;差集 = SCGN |
| F ↔ WSL 逐字节 | 137 相同 / 0 不同 |
| 4 个文件 CR 计数 | 全部 0 |
六、怎么解读 / 去向
| 问题 | 回答 |
|---|---|
| 这一步能下什么结论 | 工程性结论。唯一影响科学结果的是 §3.4 三分法与 §3.5 输入集,两者都会在第 4 步产生新的漏斗数字 |
| 进正文还是补充 | 代码改造本身都不进 |
| 进补充方法的 | §3.4 的三分法定义(尤其"不可评估 ≠ 阴性"这条判定规则必须写清楚) |
| 进Limitations 的 | Maculopathy 无任何外部复制队列(19 对全部不可评估) |
| 后续依赖 | §4 的预演数字就是第 4 步的跑前预期,跑完要逐项核对 |
七、⚠️ 两处待决(需拍板后才能继续)
7.1 tier(A/B/C/D)的去留
v3 的报告方式是漏斗,计划写「取消 A/B/C/D 标签」。但实测 tier 被 10 个文件、47 处引用(06 / 07 / 08 / 10 / 21 / 32 / 33 / 37 / 41 / _singlecell_io), 且下游按字面字符串匹配(如 "C-MHC区(LD混杂风险)")。
「取消」在计划里是一行字,落到代码是一次十文件重构。三个选项:
| 选项 | 代价 |
|---|---|
| ① 彻底删除,改为漏斗阶段列 | 10 文件重构,风险最高,但口径最干净 |
② 保留 tier 作程序内部分组(决定谁进主图),论文只用漏斗 | 改动最小;但 tier 的定义仍隐含"共定位是否决、MHC 要降级",与 v3 相悖 |
| ③ 保留列名、按 v3 重新定义取值 | 下游字面匹配会全部失效,等同于①但更隐蔽 |
本轮已按最小侵入处理:tier 原样保留并标注「待重构」,v3 的新列全部增量添加, 不破坏任何下游。等拍板。
7.2 第 4 步「外部复制」到底指哪条轴
我们其实有两条互相独立的外部复制线:
| 轴 | 脚本 | 复制的是什么 | 现状 |
|---|---|---|---|
| 结局侧 | 17_replicate_external.R | 换独立结局队列(MVP 糖网/神经、Salem 糖肾) | ✅ 已改三分法 |
| 暴露侧·跨平台 | 28_decode_reselect.R + tools/decode_platform_check_r9dm.R | 换蛋白测定平台(deCODE / SomaScan, N=35,559) | ⚠️ 无任何代码层判定规则 |
★ 关键:C1R 预印本对应的是暴露侧跨平台那条 ("evaluated in an independent Icelandic cohort (N=35,559, SomaLogic platform)"), 而 v3 流程图第 4 步的措辞正是照 C1R 定的。
★ 且实测:decode_reselect_mr.csv 没有任何脚本消费—— "三平台同向"那些结论是手工比对得出的,从未固化成规则。
需要决定:第 4 步取结局侧、暴露侧、还是两条都要(若都要,如何组合成一个存活判定)。 这决定漏斗长什么样,不宜由我代拍。
八、本步的 git 记录
新仓 F:\project\mr-pipeline-r9v3,4 个提交:
9a0edc3 feat: v3 第4步外部复制三分法 + 富集改用第4步存活集
6228626 refactor: MHC 判定单一真值源 + 根目录不再硬编码 + 缓存指纹 v3 + resume=false
9bf8e42 fix: 按 Sun 2023 原文重写 instrument_definition;统一 4 个文件行尾为 LF
c00451a chore: 关闭行尾转换(core.autocrlf=false + .gitattributes '* -text')
aeab838 chore: 从 mr-pipeline-r9 备份解出的 v3 重跑基线