Czytanie dużego pliku .csv

107

Obecnie próbuję odczytać dane z plików .csv w Pythonie 2.7 z maksymalnie 1 milionem wierszy i 200 kolumnami (zakres plików od 100 MB do 1,6 GB). Mogę to zrobić (bardzo powoli) dla plików z mniej niż 300 000 wierszy, ale gdy przejdę powyżej, pojawiają się błędy pamięci. Mój kod wygląda tak:

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

Powodem zastosowania klauzuli else w funkcji getstuff jest to, że wszystkie elementy, które pasują do kryterium, zostaną wymienione razem w pliku csv, więc zostawiam pętlę, gdy mijam je, aby zaoszczędzić czas.

Moje pytania to:

  1. Jak mogę to zrobić z większymi plikami?

  2. Czy jest jakiś sposób, żebym mógł to przyspieszyć?

Mój komputer ma 8 GB pamięci RAM, 64-bitowy system Windows 7, a procesor ma 3,40 GHz (nie wiem, jakich informacji potrzebujesz).

Charles Dillon
źródło
1
Zdaję sobie sprawę, że istnieje kilka podobnych pozornych pytań, ale żadne z nich nie wydawało się być na tyle szczegółowe, by pomóc mi w bardzo dużym stopniu. Przepraszam, jeśli jest taki, który przegapiłem.
Charles Dillon,
2
Odczytane dane należy przechowywać w bazie danych (np. Sqlite) zamiast przechowywać je w pamięci. Następnie możesz uruchomić dalsze przetwarzanie, takie jak filtrowanie na bazie danych
Michael Butscher,

Odpowiedzi:

159

Wczytujesz wszystkie wiersze na listę, a następnie przetwarzasz tę listę. Nie rób tego .

Przetwarzaj wiersze w miarę ich tworzenia. Jeśli musisz najpierw przefiltrować dane, użyj funkcji generatora:

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

Uprościłem również twój test filtra; logika jest taka sama, ale bardziej zwięzła.

Ponieważ dopasowujesz tylko jedną sekwencję wierszy pasujących do kryterium, możesz również użyć:

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

Możesz teraz wykonać pętlę getstuff()bezpośrednio. Zrób to samo w getdata():

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

Teraz pętla bezpośrednio getdata()w kodzie:

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

Teraz masz w pamięci tylko jeden wiersz , zamiast tysięcy wierszy na kryterium.

yieldprzekształca funkcję w funkcję generatora , co oznacza, że ​​nie będzie ona działać, dopóki nie zaczniesz nad nią zapętlać.

Martijn Pieters
źródło
czy uzyskujesz taką samą wydajność pamięci, używając tej techniki z csv.DictReader? Ponieważ moje testy na pliku .csv o pojemności 2,5 GB pokazują, że próba iteracji wiersz po wierszu w ten sposób podczas używania tego zamiast csv.readerpowoduje, że proces Pythona rośnie do pełnego wykorzystania pamięci 2,5 GB.
user5359531
@ user5359531, co oznaczałoby, że przechowujesz gdzieś odniesienia do obiektów słownika. Sam DictReader nie zachowuje odniesień, więc problem leży gdzie indziej.
Martijn Pieters
39

Chociaż odpowiedź Martijina jest najlepsza. Oto bardziej intuicyjny sposób przetwarzania dużych plików CSV dla początkujących. Pozwala to na jednoczesne przetwarzanie grup wierszy lub fragmentów.

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)
mmann1123
źródło
9
Dlaczego używanie pand sprawia, że ​​jest to bardziej intuicyjne?
wwii
25
4 linie kodu są zawsze lepsze dla początkujących, takich jak ja.
mmann1123
3
Zwykły kod w Pythonie jest równie krótki i umożliwia przetwarzanie w każdym wierszu. Funkcja generatora służy tylko do filtrowania rzeczy; jak byś zrobił to samo filtrowanie w Pandach?
Martijn Pieters
1
To jest niesamowite! Rozwiązałem mój problem z ładowaniem i przetwarzaniem dużych plików CSV za pomocą pand. Dzięki!
Elsa Li
1
Działa bardzo dobrze, nawet jeśli zawartość niektórych wierszy obejmuje wiele wierszy!
Sprzedaż Dielson
19

Wykonuję sporo analiz drgań i patrzę na duże zbiory danych (dziesiątki i setki milionów punktów). Moje testy wykazały, że funkcja pandas.read_csv () jest 20 razy szybsza niż numpy.genfromtxt (). Funkcja genfromtxt () jest 3 razy szybsza niż numpy.loadtxt (). Wygląda na to, że potrzebujesz pand do dużych zbiorów danych.

Opublikowałem kod i zestawy danych, których użyłem w tych testach, na blogu omawiającym MATLAB vs Python do analizy drgań .

Steve
źródło
3
Podstawowym problemem PO nie była szybkość, ale wyczerpanie pamięci. Użycie innej funkcji do przetwarzania samego pliku nie usuwa wad wczytywania go do listy, a nie korzystania z procesora strumieniowego.
pydsigner
6

to, co zadziałało, było i jest superszybkie

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)

Innym działającym rozwiązaniem jest:

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
Portfel Yury
źródło
czy df_train=df_train.compute()linia w twoim pierwszym rozwiązaniu nie ładuje całego zbioru danych do pamięci ... czego on stara się nie robić?
Sam Dillard
3

Dla kogoś, kto zadaje to pytanie. Używanie pand z parametrami „ chunksize ” i „ usecols ” pomogło mi odczytać ogromny plik zip szybciej niż inne proponowane opcje.

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
źródło
1

oto inne rozwiązanie dla 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

tutaj datareaderjest funkcja generatora.

Rishabh Agrahari
źródło
To działa tak samo wydajnie, jak rozwiązanie wykorzystujące operator plonu. : przepraszam, tak nie jest. Wywołanie funkcji zwrotnej zwiększa obciążenie, zwłaszcza, że ​​stan musi być obsługiwany jawnie i oddzielnie.
Martijn Pieters
@MartijnPieters Thanks. Zaktualizowałem odpowiedź.
Rishabh Agrahari,
0

Jeśli używasz pandy i mieć dużo pamięci RAM (wystarczający aby przeczytać cały plik do pamięci), spróbuj skorzystać pd.read_csvz low_memory=False, na przykład:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
Mike T.
źródło