Snakemake: Как использовать функцию, которая принимает подстановочный знак и возвращает значение?

У меня есть файлы cram (bam), которые я хочу разделить по группам чтения. Это требует чтения заголовка и извлечения идентификаторов групп чтения.

У меня есть эта функция, которая делает это в моем файле Snakemake:

def identify_read_groups(cram_file):
    import subprocess
    command = 'samtools view -H ' + cram_file + ' | grep ^@RG | cut -f2 | cut -f2 -d":" '
    read_groups = subprocess.check_output(command, shell=True)
    read_groups = read_groups.split('\n')[:-1]
    return(read_groups)

У меня это правило все:

rule all:
input:
    expand('cram/RG_bams/{sample}.RG{read_groups}.bam', read_groups=identify_read_groups('cram/{sample}.bam.cram'))

И это правило для фактического разделения:

rule split_cram_by_rg:
input:
    cram_file='cram/{sample}.bam.cram',
    read_groups=identify_read_groups('cram/{sample}.bam.cram')
output:
    'cram/RG_bams/{sample}.RG{read_groups}.bam'
run:
    import subprocess
    read_groups = open(input.readGroupIDs).readlines()
    read_groups = [str(rg.replace('\n','')) for rg in read_groups]
    for rg in read_groups:
        command = 'samtools view -b -r ' + str(rg) + ' ' + str(input.cram_file) + ' > ' + str(output)
        subprocess.check_output(command, shell=True)

Я получаю эту ошибку при пробном запуске:

[E::hts_open_format] fail to open file 'cram/{sample}.bam.cram'
samtools view: failed to open "cram/{sample}.bam.cram" for reading: No such file or directory
TypeError in line 19 of /gpfs/gsfs5/users/mcgaugheyd/projects/nei/mcgaughey/EGA_EGAD00001002656/Snakefile:
a bytes-like object is required, not 'str'
File "/gpfs/gsfs5/users/mcgaugheyd/projects/nei/mcgaughey/EGA_EGAD00001002656/Snakefile", line 37, in <module>
  File "/gpfs/gsfs5/users/mcgaugheyd/projects/nei/mcgaughey/EGA_EGAD00001002656/Snakefile", line 19, in identify_read_groups

{sample} не передается в функцию.

Как мне решить эту проблему? Я открыт для других подходов, если я не делаю это «змеиным» способом.

==============

ИЗМЕНИТЬ 1

Хорошо, в первом наборе примеров, которые я привел, было много проблем.

Вот лучший (?) Набор кода, который, я надеюсь, демонстрирует мою проблему.

import sys
from os.path import join

shell.prefix("set -eo pipefail; ")

def identify_read_groups(wildcards):
    import subprocess
    cram_file = 'cram/' + wildcards + '.bam.cram'
    command = 'samtools view -H ' + cram_file + ' | grep ^@RG | cut -f2 | cut -f2 -d":" '
    read_groups = subprocess.check_output(command, shell=True)
    read_groups = read_groups.decode().split('\n')[:-1]
    return(read_groups)

SAMPLES, = glob_wildcards(join('cram/', '{sample}.bam.cram'))
RG_dict = {}
for i in SAMPLES:
    RG_dict[i] = identify_read_groups(i)

rule all:
    input:
        expand('{sample}.boo.txt', sample=list(RG_dict.keys()))

rule split_cram_by_rg:
    input:
        file='cram/{sample}.bam.cram',
        RG = lambda wildcards: RG_dict[wildcards.sample]
    output:
        expand('cram/RG_bams/{{sample}}.RG{input_RG}.bam') # I have a problem HERE. How can I get my read groups values applied here? I need to go from one cram to multiple bam files split by RG (see -r in samtools view below). It can't pull the RG from the input.
    shell:
        'samtools view -b -r {input.RG} {input.file} > {output}'


rule merge_RG_bams_into_one_bam:
    input:
        rules.split_cram_by_rg.output
    output:
        '{sample}.boo.txt'
    message:
        'echo {input}'
    shell:
        'samtools merge {input} > {output}' #not working
        """

==============

ИЗМЕНИТЬ 2

Становимся НАМНОГО ближе, но в настоящее время изо всех сил пытаемся правильно развернуть, создавая файлы полосы движения и сохраняя подстановочные знаки

Я использую этот цикл для создания промежуточных имен файлов:

for sample in SAMPLES:
    for rg_id in list(return_ID(sample)):
        out_rg_bam.append("temp/lane_bam/{}.ID{}.bam".format(sample, rg_id))

return_ID - это функция, которая принимает подстановочный знак образца и возвращает список групп чтения, которые содержит образец.

Если я использую out_rg_bam в качестве входных данных для правила слияния, то ВСЕ файлы объединяются в объединенный бац вместо того, чтобы разделяться на sample.

Если я использую expand('temp/realigned/{{sample}}.ID{rg_id}.realigned.bam', sample=SAMPLES, rg_id = return_ID(sample)), то к каждому образцу применяется rg_id. Итак, если у меня есть два образца (a, b) с группами чтения (0,1) и (0,1,2), я получаю a0, a1, a0, a1, a2 и b0, b1, b0, b1 , Би 2.


person David M    schedule 12.10.2017    source источник
comment
expand('cram/RG_bams/{{sample}}.RG{input_RG}.bam') требуется информация о том, как раскрыть input_RG. Это должны быть значения в RG_dict или что-то в этом роде?   -  person bli    schedule 16.10.2017
comment
Также обратите внимание, что если выходные данные определяются на основе подстановочного знака и расширяются по нему, эти подстановочные знаки не будут доступны внутри правила. Если вы хотите применить samtools view один раз для каждой группы чтения, то раскрытие относится не к выходным данным правила split_cram_by_rg, а к следующему правилу. Тогда подстановочный знак группы чтения будет доступен в правилах выше того, где происходит раскрытие.   -  person bli    schedule 16.10.2017
comment
Вы абсолютно правы. Я все еще пытаюсь заставить свой пример работать. Я намного ближе и опубликую, когда я заработаю. Прямо сейчас я борюсь с правильным построением расширения. В разных файлах BAM разное количество групп чтения, поэтому я не могу использовать простой пример расширения, который видел.   -  person David M    schedule 24.10.2017
comment
Мой совет для расширения: попробуйте вручную с логикой цикла Python вместо использования предоставленного snakemake expand.   -  person bli    schedule 24.10.2017
comment
добавлено редактирование 2, чтобы объяснить мою текущую проблему   -  person David M    schedule 24.10.2017
comment
Хорошо, я думаю, что я ее взломал (решил). В итоге мне пришлось написать функцию для создания расширенных входных файлов для этапа слияния. Я запускаю его сейчас и опубликую код, если он действительно работает правильно.   -  person David M    schedule 24.10.2017


Ответы (3)


Я дам более общий ответ, чтобы помочь другим, кто может найти эту ветку. Snakemake применяет подстановочные знаки к строкам в разделах «ввод» и «вывод» только тогда, когда строки указаны непосредственно в списке, например:

input:
    '{sample}.bam'

Если вы пытаетесь использовать такие функции, как были здесь:

input:
    read_groups=identify_read_groups('cram/{sample}.bam.cram')

Замена подстановочного знака не производится. Вы можете использовать лямбда-функцию и произвести замену самостоятельно:

input:
    read_groups=lambda wildcards: identify_read_groups('cram/{sample}.bam.cram'.format(sample=wildcards.sample))
person Colin    schedule 24.10.2017

попробуйте следующее: я использую id = 0, 1, 2, 3, чтобы назвать выходной файл BAM в зависимости от того, сколько групп чтения для файла BAM.

## this is a regular function which takes the cram file, and get the read-group to 
## construct your rule all
## you actually just need the number of @RG, below can be simplified  
def get_read_groups(sample):
    import subprocess
    cram_file = 'cram/' + sample + '.bam.cram'
    command = 'samtools view -H ' + cram_file + ' | grep ^@RG | cut -f2 | cut -f2 -d":" '
    read_groups = subprocess.check_output(command, shell=True)
    read_groups = read_groups.decode().split('\n')[:-1]
    return(read_groups)

SAMPLES, = glob_wildcards(join('cram/', '{sample}.bam.cram'))
RG_dict = {}
for sample in SAMPLES:
    RG_dict[sample] = get_read_groups(sample)

outbam = []
for sample in SAMPLES:
    read_groups = RG_dict[sample]
    for i in range(len(read_groups)):
        outbam.append("{}.RG{}.bam".format(sample, id))


rule all:
    input:
        outbam

## this is the input function, only uses wildcards as argument 
def identify_read_groups(wildcards):
    import subprocess
    cram_file = 'cram/' + wildcards.sample + '.bam.cram'
    command = 'samtools view -H ' + cram_file + ' | grep ^@RG | cut -f2 | cut -f2 -d":" '
    read_groups = subprocess.check_output(command, shell=True)
    read_groups = read_groups.decode().split('\n')[:-1]
    return(read_groups[wildcards.id])

rule split_cram_by_rg:
    input:
        cram_file='cram/{sample}.bam.cram',
        read_groups=identify_read_groups
    output:
        'cram/RG_bams/{sample}.RG{id}.bam'  
    run:
        import subprocess
        read_groups = input.read_groups
        for rg in read_groups:
            command = 'samtools view -b -r ' + str(rg) + ' ' + str(input.cram_file) + ' > ' + str(output)
            subprocess.check_output(command, shell=True)

при использовании змеимейка думайте снизу вверх. Сначала определите, что вы хотите сгенерировать в правиле all, а затем создайте правило, чтобы создать свое окончательное все.

person crazyhottommy    schedule 14.10.2017
comment
Спасибо. Сегодня у меня нет времени проверять это, но outbam.append("{}.RG{}.bam".format(sample, id)) мне пригодится. Фактический результат - все еще один файл, но я надеюсь, что смогу построить логику. - person David M; 14.10.2017
comment
при использовании snakemake думайте снизу вверх: я думаю, что это действительно важный совет. По сути, ввод правила all сообщает snakemake настоящие имена всех файлов, которые вы хотите (здесь нет подстановочных знаков, возможно, путем замены подстановочных знаков их возможными реальными значениями с помощью expand или других конструкций). Затем snakemake пытается сопоставить эти имена с выходными данными восходящих правил: этот процесс сопоставления определяет значения подстановочных знаков в данном экземпляре правила сопоставления. - person bli; 16.10.2017

В вашем правиле all не должно быть подстановочных знаков. Это не подстановочная зона.

ИЗМЕНИТЬ 1

Я набрал этот псевдокод в Notepad ++, он не предназначен для компиляции, а просто пытается предоставить фреймворк. Я думаю, это больше то, что вам нужно.

Используйте функцию внутри расширения, чтобы сгенерировать список имен файлов, который затем будет использоваться для управления всем правилом конвейера Snakemake. Переменные baseSuffix и basePrefix предназначены только для того, чтобы дать вам представление о передаче String, здесь разрешены аргументы. При передаче списка строк вам нужно будет распаковать их, чтобы Snakemake правильно прочитал результат.

def getSampleFileList(String basePrefix, String baseSuffix){
    myFileList = []
    ListOfSamples = *The wildcard glob call*
    for sample in ListOfSamples:
        command = "samtools -h " + sample + "SAME CALL USED TO GENERATE LIST OF HEADERS"
        for rg in command:
            myFileList.append(basePrefix + sample + ".RG" + rg + baseSuffix)
}


basePreix = "cram/RG_bams/"
baseSuffix = ".bam" 

rule all:
    input:
        unpack(expand("{fileName}", fileName=getSampleFileList(basePrefix, baseSuffix)))


rule processing_rg_files:
    input:
        'cram/RG_bams/{sample}.RG{read_groups}.bam'
    output:
        'cram/RG_TXTs/{sample}.RG{read_groups}.txt'
    run:
        "Let's pretend this is useful code"

КОНЕЦ РЕДАКТИРОВАНИЯ

Если бы этого не было во всем правиле, вы бы использовали встроенные функции

Так что я не уверен, чего вы пытаетесь достичь. В соответствии с моими предположениями, прочтите ниже некоторые заметки о вашем коде.

rule all:
input:
    expand('cram/RG_bams/{sample}.RG{read_groups}.bam', read_groups=identify_read_groups('cram/{sample}.bam.cram'))

Пробный прогон завершается ошибкой, когда он вызывает функцию "identify_read_groups" внутри правила all call. Он передается в ваш вызов функции как строка, а не подстановочный знак.

Технически, если вызов samtools не завершился ошибкой, а вызов функции «identify_read_groups (cram_file)» вернул список из 5 строк, он расширился бы до чего-то вроде этого:

rule all:
    input:
        'cram/RG_bams/{sample}.RG<output1FromFunctionCall>.bam',
        'cram/RG_bams/{sample}.RG<output2FromFunctionCall>.bam',
        'cram/RG_bams/{sample}.RG<output3FromFunctionCall>.bam',
        'cram/RG_bams/{sample}.RG<output4FromFunctionCall>.bam',
        'cram/RG_bams/{sample}.RG<output5FromFunctionCall>.bam'

Но термин "{образец}" на данном этапе предварительной обработки Snakemake считается строкой. Поскольку вам нужно обозначить подстановочные знаки в функции расширения с помощью {{}}.

Посмотрите, как я обращаюсь к каждой переменной Snakemake, которую я объявляю для своего правила всем входным вызовом и не использую подстановочные знаки:

expand("{outputDIR}/{pathGVCFT}/tables/{samples}.{vcfProgram}.{form[1][varType]}{form[1][annotated]}.txt", outputDIR=config["outputDIR"], pathGVCFT=config["vcfGenUtil_varScanDIR"], samples=config["sample"], vcfProgram=config["vcfProgram"], form=read_table(StringIO(config["sampleFORM"]), " ").iterrows())

В этом случае read_table возвращает в форму 2-мерный массив. Snakemake хорошо поддерживается питоном. Мне это понадобилось для объединения разных аннотаций с разными типами вариантов.

Все ваше правило должно быть строкой или списком строк в качестве входных данных. У вас не может быть подстановочных знаков в вашем правиле «все». Это правило: все входные строки - это то, что Snakemake использует для генерации совпадений для ДРУГИХ подстановочных знаков. Создайте полное имя файла в вызове функции и верните его, если вам нужно.

Думаю, вам стоит просто превратить это в что-то вроде этого:

rule all:
input:
    expand("{fileName}", fileName=myFunctionCall(BecauseINeededToPass, ACoupleArgs))

Также подумайте о том, чтобы сделать это более общим:

rule split_cram_by_rg:
    input:
        cram_file='cram/{sample}.bam.cram',
        read_groups=identify_read_groups('cram/{sample}.bam.cram')

Он может иметь два или более подстановочных знака (почему мы любим Snakemake). Вы можете получить доступ к подстановочным знакам позже в директиве python «run» через объект подстановочных знаков, так как похоже, что вы захотите использовать их для каждого цикла. Я думаю, что подстановочные знаки ввода и вывода должны совпадать, так что, может быть, попробуйте и так.

rule split_cram_by_rg:
    input:
        'cram/{sample}.bam.cram'
    output:
        expand('cram/RG_bams/{{sample}}.RG{read_groups}.bam', read_groups=myFunctionCall(BecauseINeededToPass, ACoupleArgs))
    ...
    params:
          rg=myFunctionCall(BecauseINeededToPass, ACoupleArgs)
    run:
        command = 'Just an example ' +  + str(params.rg)

Опять же, не очень уверен, что вы пытаетесь сделать, я не уверен, что мне нравится идея вызова функции дважды, но эй, она будет работать; P Также обратите внимание на использование подстановочного знака «образец» во входной директиве в строке {} и в директиве вывода в расширении {{}}.

Пример доступа к подстановочным знакам в вашей директиве запуска

Пример вызовов функций в местах, о которых вы даже не догадывались. Я взял поля VCF, но это могло быть что угодно. Я использую здесь внешний файл конфигурации.

person TBoyarski    schedule 13.10.2017
comment
Спасибо, но я все еще в замешательстве. Мой входной файл - это один файл с зубьями. Файл cram имеет несколько групп чтения (потому что он упорядочен по нескольким дорожкам). Я хочу разбить файл cram на несколько файлов, по одному для каждой уникальной группы чтения. Я не могу заставить это работать. - person David M; 13.10.2017
comment
Да, здесь много всего происходит, и я не уверен, что вам это нужно. Конечный результат, который вы хотите, похож на имя образца файла для каждой соответствующей группы чтения в файле cram? - person TBoyarski; 13.10.2017
comment
Да .... Я думаю, это должно быть просто, но я продолжаю пытаться использовать функцию в выводе для генерации имен собственных, и кажется, что это не то, как вы должны делать Snakemake. Но я не могу указать группы чтения в качестве подстановочных знаков, так как я не знаю, что это за группы чтения, пока не прочту входной файл. - person David M; 13.10.2017
comment
Просто нужно немного больше ясности. Откуда вы берете свой список образцов имен? И группы чтения для любого данного образца определяются путем чтения заголовочного файла этого файла, правильно? IE, если файл заголовка не имеет этой группы чтения, не создавайте этот образец конкретного файла для этой группы чтения. - person TBoyarski; 13.10.2017
comment
Список образцов имен (вероятно) будет получен при использовании глобуса в папке. Вы правы насчет того, как работают группы чтения. - person David M; 13.10.2017
comment
Похоже, это аналогичная проблема. К сожалению, с некрасивыми ответами. Нет элегантного способа решить такую ​​проблему в snakemake? groups.google.com/forum / #! searchin / snakemake / - person David M; 13.10.2017