Я постараюсь ответить на ваш вопрос B и приведу дополнительные подробности, которые, я надеюсь, могут быть полезны вам и другим.
Редактировать: в конце я добавил несколько попыток ответить на вопрос С.
Относительно кавычек
Во-первых, что касается того, что вы называете «кавычками», их обычно называют «одинарными кавычками», и они используются в python для построения строк. Двойные кавычки также могут быть использованы для той же цели. Основное отличие заключается в том, что вы пытаетесь создать строки, содержащие кавычки. Использование двойных кавычек позволяет создавать строки, содержащие одинарные кавычки, и наоборот. В противном случае вам нужно «экранировать» кавычку, используя обратную косую черту («\»):
s1 = 'Contains "double quotes"'
s1_bis = "Contains \"double quotes\""
s2 = "Contains 'single quotes'"
s2_bis = 'Contains \'single quotes\''
(Я предпочитаю двойные кавычки, это просто личный вкус.)
Разобрать пример
rule trim_galore_pe:
input:
sample=lambda wildcards: expand(f"{config['samples'][wildcards.sample]}_{{num}}.fastq.gz", num=[1,2])
Вы присваиваете функцию (lambda wildcards: ...
) переменной (sample
), которая принадлежит к входному разделу правила.
Это заставит snakemake использовать эту функцию, когда дело доходит до определения ввода конкретного экземпляра правила, основываясь на текущих значениях подстановочных знаков (исходя из текущего значения вывода, которое он хочет сгенерировать).
Для ясности, вполне вероятно, что это можно переписать, отделив определение функции от объявления правила, без использования конструкции lambda
, и она будет работать идентично:
def determine_sample(wildcards):
return expand(
f"{config['samples'][wildcards.sample]}_{{num}}.fastq.gz",
num=[1,2])
rule trim_galore_pe:
input:
sample = determine_sample
expand
- это функция, специфичная для snakemake (но вы можете импортировать ее в любую программу на python или в интерактивный интерпретатор с from snakemake.io import expand
), которая упрощает создание списков строк. В следующем интерактивном сеансе python3.6 мы попытаемся воспроизвести то, что происходит, когда вы его используете, используя различные нативные конструкции python.
Доступ к конфигурации
# We'll try to see how `expand` works, we can import it from snakemake
from snakemake.io import expand
# We want to see how it works using the following example
# expand(f"{config['samples'][wildcards.sample]}_{{num}}.fastq.gz", num=[1,2])
# To make the example work, we will first simulate the reading
# of a configuration file
import yaml
config_text = """
samples:
Corces2016_4983.7A_Mono: fastq_files/SRR2920475
Corces2016_4983.7B_Mono: fastq_files/SRR2920476
cell_types:
Mono:
- Corces2016_4983.7A
index: /home/genomes_and_index_files/hg19
"""
# Here we used triple quotes, to have a readable multi-line string.
# The following is equivalent to what snakemake does with the configuration file:
config = yaml.load(config_text)
config
Выход:
{'cell_types': {'Mono': ['Corces2016_4983.7A']},
'index': '/home/genomes_and_index_files/hg19',
'samples': {'Corces2016_4983.7A_Mono': 'fastq_files/SRR2920475',
'Corces2016_4983.7B_Mono': 'fastq_files/SRR2920476'}}
Мы получили словарь, в котором ключ «samples» связан с вложенным словарем.
# We can access the nested dictionary as follows
config["samples"]
# Note that single quotes could be used instead of double quotes
# Python interactive interpreter uses single quotes when it displays strings
Выход:
{'Corces2016_4983.7A_Mono': 'fastq_files/SRR2920475',
'Corces2016_4983.7B_Mono': 'fastq_files/SRR2920476'}
# We can access the value corresponding to one of the keys
# again using square brackets
config["samples"]["Corces2016_4983.7A_Mono"]
Выход:
'fastq_files/SRR2920475'
# Now, we will simulate a `wildcards` object that has a `sample` attribute
# We'll use a namedtuple for that
# https://docs.python.org/3/library/collections.html#collections.namedtuple
from collections import namedtuple
Wildcards = namedtuple("Wildcards", ["sample"])
wildcards = Wildcards(sample="Corces2016_4983.7A_Mono")
wildcards.sample
Выход:
'Corces2016_4983.7A_Mono'
Редактировать (15/11/2018) : Я нашел лучший способ создания подстановочных знаков:
from snakemake.io import Wildcards
wildcards = Wildcards(fromdict={"sample": "Corces2016_4983.7A_Mono"})
# We can use this attribute as a key in the nested dictionary
# instead of using directly the string
config["samples"][wildcards.sample]
# No quotes here: `wildcards.sample` is a string variable
Выход:
'fastq_files/SRR2920475'
Деконструкция expand
# Now, the expand of the example works, and it results in a list with two strings
expand(f"{config['samples'][wildcards.sample]}_{{num}}.fastq.gz", num=[1,2])
# Note: here, single quotes are used for the string "sample",
# in order not to close the opening double quote of the whole string
Выход:
['fastq_files/SRR2920475_1.fastq.gz', 'fastq_files/SRR2920475_2.fastq.gz']
# Internally, I think what happens is something similar to the following:
filename_template = f"{config['samples'][wildcards.sample]}_{{num}}.fastq.gz"
# This template is then used for each element of this "list comprehension"
[filename_template.format(num=num) for num in [1, 2]]
Выход:
['fastq_files/SRR2920475_1.fastq.gz', 'fastq_files/SRR2920475_2.fastq.gz']
# This is equivalent to building the list using a for loop:
filenames = []
for num in [1, 2]:
filename = filename_template.format(num=num)
filenames.append(filename)
filenames
Выход:
['fastq_files/SRR2920475_1.fastq.gz', 'fastq_files/SRR2920475_2.fastq.gz']
Строковые шаблоны и форматирование
# It is interesting to have a look at `filename_template`
filename_template
Выход:
'fastq_files/SRR2920475_{num}.fastq.gz'
# The part between curly braces can be substituted
# during a string formatting operation:
"fastq_files/SRR2920475_{num}.fastq.gz".format(num=1)
Выход:
'fastq_files/SRR2920475_1.fastq.gz'
Теперь давайте подробнее покажем, как можно использовать форматирование строки.
# In python 3.6 and above, one can create formatted strings
# in which the values of variables are interpreted inside the string
# if the string is prefixed with `f`.
# That's what happens when we create `filename_template`:
filename_template = f"{config['samples'][wildcards.sample]}_{{num}}.fastq.gz"
filename_template
Выход:
'fastq_files/SRR2920475_{num}.fastq.gz'
При форматировании строки произошли две замены:
Значение config['samples'][wildcards.sample]
использовалось для создания первой части строки. (Одиночные кавычки использовались около sample
, потому что это выражение Python находилось внутри строки, построенной из двойных кавычек.)
Двойные скобки вокруг num
были уменьшены до единичных в рамках операции форматирования. Вот почему мы можем использовать это снова в дальнейших операциях форматирования, включая num
.
# Equivalently, without using 3.6 syntax:
filename_template = "{filename_prefix}_{{num}}.fastq.gz".format(
filename_prefix = config["samples"][wildcards.sample])
filename_template
Выход:
'fastq_files/SRR2920475_{num}.fastq.gz'
# We could achieve the same by first extracting the value
# from the `config` dictionary
filename_prefix = config["samples"][wildcards.sample]
filename_template = f"{filename_prefix}_{{num}}.fastq.gz"
filename_template
Выход:
'fastq_files/SRR2920475_{num}.fastq.gz'
# Or, equivalently:
filename_prefix = config["samples"][wildcards.sample]
filename_template = "{filename_prefix}_{{num}}.fastq.gz".format(
filename_prefix=filename_prefix)
filename_template
Выход:
'fastq_files/SRR2920475_{num}.fastq.gz'
# We can actually perform string formatting on several variables
# at the same time:
filename_prefix = config["samples"][wildcards.sample]
num = 1
"{filename_prefix}_{num}.fastq.gz".format(
filename_prefix=filename_prefix, num=num)
Выход:
'fastq_files/SRR2920475_1.fastq.gz'
# Or, using 3.6 formatted strings
filename_prefix = config["samples"][wildcards.sample]
num = 1
f"{filename_prefix}_{num}.fastq.gz"
Выход:
'fastq_files/SRR2920475_1.fastq.gz'
# We could therefore build the result of the expand in a single step:
[f"{config['samples'][wildcards.sample]}_{num}.fastq.gz" for num in [1, 2]]
Выход:
['fastq_files/SRR2920475_1.fastq.gz', 'fastq_files/SRR2920475_2.fastq.gz']
Комментарии к вопросу C
Следующее немного сложнее с точки зрения того, как Python будет строить строку:
input:
lambda wildcards: expand(f"fastq_files/{config['samples'][wildcards.sample]}_{{num}}.fastq.gz", num=[1,2])
Но это должно работать, как мы можем видеть в следующей симуляции:
from collections import namedtuple
from snakemake.io import expand
Wildcards = namedtuple("Wildcards", ["sample"])
wildcards = Wildcards(sample="Corces2016_4983.7A_Mono")
config = {"samples": {
"Corces2016_4983.7A_Mono": "SRR2920475",
"Corces2016_4983.7B_Mono": "SRR2920476"}}
expand(
f"fastq_files/{config['samples'][wildcards.sample]}_{{num}}.fastq.gz",
num=[1,2])
Выход:
['fastq_files/SRR2920475_1.fastq.gz', 'fastq_files/SRR2920475_2.fastq.gz']
Проблема в правиле trim_galore_pe
на самом деле в его разделе output
: вам не следует использовать {wildcards.sample}
там, а просто {sample}
.
В разделе output
правила вы сообщаете snakemake о том, какими будут атрибуты подстановочных знаков для данного экземпляра правила, путем сопоставления файла, который он хочет получить, с данными шаблонами. Части, соответствующие фигурным скобкам, будут использоваться для установки значений имени соответствующего атрибута.
Например, если snakemake хочет файл с именем "trimmed_fastq_files/Corces2016_4983.7A_Mono_1_val_1.fq.gz"
, он попытается сопоставить его со всеми шаблонами, присутствующими во всех выходных разделах правила, и в конечном итоге найдет этот: "trimmed_fastq_files/{sample}_1_val_1.fq.gz"
К счастью, он сможет сопоставить имя файла с шаблоном, установив соответствие между Corces2016_4983.7A_Mono
и частью {sample}
. Затем он поместит атрибут sample
в локальный экземпляр подстановочных знаков, как если бы я вручную делал следующее:
Wildcards = namedtuple("Wildcards", ["sample"])
wildcards = Wildcards(sample="Corces2016_4983.7A_Mono")
Я не знаю, что именно происходит в snakemake, если вы используете {wildcards.sample}
вместо {wildcards}
, но давайте попробуем с моей платформой моделирования:
Wildcards = namedtuple("Wildcards", ["sample"])
wildcards = Wildcards(wildcards.sample="Corces2016_4983.7A_Mono")
File "<ipython-input-12-c02ce12bff85>", line 1
wildcards = Wildcards(wildcards.sample="Corces2016_4983.7A_Mono")
^
SyntaxError: keyword can't be an expression
А как насчет вашей следующей попытки?
output:
expand(f"trimmed_fastq_files/{config['samples'][wildcards.sample]}_{{num}}_val_{{num}}.fq.gz", num=[1,2]),
Здесь я понимаю, что Python сначала пытается применить форматирование f
к f"trimmed_fastq_files/{config['samples'][wildcards.sample]}_{{num}}_val_{{num}}.fq.gz"
.
Для этого ему нужно будет оценить config['samples'][wildcards.sample]
, но объект wildcards
еще не существует. Отсюда wildcards not defined
.
wildcards
будет сгенерировано только после сопоставления имени файла, необходимого для "нижестоящего" правила, со строкой, содержащей шаблоны {attribute_name}
. Но это та строка, которую змеиный в настоящее время пытается построить.
Вот несколько важных моментов, которые следует помнить:
wildcards
на самом деле существуют только локально, в экземпляре правила, после сопоставления его вывода с файлом, который требуется другому «нижестоящему» экземпляру правила.
- Вы не определяете переменные во входных секциях. Вы используете переменные для построения конкретных имен файлов, которые понадобятся экземпляру правила (или, точнее, того, что вы говорите, что хотите существовать до того, как экземпляр правила может быть запущен: правилу на самом деле не нужно использовать эти файлы). Эти переменные определены вне области действия правил, непосредственно на уровне земли файла змеи, в чистом режиме Python и локальном объекте
wildcards
. По умолчанию заполнители {attribute_name}
будут заменены атрибутами локального объекта wildcards
("{sample}"
становится "Corces2016_4983.7A_Mono"
), но если вы хотите сделать более сложные вещи для построения имен файлов, вам нужно сделать это через функцию, которая должна явно обрабатывать этот объект wildcards
(lambda wildcards: f"{wildcards.sample}"
становится "Corces2016_4983.7A_Mono"
).