Dzielenie i Grupowanie¶
Tłumaczenie wspomagane przez AI - dowiedz się więcej i zasugeruj ulepszenia
Nextflow zapewnia potężne narzędzia do elastycznej pracy z danymi. Kluczową możliwością jest dzielenie danych na różne strumienie, a następnie grupowanie powiązanych elementów z powrotem. Jest to szczególnie cenne w workflow'ach bioinformatycznych, gdzie trzeba przetwarzać różne typy próbek oddzielnie, a następnie łączyć wyniki do analizy.
Pomyśl o tym jak o sortowaniu poczty: oddzielasz listy według miejsca przeznaczenia, przetwarzasz każdy stos inaczej, a następnie łączysz ponownie elementy idące do tej samej osoby. Nextflow używa specjalnych operatorów do wykonania tego z danymi naukowymi. To podejście jest również powszechnie znane jako wzorzec scatter/gather w obliczeniach rozproszonych i workflow'ach bioinformatycznych.
System kanałów Nextflow jest sercem tej elastyczności. Kanały łączą różne części workflow'u, umożliwiając przepływ danych przez analizę. Możesz utworzyć wiele kanałów z jednego źródła danych, przetwarzać każdy kanał inaczej, a następnie scalać kanały z powrotem, gdy jest to potrzebne. To podejście pozwala projektować workflow'y, które naturalnie odzwierciedlają rozgałęziające się i zbiegające się ścieżki złożonych analiz bioinformatycznych.
Cele szkolenia¶
W tej misji pobocznej nauczysz się dzielić i grupować dane przy użyciu operatorów kanałów Nextflow. Zaczniemy od pliku CSV zawierającego informacje o próbkach i powiązanych plikach danych, a następnie będziemy manipulować i reorganizować te dane.
Pod koniec tej misji pobocznej będziesz w stanie efektywnie rozdzielać i łączyć strumienie danych, używając następujących technik:
- Odczytywanie danych z plików za pomocą
splitCsv - Filtrowanie i transformowanie danych za pomocą
filterimap - Łączenie powiązanych danych za pomocą
joinigroupTuple - Tworzenie kombinacji danych za pomocą
combinedo przetwarzania równoległego - Optymalizacja struktury danych za pomocą
subMapi strategii deduplikacji - Budowanie funkcji wielokrotnego użytku z nazwanymi domknięciami, które pomogą Ci manipulować strukturami kanałów
Te umiejętności pomogą Ci budować workflow'y, które mogą efektywnie obsługiwać wiele plików wejściowych i różne typy danych, zachowując czystą, łatwą w utrzymaniu strukturę kodu.
Wymagania wstępne¶
Przed podjęciem się tej misji pobocznej powinieneś:
- Ukończyć tutorial Hello Nextflow lub równoważny kurs dla początkujących.
- Swobodnie posługiwać się podstawowymi koncepcjami i mechanizmami Nextflow (procesy, kanały, operatory, praca z plikami, metadane)
Opcjonalnie: Zalecamy najpierw ukończenie misji pobocznej Metadane w workflow'ach.
Obejmuje ona podstawy odczytu plików CSV za pomocą splitCsv i tworzenia map meta, których będziemy tu intensywnie używać.
0. Rozpoczęcie pracy¶
Otwórz codespace szkoleniowy¶
Jeśli jeszcze tego nie zrobiłeś, upewnij się, że otworzysz środowisko szkoleniowe zgodnie z opisem w Konfiguracja środowiska.
Przejdź do katalogu projektu¶
Przejdźmy do katalogu, w którym znajdują się pliki do tego tutorialu.
Możesz ustawić VSCode, aby skupić się na tym katalogu:
Przejrzyj materiały¶
Znajdziesz główny plik workflow'u i katalog data zawierający arkusz próbek o nazwie samplesheet.csv.
Arkusz próbek zawiera informacje o próbkach od różnych pacjentów, w tym identyfikator pacjenta, numer powtórzenia próbki, typ (normalny lub nowotworowy) oraz ścieżki do hipotetycznych plików danych (które tak naprawdę nie istnieją, ale będziemy udawać, że istnieją).
id,repeat,type,bam
patientA,1,normal,patientA_rep1_normal.bam
patientA,1,tumor,patientA_rep1_tumor.bam
patientA,2,normal,patientA_rep2_normal.bam
patientA,2,tumor,patientA_rep2_tumor.bam
patientB,1,normal,patientB_rep1_normal.bam
patientB,1,tumor,patientB_rep1_tumor.bam
patientC,1,normal,patientC_rep1_normal.bam
patientC,1,tumor,patientC_rep1_tumor.bam
Ten arkusz próbek zawiera osiem próbek od trzech pacjentów (A, B, C).
Dla każdego pacjenta mamy próbki typu tumor (zazwyczaj pochodzące z biopsji guza) lub normal (pobrane ze zdrowej tkanki lub krwi).
Jeśli nie znasz analizy raka, wiedz tylko, że odpowiada to modelowi eksperymentalnemu, który używa sparowanych próbek guz/normalnych do wykonywania analiz kontrastowych.
Dla pacjenta A mamy konkretnie dwa zestawy replikatów technicznych (powtórzeń).
Note
Nie martw się, jeśli nie znasz tego projektu eksperymentalnego, nie jest to kluczowe dla zrozumienia tego tutorialu.
Przejrzyj zadanie¶
Twoim wyzwaniem jest napisanie workflow'u Nextflow, który:
- Odczyta dane próbek z pliku CSV i ustrukturyzuje je za pomocą map meta
- Rozdzieli próbki na różne kanały na podstawie typu (normalny vs nowotworowy)
- Połączy dopasowane pary guz/normalny według identyfikatora pacjenta i numeru replikatu
- Rozdzieli próbki na interwały genomowe do przetwarzania równoległego
- Zgrupuje powiązane próbki z powrotem dla analizy downstream
Reprezentuje to powszechny wzorzec bioinformatyczny, w którym musisz podzielić dane do niezależnego przetwarzania, a następnie ponownie połączyć powiązane elementy dla analizy porównawczej.
Lista kontrolna gotowości¶
Myślisz, że jesteś gotowy do działania?
- Rozumiem cel tego kursu i jego wymagania wstępne
- Mój codespace jest uruchomiony
- Ustawiłem odpowiednio mój katalog roboczy
- Rozumiem zadanie
Jeśli możesz zaznaczyć wszystkie pola, jesteś gotowy do działania.
1. Odczytanie danych próbek¶
1.1. Odczytanie danych próbek za pomocą splitCsv i utworzenie map meta¶
Zacznijmy od odczytania danych próbek za pomocą splitCsv i uporządkowania ich w wzorzec mapy meta. W pliku main.nf zobaczysz, że już zaczęliśmy workflow.
Note
W całym tym tutorialu będziemy używać prefiksu ch_ dla wszystkich zmiennych kanałów, aby wyraźnie wskazać, że są to kanały Nextflow.
Jeśli ukończyłeś misję poboczną Metadane w workflow'ach, rozpoznasz ten wzorzec. Użyjemy splitCsv do odczytu CSV i natychmiastowego ustrukturyzowania danych za pomocą mapy meta, aby oddzielić metadane od ścieżek plików.
Info
Napotkamy dwa różne koncepty nazywane map w tym szkoleniu:
- Struktura danych: Mapa Groovy (odpowiednik słowników/haszy w innych językach), która przechowuje pary klucz-wartość
- Operator kanału: Operator
.map(), który przekształca elementy w kanale
Będziemy wyjaśniać, którą z nich mamy na myśli w kontekście, ale to rozróżnienie jest ważne do zrozumienia podczas pracy z Nextflow.
Zastosuj te zmiany w main.nf:
To łączy operację splitCsv (odczyt CSV z nagłówkami) i operację map (strukturyzowanie danych jako krotek [meta, file]) w jednym kroku. Zastosuj tę zmianę i uruchom pipeline:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [deadly_mercator] DSL2 - revision: bd6b0224e9
[[id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam]
[[id:patientA, repeat:1, type:tumor], patientA_rep1_tumor.bam]
[[id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam]
[[id:patientA, repeat:2, type:tumor], patientA_rep2_tumor.bam]
[[id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam]
[[id:patientB, repeat:1, type:tumor], patientB_rep1_tumor.bam]
[[id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam]
[[id:patientC, repeat:1, type:tumor], patientC_rep1_tumor.bam]
Mamy teraz kanał, w którym każdy element jest krotką [meta, file] - metadane oddzielone od ścieżek plików. Ta struktura pozwala nam dzielić i grupować naszą pracę na podstawie pól metadanych.
2. Filtrowanie i transformowanie danych¶
2.1. Filtrowanie danych za pomocą filter¶
Możemy użyć operatora filter do filtrowania danych na podstawie warunku. Powiedzmy, że chcemy przetwarzać tylko próbki normalne. Możemy to zrobić, filtrując dane na podstawie pola type. Wstawmy to przed operatorem view.
Uruchom workflow ponownie, aby zobaczyć przefiltrowany wynik:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [admiring_brown] DSL2 - revision: 194d61704d
[[id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam]
[[id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam]
[[id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam]
[[id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam]
Pomyślnie przefiltrowaliśmy dane, aby zawierały tylko próbki normalne. Podsumujmy, jak to działa.
Operator filter przyjmuje domknięcie, które jest stosowane do każdego elementu w kanale. Jeśli domknięcie zwróci true, element jest uwzględniany; jeśli zwróci false, element jest wykluczany.
W naszym przypadku chcemy zachować tylko próbki, gdzie meta.type == 'normal'. Domknięcie używa krotki meta,file do odwoływania się do każdej próbki, uzyskuje dostęp do typu próbki za pomocą meta.type i sprawdza, czy jest równy 'normal'.
Osiągamy to za pomocą pojedynczego domknięcia, które wprowadziliśmy powyżej:
| main.nf | |
|---|---|
2.2. Utworzenie oddzielnych przefiltrowanych kanałów¶
Obecnie stosujemy filtr do kanału utworzonego bezpośrednio z CSV, ale chcemy filtrować to na więcej sposobów niż jeden, więc przepiszmy logikę, aby utworzyć oddzielny przefiltrowany kanał dla próbek normalnych:
Uruchom pipeline, aby zobaczyć wyniki:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [trusting_poisson] DSL2 - revision: 639186ee74
[[id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam]
[[id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam]
[[id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam]
[[id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam]
Pomyślnie przefiltrowaliśmy dane i utworzyliśmy oddzielny kanał dla próbek normalnych.
Utwórzmy również przefiltrowany kanał dla próbek nowotworowych:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [maniac_boltzmann] DSL2 - revision: 3636b6576b
Tumor sample: [[id:patientA, repeat:1, type:tumor], patientA_rep1_tumor.bam]
Tumor sample: [[id:patientA, repeat:2, type:tumor], patientA_rep2_tumor.bam]
Normal sample: [[id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam]
Normal sample: [[id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam]
Normal sample: [[id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam]
Normal sample: [[id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam]
Tumor sample: [[id:patientB, repeat:1, type:tumor], patientB_rep1_tumor.bam]
Tumor sample: [[id:patientC, repeat:1, type:tumor], patientC_rep1_tumor.bam]
Rozdzieliliśmy próbki normalne i nowotworowe na dwa różne kanały i użyliśmy domknięcia dostarczonego do view(), aby oznaczyć je inaczej w wyjściu: ch_tumor_samples.view{'Tumor sample: ' + it}.
Wnioski¶
W tej sekcji nauczyłeś się:
- Filtrowanie danych: Jak filtrować dane za pomocą
filter - Dzielenie danych: Jak dzielić dane na różne kanały na podstawie warunku
- Wyświetlanie danych: Jak używać
viewdo wydrukowania danych i oznaczania wyjścia z różnych kanałów
Rozdzieliliśmy teraz próbki normalne i nowotworowe na dwa różne kanały. Następnie połączymy próbki normalne i nowotworowe na polu id.
3. Łączenie kanałów według identyfikatorów¶
W poprzedniej sekcji rozdzieliliśmy próbki normalne i nowotworowe na dwa różne kanały. Mogą one być przetwarzane niezależnie przy użyciu określonych procesów lub workflow'ów na podstawie ich typu. Ale co się dzieje, gdy chcemy porównać próbki normalne i nowotworowe tego samego pacjenta? W tym momencie musimy połączyć je z powrotem, upewniając się, że dopasowujemy próbki na podstawie ich pola id.
Nextflow zawiera wiele metod łączenia kanałów, ale w tym przypadku najbardziej odpowiednim operatorem jest join. Jeśli znasz SQL, działa podobnie do operacji JOIN, gdzie określamy klucz, według którego łączymy, i typ łączenia do wykonania.
3.1. Użyj map i join do łączenia na podstawie identyfikatora pacjenta¶
Jeśli sprawdzimy dokumentację join, zobaczymy, że domyślnie łączy dwa kanały na podstawie pierwszego elementu w każdej krotce.
3.1.1. Sprawdzenie struktury danych¶
Jeśli nie masz jeszcze dostępnego wyjścia konsoli, uruchommy pipeline, aby sprawdzić naszą strukturę danych i zobaczyć, jak musimy ją zmodyfikować, aby połączyć według pola id.
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [maniac_boltzmann] DSL2 - revision: 3636b6576b
Tumor sample: [[id:patientA, repeat:1, type:tumor], patientA_rep1_tumor.bam]
Tumor sample: [[id:patientA, repeat:2, type:tumor], patientA_rep2_tumor.bam]
Normal sample: [[id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam]
Normal sample: [[id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam]
Normal sample: [[id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam]
Normal sample: [[id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam]
Tumor sample: [[id:patientB, repeat:1, type:tumor], patientB_rep1_tumor.bam]
Tumor sample: [[id:patientC, repeat:1, type:tumor], patientC_rep1_tumor.bam]
Widać, że pole id jest pierwszym elementem w każdej mapie meta. Aby join zadziałał, powinniśmy wyodrębnić pole id w każdej krotce. Po tym możemy po prostu użyć operatora join, aby połączyć dwa kanały.
3.1.2. Wyodrębnienie pola id¶
Aby wyodrębnić pole id, możemy użyć operatora map do utworzenia nowej krotki z polem id jako pierwszym elementem.
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [mad_lagrange] DSL2 - revision: 9940b3f23d
Tumor sample: [patientA, [id:patientA, repeat:1, type:tumor], patientA_rep1_tumor.bam]
Tumor sample: [patientA, [id:patientA, repeat:2, type:tumor], patientA_rep2_tumor.bam]
Normal sample: [patientA, [id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam]
Normal sample: [patientA, [id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam]
Tumor sample: [patientB, [id:patientB, repeat:1, type:tumor], patientB_rep1_tumor.bam]
Tumor sample: [patientC, [id:patientC, repeat:1, type:tumor], patientC_rep1_tumor.bam]
Normal sample: [patientB, [id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam]
Normal sample: [patientC, [id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam]
Może to być subtelne, ale powinieneś być w stanie zobaczyć, że pierwszy element w każdej krotce to pole id.
3.1.3. Połączenie dwóch kanałów¶
Teraz możemy użyć operatora join, aby połączyć dwa kanały na podstawie pola id.
Ponownie użyjemy view, aby wydrukować połączone wyjścia.
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [soggy_wiles] DSL2 - revision: 3bc1979889
[patientA, [id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam, [id:patientA, repeat:1, type:tumor], patientA_rep1_tumor.bam]
[patientA, [id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam, [id:patientA, repeat:2, type:tumor], patientA_rep2_tumor.bam]
[patientB, [id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam, [id:patientB, repeat:1, type:tumor], patientB_rep1_tumor.bam]
[patientC, [id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam, [id:patientC, repeat:1, type:tumor], patientC_rep1_tumor.bam]
Trochę trudno to powiedzieć, ponieważ jest tak szerokie, ale powinieneś być w stanie zobaczyć, że próbki zostały połączone według pola id. Każda krotka ma teraz format:
id: Identyfikator próbkinormal_meta_map: Metadane próbki normalnej, w tym typ, replikat i ścieżka do pliku bamnormal_sample_file: Plik próbki normalnejtumor_meta_map: Metadane próbki nowotworowej, w tym typ, replikat i ścieżka do pliku bamtumor_sample: Próbka nowotworowa, w tym typ, replikat i ścieżka do pliku bam
Warning
Operator join odrzuci wszystkie niedopasowane krotki. W tym przykładzie upewniliśmy się, że wszystkie próbki są dopasowane dla guza i normalne, ale jeśli to nie jest prawda, musisz użyć parametru remainder: true, aby zachować niedopasowane krotki. Sprawdź dokumentację po więcej szczegółów.
Więc teraz wiesz, jak używać map do wyodrębnienia pola w krotce i jak używać join do łączenia krotek na podstawie pierwszego pola.
Dzięki tej wiedzy możemy pomyślnie łączyć kanały na podstawie wspólnego pola.
Następnie rozważymy sytuację, w której chcesz łączyć na wielu polach.
3.2. Łączenie na wielu polach¶
Mamy 2 replikaty dla sampleA, ale tylko 1 dla sampleB i sampleC. W tym przypadku mogliśmy je skutecznie połączyć, używając pola id, ale co by się stało, gdyby były nie zsynchronizowane? Moglibyśmy pomylić próbki normalne i nowotworowe z różnych replikatów!
Aby tego uniknąć, możemy łączyć na wielu polach. W rzeczywistości istnieje wiele sposobów, aby to osiągnąć, ale skupimy się na utworzeniu nowego klucza łączącego, który zawiera zarówno id próbki, jak i numer replicate.
Zacznijmy od utworzenia nowego klucza łączącego. Możemy to zrobić w ten sam sposób co wcześniej, używając operatora map do utworzenia nowej krotki z polami id i repeat jako pierwszym elementem.
Teraz powinniśmy zobaczyć, że łączenie odbywa się, ale przy użyciu zarówno pól id, jak i repeat. Uruchom workflow:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [prickly_wing] DSL2 - revision: 3bebf22dee
[[patientA, 1], [id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam, [id:patientA, repeat:1, type:tumor], patientA_rep1_tumor.bam]
[[patientA, 2], [id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam, [id:patientA, repeat:2, type:tumor], patientA_rep2_tumor.bam]
[[patientB, 1], [id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam, [id:patientB, repeat:1, type:tumor], patientB_rep1_tumor.bam]
[[patientC, 1], [id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam, [id:patientC, repeat:1, type:tumor], patientC_rep1_tumor.bam]
Zauważ, jak mamy krotkę dwóch elementów (pola id i repeat) jako pierwszy element każdego połączonego wyniku. To pokazuje, jak złożone elementy mogą być używane jako klucz łączący, umożliwiając dość skomplikowane dopasowywanie między próbkami z tych samych warunków.
Jeśli chcesz zbadać więcej sposobów łączenia na różnych kluczach, sprawdź dokumentację operatora join po dodatkowe opcje i przykłady.
3.3. Użyj subMap do utworzenia nowego klucza łączącego¶
Poprzednie podejście traci nazwy pól z naszego klucza łączącego - pola id i repeat stają się tylko listą wartości. Aby zachować nazwy pól do późniejszego dostępu, możemy użyć metody subMap.
Metoda subMap wyodrębnia tylko określone pary klucz-wartość z mapy. Tutaj wyodrębnimy tylko pola id i repeat, aby utworzyć nasz klucz łączący.
| main.nf | |
|---|---|
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [reverent_wing] DSL2 - revision: 847016c3b7
[[id:patientA, repeat:1], [id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam, [id:patientA, repeat:1, type:tumor], patientA_rep1_tumor.bam]
[[id:patientA, repeat:2], [id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam, [id:patientA, repeat:2, type:tumor], patientA_rep2_tumor.bam]
[[id:patientB, repeat:1], [id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam, [id:patientB, repeat:1, type:tumor], patientB_rep1_tumor.bam]
[[id:patientC, repeat:1], [id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam, [id:patientC, repeat:1, type:tumor], patientC_rep1_tumor.bam]
Teraz mamy nowy klucz łączący, który nie tylko zawiera pola id i repeat, ale także zachowuje nazwy pól, więc możemy uzyskać do nich dostęp później według nazwy, np. meta.id i meta.repeat.
3.4. Użyj nazwanego domknięcia w map¶
Aby uniknąć duplikacji i zmniejszyć błędy, możemy użyć nazwanego domknięcia. Nazwane domknięcie pozwala nam utworzyć funkcję wielokrotnego użytku, którą możemy wywołać w wielu miejscach.
Aby to zrobić, najpierw definiujemy domknięcie jako nową zmienną:
Zdefiniowaliśmy transformację map jako nazwaną zmienną, którą możemy ponownie wykorzystać.
Zauważ, że również konwertujemy ścieżkę pliku na obiekt Path za pomocą file(), aby każdy proces otrzymujący ten kanał mógł prawidłowo obsłużyć plik (po więcej informacji zobacz Praca z plikami).
Zaimplementujmy domknięcie w naszym workflow'u:
| main.nf | |
|---|---|
Note
Operator map zmienił się z używania { } na używanie ( ) do przekazania domknięcia jako argumentu. Dzieje się tak, ponieważ operator map oczekuje domknięcia jako argumentu, a { } jest używane do definiowania anonimowego domknięcia. Podczas wywoływania nazwanego domknięcia użyj składni ( ).
Uruchom workflow jeszcze raz, aby sprawdzić, czy wszystko nadal działa:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [angry_meninsky] DSL2 - revision: 2edc226b1d
[[id:patientA, repeat:1], [id:patientA, repeat:1, type:normal], patientA_rep1_normal.bam, [id:patientA, repeat:1, type:tumor], patientA_rep1_tumor.bam]
[[id:patientA, repeat:2], [id:patientA, repeat:2, type:normal], patientA_rep2_normal.bam, [id:patientA, repeat:2, type:tumor], patientA_rep2_tumor.bam]
[[id:patientB, repeat:1], [id:patientB, repeat:1, type:normal], patientB_rep1_normal.bam, [id:patientB, repeat:1, type:tumor], patientB_rep1_tumor.bam]
[[id:patientC, repeat:1], [id:patientC, repeat:1, type:normal], patientC_rep1_normal.bam, [id:patientC, repeat:1, type:tumor], patientC_rep1_tumor.bam]
Używanie nazwanego domknięcia pozwala nam ponownie wykorzystać tę samą transformację w wielu miejscach, zmniejszając ryzyko błędów i czyniąc kod bardziej czytelnym i łatwiejszym w utrzymaniu.
3.5. Zmniejszenie duplikacji danych¶
Mamy dużo zduplikowanych danych w naszym workflow'u. Każdy element w połączonych próbkach powtarza pola id i repeat. Ponieważ ta informacja jest już dostępna w kluczu grupującym, możemy uniknąć tej redundancji. Jako przypomnienie, nasza obecna struktura danych wygląda tak:
[
[
"id": "sampleC",
"repeat": "1",
],
[
"id": "sampleC",
"repeat": "1",
"type": "normal",
],
"sampleC_rep1_normal.bam"
[
"id": "sampleC",
"repeat": "1",
"type": "tumor",
],
"sampleC_rep1_tumor.bam"
]
Ponieważ pola id i repeat są dostępne w kluczu grupującym, usuńmy je z reszty każdego elementu kanału, aby uniknąć duplikacji. Możemy to zrobić, używając metody subMap do utworzenia nowej mapy z tylko polem type. To podejście pozwala nam zachować wszystkie niezbędne informacje, eliminując redundancję w naszej strukturze danych.
Teraz domknięcie zwraca krotkę, gdzie pierwszy element zawiera pola id i repeat, a drugi element zawiera tylko pole type. To eliminuje redundancję, przechowując informacje id i repeat raz w kluczu grupującym, zachowując jednocześnie wszystkie niezbędne informacje.
Uruchom workflow, aby zobaczyć, jak to wygląda:
Wyjście polecenia
[[id:patientA, repeat:1], [type:normal], /workspaces/training/side-quests/splitting_and_grouping/patientA_rep1_normal.bam, [type:tumor], /workspaces/training/side-quests/splitting_and_grouping/patientA_rep1_tumor.bam]
[[id:patientA, repeat:2], [type:normal], /workspaces/training/side-quests/splitting_and_grouping/patientA_rep2_normal.bam, [type:tumor], /workspaces/training/side-quests/splitting_and_grouping/patientA_rep2_tumor.bam]
[[id:patientB, repeat:1], [type:normal], /workspaces/training/side-quests/splitting_and_grouping/patientB_rep1_normal.bam, [type:tumor], /workspaces/training/side-quests/splitting_and_grouping/patientB_rep1_tumor.bam]
[[id:patientC, repeat:1], [type:normal], /workspaces/training/side-quests/splitting_and_grouping/patientC_rep1_normal.bam, [type:tumor], /workspaces/training/side-quests/splitting_and_grouping/patientC_rep1_tumor.bam]
Widać, że podajemy tylko pola id i repeat raz w kluczu grupującym i mamy pole type w danych próbki. Nie straciliśmy żadnych informacji, ale udało nam się uczynić zawartość naszego kanału bardziej zwięzłą.
3.6. Usunięcie zbędnych informacji¶
Usunęliśmy zduplikowane informacje powyżej, ale nadal mamy inne zbędne informacje w naszych kanałach.
Na początku rozdzieliliśmy próbki normalne i nowotworowe za pomocą filter, a następnie połączyliśmy je na podstawie kluczy id i repeat. Operator join zachowuje kolejność, w jakiej krotki są łączone, więc w naszym przypadku, z próbkami normalnymi po lewej stronie i próbkami nowotworowymi po prawej, wynikowy kanał utrzymuje tę strukturę: id, <elementy normalne>, <elementy nowotworowe>.
Ponieważ znamy pozycję każdego elementu w naszym kanale, możemy dalej uprościć strukturę, usuwając metadane [type:normal] i [type:tumor].
Uruchom ponownie, aby zobaczyć wynik:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [confident_leavitt] DSL2 - revision: a2303895bd
[[id:patientA, repeat:1], patientA_rep1_normal.bam, patientA_rep1_tumor.bam]
[[id:patientA, repeat:2], patientA_rep2_normal.bam, patientA_rep2_tumor.bam]
[[id:patientB, repeat:1], patientB_rep1_normal.bam, patientB_rep1_tumor.bam]
[[id:patientC, repeat:1], patientC_rep1_normal.bam, patientC_rep1_tumor.bam]
Wnioski¶
W tej sekcji nauczyłeś się:
- Manipulowania krotkami: Jak używać
mapdo wyodrębnienia pola w krotce - Łączenia krotek: Jak używać
joindo łączenia krotek na podstawie pierwszego pola - Tworzenia kluczy łączących: Jak używać
subMapdo utworzenia nowego klucza łączącego - Nazwanych domknięć: Jak używać nazwanego domknięcia w map
- Łączenia na wielu polach: Jak łączyć na wielu polach dla bardziej precyzyjnego dopasowania
- Optymalizacji struktury danych: Jak usprawnić strukturę kanału, usuwając zbędne informacje
Masz teraz workflow, który może podzielić arkusz próbek, przefiltrować próbki normalne i nowotworowe, połączyć je według identyfikatora próbki i numeru replikatu, a następnie wydrukować wyniki.
Jest to powszechny wzorzec w workflow'ach bioinformatycznych, gdzie musisz dopasować próbki lub inne typy danych po przetworzeniu niezależnie, więc jest to przydatna umiejętność. Następnie przyjrzymy się powtarzaniu próbki wielokrotnie.
4. Rozpraszanie próbek na interwały¶
Kluczowym wzorcem w workflow'ach bioinformatycznych jest dystrybucja analizy na regiony genomowe. Na przykład wywoływanie wariantów może być zrównoleglone poprzez podzielenie genomu na interwały (jak chromosomy lub mniejsze regiony). Ta strategia zrównoleglenia znacząco poprawia wydajność pipeline'u poprzez dystrybucję obciążenia obliczeniowego na wiele rdzeni lub węzłów, zmniejszając całkowity czas wykonania.
W następnej sekcji pokażemy, jak rozdzielić nasze dane próbek na wiele interwałów genomowych. Sparujemy każdą próbkę z każdym interwałem, umożliwiając równoległe przetwarzanie różnych regionów genomowych. To pomnoży rozmiar naszego zbioru danych przez liczbę interwałów, tworząc wiele niezależnych jednostek analizy, które można później połączyć z powrotem.
4.1. Rozproszenie próbek na interwały za pomocą combine¶
Zacznijmy od utworzenia kanału interwałów. Aby zachować prostotę, użyjemy tylko 3 interwałów, które zdefiniujemy ręcznie. W rzeczywistym workflow'u możesz je odczytać z pliku wejściowego lub nawet utworzyć kanał z wieloma plikami interwałów.
Teraz pamiętaj, chcemy powtórzyć każdą próbkę dla każdego interwału. Jest to czasami określane jako iloczyn kartezjański próbek i interwałów. Możemy to osiągnąć za pomocą operatora combine. Weźmie każdy element z kanału 1 i powtórzy go dla każdego elementu w kanale 2. Dodajmy operator combine do naszego workflow'u:
Teraz uruchommy to i zobaczmy, co się stanie:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [mighty_tesla] DSL2 - revision: ae013ab70b
[[id:patientA, repeat:1], patientA_rep1_normal.bam, patientA_rep1_tumor.bam, chr1]
[[id:patientA, repeat:1], patientA_rep1_normal.bam, patientA_rep1_tumor.bam, chr2]
[[id:patientA, repeat:1], patientA_rep1_normal.bam, patientA_rep1_tumor.bam, chr3]
[[id:patientA, repeat:2], patientA_rep2_normal.bam, patientA_rep2_tumor.bam, chr1]
[[id:patientA, repeat:2], patientA_rep2_normal.bam, patientA_rep2_tumor.bam, chr2]
[[id:patientA, repeat:2], patientA_rep2_normal.bam, patientA_rep2_tumor.bam, chr3]
[[id:patientB, repeat:1], patientB_rep1_normal.bam, patientB_rep1_tumor.bam, chr1]
[[id:patientB, repeat:1], patientB_rep1_normal.bam, patientB_rep1_tumor.bam, chr2]
[[id:patientB, repeat:1], patientB_rep1_normal.bam, patientB_rep1_tumor.bam, chr3]
[[id:patientC, repeat:1], patientC_rep1_normal.bam, patientC_rep1_tumor.bam, chr1]
[[id:patientC, repeat:1], patientC_rep1_normal.bam, patientC_rep1_tumor.bam, chr2]
[[id:patientC, repeat:1], patientC_rep1_normal.bam, patientC_rep1_tumor.bam, chr3]
Sukces! Powtórzyliśmy każdą próbkę dla każdego pojedynczego interwału na naszej liście 3 interwałów. Skutecznie potroiliśmy liczbę elementów w naszym kanale.
Trochę trudno to przeczytać, więc w następnej sekcji to uporządkujemy.
4.2. Organizacja kanału¶
Możemy użyć operatora map do uporządkowania i refaktoryzacji naszych danych próbek, aby były łatwiejsze do zrozumienia. Przenieśmy ciąg interwałów do mapy łączącej na pierwszym elemencie.
Rozłóżmy, co ta operacja map robi krok po kroku.
Po pierwsze, używamy nazwanych parametrów, aby kod był bardziej czytelny. Używając nazw grouping_key, normal, tumor i interval, możemy odwoływać się do elementów w krotce według nazwy zamiast według indeksu:
Następnie łączymy grouping_key z polem interval. grouping_key jest mapą zawierającą pola id i repeat. Tworzymy nową mapę z interval i scalamy je za pomocą dodawania map Groovy (+):
Na koniec zwracamy to jako krotkę z trzema elementami: połączoną mapą metadanych, plikiem próbki normalnej i plikiem próbki nowotworowej:
Uruchommy to ponownie i sprawdźmy zawartość kanału:
Wyjście polecenia
N E X T F L O W ~ version 25.10.2
Launching `main.nf` [sad_hawking] DSL2 - revision: 1f6f6250cd
[[id:patientA, repeat:1, interval:chr1], patientA_rep1_normal.bam, patientA_rep1_tumor.bam]
[[id:patientA, repeat:1, interval:chr2], patientA_rep1_normal.bam, patientA_rep1_tumor.bam]
[[id:patientA, repeat:1, interval:chr3], patientA_rep1_normal.bam, patientA_rep1_tumor.bam]
[[id:patientA, repeat:2, interval:chr1], patientA_rep2_normal.bam, patientA_rep2_tumor.bam]
[[id:patientA, repeat:2, interval:chr2], patientA_rep2_normal.bam, patientA_rep2_tumor.bam]
[[id:patientA, repeat:2, interval:chr3], patientA_rep2_normal.bam, patientA_rep2_tumor.bam]
[[id:patientB, repeat:1, interval:chr1], patientB_rep1_normal.bam, patientB_rep1_tumor.bam]
[[id:patientB, repeat:1, interval:chr2], patientB_rep1_normal.bam, patientB_rep1_tumor.bam]
[[id:patientB, repeat:1, interval:chr3], patientB_rep1_normal.bam, patientB_rep1_tumor.bam]
[[id:patientC, repeat:1, interval:chr1], patientC_rep1_normal.bam, patientC_rep1_tumor.bam]
[[id:patientC, repeat:1, interval:chr2], patientC_rep1_normal.bam, patientC_rep1_tumor.bam]
[[id:patientC, repeat:1, interval:chr3], patientC_rep1_normal.bam, patientC_rep1_tumor.bam]
Używanie map do wymuszenia, aby dane miały poprawną strukturę, może być trudne, ale jest kluczowe