102 votes

Comment supprimer des lignes dans un graphique Matplotlib

Comment puis-je supprimer une ligne (ou des lignes) d'un axe matplotlib de manière à ce qu'elle soit réellement ramassée par le ramasse-miettes et libère la mémoire ? Le code ci-dessous semble supprimer la ligne, mais ne libère jamais la mémoire (même avec des appels explicites de gc.collect())

from matplotlib import pyplot
import numpy
a = numpy.arange(int(1e7))
# grand pour que vous puissiez facilement voir l'empreinte mémoire sur le moniteur du système.
fig = pyplot.Figure()
ax  = pyplot.add_subplot(1, 1, 1)
lines = ax.plot(a) # cela utilise 230 Mb supplémentaires de mémoire.
# puis-je récupérer la mémoire ?
l = lines[0]
l.remove()
del l
del lines
# ne libère pas la mémoire
ax.cla() # cela libère la mémoire, mais supprime également toutes les autres lignes.

Existe-t-il un moyen de supprimer simplement une ligne d'un axe et de récupérer la mémoire ? Cette solution potentielle ne fonctionne pas non plus.

94voto

Vorticity Points 1024

C'est une explication très détaillée que j'ai rédigée pour un collègue à moi. Je pense qu'elle serait utile ici aussi. Soyez patient, cependant. J'aborde le vrai problème que vous rencontrez vers la fin. Juste pour vous donner un avant-goût, il s'agit d'un problème avec des références supplémentaires à vos objets Line2D qui traînent.

ATTENTION : Une autre remarque avant de plonger. Si vous utilisez IPython pour tester cela, IPython garde ses propres références et toutes ne sont pas des weakrefs. Donc, tester la collecte de déchets dans IPython ne fonctionne pas. Cela complique juste les choses.

D'accord, allons-y. Chaque objet matplotlib (Figure, Axes, etc.) donne accès à ses éléments enfants via divers attributs. L'exemple suivant devient assez long, mais devrait être éclairant.

Nous commençons par créer un objet Figure, puis ajoutons un objet Axes à cette figure. Notez que ax et fig.axes[0] sont le même objet (même id()).

>>> #Créer une figure
>>> fig = plt.figure()
>>> fig.axes
[]

>>> #Ajouter un objet axes
>>> ax = fig.add_subplot(1,1,1)

>>> #L'objet dans ax est le même que l'objet dans fig.axes[0], qui est
>>> #  une liste d'objets axes attachée à fig
>>> print ax
Axes(0.125,0.1;0.775x0.8)
>>> print fig.axes[0]
Axes(0.125,0.1;0.775x0.8)  #Même que "print ax"
>>> id(ax), id(fig.axes[0])
(212603664, 212603664) #Mêmes ids => mêmes objets

Cela s'applique également aux lignes dans un objet axes :

>>> #Ajouter une ligne à ax
>>> lines = ax.plot(np.arange(1000))

>>> #Les lignes et ax.lines contiennent les mêmes instances Line2D
>>> print lines
[]
>>> print ax.lines
[]

>>> print lines[0]
Line2D(_line0)
>>> print ax.lines[0]
Line2D(_line0)

>>> #Même ID => même objet
>>> id(lines[0]), id(ax.lines[0])
(216550352, 216550352)

Si vous appelez plt.show() avec ce qui a été fait ci-dessus, vous verrez une figure contenant un ensemble d'axes et une seule ligne :

Une figure contenant un ensemble d'axes et une seule ligne

Maintenant, bien que nous ayons vu que le contenu de lines et de ax.lines est le même, il est très important de noter que l'objet référencé par la variable lines n'est pas le même que l'objet référencé par ax.lines, comme on peut le voir ci-dessous :

>>> id(lines), id(ax.lines)
(212754584, 211335288)

Par conséquent, retirer un élément de lines ne fait rien au tracé actuel, mais retirer un élément de ax.lines supprime cette ligne du tracé actuel. Donc :

>>> #Cela NE FAIT RIEN :
>>> lines.pop(0)

>>> #CETTE LIGNE ÉLIMINE LA PREMIÈRE LIGNE :
>>> ax.lines.pop(0)

Ainsi, si vous exécutez la deuxième ligne de code, vous supprimez l'objet Line2D contenu dans ax.lines[0] du tracé actuel et il disparaîtra. Notez que cela peut également être fait via ax.lines.remove(), ce qui signifie que vous pouvez enregistrer une instance Line2D dans une variable, puis la passer à ax.lines.remove() pour supprimer cette ligne, comme ceci :

>>> #Créer une nouvelle ligne
>>> lines.append(ax.plot(np.arange(1000)/2.0))
>>> ax.lines
[,  ]

Une figure contenant un ensemble d'axes et deux lignes

>>> #Supprimer cette nouvelle ligne
>>> ax.lines.remove(lines[0])
>>> ax.lines
[]

Une figure contenant un ensemble d'axes et seulement la deuxième ligne

Tout ce qui précède fonctionne aussi bien pour fig.axes que pour ax.lines

Maintenant, le vrai problème ici. Si nous stockons la référence contenue dans ax.lines[0] dans un objet weakref.ref, puis tentons de la supprimer, nous remarquerons qu'elle n'est pas collectée par le ramasse-miettes :

>>> #Créer une référence faible à l'objet Line2D
>>> from weakref import ref
>>> wr = ref(ax.lines[0])
>>> print wr

>>> print wr()

>>> #Supprimer la ligne des axes
>>> ax.lines.remove(wr())
>>> ax.lines
[]

>>> #Tester à nouveau la weakref
>>> print wr

>>> print wr()

La référence est toujours active! Pourquoi ? Parce qu'il y a encore une autre référence à l'objet Line2D auquel la référence dans wr pointe. Vous souvenez-vous comment lines n'avait pas le même ID que ax.lines

>>> #Afficher lines
>>> print lines
[,  ]

Pour résoudre ce problème, il suffit de supprimer `lines`, de le vider ou de le laisser sortir de la portée.

>>> #Réinitialiser lines pour le vider
>>> lines = []
>>> print lines
[]
>>> print wr

La morale de l'histoire est donc de bien ranger. Si vous vous attendez à ce que quelque chose soit ramassé par le ramasse-miettes mais que ce n'est pas le cas, vous laissez probablement une référence traîner quelque part.

2 votes

Exactement ce dont j'avais besoin. Je trace des milliers de cartes, chacune avec un graphique de dispersion en superposition d'une projection de carte du monde. Elles prenaient 3 secondes chacune ! En réutilisant la figure avec la carte déjà dessinée et en retirant la collection résultante de ax.collections, je suis passé à 1/3 de seconde. Merci !

4 votes

Je pense que cela n'est plus nécessaire dans les versions actuelles de mpl. Les artistes ont une fonction remove() qui les nettoiera du côté mpl, et il vous suffit ensuite de suivre vos références.

2 votes

Huh, avez-vous une idée de la version de matplotlib dans laquelle ce changement est apparu?

78voto

Paul Points 13042

Je montre qu'une combinaison de lines.pop(0) l.remove() et del l fait l'affaire.

from matplotlib import pyplot
import numpy, weakref
a = numpy.arange(int(1e3))
fig = pyplot.Figure()
ax  = fig.add_subplot(1, 1, 1)
lines = ax.plot(a)

l = lines.pop(0)
wl = weakref.ref(l)  # créer une référence faible pour voir si les références existent toujours
#                      à cet objet
print wl  # pas mort
l.remove()
print wl  # pas mort
del l
print wl  # mort  (supprimez l'une des étapes ci-dessus et ceci est toujours en vie)

J'ai vérifié votre grand jeu de données et la libération de la mémoire est confirmée également sur le moniteur du système.

Bien sûr, la manière la plus simple (lorsqu'il n'y a pas de dépannage) serait de le retirer de la liste et d'appeler remove sur l'objet de ligne sans créer de référence forte :

lines.pop(0).remove()

0 votes

J'ai exécuté votre code et j'ai obtenu : [20:37]@flattop:~/Bureau/sandbox>python delete_lines.py J'utilise la version 0.99.1.1 de matplotlib sous ubuntu 10.04

2 votes

@David Morton Je viens de rétrograder à la version 0.99.1 et maintenant je reproduis ton problème. Je suppose que je ne peux que te recommander de passer à la version 1.0.1. Il y a eu beaucoup de corrections de bogues depuis la version 0.99.x

1 votes

Le problème ici est probablement un problème de références qui restent alors qu'elles ne le devraient pas. Je parie que l'OP utilisait IPython pour tester des choses. Voir ma réponse.

18voto

J'ai essayé beaucoup de différentes réponses dans divers forums. Je suppose que cela dépend de la machine que vous développez. Mais j'ai utilisé l'instruction

ax.lines = []

et ça marche parfaitement. Je n'utilise pas cla() car cela supprime toutes les définitions que j'ai faites pour le graphique

Ex.

pylab.setp(_self.ax.get_yticklabels(), fontsize=8)

mais j'ai essayé de supprimer les lignes plusieurs fois. J'ai aussi utilisé la bibliothèque weakref pour vérifier la référence à cette ligne pendant que je la supprimais mais rien n'a fonctionné pour moi.

J'espère que cela fonctionnera pour quelqu'un d'autre =D

0 votes

Le problème ici est probablement un problème de références qui traînent alors qu'elles ne devraient pas être là. Je parierais que l'OP utilisait IPython pour tester des choses. Voir ma réponse.

0 votes

Merci, ça m'a beaucoup aidé!

0 votes

Il n'est pas possible de définir un attribut dans matplotlib 3.5.3.

10voto

brobr Points 11

Je espère que cela peut aider les autres : Les exemples ci-dessus utilisent ax.lines. Avec des mpl plus récents (3.3.1), il y a ax.get_lines(). Cela contourne le besoin d'appeler ax.lines=[]

pour ligne dans ax.get_lines(): # ax.lines:
    ligne.remove()
# ax.lines=[] # nécessaire pour compléter la suppression lors de l'utilisation de ax.lines

3 votes

Ceci devrait être plus haut dans la liste car les autres réponses sont vraiment dépassées et confuses.

6voto

Jeronimo Points 11

(en utilisant le même exemple que le mec ci-dessus)

de matplotlib importer pyplot
importation de numpy
a = numpy.arange(int(1e3))
fig = pyplot.Figure()
ax  = fig.add_subplot(1, 1, 1)
lines = ax.plot(a)

for i, line in enumerate(ax.lines):
    ax.lines.pop(i)
    ligne.remove()

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