2011-02-13 35 views
54

¿Cómo puedo eliminar una línea (o líneas) de un matplotlib ejes de tal manera que realmente obtiene basura recogida y devuelve la memoria? El código siguiente aparece para eliminar la línea, pero nunca libera la memoria (incluso con llamadas explícitas a gc.collect())Cómo eliminar líneas en un gráfico Matplotlib

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 una manera de eliminar sólo una línea que conecta un eje y obtener la memoria ¿espalda? This potential solution tampoco funciona.

Respuesta

40

Estoy mostrando que una combinación de lines.pop(0)l.remove() y del l hace 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) 

Hemos comprobado su gran base de datos y la liberación de la memoria se confirma en el monitor del sistema, así.

supuesto, la forma más simple (cuando no la solución de problemas) sería para que salte de la lista y llamar remove en el objeto de línea sin necesidad de crear una referencia difícil que:

lines.pop(0).remove() 
+0

Ejecuté su código y obtuve: [8:37 pm] @flattop: ~/Escritorio/sandbox> python delete_lines.py Estoy usando matplotlib versión 0.99.1.1 en ubuntu 10.04 –

+1

@David Morton Acabo de degradar a 0.99.1 y ahora reproduzco su problema. Creo que solo puedo recomendar actualizar a 1.0.1.Hubo un * lote * de correcciones de errores desde 0.99.x – Paul

+0

¡Gracias! Agradezco mucho tu ayuda. –

2

(utilizando el mismo ejemplo como 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() 
9

He intentado 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 uso cla() porque elimina todas las definiciones que he hecho en el gráfico

Ej.

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

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

Hope esto funciona para otra persona = D

+0

El problema aquí es probablemente un problema de referencias cuando no deberían. Apuesto a que el OP estaba usando IPython para probar cosas. Ver mi respuesta – Vorticity

46

Ésta es una explicación muy larga que he escrito para un compañero de trabajo mío. Creo que sería útil aquí también. Sé paciente, sin embargo. Llego al verdadero problema que tienes hacia el final. Solo como un adelanto, es una cuestión de tener referencias adicionales a sus objetos Line2D dando vueltas.

ADVERTENCIA: Otra nota antes de sumergirnos. Si está usando IPython para probar esto, IPython conserva sus propias referencias y no todas son referencias débiles. Por lo tanto, probar la recolección de basura en IPython no funciona. Simplemente confunde las cosas.

Bien, aquí vamos. Cada objeto matplotlib (Figure, Axes, etc.) proporciona acceso a sus artistas secundarios a través de diversos atributos. El siguiente ejemplo es bastante largo, pero debe ser esclarecedor.

Comenzamos creando un objeto Figure, luego agregamos un objeto Axes a esa figura. Tenga en cuenta que ax y fig.axes[0] son el mismo objeto (el 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 ejes del objeto:

>>> #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 se va a llamar plt.show() usando lo que se hizo anteriormente, que se vería una figura que contiene un conjunto de ejes y una sola línea :

A figure containing a set of axes and a single line

Ahora, si bien hemos visto que el contenido de lines y es ax.lines el mismo, es muy importante tener en cuenta que el objeto referenciado por la variable de lines no es el mismo que el objeto reverenciado por ax.lines como puede verse por el siguiente:

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

Como consecuencia, la eliminación de un elemento de lines no hace nada con el trazado actual, pero al eliminar un elemento del ax.lines se elimina esa línea del trazado actual. Por lo tanto:

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

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

lo tanto, si se va a ejecutar la segunda línea de código, se eliminaría el objeto Line2D contenida en ax.lines[0] de la trama actual y que se había quedado. Tenga en cuenta que esto también se puede hacer a través de ax.lines.remove() significado que puede guardar una instancia Line2D en una variable, luego pasarlo a ax.lines.remove() 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>] 

A figure containing a set of axes and two lines

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

A figure containing a set of axes and only the second line

Todo lo anterior funciona para fig.axes tan bien como funciona para ax.lines

Ahora, el problema real aquí. Si almacenamos la referencia contenida en ax.lines[0] en un objeto weakref.ref, a continuación, intentar eliminarlo, notaremos que no consigue basura recogida:

>>> #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 sigue vivo! ¿Por qué? Esto se debe a que todavía hay otra referencia al objeto Line2D al que apunta la referencia en wr. ¿Recuerda que lines no tenía la misma ID que ax.lines pero 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> 

Por lo tanto, la moraleja de la historia es, limpiar después de ti mismo. Si espera que algo sea basura, pero no lo es, es probable que deje una referencia en alguna parte.

+2

Exactamente lo que necesitaba. Estoy trazando miles de mapas, cada uno con un diagrama de dispersión sobre la parte superior de una proyección de mapa mundial. ¡Tardaron 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

+0

¡Me alegro de poder ayudar! – Vorticity

+3

Creo que esto ya no es necesario en las versiones actuales de mpl. El artista tiene una función 'remove()' que los eliminará del resto de las cosas, y entonces solo necesita hacer un seguimiento de sus referencias. – tacaswell

Cuestiones relacionadas