【问题标题】:How to remove lines in a Matplotlib plot如何删除 Matplotlib 图中的线条
【发布时间】:2021-10-28 16:01:46
【问题描述】:

如何删除 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.

那么有没有办法从轴上删除一条线并取回内存? This potential solution 也不行。

【问题讨论】:

    标签: python matplotlib plot


    【解决方案1】:

    这是一个很长的解释,是我为我的一个同事打的。我认为在这里也会有所帮助。不过,请耐心等待。我谈到了你在最后遇到的真正问题。就像预告片一样,这是一个额外引用您的 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 变量引用的对象与@ 尊敬的对象不同987654339@ 如下图所示:

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

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

    参考仍然有效!为什么?这是因为wr 中的引用指向的Line2D 对象还有另一个引用。还记得lines 没有与ax.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>
    

    所以,这个故事的寓意是,清理自己。如果您希望某些东西会被垃圾回收,但事实并非如此,那么您可能会将引用留在某处。

    【讨论】:

    • 正是我需要的。我正在绘制数千张地图,每张地图在世界地图投影的顶部都有一个散点图。他们每人用了3秒!通过重用已绘制地图的图形并从 ax.collections 弹出结果集合,我将其降低到 1/3 秒。谢谢!
    • 我认为在当前版本的 mpl 中不再需要这样做。艺术家有一个remove() 函数,可以将它们从 mpl 方面清除,然后你只需要跟踪你的引用。
    • 呵呵,知道这个变化在哪个版本的 matplotlib 中相同吗?
    • 发现这在 matplotlib 动画中使用一堆图时很有用。否则,您最终会使用大量内存。现在让这件事变得更快。
    【解决方案2】:

    我展示了 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)
    

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

    当然,更简单的方法(当不排除故障时)是将其从列表中弹出并在 line 对象上调用 remove 而不创建对其的硬引用:

    lines.pop(0).remove()
    

    【讨论】:

    • 我运行了你的代码,我得到了:[8:37pm]@flattop:~/Desktop/sandbox>python delete_lines.py 我在 ubuntu 10.04 中使用 matplotlib 版本 0.99.1.1
    • @David Morton 我刚刚降级到 0.99.1,现在我重现了您的问题。我想我只能建议升级到 1.0.1。自 0.99.x 以来有 很多 的错误修正
    • 这里的问题很可能是不应该出现的引用问题。我敢打赌,OP 正在使用 IPython 进行测试。看我的回答。
    【解决方案3】:

    我在不同的论坛尝试了很多不同的答案。我想这取决于您开发的机器。但是我已经使用了该语句

    ax.lines = []
    

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

    例如

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

    但我已尝试多次删除这些行。在我删除时还使用弱引用库检查对该行的引用,但对我没有任何作用。

    希望这对其他人有用=D

    【讨论】:

    • 这里的问题很可能是不应该出现的引用问题。我敢打赌,OP 正在使用 IPython 进行测试。看我的回答。
    【解决方案4】:

    (使用与上面那个人相同的例子)

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

    【讨论】:

      【解决方案5】:

      希望这可以帮助其他人:以上示例使用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
      

      【讨论】:

      • 这应该在列表中更靠前,因为其他答案确实过时且令人困惑。
      【解决方案6】:

      虽然您可以删除轴中具有特定索引的任何行,如下所示:

      import matplotlib.pyplot as plt
      
      fig = plt.figure()
      ax1 = fig.add_subplot(1, 2, 1)
      ax2 = fig.add_subplot(1, 2, 1)
      
      axes = fig.axes  # = [ax1, ax2]
      
      # add two lines to ax1
      ax1.plot([0, 1, 2, 3, 4], [10, 1, 20, 3, 40], lw=2, color='k', label='2 Hz')
      ax1.plot([0, 1, 2, 3, 4], [15, 6, 25, 8, 45], lw=2, color='r', label='4 Hz')
      
      # remove one line from ax1
      ax1.lines[0].remove()  # remove the first line in ax1
      # axes[0].lines[0].remove()  # equivalent to the line above
      
      

      【讨论】:

      • 第五行可以调用fig.get_axes()方法。
      猜你喜欢
      • 2016-06-16
      • 1970-01-01
      • 2020-05-01
      • 2018-03-31
      • 2023-01-22
      • 2019-05-08
      • 2013-04-01
      • 2018-09-29
      • 1970-01-01
      相关资源
      最近更新 更多