Comment lire un gros fichier csv avec des pandas?

207

J'essaie de lire un gros fichier csv (environ 6 Go) dans pandas et j'obtiens une erreur de mémoire:

MemoryError                               Traceback (most recent call last)
<ipython-input-58-67a72687871b> in <module>()
----> 1 data=pd.read_csv('aphro.csv',sep=';')

...

MemoryError: 

Une aide à ce sujet?

Rajkumar Kumawat
la source
3
Curieusement, une question très similaire a été posée presque un an avant celle-ci ...
DarkCygnus
Copie
Est-ce que cela répond à votre question?
AMC

Réponses:

287

L'erreur montre que la machine ne dispose pas de suffisamment de mémoire pour lire le CSV entier dans un DataFrame en même temps. En supposant que vous n'avez pas besoin de l'ensemble de données en mémoire en une seule fois, une façon d'éviter le problème serait de traiter le CSV en morceaux (en spécifiant le chunksizeparamètre):

chunksize = 10 ** 6
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)

Le chunksizeparamètre spécifie le nombre de lignes par bloc. (Le dernier morceau peut contenir moins de chunksizelignes, bien sûr.)

unutbu
la source
21
vous avez généralement besoin de 2X la mémoire finale pour lire quelque chose (à partir de csv, bien que d'autres formats soient meilleurs pour avoir des besoins de mémoire inférieurs). Pour votre information, c'est vrai pour essayer de faire presque n'importe quoi à la fois. Beaucoup mieux de le fragmenter (qui a une utilisation constante de la mémoire).
Jeff le
26
@altabq: Le problème ici est que nous n'avons pas assez de mémoire pour construire un seul DataFrame contenant toutes les données. La solution ci-dessus tente de faire face à cette situation en réduisant les morceaux (par exemple en agrégeant ou en extrayant uniquement les informations souhaitées) un morceau à la fois - économisant ainsi de la mémoire. Quoi que vous fassiez, NE PAS appeler DF.append(chunk)à l'intérieur de la boucle. Cela utilisera des O(N^2)opérations de copie. Il est préférable d'ajouter les données agrégées à une liste , puis de créer le DataFrame à partir de la liste avec un appel à pd.DataFrameou pd.concat(selon le type de données agrégées).
unutbu
12
@altabq: L'appel DF.append(chunk)dans une boucle nécessite des O(N^2)opérations de copie où Nest la taille des morceaux, car chaque appel à DF.appendrenvoie un nouveau DataFrame. L'appel pd.DataFrameou pd.concat une fois en dehors de la boucle réduit la quantité de copie vers O(N).
unutbu
5
@Pyderman: Oui, le chunksizeparamètre fait référence au nombre de lignes par bloc. Le dernier morceau peut contenir moins de chunksizelignes, bien sûr.
unutbu
9
@Pyderman: Oui; appeler pd.concat([list_of_dfs]) une fois après la boucle est beaucoup plus rapide que d'appeler pd.concatou df.appendplusieurs fois dans la boucle. Bien sûr, vous aurez besoin d'une quantité considérable de mémoire pour contenir l'intégralité du csv de 6 Go en un seul DataFrame.
unutbu
100

La segmentation ne devrait pas toujours être le premier port d'escale pour ce problème.

  1. Le fichier est-il volumineux en raison de données non numériques répétées ou de colonnes indésirables?

    Si tel est le cas, vous pouvez parfois constater des économies de mémoire massives en lisant les colonnes sous forme de catégories et en sélectionnant les colonnes requises via le paramètre pd.read_csv usecols .

  2. Votre flux de travail nécessite-t-il un découpage, une manipulation, une exportation?

    Si tel est le cas, vous pouvez utiliser dask.dataframe pour découper, effectuer vos calculs et exporter de manière itérative. La segmentation est effectuée silencieusement par dask, qui prend également en charge un sous-ensemble d'API pandas.

  3. Si tout le reste échoue, lisez ligne par ligne via des morceaux.

    Chunk via pandas ou via la bibliothèque csv en dernier recours.

jpp
la source
38

Pour les données volumineuses, je vous recommande d'utiliser la bibliothèque "dask"
par exemple:

# Dataframes implement the Pandas API
import dask.dataframe as dd
df = dd.read_csv('s3://.../2018-*-*.csv')

Vous pouvez en savoir plus dans la documentation ici .

Une autre excellente alternative serait d'utiliser modin car toutes les fonctionnalités sont identiques à celles des pandas tout en exploitant des bibliothèques de dataframe distribuées telles que dask.

Simbarashe Timothy Motsi
la source
14
Tout avantage sur les pandas, pourrait apprécier d'ajouter quelques pointeurs supplémentaires
PirateApp
2
Je n'ai pas utilisé Dask depuis très longtemps, mais les principaux avantages dans mes cas d'utilisation étaient que Dask peut fonctionner en parallèle sur plusieurs machines, il peut également insérer des données sous forme de tranches en mémoire.
Simbarashe Timothy Motsi
2
Merci! est dask pour remplacer ou pandas géants fonctionne - t - il au - dessus de pandas géants en tant que couche
PirateApp
3
Bienvenue, il fonctionne comme un wrapper pour Numpy, Pandas et Scikit-Learn.
Simbarashe Timothy Motsi
1
J'ai essayé de faire face à plusieurs problèmes avec Dask et jette toujours une erreur pour tout. Même avec des morceaux, cela génère également des erreurs de mémoire. Voir stackoverflow.com/questions/59865572/…
Genarito
34

J'ai procédé comme ceci:

chunks=pd.read_table('aphro.csv',chunksize=1000000,sep=';',\
       names=['lat','long','rf','date','slno'],index_col='slno',\
       header=None,parse_dates=['date'])

df=pd.DataFrame()
%time df=pd.concat(chunk.groupby(['lat','long',chunk['date'].map(lambda x: x.year)])['rf'].agg(['sum']) for chunk in chunks)
Rajkumar Kumawat
la source
25
Y a-t-il une raison pour laquelle vous êtes passé de read_csvà read_table?
Pyderman
10

La réponse ci-dessus satisfait déjà le sujet. Quoi qu'il en soit, si vous avez besoin de toutes les données en mémoire, jetez un œil à bcolz . Sa compression des données en mémoire. J'ai eu une très bonne expérience avec. Mais il manque beaucoup de fonctionnalités de pandas

Edit: J'ai eu des taux de compression d'environ 1/10 ou de la taille d'origine je pense, bien sûr en fonction du type de données. Les éléments importants manquants étaient les agrégats.

PlagTag
la source
2
Veuillez améliorer cette réponse en nous indiquant a) quels taux de compression vous obtenez et b) quelles fonctionnalités principales des pandas il manque? Peut-il gérer les NA? des cordes? catégoriques? Rendez-vous?
smci
Hein? Peut-il gérer les NA? des cordes? catégoriques? Rendez-vous? Ce sont les choses qui rendent la lecture des pandas csv lente et molle. Les NA et les objets comme les chaînes (même les plus courtes) sont un tueur. Btw le .ipynb référencé à partir de votre blog est en panne.
smci
1
@smci je lisais votre note. mais je vous suggère de jeter un œil à la documentation. j'aurais besoin de les lire moi-même.
PlagTag
2
Ok, donc il ne peut pas gérer les NA, les chaînes ou les dates. Je doute qu'il puisse gérer les flotteurs non plus.
smci
1
Je suppose que vous pouvez prétraiter avec des pandas en utilisant la chunksméthode mentionnée, puis utiliser bcolz si vous avez besoin de toutes les données en mémoire pour faire une analyse. Juste une pensée.
JakeCowton
6

Vous pouvez lire les données sous forme de morceaux et enregistrer chaque morceau sous forme de cornichon.

import pandas as pd 
import pickle

in_path = "" #Path where the large file is
out_path = "" #Path to save the pickle files to
chunk_size = 400000 #size of chunks relies on your available memory
separator = "~"

reader = pd.read_csv(in_path,sep=separator,chunksize=chunk_size, 
                    low_memory=False)    


for i, chunk in enumerate(reader):
    out_file = out_path + "/data_{}.pkl".format(i+1)
    with open(out_file, "wb") as f:
        pickle.dump(chunk,f,pickle.HIGHEST_PROTOCOL)

À l'étape suivante, vous lisez les cornichons et ajoutez chaque cornichon à la trame de données souhaitée.

import glob
pickle_path = "" #Same Path as out_path i.e. where the pickle files are

data_p_files=[]
for name in glob.glob(pickle_path + "/data_*.pkl"):
   data_p_files.append(name)


df = pd.DataFrame([])
for i in range(len(data_p_files)):
    df = df.append(pd.read_pickle(data_p_files[i]),ignore_index=True)
Lukas Humpe
la source
4
Si votre finale dftient entièrement en mémoire (comme implicite) et contient la même quantité de données que votre entrée, vous n'avez sûrement pas besoin de segmenter du tout?
jpp
Vous auriez besoin de segmenter dans ce cas si, par exemple, votre fichier est très large (comme plus de 100 colonnes avec beaucoup de colonnes de chaîne). Cela augmente la mémoire nécessaire pour conserver le df en mémoire. Même un fichier de 4 Go comme celui-ci pourrait finir par utiliser entre 20 et 30 Go de RAM sur une boîte avec 64 Go de RAM.
cdabel
4

La fonction read_csv et read_table est presque la même. Mais vous devez affecter le délimiteur «,» lorsque vous utilisez la fonction read_table dans votre programme.

def get_from_action_data(fname, chunk_size=100000):
    reader = pd.read_csv(fname, header=0, iterator=True)
    chunks = []
    loop = True
    while loop:
        try:
            chunk = reader.get_chunk(chunk_size)[["user_id", "type"]]
            chunks.append(chunk)
        except StopIteration:
            loop = False
            print("Iteration is stopped")

    df_ac = pd.concat(chunks, ignore_index=True)
Tyrion W
la source
Il serait utile de préciser votre question dans cet article. Comme "Quelle est la différence entre read_csv et read_table?" ou "Pourquoi la table de lecture a-t-elle besoin d'un délimiteur?"
nate_weldon
1
Cela dépend de l'apparence de votre fichier. Certains fichiers ont des délimiteurs communs tels que "," ou "|" ou "\ t" mais vous pouvez voir d'autres fichiers avec des délimiteurs tels que 0x01, 0x02 (ce qui crée celui-ci) etc. Ainsi, read_table est plus adapté aux délimiteurs peu communs mais read_csv peut tout aussi bien faire le même travail.
Naufal
4

Je souhaite apporter une réponse plus complète basée sur la plupart des solutions potentielles déjà proposées. Je tiens également à souligner une autre aide potentielle qui peut aider le processus de lecture.

Option 1: dtypes

"dtypes" est un paramètre assez puissant que vous pouvez utiliser pour réduire la pression mémoire des readméthodes. Voyez ceci et cette réponse. Les pandas, par défaut, essaient de déduire les dtypes des données.

En se référant aux structures de données, à chaque donnée stockée, une allocation de mémoire a lieu. Au niveau de base, reportez-vous aux valeurs ci-dessous (le tableau ci-dessous illustre les valeurs pour le langage de programmation C):

The maximum value of UNSIGNED CHAR = 255                                    
The minimum value of SHORT INT = -32768                                     
The maximum value of SHORT INT = 32767                                      
The minimum value of INT = -2147483648                                      
The maximum value of INT = 2147483647                                       
The minimum value of CHAR = -128                                            
The maximum value of CHAR = 127                                             
The minimum value of LONG = -9223372036854775808                            
The maximum value of LONG = 9223372036854775807

Reportez-vous à cette page pour voir la correspondance entre les types NumPy et C.

Disons que vous avez un tableau d'entiers de chiffres . Vous pouvez à la fois théoriquement et pratiquement affecter, par exemple, un tableau de type entier 16 bits, mais vous alloueriez alors plus de mémoire que ce dont vous avez réellement besoin pour stocker ce tableau. Pour éviter cela, vous pouvez activer l' dtypeoption read_csv. Vous ne souhaitez pas stocker les éléments du tableau sous forme d'entier long où vous pouvez en fait les insérer avec un entier 8 bits ( np.int8ou np.uint8).

Observez la carte dtype suivante.

Source: https://pbpython.com/pandas_dtypes.html

Vous pouvez passer le dtypeparamètre comme paramètre sur les méthodes pandas comme dict sur readcomme {column: type}.

import numpy as np
import pandas as pd

df_dtype = {
        "column_1": int,
        "column_2": str,
        "column_3": np.int16,
        "column_4": np.uint8,
        ...
        "column_n": np.float32
}

df = pd.read_csv('path/to/file', dtype=df_dtype)

Option 2: lire par morceaux

La lecture des données par blocs vous permet d'accéder à une partie des données en mémoire, et vous pouvez appliquer un prétraitement sur vos données et conserver les données traitées plutôt que les données brutes. Ce serait bien mieux si vous combinez cette option avec la première, dtypes .

Je tiens à souligner les sections du livre de recettes pandas pour ce processus, où vous pouvez le trouver ici . Notez ces deux sections ici;

Option 3: Dask

Dask est un framework défini sur le site Web de Dask comme:

Dask fournit un parallélisme avancé pour l'analyse, permettant des performances à grande échelle pour les outils que vous aimez

Il est né pour couvrir les parties nécessaires où les pandas ne peuvent pas atteindre. Dask est un framework puissant qui vous permet d'accéder beaucoup plus aux données en les traitant de manière distribuée.

Vous pouvez utiliser dask pour prétraiter vos données dans leur ensemble, Dask s'occupe de la partie de segmentation, donc contrairement aux pandas, vous pouvez simplement définir vos étapes de traitement et laisser Dask faire le travail. Dask n'applique pas les calculs avant d'être explicitement poussé par computeet / ou persist(voir la réponse ici pour la différence).

Autres aides (idées)

  • Flux ETL conçu pour les données. Conserver uniquement ce qui est nécessaire à partir des données brutes.
    • Tout d'abord, appliquez ETL à des données entières avec des frameworks comme Dask ou PySpark, et exportez les données traitées.
    • Vérifiez ensuite si les données traitées peuvent être stockées dans la mémoire dans son ensemble.
  • Pensez à augmenter votre RAM.
  • Envisagez de travailler avec ces données sur une plateforme cloud.
nul
la source
3

Solution 1:

Utiliser des pandas avec des données volumineuses

Solution 2:

TextFileReader = pd.read_csv(path, chunksize=1000)  # the number of rows per chunk

dfList = []
for df in TextFileReader:
    dfList.append(df)

df = pd.concat(dfList,sort=False)
mouton noir
la source
3
Ici encore, nous
chargeons
6
ne faites pas dfList.append, traitez simplement chaque morceau ( df) séparément
gokul_uf
3

Voici un exemple:

chunkTemp = []
queryTemp = []
query = pd.DataFrame()

for chunk in pd.read_csv(file, header=0, chunksize=<your_chunksize>, iterator=True, low_memory=False):

    #REPLACING BLANK SPACES AT COLUMNS' NAMES FOR SQL OPTIMIZATION
    chunk = chunk.rename(columns = {c: c.replace(' ', '') for c in chunk.columns})

    #YOU CAN EITHER: 
    #1)BUFFER THE CHUNKS IN ORDER TO LOAD YOUR WHOLE DATASET 
    chunkTemp.append(chunk)

    #2)DO YOUR PROCESSING OVER A CHUNK AND STORE THE RESULT OF IT
    query = chunk[chunk[<column_name>].str.startswith(<some_pattern>)]   
    #BUFFERING PROCESSED DATA
    queryTemp.append(query)

#!  NEVER DO pd.concat OR pd.DataFrame() INSIDE A LOOP
print("Database: CONCATENATING CHUNKS INTO A SINGLE DATAFRAME")
chunk = pd.concat(chunkTemp)
print("Database: LOADED")

#CONCATENATING PROCESSED DATA
query = pd.concat(queryTemp)
print(query)
Jonathask
la source
2

Vous pouvez essayer sframe, qui a la même syntaxe que les pandas mais vous permet de manipuler des fichiers plus gros que votre RAM.

nunodsousa
la source
Lien vers la documentation SFrame: turi.com/products/create/docs/generated/graphlab.SFrame.html
ankostis
"Les données dans SFrame sont stockées par colonne côté serveur GraphLab" est-ce un service ou un package?
Danny Wang
2

Si vous utilisez des pandas, lisez un gros fichier en bloc, puis cédez ligne par ligne, voici ce que j'ai fait

import pandas as pd

def chunck_generator(filename, header=False,chunk_size = 10 ** 5):
   for chunk in pd.read_csv(filename,delimiter=',', iterator=True, chunksize=chunk_size, parse_dates=[1] ): 
        yield (chunk)

def _generator( filename, header=False,chunk_size = 10 ** 5):
    chunk = chunck_generator(filename, header=False,chunk_size = 10 ** 5)
    for row in chunk:
        yield row

if __name__ == "__main__":
filename = r'file.csv'
        generator = generator(filename=filename)
        while True:
           print(next(generator))
paulg
la source
0

En plus des réponses ci-dessus, pour ceux qui souhaitent traiter CSV puis exporter vers csv, parquet ou SQL, d6tstack est une autre bonne option. Vous pouvez charger plusieurs fichiers et il traite des changements de schéma de données (colonnes ajoutées / supprimées). La prise en charge de base est déjà intégrée.

def apply(dfg):
    # do stuff
    return dfg

c = d6tstack.combine_csv.CombinerCSV([bigfile.csv], apply_after_read=apply, sep=',', chunksize=1e6)

# or
c = d6tstack.combine_csv.CombinerCSV(glob.glob('*.csv'), apply_after_read=apply, chunksize=1e6)

# output to various formats, automatically chunked to reduce memory consumption
c.to_csv_combine(filename='out.csv')
c.to_parquet_combine(filename='out.pq')
c.to_psql_combine('postgresql+psycopg2://usr:pwd@localhost/db', 'tablename') # fast for postgres
c.to_mysql_combine('mysql+mysqlconnector://usr:pwd@localhost/db', 'tablename') # fast for mysql
c.to_sql_combine('postgresql+psycopg2://usr:pwd@localhost/db', 'tablename') # slow but flexible
citynorman
la source
0

Au cas où quelqu'un cherche toujours quelque chose comme ça, j'ai trouvé que cette nouvelle bibliothèque appelée modin peut aider. Il utilise l'informatique distribuée qui peut aider à la lecture. Voici un bel article comparant ses fonctionnalités aux pandas. Il utilise essentiellement les mêmes fonctions que les pandas.

import modin.pandas as pd
pd.read_csv(CSV_FILE_NAME)
Jaskaran
la source
Pouvez-vous commenter la modincomparaison de ce nouveau module avec le bien établi dask.dataframe? Par exemple, consultez passer de pandas à dask pour utiliser tous les cœurs de processeur locaux .
jpp
0

Avant d'utiliser l'option chunksize, si vous voulez être sûr de la fonction de processus que vous souhaitez écrire dans la boucle for de segmentation comme mentionné par @unutbu, vous pouvez simplement utiliser l'option nrows.

small_df = pd.read_csv(filename, nrows=100)

Une fois que vous êtes sûr que le bloc de processus est prêt, vous pouvez le placer dans la boucle de segmentation for pour l'ensemble de la trame de données.

sam
la source