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。就这样,一条规则管一百天。

避坑提醒:通配符的名字必须和 output 里出现的一致。我曾经手滑把 output 写成 {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,它们就自动串起来了,代码里一句依赖声明都不需要写。

核心思想:Snakemake 的工作流是由输出文件驱动的。你指定最终目标文件,它就会反向查找所有需要的输入,直到构建出完整的执行图。

SVG:规则核心结构图

下面这张图展示了规则、输入输出、通配符之间的关系。对照着看,应该更容易理解。

Snakefile 规则核心结构 规则 (Rule) 输入文件 输出文件 通配符 {sample} shell / run 动作 Snakemake 自动匹配 Snakemake 根据输出文件名中的通配符 去匹配输入文件名,然后执行动作

实战小贴士

  • 规则名不要重名:Snakemake 会覆盖,且不报错。后果就是你的工作流可能偷摸少跑一个步骤。
  • output 最好只定义一个文件:虽然支持多个,但为了保持依赖清晰,我通常一个规则只产出一个主要文件,其他副产品用 touch() 标记。
  • 善用 log 和 params:log 可以记录日志文件,params 用来传参数。虽然本章没细讲,但你在写规则时尽早加上 log,调试会舒服很多。
注意:如果你在 input 里写了通配符,但 output 里却没有对应的通配符同名——Snakemake 会报 WildcardError。例如:output: "result.csv" 而 input: "data/{sample}.csv",这是不合法的,因为 {sample} 无法从 output 中推断。

表格:常见通配符匹配场景

模式示例 output匹配 input说明
单通配符clean/{sample}.csvraw/{sample}.csv最常见,一一对应
多通配符{ds}/{run}/model.pklfeats/{ds}/{run}.csv提取两个维度
带约束通配符out/{date:\d{8}}.csvin/{date}.csv约束date格式为8位数字
正则通配符result/{id:[a-z]+}.txtinput/{id}.txt只允许小写字母

写到这里,你可能会问:“那我是不是每条规则都必须用通配符?” 不一定。对于只有一个输入文件的固定任务,直接写全路径就行。但一旦涉及到批量处理,通配符就是你的最佳拍档。我在做一个基因特征工程项目时,用一条带 {sample} 的通配规则替代了原来 3000 多行重复脚本——那种感觉,真的很爽。

总结

这章我们聊了规则的骨架、输入输出写法、以及通配符的核心用法。记住三个关键点:

  1. 规则的名字要见名知意,缩进用空格别用 Tab。
  2. 输入输出用字典形式命名文件更清晰。
  3. 通配符必须出现在 output 中,Snakemake 才能反向推导。

下一站,你会看到这些规则如何组合成一个完整的工作流——但那是后话了。先把基础打牢,回头写 Snakefile 的时候,顺手得像写列表一样自然。