如何在Matplotlib绘图中移除线条

103 投票
6 回答
187703 浏览
提问于 2025-04-16 11:43

我想知道怎么才能从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.

那么,有没有办法只删除坐标轴上的一条线,并且能把内存还回来呢?这个可能的解决方案也没有效果。

6 个回答

18

我在不同的论坛上试过很多不同的答案。我想这可能跟你开发的机器有关。不过我用过这个语句

ax.lines = []

效果很好。我不使用 cla(),因为它会删除我对图表所做的所有定义。

比如:

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

但我尝试过很多次删除那些行。同时也用过弱引用库来检查我在删除时对那行的引用,但对我来说都没用。

希望这个对其他人有帮助 =D

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

这是我为我的同事写的一段很长的解释。我觉得在这里也会有帮助。不过请耐心点,真正的问题我会在最后提到。简单来说,问题在于你有多余的引用指向你的 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

这同样适用于 Axes 对象中的线条:

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

如果你运行第二行代码,你会把 ax.lines[0] 中的 Line2D 对象从当前图形中移除,它就会消失。注意,这也可以通过 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>]

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

包含一组坐标轴和只有第二条线的图形

以上所有内容对于 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>

这个引用仍然存在!为什么呢?因为还有另一个引用指向 wr 中的 Line2D 对象。记得 linesax.lines 的 ID 不一样,但内容是一样的吗?这就是问题所在。

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

所以,这个故事的道理是,自己要清理干净。如果你期待某个东西被垃圾回收,但它没有被回收,那你很可能在某个地方留下了一个引用。

78

我发现用 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()

撰写回答