Cómo eliminar líneas en una gráfica de Matplotlib

84

¿Cómo puedo eliminar una línea (o líneas) de los ejes de un matplotlib de tal manera que realmente se recolecta la basura y se libera la memoria? El siguiente código parece eliminar la línea, pero nunca libera la memoria (incluso con llamadas explícitas a 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.

Entonces, ¿hay alguna manera de eliminar una línea de un eje y recuperar la memoria? Esta posible solución tampoco funciona.

David Morton
fuente

Respuestas:

70

Estoy mostrando que una combinación de lines.pop(0) l.remove()y del lhace el truco.

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)

Verifiqué su gran conjunto de datos y la liberación de la memoria también se confirma en el monitor del sistema.

Por supuesto, la forma más sencilla (cuando no se trata de solucionar problemas) sería sacarlo de la lista y llamar removeal objeto de línea sin crear una referencia estricta a él:

lines.pop(0).remove()
Paul
fuente
Ejecuté su código y obtuve: [8:37 pm] @flattop: ~ / Desktop / sandbox> python delete_lines.py <thinref at 0x8dd348c; a 'Line2D' en 0x8dd43ec> <débilref en 0x8dd348c; a 'Line2D' en 0x8dd43ec> <débilref en 0x8dd348c; a 'Line2D' en 0x8dd43ec> Estoy usando la versión 0.99.1.1 de matplotlib en ubuntu 10.04
David Morton
1
@David Morton Acabo de rebajar a 0.99.1 y ahora reproduzco su problema. Supongo que solo puedo recomendar actualizar a 1.0.1. Hubo muchas correcciones de errores desde 0.99.x
Paul
1
El problema aquí es probablemente un problema de referencias que están rondando cuando no deberían serlo. Apostaría a que el OP estaba usando IPython para probar cosas. Mira mi respuesta.
Vorticity
66

Esta es una explicación muy larga que escribí para un compañero de trabajo. Creo que aquí también sería útil. Pero tenga paciencia. Llego al problema real que está teniendo hacia el final. Solo como un adelanto, se trata de tener referencias adicionales a sus Line2Dobjetos merodeando.

ADVERTENCIA: Una nota más antes de sumergirnos. Si está utilizando IPython para probar esto, IPython mantiene sus propias referencias y no todas son referencias débiles. Entonces, probar la recolección de basura en IPython no funciona. Simplemente confunde las cosas.

Bien, aquí vamos. Cada matplotlibobjeto ( Figure, Axes, etc.) proporciona acceso a sus niños artistas a través de varios atributos. El siguiente ejemplo se está volviendo bastante largo, pero debería ser esclarecedor.

Comenzamos creando un Figureobjeto, luego agregamos un Axesobjeto a esa figura. Tenga en cuenta que axy fig.axes[0]son el mismo objeto (mismo 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

Esto también se extiende a las líneas en un objeto de ejes:

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

Si llamara plt.show()usando lo que se hizo anteriormente, verá una figura que contiene un conjunto de ejes y una sola línea:

Una figura que contiene un conjunto de ejes y una sola línea.

Ahora, si bien hemos visto que el contenido de linesy ax.lineses el mismo, es muy importante notar que el objeto al que hace referencia la linesvariable no es el mismo que el objeto reverenciado por ax.linescomo se puede ver en lo siguiente:

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

Como consecuencia, eliminar un elemento de linesno afecta al gráfico actual, pero eliminar un elemento de ax.lineselimina esa línea del gráfico actual. Entonces:

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

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

Entonces, si tuviera que ejecutar la segunda línea de código, eliminaría el Line2Dobjeto contenido en ax.lines[0]el gráfico actual y desaparecería. Tenga en cuenta que esto también se puede hacer, ax.lines.remove()lo que significa que puede guardar una Line2Dinstancia en una variable, luego pasarla a ax.lines.remove()para eliminar esa línea, así:

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

Una figura que contiene un conjunto de ejes y dos líneas.

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

Una figura que contiene un conjunto de ejes y solo la segunda línea

Todo lo anterior funciona fig.axestan bien como funciona paraax.lines

Ahora, el verdadero problema aquí. Si almacenamos la referencia contenida en ax.lines[0]un weakref.refobjeto, luego intentamos eliminarlo, notaremos que no se recolecta la basura:

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

¡La referencia aún está viva! ¿Por qué? Esto se debe a que todavía hay otra referencia al Line2Dobjeto al que wrapunta la referencia . ¿Recuerda linesque no tenía el mismo ID ax.linespero contenía los mismos elementos? Bueno, ese es el problema.

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

Entonces, la moraleja de la historia es, limpia después de ti mismo. Si espera que algo sea recolectado como basura pero no lo es, es probable que esté dejando una referencia en algún lugar.

Vorticidad
fuente
2
Exactamente lo que necesitaba. Estoy trazando miles de mapas, cada uno con un diagrama de dispersión sobre la proyección de un mapa mundial. ¡Estaban tomando 3 segundos cada uno! Al reutilizar la figura con el mapa ya dibujado y hacer estallar la colección resultante de ax.collections, la reduje a 1/3 de segundo. ¡Gracias!
GaryBishop
3
Creo que esto ya no es necesario en las versiones actuales de mpl. El artista tiene una remove()función que los limpiará del lado mpl de las cosas, y luego solo necesita realizar un seguimiento de sus referencias.
tacaswell
2
Eh, ¿alguna idea de en qué versión de matplotlib este cambio?
Vorticity
Encontré que esto es útil cuando se usan muchos gráficos en una animación matplotlib. De lo contrario, terminará con una gran cantidad de memoria en uso. Ahora para hacer esto más rápido.
Danny Staple
14

Probé muchas respuestas diferentes en diferentes foros. Supongo que depende de la máquina que desarrolles. Pero he usado la declaración

ax.lines = []

y funciona perfectamente. No lo uso cla()porque elimina todas las definiciones que hice en la trama

Ex.

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

pero he intentado borrar las líneas muchas veces. También usé la biblioteca débilref para verificar la referencia a esa línea mientras estaba eliminando, pero nada funcionó para mí.

Espero que esto funcione para alguien más = D

Jerónimo Schreyer
fuente
El problema aquí es probablemente un problema de referencias que están rondando cuando no deberían serlo. Apostaría a que el OP estaba usando IPython para probar cosas. Mira mi respuesta.
Vorticity
5

(usando el mismo ejemplo que el chico de arriba)

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()
Jerónimo
fuente
1

Con suerte, esto puede ayudar a otros: Los ejemplos anteriores usan ax.lines. Con mpl más reciente (3.3.1), hay ax.get_lines(). Esto evita la necesidad de llamarax.lines=[]

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