理解 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] 访问。
| 特性 | shell | run |
|---|---|---|
| 灵活性 | 依赖外部工具 | 可直接写 Python 逻辑 |
| 错误处理 | 通过返回码判断 | 可用 try/except |
| 性能 | 无额外开销 | 加载 Python 环境略慢 |
| 推荐场景 | 简单命令行 | 数据后处理、条件分支 |
Snakefile 的完整结构一览
下面这张图概括了 Snakefile 的核心要素。你写流水线时,脑子里始终要有这个框架。
多规则流水线的组织形式
一个完整的 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 打印计划执行的任务,而不实际运行。这样能快速发现依赖和通配符解析的问题。