Esercizio 7

Dato un file in formato FASTQ, che memorizza reads NGS e la qualità di ognuna delle loro basi, tenere solo i reads che hanno una percentuale almeno P di basi con qualità almeno Q, effettuarne il trimming e produrre in output in formato FASTQ solo i read per cui la porzione rimasta è lunga almeno L.

Il trimming di un read consiste nel trovare la più lunga sottostringa (o una delle più lunghe) le cui basi abbiano tutte una qualità pari ad almeno Q.


Parametri in input:

  • nome del file in formato FASTQ
  • soglia minima di qualità
  • percentuale minima (compresa tra 0 e 1) delle basi con qualità almeno Q
  • lunghezza minima dopo il trimming

Requisiti:

  • deve essere definita la funzione ascii_to_quality() che prenda come argomento un carattere ASCII e restituisca il corrispondente Phred Value.
  • deve essere definita la funzione get_quality_percentage() che prenda in input una stringa di qualità (cioé una stringa di caratteri ASCII che codificano Phred Values) e una soglia minima, e restituisca la percentuale di caratteri che codificano una qualità pari ad almeno la soglia minima.
  • deve essere definita la funzione get_trimming_interval() che prenda in input una stringa di qualità e una soglia minima, e restituisca il più lungo intervallo di posizioni (o uno dei più lunghi) contenente solo caratteri che codificano qualità almeno pari alla soglia minima.
  • deve essere definita la funzione get_trimmed_read() che prenda in input una soglia minima di qualità e la lista delle quattro stringhe di un read in formato FASTQ (header della sequenza del read, sequenza del read, header della stringa di qualità, stringa di qualità). La funzione deve effettuare il trimming, cioé deve trovare la più lunga sottostringa (o una delle più lunghe) del read le cui basi abbiano una qualità pari ad almeno la soglia minima, e restituire la lista delle quattro stringhe del formato FASTQ del read dopo avere effettuato il trimming. La funzione deve chiamare get_trimming_interval(). Ad esempio per una soglia minima di qualità pari a 58 e per il read seguente:

      @HWUSI-EAS522:8:5:662:692#0/1
      TATGGAGGCCCAACTTCTTGTATTCACAGGTTCTGC
      +HWUSI-EAS522:8:5:662:692#0/1
      aaaa`aa`aa`]__`aa`_U[_a`^\\UTWZ`X^QX
    

    il trimming produce:

      @HWUSI-EAS522:8:5:662:692#0/1
      TATGGAGGCCCAACTTCTT
      +HWUSI-EAS522:8:5:662:692#0/1,
      aaaa`aa`aa`]__`aa`_
    

    che in questo particolare caso corrisponde a tenere un prefisso del read.


Variabili di output:

  • output_list, lista dei reads di output in formato FASTQ. Ogni elemento della lista è una lista annidata contenente le quattro righe del formato FASTQ di un read.

Soluzione

Parametri in input


In [1]:
fastq_file_name = './input.fq'
quality_threshold = 58
min_percentage = 0.7
min_length = 20

Definizione della funzione ascii_to_quality()


In [31]:
def ascii_to_quality(c):
    return ord(c)-33

Definizione della funzione get_quality_percentage()


In [33]:
def get_quality_percentage(quality_string, quality_threshold):
    return float(len([c for c in quality_string if ascii_to_quality(c) >= quality_threshold]))/len(quality_string)

NOTA BENE: supporre che il carattere \n sia già stato rimosso dalla fine della stringa quality_string passata come argomento.


Definizione della funzione get_trimming_interval()


In [58]:
def get_trimming_interval(quality_string, quality_threshold):
    bool_list = [(ascii_to_quality(c) >= quality_threshold) for c in quality_string]
    
    start_list = [i for i in range(len(bool_list))[1:] if bool_list[i] == True and bool_list[i-1] == False]
    if bool_list[0] == True:
        start_list[:0] = [0]
        
    start_list[:0] = [1]
    
    end_list = [j for j in range(len(bool_list))[:-1] if bool_list[j] == True and bool_list[j+1] == False]
    if bool_list[-1] == True:
        end_list[len(end_list):] = [len(bool_list)-1]
        
    end_list[:0] = [0]
    
    interval_lengths = [end_list[p]-start_list[p]+1 for p in range(len(start_list))]
    
    max_value_index = interval_lengths.index(max(interval_lengths))
    start_interval = start_list[max_value_index]
    end_interval = end_list[max_value_index]
    
    return [start_interval, end_interval+1]

NOTA BENE: supporre che il carattere \n sia già stato rimosso dalla fine della stringa quality_string passata come argomento.

Spiegazione della funzione get_trimming_interval()

Si supponga che i due argomenti della funzione siano:


In [34]:
quality_string = 'aaaa`aa`aa`]__`aa`_U[_a`^\\\\UTWZ`X^QX'
quality_threshold = 58

NOTA BENE: il letterale \\ rappresenta il backslash.

Costruire la lista bool_list di valori di tipo bool lunga come la stringa di qualità, tale per cui il valore i-esimo è True se il carattere i-esimo della stringa di qualità codifica un Phred Value almeno pari alla soglia di qualità passata come secondo argomento.


In [37]:
bool_list = [(ascii_to_quality(c) >= quality_threshold) for c in quality_string]

In [38]:
bool_list


Out[38]:
[True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 False,
 True,
 True,
 True,
 True,
 True,
 True,
 True,
 False,
 False,
 False,
 False,
 True,
 False,
 True,
 False,
 False]

Basterà quindi che la funzione trovi e restituisca il più lungo intervallo di posizioni (o uno dei più lunghi) di bool_list che contiene solo valori True.

Costruire a questo punto la lista start_list delle posizioni di inizio di un intervallo di valori True, cioé la prima posizione i=0 se contiene un valore True, e le posizioni i > 0 tali per cui bool_list[i] è uguale a True e bool_list[i-1] è uguale a False.


In [41]:
start_list = [i for i in range(len(bool_list))[1:] if bool_list[i] == True and bool_list[i-1] == False]
if bool_list[0] == True:
    start_list[:0] = [0]

In [42]:
start_list


Out[42]:
[0, 20, 31, 33]

Aggiunta all'inizio della lista di una fake position pari a 1 (per gestire il caso in cui tutti i valori in bool_list sono uguali a False, cioé il caso in cui tutti i valori di qualità sono al di sotto della soglia minima).


In [43]:
start_list[:0] = [1]

In [44]:
start_list


Out[44]:
[1, 0, 20, 31, 33]

Costruire in modo analogo la lista end_list delle posizioni di fine di un intervallo di valori True, cioé le posizioni j < len(bool_list)-1 tali per cui bool_list[j] è uguale a True e bool_list[j+1] è uguale a False, e l'ultima posizione j=len(bool_list)-1 se contiene un valore True.


In [45]:
end_list = [j for j in range(len(bool_list))[:-1] if bool_list[j] == True and bool_list[j+1] == False]
if bool_list[-1] == True:
    end_list[len(end_list):] = [len(bool_list)-1]

In [46]:
end_list


Out[46]:
[18, 26, 31, 33]

Aggiunta all'inizio della lista di una fake position pari a 0 (per gestire il caso in cui tutti i valori in bool_list sono uguali a False, cioé il caso in cui tutti i valori di qualità sono al di sotto della soglia minima).


In [47]:
end_list[:0] = [0]

In [48]:
end_list


Out[48]:
[0, 18, 26, 31, 33]

NOTA BENE: nella stessa posizione p, start_list[p] e end_list[p] sono le posizioni di inizio e di fine su bool_list di un intervallo di soli valori True. Per p=0 si ha che lo start è pari a 1 e l'end è pari a 0, e sono le posizioni di inizio e fine del fake interval.

A questo punto basta determinare l'intervallo più ampio.

Costruire la lista interval_lengths delle lunghezze degli intervalli, calcolate come end-start+1 (il fake interval avrà lunghezza pari a 0).


In [50]:
interval_lengths = [end_list[p]-start_list[p]+1 for p in range(len(start_list))]

In [51]:
interval_lengths


Out[51]:
[0, 19, 7, 1, 1]

Estrarre l'inizio e la fine dell'intervallo di lunghezza massima (cioé del trimming interval).


In [55]:
max_value_index = interval_lengths.index(max(interval_lengths))
start_interval = start_list[max_value_index]
end_interval = end_list[max_value_index]

In [56]:
start_interval


Out[56]:
0

In [57]:
end_interval


Out[57]:
18

La funzione get_trimming_interval() restituisce l'intervallo [start_interval, end_interval+1] pronto per effettuare l'operazione di slicing sul read e sulla stringa di qualità.

NOTA BENE: se viene restituito il fake interval [1,1] allora significa che tutti i valori in bool_list sono uguali a False (cioé nella stringa di qualità tutti i valori sono al di sotto della soglia minima).


Definizione della funzione get_trimmed_read()

Il primo argomento della funzione è la soglia minima di qualità, mentre il secondo argomento è la lista delle quattro stringhe del formato FASTQ di un read. Supporre che il carattere \n non sia ancora stato tolto.


In [59]:
def get_trimmed_read(quality_threshold, fastq_read):
    trimming_interval = get_trimming_interval(fastq_read[3].rstrip(), quality_threshold)
    trimmed_read_sequence = fastq_read[1].rstrip()[trimming_interval[0]:trimming_interval[1]]
    trimmed_quality_string = fastq_read[3].rstrip()[trimming_interval[0]:trimming_interval[1]]
    return [fastq_read[0], trimmed_read_sequence+'\n', fastq_read[2], trimmed_quality_string+'\n']

Lettura del file in formato FASTQ

Lettura delle righe del file FASTQ e memorizzazione nella lista fastq_file_rows


In [20]:
with open(fastq_file_name, 'r') as fastq_input_file:
    fastq_file_rows = fastq_input_file.readlines()

In [21]:
fastq_file_rows


Out[21]:
['@HWUSI-EAS522:8:5:662:692#0/1\n',
 'TATGGAGGCCCAACTTCTTGTATTCACAGGTTCTGC\n',
 '+HWUSI-EAS522:8:5:662:692#0/1\n',
 'aaaa`aa`aa`]__`aa`_U[_a`^\\\\UTWZ`X^QX\n',
 '@HWUSI-EAS522:8:5:662:693#0/1\n',
 'TCTGCCAACTTCTTATGGAGGCCTGTATTCACAGGT\n',
 '+HWUSI-EAS522:8:5:662:693#0/1\n',
 'Aaaa`aa`aa`]__`:a`_U;_A`^\\\\UTWZ`X^QX\n',
 '@HWUSI-EAS522:8:5:662:694#0/1\n',
 'TCTGCCAGAGGCCTGTATTCACAGGTACTTCTTATG\n',
 '+HWUSI-EAS522:8:5:662:694#0/1\n',
 'aaaa`aa`aa`]__`aa`_u[_a`^\\\\utwz`x^QX\n',
 '@HWUSI-EAS522:8:5:662:695#0/1\n',
 'TCGCCTGTATTCACAGGTTGCCAACTTCTTATGGAG\n',
 '+HWUSI-EAS522:8:5:662:695#0/1\n',
 'AaaA`aa`aa`]__`:A`_U;_A`^\\\\UTWZ`X^QX\n']

Costruire la lista read_header_pos degli indici di posizione (cioé 0, 4, 8, 12, etc.) delle righe di intestazione dei read (quelle che iniziano con il carattere @).


In [61]:
read_header_pos = [i for i in range(len(fastq_file_rows)) if i % 4 == 0]

Usare read_header_pos per raggruppare per read le righe della lista fastq_file_rows e memorizzare i raggruppamenti (liste delle quattro stringhe del formato FASTQ) nella lista output_list.


In [63]:
output_list = [fastq_file_rows[i:i+4] for i in read_header_pos]

In [64]:
output_list


Out[64]:
[['@HWUSI-EAS522:8:5:662:692#0/1\n',
  'TATGGAGGCCCAACTTCTTGTATTCACAGGTTCTGC\n',
  '+HWUSI-EAS522:8:5:662:692#0/1\n',
  'aaaa`aa`aa`]__`aa`_U[_a`^\\\\UTWZ`X^QX\n'],
 ['@HWUSI-EAS522:8:5:662:693#0/1\n',
  'TCTGCCAACTTCTTATGGAGGCCTGTATTCACAGGT\n',
  '+HWUSI-EAS522:8:5:662:693#0/1\n',
  'Aaaa`aa`aa`]__`:a`_U;_A`^\\\\UTWZ`X^QX\n'],
 ['@HWUSI-EAS522:8:5:662:694#0/1\n',
  'TCTGCCAGAGGCCTGTATTCACAGGTACTTCTTATG\n',
  '+HWUSI-EAS522:8:5:662:694#0/1\n',
  'aaaa`aa`aa`]__`aa`_u[_a`^\\\\utwz`x^QX\n'],
 ['@HWUSI-EAS522:8:5:662:695#0/1\n',
  'TCGCCTGTATTCACAGGTTGCCAACTTCTTATGGAG\n',
  '+HWUSI-EAS522:8:5:662:695#0/1\n',
  'AaaA`aa`aa`]__`:A`_U;_A`^\\\\UTWZ`X^QX\n']]

Tenere in output_list solo i read la cui percentuale di basi con qualità almeno pari alla soglia minima (valore in quality_threshold) è almeno pari al parametro in input min_percentage.


In [65]:
output_list = [read for read in output_list if get_quality_percentage(read[3].rstrip(), quality_threshold) >= min_percentage]

In [66]:
output_list


Out[66]:
[['@HWUSI-EAS522:8:5:662:692#0/1\n',
  'TATGGAGGCCCAACTTCTTGTATTCACAGGTTCTGC\n',
  '+HWUSI-EAS522:8:5:662:692#0/1\n',
  'aaaa`aa`aa`]__`aa`_U[_a`^\\\\UTWZ`X^QX\n'],
 ['@HWUSI-EAS522:8:5:662:694#0/1\n',
  'TCTGCCAGAGGCCTGTATTCACAGGTACTTCTTATG\n',
  '+HWUSI-EAS522:8:5:662:694#0/1\n',
  'aaaa`aa`aa`]__`aa`_u[_a`^\\\\utwz`x^QX\n']]

Effettuare il trimming dei reads di output_list.


In [67]:
output_list = [get_trimmed_read(quality_threshold, read) for read in output_list]

In [68]:
output_list


Out[68]:
[['@HWUSI-EAS522:8:5:662:692#0/1\n',
  'TATGGAGGCCCAACTTCTT\n',
  '+HWUSI-EAS522:8:5:662:692#0/1\n',
  'aaaa`aa`aa`]__`aa`_\n'],
 ['@HWUSI-EAS522:8:5:662:694#0/1\n',
  'TCTGCCAGAGGCCTGTATTCACAGGTACTTCTTA\n',
  '+HWUSI-EAS522:8:5:662:694#0/1\n',
  'aaaa`aa`aa`]__`aa`_u[_a`^\\\\utwz`x^\n']]

Eliminare da output_list i reads troppo corti.


In [69]:
output_list = [read for read in output_list if len(read[1].rstrip()) >= min_length]

In [70]:
output_list


Out[70]:
[['@HWUSI-EAS522:8:5:662:694#0/1\n',
  'TCTGCCAGAGGCCTGTATTCACAGGTACTTCTTA\n',
  '+HWUSI-EAS522:8:5:662:694#0/1\n',
  'aaaa`aa`aa`]__`aa`_u[_a`^\\\\utwz`x^\n']]

In [ ]: