138 votes

Lecture d'un énorme fichier .csv

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 mb à 1,6 gb). Je peux le faire (très lentement) pour les fichiers de moins de 300 000 lignes, mais dès que je dépasse ce chiffre, 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 répondent au critère seront répertoriés ensemble dans le fichier csv, donc je quitte la boucle lorsque je les dépasse pour gagner du temps.

Mes questions sont les suivantes :

  1. Comment faire pour que cela fonctionne avec les plus gros fichiers ?

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

Mon ordinateur possède 8 Go de RAM, fonctionne sous Windows 7 64 bits et son processeur est de 3,40 GHz (je ne suis pas certain des informations dont vous avez besoin).

179voto

Martijn Pieters Points 271458

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

Traitez vos rangs 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 mettez en correspondance 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 sur 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 sur getdata() dans votre code :

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

Vous ne détenez plus que une rangée en mémoire, au lieu de vos milliers de lignes par critère.

yield rend une fonction a fonction de générateur ce qui signifie qu'il n'effectuera aucun travail jusqu'à ce que vous commenciez à boucler sur lui.

64voto

mmann1123 Points 422

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

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

22voto

Steve Points 219

Je fais une bonne partie de l'analyse des vibrations et j'examine de grands ensembles de données (des dizaines et des centaines de millions de points). Mes tests ont montré que le pandas.read_csv() pour être 20 fois plus rapide que numpy.genfromtxt(). Et la fonction genfromtxt() est 3 fois plus rapide que numpy.loadtxt(). Il semble que 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 pour ces tests sur un blog où l'on discute. MATLAB et Python pour l'analyse des vibrations .

11voto

ewalel Points 178

Pour quelqu'un qui atterrit à cette question. En utilisant pandas avec Taille des morceaux 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)

10voto

Yury Wallet Points 374

Ce qui m'a réussi et ce qui marche super bien, c'est

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 efficace 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X