1

私はよく、数列(通常は10未満)と最大数千万行を含むASCIIテーブルを扱います。彼らはのように見えます

176.792 -2.30523 0.430772 32016 1 1 2 
177.042 -1.87729 0.430562 32016 1 1 1
177.047 -1.54957 0.431853 31136 1 1 1
...
177.403 -0.657246 0.432905 31152 1 1 1

ファイルを読み取り、操作し、保存するPythonコードがいくつかあります。私はいつもそれを使ってきnumpy.loadtxtましnumpy.savetxtた。ただし、numpy.loadtxt1GbのASCIIファイルを読み取るには少なくとも5〜6GbのRAMが必要です。

昨日、私はパンダを発見しました。これにより、ほとんどすべての問題が解決pandas.read_tablenumpy.savetxtました。メモリ効率が非常に高く、スクリプトの実行速度(2)が3倍または4倍向上しました。

最初にコメント行がいくつか含まれているファイルを読み込もうとするまでは、すべて問題ありません。doc文字列(v = 0.10.1.dev_f73128e)は、行コメントがサポートされていないことを示しています。これは素晴らしいことだと思います。の行コメントを除外するのが本当に好きnumpy.loadtxtです。これがどのように利用可能になるかについてのアイデアはありますか?それらの行をスキップする可能性があるのもいいでしょう(ドキュメントはそれらがempyとして返されると述べています)

ファイルにコメント行がいくつあるかわからない(さまざまな人からのコメント行を何千も処理している)ので、ファイルを開いて、ファイルの先頭にあるコメントで始まる行数を数えます。

def n_comments(fn, comment):
    with open(fname, 'r') as f:
        n_lines = 0
        pattern = re.compile("^\s*{0}".format(comment))
        for l in f:
            if pattern.search(l) is None:
                break
            else:
                n_lines += 1
    return n_lines

その後

pandas.read_table(fname, skiprows=n_comments(fname, '#'), header=None, sep='\s')

それを行うためのより良い方法(おそらくパンダ内)はありますか?

pandas.io.parsers.py最後に、投稿する前に、内部でどのように機能するかを理解するためにコードを少し調べましたが、pandas.read_table迷子になりました。誰かがファイルの読み取りを実装する場所を教えてもらえますか?

ありがとう

EDIT2: @ThorstenKranzifの2番目の実装のいくつかを取り除くためにいくつかの改善を得ると思いましたがFileWrapper、ほとんど改善されませんでした

class FileWrapper(file):
    def __init__(self, comment_literal, *args):
        super(FileWrapper, self).__init__(*args)
        self._comment_literal = comment_literal
        self._next = self._next_comment

    def next(self):
        return self._next()

    def _next_comment(self):
        while True:
            line = super(FileWrapper, self).next()
            if not line.strip()[0] == self._comment_literal:
                self._next = self._next_no_comment
                return line
    def _next_no_comment(self):
        return super(FileWrapper, self).next()
4

2 に答える 2

3

read_csvコメント文字から行末までのバイトをスキップread_tableするオプションがあります。行全体commentをスキップする必要がある場合、パーサーはフィールドが含まれていない行が表示されたと見なし、最終的に有効なデータ行が表示されて混乱するため、これは正しくありません。

回避策を使用して、ファイル内で手動でスキップする行数を決定することをお勧めします。行全体がコメントである場合に、行を自動的にスキップできるようにするオプションがあると便利です。

https://github.com/pydata/pandas/issues/2685

これをうまく実装するには、Cトークナイザーコードに浸る必要があります。聞こえるほど悪くはありません。

于 2013-01-11T14:57:15.980 に答える
2

継承するクラスを作成することで、コンパクトなソリューションを見つけましたfile

import pandas as pd

class FileWrapper(file):
    def __init__(self, comment_literal, *args):
        super(FileWrapper, self).__init__(*args)
        self._comment_literal = comment_literal

    def next(self):
        while True:
            line = super(FileWrapper, self).next()
            if not line.startswith(self._comment_literal):
                return line

df = pd.read_table(FileWrapper("#", "14276661.txt", "r"), delimiter=" ", header=None)

Atm、pandas(0.8.1)は、.next()ファイルのようなオブジェクトを反復処理するために-methodのみを使用します。このメソッドをオーバーロードして、専用のコメントで始まらない行のみを返すことができます-私の例ではリテラル"#"です。

入力ファイルの場合:

176.792 -2.30523 0.430772 32016 1 1 2 
# 177.042 -1.87729 0.430562 32016 1 1 1
177.047 -1.54957 0.431853 31136 1 1 1
177.403 -0.657246 0.432905 31152 1 1 1

我々が得る

>>> df
       X.1       X.2       X.3    X.4  X.5  X.6  X.7
0  176.792 -2.305230  0.430772  32016    1    1    2
1  177.047 -1.549570  0.431853  31136    1    1    1
2  177.403 -0.657246  0.432905  31152    1    1    1

とのために

176.792 -2.30523 0.430772 32016 1 1 2 
177.042 -1.87729 0.430562 32016 1 1 1
177.047 -1.54957 0.431853 31136 1 1 1
177.403 -0.657246 0.432905 31152 1 1 1

我々が得る

>>> df
       X.1       X.2       X.3    X.4  X.5  X.6  X.7
0  176.792 -2.305230  0.430772  32016    1    1    2
1  177.042 -1.877290  0.430562  32016    1    1    1
2  177.047 -1.549570  0.431853  31136    1    1    1
3  177.403 -0.657246  0.432905  31152    1    1    1

継承の代わりに委任を使用することもできますが、それはあなたの好み次第です。

編集 私はパフォーマンスを改善するために他の多くの方法を試しました。しかし、それは大変な仕事です。私は試した

  • スレッド化:低レベルのio操作と大きなチャンクを使用して1つのスレッドでファイルを先読みし、行に分割し、これらをキューに入れて、キューからのみ取得します。next()
  • マルチプロセッシングと同じ
  • 同様のマルチスレッドアプローチですが、readlines(size_hint)
  • mmapファイルから読み取るため

最初の3つのアプローチは驚くほど遅いので、メリットはありません。使用するとmmap、パフォーマンスが大幅に向上しました。コードは次のとおりです。

class FileWrapper(file):
    def __init__(self, comment_literal, *args):
        super(FileWrapper, self).__init__(*args)
        self._comment_literal = comment_literal
        self._in_comment = True
        self._prepare()

    def __iter__(self):
        return self

    def next(self):
        if self._in_comment:
            while True:
                line = self._get_next_line()
                if line == "":
                    raise StopIteration()
                if not line[0] == self._comment_literal:
                    self._in_comment = False
                    return line
        line = self._get_next_line()
        if line == "":
            raise StopIteration()
        return line

    def _get_next_line(self):
        return super(FileWrapper, self).next()

    def _prepare(self):
        pass

class MmapWrapper(file):
    def __init__(self, fd, comment_literal = "#"):
        self._mm = mmap.mmap(fd, 0, prot=mmap.PROT_READ)
        self._comment_literal = comment_literal
        self._in_comment = True

    def __iter__(self):
        return self #iter(self._mm.readline, "")#self

    def next(self):
        if self._in_comment:
            while True:
                line = self._mm.readline()
                if line == "":
                    raise StopIteration()
                if not line[0] == self._comment_literal:
                    self._in_comment = False
                    return line
        line = self._mm.readline()
        if line == "":
            raise StopIteration()
        return line

if __name__ == "__main__":
    t0 = time.time()    
    for i in range(10):    
        with open("1gram-d_1.txt", "r+b") as f:
            df1 = pd.read_table(MmapWrapper(f.fileno()), delimiter="\t", header=None)
    print "mmap:", time.time()-t0

    t0 = time.time()    
    for i in range(10):    
        df2 = pd.read_table(FileWrapper("#", "1gram-d_1.txt", "r"), delimiter="\t", header=None)
    print "Unbuffered:", time.time()-t0

    print (df1==df2).mean()

出力として与える

mmap: 35.3251504898
Unbuffered: 41.3274121284
X.1    1
X.2    1
X.3    1
X.4    1

また、コメント以外の最初の行が見つかるまでコメントチェックを実装しました。これはソリューションに準拠し、パフォーマンスをさらに向上させます。

ただしmmap、sにはいくつかの制限があります。ファイルサイズが大きい場合は、十分なRAMを確保してください。32ビットOSで作業している場合、2GBを超えるファイルを読み取ることはできません。

于 2013-01-11T13:51:12.883 に答える