Büyük bir .csv dosyası okunuyor


107

Şu anda Python 2.7'deki .csv dosyalarından 1 milyon satıra ve 200 sütuna kadar veri okumaya çalışıyorum (dosyalar 100mb'den 1.6gb'ye kadar değişir). Bunu (çok yavaş) 300.000 satırın altındaki dosyalar için yapabilirim, ancak bunun üstüne çıktığımda bellek hataları alıyorum. Kodum şöyle görünüyor:

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 fonksiyonundaki else cümlesinin nedeni, kritere uyan tüm elemanların csv dosyasında birlikte listeleneceğidir, bu yüzden onları geçince zaman kazanmak için döngüden çıkıyorum.

Sorularım:

  1. Bunun daha büyük dosyalarla çalışmasını nasıl sağlayabilirim?

  2. Daha hızlı yapmamın bir yolu var mı?

Bilgisayarımda 64bit Windows 7 çalıştıran 8 gb RAM var ve işlemci 3.40 GHz (hangi bilgilere ihtiyacınız olduğundan emin değil).


1
Benzer görünen birkaç soru olduğunun farkındayım, ancak hiçbiri sorunuma çok yardımcı olacak kadar spesifik görünmüyordu. Özür dilerim
Charles Dillon

2
Okunan verileri hafızada tutmak yerine bir veritabanında (örn. Sqlite) saklamalısınız. Ardından,
veritabanında

Yanıtlar:


159

Tüm satırları bir listeye okuyor, ardından bu listeyi işliyorsunuz. Bunu yapma .

Satırlarınızı üretirken işleyin. Önce verileri filtrelemeniz gerekiyorsa, bir oluşturucu işlevi kullanın:

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

Ayrıca filtre testinizi de basitleştirdim; mantık aynıdır ancak daha özlüdür.

Ölçütle eşleşen yalnızca tek bir satır dizisini eşleştirdiğiniz için, şunları da kullanabilirsiniz:

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

Artık getstuff()doğrudan dönebilirsiniz. Aynısını şurada yapın getdata():

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

Şimdi doğrudan getdata()kodunuzda döngü yapın:

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

Ölçüt başına binlerce satırınız yerine artık bellekte yalnızca bir satır tutuyorsunuz .

yieldbir işlevi bir jeneratör işlevi yapar , bu da siz üzerinde döngü oluşturana kadar hiçbir iş yapmayacağı anlamına gelir.


Bu tekniği kullanırken aynı hafıza verimliliğini elde ediyor csv.DictReadermusunuz? Çünkü 2.5GB .csv dosyası üzerinde yaptığım testler csv.reader, Python işleminin tam 2.5GB bellek kullanımına büyümesine neden olmak yerine bunu kullanırken bu şekilde satır satır yinelemeye çalışmanın olduğunu gösteriyor .
user5359531

@ user5359531, sözlük nesnelerine referansları bir yerde tuttuğunuzu gösterir. DictReader kendi başına referansları saklamaz, bu nedenle sorun başka yerde yatar.
Martijn Pieters

40

Martijin'in cevabı en iyisi olsa da. Yeni başlayanlar için büyük csv dosyalarını işlemenin daha sezgisel bir yolu. Bu, bir seferde satır gruplarını veya öbekleri işlemenizi sağlar.

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

9
Pandalar kullanmak onu neden daha sezgisel hale getiriyor?
Dünya Savaşı

25
4 satır kod, benim gibi yeni başlayanlar için her zaman daha iyidir.
mmann1123

3
Normal Python kodu da kısadır ve satır başına işlem yapmanıza izin verir. Jeneratör işlevi yalnızca öğeleri filtrelemek için vardır; Pandalar'da aynı filtrelemeyi nasıl yapardınız?
Martijn Pieters

1
Bu harika! Pandalar kullanarak büyük csv dosyalarını yükleme ve işleme sorunumu çözdüm. Teşekkürler!
Elsa Li

1
Bazı satırların içeriği birden fazla satıra yayıldığında bile çok iyi çalışır!
Dielson Satışları

19

Makul miktarda titreşim analizi yapıyorum ve büyük veri setlerine bakıyorum (onlarca ve yüz milyonlarca nokta). Benim test gösterdi pandas.read_csv () olmak işlevini 20 kat daha hızlı numpy.genfromtxt daha (). Ve genfromtxt () işlevi numpy.loadtxt () işlevinden 3 kat daha hızlıdır. Görünüşe göre büyük veri kümeleri için pandalara ihtiyacınız var .

Bu testte kullandığım kodu ve veri setlerini , titreşim analizi için MATLAB ve Python'u tartışan bir blogda yayınladım .


3
OP'nin ana sorunu hız değil, hafıza tükenmesiydi. Dosyayı işlemek için farklı bir işlev kullanmak, bir akış işlemcisi kullanmak yerine onu bir listeye okumanın olumsuz yönlerini ortadan kaldırmaz.
pydsigner

6

benim için işe yarayan şeydi ve süper hızlı

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)

Başka bir çalışma çözümü şudur:

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

gelmez df_train=df_train.compute()ilk çözeltide satır yapması gereken değil ne çalışıyor olduğu belleğe tüm veri kümesi ... yük?
Sam Dillard

3

Bu soruya inen biri için. Pandaları ' chunksize ' ve ' usecols ' ile kullanmak , büyük bir zip dosyasını önerilen diğer seçeneklerden daha hızlı okumama yardımcı oldu.

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

İşte Python3 için başka bir çözüm:

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

işte datareaderbir jeneratör işlevi.


Dolayısıyla bu, getiri operatörünü kullanan çözüm kadar verimli çalışır. : üzgünüm, öyle değil. Geri arama işlevi çağrısı, özellikle durumu açıkça ve ayrı ayrı ele almanız gerektiğinden, daha fazla ek yük getirir.
Martijn Pieters

@MartijnPieters Teşekkürler. Cevap güncellendi.
Rishabh Agrahari

0

Eğer kullanmayı deneyin pandaların kullanarak ve (belleğe tüm dosyayı okumak için yeterli) RAM çok sayıda varsa pd.read_csvile low_memory=Falseörneğin:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.