Jak ustawić dane wejściowe Snakemake jako opcjonalne, ale nie puste?

Dec 10 2020

Buduję skrypt SQL z danych tekstowych. (Część) skryptu składa się z CREATE TABLEoświadczenia i opcjonalnego INSERT INTOoświadczenia. Wartości dla INSERT INTOinstrukcji są pobierane z listy plików, każdy może istnieć lub nie; wszystkie wartości istniejących plików są scalane. Najważniejsze jest to, że INSERT INTOwyciąg należy pominąć, gdy nie ma jednego pliku danych.

Stworzyłem skrypt w Snakemake, który to robi. Istnieją dwie niejednoznaczne reguły, które tworzą skrypt: ta, która tworzy skrypt dla pustych danych, i ta, która tworzy tabelę, ale wstawia dane (niejednoznaczność jest rozwiązywana za pomocą ruleorderinstrukcji).

Ciekawostką jest reguła, która łączy wartości z plików danych. Tworzy dane wyjściowe za każdym razem, gdy obecne jest co najmniej jedno wejście, a tej zasady nie należy traktować inaczej. Istnieją dwie trudności: uczynić każde wejście opcjonalnym i uniemożliwić Snakemake używanie tej reguły, gdy nie ma żadnych plików. Zrobiłem to podstępem:

def require_at_least_one(filelist):
    existing = [file for file in filelist if os.path.isfile(file)]
    return existing if len(existing) else "non_existing_file"

rule merge_values:
    input: require_at_least_one(expand("path_to_data/{dataset}/values", dataset=["A", "B", "C"]))
    output: ...
    shell: ...

require_at_least_oneFunkcja przyjmuje listę nazw plików i odfiltrowuje te nazwy plików, które nie reprezentują pliku. Dzięki temu każde wejście może być opcjonalne. W przypadku narożnika, gdy żaden plik nie istnieje, ta funkcja zwraca specjalną wartość, która reprezentuje nieistniejący plik. Pozwala to na przycięcie tej gałęzi i preferowanie tej, która tworzy skrypt bez INSERTinstrukcji.

Mam ochotę wymyślić na nowo koło, a ponadto sztuczka „non_existing_file” wygląda na trochę brudną. Czy w Snakemake są na to lepsze i idiomatyczne sposoby?

Odpowiedzi

silence Feb 11 2021 at 16:24

moim rozwiązaniem byłoby coś w rodzaju, że nie powinieneś zmuszać snakemake do używania lub nie używania reguły wewnątrz reguły, ale określ, które wyjścia potrzebujesz, a snakemake zdecyduje, czy musi użyć reguły. Więc dla twojego przykładu zrobiłbym coś takiego:

def required_files(filelist):
    return [file for file in filelist if os.path.isfile(file)]

rule what_to_gen:
    input: 
        merged = [] if required_files(expand("path_to_data/{dataset}/values", dataset=["A", "B", "C"])) else 'merged_files.txt'

rule merge_values:
    input: required_files(expand("path_to_data/{dataset}/values", dataset=["A", "B", "C"]))
    output: 'merged_files.txt'
    shell: ...

Spowoduje to wykonanie reguły merge_values ​​tylko wtedy, gdy required_files nie jest pusta.