web-dev-qa-db-ja.com

文字列の任意の場所で1つの不一致を許容する文字列を検索します

私は長さ25のDNA配列を扱っています(以下の例を参照)。私は230,000のリストを持っており、ゲノム全体(toxoplasma gondii寄生虫)の各配列を探す必要があります。ゲノムの大きさはわかりませんが、23万配列よりはるかに長いです。

たとえば、25文字のシーケンスをそれぞれ検索する必要があります(AGCCTCCCATGATTGAACAGATCAT)。

ゲノムは連続文字列としてフォーマットされます。つまり、(CATGGGAGGCTTGCGGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTTGCGGAGTGCGGAGCCTGAGCCTGAGGGCGGAGCCTGAGGTGGGAGGCTT ....)

どこに何回見つかったのかは気にせず、見つかったかどうかだけです。
これは簡単だと思います-

str.find(AGCCTCCCATGATTGAACAGATCAT)

しかし、私はまた、1つの場所だけで、どの場所でも間違っている(不一致)と定義されている厳密な一致を見つけて、その場所をシーケンスに記録します。これをどのように行うのかわかりません。私が考えることができる唯一のことは、ワイルドカードを使用し、各位置にワイルドカードを使用して検索を実行することです。つまり、25回検索します。

例えば、
AGCCTCCCATGATTGAACAGATCAT
AGCCTCCCATGATAGAACAGATCAT

位置13での不一致との密接な一致。

スピードは3回しかないので大した問題ではありませんが、速ければいいのですが。

これを行うプログラム(一致と部分一致の検索)がありますが、これらのアプリケーションでは検出できないタイプの部分一致を探しています。

これはPerlの同様の投稿ですが、シーケンスを比較しているだけで、連続した文字列を検索していません。

関連記事

23
Vincent

を読む前に、 biopython を見たことがありますか?

1つの置換エラー、およびゼロの挿入/削除エラー、つまりハミング距離が1の近似一致を見つけたいようです。

ハミング距離一致関数(Ignacioが提供するリンクなどを参照)がある場合は、次のように使用して最初の一致を検索できます。

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

ただし、これはかなり遅くなります。(1)ハミング距離関数は2番目の置換エラーの後も研削を続けます(2)失敗後は、カーソルを(ボイヤーのように)見たものに基づいてスキップするのではなく、1つ進めます。ムーア検索は行います)。

次のような関数で(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 

注:適度な速度を得るにはCを使用する必要があるため(おそらくCython経由)、これは意図的にPythonicではなくCicです。

スキップを伴うビット並列近似レーベンシュタイン検索に関するいくつかの作業は、NavarroとRaffinot(google "Navarro Raffinot nrgrep")によって行われ、これはハミング検索に適合させることができます。ビット並列方式にはクエリ文字列の長さとアルファベットのサイズに制限がありますが、それぞれ25と4であるため、問題はありません。更新:アルファベットサイズが4の場合、スキップしてもあまり役に立ちません。

ハミング距離検索をグーグルで検索すると、ソフトウェアではなく、ハードウェアでの実装に関する多くのことに気付くでしょう。これは、思いついたアルゴリズムがCまたはその他のコンパイル言語で実装されるべきであることを示す大きなヒントです。

更新:ビット並列メソッドの作業コード

また、正確性のチェックを支援するための単純な方法を提供し、いくつかの比較のためにPaulのreコードのバリエーションをパッケージ化しました。 re.finditer()を使用すると、重複しない結果が得られることに注意してください。これにより、距離1の一致が完全一致をシャドウする可能性があります。私の最後のテストケースを参照してください。

ビット並列方式には次の機能があります。保証された線形動作O(N)ここで、Nはテキストの長さです。単純な方式はO(NM)そのままです。正規表現法(Mはパターンの長さです)。Boyer-Mooreスタイルの方法は最悪の場合O(NM)そして期待されるO(N)です。ビット並列法も可能です。入力をバッファリングする必要がある場合に簡単に使用できます。一度に1バイトまたは1メガバイトを供給することができます。先読みがなく、バッファ境界に問題がありません。大きな利点:コーディング時の単純な入力バイトごとのコードの速度Cで。

欠点:パターンの長さは、高速レジスタのビット数に効果的に制限されます。 32または64。この場合、パターンの長さは25です。問題ない。パターン内の個別の文字数に比例する追加のメモリ(S_table)を使用します。この場合、「アルファベットサイズ」はわずか4です。問題ない。

このテクニカルレポート からの詳細。レーベンシュタイン距離を使用した近似検索用のアルゴリズムがあります。ハミング距離の使用に変換するために、挿入と削除を処理するステートメント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
24
John Machin

Python regex ライブラリはファジー正規表現マッチングをサポートしています。 [〜#〜] tre [〜#〜] に対する1つの利点は、テキスト内の正規表現のすべての一致を検索できることです(重複する一致もサポートします)。

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

これらのゲノムファイルのいくつかをオンラインで見つけるために、「toxoplasmagondii寄生虫ゲノム」をグーグルで検索しました。近いと思うもの、「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)

一致するたびに、一致した実際の文字列を含むタプルと、不一致の場所のリストが返されます。もちろん、完全一致は2番目の値に対して空のリストを返します。 (私はこのクラスが好きです。次のリリースの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つからランダムに検索シーケンスを取得して、完全に一致するものを見つけられるようにしました。好奇心から、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分半かかりました。 pyparsingソリューションが行ったこと。

6
PaulMcG

Python-Levenshtein でさまざまなルーチンが使用されている場合があります。

>>> 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のゲノムの場合、1つのスレッドで230,000のシーケンスをスキャンするために、約2。5日を見ているので、タスクをいくつかのコア/ CPUに分割することをお勧めします。

これが実行されている間は、いつでもより効率的なアルゴリズムの実装を開始できます:)

単一の削除または追加された要素を検索する必要がある場合は、正規表現をこれに変更します

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

最長共通部分列問題 のこのヒント。ここでの文字列の類似性の問題は、230000シーケンスの連続した文字列に対してテストする必要があることです。したがって、25のシーケンスの1つを連続文字列と比較している場合、類似性は非常に低くなります。

25シーケンスと連続文字列の間で最長共通部分列を計算すると、長さが同じであれば、それが文字列内にあるかどうかがわかります。

一致させたいすべての異なるシーケンスから trie を作成できます。ここで、最大で1つの不一致を許容する、深さ優先探索関数をトライで作成する際の注意が必要な部分です。

この方法の利点は、すべてのシーケンスを一度に検索できることです。これにより、多くの比較を節約できます。たとえば、最上位ノードから開始して「A」ブランチを下る場合、「A」で始まるすべてのシーケンスと即座に一致するため、何千もの比較を保存できます。別の議論については、与えられた配列と完全に一致するゲノムのスライスを考えてみましょう。シーケンスのリストに最後のシンボルのみが異なる別のシーケンスがある場合、トライを使用すると23回の比較操作が節約されます。

これを実装する1つの方法があります。 'A'、 'C​​'、T '、G'を0,1,2,3またはその変形に変換します。次に、タプルのタプルをトライの構造として使用します。各ノードで、配列の最初の要素は「A」に対応し、2番目の要素は「C」に対応します。 'A'がこのノードのブランチである場合、このノードのタプルの最初の項目として4要素の別のタプルがあります。 'A'ブランチがない場合は、最初の項目を0に設定します。トライの下部には、一致リストに入れることができるように、そのシーケンスのIDを持つノードがあります。

この種のトライに対して1つの不一致を許可する再帰検索関数は次のとおりです。

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に等しい場合は、不一致がなかったことを意味します。とにかく、あなたが写真を撮れるように私が十分に明確であったことを願っています。

1
Justin Peel

私はいくつかの解決策を試しましたが、すでに書いたように、大量のシーケンス(文字列)を処理するときは遅くなります。

bowtie を使用して、対象の部分文字列(soi)をFASTA形式の文字列を含む参照ファイルにマッピングすることを思いつきました。許可された不一致の数(0..3)を指定すると、許可された不一致が与えられた場合にsoiがマップされた文字列が返されます。これはうまく機能し、かなり高速です。

1

「近似マッチング」には、正規表現マッチングライブラリ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/

0
sefakilic

これはかなり古いですが、おそらくこの単純な解決策が機能する可能性があります。 25文字のスライスを取得してシーケンスをループします。スライスをnumpy配列に変換します。 25char文字列と比較してください(numpy配列としても)。答えを合計し、答えが24の場合は、ループ内の位置と不一致を出力します。

次の数行はそれが機能していることを示しています

numpyをnpとしてインポート

a = ['A'、 'B'、 'C​​']

b = np.array(a)

b

array(['A'、 'B'、 'C​​']、dtype = '

c = ['A'、 'D'、 'C​​']

d = np.array(c)

b == d

array([True、False、True])

合計(b == d)

2

0
Alan Johnstone

以下のコードはシンプルで便利だと思いました。

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

チェックしたいゲノムやセグメントを簡単に挿入したり、ミスマッチの値を設定したりできます。

0
nosense

これは少し遅れるかもしれませんが、PatMaNという名前のツールがあります。これはまさにあなたが望むことを実行します: http://bioinf.eva.mpg.de/patman/

0
cpp1

Pythonの組み込み機能を使用して、正規表現のマッチングで検索を行うことができます。

python http://docs.python.org/library/re.html のreモジュール

正規表現入門書 http://www.regular-expressions.info/

0
Daniel