Snakemake 配置与变量:配置文件、全局变量、params、resources
说实话,Snakemake 最让我欣赏的一点就是它的配置系统。你不需要把参数硬编码在规则里,而是可以通过外部文件、全局变量、params 和 resources 灵活传递。今天我们就把这几个概念彻底理清楚。
配置文件:把参数从代码里拆出来
我习惯把配置单独写成一个 config.yaml,然后在 Snakefile 开头用一行指令加载:
configfile: "config.yaml"
以后要改参数,编辑 YAML 文件就行,不用动 Snakefile。我最怕的就是跑管道时反复修改脚本,稍不留神就改出 bug。
支持 YAML、JSON、TOML 三种格式。我个人偏爱 YAML,可读性好,也支持注释。举个例子:
# config.yaml
samples: ["sample1", "sample2", "sample3"]
reference: "refdata/hg38"
min_cells: 3
min_features: 200
threads: 8
路径建议用相对路径或全局变量,别写死绝对路径。我在项目中吃过这个亏——把绝对路径写到配置文件里,换到集群上跑时全得重改。
全局变量 config:管道里的「共享状态」
Snakemake 把配置文件的内容自动变成一个全局字典 config。你在任何规则里都能通过 config["key"] 或 config.get("key", default) 访问。
当然,config 里不仅能放从外部文件读来的值,也可以在 Snakefile 里直接赋值:
config["project"] = "scRNA_2024"
config["output_dir"] = "results/"
不过我不推荐在 Snakefile 里大段赋值——那和硬编码没区别。配置文件才是正道。
另外,运行 snakemake 时可以通过 --config key=value 覆盖已有值:
snakemake --cores 8 --config min_cells=5
这样调试很方便。你想想,不用改任何文件就能试参数。
params:给规则传参数的灵活工具
每个 rule 里可以用 params 指令定义额外的参数,这些参数可以来自 config、wildcards 甚至处理过的表达式。
rule create_seurat_object:
input:
counts = "data/{sample}.mtx",
barcodes = "data/{sample}.barcodes.tsv"
output:
seurat_obj = "results/{sample}_seurat.rds"
params:
min_cells = config["min_cells"],
min_features = config["min_features"],
project = "my_project"
shell:
"""
Rscript scripts/create_seurat.R \
--input {input.counts} \
--barcodes {input.barcodes} \
--output {output.seurat_obj} \
--min_cells {params.min_cells} \
--min_features {params.min_features}
"""
看到没?参数可以混搭:从 config 取值,也可以写固定值。甚至可以基于 wildcards 动态计算:
params:
outfile = lambda wildcards: f"intermediate/{wildcards.sample}_qc.pdf"
注意:params 的值会在规则运行时被求值。如果你用了 lambda,确保函数执行没有副作用。我曾经写过一段从网络请求参数的 lambda,结果每次重启管道都要重新下载,浪费时间。
resources:告诉调度器你用了多少资源
resources 指令用来声明规则需要消耗的资源:内存、CPU 时间、GPU 等。它不是强制的,但强烈建议写上——尤其是在集群上跑管道时。
rule run_qc:
input:
seurat_obj = "results/{sample}_seurat.rds"
output:
qc_report = "results/{sample}_qc.html"
resources:
mem_mb = 8000,
threads = 4,
runtime = 30
shell:
"Rscript scripts/run_qc.R {input.seurat_obj} {output.qc_report}"
调度器会根据你声明的 resources 分配节点。比如在 SLURM 集群上,Snakemake 能自动转成 --mem --cpus-per-task 等参数。你不需要手动写 sbatch 脚本,它全帮你搞定。
常见的资源键名有:
| 键 | 单位 | 说明 |
|---|---|---|
mem_mb |
MB | 所需内存 |
mem_gb |
GB | Snakemake 7.8+ 支持,自动换算 |
threads |
整数 | 并行线程数 |
runtime |
分钟 | 预计运行时间 |
disk_mb |
MB | 磁盘空间(部分集群支持) |
gpu |
整数 | GPU 数量 |
params vs resources 如何选择?
- params 只传递值,不影响调度;resources 影响资源分配。
- 如果参数只用于脚本内部(比如阈值、文件名后缀),用 params。
- 如果参数决定了计算资源需求(内存、线程、时间),用 resources。
一张图看懂配置流动
下面这张图我把整个流程串了起来:配置文件、Snakefile、config 字典、params 和 resources 之间的关系一清二楚。你看完后应该能把握整个体系。
实战:一个完整的 Seurat 管道配置
下面我给你展示一个真实项目的片段。配置文件长这样:
# config.yaml for Seurat pipeline
samples:
- sample1
- sample2
- sample3
reference_dir: "/data/refs/hg38"
qc:
min_cells: 3
min_features: 200
max_mito: 0.05
clustering:
resolution: 0.8
dims: 1:30
resources:
default_mem: 8000
default_threads: 4
qc_threads: 2
clustering_mem: 16000
然后在 Snakefile 里用 configfile: "config.yaml" 加载,接着可以写出这样的规则:
rule filter_cells:
input:
seurat_obj = "results/{sample}_raw.rds"
output:
seurat_obj = "results/{sample}_filtered.rds"
params:
min_cells = config["qc"]["min_cells"],
min_features = config["qc"]["min_features"],
max_mito = config["qc"]["max_mito"]
resources:
mem_mb = config["resources"]["qc_threads"] * 2000,
threads = config["resources"]["qc_threads"]
shell:
"""
Rscript scripts/filter_cells.R \
--input {input.seurat_obj} \
--output {output.seurat_obj} \
--min_cells {params.min_cells} \
--min_features {params.min_features} \
--max_mito {params.max_mito}
"""
我的经验:资源尽量和配置里的数字统一管理,别在 rule 里写死 mem_mb = 8000。一旦集群资源变化,你只需要改 config.yaml 一处就可以了。
避坑指南
- 别把 configfile 写错路径。我曾经把配置文件名写成了
config,没有扩展名,Snakemake 根本认不出来。(还是小写config.yaml最稳。) - 小心 YAML 的布尔值陷阱。
yes/no/true/false会被解析成布尔类型。如果你需要字符串"yes",请加引号。 - resources 只是声明,不保证分配。调度器会尽量满足,但如果你没有配置集群,Snakemake 默认忽略 resources。我在本地测试时经常忘了设
--resources,结果导致并行太多把内存撑爆。 - params 中的 lambda 延迟求值。如果依赖 wildcards,确保在 lambda 内部能用
wildcards.sample访问,不要在 lambda 外面就计算了。
好了,配置这一块的内容基本就这些了。你能清晰地区分配置文件、config 字典、params 和 resources,就起码掌握了 Snakemake 管道里参数流动的骨架。后面写规则才不容易犯迷糊。