搜索字符串,允许在字符串的任何位置发生一次不匹配

时间:2010-03-10 20:42:30

标签: python string pattern-matching string-matching dna-sequence

我正在使用长度为25的DNA序列(参见下面的例子)。我有一个230,000的清单,需要寻找整个基因组中的每个序列(弓形虫寄生虫)。我不确定基因组有多大,但比230,000个序列长得多。

我需要查找每个25个字符的序列,例如,(AGCCTCCCATGATTGAACAGATCAT)。

基因组格式为连续字符串,即(CATGGGAGGCTTGCGGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTTGCGGAGTGCGGAGCCTGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTT ....)

我不关心它被发现的次数或次数,只关注它是否存在 我认为这很简单 -

str.find(AGCCTCCCATGATTGAACAGATCAT)

但我还要找到在任何位置定义为错误(不匹配)的近距离匹配,但只有一个位置,并记录序列中的位置。我不知道怎么做到这一点。我唯一能想到的是使用通配符并在每个位置使用通配符执行搜索。即,搜索25次。

例如,
AGCCTCCCATGATTGAACAGATCAT
AGCCTCCCATGATAGAACAGATCAT

与第13位不匹配的近距离匹配。

速度不是一个大问题,因为我只做了3次,但如果它很快就会很好。

有些程序可以执行此操作 - 查找匹配项和部分匹配项 - 但我正在寻找一种使用这些应用程序无法发现的部分匹配。

这是perl的类似帖子,虽然它们只是比较序列而不是搜索连续的字符串:

Related post

13 个答案:

答案 0 :(得分:23)

在您阅读之前,您是否看过biopython

您似乎希望找到一个替换错误的近似匹配,以及零插入/删除错误,即汉明距离为1.

如果您有汉明距离匹配功能(请参阅例如Ignacio提供的链接),您可以像这样使用它来搜索第一场比赛:

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

但这会相当慢,因为(1)汉明距离函数会在失败后第二次替换错误(2)之后继续磨削,它会将光标前进一次而不是根据它看到的向前跳过(如一个Boyer-Moore搜索确实。)

你可以用这样的函数克服(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)来获得合理的速度。

Navarro和Raffinot(谷歌“Navarro Raffinot nrgrep”)完成了一些关于平行近似Levenshtein搜索和跳过的工作,这可以适用于汉明搜索。请注意,位并行方法对查询字符串和字母大小的长度有限制,但是你的分别是25和4,所以没有问题。更新:跳过可能对字母大小为4的帮助不大。

当你谷歌进行汉明距离搜索时,你会注意到很多关于在硬件中实现它的东西,而不是软件。这是一个很大的提示,也许你想出的任何算法都应该用C语言或其他编译语言实现。

更新: 位并行方法的工作代码

我还提供了一种简单的方法来帮助进行正确性检查,并且我已经打包了Paul的重新编码的变体以进行一些比较。请注意,使用re.finditer()会提供非重叠的结果,这会导致distance-1匹配阴影完全匹配;看我上次的测试用例。

位并行方法具有以下特征:保证线性行为O(N),其中N是文本长度。注意天真方法是O(NM),因为正则表达式方法(M是模式长度)。 Boyer-Moore风格的方法将是最坏的情况O(NM)和预期的O(N)。当输入必须被缓冲时,也可以很容易地使用位并行方法:它可以一次输入一个字节或一个兆字节;没有预见,没有缓冲区边界的问题。最大的优势:用C编码时,简单的每输入字节代码的速度。

下行:模式长度实际上限于快速寄存器中的位数,例如32或64.在这种情况下,图案长度为25;没问题。它使用与模式中不同字符数成比例的额外内存(S_table)。在这种情况下,“字母大小”仅为4;没问题。

来自this technical report的详细信息。该算法用于在Levenshtein距离内进行近似搜索。要转换为使用汉明距离,我只需(!)删除处理插入和删除的语句2.1。你会注意到很多带有“d”上标的“R”。 “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

答案 1 :(得分:20)

Python regex库支持模糊正则表达式匹配。与TRE相比的一个优点是它允许在文本中找到正则表达式的所有匹配项(也支持重叠匹配)。

import regex
m=regex.findall("AA", "CAG")
>>> []
m=regex.findall("(AA){e<=1}", "CAAG") # means allow up to 1 error
m
>>> ['CA', 'AG']

答案 2 :(得分:6)

我用Google搜索“弓形虫寄生虫基因组”来在线查找这些基因组文件。我找到了我认为很接近的文件,名为“TgondiiGenomic_ToxoDB-6.0.fasta”的文件位于http://toxodb.org,大小约为158Mb。我用下面的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 Token类的子类,用于进行紧密匹配:

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版本中。)

然后我运行此代码来搜索从.fasta文件读取的所有序列中的“up-to-2-mismatch”匹配(回想一下,genedata是一个ParseResults组序列,每个包含一个id,一个整数长度和序列字符串):

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分钟后,我得到了这个输出,列出了每个id和基因长度,以及找到的任何部分匹配:

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

因此虽然没有设置任何速度记录,但我完成了工作,并发现了一些2场比赛,以防它们可能会引起关注。

为了进行比较,这里是一个基于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个字符。)

因此,在第一次通过提取基因序列以进行匹配后,这个基于RE的搜索者再花了大约1-1 / 2分钟来扫描所有未文本包装的序列,找到所有相同的1 - pyparsing解决方案所做的-mismatch条目。

答案 3 :(得分:3)

您可能会在Python-Levenshtein中找到一些使用的各种例程。

答案 4 :(得分:2)

>>> 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天的单个线程扫描230,000个序列,因此您可能希望将任务拆分为几个核心/ cpu。

在运行此算法时,您始终可以开始实施更高效的算法:)

如果您希望搜索单个删除或添加的元素,请将正则表达式更改为此

>>> seq_re=re.compile('|'.join(seq[:i]+'.{0,2}'+seq[i+1:] for i in range(len(seq))))

答案 5 :(得分:1)

这是longest common subsequence problem的提示。这里字符串相似性的问题是你需要测试连续的230000个序列串;因此,如果您将25个序列中的一个与连续字符串进行比较,则会得到非常低的相似度。

如果计算25个序列和连续字符串之间最长的公共子序列,如果长度相同,你就会知道它是否在字符串中。

答案 6 :(得分:1)

您可以从要匹配的所有不同序列中选择trie。现在是在trie中创建深度优先搜索功能的最棘手的部分,它允许最多一个不匹配。

此方法的优点是您可以一次搜索所有序列。这将为您节省大量的比较。例如,当您从顶部节点开始并沿着'A'分支向下时,您刚刚保存了数千个比较,因为它们刚刚与所有以'A'开头的序列匹配。对于不同的论点,考虑与给定序列完全匹配的基因组切片。如果序列列表中的序列不同,仅在最后一个符号中有所不同,那么使用trie刚刚保存了23个比较操作。

以下是实现此目的的一种方法。将'A','C',T',G'转换为0,1,2,3或其变体。然后使用元组元组作为你的结构。在每个节点,数组中的第一个元素对应'A',第二个元素对应'C',依此类推。如果'A'是该节点的分支,那么另外有4个元素的元组作为该节点元组的第一项。如果没有'A'分支,则将第一项设置为0.在trie的底部是具有该序列的id的节点,以便可以将其放入匹配列表中。

以下是递归搜索函数,允许这种类型的一个不匹配:

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)

和addtomatches类似于

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

等于-1意味着没有不匹配。无论如何,我希望我足够清楚,以便你了解情况。

答案 7 :(得分:1)

我尝试了一些解决方案,但是在处理大量序列(字符串)时,它们已经很慢了。

我想出了使用bowtie并将感兴趣的子字符串(soi)映射到包含FASTA格式的字符串的参考文件。您可以提供允许的不匹配数(0..3),并在给定允许的不匹配的情况下返回soi映射的字符串。这种方法效果很好而且非常快。

答案 8 :(得分:0)

您可以使用内置功能的Pythons来进行正则表达式匹配的搜索。

在python中重新模块 http://docs.python.org/library/re.html

正则表达引物 http://www.regular-expressions.info/

答案 9 :(得分:0)

我想这可能会有点晚,但有一个名为PatMaN的工具可以完全按照你的意愿行事: http://bioinf.eva.mpg.de/patman/

答案 10 :(得分:0)

您可以使用正则表达式匹配库TRE,进行“近似匹配”。它还具有Python,Perl和Haskell的绑定。

import tre

pt = tre.compile("Don(ald)?( Ervin)? Knuth", tre.EXTENDED)
data = """
In addition to fundamental contributions in several branches of
theoretical computer science, Donnald Erwin Kuth is the creator of
the TeX computer typesetting system, the related METAFONT font
definition language and rendering system, and the Computer Modern
family of typefaces.
"""

fz = tre.Fuzzyness(maxerr = 3)
print fz
m = pt.search(data, fz)

if m:
    print m.groups()
    print m[0]

将输出

tre.Fuzzyness(delcost=1,inscost=1,maxcost=2147483647,subcost=1, maxdel=2147483647,maxerr=3,maxins=2147483647,maxsub=2147483647)
((95, 113), (99, 108), (102, 108))
Donnald Erwin Kuth

http://en.wikipedia.org/wiki/TRE_%28computing%29

http://laurikari.net/tre/

答案 11 :(得分:0)

我认为下面的代码简单方便。

in_pattern = "";
in_genome = "";
in_mistake = d;
out_result = ""


kmer = len(in_pattern)

def FindMistake(v):
    mistake = 0
    for i in range(0, kmer, 1):
        if (v[i]!=in_pattern[i]):
            mistake+=1
        if mistake>in_mistake:
            return False
    return True


for i in xrange(len(in_genome)-kmer+1):
    v = in_genome[i:i+kmer]
    if FindMistake(v):
        out_result+= str(i) + " "

print out_result

您可以轻松插入要检查的基因组和片段,并设置不匹配的值。

答案 12 :(得分:0)

这已经很老了,但也许这个简单的解决方案可行。 循环通过序列采取25个字符切片。将切片转换为numpy数组。比较25char字符串(也作为numpy数组)。总结答案,如果答案是24,则打印出循环中的位置和不匹配。

接下来的几行显示它正常工作

  
    
      

将numpy导入为np

             

a = ['A','B','C']

             

b = np.array(a)

             

B'/ P>     

  

数组(['A','B','C'],dtype ='

  
    
      

c = ['A','D','C']

             

d = np.array(c)

             

b == d

    
  

array([True,False,True])

  
    
      

和(B == d)

    
  

2