184 votes

Comment libérer la mémoire utilisée par un dataframe pandas ?

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 que j'ai fait cela, l'utilisation de la mémoire augmente de 2 Go, ce qui est normal puisque ce fichier contient des millions de lignes. Mon problème est que je dois libérer cette mémoire. J'ai exécuté le programmean....

del df

Cependant, mon utilisation de la mémoire n'a pas diminué. Est-ce la bonne approche pour libérer la mémoire utilisée par un cadre de données pandas ? Si c'est le cas, quelle est la bonne méthode ?

7 votes

C'est exact, le ramasseur d'ordures peut ne pas libérer la mémoire immédiatement, vous pouvez également importer le fichier gc et appeler gc.collect() mais il ne peut pas récupérer la mémoire

0 votes

del df n'est pas appelé directement après la création de df, n'est-ce pas ? Je pense qu'il y a des références au df au moment où vous supprimez le df. Donc il ne sera pas supprimé mais le nom sera supprimé.

6 votes

La question de savoir si la mémoire récupérée par le ramasseur de déchets est effectivement rendue au système d'exploitation dépend de l'implémentation ; la seule garantie que donne le ramasseur de déchets est que la mémoire récupérée peut être utilisée par le processus Python actuel pour d'autres choses au lieu de demander, voire de plus de la mémoire de l'OS.

182voto

Wilfred Hughes Points 3507

Il est difficile de réduire l'utilisation de la mémoire en Python, car Python ne restitue pas réellement la mémoire au système d'exploitation. . Si vous supprimez des objets, la mémoire est alors disponible pour de nouveaux objets Python, mais pas free() de retour au système ( voir cette question ).

Si vous vous en tenez aux tableaux numériques numpy, ceux-ci sont libérés, mais pas les objets en boîte.

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

Réduire le nombre d'images de données

Python maintient notre mémoire en filigrane, mais nous pouvons réduire le nombre total de dataframes que nous créons. Lorsque vous modifiez votre dataframe, préférez inplace=True afin de ne pas créer de copies.

Une autre erreur courante consiste à conserver des copies de cadres de données précédemment créés 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. Alternativement, vous pouvez ajuster la quantité d'historique conservé par ipython avec ipython --cache-size=5 (la valeur par défaut est 1000).

Réduire la taille de la trame de données

Dans la mesure du possible, évitez d'utiliser des dtypes d'objets.

>>> 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 avec 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 Python complet sur le tas pour chaque valeur de votre cadre de données. Cela inclut les chaînes de caractères.

Alors que numpy supporte les chaînes de taille fixe dans les tableaux, pandas ne le fait pas ( cela a provoqué une confusion chez les utilisateurs ). 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înes de caractères ou trouver un moyen de représenter les données de chaînes de caractères sous forme de nombres.

Si vous disposez d'un cadre de données qui contient de nombreuses valeurs répétées (NaN est très courant), vous pouvez utiliser la fonction structure de données éparses 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

Visualisation de l'utilisation de la mémoire

Vous pouvez visualiser 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 la version 0.17.1 de pandas, vous pouvez également effectuer les opérations suivantes df.info(memory_usage='deep') pour voir l'utilisation de la mémoire, y compris les objets.

5 votes

Cette réponse doit être marquée "Réponse acceptée". Elle explique brièvement mais clairement comment python conserve la mémoire même lorsqu'il n'en a pas vraiment besoin. Les conseils pour économiser de la mémoire sont tous judicieux et utiles. Comme autre conseil, j'ajouterais simplement l'utilisation du 'multiprocessing' (comme expliqué dans la réponse de @Ami).

5 votes

"Lorsque cela est possible, utilisez inplace=True ". Non, c'est un mythe ! Voir cette réponse pour le pourquoi. (Sinon, excellente réponse dans l'ensemble.)

75voto

Ami Tavory Points 24416

Comme indiqué dans les commentaires, il y a certaines choses à essayer : gc.collect (@EdChum) peut éclaircir des choses, par exemple. Au moins d'après mon expérience, ces choses fonctionnent parfois et souvent non.

Il y a cependant une chose qui fonctionne toujours, car elle est effectuée au niveau du système d'exploitation et non du langage.

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

Alors si vous faites quelque chose comme

import multiprocessing

result = multiprocessing.Pool(1).map(huge_intermediate_calc, [something_])[0]

Puis la fonction est exécutée dans un processus différent . Lorsque ce processus se termine, le système d'exploitation récupère toutes les ressources qu'il a utilisées. Il n'y a vraiment rien que Python, pandas, le ramasseur de déchets, puissent faire pour empêcher cela.

0 votes

J'ai essayé d'exécuter gc.collect() mais cela n'a pas libéré la mémoire. L'ouverture dans un processus fonctionne, mais cela semble être une approche tellement inutile pour quelque chose d'aussi simple que la suppression d'un cadre de données lorsqu'il n'est plus nécessaire. Est-ce que pandas est censé se comporter de cette manière ?

4 votes

@b10hazard Même sans les pandas, je n'ai jamais bien compris comment la mémoire Python fonctionne en pratique. Cette technique rudimentaire est la seule chose sur laquelle je m'appuie.

21 votes

Cela fonctionne très bien. Cependant, dans un environnement Python (comme le notebook de Jupyter), j'ai découvert que vous devez .close() et .join() ou .terminate() le pool pour vous débarrasser du processus créé. La façon la plus simple de le faire depuis Python 3.3 est d'utiliser le protocole de gestion de contexte : with multiprocessing.Pool(1) as pool: result = pool.map(huge_intermediate_calc, [something]) qui se charge de fermer le pool une fois terminé.

50voto

hardi Points 91

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 défini comme nul.

4 votes

Pourquoi les dataframes sont ajoutés dans la sous-liste [[df_1,df_2]] ? ? Une raison spécifique ? Veuillez expliquer.

6 votes

Pourquoi n'utilisez-vous pas simplement les deux dernières déclarations ? Je ne pense pas que vous ayez besoin des deux premières déclarations.

0 votes

@spacedustpi car utiliser uniquement les deux dernières déclarations ne fonctionnera pas.

5voto

Il semble qu'il y ait un problème avec la glibc qui affecte l'allocation de mémoire dans Pandas : https://github.com/pandas-dev/pandas/issues/2659

Le site corre correctif de singe détaillé sur cette question a résolu le problème pour moi :

# monkeypatches.py

# Solving memory leak problem in pandas
# https://github.com/pandas-dev/pandas/issues/2659#issuecomment-12021083
import pandas as pd
from ctypes import cdll, CDLL
try:
    cdll.LoadLibrary("libc.so.6")
    libc = CDLL("libc.so.6")
    libc.malloc_trim(0)
except (OSError, AttributeError):
    libc = None

__old_del = getattr(pd.DataFrame, '__del__', None)

def __new_del(self):
    if __old_del:
        __old_del(self)
    libc.malloc_trim(0)

if libc:
    print('Applying monkeypatch for pd.DataFrame.__del__', file=sys.stderr)
    pd.DataFrame.__del__ = __new_del
else:
    print('Skipping monkeypatch for pd.DataFrame.__del__: libc or malloc_trim() not found', file=sys.stderr)

0 votes

Il est probable que si j'utilise la dernière version de Panda, je ne serai pas confronté à ce problème, n'est-ce pas ?

0 votes

Je suis également confronté au même problème, mais dans mon cas, j'utilise l'api drop de pandas, et j'ai également ajouté la correction ci-dessus. Croisons les doigts.

1 votes

@ajayramesh le problème github lié a été fermé "won't fix", donc je suppose que le problème est toujours présent avec Pandas 1.0.

5voto

Marlon Abeykoon Points 105

del df ne sera pas supprimée s'il existe une référence à l'élément df au moment de la suppression. Vous devez donc supprimer toutes les références à ce fichier avec la commande del df pour libérer la mémoire.

Ainsi, toutes les instances liées à df doivent être supprimées pour déclencher la collecte des déchets.

Utilisez objgragh pour vérifier qui retient les objets.

0 votes

Le lien pointe vers objgraph ( mg.pov.lt/objgraph ), il s'agit d'une erreur de frappe dans votre réponse, à moins qu'il n'y ait une objgragh.

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