Conditional execution of one rule or another depending on the arguments given to a snakemake pipeline

Viewed 667

I am creating a snakemake pipeline where I have, in some point, to filter my results. But there are two kind of filters that I could apply, so I would like to give it as an argument when launching the pipelin and then, depending on the argument, I would like to apply a rule or another.

As an example:

snakemake --snakefile my_pipeline.sm --config filter=${1}

where filter can be Hard or Soft

my_pipeline.sm is conformed by 4 rules:

rule A:
    input:
          A.bam
    outpu:
          A.vcf
    shell:
          "do.something"

rule B:
    input:
         A.vcf
    output:
         A.hard_filtered.vcf
    shell:
         "do.something"

rule C:
    input:
         A.vcf
    output:
         A.soft_filtered.vcf
    shell:
         "do.something"

rule D:
    input:
         A.*_filtered.vcf
    output:
         A.annotated.vcf
    shell:
         "do.something"

Is there anyway to execute rule B if filter argument is Hard, while executing rule C if filter argument is Soft; instead of performing a conditional clausule in the shell command of a unique rule? I didn't find this information in snakemake manual.

2 Answers

Snakemake has a feature called ruleorder. That allows you to disambiguate rules. For example in your code (which wouldn't work, but let's assume that it is corrected) you have two equal branches to get the target file done:

  • A.bam -> A.vcf -> A.hard_filtered.vcf -> A.annotated.vcf
  • A.bam -> A.vcf -> A.soft_filtered.vcf -> A.annotated.vcf

Snakemake will complain that it doesn't know which branch to prefer. That could be fixed by:

ruleorder: rule1 > rule2

Now Snakemake will always prefer rule1 to the rule2.

You may define two different orderings based on the configuration (that comes either from file or from command line). Below is my simplified code sample.

configfile: "config.yml"

if config["filtering"] == "soft":
    ruleorder: annotate_soft > annotate_hard
else:
    ruleorder: annotate_hard > annotate_soft

rule all:
    input:
        "A.annotated.vcf"

rule hard:
    input:
        "A.vcf"
    output:
        "A.hard_filtered.vcf"
    shell:
        "echo 'hard' > {output}"

rule soft:
    input:
        "A.vcf"
    output:
        "A.soft_filtered.vcf"
    shell:
        "echo 'soft' > {output}"

rule annotate_hard:
    input:
        "A.hard_filtered.vcf"
    output:
        "A.annotated.vcf"
    shell:
        "cp {input} {output}"

rule annotate_soft:
    input:
        "A.soft_filtered.vcf"
    output:
        "A.annotated.vcf"
    shell:
        "cp {input} {output}"

I can think of two options that will allow you to exclude rules based on configuration settings.

  • You can place entire rules or sets of rules in conditional statements-- though this feels like something that should be used sparingly as it could lead to impenetrable code if extensively employed. Example below.

  • The other alternative, which I have not had the need for yet, so don't have first hand experience is to use [checkpoints(https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#data-dependent-conditional-execution) - though this may be a little convoluted to.

rule A:
    input:
          A.bam
    outpu:
          A.vcf
    shell:
          "do.something"

# Here you go
if config['filter_type'] in ["hard"]:
    rule B:
        input:
             A.vcf
        output:
             A.hard_filtered.vcf
        shell:
             "do.something"
elif config['filter_type'] in ["soft"]:
    rule C:
        input:
             A.vcf
        output:
             A.soft_filtered.vcf
        shell:
             "do.something"

rule D:
    input:
         A.*_filtered.vcf
    output:
         A.annotated.vcf
    shell:
         "do.something"

And you I think you could leave everything as initially set and use a the config variable to compose the ruleD expected input. Like so:


rule D:
    input:
         f"A.{config['filter_type']}_filtered.vcf"
    output:
         A.annotated.vcf
    shell:
         "do.something"

If the last one caused no errors, it's the one I'd go with. This assumes you always have filter type set- and would crash unless there was a global config value set that the command line was over-riding.

Related