Snakemake - 从输入文件动态派生目标

Snakemake - dynamically derive the targets from input files

我有大量的输入文件是这样组织的:

data/
├── set1/
│   ├── file1_R1.fq.gz
│   ├── file1_R2.fq.gz
│   ├── file2_R1.fq.gz
│   ├── file2_R2.fq.gz
|   :
│   └── fileX_R2.fq.gz
├── another_set/
│   ├── asdf1_R1.fq.gz
│   ├── asdf1_R2.fq.gz
│   ├── asdf2_R1.fq.gz
│   ├── asdf2_R2.fq.gz
|   :
│   └── asdfX_R2.fq.gz
:   
└── many_more_sets/
    ├── zxcv1_R1.fq.gz
    ├── zxcv1_R2.fq.gz
    :
    └── zxcvX_R2.fq.gz

如果您熟悉生物信息学——这些当然是来自配对末端测序运行的 fastq 文件。我正在尝试生成一个读取所有这些的 snakemake 工作流程,但我已经在第一条规则上失败了。这是我的尝试:

configfile: "config.yaml"

rule all:
    input:
        read1=expand("{output}/clipped_and_trimmed_reads/{{sample}}_R1.fq.gz", output=config["output"]),
        read2=expand("{output}/clipped_and_trimmed_reads/{{sample}}_R2.fq.gz", output=config["output"])

rule clip_and_trim_reads:
    input:
        read1=expand("{data}/{set}/{{sample}}_R1.fq.gz", data=config["raw_data"], set=config["sets"]),
        read2=expand("{data}/{set}/{{sample}}_R2.fq.gz", data=config["raw_data"], set=config["sets"])
    output:
        read1=expand("{output}/clipped_and_trimmed_reads/{{sample}}_R1.fq.gz", output=config["output"]),
        read2=expand("{output}/clipped_and_trimmed_reads/{{sample}}_R2.fq.gz", output=config["output"])
    threads: 16
    shell:
        """
        someTool -o {output.read1} -p {output.read2} \
        {input.read1} {input.read2}
        """

我无法将 clip_and_trim_reads 指定为目标,因为 Target rules may not contain wildcards. 我尝试添加 all 规则,但这给了我另一个错误:

$ snakemake -np
Building DAG of jobs...
WildcardError in line 3 of /work/project/Snakefile:
Wildcards in input files cannot be determined from output files:
'sample'

我也尝试使用 dynamic() 函数作为 all 规则,它确实找到了文件,但仍然给我这个错误:

$ snakemake -np
Dynamic output is deprecated in favor of checkpoints (see docs). It will be removed in Snakemake 6.0.
Building DAG of jobs...
MissingInputException in line 7 of /work/project/ladsie_002/analyses/scripts/2019-08-02_splice_leader_HiC/Snakefile:
Missing input files for rule clip_and_trim_reads:
data/raw_data/set1/__snakemake_dynamic___R1.fq.gz
data/raw_data/set1/__snakemake_dynamic___R2.fq.gz
data/raw_data/set1/__snakemake_dynamic___R2.fq.gz
data/raw_data/set1/__snakemake_dynamic___R1.fq.gz
[...]

我有一百多个不同的文件,所以我非常想避免创建包含所有文件名的列表。有什么想法可以实现吗?

我认为您误解了 snakemake 的工作原理。当你 运行 snakemake 你定义你想要的输出时,要么在命令行上,要么将生成 Snakefile 中第一条规则的输入(你的规则全部)。由于你没有指定任何输出(snakemake -np),Snakemake 将尝试生成规则所有的输入。

你的规则的输入基本上都是:

"somepath/clipped_and_trimmed_reads/{sample}_R1.fq.gz"

不幸的是,Snakemake 不知道如何从中生成输出...我们需要告诉 Snakemake 使用哪些文件。我们可以手动执行此操作:

rule all:
    input:
        "somepath/clipped_and_trimmed_reads/file1_R1.fq.gz",
        "somepath/clipped_and_trimmed_reads/asdf1_R1.fq.gz",
        "somepath/clipped_and_trimmed_reads/zxcv1_R1.fq.gz"

但是随着我们获得更多文件,这变得非常麻烦,而且正如您在问题中指定的那样,这不是您想要的。我们需要编写一个小函数来为我们获取所有文件名。

import glob
import re

data=config["raw_data"]
samples = []
locations = {}
for file in glob.glob(data + "/**", recursive=True):
    if '_R1.fq.gz' in file:
        split = re.split('/|_R1', file)
        filename, directory = split[-2], split[-3]
        locations[filename] = directory  # we will need this one later
        samples.append(filename)

我们现在可以将其提供给我们的规则:

rule all:
    input:
        read1=expand("{output}/clipped_and_trimmed_reads/{sample}_R1.fq.gz", output=config["output"], sample=samples),
        read2=expand("{output}/clipped_and_trimmed_reads/{sample}_R2.fq.gz", output=config["output"], sample=samples)

请注意,我们不再将 sample 作为通配符,但我们 'expand' 将其放入我们的 read1 和 read2 中,从而实现输出和样本的所有可能组合。

然而我们只完成了一半.. 如果我们像这样调用 Snakemake,它会确切地知道我们想要哪个输出,以及哪个规则可以生成这个(规则 clip_and_trim_reads)。然而它仍然不知道去哪里寻找这些文件。幸运的是,我们已经有了一个字典来存储这些(存储在 位置 )。

rule clip_and_trim_reads:
    input:
        read1=lambda wildcards: expand("{data}/{set}/{sample}_R1.fq.gz", data=config["raw_data"], set=locations[wildcards.sample], sample=wildcards.sample),
        read2=lambda wildcards: expand("{data}/{set}/{sample}_R2.fq.gz", data=config["raw_data"], set=locations[wildcards.sample], sample=wildcards.sample)
    output:
        ...

现在一切正常了!!甚至更好;由于规则 clip_and_trim_reads 的所有结果都写入了一个文件夹,因此从这里继续应该容易得多!

p.s。我没有测试过任何代码,所以第一次尝试时可能并非一切正常。但是消息仍然存在。