web-dev-qa-db-fra.com

Lire un énorme fichier .csv

J'essaie actuellement de lire les données de fichiers .csv dans 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 suis au-dessus de cela, je reçois 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. Je laisse donc la boucle lorsque je les ai dépassés pour gagner du temps.

Mes questions sont:

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

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

Mon ordinateur dispose de 8 Go de RAM et de Windows 7 64 bits, et le processeur de 3,40 GHz (vous ne savez pas exactement de quelles informations vous avez besoin).

89
Charles Dillon

Vous lisez toutes les lignes d'une liste, puis vous traitez cette liste. Ne faites 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 faites correspondre 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 passer en boucle sur getstuff() directement. Faites la même chose dans getdata():

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

Bouclez maintenant directement sur getdata() dans votre code:

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

Vous ne conservez plus que une ligne en mémoire, au lieu de vos milliers de lignes par critère.

yield transforme une fonction en fonction du générateur , ce qui signifie qu'elle ne fonctionnera pas tant que vous ne commencerez pas à la lire en boucle.

136
Martijn Pieters

Bien que la réponse de Martijin soit probablement la meilleure. Voici un moyen plus intuitif de traiter de 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)
29
mmann1123

Je fais pas mal d’analyses de vibrations et j’observe des ensembles de données volumineux (des dizaines et des 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 numpy.loadtxt (). Il semble que vous ayez besoin de pandas pour de grands ensembles de données.

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

12
Steve

ce qui a fonctionné pour moi a été et reste super 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
4
Yury Wallet

Pour quelqu'un qui atterrit à cette question. Utiliser pandas avec ' chunksize ' et ' usecols 'm'a aidé à lire un gros 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)
1
ewalel

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

ici datareader est une fonction génératrice.

1
Rishabh Agrahari