理解 Snakefile 结构——流水线的“骨架”

Snakefile 是整个流水线的核心。说白了,它就像一张施工蓝图,告诉 Snakemake 每一步要做什么、需要什么、产出什么。我刚开始接触 Snakemake 时,总觉得它跟 Makefile 差不多,后来才体会到它的设计要精巧得多。

Snakefile 的基本组成

一个典型的 Snakefile 由若干条 规则(rule) 组成。每条规则定义一个任务,包括输入、输出、参数和要执行的命令。我习惯把 Snakefile 想象成一张有向无环图(DAG),每个节点是一个规则,边就是文件依赖。

关键点:Snakefile 不是脚本,而是声明式配置。你只需告诉它“我要什么”,它自己推导出执行顺序。

下面是最简单的规则模板:

rule predict_admet:
    input:
        "data/compound.smi"
    output:
        "results/admet.csv"
    shell:
        "admet_predictor --input {input} --output {output}"

这条规则做了三件事:声明输入文件、声明输出文件、用 shell 命令执行预测。注意 {input} 和 {output} 这两个占位符——它们会在运行时被实际文件路径替换。

输入与输出:依赖关系的“桥梁”

输入输出不仅仅是路径字符串,更是 Snakemake 建立 DAG 的依据。如果规则 B 的输入正好是规则 A 的输出,Snakemake 就会自动先执行 A 再执行 B。我曾经在一个项目里因为输出路径写错了一个斜杠,导致任务永远无法触发,排查了半天……嗯,路径匹配必须精确。

常见输入输出写法:

  • 单文件:"results/{sample}.csv"
  • 多文件:列表 ["file1.txt", "file2.txt"]
  • 命名输入:input: smiles="data/{sample}.smi",调用时用 {input.smiles}

个人经验:我建议把输入按逻辑分组命名,比如 input: model="models/rf.pkl", compound="data/{sample}.smi",这样在 shell 中引用时一目了然,不易出错。

通配符:批量处理的“魔法”

通配符(wildcards)是 Snakefile 最强大的特性之一。你可以用 {sample}、{assay} 等占位符,让一条规则同时处理上百个文件。运行时 Snakemake 会根据实际文件自动解析通配符的值。

举个例子:

rule predict_all:
    input:
        "data/{sample}.smi"
    output:
        "results/{sample}/admet.csv"
    shell:
        "mkdir -p results/{sample} && "
        "admet_predictor --input {input} --output {output}"

当你执行 snakemake results/compound_123/admet.csv 时,Snakemake 会自动把 {sample} 替换为 compound_123。

避坑指南:我曾经写过一条规则,通配符只在输出中出现,输入里却没有。Snakemake 虽然能运行,但无法自动推导所有目标文件,需要手动指定。记住:通配符必须在输入或输出中被“消费”,否则会报错。

参数(params)与自定义变量

除了输入输出,你还可以用 params 传递额外参数,比如阈值、模型路径、日志选项。这样把配置和命令逻辑分开,维护起来更清爽。

rule filter_by_property:
    input:
        "results/{sample}.csv"
    output:
        "results/{sample}_filtered.csv"
    params:
        max_logp = 5.0,
        min_solubility = -4.0
    shell:
        "filter_admet --input {input} --output {output} "
        "--max-logp {params.max_logp} --min-sol {params.min_solubility}"

params 可以是字符串、数字甚至列表。如果参数需要随样本变化,你还可以在 params 里使用函数。

shell 与 run:执行命令的两种方式

Shell 直接运行字符串命令,简单直接;Run 则让你写 Python 代码,适合复杂逻辑。我个人的习惯是:能用 shell 解决的就用 shell,需要调用 Python API 或复杂条件判断时再用 run。

下面这个例子展示了 run 块的使用:

rule compute_stats:
    input:
        "results/{sample}.csv"
    output:
        "results/{sample}_stats.json"
    run:
        import pandas as pd
        import json
        df = pd.read_csv(input[0])
        stats = {
            "mean": df["value"].mean(),
            "std": df["value"].std()
        }
        with open(output[0], "w") as f:
            json.dump(stats, f)

注意在 run 块里,输入输出都是 Python 列表或字符串,直接用 input[0] 访问。

特性shellrun
灵活性依赖外部工具可直接写 Python 逻辑
错误处理通过返回码判断可用 try/except
性能无额外开销加载 Python 环境略慢
推荐场景简单命令行数据后处理、条件分支

Snakefile 的完整结构一览

下面这张图概括了 Snakefile 的核心要素。你写流水线时,脑子里始终要有这个框架。

Snakefile 核心结构 rule predict_admet input: "data.smi" output: "admet.csv" params: threshold=0.5 shell: "cmd {input}" 通配符 {sample} Snakemake 根据输入输出自动推断执行顺序 通配符使一条规则可批量处理多个文件

多规则流水线的组织形式

一个完整的 ADMET 预测流水线通常包含多个规则:数据清洗、描述符计算、模型预测、结果汇总。你可以把所有规则写在同一个 Snakefile 里,也可以拆分成多个文件用 include 导入。我个人倾向按功能模块拆分:比如 preprocess.smk、predict.smk、report.smk,然后在主 Snakefile 中 include。

# 主 Snakefile
include: "rules/preprocess.smk"
include: "rules/predict.smk"
include: "rules/report.smk"

这样每个文件只关注一个阶段,修改起来不会牵一发而动全身。而且多人协作时可以并行开发不同的模块。

实战避坑:Snakefile 常见错误

我整理了几个自己踩过的坑,希望对你有帮助:

  • 路径大小写敏感:Linux 下 Data/ 和 data/ 是两个目录,Snakemake 不会帮你统一。
  • 通配符未消费:如果输出有 {sample} 但输入没有,Snakemake 会认为这是一个“未知”通配符,需要你在执行时手动指定。
  • 重复规则名:Snakemake 不允许两条 rule 同名,否则会报错。我通常用 rule predict_admet_rf、rule predict_admet_xgb 这样的命名区分模型。
  • 忘记 mkdir -p:Shell 命令不会自动创建目录,输出路径如果包含子目录,记得先创建。

小技巧:在开发阶段,可以用 snakemake -np 打印计划执行的任务,而不实际运行。这样能快速发现依赖和通配符解析的问题。

本章小结