Membaca file .csv yang sangat besar

107

Saat ini saya mencoba membaca data dari file .csv dengan Python 2.7 hingga 1 juta baris, dan 200 kolom (file berkisar dari 100mb hingga 1.6gb). Saya dapat melakukan ini (sangat lambat) untuk file dengan kurang dari 300.000 baris, tetapi begitu saya pergi ke atas, saya mendapatkan kesalahan memori. Kode saya terlihat seperti ini:

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

Alasan klausa else dalam fungsi getstuff adalah bahwa semua elemen yang sesuai dengan kriteria akan dicantumkan bersama dalam file csv, jadi saya meninggalkan loop ketika saya melewatinya untuk menghemat waktu.

Pertanyaan saya adalah:

  1. Bagaimana saya bisa mengatur agar ini bekerja dengan file yang lebih besar?

  2. Apakah ada cara agar saya bisa membuatnya lebih cepat?

Komputer saya memiliki RAM 8GB, menjalankan Windows 7 64bit, dan prosesornya 3,40 GHz (tidak yakin informasi apa yang Anda butuhkan).

Charles Dillon
sumber
1
Saya sadar bahwa ada beberapa pertanyaan yang tampak serupa, tetapi tidak satupun dari pertanyaan itu tampaknya cukup spesifik untuk masalah saya sehingga dapat banyak membantu. Maaf jika ada yang terlewat.
Charles Dillon
2
Anda harus menyimpan data yang sudah dibaca dalam database (misalnya Sqlite) daripada menyimpannya di memori. Anda kemudian dapat menjalankan pemrosesan lebih lanjut seperti pemfilteran pada db
Michael Butscher

Jawaban:

159

Anda membaca semua baris ke dalam daftar, lalu memproses daftar itu. Jangan lakukan itu .

Proses baris Anda saat Anda membuatnya. Jika Anda perlu memfilter data terlebih dahulu, gunakan fungsi generator:

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

Saya juga menyederhanakan pengujian filter Anda; logikanya sama tetapi lebih ringkas.

Karena Anda hanya mencocokkan satu urutan baris yang cocok dengan kriteria, Anda juga bisa menggunakan:

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

Anda sekarang dapat mengulang getstuff()secara langsung. Lakukan hal yang sama di getdata():

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

Sekarang putar langsung ke getdata()kode Anda:

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

Anda sekarang hanya memiliki satu baris dalam memori, bukan ribuan baris per kriteria.

yieldmenjadikan suatu fungsi sebagai fungsi generator , yang berarti fungsi tidak akan berfungsi sampai Anda mulai mengulanginya.

Martijn Pieters
sumber
apakah Anda mendapatkan efisiensi memori yang sama saat menggunakan teknik ini dengan csv.DictReader? Karena pengujian saya pada file .csv 2.5GB menunjukkan bahwa mencoba mengulang baris demi baris seperti ini saat menggunakannya alih-alih csv.readermenyebabkan proses Python berkembang menjadi penggunaan memori 2.5GB penuh.
pengguna5359531
@ user5359531 yang akan menunjukkan Anda menyimpan referensi ke objek kamus di suatu tempat. DictReader dengan sendirinya tidak menyimpan referensi sehingga masalahnya ada di tempat lain.
Martijn Pieters
39

Meskipun jawaban Martijin adalah prob yang terbaik. Berikut adalah cara yang lebih intuitif untuk memproses file csv besar untuk pemula. Ini memungkinkan Anda memproses kelompok baris, atau potongan, pada satu waktu.

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)
mmann1123
sumber
9
Mengapa menggunakan panda membuatnya lebih intuitif?
Perang Dunia II
25
4 baris kode selalu lebih baik untuk pemula seperti saya.
mmann1123
3
Kode Python biasa sama pendeknya, dan memungkinkan Anda memproses per baris. Fungsi generator hanya ada untuk menyaring barang; bagaimana Anda akan melakukan pemfilteran yang sama di Pandas?
Martijn Pieters
1
Ini luar biasa! Memecahkan masalah saya memuat dan memproses file csv besar menggunakan panda. Terima kasih!
Elsa Li
1
Ia bekerja dengan sangat baik bahkan ketika konten beberapa baris terbentang di beberapa baris!
Penjualan Dielson
19

Saya melakukan cukup banyak analisis getaran dan melihat kumpulan data besar (puluhan dan ratusan juta poin). Pengujian saya menunjukkan fungsi pandas.read_csv () menjadi 20 kali lebih cepat daripada numpy.genfromtxt (). Dan fungsi genfromtxt () 3 kali lebih cepat dari numpy.loadtxt (). Tampaknya Anda membutuhkan panda untuk kumpulan data yang besar.

Saya memposting kode dan kumpulan data yang saya gunakan dalam pengujian ini di blog yang membahas MATLAB vs Python untuk analisis getaran .

Steve
sumber
3
Masalah utama OP bukanlah pada kecepatan, itu adalah salah satu kehabisan memori. Menggunakan fungsi yang berbeda untuk memproses file itu sendiri tidak menghilangkan kerugian dari membacanya ke dalam daftar daripada menggunakan prosesor aliran.
pydsigner
6

apa yang berhasil untuk saya dulu dan sekarang adalah supercepat

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)

Solusi kerja lainnya adalah:

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
Dompet Yury
sumber
bukankah df_train=df_train.compute()baris dalam solusi pertama Anda memuat seluruh kumpulan data ke dalam memori ... yang mana dia berusaha untuk tidak melakukannya?
Sam Dard
3

Untuk seseorang yang mendapatkan pertanyaan ini. Menggunakan panda dengan ' chunksize ' dan ' usecols ' membantu saya membaca file zip besar lebih cepat daripada opsi lain yang diusulkan.

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)
ewalel
sumber
1

inilah solusi lain untuk 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

di sini datareaderadalah fungsi generator.

Rishabh Agrahari
sumber
Jadi, ini bekerja seefisien solusi yang menggunakan operator hasil. : maaf, tidak. Panggilan fungsi callback menambahkan lebih banyak overhead, terutama karena Anda di sana harus menangani status secara eksplisit dan terpisah.
Martijn Pieters
@MartijnPieters Terima kasih. Memperbarui jawabannya.
Rishabh Agrahari
0

Jika Anda menggunakan panda dan memiliki banyak RAM (cukup untuk membaca seluruh file ke dalam memori) coba gunakan pd.read_csvdengan low_memory=False, misalnya:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
Mike T
sumber