2. Snakefile 基础:规则构成、输入输出与通配符
说实话,刚接触 Snakemake 的人,第一反应往往是:“这不就是个加强版 Makefile 吗?”
嗯,有点像,但核心逻辑完全不同。
Makefile 关注的是“如何编译”,Snakefile 关注的是“如何串联数据流水线”。
我刚开始用的时候,也踩过不少坑。今天我们就从最基础的规则开始,把骨架搭起来。
2.1 规则长什么样?
一个规则,说白了就是一个“加工指令”。
告诉 Snakemake:你要用什么原料,通过什么方式,做成什么成品。
最基本的结构就四部分:规则名、输入、输出、执行动作。
看这个例子:
rule clean_data:
input:
"data/raw_data.csv"
output:
"data/cleaned.csv"
shell:
"python scripts/clean.py {input} {output}"
规则名叫 clean_data。输入是原始 CSV,输出是清洗后的文件。
Shell 部分就是实际干活的那行命令。大括号里的 {input} 和 {output} 会自动替换成你定义的文件路径。
2.2 input 和 output:规则的核心纽带
Snakemake 的规则是靠文件依赖关系连接起来的。
A 规则的输出,可以成为 B 规则的输入。这样,流水线就串起来了。
关键点在哪?Snakemake 只关心“目标文件是否存在”。
比如你运行:
snakemake data/feature_engineered.csv
它会一路向上回溯,找到那个能产生这个文件的规则,然后检查所有上游依赖是否齐全。
看一个多步骤的例子:
rule normalize:
input:
"data/cleaned.csv"
output:
"data/normalized.csv"
shell:
"python scripts/normalize.py {input} {output}"
rule engineer_features:
input:
"data/normalized.csv"
output:
"data/feature_engineered.csv"
shell:
"python scripts/engineer.py {input} {output}"
engineer_features 依赖 normalize 的输出。normalize 又依赖 clean_data 的输出。
你只需要指定最终目标文件,Snakemake 自动帮你排好执行顺序。
2.3 通配符:解开重复规则的锁
上面写的规则,一次只能处理一个文件。
但在真实项目里,你可能有几十个原始数据文件,难道每个都要写一条规则?
当然不。这时候就需要通配符。
通配符用大括号括起来,写在文件名里。Snakemake 会自动匹配实际文件中的对应部分。
看这个例子:
rule clean_sample:
input:
"data/raw/{sample}.csv"
output:
"data/cleaned/{sample}.csv"
shell:
"python scripts/clean.py {input} {output}"
{sample} 就是通配符。它可以匹配任何字符序列。
当你执行 snakemake data/cleaned/01.csv 时,Snakemake 就知道 sample=01。
然后它自动寻找 data/raw/01.csv 作为输入。
你想想看,这就意味着你只需要一条规则,就能处理任意数量的样本。
2.4 多个通配符的组合
一个规则里可以有好几个通配符。
比如我要按日期和特征类型来处理:
rule extract_features:
input:
"data/cleaned/{date}/{feature_type}.csv"
output:
"data/features/{date}/{feature_type}.parquet"
shell:
"python scripts/extract.py {input} {output}"
当 Snakemake 需要生成 data/features/2024-01-01/rolling_min.parquet 时:
{date} → 2024-01-01{feature_type} → rolling_min
它会自动寻找 data/cleaned/2024-01-01/rolling_min.csv 作为输入。
| 通配符模式 | 匹配示例 | 说明 |
|---|---|---|
| {sample} | sample_01、batch_A | 匹配任意单段路径 |
| {date} | 2024-01-01、2024-02_batch | 可包含连字符等字符 |
| {feature} | rolling_mean、max_5min | 常用于特征名或类型 |
/。如果你需要匹配多级目录,必须显式定义多个通配符,或者使用通配符与固定路径结合。
2.5 通配符的实际机制
通配符的解析,依赖于规则输出中的模式。
Snakemake 拿到目标文件路径后,会跟所有规则的 output 模式做匹配。
一旦匹配成功,通配符的值就被确定。然后 Snakemake 用这个值去填充 input 模式,得到真正的输入文件路径。
这个过程很聪明,但也有限制:
- 一个规则里的所有通配符,值是一次确定的
- 不同的规则可以有相同的通配符名,值可以不同
- 输出文件中的通配符,必须在输入文件中出现,否则 Snakemake 不知道要依赖什么
2.6 规则命名与组织建议
规则名要清晰,我习惯用动词开头:clean_data、normalize_features、engineer_rolling_window。
这样一看就知道这个规则在干什么。
另外,不要把任何规则写得太长。一个规则最好只做一件事。
我早期写过一个大规则,里面包含清洗、归一化、特征提取三条命令。后来想重用其中一部分,才发现拆无可拆。
如果你有多个输出文件,可以用列表:
rule generate_features:
output:
"features/mean.csv",
"features/std.csv",
"features/max.csv"
这样一条规则可以同时产生多个成品。
2.7 避坑指南:通配符常见错误
我见过好几个新手在通配符上栽跟头。这里列几个最常见的:
- 通配符不在输出中出现:如果输出写死了一个固定文件名,但输入用了通配符,Snakemake 无法解析,直接报错。
- 多个通配符命名冲突:同一个规则里不能用两个同名通配符,但不同规则可以各自定义自己的同名通配符,互不干扰。
- 路径不匹配:比如输出是
data/{sample}/result.csv,但目录data/{sample}不存在,Snakemake 也不会自动创建目录。需要手动处理目录结构。
data/{sample}/features.parquet,但 input 里用了 data/raw/{sample}.csv。执行时 Snakemake 报错说通配符 {sample} 在 input 中不完整。后来我把 input 改成 data/raw/{sample}/clean.csv,问题才解决。通配符的层级必须一致。
2.8 小结
规则的构成很简单:input、output、shell(或 run)。
输入输出是流水线的粘合剂,通配符是解决重复问题的利器。
记住:Snakemake 是靠文件路径来管理依赖的。只要你把输入输出关系写清楚,它就能自动搞定执行顺序和重跑判断。