Поиск строки, допускающей одно несоответствие в любом месте строки

Я работаю с последовательностями DNA длины 25 (см. примеры ниже). Я имею список 230,000 и должен искать каждую последовательность во всем геноме (токсоплазма gondii паразит). Я не уверен, насколько большой геном, но намного, чем 230 000 последовательностей.

Я должен искать каждую из своих последовательностей 25 символов, например, (AGCCTCCCATGATTGAACAGATCAT).

Геном отформатирован как непрерывная строка, т.е. (CATGGGAGGCTTGCGGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTTGCGGAGTGCGGAGCCTGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTT....)

Я не забочусь, где или сколько раз найдено, только является ли это или нет.
Это просто, я думаю-

str.find(AGCCTCCCATGATTGAACAGATCAT)

Но я также, что найти близкое соответствие определенным, как неправильно (не соответствуется) в любом местоположении, но только одном местоположении, и записать местоположение в последовательности. Я не уверен, как действительно делают это. Единственная вещь, о которой я могу думать, использует подстановочный знак и выполняет поиск с подстановочным знаком в каждом положении. Т.е. ищите 25 раз.

Например,
AGCCTCCCATGATTGAACAGATCAT
AGCCTCCCATGATAGAACAGATCAT

Близкое соответствие с несоответствием в положении 13.

Скорость не является большой проблемой, потому что я только делаю ее 3 раза, хотя было бы хорошо, если бы это было быстро.

Существуют программы, которые делают это - находит соответствия и частичные соответствия - но я ищу тип частичного соответствия, которое не является поддающимся обнаружению с этими приложениями.

Вот подобное сообщение для жемчуга, хотя они только сравнивают последовательности и не ищут непрерывную строку:

Связанное сообщение

23
задан Community 23 May 2017 в 12:10
поделиться

8 ответов

Прежде чем читать дальше, посмотрите на biopython?

Похоже, что вы хотите найти приблизительные совпадения с одной ошибкой замены и нулевой ошибкой вставки/удаления, т.е. расстояние Хэмминга равно 1.

Если у вас есть функция поиска совпадений по расстоянию Хэмминга (см. напр. ссылку, предоставленную Ignacio), вы можете использовать ее для поиска первого совпадения:

any(Hamming_distance(genome[x:x+25], sequence) == 1 for x in xrange(len(genome)))

но это будет довольно медленно, потому что (1) функция расстояния Хэмминга продолжит поиск после 2-й ошибки подстановки (2) после неудачи она продвигает курсор на единицу, а не пропускает вперед, основываясь на том, что она увидела (как это делает поиск Бойера-Мура).

Вы можете преодолеть (1) с помощью функции вроде этой:

def Hamming_check_0_or_1(genome, posn, sequence):
    errors = 0
    for i in xrange(25):
        if genome[posn+i] != sequence[i]:
            errors += 1
            if errors >= 2:
                return errors
    return errors 

Примечание: это намеренно не Pythonic, а Cic, потому что для достижения разумной скорости вам придется использовать C (возможно, через Cython).

Некоторые работы по бито-параллельному приближенному поиску Левенштейна с пропусками были выполнены Наварро и Раффино (google "Navarro Raffinot nrgrep"), и это можно адаптировать к поиску Хэмминга. Обратите внимание, что у бит-параллельных методов есть ограничения на длину строки запроса и размер алфавита, но у вас 25 и 4 соответственно, так что проблем нет. Обновление: пропуск, вероятно, не сильно поможет при размере алфавита 4.

Когда вы набираете в Гугле поиск по расстоянию Хэмминга, вы заметите много информации о его аппаратной реализации, и совсем немного о программной. Это большой намек на то, что любой алгоритм, который вы придумаете, должен быть реализован на C или другом компилируемом языке.

Обновление: Рабочий код для бито-параллельного метода

Я также предоставил упрощенный метод для помощи в проверке корректности, и я упаковал вариант кода re Пола для некоторых сравнений. Обратите внимание, что использование re.finditer() дает непересекающиеся результаты, и это может привести к тому, что совпадение по расстоянию 1 будет затенять точное совпадение; см. мой последний тестовый пример.

Бито-параллельный метод имеет следующие особенности: гарантированное линейное поведение O(N), где N - длина текста. Обратите внимание, что наивный метод - O(NM), как и метод regex (M - длина шаблона). Метод в стиле Бойера-Мура в худшем случае будет O(NM), а в ожидаемом O(N). Также поразрядно-параллельный метод можно легко использовать, когда входные данные должны быть буферизованы: можно подавать по байту или мегабайту за раз; никакого опережающего просмотра, никаких проблем с границами буфера. Большое преимущество: скорость этого простого байтового кода на вход при кодировании на C.

Недостатки: длина паттерна фактически ограничена количеством бит в быстром регистре, например, 32 или 64. В данном случае длина шаблона равна 25; нет проблем. Используется дополнительная память (S_table), пропорциональная количеству отдельных символов в шаблоне. В этом случае "размер алфавита" составляет всего 4; нет проблем.

Подробности из этого технического отчета. Алгоритм там предназначен для приблизительного поиска с использованием расстояния Левенштейна. Чтобы перейти к использованию расстояния Хэмминга, я просто (!) удалил части утверждения 2.1, которые обрабатывают вставку и удаление. Вы заметите много ссылок на "R" с надстрочным индексом "d". "d" - это расстояние. Нам нужны только 0 и 1. Эти "R" становятся переменными R0 и R1 в приведенном ниже коде.

# coding: ascii

from collections import defaultdict
import re

_DEBUG = 0


# "Fast Text Searching with Errors" by Sun Wu and Udi Manber
# TR 91-11, Dept of Computer Science, University of Arizona, June 1991.
# http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.20.8854

def WM_approx_Ham1_search(pattern, text):
    """Generate (Hamming_dist, start_offset)
    for matches with distance 0 or 1"""
    m = len(pattern)
    S_table = defaultdict(int)
    for i, c in enumerate(pattern):
        S_table[c] |= 1 << i
    R0 = 0
    R1 = 0
    mask = 1 << (m - 1)
    for j, c in enumerate(text):
        S = S_table[c]
        shR0 = (R0 << 1) | 1
        R0 = shR0 & S
        R1 = ((R1 << 1) | 1) & S | shR0
        if _DEBUG:
            print "j= %2d msk=%s S=%s R0=%s R1=%s" \
                % tuple([j] + map(bitstr, [mask, S, R0, R1]))
        if R0 & mask: # exact match
            yield 0, j - m + 1
        elif R1 & mask: # match with one substitution
            yield 1, j - m + 1

if _DEBUG:

    def bitstr(num, mlen=8):
       wstr = ""
       for i in xrange(mlen):
          if num & 1:
             wstr = "1" + wstr
          else:
             wstr = "0" + wstr
          num >>= 1
       return wstr

def Ham_dist(s1, s2):
    """Calculate Hamming distance between 2 sequences."""
    assert len(s1) == len(s2)
    return sum(c1 != c2 for c1, c2 in zip(s1, s2))

def long_check(pattern, text):
    """Naively and understandably generate (Hamming_dist, start_offset)
    for matches with distance 0 or 1"""
    m = len(pattern)
    for i in xrange(len(text) - m + 1):
        d = Ham_dist(pattern, text[i:i+m])
        if d < 2:
            yield d, i

def Paul_McGuire_regex(pattern, text):
    searchSeqREStr = (
        '('
        + pattern
        + ')|('
        + ')|('.join(
            pattern[:i]
            + "[ACTGN]".replace(c,'')
            + pattern[i+1:]
            for i,c in enumerate(pattern)
            )
        + ')'
        )
    searchSeqRE = re.compile(searchSeqREStr)
    for match in searchSeqRE.finditer(text):
        locn = match.start()
        dist = int(bool(match.lastindex - 1))
        yield dist, locn


if __name__ == "__main__":

    genome1 = "TTTACGTAAACTAAACTGTAA"
    #         01234567890123456789012345
    #                   1         2

    tests = [
        (genome1, "ACGT ATGT ACTA ATCG TTTT ATTA TTTA"),
        ("T" * 10, "TTTT"),
        ("ACGTCGTAAAA", "TCGT"), # partial match can shadow an exact match
        ]

    nfailed = 0
    for genome, patterns in tests:
        print "genome:", genome
        for pattern in patterns.split():
            print pattern
            a1 = list(WM_approx_Ham1_search(pattern, genome))
            a2 = list(long_check(pattern, genome))
            a3 = list(Paul_McGuire_regex(pattern, genome))
            print a1
            print a2
            print a3
            print a1 == a2, a2 == a3
            nfailed += (a1 != a2 or a2 != a3)
    print "***", nfailed
23
ответ дан 29 November 2019 в 01:18
поделиться

Вы можете использовать встроенные возможности Pythons для выполнения поиска с сопоставлением регулярных выражений.

re модуль в python http://docs.python.org/library/re.html

учебник по регулярным выражениям http: // www. regular-expressions.info/

0
ответ дан 29 November 2019 в 01:18
поделиться

Это намекает на самую длинную общую проблему подпоследовательности . Проблема схожести строк здесь в том, что вам нужно протестировать непрерывную строку из 230000 последовательностей; поэтому, если вы сравниваете одну из своих 25 последовательностей с непрерывной строкой, вы получите очень низкое сходство.

Если вы вычислите самую длинную общую подпоследовательность между вашими 25 последовательностями и непрерывной строкой, вы узнаете, есть ли она в строке, если длины одинаковы.

1
ответ дан 29 November 2019 в 01:18
поделиться

Я искал в Google "геном паразита toxoplasma gondii", чтобы найти некоторые из этих файлов генома в Интернете. Я нашел то, что мне показалось очень близким, файл под названием «TgondiiGenomic_ToxoDB-6.0.fasta» на http://toxodb.org , размером около 158 МБ. Я использовал следующее выражение pyparsing для извлечения последовательностей генов, это заняло чуть менее 2 минут:

fname = "TgondiiGenomic_ToxoDB-6.0.fasta"
fastasrc = open(fname).read()   # yes! just read the whole dang 158Mb!

"""
Sample header:
>gb|scf_1104442823584 | organism=Toxoplasma_gondii_VEG | version=2008-07-23 | length=1448
"""
integer = Word(nums).setParseAction(lambda t:int(t[0]))
genebit = Group(">gb|" + Word(printables)("id") + SkipTo("length=") + 
                "length=" + integer("genelen") + LineEnd() + 
                Combine(OneOrMore(Word("ACGTN")),adjacent=False)("gene"))

# read gene data from .fasta file - takes just under a couple of minutes
genedata = OneOrMore(genebit).parseString(fastasrc)

(Сюрприз! Некоторые последовательности генов включают в себя серии "N"! Что это, черт возьми?!)

Затем я написал этот класс как подкласс класса токена pyparsing для выполнения близких совпадений:

class CloseMatch(Token):
    def __init__(self, seq, maxMismatches=1):
        super(CloseMatch,self).__init__()
        self.name = seq
        self.sequence = seq
        self.maxMismatches = maxMismatches
        self.errmsg = "Expected " + self.sequence
        self.mayIndexError = False
        self.mayReturnEmpty = False

    def parseImpl( self, instring, loc, doActions=True ):
        start = loc
        instrlen = len(instring)
        maxloc = start + len(self.sequence)

        if maxloc <= instrlen:
            seq = self.sequence
            seqloc = 0
            mismatches = []
            throwException = False
            done = False
            while loc < maxloc and not done:
                if instring[loc] != seq[seqloc]:
                    mismatches.append(seqloc)
                    if len(mismatches) > self.maxMismatches:
                        throwException = True
                        done = True
                loc += 1
                seqloc += 1
        else:
            throwException = True

        if throwException:
            exc = self.myException
            exc.loc = loc
            exc.pstr = instring
            raise exc

        return loc, (instring[start:loc],mismatches)

Для каждого совпадения это будет возвращать кортеж, содержащий фактическую строку, которая была сопоставлена, и список несовпадающих местоположений.Точные совпадения, конечно, вернут пустой список для второго значения. (Мне нравится этот класс, думаю, я добавлю его в следующий выпуск pyparsing.)

Затем я запустил этот код для поиска совпадений "до 2-несовпадений" во всех последовательностях, считанных из .fasta файл (напомним, что genedata - это последовательность групп ParseResults, каждая из которых содержит идентификатор, целую длину и строку последовательности):

searchseq = CloseMatch("ATCATCGAATGGAATCTAATGGAAT", 2)
for g in genedata:
    print "%s (%d)" % (g.id, g.genelen)
    print "-"*24
    for t,startLoc,endLoc in searchseq.scanString(g.gene):
        matched, mismatches = t[0]
        print "MATCH:", searchseq.sequence
        print "FOUND:", matched
        if mismatches:
            print "      ", ''.join(' ' if i not in mismatches else '*' 
                            for i,c in enumerate(searchseq.sequence))
        else:
            print "<exact match>"
        print "at location", startLoc
        print
    print

Я произвольно взял последовательность поиска из одного из битов гена, чтобы убедиться, что я могли найти точное совпадение и просто из любопытства посмотреть, сколько было несоответствий по 1 и 2 элементам.

Это заняло некоторое время. Через 45 минут я получил следующий результат, в котором перечислены все идентификаторы и длины гена, а также все найденные частичные совпадения:

scf_1104442825154 (964)
------------------------

scf_1104442822828 (942)
------------------------

scf_1104442824510 (987)
------------------------

scf_1104442823180 (1065)
------------------------
...

Я разочаровывался, не видеть никаких совпадений до:

scf_1104442823952 (1188)
------------------------
MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAACGGAATCGAATGGAAT
                *      *        
at location 33

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGAATCGAATGGAAT
                       *        
at location 175

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGAATCGAATGGAAT
                       *        
at location 474

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGAATCGAATGGAAT
                       *        
at location 617

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGAATCGAATAGAAT
                       *   *    
at location 718

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGATTCGAATGGAAT
                    *  *        
at location 896

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGAATCGAATGGTAT
                       *     *  
at location 945

И, наконец, мое точное совпадение в:

scf_1104442823584 (1448)
------------------------
MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGACTCGAATGGAAT
                    *  *        
at location 177

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGAATCAAATGGAAT
                       *        
at location 203

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCAAATGGAATCGAATGGAAT
             *         *        
at location 350

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGAATCGAATGGAAA
                       *       *
at location 523

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCAAATGGAATCGAATGGAAT
             *         *        
at location 822

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCATCGAATGGAATCTAATGGAAT
<exact match>
at location 848

MATCH: ATCATCGAATGGAATCTAATGGAAT
FOUND: ATCGTCGAATGGAGTCTAATGGAAT
          *         *           
at location 969

Так что, хотя это не установило никаких рекордов скорости, я выполнил свою работу и нашел еще несколько совпадений на случай, если они могут быть интересны.

Для сравнения, вот версия на основе RE, которая находит только совпадения с несоответствием 1:

import re
seqStr = "ATCATCGAATGGAATCTAATGGAAT"
searchSeqREStr = seqStr + '|' + \
    '|'.join(seqStr[:i]+"[ACTGN]".replace(c,'') +seqStr[i+1:] 
             for i,c in enumerate(seqStr))

searchSeqRE = re.compile(searchSeqREStr)

for g in genedata:
    print "%s (%d)" % (g.id, g.genelen)
    print "-"*24
    for match in searchSeqRE.finditer(g.gene):
        print "MATCH:", seqStr
        print "FOUND:", match.group(0)
        print "at location", match.start()
        print
    print

(Сначала я попытался выполнить поиск самого исходного файла FASTA, но был озадачен, почему так мало совпадений по сравнению с pyparsing Затем я понял, что некоторые совпадения должны пересекать разрывы строк, поскольку вывод файла fasta состоит из n символов.)

Итак, после первого прохода pyparsing для извлечения последовательностей генов для сопоставления, этот основанный на RE Затем поисковику потребовалось еще около 1-1 / 2 минуты, чтобы просканировать все последовательности без обтекания текстом, чтобы найти все те же записи с одним несоответствием, что и решение pyparsing.

6
ответ дан 29 November 2019 в 01:18
поделиться
>>> import re
>>> seq="AGCCTCCCATGATTGAACAGATCAT"
>>> genome = "CATGGGAGGCTTGCGGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTTGCGGAGTGCGGAGCCTGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTT..."
>>> seq_re=re.compile('|'.join(seq[:i]+'.'+seq[i+1:] for i in range(len(seq))))

>>> seq_re.findall(genome)  # list of matches
[]  

>>> seq_re.search(genome) # None if not found, otherwise a match object

Это останавливает первое совпадение, поэтому может быть немного быстрее, когда есть несколько совпадений

>>> print "found" if any(seq_re.finditer(genome)) else "not found"
not found

>>> print "found" if seq_re.search(genome) else "not found" 
not found

>>> seq="CAT"
>>> seq_re=re.compile('|'.join(seq[:i]+'.'+seq[i+1:] for i in range(len(seq))))
>>> print "found" if seq_re.search(genome) else "not found"
found

для генома длиной 10 000 000, вы смотрите примерно на 2,5 дня для одного потока для сканирования 230000 последовательностей, поэтому вы можете хотите разделить задачу на несколько ядер / процессор.

Вы всегда можете начать реализацию более эффективного алгоритма, пока он работает :)

Если вы хотите искать отдельные отброшенные или добавленные элементы, измените регулярное выражение на это

>>> seq_re=re.compile('|'.join(seq[:i]+'.{0,2}'+seq[i+1:] for i in range(len(seq))))
2
ответ дан 29 November 2019 в 01:18
поделиться

Вы можете создать дерево из всех различных последовательностей, которые вы хотите сопоставить. Теперь сложная часть создания функции поиска в глубину по дереву, которая допускает не более одного несоответствия.

Преимущество этого метода в том, что вы просматриваете все последовательности одновременно. Это избавит вас от множества сравнений. Например, когда вы начинаете с верхнего узла и спускаетесь по ветви «А», вы только что сохранили для себя много тысяч сравнений, потому что мгновенно сопоставили все последовательности, начинающиеся с «А». В качестве другого аргумента рассмотрим фрагмент генома, который точно соответствует заданной последовательности. Если у вас есть другая последовательность в вашем списке последовательностей, которая отличается только последним символом, то использование дерева только что спасло вам 23 операции сравнения.

Вот один из способов реализации этого. Преобразуйте 'A', 'C', T ', G' в 0,1,2,3 или его вариант. Затем используйте кортежи кортежей в качестве структуры для вашего дерева. В каждом узле первый элемент в вашем массиве соответствует букве «А», второй - букве «С» и так далее. Если «A» является ветвью этого узла, то есть еще один кортеж из 4 элементов в качестве первого элемента кортежа этого узла. Если ветки «A» нет, установите для первого элемента значение 0.Внизу дерева находятся узлы, у которых есть идентификатор этой последовательности, чтобы ее можно было поместить в список совпадений.

Вот рекурсивные функции поиска, допускающие одно несоответствие для такого типа дерева:

def searchnomismatch(node,genome,i):
    if i == 25:
        addtomatches(node)
    else:
        for x in range(4):
            if node[x]:
                if x == genome[i]:
                    searchnomismatch(node[x],genome,i+1)
                else:
                    searchmismatch(node[x],genome,i+1,i)

def searchmismatch(node,genome,i,where):
    if i == 25:
        addtomatches(node,where)
    else:
        if node[genome[i]]:
            searchmismatch(node[genome[i]],genome,i+1,where)

Здесь я начинаю поиск с чего-то вроде

searchnomismatch(trie,genome[ind:ind+25],0)

, а дополнительные совпадения похожи на

def addtomatches(id,where=-1):
    matches.append(id,where)

, где значение -1 означает, что не было Несоответствие. В любом случае, я надеюсь, что я был достаточно ясен, чтобы вы поняли.

1
ответ дан 29 November 2019 в 01:18
поделиться

Вы можете найти различные подпрограммы в Python-Levenshtein , которые могут пригодиться.

3
ответ дан 29 November 2019 в 01:18
поделиться

Думаю, это может произойти немного поздно, но есть инструмент под названием PatMaN, который делает именно то, что вы хотите: http://bioinf.eva.mpg.de/patman/

0
ответ дан 29 November 2019 в 01:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: