Snakefile 基础:规则、输入输出与通配符
做自动化工作流,说白了就是让计算机按你定好的顺序干活。Snakemake 里的核心单元,就是 规则(Rule)。我刚开始接触的时候,总觉得它跟 Makefile 里的 target 差不多,但用多了才发现,Snakemake 的规则更像是一张「工序卡片」——你告诉它要产出什么,它自己想办法找原料、调步骤。
规则的定义与骨架
每条规则都有一个名字,比如 rule preprocessing。名字你自己起,但最好能一眼看出这步在干嘛。我曾经在团队里见过有人用 rule a、rule b,结果两周后自己都忘了是干什么的……从那以后,我强制要求命名必须带具体动作。
规则的骨架长这样:
rule 规则名:
input:
"原始数据/{sample}.csv"
output:
"清洗后/{sample}.csv"
shell:
"python clean.py {input} {output}"
嗯,注意缩进。Snakemake 用 YAML 风格的缩进,四个空格是标准。我见过有人用 Tab,然后报错找半天——相信我,别用 Tab。
输入与输出的定义方式
input 和 output 可以是单个文件路径,也可以是多个文件列表。比如特征工程里,你可能需要同时读入数值列和类别列:
rule feature_union:
input:
num = "features/num.csv",
cat = "features/cat.csv"
output:
"features/merged.csv"
shell:
"paste -d',' {input.num} {input.cat} > {output}"
我个人习惯给输入文件起个名字(比如 num 和 cat),这样在 shell 里引用时更清晰,尤其是文件多的时候。
通配符:自动化最灵活的组件
通配符是 Snakemake 的杀手锏。什么意思呢?你想想看,如果你有 100 个样本要处理,难道要写 100 条规则吗?当然不用——用 {wildcard} 占位符,Snakemake 会自动从输出文件名里提取出通配符的值,然后用它去匹配输入。
举个特征工程里的例子:假设你有按日期分区的原始日志,想对每一天做特征聚合:
rule daily_agg:
output:
"agg/{date}_features.csv"
input:
"raw/{date}/log.csv"
shell:
"python aggregate.py --input {input} --output {output}"
当你执行 snakemake agg/2024-01-15_features.csv 时,Snakemake 会自动把 {date} 替换成 2024-01-15,然后去找 raw/2024-01-15/log.csv。就这样,一条规则管一百天。
{sample}_clean.csv,而 shell 里引用的是 {id}——Snakemake 会直接报错,说未定义通配符。这个错误很常见,注意检查拼写。
通配符的约束与多通配符
一个规则里可以有多个通配符,比如同时区分样本名和日期:
rule multi_wild:
output:
"results/{patient}/{date}/report.csv"
input:
"data/{patient}/{date}/raw.csv"
shell:
"python report.py {input} {output}"
但要注意:通配符不能出现在 input 里而不出现在 output 里。为什么?因为 Snakemake 只能通过 output 文件名倒推通配符值,如果 input 里多了一个 output 里没有的占位符,它就不知道这个值该从哪来。换句话说,output 定义了通配符的“来源”。
另外,有些时候你希望对通配符加一些约束,比如只匹配特定的字符范围。可以在规则前面加一个 wildcard_constraints 块:
wildcard_constraints:
date = "\d{4}-\d{2}-\d{2}"
rule daily_agg:
output:
"agg/{date}_features.csv"
...
这样就可以防止 date 匹配到像 abc 这样的非法字符串。我在处理用户上传的混乱数据时,这个约束救过我好几次。
规则之间的依赖是如何自动构建的
Snakemake 的一大特点是不需要你显式写依赖顺序。它通过文件名匹配来推断上游规则。比如你想生成 final_model.pkl,而这个文件依赖于 features/merged.csv,那么只要另外有一条规则的 output 正好是 features/merged.csv,Snakemake 就会自动把它作为前置步骤。
这就是为何我强调——文件命名要规范化。你想想看,如果一条规则输出 output/abc.csv,另一条规则输入 output/abc.csv,它们就自动串起来了,代码里一句依赖声明都不需要写。
SVG:规则核心结构图
下面这张图展示了规则、输入输出、通配符之间的关系。对照着看,应该更容易理解。
实战小贴士
- 规则名不要重名:Snakemake 会覆盖,且不报错。后果就是你的工作流可能偷摸少跑一个步骤。
- output 最好只定义一个文件:虽然支持多个,但为了保持依赖清晰,我通常一个规则只产出一个主要文件,其他副产品用
touch()标记。 - 善用
log和params:log 可以记录日志文件,params 用来传参数。虽然本章没细讲,但你在写规则时尽早加上 log,调试会舒服很多。
WildcardError。例如:output: "result.csv" 而 input: "data/{sample}.csv",这是不合法的,因为 {sample} 无法从 output 中推断。
表格:常见通配符匹配场景
| 模式 | 示例 output | 匹配 input | 说明 |
|---|---|---|---|
| 单通配符 | clean/{sample}.csv | raw/{sample}.csv | 最常见,一一对应 |
| 多通配符 | {ds}/{run}/model.pkl | feats/{ds}/{run}.csv | 提取两个维度 |
| 带约束通配符 | out/{date:\d{8}}.csv | in/{date}.csv | 约束date格式为8位数字 |
| 正则通配符 | result/{id:[a-z]+}.txt | input/{id}.txt | 只允许小写字母 |
写到这里,你可能会问:“那我是不是每条规则都必须用通配符?” 不一定。对于只有一个输入文件的固定任务,直接写全路径就行。但一旦涉及到批量处理,通配符就是你的最佳拍档。我在做一个基因特征工程项目时,用一条带 {sample} 的通配规则替代了原来 3000 多行重复脚本——那种感觉,真的很爽。
总结
这章我们聊了规则的骨架、输入输出写法、以及通配符的核心用法。记住三个关键点:
- 规则的名字要见名知意,缩进用空格别用 Tab。
- 输入输出用字典形式命名文件更清晰。
- 通配符必须出现在 output 中,Snakemake 才能反向推导。
下一站,你会看到这些规则如何组合成一个完整的工作流——但那是后话了。先把基础打牢,回头写 Snakefile 的时候,顺手得像写列表一样自然。