web-dev-qa-db-fra.com

Comment concaténer plusieurs pandas.DataFrames sans exécuter MemoryError

J'ai trois DataFrames que j'essaye de concaténer.

concat_df = pd.concat([df1, df2, df3])

Il en résulte une erreur MemoryError. Comment puis-je résoudre ça?

Notez que la plupart des questions similaires existantes sont sur MemoryErrors se produisant lors de la lecture de fichiers volumineux. Je n'ai pas ce problème. J'ai lu mes fichiers dans DataFrames. Je ne peux tout simplement pas concaténer ces données.

17
bluprince13

Je remercie la communauté pour ses réponses. Cependant, dans mon cas, j'ai découvert que le problème était dû au fait que j'utilisais Python 32 bits.

Il existe limites de mémoire définies pour les systèmes d'exploitation Windows 32 et 64 bits. Pour un processus 32 bits , ce n'est que 2 Go. Donc, même si votre RAM a plus de 2 Go, et même si vous exécutez le système d'exploitation 64 bits, mais que vous exécutez un processus 32 bits, ce processus sera limité à seulement 2 Go de RAM - dans mon cas, ce processus était Python.

J'ai mis à niveau vers Python 64 bits, et je n'ai pas eu d'erreur de mémoire depuis lors!

Les autres questions pertinentes sont: Limites de mémoire Python 32 bits sur les fenêtres 64 bits , Dois-je utiliser Python 32 bits ou Python = 64bit , Pourquoi ce tableau numpy est-il trop gros pour être chargé?

3
bluprince13

Je vous conseille de mettre vos dataframes en fichier csv unique par concaténation. Ensuite pour lire votre fichier csv.

Exécutez cela:

# write df1 content in file.csv
df1.to_csv('file.csv', index=False)
# append df2 content to file.csv
df2.to_csv('file.csv', mode='a', columns=False, index=False)
# append df3 content to file.csv
df3.to_csv('file.csv', mode='a', columns=False, index=False)

# free memory
del df1, df2, df3

# read all df1, df2, df3 contents
df = pd.read_csv('file.csv')

Si cette solution n'est pas suffisamment performante, pour concaténer des fichiers plus volumineux que d'habitude. Faire:

df1.to_csv('file.csv', index=False)
df2.to_csv('file1.csv', index=False)
df3.to_csv('file2.csv', index=False)

del df1, df2, df3

Exécutez ensuite la commande bash:

cat file1.csv >> file.csv
cat file2.csv >> file.csv
cat file3.csv >> file.csv

Ou concatérez des fichiers csv dans python:

def concat(file1, file2):
    with open(file2, 'r') as filename2:
        data = file2.read()
    with open(file1, 'a') as filename1:
        file.write(data)

concat('file.csv', 'file1.csv')
concat('file.csv', 'file2.csv')
concat('file.csv', 'file3.csv')

Après avoir lu:

df = pd.read_csv('file.csv')
17
glegoux

Le problème est, comme vu dans les autres réponses, un problème de mémoire. Et une solution consiste à stocker des données sur disque, puis à créer une trame de données unique.

Avec de telles données, les performances sont un problème.

les solutions csv sont très lentes, car la conversion en mode texte se produit. Les solutions HDF5 sont plus courtes, plus élégantes et plus rapides depuis l'utilisation du mode binaire. Je propose une troisième voie en mode binaire, avec pickle , qui semble être encore plus rapide, mais plus technique et nécessitant un peu plus de place. Et un quatrième, à la main.

Voici le code:

import numpy as np
import pandas as pd

# a DataFrame factory:
dfs=[]
for i in range(10):
    dfs.append(pd.DataFrame(np.empty((10**5,4)),columns=range(4)))

# a csv solution
def bycsv(dfs):
    md,hd='w',True
    for df in dfs:
        df.to_csv('df_all.csv',mode=md,header=hd,index=None)
        md,hd='a',False
    #del dfs
    df_all=pd.read_csv('df_all.csv',index_col=None)
    os.remove('df_all.csv') 
    return df_all    

De meilleures solutions:

def byHDF(dfs):
    store=pd.HDFStore('df_all.h5')
    for df in dfs:
        store.append('df',df,data_columns=list('0123'))
    #del dfs
    df=store.select('df')
    store.close()
    os.remove('df_all.h5')
    return df

def bypickle(dfs):
    c=[]
    with open('df_all.pkl','ab') as f:
        for df in dfs:
            pickle.dump(df,f)
            c.append(len(df))    
    #del dfs
    with open('df_all.pkl','rb') as f:
        df_all=pickle.load(f)
        offset=len(df_all)
        df_all=df_all.append(pd.DataFrame(np.empty(sum(c[1:])*4).reshape(-1,4)))

        for size in c[1:]:
            df=pickle.load(f)
            df_all.iloc[offset:offset+size]=df.values 
            offset+=size
    os.remove('df_all.pkl')
    return df_all

Pour des trames de données homogènes, nous pouvons faire encore mieux:

def byhand(dfs):
    mtot=0
    with open('df_all.bin','wb') as f:
        for df in dfs:
            m,n =df.shape
            mtot += m
            f.write(df.values.tobytes())
            typ=df.values.dtype                
    #del dfs
    with open('df_all.bin','rb') as f:
        buffer=f.read()
        data=np.frombuffer(buffer,dtype=typ).reshape(mtot,n)
        df_all=pd.DataFrame(data=data,columns=list(range(n))) 
    os.remove('df_all.bin')
    return df_all

Et quelques tests sur (peu, 32 Mo) de données pour comparer les performances. vous devez multiplier par environ 128 pour 4 Go.

In [92]: %time w=bycsv(dfs)
Wall time: 8.06 s

In [93]: %time x=byHDF(dfs)
Wall time: 547 ms

In [94]: %time v=bypickle(dfs)
Wall time: 219 ms

In [95]: %time y=byhand(dfs)
Wall time: 109 ms

Un chèque :

In [195]: (x.values==w.values).all()
Out[195]: True

In [196]: (x.values==v.values).all()
Out[196]: True

In [197]: (x.values==y.values).all()
Out[196]: True

Bien sûr, tout cela doit être amélioré et réglé pour s'adapter à votre problème.

Par exemple, df3 peut être divisé en morceaux de taille 'total_memory_size - df_total_size' pour pouvoir exécuter bypickle.

Je peux le modifier si vous donnez plus d'informations sur la structure et la taille de vos données si vous le souhaitez. Belle question!

14
B. M.

Similaire à ce que suggère @glegoux, également pd.DataFrame.to_csv peut écrire en mode ajout, vous pouvez donc faire quelque chose comme:

df1.to_csv(filename)
df2.to_csv(filename, mode='a', columns=False)
df3.to_csv(filename, mode='a', columns=False)

del df1, df2, df3
df_concat = pd.read_csv(filename)
8
Pietro Tortella

Je suppose que je devine ici, mais peut-être:

df1 = pd.concat([df1,df2])
del df2
df1 = pd.concat([df1,df3])
del df3

Évidemment, vous pouvez le faire plus en boucle, mais la clé est que vous souhaitez supprimer df2, df3, etc. au fur et à mesure. Comme vous le faites dans la question, vous n'effacez jamais les anciens cadres de données, vous utilisez donc environ deux fois plus de mémoire que nécessaire.

Plus généralement, si vous lisez et concatendez, je ferais quelque chose comme ça (si vous aviez 3 CSV: foo0, foo1, foo2):

concat_df = pd.DataFrame()
for i in range(3):
    temp_df = pd.read_csv('foo'+str(i)+'.csv')
    concat_df = pd.concat( [concat_df, temp_df] )

En d'autres termes, lorsque vous lisez des fichiers, vous ne gardez que les petits cadres de données en mémoire temporairement, jusqu'à ce que vous les concaténiez dans le df combiné, concat_df. Comme vous le faites actuellement, vous conservez tous les petits cadres de données, même après les avoir concaténés.

7
JohnE

Dask pourrait être une bonne option pour essayer de gérer des cadres de données volumineux - Passez par Dask Docs

5
Tanu

Vous pouvez stocker vos trames de données individuelles dans un HDF Store , puis appeler la boutique comme une grande trame de données.

# name of store
fname = 'my_store'

with pd.get_store(fname) as store:

    # save individual dfs to store
    for df in [df1, df2, df3, df_foo]:
        store.append('df',df,data_columns=['FOO','BAR','ETC']) # data_columns = identify the column in the dfs you are appending

    # access the store as a single df
    df = store.select('df', where = ['A>2'])  # change where condition as required (see documentation for examples)
    # Do other stuff with df #

# close the store when you're done
os.remove(fname)
3
NickBraunagel

Une autre option:

1) Écrivez df1 Dans le fichier .csv: df1.to_csv('Big file.csv')

2) Ouvrez le fichier .csv, puis ajoutez df2:

with open('Big File.csv','a') as f:
    df2.to_csv(f, header=False)

3) Répétez l'étape 2 avec df3

with open('Big File.csv','a') as f:
    df3.to_csv(f, header=False)
2
Walt Reed

J'ai rencontré des problèmes de performances similaires en essayant de concaténer un grand nombre de DataFrames à un DataFrame `` en croissance ''.

Ma solution de contournement consistait à ajouter tous les sous-DataFrames à une liste, puis à concaténer la liste des DataFrames une fois le traitement des sous-DataFrames terminé. Cela portera le temps d'exécution à près de la moitié.

0
Prakhar Agarwal