So entfernen Sie Linien in einem Matplotlib-Diagramm

84

Wie kann ich eine Zeile (oder Zeilen) einer Matplotlib-Achse so entfernen, dass tatsächlich Müll gesammelt wird und der Speicher wieder freigegeben wird? Der folgende Code scheint die Zeile zu löschen, gibt jedoch niemals den Speicher frei (selbst bei expliziten Aufrufen von gc.collect()).

from matplotlib import pyplot
import numpy
a = numpy.arange(int(1e7))
# large so you can easily see the memory footprint on the system monitor.
fig = pyplot.Figure()
ax  = pyplot.add_subplot(1, 1, 1)
lines = ax.plot(a) # this uses up an additional 230 Mb of memory.
# can I get the memory back?
l = lines[0]
l.remove()
del l
del lines
# not releasing memory
ax.cla() # this does release the memory, but also wipes out all other lines.

Gibt es also eine Möglichkeit, nur eine Zeile aus einer Achse zu löschen und den Speicher zurückzugewinnen? Diese mögliche Lösung funktioniert auch nicht.

David Morton
quelle

Antworten:

70

Ich zeige, dass eine Kombination aus lines.pop(0) l.remove()und del lden Trick macht.

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)  # create a weak reference to see if references still exist
#                      to this object
print wl  # not dead
l.remove()
print wl  # not dead
del l
print wl  # dead  (remove either of the steps above and this is still live)

Ich habe Ihren großen Datensatz überprüft und die Freigabe des Speichers wird auch auf dem Systemmonitor bestätigt.

Der einfachere Weg (wenn keine Fehlerbehebung erfolgt) wäre natürlich, es aus der Liste zu entfernen und das Linienobjekt aufzurufen, removeohne einen harten Verweis darauf zu erstellen:

lines.pop(0).remove()
Paul
quelle
Ich habe Ihren Code ausgeführt und Folgendes erhalten: [20.37 Uhr] @flattop: ~ / Desktop / sandbox> python delete_lines.py <schwachref bei 0x8dd348c; zu 'Line2D' bei 0x8dd43ec> <schwachref bei 0x8dd348c; zu 'Line2D' bei 0x8dd43ec> <schwachref bei 0x8dd348c; zu 'Line2D' bei 0x8dd43ec> Ich verwende matplotlib Version 0.99.1.1 in Ubuntu 10.04
David Morton
1
@ David Morton Ich habe gerade auf 0.99.1 heruntergestuft und jetzt reproduziere ich Ihr Problem. Ich kann nur ein Upgrade auf 1.0.1 empfehlen. Es gab viele Bugfixes seit 0.99.x
Paul
1
Das Problem hier ist wahrscheinlich ein Problem von Referenzen, die herumhängen, wenn sie nicht sein sollten. Ich würde wetten, dass das OP IPython verwendet, um Dinge zu testen. Siehe meine Antwort.
Vorticity
67

Dies ist eine sehr lange Erklärung, die ich für einen meiner Kollegen geschrieben habe. Ich denke, das wäre auch hier hilfreich. Sei aber geduldig. Ich komme zu dem eigentlichen Problem, das Sie gegen Ende haben. Nur als Teaser geht es darum, zusätzliche Verweise auf Ihre zu habenLine2D Objekte zu haben.

WARNUNG: Noch ein Hinweis, bevor wir eintauchen. Wenn Sie IPython zum Testen verwenden, behält IPython eigene Referenzen bei und nicht alle sind Schwachstellen. Das Testen der Garbage Collection in IPython funktioniert also nicht. Es verwirrt nur die Dinge.

Okay, los geht's. Jedes matplotlibObjekt ( Figure, Axesusw.) bietet Zugang zu seinem Kind Künstler über verschiedene Attribute. Das folgende Beispiel wird ziemlich lang, sollte aber leuchten.

Wir beginnen mit der Erstellung eines FigureObjekts und fügen Axesdieser Figur dann ein Objekt hinzu. Beachten Sie, dass axund fig.axes[0]dasselbe Objekt sind (dasselbe id()).

>>> #Create a figure
>>> fig = plt.figure()
>>> fig.axes
[]

>>> #Add an axes object
>>> ax = fig.add_subplot(1,1,1)

>>> #The object in ax is the same as the object in fig.axes[0], which is 
>>> #   a list of axes objects attached to fig 
>>> print ax
Axes(0.125,0.1;0.775x0.8)
>>> print fig.axes[0]
Axes(0.125,0.1;0.775x0.8)  #Same as "print ax"
>>> id(ax), id(fig.axes[0])
(212603664, 212603664) #Same ids => same objects

Dies erstreckt sich auch auf Linien in einem Achsenobjekt:

>>> #Add a line to ax
>>> lines = ax.plot(np.arange(1000))

>>> #Lines and ax.lines contain the same line2D instances 
>>> print lines
[<matplotlib.lines.Line2D object at 0xce84bd0>]
>>> print ax.lines
[<matplotlib.lines.Line2D object at 0xce84bd0>]

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

>>> #Same ID => same object
>>> id(lines[0]), id(ax.lines[0])
(216550352, 216550352)

Wenn Sie mit den oben beschriebenen Schritten anrufen plt.show()würden, würden Sie eine Abbildung sehen, die einen Satz Achsen und eine einzelne Zeile enthält:

Eine Figur, die einen Satz Achsen und eine einzelne Linie enthält

Obwohl wir gesehen haben, dass der Inhalt von linesund ax.linesderselbe ist, ist es sehr wichtig zu beachten, dass das Objekt, auf das die linesVariable verweist, nicht dasselbe ist wie das Objekt , auf das verwiesen wird ax.lines, wie aus dem Folgenden ersichtlich ist:

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

Infolgedessen hat das Entfernen eines Elements aus linesnichts mit dem aktuellen Plot zu tun, aber das Entfernen eines Elements aus ax.linesentfernt diese Linie aus dem aktuellen Plot. Damit:

>>> #THIS DOES NOTHING:
>>> lines.pop(0)

>>> #THIS REMOVES THE FIRST LINE:
>>> ax.lines.pop(0)

Wenn Sie also die zweite Codezeile ausführen würden, würden Sie das darin Line2Denthaltene Objekt ax.lines[0]aus dem aktuellen Plot entfernen und es wäre verschwunden. Beachten Sie, dass dies auch über die ax.lines.remove()Bedeutung erfolgen kann, dass Sie eine Line2DInstanz in einer Variablen speichern und dann an übergeben können ax.lines.remove(), um diese Zeile wie folgt zu löschen:

>>> #Create a new line
>>> lines.append(ax.plot(np.arange(1000)/2.0))
>>> ax.lines
[<matplotlib.lines.Line2D object at 0xce84bd0>,  <matplotlib.lines.Line2D object at 0xce84dx3>]

Eine Figur mit einer Reihe von Achsen und zwei Linien

>>> #Remove that new line
>>> ax.lines.remove(lines[0])
>>> ax.lines
[<matplotlib.lines.Line2D object at 0xce84dx3>]

Eine Figur, die einen Satz Achsen und nur die zweite Zeile enthält

Alle oben genannten Funktionen funktionieren fig.axesgenauso gut wie fürax.lines

Nun das eigentliche Problem hier. Wenn wir die darin enthaltene Referenz ax.lines[0]in einem weakref.refObjekt speichern und dann versuchen, sie zu löschen, werden wir feststellen, dass kein Müll gesammelt wird:

>>> #Create weak reference to Line2D object
>>> from weakref import ref
>>> wr = ref(ax.lines[0])
>>> print wr
<weakref at 0xb758af8; to 'Line2D' at 0xb757fd0>
>>> print wr()
<matplotlib.lines.Line2D at 0xb757fd0>

>>> #Delete the line from the axes
>>> ax.lines.remove(wr())
>>> ax.lines
[]

>>> #Test weakref again
>>> print wr
<weakref at 0xb758af8; to 'Line2D' at 0xb757fd0>
>>> print wr()
<matplotlib.lines.Line2D at 0xb757fd0>

Die Referenz ist noch live! Warum? Dies liegt daran, dass es noch einen weiteren Verweis auf das Line2DObjekt gibt, auf den der Verweis in wrverweist. Erinnern Sie sich, wie Sie linesnicht dieselbe ID hatten, ax.linesaber dieselben Elemente enthielten? Nun, das ist das Problem.

>>> #Print out lines
>>> print lines
[<matplotlib.lines.Line2D object at 0xce84bd0>,  <matplotlib.lines.Line2D object at 0xce84dx3>]

To fix this problem, we simply need to delete `lines`, empty it, or let it go out of scope.

>>> #Reinitialize lines to empty list
>>> lines = []
>>> print lines
[]
>>> print wr
<weakref at 0xb758af8; dead>

Die Moral der Geschichte lautet also: Räumen Sie nach sich selbst auf. Wenn Sie erwarten, dass etwas Müll gesammelt wird, dies aber nicht ist, lassen Sie wahrscheinlich irgendwo eine Referenz hängen.

Vorticity
quelle
2
Genau das, was ich brauchte. Ich zeichne Tausende von Karten, jede mit einem Streudiagramm über einer Weltkartenprojektion. Sie brauchten jeweils 3 Sekunden! Durch die Wiederverwendung der Figur mit der bereits gezeichneten Karte und das Löschen der resultierenden Sammlung aus ax.collections habe ich sie auf 1/3 Sekunde reduziert. Vielen Dank!
GaryBishop
3
Ich denke, dass dies in aktuellen Versionen von mpl nicht mehr notwendig ist. Der Künstler hat eine remove()Funktion, die ihn von der MPL-Seite der Dinge befreit, und dann müssen Sie nur noch Ihre Referenzen verfolgen.
Tacaswell
2
Huh, eine Idee, in welcher Version von matplotlib diese Änderung gleich herauskommt?
Vorticity
Ich fand dies nützlich, wenn ich eine Reihe von Plots in einer Matplotlib-Animation verwende. Andernfalls wird sehr viel Speicher verwendet. Nun, um das Ding schneller zu machen.
Danny Staple
14

Ich habe viele verschiedene Antworten in verschiedenen Foren ausprobiert. Ich denke, es hängt von der Maschine ab, die Sie entwickeln. Aber ich habe die Aussage benutzt

ax.lines = []

und funktioniert perfekt. Ich benutze nichtcla() weil dadurch alle Definitionen gelöscht werden, die ich für den Plot vorgenommen habe

Ex.

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

aber ich habe viele Male versucht, die Zeilen zu löschen. Verwenden Sie auch die schwache Referenzbibliothek, um den Verweis auf diese Zeile zu überprüfen, während ich löschte, aber nichts funktionierte für mich.

Hoffe das funktioniert für jemand anderen = D.

Jeronimo Schreyer
quelle
Das Problem hier ist wahrscheinlich ein Problem von Referenzen, die herumhängen, wenn sie nicht sein sollten. Ich würde wetten, dass das OP IPython verwendet, um Dinge zu testen. Siehe meine Antwort.
Vorticity
5

(am selben Beispiel wie der Typ oben)

from matplotlib import pyplot
import 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)
    line.remove()
Jeronimo
quelle
1

Hoffentlich kann dies anderen helfen: Die obigen Beispiele verwenden ax.lines. Mit neueren mpl (3.3.1) gibt es ax.get_lines(). Dadurch wird die Notwendigkeit eines Anrufs umgangenax.lines=[]

for line in ax.get_lines(): # ax.lines:
    line.remove()
# ax.lines=[] # needed to complete removal when using ax.lines
brobr
quelle