8

約1億行のファイルがあり、タブ区切りファイルに保存されている代替テキストにテキストを置き換えたいと考えています。私が持っているコードは機能しますが、最初の70K行を処理するのに約1時間かかります。Pythonスキルを段階的に向上させようとすると、これを行うためのより速い方法があるかどうか疑問に思います。ありがとう!入力ファイルは次のようになります。

CHROMOSOME_IVncRNA遺伝子57230855723105。-。ID = Gene:WBGene00045518 CHROMOSOME_IV ncRNA ncRNA57230855723105。-。Parent = Gene:WBGene00045518

置換値を持つファイルは次のようになります。

WBGene00045518 21ur-5153

これが私のコードです:

infile1 = open('f1.txt', 'r')
infile2 = open('f2.txt', 'r')
outfile = open('out.txt', 'w')

import re
from datetime import datetime
startTime = datetime.now()

udict = {}
for line in infile1:
    line = line.strip()
    linelist = line.split('\t')
    udict1 = {linelist[0]:linelist[1]} 
    udict.update(udict1)

mult10K = []
for x in range(100):
    mult10K.append(x * 10000)   
linecounter = 0
for line in infile2:
    for key, value in udict.items():
        matches = line.count(key)
        if matches > 0: 
            print key, value
            line = line.replace(key, value)
            outfile.write(line + '\n')
        else:
            outfile.write(line + '\n')
    linecounter += 1
    if linecounter in mult10K:
        print linecounter   
        print (datetime.now()-startTime)
infile1.close()
infile2.close()
outfile.close()
4

6 に答える 6

6

行を「単語」に分割し、辞書でこれらの単語のみを検索する必要があります。

>>> re.findall(r"\w+", "CHROMOSOME_IV ncRNA gene 5723085 5723105 . - . ID=Gene:WBGene00045518 CHROMOSOME_IV ncRNA ncRNA 5723085 5723105 . - . Parent=Gene:WBGene00045518")
['CHROMOSOME_IV', 'ncRNA', 'gene', '5723085', '5723105', 'ID', 'Gene', 'WBGene00045518', 'CHROMOSOME_IV', 'ncRNA', 'ncRNA', '5723085', '5723105', 'Parent', 'Gene', 'WBGene00045518']

これにより、1行ごとに行う辞書のループがなくなります。

完全なコードは次のとおりです。

import re

with open("f1.txt", "r") as infile1:
    udict = dict(line.strip().split("\t", 1) for line in infile1)

with open("f2.txt", "r") as infile2, open("out.txt", "w") as outfile:
    for line in infile2:
        for word in re.findall(r"\w+", line):
            if word in udict:
                line = line.replace(word, udict[word])
        outfile.write(line)

編集:別のアプローチは、辞書から単一のメガ正規表現を作成することです。

with open("f1.txt", "r") as infile1:
    udict = dict(line.strip().split("\t", 1) for line in infile1)
regex = re.compile("|".join(map(re.escape, udict)))
with open("f2.txt", "r") as infile2, open("out.txt", "w") as outfile:
    for line in infile2:
        outfile.write(regex.sub(lambda m: udict[m.group()], line))
于 2012-04-20T16:29:44.730 に答える
6

私は、辞書キーのループと、これを最適化するためのwqyaについて考えていました。後で、コードに他のコメントを付けましょう。

しかし、それから私はこの部分につまずきました:

if linecounter in mult10K:
    print linecounter   
    print (datetime.now()-startTime)

この無邪気な見た目のスニペットは、実際にPythonに、ファイルの各行の「linecounter」リストにある10000個のアイテムを順番に調べて比較させます。

この部品を次のものに置き換えます。

if linecounter % 10000 == 0:
    print linecounter   
    print (datetime.now()-startTime)

(そして、すべてのmult10kの部分を忘れてください)-そして、あなたはかなりのスピードアップを得るはずです。

また、入力ラインごとに複数の出力ラインを記録しているようです。メインループは次のようになります。

linecounter = 0
for line in infile2:
    for key, value in udict.items():
        matches = line.count(key)
        if matches > 0: 
            print key, value
            line = line.replace(key, value)
            outfile.write(line + '\n')
        else:
            outfile.write(line + '\n')
    linecounter += 1

これと交換してください:

for linecounter, line in enumerate(infile2):
    for key, value in udict.items():
        matches = line.count(key)
        if matches > 0: 
            print key, value
            line = line.replace(key, value)
    outfile.write(line + '\n')

これは、入力行ごとに1つの出力行のみを適切に書き込みます(コードの重複を排除し、「pythonic」方式で行のカウントを処理する以外)

于 2012-04-20T16:46:03.953 に答える
5

このコードは線形検索でいっぱいです。ゆっくりと動いているのも不思議ではありません。入力について詳しく知らなければ、これらの問題を解決する方法についてアドバイスすることはできませんが、少なくとも問題を指摘することはできます。大きな問題といくつかの小さな問題に注意します。

udict = {}
for line in infile1:
    line = line.strip()
    linelist = line.split('\t')
    udict1 = {linelist[0]:linelist[1]} 
    udict.update(udict1)

ここでは使用しないupdateでください。アイテムを辞書に追加するだけです。

    udict[linelist[0]] = linelist[1]

これは、すべてのエントリの辞書を作成するよりも高速です。(実際には、この辞書を作成するためのSven Marnachのジェネレータベースのアプローチの方が優れています。)ただし、これはかなりマイナーです。

mult10K = []
for x in range(100):
    mult10K.append(x * 10000)

これは完全に不要です。これを削除します。これなしで間隔を置いて印刷する1つの方法を紹介します。

linecounter = 0
for line in infile2:
    for key, value in udict.items():

これがあなたの最初の大きな問題です。各行について、その行のキーを辞書で線形検索しています。辞書が非常に大きい場合、これには膨大な数の操作が必要になります:100,000,000 * len(udict)。

        matches = line.count(key)

これは別の問題です。線形検索を使用して一致を探しています。次にreplace、同じ線形探索を実行します。一致するかどうかを確認する必要はありません。replace文字列がない場合は、同じ文字列を返します。これも大きな違いはありませんが、何かを得ることができます。

        line = line.replace(key, value)

これらの置換を続けて、すべての置換が完了した後でのみ行を書き込んでください。

    outfile.write(line + '\n')

そして最後に、

    linecounter += 1
    if linecounter in mult10K:

私を許してください、しかしこれはこれをするためのばかげた方法です!線形検索をlinecounter実行して、行を印刷するタイミングを決定します。ここでも、これにより、合計でほぼ100,000,000*100の操作が追加されます。少なくともセットで検索する必要があります。しかし、最善のアプローチ(本当にこれを行う必要がある場合)は、モジュロ演算を実行してそれをテストすることです。

    if not linecounter % 10000: 
        print linecounter   
        print (datetime.now()-startTime)

このコードを効率的にするには、これらの線形検索を取り除く必要があります。Sven Marnachの回答は、機能する1つの方法を示唆していますが、置換キーが明白な単語の境界に対応していない可能性があるため、ファイル内のデータに依存すると思います。(ただし、彼が追加した正規表現のアプローチは、それに対処します。)

于 2012-04-20T16:48:21.057 に答える
1

これはPython固有ではありませんが、ループのすべての反復でファイルの書き込みが発生しないように、doubleforループを少し展開することができます。おそらく、1000行または10,000行ごとにファイルに書き込みます。

于 2012-04-20T16:25:39.680 に答える
1

入力の各行に置換文字列の数を掛けたものに出力の行を書き込むことはバグであり、実際には入力ごとに1つの出力のみを書き込むことを意図していることを望んでいます。

入力行の一致をできるだけ早くテストする方法を見つける必要があります。辞書全体をループすることがおそらくあなたのボトルネックです。

正規表現は、非常に効率的なステートマシンにプリコンパイルされていると思います。巨大な表現を生成するとパフォーマンスがどのように低下​​するかはわかりませんが、試してみる価値はあります。

freakin_huge_re = re.compile('(' + ')|('.join(udict.keys()) + ')')
for line in infile2:
    matches = [''.join(tup) for tup in freakin_huge_re.findall(line)]
    if matches:
        for key in matches:
            line = line.replace(key, udict[key])
于 2012-04-20T17:37:56.387 に答える
-1

Pythonで明らかなのは、リスト内包表記です。これを行うには、より高速な(そしてより読みやすい)方法です。

mult10K = []
for x in range(100):
    mult10K.append(x * 10000)

このように:

mult10K = [x*10000 for x in range(100)]

同様に、あなたが持っているところ:

udict = {}
for line in infile1:
    line = line.strip()
    linelist = line.split('\t')
    udict1 = {linelist[0]:linelist[1]} 
    udict.update(udict1)

dict内包表記(ジェネレータ式を使用)を使用できます。

lines = (line.strip().split('\t') for line in infile1)
udict = {line[0]: line[1] for line in lines}

ここでは、タブ区切りファイルを使用しているように見えることにも注意してください。その場合、モジュールcsvを使用するよりもはるかに優れたオプションになる可能性がありますsplit()

また、ステートメントを使用するwith読みやすさが向上し、ファイルが確実に閉じられるようになります(例外がある場合でも)。

また、printステートメントは、すべてのループで実行されている場合、処理速度が大幅に低下します。デバッグには役立ちますが、データのメインチャンクで実行する場合は、削除する価値があります。

あなたができるもう一つの「よりpythonic」なことはenumerate()、毎回変数に1つ追加する代わりに使用することです。例えば:

linecounter = 0
for line in infile2:
   ...
   linecouter += 1

次のように置き換えることができます:

for linecounter, line in enumerate(infile2):
    ...

キーの発生をカウントする場合、より良い解決策は以下を使用することinです。

if key in line:

これはインスタンスを見つけた後に短絡するためです。

これらすべてを合計して、私たちが持っているものを見てみましょう:

import csv
from datetime import datetime
startTime = datetime.now()

with open('f1.txt', 'r') as infile1:
    reader = csv.reader(delimiter='\t')
    udict = dict(reader)

with open('f2.txt', 'r') as infile2, open('out.txt', 'w') as outfile:
    for line in infile2:
        for key, value in udict.items():
            if key in line: 
                line = line.replace(key, value)
        outfile.write(line + '\n')

編集:コメントで要求されているように、コンプと通常のループを一覧表示します。

python -m timeit "[i*10000 for i in range(10000)]"
1000 loops, best of 3: 909 usec per loop

python -m timeit "a = []" "for i in range(10000):" "  a.append(i)"
1000 loops, best of 3: 1.01 msec per loop

usecとmsecに注意してください。大きくはありませんが、何かです。

于 2012-04-20T16:27:12.953 に答える