Lire un énorme fichier .csv

107

J'essaie actuellement de lire des données à partir de fichiers .csv en Python 2.7 avec jusqu'à 1 million de lignes et 200 colonnes (les fichiers vont de 100 Mo à 1,6 Go). Je peux le faire (très lentement) pour les fichiers de moins de 300 000 lignes, mais une fois que je dépasse, j'obtiens des erreurs de mémoire. Mon code ressemble à ceci:

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 raison de la clause else dans la fonction getstuff est que tous les éléments qui correspondent au critère seront listés ensemble dans le fichier csv, donc je quitte la boucle lorsque je les dépasse pour gagner du temps.

Mes questions sont:

  1. Comment puis-je faire en sorte que cela fonctionne avec les gros fichiers?

  2. Y a-t-il moyen de le rendre plus rapide?

Mon ordinateur dispose de 8 Go de RAM, exécute Windows 7 64 bits et le processeur est de 3,40 GHz (je ne sais pas quelles informations vous avez besoin).

Charles Dillon
la source
1
Je suis conscient qu'il y a plusieurs questions apparentes similaires, mais aucune d'entre elles ne semblait être suffisamment spécifique à mon problème pour aider beaucoup. Désolé s'il y en a un que j'ai manqué.
Charles Dillon
2
Vous devez stocker les données lues dans une base de données (par exemple Sqlite) au lieu de les conserver en mémoire. Vous pouvez ensuite exécuter un traitement supplémentaire comme le filtrage sur la base de données
Michael Butscher

Réponses:

159

Vous lisez toutes les lignes dans une liste, puis vous traitez cette liste. Ne fais pas ça .

Traitez vos lignes au fur et à mesure que vous les produisez. Si vous devez d'abord filtrer les données, utilisez une fonction de générateur:

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

J'ai également simplifié votre test de filtre; la logique est la même mais plus concise.

Comme vous ne correspondez qu'à une seule séquence de lignes correspondant au critère, vous pouvez également utiliser:

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

Vous pouvez maintenant boucler getstuff()directement. Faites de même dans getdata():

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

Maintenant, bouclez directement getdata()dans votre code:

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

Vous n'avez plus qu'une seule ligne en mémoire, au lieu de vos milliers de lignes par critère.

yieldfait d'une fonction une fonction de générateur , ce qui signifie qu'elle ne fonctionnera pas tant que vous ne commencerez pas à la boucler.

Martijn Pieters
la source
obtenez-vous la même efficacité de mémoire en utilisant cette technique avec csv.DictReader? Parce que mes tests sur un fichier .csv de 2,5 Go montrent que le fait d'essayer d'itérer ligne par ligne comme ceci lorsque vous l'utilisez au lieu de csv.readerfaire croître le processus Python jusqu'à l'utilisation totale de la mémoire de 2,5 Go.
user5359531
@ user5359531 qui indiquerait que vous gardez les références aux objets du dictionnaire quelque part. DictReader en lui - même ne retient pas les références , le problème est donc ailleurs.
Martijn Pieters
39

Bien que la réponse de Martijin soit probablement la meilleure. Voici une façon plus intuitive de traiter de gros fichiers csv pour les débutants. Cela vous permet de traiter des groupes de lignes ou de blocs à la fois.

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)
mmann1123
la source
9
Pourquoi l'utilisation de pandas le rend-il plus intuitif?
wwii
25
4 lignes de code sont toujours mieux pour les débutants comme moi.
mmann1123
3
Le code Python normal est tout aussi court et vous permet de traiter par ligne. La fonction générateur n'est là que pour filtrer les choses; comment feriez-vous le même filtrage dans Pandas?
Martijn Pieters
1
C'est génial! J'ai résolu mon problème de chargement et de traitement de gros fichiers csv à l'aide de pandas. Merci!
Elsa Li
1
Cela fonctionne très bien même lorsque le contenu de certaines lignes s'étend sur plusieurs lignes!
Dielson Sales
19

Je fais pas mal d'analyses vibratoires et je regarde de grands ensembles de données (dizaines et centaines de millions de points). Mes tests ont montré que la fonction pandas.read_csv () était 20 fois plus rapide que numpy.genfromtxt (). Et la fonction genfromtxt () est 3 fois plus rapide que la fonction numpy.loadtxt (). Il semble que vous ayez besoin de pandas pour les grands ensembles de données.

J'ai publié le code et les ensembles de données que j'ai utilisés dans ces tests sur un blog discutant MATLAB vs Python pour l'analyse des vibrations .

Steve
la source
3
Le principal problème du PO n'était pas un problème de vitesse, c'était un problème d'épuisement de la mémoire. L'utilisation d'une fonction différente pour traiter le fichier lui-même ne supprime pas les inconvénients de le lire dans une liste plutôt que d'utiliser un processeur de flux.
pydsigner
6

ce qui a fonctionné pour moi était et est ultra-rapide

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)

Une autre solution de travail est:

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
Portefeuille Yury
la source
la df_train=df_train.compute()ligne de votre première solution ne charge-t-elle pas l'ensemble de données en mémoire ... ce qu'il essaie de ne pas faire?
Sam Dillard le
3

Pour quelqu'un qui atterrit à cette question. L'utilisation de pandas avec ' chunksize ' et ' usecols ' m'a aidé à lire un énorme fichier zip plus rapidement que les autres options proposées.

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
la source
1

voici une autre solution pour 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

voici datareaderune fonction générateur.

Rishabh Agrahari
la source
Donc, cela fonctionne aussi efficacement que la solution qui utilise l'opérateur de rendement. : désolé, ce n'est pas le cas. L'appel de la fonction de rappel ajoute plus de surcharge, d'autant plus que vous devez gérer l'état explicitement et séparément.
Martijn Pieters
@MartijnPieters Merci. Mise à jour de la réponse.
Rishabh Agrahari
0

Si vous utilisez des pandas et que vous avez beaucoup de RAM (assez pour lire le fichier entier en mémoire), essayez d'utiliser pd.read_csvavec low_memory=False, par exemple:

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