如何基于字典值用Snakemake的expand编写rule all匹配指定样本?
现有一个存储患者与对应样本映射关系的字典:
patient_samples = { "patientA": ["sample1", "sample2", "sample3"], "patientB": ["sample1", "sample4", "sample5", "sample6"] }
需要将每个sample.fastq文件比对后,把生成的.bam文件存放在对应患者的目录下,期望得到的目录结构如下:
├── patientA │ ├── sample1.bam │ ├── sample2.bam │ ├── sample3.bam ├── patientB │ ├── sample1.bam │ ├── sample4.bam │ ├── sample5.bam │ ├── sample6.bam
已编写align规则如下:
rule align: input: lambda wildcards: \ ["{0}.fastq".format(sample_id) \ for sample_id in patient_samples[wildcards.patient_id] ] output: "{patient_id}/{sample_id}.bam" shell: ### Alignment command
但编写rule all时,尝试以下写法出现NameError(提示name 'patient_id' is not defined):
rule all: input: expand("{patient_id}/{sample_id}.bam", patient_id=patient_samples.keys(), sample_id=patient_samples[patient_id])
需要解决如何正确实现rule all以仅为每个患者比对指定样本的问题。
之前的expand写法报错是因为patient_samples[patient_id]中的patient_id在expand的参数解析阶段尚未被定义,expand无法在参数内部引用另一个参数的动态值。以下是几种可行的解决方法:
方法1:用列表生成式构建所有目标路径
直接遍历patient_samples字典,生成所有需要的.bam路径,作为rule all的输入:
rule all: input: [f"{patient}/{sample}.bam" for patient, samples in patient_samples.items() for sample in samples]
这种方式简单直接,通过两层循环遍历每个患者及其对应的样本列表,拼接出完整的输出路径。
方法2:使用expand的嵌套迭代
利用expand的zip参数,将每个患者ID和其对应的样本列表进行配对迭代:
rule all: input: expand("{patient_id}/{sample_id}.bam", zip, patient_id=patient_samples.keys(), sample_id=patient_samples.values())
zip参数会让expand逐个取出每个患者ID和对应的样本列表,然后将患者ID与列表中的每个样本一一组合,生成正确的路径。
方法3:优化align规则的输入逻辑(可选)
原align规则的输入写法会让每个样本触发规则时,输入是该患者下所有样本的fastq文件,这通常不符合实际需求(每个样本应该只对应自己的fastq文件)。可以修改align规则,让输入仅针对当前样本:
rule align: input: "{sample_id}.fastq" output: "{patient_id}/{sample_id}.bam" shell: # 替换为实际的比对命令,例如bwa mem结合samtools "bwa mem reference.fasta {input} | samtools view -Sb - > {output}"
调整后,每个样本单独处理自身的输入文件,规则逻辑更清晰,此时rule all使用方法1或方法2的写法依然生效。
内容的提问来源于stack exchange,提问作者woostersauce

