Определите длину последовательности, используя СИГАРУ
Чтобы дать вам немного контекста: я пытаюсь преобразовать файл sam в bam
samtools view -bT reference.fasta sequences.sam > sequences.bam
который выходит со следующей ошибкой
[E::sam_parse1] CIGAR and query sequence are of different length
[W::sam_read1] parse error at line 102
[main_samview] truncated file
и оскорбительная строка выглядит так:
SRR808297.2571281 99 gi|309056|gb|L20934.1|MSQMTCG 747 80 101M = 790 142 TTGGTATAAAATTTAATAATCCCTTATTAATTAATAAACTTCGGCTTCCTATTCGTTCATAAGAAATATTAGCTAAACAAAATAAACCAGAAGAACAT @@CFDDFD?HFDHIGEGGIEEJIIJJIIJIGIDGIGDCHJJCHIGIJIJIIJJGIGHIGICHIICGAHDGEGGGGACGHHGEEEFDC@=?CACC>CCC NM:i:2 MD:Z:98A1A
Моя последовательность длиной 98 символов, но возможная ошибка при создании файла sam, о котором было сообщено в CIGAR, 101. Я могу позволить себе роскошь потерять пару чтений, и в данный момент у меня нет доступа к исходному коду, который создал файлы sam, поэтому нет возможности выследить ошибку и повторно выполнить выравнивание. Другими словами, мне нужно прагматичное решение, чтобы двигаться дальше (пока). Поэтому я разработал скрипт на python, который подсчитывает длину моей строки нуклеотидов, сравнивает ее с тем, что зарегистрировано в CIGAR, и сохраняет "нормальные" строки в новом файле.
#!/usr/bin/python
import itertools
import cigar
with open('myfile.sam', 'r') as f:
for line in itertools.islice(f,3,None): #Loop through the file and skip the first three lines
cigar=line.split("\t")[5]
cigarlength = len(Cigar(cigar)) #Use module Cigar to obtain the length reported in the CIGAR string
seqlength = len(line.split("\t")[9])
if (cigarlength == seqlength):
...Preserve the line in a new file...
Как видите, для перевода СИГАРЫ в целое число, показывающее длину, я использую модуль СИГАР. Если честно, я немного настороженно отношусь к его поведению. Этот модуль, похоже, неправильно рассчитал длину в очень очевидных случаях. Есть ли другой модуль или более явная стратегия для перевода СИГАРЫ в длину последовательности?
Sidenote: Интересно, что, мягко говоря, эта проблема широко освещалась, но в интернете не найдено прагматического решения. Смотрите ссылки ниже:
https://github.com/COMBINE-lab/RapMap/issues/9
http://seqanswers.com/forums/showthread.php?t=67253
http://seqanswers.com/forums/showthread.php?t=21120
https://groups.google.com/forum/#!msg/snap-user/FoDsGeNBDE0/nRFq-GhlAQAJ
1 ответ
Спецификация SAM предлагает нам эту таблицу операций CIGAR, которая указывает, какие из них "потребляют" запрос или ссылку, вместе с явными инструкциями о том, как вычислить длину последовательности из строки CIGAR:
Consumes Consumes Op BAM Description query reference M 0 alignment match (can be a sequence match or mismatch) yes yes I 1 insertion to the reference yes no D 2 deletion from the reference no yes N 3 skipped region from the reference no yes S 4 soft clipping (clipped sequences present in SEQ) yes no H 5 hard clipping (clipped sequences NOT present in SEQ) no no P 6 padding (silent deletion from padded reference) no no = 7 sequence match yes yes X 8 sequence mismatch yes yes
- "Потребляет запрос" и "потребляет ссылку" указывает, вызывает ли операция CIGAR выравнивание по последовательности запроса и эталонной последовательности соответственно.
...
- Сумма длин операций M/I/S/=/X должна равняться длине SEQ.
Это позволяет нам тривиально вычислить длину последовательности из ее CIGAR, сложив длины всех операций "потребляет запрос" в CIGAR. Это именно то, что происходит в модуле сигары (см. https://github.com/brentp/cigar/blob/754cfed348364d390ec1aa40c951362ca1041f7a/cigar.py), поэтому я не знаю, почему OP здесь посчитал реализацию этого модуля был неправ.
Если мы извлечем соответствующий код из (уже очень короткого) модуля сигары, у нас останется что-то вроде этого в виде короткой реализации на Python операции суммирования, описанной в приведенной выше цитате:
from itertools import groupby
def query_len(cigar_string):
"""
Given a CIGAR string, return the number of bases consumed from the
query sequence.
"""
read_consuming_ops = ("M", "I", "S", "=", "X")
result = 0
cig_iter = groupby(cigar_string, lambda chr: chr.isdigit())
for _, length_digits in cig_iter:
length = int(''.join(length_digits))
op = next(next(cig_iter)[1])
if op in read_consuming_ops:
result += length
return result
Я подозреваю, что причина, по которой нет инструмента для решения этой проблемы, заключается в том, что нет общего решения, кроме повторного выравнивания с использованием программного обеспечения, которое не демонстрирует эту проблему. В вашем примере последовательность запросов идеально выравнивается по ссылке, и поэтому в этом случае строка CIGAR не очень интересна (всего одна M
операция atch с префиксом общей длины запроса). В этом случае исправление просто требует изменения 101M
в 98M
,
Тем не менее, для более сложных строк CIGAR (например, те, которые включают I
nsertions, D
элементы или другие операции), вы не сможете узнать, какая часть строки CIGAR слишком длинная. Если вы вычтете из неправильной части строки CIGAR, у вас останется неправильное чтение, что, вероятно, хуже для вашего последующего анализа, чем просто полное считывание.
Тем не менее, если это оказывается тривиальным, чтобы сделать это правильно (возможно, ваша процедура неправильного выравнивания всегда добавляет дополнительные базы к первой или последней операции CIGAR), то вам нужно знать, как правильно рассчитать длину запроса в соответствии с Строка CIGAR, чтобы вы знали, что из нее вычесть.
samtools
рассчитывает это с помощью htslib
функция bam_cigar2qlen.
Другие функции, которые bam_cigar2qlen
вызовы определены в sam.h, включая полезный комментарий, показывающий таблицу истинности, для которой операции используют последовательность запросов против ссылочной последовательности.
Короче говоря, чтобы вычислить длину запроса строки CIGAR так, как это делает samtools (действительно htslib), вы должны добавить данную длину для операций CIGAR. M
, I
, S
, =
, или же X
и игнорировать длину операций CIGAR для любых других операций.
Текущая версия модуля сигары Python, похоже, использует тот же набор операций и алгоритм для расчета длины запроса (что len(Cigar(cigar))
вернулся бы) выглядит прямо для меня. Что заставляет вас думать, что это не дает правильных результатов?
Похоже, вы должны иметь возможность использовать модуль Python для сигары, чтобы жестко обрезать левый или правый конец, используя mask_left
или же mask_right
метод с mask="H"
,