Leggere un enorme file .csv


107

Attualmente sto cercando di leggere i dati dai file .csv in Python 2.7 con un massimo di 1 milione di righe e 200 colonne (i file vanno da 100 MB a 1,6 GB). Posso farlo (molto lentamente) per i file con meno di 300.000 righe, ma una volta che vado sopra ottengo errori di memoria. Il mio codice ha questo aspetto:

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

La ragione per la clausola else nella funzione getstuff è che tutti gli elementi che soddisfano il criterio saranno elencati insieme nel file csv, quindi lascio il ciclo quando li supero per risparmiare tempo.

Le mie domande sono:

  1. Come posso farlo funzionare con i file più grandi?

  2. C'è un modo per renderlo più veloce?

Il mio computer ha 8 GB di RAM, esegue Windows 7 a 64 bit e il processore è 3,40 GHz (non è sicuro di quali informazioni hai bisogno).


1
Sono consapevole del fatto che ci sono molte domande apparenti simili, ma nessuna di esse sembrava essere abbastanza specifica per il mio problema da aiutare molto. Scusa se ce n'è uno che mi è mancato.
Charles Dillon

2
È necessario memorizzare i dati letti in un database (ad esempio Sqlite) invece di tenerli in memoria. È quindi possibile eseguire ulteriori elaborazioni come il filtraggio sul database
Michael Butscher

Risposte:


159

Stai leggendo tutte le righe in un elenco, quindi elaborando quell'elenco. Non farlo .

Elabora le tue righe mentre le produci. Se devi prima filtrare i dati, usa una funzione di generatore:

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

Ho anche semplificato il test del filtro; la logica è la stessa ma più concisa.

Poiché stai abbinando solo una singola sequenza di righe che corrisponde al criterio, potresti anche utilizzare:

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

Ora puoi eseguire il ciclo getstuff()direttamente. Fai lo stesso in getdata():

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

Ora getdata()fai il loop direttamente nel tuo codice:

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

Ora tieni solo una riga in memoria, invece delle tue migliaia di righe per criterio.

yieldrende una funzione una funzione del generatore , il che significa che non farà alcun lavoro finché non inizi a scorrere su di essa.


ottieni la stessa efficienza di memoria quando usi questa tecnica con csv.DictReader? Perché i miei test su un file .csv da 2,5 GB mostrano che il tentativo di iterare riga per riga in questo modo quando lo si utilizza invece di csv.readerfar crescere il processo Python fino al pieno utilizzo della memoria da 2,5 GB.
user5359531

@ user5359531 che indicherebbe di mantenere i riferimenti agli oggetti del dizionario da qualche parte. DictReader da solo non conserva i riferimenti quindi il problema è altrove.
Martijn Pieters

39

Anche se la risposta di Martijin è probabilmente la migliore. Ecco un modo più intuitivo per elaborare file CSV di grandi dimensioni per principianti. Ciò consente di elaborare gruppi di righe o blocchi alla volta.

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

9
Perché usare i panda lo rende più intuitivo?
seconda guerra mondiale

25
4 righe di codice sono sempre migliori per i neofiti come me.
mmann1123

3
Il normale codice Python è altrettanto breve e ti consente di elaborare per riga. La funzione del generatore è lì solo per filtrare le cose; come faresti a fare lo stesso filtro in Panda?
Martijn Pieters

1
Questo e spettacolare! Risolto il mio problema di caricamento ed elaborazione di file CSV di grandi dimensioni utilizzando panda. Grazie!
Elsa Li

1
Funziona molto bene anche quando il contenuto di alcune righe si estende su più righe!
Dielson Sales

19

Eseguo una discreta quantità di analisi delle vibrazioni e guardo grandi set di dati (decine e centinaia di milioni di punti). I miei test hanno mostrato che la funzione pandas.read_csv () è 20 volte più veloce di numpy.genfromtxt (). E la funzione genfromtxt () è 3 volte più veloce di numpy.loadtxt (). Sembra che tu abbia bisogno di panda per grandi set di dati.

Ho pubblicato il codice e i set di dati che ho usato in questo test su un blog che discuteva di MATLAB e Python per l'analisi delle vibrazioni .


3
Il problema principale dell'OP non era quello della velocità, era quello dell'esaurimento della memoria. L'uso di una funzione diversa per l'elaborazione del file stesso non rimuove gli svantaggi della lettura in un elenco piuttosto che l'utilizzo di un processore di flusso.
pydsigner

6

quello che ha funzionato per me era ed è superveloce lo è

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)

Un'altra soluzione di lavoro è:

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

la df_train=df_train.compute()riga nella tua prima soluzione non carica l'intero set di dati in memoria ... che è quello che sta cercando di non fare?
Sam Dillard

3

Per qualcuno che atterra a questa domanda. L'utilizzo di panda con " chunksize " e " usecols " mi ha aiutato a leggere un enorme file zip più velocemente rispetto alle altre opzioni proposte.

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

ecco un'altra soluzione per 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

ecco datareaderuna funzione del generatore.


Quindi, questo funziona in modo efficiente quanto la soluzione che utilizza l'operatore di resa. : scusa, non è così. La chiamata alla funzione di callback aggiunge più overhead, soprattutto perché devi gestire lo stato in modo esplicito e separato.
Martijn Pieters

@MartijnPieters Thanks. Aggiornata la risposta.
Rishabh Agrahari

0

Se si utilizza panda e hanno un sacco di RAM (basta leggere l'intero file in memoria) provare a utilizzare pd.read_csvcon low_memory=False, ad esempio:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.