J'ai un très gros fichier csv que j'ai ouvert dans pandas comme suit ....
import pandas
df = pandas.read_csv('large_txt_file.txt')
Une fois cette opération effectuée, la mémoire utilisée augmente de 2 Go, ce qui est normal car ce fichier contient des millions de lignes. Mon problème vient quand j'ai besoin de libérer cette mémoire. J'ai couru....
del df
Cependant, l'utilisation de ma mémoire n'a pas diminué. Est-ce une mauvaise approche pour libérer de la mémoire utilisée par un cadre de données pandas? Si c'est le cas, quelle est la bonne manière?
Réduire l'utilisation de la mémoire dans Python est difficile, car Python ne restitue pas la mémoire au système d'exploitation . Si vous supprimez des objets, alors la mémoire est disponible pour les nouveaux objets Python, mais pas pour free()
'au système ( voir cette question ).
Si vous vous en tenez aux tableaux numpy numériques, ceux-ci sont libérés, mais les objets encadrés ne le sont pas.
>>> import os, psutil, numpy as np
>>> def usage():
... process = psutil.Process(os.getpid())
... return process.get_memory_info()[0] / float(2 ** 20)
...
>>> usage() # initial memory usage
27.5
>>> arr = np.arange(10 ** 8) # create a large array without boxing
>>> usage()
790.46875
>>> del arr
>>> usage()
27.52734375 # numpy just free()'d the array
>>> arr = np.arange(10 ** 8, dtype='O') # create lots of objects
>>> usage()
3135.109375
>>> del arr
>>> usage()
2372.16796875 # numpy frees the array, but python keeps the heap big
Python maintient notre mémoire en haut filigrane, mais nous pouvons réduire le nombre total de trames de données que nous créons. Lorsque vous modifiez votre cadre de données, préférez inplace=True
, afin de ne pas créer de copies.
Un autre piège courant est de conserver des copies des images précédemment créées dans ipython:
In [1]: import pandas as pd
In [2]: df = pd.DataFrame({'foo': [1,2,3,4]})
In [3]: df + 1
Out[3]:
foo
0 2
1 3
2 4
3 5
In [4]: df + 2
Out[4]:
foo
0 3
1 4
2 5
3 6
In [5]: Out # Still has all our temporary DataFrame objects!
Out[5]:
{3: foo
0 2
1 3
2 4
3 5, 4: foo
0 3
1 4
2 5
3 6}
Vous pouvez résoudre ce problème en tapant %reset Out
pour effacer votre historique. Vous pouvez également régler la quantité d'historique conservée par ipython avec ipython --cache-size=5
(la valeur par défaut est 1000).
Dans la mesure du possible, évitez d'utiliser des types d'objet.
>>> df.dtypes
foo float64 # 8 bytes per value
bar int64 # 8 bytes per value
baz object # at least 48 bytes per value, often more
Les valeurs associées à un type d'objet sont encadrées, ce qui signifie que le tableau numpy ne contient qu'un pointeur et que vous avez un objet complet Python sur le segment de mémoire pour chaque valeur de votre cadre de données. Cela inclut les chaînes.
Bien que numpy prenne en charge les chaînes de taille fixe dans les tableaux, pandas ne le fait pas ( cela a causé de la confusion chez l'utilisateur ). Cela peut faire une différence significative:
>>> import numpy as np
>>> arr = np.array(['foo', 'bar', 'baz'])
>>> arr.dtype
dtype('S3')
>>> arr.nbytes
9
>>> import sys; import pandas as pd
>>> s = pd.Series(['foo', 'bar', 'baz'])
dtype('O')
>>> sum(sys.getsizeof(x) for x in s)
120
Vous voudrez peut-être éviter d'utiliser des colonnes de chaîne ou trouver un moyen de représenter les données de chaîne sous forme de nombres.
Si vous avez une trame de données contenant de nombreuses valeurs répétées (NaN est très courant), vous pouvez utiliser un structure de données fragmentée pour réduire l'utilisation de la mémoire:
>>> df1.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo float64
dtypes: float64(1)
memory usage: 605.5 MB
>>> df1.shape
(39681584, 1)
>>> df1.foo.isnull().sum() * 100. / len(df1)
20.628483479893344 # so 20% of values are NaN
>>> df1.to_sparse().info()
<class 'pandas.sparse.frame.SparseDataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 1 columns):
foo float64
dtypes: float64(1)
memory usage: 543.0 MB
Vous pouvez voir l’utilisation de la mémoire ( docs ):
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 39681584 entries, 0 to 39681583
Data columns (total 14 columns):
...
dtypes: datetime64[ns](1), float64(8), int64(1), object(4)
memory usage: 4.4+ GB
À partir de pandas 0.17.1, vous pouvez également utiliser df.info(memory_usage='deep')
pour voir l'utilisation de la mémoire, y compris les objets.
Comme indiqué dans les commentaires, il convient d'essayer certaines choses: gc.collect
(@EdChum) peut effacer des éléments, par exemple. Au moins d'après mon expérience, ces choses fonctionnent parfois et souvent non.
Cependant, il y a une chose qui fonctionne toujours, car cela se fait au niveau du système d'exploitation, pas de la langue, mais au niveau.
Supposons que vous ayez une fonction qui crée un énorme DataFrame intermédiaire et renvoie un résultat plus petit (qui peut également être un DataFrame):
def huge_intermediate_calc(something):
...
huge_df = pd.DataFrame(...)
...
return some_aggregate
Ensuite, si vous faites quelque chose comme
import multiprocessing
result = multiprocessing.Pool(1).map(huge_intermediate_calc, [something_])[0]
Alors la fonction est exécutée à un processus différent . Une fois ce processus terminé, le système d'exploitation reprend toutes les ressources utilisées. Il n’ya vraiment rien que Python, les pandas, le ramasseur d’ordures, puisse faire pour empêcher cela.
Cela résout le problème de la libération de la mémoire pour moi !!!
del [[df_1,df_2]]
gc.collect()
df_1=pd.DataFrame()
df_2=pd.DataFrame()
le cadre de données sera explicitement mis à null
del df
ne sera pas supprimé s'il existe une référence à la df
au moment de la suppression. Vous devez donc supprimer toutes les références qui s’y trouvent avec del df
pour libérer la mémoire.
Ainsi, toutes les instances liées à df doivent être supprimées pour déclencher le garbage collection.
Utilisez objgragh pour vérifier lequel tient les objets.