24

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

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

基因组被格式化为一个连续的字符串,即 ( CATGGGAGGCTTGCGGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTTGCGGAGTGCGGAGCCTGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTT....)

我不在乎它在哪里或找到了多少次,只关心它是否存在。
这很简单,我想——

str.find(AGCCTCCCATGATTGAACAGATCAT)

但我也找到了一个在任何位置定义为错误(不匹配)的紧密匹配,但只有一个位置,并在序列中记录该位置。我不确定如何做到这一点。我唯一能想到的是使用通配符并在每个位置使用通配符执行搜索。即,搜索 25 次。

例如,

AGCCTCCCATGATTGAACAGATCAT    
AGCCTCCCATGATAGAACAGATCAT

在第 13 位出现不匹配的势均力敌的比赛。

速度不是什么大问题,因为我只做了 3 次,不过如果速度快就好了。

有一些程序可以做到这一点——查找匹配项和部分匹配项——但我正在寻找一种在这些应用程序中无法发现的部分匹配项。

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

相关帖子

4

13 回答 13

28

在你继续阅读之前,你看过biopython吗?

您似乎希望找到具有一个替换错误和零插入/删除错误(即汉明距离为 1)的近似匹配。

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

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

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

您可以使用以下函数克服 (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 搜索的工作,这可以适用于 Hamming 搜索。请注意,位并行方法对查询字符串的长度和字母大小有限制,但您的方法分别是 25 和 4,因此没有问题。更新:对于字母大小为 4 的跳过可能没有太大帮助。

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

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

我还提供了一种简单的方法来帮助进行正确性检查,并且我打包了 Paul 的 re 代码的变体以进行一些比较。请注意,使用 re.finditer() 会提供不重叠的结果,这可能会导致距离为 1 的匹配掩盖了精确匹配;请参阅我的最后一个测试用例。

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

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

本技术报告中的详细信息。该算法用于使用 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
于 2010-03-10T23:09:45.410 回答
25

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

import regex
m=regex.findall("AA", "CAG")
>>> []
m=regex.findall("(AA){e<=1}", "CAAG") # means allow up to 1 error
m
>>> ['CA', 'AG']
于 2012-06-11T19:44:02.757 回答
6

我在网上搜索“弓形虫寄生虫基因组”以找到其中一些基因组文件。我在http://toxodb.org找到了一个我认为很接近的文件,一个名为“TgondiiGenomic_ToxoDB-6.0.fasta”的文件,大小约为 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 文件读取的所有序列中搜索“最多 2 个不匹配”匹配项(回想一下,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人物。)

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

于 2010-03-11T05:59:49.930 回答
3

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

于 2010-03-10T20:49:21.517 回答
3
>>> 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 的基因组,单线程扫描 230,000 个序列大约需要 2.5 天,因此您可能希望将任务拆分到几个核心/cpu 上。

你总是可以在这个运行时开始实施更有效的算法:)

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

>>> seq_re=re.compile('|'.join(seq[:i]+'.{0,2}'+seq[i+1:] for i in range(len(seq))))
于 2010-03-10T20:59:04.977 回答
1

您可以尝试使用您想要匹配的所有不同序列现在是使深度优先搜索功能向下允许最多一个不匹配的特里的棘手部分。

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

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

下面是递归搜索函数,允许这种 trie 出现一个不匹配:

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 表示不存在不匹配。无论如何,我希望我足够清楚,以便您了解图片。

于 2010-03-10T23:28:19.270 回答
1

这暗示了最长公共子序列问题。这里字符串相似性的问题是您需要针对 230000 个序列的连续字符串进行测试;因此,如果您将 25 个序列中的一个与连续字符串进行比较,您将获得非常低的相似性。

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

于 2010-03-10T20:57:35.427 回答
1

我尝试了一些解决方案,但正如已经写过的那样,它们在处理大量序列(字符串)时速度很慢。

我想出了使用bowtie并将感兴趣的子字符串 (soi) 映射到包含 FASTA 格式字符串的参考文件。您可以提供允许的不匹配数 (0..3),然后根据允许的不匹配返回 soi 映射到的字符串。这很好用而且很快。

于 2012-02-07T15:22:14.197 回答
0

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

python中的re模块 http://docs.python.org/library/re.html

正则表达式入门 http://www.regular-expressions.info/

于 2010-03-10T20:49:15.730 回答
0

我想这可能来得有点晚,但是有一个名为 PatMaN 的工具可以完全满足您的需求: http ://bioinf.eva.mpg.de/patman/

于 2010-05-27T17:46:45.393 回答
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/

于 2012-06-11T18:45:12.493 回答
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

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

于 2016-01-30T19:19:33.187 回答
0

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

接下来的几行表明它有效

将 numpy 导入为 np

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

b = np.array(a)

b

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

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

d = np.array(c)

b==d

数组([真,假,真])

总和(b==d)

2

于 2018-02-24T11:42:58.247 回答