Parte 2: Hello Channels¶
Nella parte 1 di questo corso (Hello World), ti abbiamo mostrato come fornire un input variabile a un processo fornendo direttamente l'input nella chiamata di processo: sayHello(params.greet)
.
Era un approccio volutamente semplificato.
In pratica, questo approccio ha grandi limitazioni; vale a dire che funziona solo per casi molto semplici in cui vogliamo eseguire il processo solo una volta, su un singolo valore.
Nella maggior parte dei casi d'uso del workflow realistici, vogliamo elaborare più valori (dati sperimentali per più campioni, ad esempio), quindi abbiamo bisogno di un modo più sofisticato per gestire gli input.
Ecco a cosa servono i canali di Nextflow. I canali sono code progettate per gestire gli input in modo efficiente e spostarli da un passaggio all'altro in workflows in più fasi, fornendo parallelismo integrato e molti vantaggi aggiuntivi.
In questa parte del corso, imparerai come utilizzare un canale per gestire più input da una varietà di fonti diverse. Imparerai anche a usare operatori per trasformare i contenuti del canale secondo necessità.
_Per la formazione sull'uso dei canali per collegare i passaggi in un workflow multi-step, vedere la parte 3 di questo corso. _
0. Riscaldamento: esegui hello-channels.nf
¶
Useremo lo script del workflow hello-channels.nf
come punto di partenza.
È equivalente allo script prodotto lavorando attraverso la Parte 1 di questo corso di formazione.
Solo per assicurarti che tutto funzioni, esegui lo script una volta prima di apportare qualsiasi modifica:
N E X T F L O W ~ version 24.10.0
Launching `hello-channels.nf` [insane_lichterman] DSL2 - revision: c33d41f479
executor > local (1)
[86/9efa08] sayHello | 1 of 1 ✔
Come in precedenza, troverai il file di output denominato output.txt
nella directory results
(specificata dalla direttiva publishDir
).
results/output.txt | |
---|---|
Se ha funzionato per te, sei pronto a conoscere i canali.
1. Fornire input variabili tramite un canale esplicitamente¶
Creeremo un canale per passare l'input variabile al processo sayHello()
invece di fare affidamento sulla gestione implicita, che ha alcune limitazioni.
1.1. Crea un canale di input¶
Ci sono una varietà di fabbriche di canali che possiamo utilizzare per creare un canale.
Per mantenere le cose semplici per ora, useremo la fabbrica di canale più semplice, chiamata Channel.of
, che creerà un canale contenente un singolo valore.
Funzionalmente questo sarà simile a come lo avevamo impostato prima, ma invece di avere Nextflow a creare implicitamente un canale, lo stiamo facendo esplicitamente ora.
Questa è la riga di codice che useremo:
Questo crea un canale chiamato greeting_ch
usando la fabbrica del canale Channel.of()
, che imposta un semplice canale di coda e carica la stringa 'Hello Channels!'
da usare come valore di saluto.
Note
Stiamo temporaneamente tornando alle stringhe codificate invece di utilizzare un parametro CLI per motivi di leggibilità. Torneremo a utilizzare i parametri CLI una volta che avremo coperto ciò che sta accadendo a livello del canale.
Nel blocco del workflow, aggiungi il codice di fabbrica del canale:
Questo non è ancora funzionale poiché non abbiamo ancora cambiato l'input alla chiamata di processo.
1.2. Aggiungi il canale come input alla chiamata di processo¶
Ora dobbiamo effettivamente collegare il nostro canale appena creato alla chiamata di processo sayHello()
, sostituendo il parametro CLI che stavamo fornendo direttamente prima.
Nel blocco del workflow, apportare la seguente modifica del codice:
Questo dice a Nextflow di eseguire il processo sayHello
sui contenuti del canale greeting_ch
.
Ora il nostro workflow è correttamente funzionale; è l'equivalente esplicito di scrivere sayHello('Hello Channels!')
.
1.3. Esegui di nuovo il comando del workflow¶
Eseguiamolo!
Se hai apportato entrambe le modifiche correttamente, dovresti ottenere un'altra esecuzione riuscita:
Output | |
---|---|
Puoi controllare la directory dei risultati per assicurarti che il risultato sia ancora lo stesso di prima.
results/output.txt | |
---|---|
Finora stiamo solo modificando progressivamente il codice per aumentare la flessibilità del nostro workflow ottenendo allo stesso tempo lo stesso risultato finale.
Note
Può sembrare che stiamo scrivendo più codice senza alcun beneficio tangibile, ma il valore diventerà chiaro non appena inizieremo a gestire più input.
Conclusioni¶
Sai come utilizzare una fabbrica di canale di base per fornire un input a un processo.
Cosa c'è dopo?¶
Scopri come utilizzare i canali per far in modo che il workflow iteri su più valori di input.
2. Modifica il workflow per eseguire su più valori di input¶
I workflows in genere vengono eseguiti su lotti di input che devono essere elaborati in blocco, quindi vogliamo aggiornare il workflow per accettare più valori di input.
2.1. Carica più saluti nel canale di input¶
Convenientemente, la fabbrica di canali Channel.of()
che abbiamo utilizzato è abbastanza felice di accettare più di un valore, quindi non abbiamo affatto bisogno di modificarlo.
Dobbiamo solo caricare più valori nel canale.
2.1.1. Aggiungi altri saluti¶
Nel blocco del workflow, apportare la seguente modifica del codice:
La documentazione ci dice che questo dovrebbe funzionare. Può davvero essere così semplice?
2.1.2. Esegui il comando e guarda l'output del registro¶
Proviamolo.
Certamente sembra funzionare bene:
Output | |
---|---|
Tuttavia... Questo sembra indicare che sono state effettuate chiamate "3 di 3" per il processo, il che è incoraggiante, ma questo ci mostra solo una singola esecuzione del processo, con un percorso di sottodirectory (cd/77a81f
).
Cosa sta succedendo?
Per impostazione predefinita, il sistema di registrazione ANSI scrive la registrazione da più chiamate allo stesso processo sulla stessa linea. Fortunatamente, possiamo disabilitare quel comportamento per vedere l'elenco completo delle chiamate di processo.
2.1.3. Esegui di nuovo il comando con l'opzione -ansi-log false
¶
Per espandere la registrazione per visualizzare una riga per chiamata di processo, aggiungi -ansi-log false
al comando.
Questa volta vediamo tutte e tre le esecuzioni di processo e le loro sottodirectory di lavoro associate elencate nell'output:
Output | |
---|---|
È molto meglio; almeno per un semplice workflow.
Per un workflow complesso o un gran numero di input, avere l'output completo dell'elenco sul terminale potrebbe diventare un po' travolgente, quindi potresti non scegliere di usare -ansi-log false
in quei casi.
Note
Il modo in cui viene riportato lo stato è un po' diverso tra le due modalità di registrazione. In modalità condensata, Nextflow segnala se le chiamate sono state completate con successo o meno. In questa modalità espansa, riporta solo che sono stati inviati.
Detto questo, abbiamo un altro problema. Se guardi nella directory results
, c'è solo un file: output.txt
!
Che succede? Non dovremmo aspettarci un file separato per saluto di input, quindi tre file in tutto? Tutti e tre i saluti sono andati in un unico file?
Puoi controllare il contenuto di output.txt
; troverai solo uno dei tre, contenente uno dei tre saluti che abbiamo fornito.
output.txt | |
---|---|
Potresti ricordare che abbiamo codificato il nome del file di output per il processo sayHello
, quindi tutte e tre le chiamate hanno prodotto un file chiamato output.txt
.
Puoi controllare le sottodirectory di lavoro per ciascuno dei tre processi; ognuno di essi contiene un file chiamato output.txt
come previsto.
Finché i file di output rimangono lì, isolati dagli altri processi, va bene.
Ma quando la direttiva publishDir
copia ciascuno di essi nella stessa directory results
, qualunque sia stato copiato lì per primo viene sovrascritto da quello successivo, e così via.
2.2. Assicurati che i nomi dei file di output siano univoci¶
Possiamo continuare a pubblicare tutti gli output nella stessa directory dei risultati, ma dobbiamo assicurarci che abbiano nomi univoci. In particolare, dobbiamo modificare il primo processo per generare un nome di file in modo dinamico in modo che i nomi dei file finali siano unici.
Quindi, come facciamo a rendere unici i nomi dei file? Un modo comune per farlo è utilizzare un pezzo unico di metadati dagli input (ricevuti dal canale di input) come parte del nome del file di output. Qui, per comodità, useremo solo il saluto stesso poiché è solo una stringa breve e lo antemeremo al nome del file di output di base.
2.2.1. Costruisci un nome di file di output dinamico¶
Nel blocco di processo, apporta le seguenti modifiche al codice:
Assicurati di sostituire output.txt
sia nella definizione di output che nel blocco di comando script:
.
Tip
Nella definizione di output, DEVI usare virgolette doppie attorno all'espressione del nome del file di output (NON virgolette singole), altrimenti fallirà.
Questo dovrebbe produrre un nome di file di output univoco ogni volta che viene chiamato il processo, in modo che possa essere distinto dagli output di altre iterazioni dello stesso processo nella directory di output.
2.2.2. Esegui il workflow¶
Eseguiamolo:
Tornando alla vista di riepilogo, l'output ha di nuovo questo aspetto:
Output | |
---|---|
È importante sottolineare che ora abbiamo tre nuovi file oltre a quello che avevamo già nella directory risultati
:
results
├── Bonjour-output.txt
├── Hello-output.txt
├── Holà-output.txt
└── output.txt
Ognuno di loro ha i contenuti previsti:
Bonjour-output.txt | |
---|---|
Hello-output.txt | |
---|---|
Holà-output.txt | |
---|---|
Successo! Ora possiamo aggiungere tutti i saluti che vorremmo senza preoccuparci che i file di output vengano sovrascritti.
Note
In pratica, nominare i file in base ai dati di input stessi è quasi sempre impraticabile. Il modo migliore per generare nomi di file dinamici è passare i metadati a un processo insieme ai file di input. I metadati sono in genere forniti tramite un "foglio campione" o equivalenti. Imparerai come farlo più avanti nel tuo corso di formazione Nextflow.
Conclusioni¶
Sai come alimentare più elementi di input attraverso un canale.
Cosa c'è dopo?¶
Impara a usare un operatore per trasformare i contenuti di un canale.
3. Usa un operatore per trasformare il contenuto di un canale¶
In Nextflow, operatori ci consentono di trasformare il contenuto di un canale.
Ti abbiamo appena mostrato come gestire più elementi di input che sono stati codificati direttamente nella fabbrica del canale. E se volessimo fornire quegli input multipli in una forma diversa?
Ad esempio, immagina di impostare una variabile di input contenente una serie di elementi come questa:
greetings_array = ['Ciao','Bonjour','Holà']
Possiamo caricarlo nel nostro canale di output e aspettarci che funzioni? Scopriamolo.
3.1. Fornire una serie di valori come input al canale¶
Il buon senso suggerisce che dovremmo essere in grado di passare semplicemente una serie di valori invece di un singolo valore. Giusto?
3.1.1. Imposta la variabile di input¶
Prendiamo la variabile greetings_array
che abbiamo appena immaginato e rendiamola una realtà aggiungendola al blocco del workflow:
3.1.2. Imposta l'array di saluti come input alla fabbrica del canale¶
Sostituiremo i valori 'Hello','Bonjour', 'Holà'
attualmente codificati nella fabbrica del canale con il greetings_array
che abbiamo appena creato.
Nel blocco del workflow, apportare la seguente modifica:
3.1.3. Esegui il workflow¶
Proviamo a eseguire questo:
Oh no! Nextflow genera un errore che inizia così:
Sembra che Nextflow abbia tentato di eseguire una singola chiamata di processo, usando [Hello, Bonjour, Holà]
come valore di stringa, invece di utilizzare le tre stringhe nell'array come valori separati.
Come facciamo a ottenere Nextflow per decomprimere l'array e caricare le singole stringhe nel canale?
3.2. Usa un operatore per trasformare i contenuti del canale¶
È qui che entrano in gioco gli operatori.
Se sfogli elenco di operatori nella documentazione di Nextflow, troverai flatten()
, che fa esattamente ciò di cui abbiamo bisogno: decomprimere il contenuto di un array e li emette come singoli elementi.
Note
È tecnicamente possibile ottenere gli stessi risultati utilizzando una fabbrica di canali diversa, Channel.fromList
, che include una fase di mappatura implicita nella sua operazione.
Qui abbiamo scelto di non usarlo per dimostrare l'uso di un operatore su un caso d'uso abbastanza semplice.
3.2.1. Aggiungi l'operatore flatten()
¶
Per applicare l'operatore flatten()
al nostro canale di input, lo apponiamo alla dichiarazione di fabbrica del canale.
Nel blocco del workflow, apportare la seguente modifica del codice:
Qui abbiamo aggiunto l'operatore sulla riga successiva per la leggibilità, ma puoi aggiungere operatori sulla stessa riga della fabbrica del canale se preferisci, come questo: greeting_ch = Channel.of(greetings_array).flatten()
3.2.2. Aggiungi view()
per ispezionare i contenuti del canale¶
Potremmo eseguirlo subito per verificare se funziona, ma già che ci siamo, aggiungeremo anche un paio di operatori view()
, che ci consentono di ispezionare il contenuto di un canale.
Puoi pensare a view()
come a uno strumento di debug, come un'istruzione print()
in Python, o il suo equivalente in altre lingue.
Nel blocco del workflow, apportare la seguente modifica del codice:
Stiamo usando un operatore closure qui - le parentesi graffe.
Questo codice viene eseguito per ogni elemento nel canale.
Definiamo una variabile temporanea per il valore interno, qui chiamata salunto
(potrebbe essere qualsiasi cosa).
Questa variabile viene utilizzata solo nell'ambito di tale chiusura.
In questo esempio, $greeting
rappresenta ogni singolo elemento caricato in un canale.
Nota su $it
In alcune pipeline potresti vedere una variabile speciale chiamata $it
utilizzata all'interno delle chiusure dell'operatore.
Questa è una variabile implicita che consente un accesso abbreviato alla variabile interna,
Senza bisogno di definirlo con un ->
.
Preferiamo essere espliciti per aiutare la chiarezza del codice, in quanto tale la sintassi $it
è scoraggiata e verrà lentamente eliminata dal linguaggio Nextflow.
3.2.3. Esegui il workflow¶
Infine, puoi provare a eseguire di nuovo il workflow!
Questa volta funziona E ci dà una visione aggiuntiva di come appaiono i contenuti del canale prima e dopo aver eseguito l'operatore flatten()
:
Output | |
---|---|
Vedi che otteniamo una singola istruzione Before flatten:
perché a quel punto il canale contiene un elemento, l'array originale.
Quindi otteniamo tre istruzioni separate Dopo appiattire:
, una per ogni saluto, che ora sono singoli elementi nel canale.
È importante sottolineare che questo significa che ogni elemento può ora essere elaborato separatamente dal workflow.
Tip
Dovresti eliminare o commentare le istruzioni view()
prima di andare avanti.
hello-channels.nf | |
---|---|
Li abbiamo lasciati nel file di soluzione hello-channels-3.nf
a scopo di riferimento.
Conclusioni¶
Sai come usare un operatore come flatten()
per trasformare il contenuto di un canale e come usare l'operatore view()
per ispezionare il contenuto del canale prima e dopo aver applicato un operatore.
Cosa c'è dopo?¶
Scopri come fare in modo che il workflow prenda un file come fonte di valori di input.
4. Usa un operatore per analizzare i valori di input da un file CSV¶
Spesso accade che, quando vogliamo eseguire su più ingressi, i valori di input sono contenuti in un file.
Ad esempio, abbiamo preparato un file CSV chiamato greetings.csv
contenente diversi saluti, uno su ogni riga (come una colonna di dati).
Quindi ora dobbiamo modificare il nostro workflow per leggere i valori di un file del genere.
4.1. Modifica lo script per aspettarti un file CSV come fonte di saluti¶
Per iniziare, dovremo apportare due modifiche chiave allo script:
- Cambia il parametro di input per puntare al file CSV
- Passa a una fabbrica di canali progettata per gestire un file
4.1.1. Cambia il parametro di input per puntare al file CSV¶
Ricordi il parametro params.greeting
che abbiamo impostato nella parte 1?
Lo aggiorneremo per puntare al file CSV contenente i nostri saluti.
Nel blocco del workflow, apportare la seguente modifica del codice:
4.1.2. Passa a una fabbrica di canali progettata per gestire un file¶
Dal momento che ora vogliamo usare un file invece di semplici stringhe come input, non possiamo usare la fabbrica di canali Channel.of()
di prima.
Dobbiamo passare all'utilizzo di una nuova fabbrica di canali, Channel.fromPath()
, che ha alcune funzionalità integrate per la gestione dei percorsi dei file.
Nel blocco del flusso di lavoro, apportare la seguente modifica del codice:
4.1.3. Esegui il workflow¶
Proviamo a eseguire il workflow con la nuova fabbrica di canali e il file di input.
Oh no, non funziona. Ecco l'inizio dell'output della console e il messaggio di errore:
The Command executed:
bit (lines 13-15) is especially helpful here.
Questo può sembrare un po' familiare. Sembra che Nextflow abbia tentato di eseguire una singola chiamata di processo utilizzando il percorso del file stesso come valore stringa. Quindi ha risolto correttamente il percorso del file, ma in realtà non ha analizzato il suo contenuto, che è quello che volevamo.
Come facciamo a fare in modo che Nextflow apra il file e carichi i suoi contenuti nel canale?
Sembra che abbiamo bisogno di un altro operatore!
4.2. Usa l'operatore splitCsv()
per analizzare il file¶
Guardando di nuovo l'elenco degli operatori, troviamo splitCsv()
, che è progettato per analizzare e dividere il testo in formato CSV.
4.2.1. Applica splitCsv()
al canale¶
Per applicare l'operatore, lo assediamo alla linea di fabbrica del canale come in precedenza.
Nel blocco del workflow, apportare la seguente modifica del codice:
Come puoi vedere, includiamo anche le dichiarazioni prima/dopo la vista mentre ci siamo.
4.2.2. Esegui di nuovo il workflow¶
Proviamo a eseguire il workflow con la logica di parsing CSV aggiunta.
È interessante notare che anche questo fallisce, ma con un errore diverso. L'output e l'errore della console iniziano così:
Questa volta Nextflow ha analizzato il contenuto del file (eviva!) Ma sono state aggiunte parentesi intorno ai saluti.
Per farla breve, splitCsv()
legge ogni riga in un array e ogni valore separato da virgole nella riga diventa un elemento nell'array.
Quindi qui ci dà tre array contenenti un elemento ciascuno.
Note
Anche se questo comportamento sembra scomodo in questo momento, sarà estremamente utile in seguito quando ci occuperemo di file di input con più colonne di dati.
We could solve this by using flatten()
, which you already know.
However, there's another operator called map()
that's more appropriate to use here and is really useful to know; it pops up a lot in Nextflow pipelines.
4.3. Usa l'operatore map()
per estrarre i saluti¶
L'operatore map()
è un piccolo strumento molto pratico che ci consente di fare tutti i tipi di mappature ai contenuti di un canale.
In questo caso, lo useremo per estrarre quell'elemento che vogliamo da ogni riga del nostro file. Ecco come appare la sintassi:
Ciò significa "per ogni elemento nel canale, prendi il primo di qualsiasi elemento che contiene".
Quindi applichiamolo al nostro parsing CSV.
4.3.1. Applica map()
al canale¶
Nel blocco del workflow, apportare la seguente modifica del codice:
Ancora una volta includiamo un'altra chiamata view()
per confermare che l'operatore fa ciò che ci aspettiamo.
4.3.2. Esegui il workflow ancora una volta¶
Eseguiamolo ancora una volta:
Questa volta dovrebbe funzionare senza errori.
Guardando l'output delle istruzioni view()
, vediamo quanto segue:
- Una singola istruzione
Before splitCsv:
: a quel punto il canale contiene un elemento, il percorso del file originale. - Tre istruzioni separate
After splitCsv:
: una per ogni saluto, ma ognuna è contenuta all'interno di un array che corrisponde a quella riga nel file. - Tre istruzioni separate
After map:
: una per ogni saluto, che ora sono elementi individuali nel canale.
Puoi anche guardare i file di output per verificare che ogni saluto sia stato correttamente estratto ed elaborato attraverso il workflow.
Abbiamo ottenuto lo stesso risultato di prima, ma ora abbiamo molta più flessibilità per aggiungere più elementi al canale di saluti che vogliamo elaborare modificando un file di input, senza modificare alcun codice.
Note
Qui abbiamo avuto tutti i saluti su una riga nel file CSV. Puoi provare ad aggiungere più colonne al file CSV e vedere cosa succede; ad esempio, prova quanto segue:
Puoi anche provare a sostituire .map { item -> item[0] }
con .flatten()
e vedere cosa succede a seconda di quante righe e colonne hai nel file di input.
Imparerai approcci più avanzati per gestire input complessi in una formazione successiva.
Conclusioni¶
Sai come usare gli operatori splitCsv()
e map()
per leggere in un file di valori di input e gestirli in modo appropriato.
Più in generale, hai una comprensione di base di come Nextflow utilizza canali per gestire gli input dei processi e operatori per trasformare i loro contenuti.
Cosa c'è dopo?¶
Fai una grande pausa, hai lavorato sodo in questo! Quando sei pronto, passa alla parte 3 per imparare come aggiungere altri passaggi e collegarli insieme in un workflow corretto.