如何删除Matplotlib图中的线


84

我该如何删除matplotlib轴的一条线(或多条线),使其实际上收集到垃圾并释放回内存?以下代码似乎删除了该行,但从不释放内存(即使显式调用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.

那么有没有办法从轴上删除一行并取回内存呢? 这种潜在的解决方案也不起作用。

Answers:


70

我正在展示lines.pop(0) l.remove()和结合del l使用的技巧。

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)

我检查了您的大型数据集,并在系统监视器上也确认了内存的释放。

当然,最简单的方法(当不进行故障排除时)是从列表中弹出它并remove在不创建硬引用的情况下调用该行对象:

lines.pop(0).remove()

我运行了您的代码,我得到:[8:37 pm] @flattop:〜/ Desktop / sandbox> python delete_lines.py <weakref at 0x8dd348c; 到0x8dd43ec处的'Line2D'> <0x8dd348c处的弱引用; 到0x8dd43ec处的'Line2D'> <0x8dd348c处的弱引用; 到0x8dd43ec的“ Line2D”>我在ubuntu 10.04中使用了matplotlib版本0.99.1.1
David Morton

1
@David Morton我刚刚降级到0.99.1,现在我重现您的问题。我想我只能建议升级到1.0.1。当时有很多bug修正,因为0.99.x
保罗

1
这里的问题很可能是在不应该存在的引用周围徘徊的问题。我敢打赌,OP正在使用IPython进行测试。看我的答案。
漩涡

66

这是我为我的一位同事输入的很长的解释。我认为这也会有所帮助。不过要有耐心。我要解决您即将面临的真正问题。就像一个预告片一样,这是对您的Line2D对象周围悬挂的额外引用的问题。

警告:在我们深入研究之前,请注意另一点。如果您正在使用IPython进行测试,则IPython会保留自己的引用,但并非所有引用都是弱引用。因此,无法在IPython中测试垃圾回收。这只会使事情变得混乱。

好的,我们开始。每个matplotlib对象(FigureAxes等)都可以通过各种属性访问其子艺术家。以下示例变得很长,但应该很有启发。

我们首先创建一个Figure对象,然后Axes向该图中添加一个对象。请注意,axfig.axes[0]是相同的对象(相同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

这也扩展到轴对象中的线:

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

如果plt.show()使用上面的操作进行调用,您将看到一个包含一组轴和一行的图形:

包含一组轴和一条直线的图形

现在,虽然我们已经看到的内容linesax.lines是一样的,但要注意的是在引用的对象是非常重要的lines变量是不一样的尊崇对象ax.lines为可通过以下所见:

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

结果,从中删除元素lines不会对当前绘图产生任何影响,但从中删除元素ax.lines会从当前绘图中删除该线。所以:

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

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

因此,如果要运行第二行代码,则应从当前图中删除其中Line2D包含的对象,该对象ax.lines[0]将消失。请注意,这也可以通过以下方式完成ax.lines.remove():将Line2D实例保存在变量中,然后将其传递ax.lines.remove()给该行以删除该行,如下所示:

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

包含一组轴和两条线的图形

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

包含一组轴和仅第二行的图形

以上所有功能都fig.axes适用于ax.lines

现在,真正的问题在这里。如果我们将其中包含的引用存储ax.lines[0]到一个weakref.ref对象中,然后尝试将其删除,则会注意到该引用不会被垃圾收集:

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

参考仍然有效!为什么?这是因为该引用所指向的Line2D对象还有另一个引用wr。还记得linesID如何不具有相同的IDax.lines却包含相同的元素吗?好吧,这就是问题所在。

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

因此,故事的寓意是,要自己清理。如果您希望某些东西被垃圾回收,但实际上却不是,那么您可能会将参考留在某个地方。


2
正是我所需要的。我正在绘制数千张地图,每张地图的散点图都位于世界地图投影上方。他们每个人花了3秒钟!通过在已绘制的地图上重复使用该图,并从ax.collections中弹出结果集合,我将其降低到1/3秒。谢谢!
2013年

3
我认为在当前版本的mpl中不再需要此功能。美工有一个remove()功能,可以将它们清除出事物的另一面,然后您只需要跟踪参考即可。
tacaswell

2
呵呵,您知道将matplotlib更改为哪个版本吗?
涡旋

发现这在matplotlib动画中使用一堆绘图时很有用。否则,最终会占用大量内存。现在,让这件事变得更快。
丹尼·史泰普

14

我在不同的论坛上尝试了许多不同的答案。我想这取决于您开发的机器。但是我已经用过了

ax.lines = []

并完美运行。我不使用,cla()因为它会删除我对绘图所做的所有定义

例如

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

但是我已经尝试了多次删除这些行。当我删除时,还使用了weakref库来检查对该行的引用,但没有任何帮助。

希望这对别人有用= D


这里的问题很可能是在不应该存在的引用周围徘徊的问题。我敢打赌,OP正在使用IPython进行测试。看我的答案。
漩涡

5

(使用与上述人员相同的示例)

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

1

希望这可以对其他人有所帮助:上面的示例使用ax.lines。随着最新的mpl(3.3.1),有ax.get_lines()。这绕过了呼叫的需要ax.lines=[]

for line in ax.get_lines(): # ax.lines:
    line.remove()
# ax.lines=[] # needed to complete removal when using ax.lines
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.