巨大な.csvファイルの読み取り


107

現在、最大100万行、200列のPython 2.7の.csvファイルからデータを読み取ろうとしています(ファイルの範囲は100mbから1.6gbです)。300,000行未満のファイルに対してこれを(非常にゆっくりと)実行できますが、それを超えるとメモリエラーが発生します。私のコードは次のようになります:

def getdata(filename, criteria):
    data=[]
    for criterion in criteria:
        data.append(getstuff(filename, criteron))
    return data

def getstuff(filename, criterion):
    import csv
    data=[]
    with open(filename, "rb") as csvfile:
        datareader=csv.reader(csvfile)
        for row in datareader: 
            if row[3]=="column header":
                data.append(row)
            elif len(data)<2 and row[3]!=criterion:
                pass
            elif row[3]==criterion:
                data.append(row)
            else:
                return data

getstuff関数のelse句の理由は、基準に一致するすべての要素がcsvファイルにまとめてリストされるため、時間を節約するためにそれらを通過したときにループを終了するためです。

私の質問は:

  1. これを大きなファイルで動作させるにはどうすればよいですか?

  2. 速くする方法はありますか?

私のコンピューターには8 GBのRAMがあり、64ビットのWindows 7を実行しています。プロセッサーは3.40 GHzです(必要な情報は不明)。


1
似たような質問がいくつかあることは承知していますが、それらのどれも私の問題に十分に特定できるほど具体的ではないようです。見逃したものがあれば申し訳ありません。
Charles Dillon

2
読み取ったデータをメモリに保存する代わりに、データベース(Sqliteなど)に保存する必要があります。その後、dbでのフィルタリングのようなさらなる処理を実行できます
Michael Butscher

回答:


158

すべての行をリストに読み込んでから、そのリストを処理しています。しないでください

行を生成しながら処理します。最初にデータをフィルタリングする必要がある場合は、ジェネレーター関数を使用します。

import csv

def getstuff(filename, criterion):
    with open(filename, "rb") as csvfile:
        datareader = csv.reader(csvfile)
        yield next(datareader)  # yield the header row
        count = 0
        for row in datareader:
            if row[3] == criterion:
                yield row
                count += 1
            elif count:
                # done when having read a consecutive series of rows 
                return

フィルターテストも簡略化しました。ロジックは同じですが、より簡潔です。

条件に一致する行の単一のシーケンスのみを一致させるため、次のように使用することもできます。

import csv
from itertools import dropwhile, takewhile

def getstuff(filename, criterion):
    with open(filename, "rb") as csvfile:
        datareader = csv.reader(csvfile)
        yield next(datareader)  # yield the header row
        # first row, plus any subsequent rows that match, then stop
        # reading altogether
        # Python 2: use `for row in takewhile(...): yield row` instead
        # instead of `yield from takewhile(...)`.
        yield from takewhile(
            lambda r: r[3] == criterion,
            dropwhile(lambda r: r[3] != criterion, datareader))
        return

これでgetstuff()直接ループできます。同じことをgetdata()

def getdata(filename, criteria):
    for criterion in criteria:
        for row in getstuff(filename, criterion):
            yield row

getdata()コードで直接ループします。

for row in getdata(somefilename, sequence_of_criteria):
    # process row

基準ごとに数千ではなく、メモリに1行しか保持しなくなりました。

yield関数をジェネレータ関数にします。つまり、ループを開始するまで関数は機能しません。


このテクニックを使用すると、同じメモリ効率が得られcsv.DictReaderますか?2.5GBの.csvファイルでのテストでcsv.readerは、Pythonプロセスが2.5GBのメモリ使用量まで増加する代わりに、それを使用するときにこのように行ごとに反復しようとすることが示されているためです。
user5359531

@ user5359531これは、辞書オブジェクトへの参照をどこかに保持していることを示します。DictReader自体は参照を保持しないため、問題は別の場所にあります。
Martijn Pieters

39

マルティジンの答えは確率が高いですが。これは、初心者向けの大きなcsvファイルを処理するためのより直感的な方法です。これにより、行またはチャンクのグループを一度に処理できます。

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)

9
パンダを使用するとなぜ直感的になるのですか?
wwii

25
私のような初心者には、常に4行のコードが適しています。
mmann1123 2017

3
通常のPythonコードも同じくらい短く、行ごとに処理できます。ジェネレータ関数は、何かをフィルタリングするためだけにあります。パンダで同じフィルタリングを行うにはどうしますか?
Martijn Pieters

1
これはすごい!パンダを使用して大きなcsvファイルをロードおよび処理するという私の問題を解決しました。ありがとう!
Elsa Li

1
一部の行のコンテンツが複数の行にまたがる場合でも非常にうまく機能します!
Dielsonセールス

19

私はかなりの量の振動解析を行い、大規模なデータセット(数千億点)を調べます。私のテストでは、pandas.read_csv()関数がnumpy.genfromtxt()より20倍高速であることが示されました。また、genfromtxt()関数は、numpy.loadtxt()より3倍高速です。大きなデータセットにはパンダが必要なようです。

このテストで使用したコードとデータセットを、振動解析用のMATLABとPythonについて議論しているブログに投稿しました。


3
OPの主な問題は速度の問題ではなく、メモリの枯渇の問題でした。ファイル自体を処理するために別の関数を使用しても、ストリームプロセッサを使用するのではなく、ファイルをリストに読み込むことの欠点が取り除かれません。
pydsigner

6

私にとってうまくいったのは、超高速です

import pandas as pd
import dask.dataframe as dd
import time
t=time.clock()
df_train = dd.read_csv('../data/train.csv', usecols=[col1, col2])
df_train=df_train.compute()
print("load train: " , time.clock()-t)

別の実用的な解決策は:

import pandas as pd 
from tqdm import tqdm

PATH = '../data/train.csv'
chunksize = 500000 
traintypes = {
'col1':'category',
'col2':'str'}

cols = list(traintypes.keys())

df_list = [] # list to hold the batch dataframe

for df_chunk in tqdm(pd.read_csv(PATH, usecols=cols, dtype=traintypes, chunksize=chunksize)):
    # Can process each chunk of dataframe here
    # clean_data(), feature_engineer(),fit()

    # Alternatively, append the chunk to list and merge all
    df_list.append(df_chunk) 

# Merge all dataframes into one dataframe
X = pd.concat(df_list)

# Delete the dataframe list to release memory
del df_list
del df_chunk

df_train=df_train.compute()最初のソリューションの行はデータセット全体をメモリにロードしていません...これは彼がやろうとしていないことです?
サムディラード

3

この質問に到達した人のために。「chunksize」と「usecols」でパンダを使用すると、他の提案されたオプションよりも高速に巨大なzipファイルを読み取ることができました。

import pandas as pd

sample_cols_to_keep =['col_1', 'col_2', 'col_3', 'col_4','col_5']

# First setup dataframe iterator, ‘usecols’ parameter filters the columns, and 'chunksize' sets the number of rows per chunk in the csv. (you can change these parameters as you wish)
df_iter = pd.read_csv('../data/huge_csv_file.csv.gz', compression='gzip', chunksize=20000, usecols=sample_cols_to_keep) 

# this list will store the filtered dataframes for later concatenation 
df_lst = [] 

# Iterate over the file based on the criteria and append to the list
for df_ in df_iter: 
        tmp_df = (df_.rename(columns={col: col.lower() for col in df_.columns}) # filter eg. rows where 'col_1' value grater than one
                                  .pipe(lambda x:  x[x.col_1 > 0] ))
        df_lst += [tmp_df.copy()] 

# And finally combine filtered df_lst into the final lareger output say 'df_final' dataframe 
df_final = pd.concat(df_lst)

1

Python3の別のソリューションを次に示します。

import csv
with open(filename, "r") as csvfile:
    datareader = csv.reader(csvfile)
    count = 0
    for row in datareader:
        if row[3] in ("column header", criterion):
            doSomething(row)
            count += 1
        elif count > 2:
            break

これdatareaderはジェネレータ関数です。


したがって、これは、yield演算子を使用するソリューションと同じくらい効率的に機能します。:申し訳ありませんが、ありません。特に状態を明示的かつ個別に処理する必要があるため、コールバック関数呼び出しによりオーバーヘッドが増加します。
Martijn Pieters

@MartijnPietersありがとう。回答を更新しました。
リシャブアグラハリ

0

パンダを使用していて、RAMがたくさんある場合(ファイル全体をメモリに読み込むのに十分なほど)、で使用pd.read_csvしてみてくださいlow_memory=False。例:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.