web-dev-qa-db-ja.com

複数のファイルの読み取りを高速化し、データをデータフレームに入れるにはどうすればよいですか?

大量のデータフレームに読み込む必要のある多数のテキストファイル(たとえば50)があります。現時点では、次の手順を使用しています。

  1. すべてのファイルを読み、ラベルが何であるかを確認します。必要な情報は、多くの場合、最初の数行に含まれています。同じラベルがファイルの残りの部分で繰り返され、そのたびに異なるタイプのデータがリストされます。
  2. これらのラベルでデータフレームを作成します。
  3. ファイルを再度読み取り、データフレームに値を入力します。
  4. そのデータフレームをマスターデータフレームと連結します。

これは、サイズが100 KBのファイル(数分、しかし50 MBの場合は数時間しかかからず、実用的ではないファイル)に対して非常にうまく機能します。

コードを最適化するにはどうすればよいですか?特に -

  1. どの関数が最も時間がかかっているかを特定するには、最適化する必要がありますか?ファイルの読み取りですか?データフレームへの書き込みですか?私のプログラムはどこで時間を過ごしていますか?
  2. マルチスレッドまたはマルチプロセッシングを検討する必要がありますか?
  3. アルゴリズムを改善できますか?
    • おそらく、ファイル全体を1行で読み込むのではなく、1行ずつ読み込むのではなく、
    • 行ごとではなく、チャンク/ファイル全体でデータを解析し、
    • データを行ごとではなく、チャンク単位でデータフレームに割り当てます。
  4. コードをより速く実行するためにできることは他にありますか?

以下にコード例を示します。私のコードはもう少し複雑です。テキストファイルがより複雑で、約10個の正規表現と複数のwhileループを使用してデータを読み取り、適切な配列の適切な場所に割り当てる必要があるためです。 MWEをシンプルに保つために、MWEの入力ファイルでもラベルを繰り返し使用していません。そのため、理由もなくファイルを2回読み取りたいと思います。それが理にかなっていることを願っています!

import re
import pandas as pd

df = pd.DataFrame()
paths = ["../gitignore/test1.txt", "../gitignore/test2.txt"]
reg_ex = re.compile('^(.+) (.+)\n')
# read all files to determine what indices are available
for path in paths:
    file_obj = open(path, 'r')
    print file_obj.readlines()

['a 1\n', 'b 2\n', 'end']
['c 3\n', 'd 4\n', 'end']

indices = []
for path in paths:
    index = []
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                index += match.group(1)
            except AttributeError:
                pass
    indices.append(index)
# read files again and put data into a master dataframe
for path, index in Zip(paths, indices):
    subset_df = pd.DataFrame(index=index, columns=["Number"])
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                subset_df.loc[[match.group(1)]] = match.group(2)
            except AttributeError:
                pass
    df = pd.concat([df, subset_df]).sort_index()
print df

  Number
a      1
b      2
c      3
d      4

私の入力ファイル:

test1.txt

a 1
b 2
end

test2.txt

c 3
d 4
end
39
bluprince13

最初に空のDataFrameを作成し、インデックスを検索してデータ行の適切な場所を見つけてから、DataFrameのその1行だけを更新するのはばかげて時間のかかるプロセスです。

これを行うより速い方法は、入力ファイルの内容をリストのリストや辞書のリストなどのプリミティブなデータ構造に読み込み、それをDataFrameに変換することです。

読み込んでいるすべてのデータが同じ列にある場合、リストを使用します。それ以外の場合は、dictsを使用して、データの各ビットがどの列に行くべきかを明示的に示します。

1月18日更新:これは にリンクされていますPythonを使用して複雑なテキストファイルを解析する方法は? 複雑なファイルを初心者に解析する方法を説明する ブログ記事

1
bluprince13

マルチプロセッシングハンマーを引き出す前に、最初のステップはプロファイリングを行うことです。 cProfileを使用して、すばやく調べて、どの関数に時間がかかっているかを特定します。残念ながら、すべての行が1つの関数呼び出しに含まれている場合、それらはライブラリ呼び出しとして表示されます。 line_profilerは優れていますが、セットアップに少し時間がかかります。

注意。 ipythonを使用している場合、%timeit(timeitモジュールのマジックコマンド)と%prun(プロファイルモジュールのマジックコマンド)を使用して、ステートメントと関数の両方のタイミングを調整できます。 Google検索では、いくつかのガイドが表示されます。

パンダは素晴らしいライブラリですが、私はそれをひどく使用して、ひどい結果を招くこともあります。特に、append()/ concat()操作には注意してください。それがあなたのボトルネックかもしれませんが、確実にプロファイルする必要があります。通常、インデックス/列のアライメントを実行する必要がない場合、numpy.vstack()およびnumpy.hstack()操作は高速です。あなたのケースでは、時間を節約できるSeriesまたは1-D numpy ndarraysでうまくいくようです。

ところで、pythonのtryブロックは、無効な条件をチェックするよりも10倍以上遅いことが多いので、毎回ループに貼り付けるときは絶対に必要です。これはおそらくもう1つの時間です; match.group(1)が失敗した場合にtryErrorブロックをチェックしてAttributeErrorをチェックしたと思いますが、最初に有効な一致をチェックします。

これらの小さな変更でも、マルチプロセッシングのような抜本的なことを試みる前に、プログラムを非常に高速に実行するのに十分なはずです。これらのPythonライブラリは素晴らしいですが、対処すべき新たな課題をもたらします。

16
clocker

マルチプロセッシングの特定の簡単な実装であるため、これを何度も使用しました。

import pandas as pd
from multiprocessing import Pool

def reader(filename):
    return pd.read_Excel(filename)

def main():
    pool = Pool(4) # number of cores you want to use
    file_list = [file1.xlsx, file2.xlsx, file3.xlsx, ...]
    df_list = pool.map(reader, file_list) #creates a list of the loaded df's
    df = pd.concat(df_list) # concatenates all the df's into a single df

if __== '__main__':
    main()

これを使用すると、あまり多くの作業をすることなく、プログラムの速度を大幅に上げることができるはずです。プロセッサの数がわからない場合は、シェルを引き上げて入力して確認できます

echo %NUMBER_OF_PROCESSORS%

編集:これをさらに速く実行するには、ファイルをcsvsに変更し、pandas function pandas.read_csv

15
Некто

まず、ファイルを複数回読んでいる場合、それがボトルネックになるようです。ファイルを1つの文字列オブジェクトに読み取ってから、cStringIOを複数回使用してみてください。

第二に、すべてのファイルを読み込む前にインデックスを作成する理由を実際に示していません。たとえそうだとしても、なぜIOにPandasを使用していますか?通常のpythonデータ構造(おそらく__slots__)その後、マスターデータフレームに配置します(2番目のループが示唆するように)ファイルYを読み取る前にファイルXのインデックスが必要ない場合は、ファイルを1回ループするだけです。

3番目に、文字列で単純なsplit/stripを使用してスペースで区切られたトークンを引き出すか、より複雑な場合(文字列引用符などがある)のCSVモジュールを使用できます。 Pythonの標準ライブラリ。実際にデータを構築する方法を示すまで、それに関連する修正を提案することは困難です。

これまでに示してきたことは、単純な

for path in paths:
    data = []
    with open(path, 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = line.strip().split()
            except ValueError:
                pass
            data.append(d1, int(d2)))
    index, values = Zip(*data)
    subset_df = pd.DataFrame({"Number": pd.Series(values, index=index)})

ディスク領域が事前に割り当てられていない仮想マシンで実行する場合のタイミングの違いは次のとおりです(生成されるファイルのサイズは約24MBです)。

import pandas as pd
from random import randint
from itertools import combinations
from posix import fsync


outfile = "indexValueInput"

for suffix in ('1', '2'):
    with open(outfile+"_" + suffix, 'w') as f:
        for i, label in enumerate(combinations([chr(i) for i in range(ord('a'), ord('z')+1)], 8)) :
            val = randint(1, 1000000)
            print >>f, "%s %d" % (''.join(label), val)
            if i > 3999999:
                break
        print >>f, "end"
        fsync(f.fileno())

def readWithPandas():
    data = []
    with open(outfile + "_2", 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = str.split(line.strip())
            except ValueError:
                pass
            data.append((d1, int(d2)))
    index, values = Zip(*data)
    subset_df = pd.DataFrame({"Numbers": pd.Series(values, index=index)})

def readWithoutPandas():
    data = []
    with open(outfile+"_1", 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = str.split(line.strip())
            except ValueError:
                pass
            data.append((d1, int(d2)))
    index, values = Zip(*data)

def time_func(func, *args):
    import time
    print "timing function", str(func.func_name)
    tStart = time.clock()
    func(*args)
    tEnd = time.clock()
    print "%f seconds " % (tEnd - tStart)

time_func(readWithoutPandas)
time_func(readWithPandas)

結果の時間は次のとおりです。

timing function readWithoutPandas
4.616853 seconds 
timing function readWithPandas
4.931765 seconds 

これらの関数をインデックスビルドアップで試して、時間の違いを確認できます。速度低下が複数のディスク読み取りに起因することはほぼ確実です。また、Pandasは辞書からデータフレームを作成するのに時間がかかりませんので、純粋なPython以前にインデックスを作成する方法を考えた方が良いでしょうデータをPandasに渡しますが、データの読み取りと1ディスク読み取りでのインデックスの作成の両方を行います。

もう1つの注意点は、コードの内部から印刷する場合、膨大な時間がかかることを期待することです。 ttyにプレーンテキストを書き込むのにかかる時間は、ディスクの読み取り/書き込みにかかる時間を短縮します。

3

一般python考慮事項:

まず、時間測定については、このようなスニペットを使用できます。

from time import time, sleep


class Timer(object):
    def __init__(self):
        self.last = time()


    def __call__(self):
        old = self.last
        self.last = time()
        return self.last - old

    @property
    def elapsed(self):
        return time() - self.last



timer = Timer()

sleep(2)
print timer.elapsed
print timer()
sleep(1)
print timer()

その後、実行中のコードを何度もベンチマークし、差分を確認できます。

これについて、私はインラインでコメントします:

with open(path, 'r') as file_obj:
    line = True
    while line: #iterate on realdines instead.
        try:
            line = file_obj.readline()
            match = reg_ex.match(line)
            index += match.group(1)
            #if match:
            #    index.extend(match.group(1)) # or extend

        except AttributeError:
            pass

以前のコードは実際にはPythonicではありません。試してみたり、除外したりすることができます。次に、最小限の行でdo inのみを試してください。

同じ注意がコードの2番目のブロックにも適用されます。

同じファイルを複数回読む必要がある場合。 StringIOを使用してRAMに保存するか、{path:content}ディクショナリを1回だけ読むように維持することができます。

Pythonの正規表現は遅いことが知られており、データは非常に単純なようです。入力ラインでsplitメソッドとstripメソッドを使用することを検討してください。

 striped=[l.split() for l in [c.strip() for c in file_desc.readlines()] if l] 

これを読むことをお勧めします: https://Gist.github.com/JeffPaine/6213790 対応するビデオはこちら https://www.youtube.com/watch?v=OSGv2VnC0go

2
cgte

マルチプロセッシングモデルをインポートし、ワーカープロセスのプールを使用して、複数のファイルをファイルオブジェクトとして同時に開き、コードの読み込み部分を高速化できます。時間をテストするには、datetime関数をインポートし、次のコードを使用します。

import datetime
start=datetime.datetime.now()

#part of your code goes here

execTime1=datetime.datetime.now()
print(execTime1-start)

#the next part of your code goes here

execTime2=datetime.datetime.now()
print(execTime2-execTime1)

各ファイルを1回だけ読み取る場合は、別のマルチプロセッシングスクリプトを使用して各ファイルの行のリストを作成し、ファイルI/O操作なしで一致をチェックできるようにすることを検討してください。

1
Ron Distante

Pd.read_csvを使用して、ファイルをpandasデータフレームに直接読み込みます。subset_dfを作成するには、skipfooterなどのメソッドを使用して、不要なファイルの最後の行をスキップします。 error_bad_linesやskip_blank_linesなど、使用している正規表現ループ関数の一部を置き換える可能性のある、さらに多くのメソッドが利用可能です。

次に、pandasによって提供されるツールを使用して、不要なデータを削除します。

これにより、開いているファイルを読み取り、ファイルを1回だけ読み取ることができます。

1
blindChicken

あなたのコードはあなたが説明したことをしません。

質問:1.すべてのファイルを読み取り、ラベルを確認します。必要な情報は、多くの場合、最初の数行に含まれています。

しかし、数行だけでなくwholeファイルを読みます。この結果、ファイルが読み取られますtwice

質問:2.ファイルを再度読み取り、データフレームに値を入力します。

ループ内のdf['a'|'b'|'c'|'d']を何度も上書きしますが、これは役に立ちません
これはあなたが望むものではないと信じています。


別のロジックを使用した提案:

data = {}
for path in paths:
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                if match.group(1) not in data:
                    data[ match.group(1) ] = []

                data[match.group(1)].append( match.group(2) )
            except AttributeError:
                pass

print('data=%s' % data)
df = pd.DataFrame.from_dict(data, orient='index').sort_index()
df.rename(index=str, columns={0: "Number"}, inplace=True)  

出力

data={'b': ['2'], 'a': ['1'], 'd': ['4'], 'c': ['3']}
<class 'pandas.core.frame.DataFrame'>
Index: 4 entries, a to d
Data columns (total 1 columns):
Number    4 non-null object
dtypes: object(1)
memory usage: 32.0+ bytes
  Number
a      1
b      2
c      3
d      4  

タイムテーブル

             Code from Q:   to_dict_from_dict
    4 values 0:00:00.033071 0:00:00.022146
 1000 values 0:00:08.267750 0:00:05.536500
10000 values 0:01:22.677500 0:00:55.365000

Python:3.4.2でテスト済み-pandas:0.19.2-re:2.2.1

1
stovfl

1ファイル用に1つの出力テンプレートを作成します(結果データフレームに列A、B Cが必要です)

2すべてのファイルを読み取り、出力テンプレート(ステップ1で確立された)に変換し、temp_idxx.csvのようなファイルを保存します。これは並行して実行できます:)

3これらのtemp_idxx.csvファイルを1つの巨大なファイルに連結し、一時ファイルを削除します

この手順の長所は、並列で実行できることであり、出力形式を作成してそれに固執しているすべてのメモリの欠点を消費することはありません。

1
quester

まず、スクリプトにプロファイラーを使用します( この質問を参照) 。どの部分がより多くの時間を消費しているかを正確に分析します。最適化できるかどうかを確認してください。

第二に、I/O操作ファイルの読み取りがボトルネックである可能性が高いと感じています。並行アプローチを使用して最適化できます。ファイルを同時に読み取り、データフレームを作成することをお勧めします。各スレッドは、新しく作成されたデータフレームをキューにプッシュできます。メインスレッド監視キューは、キューからデータフレームを取得し、それをマスターデータフレームとマージできます。

お役に立てれば。

1
EngineeredBrain