【问题标题】:How to animate a scatter plot如何动画散点图
【发布时间】:2012-03-13 04:27:44
【问题描述】:

我正在尝试制作散点图的动画,其中点的颜色和大小在动画的不同阶段发生变化。对于数据,我有两个带有 x 值和 y 值的 numpy ndarray:

data.shape = (ntime, npoint)
x.shape = (npoint)
y.shape = (npoint)

现在我想绘制类型的散点图

pylab.scatter(x,y,c=data[i,:])

并在索引i 上创建动画。我该怎么做?

【问题讨论】:

标签: python matplotlib


【解决方案1】:

TL/DR:如果您在使用 ax.set_... 动画散点图的方法时遇到问题,您可以尝试只清除每帧的绘图(即 ax.clear())并重新- 根据需要绘制事物。这较慢,但当您想在一个小动画中更改很多内容时可能会很有用。


这是一个展示这种“清晰”方法的示例:

import itertools

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

# set parameters
frames = 10
points = 20
np.random.seed(42)

# create data
data = np.random.rand(points, 2)

# set how the graph will change each frame
sizes = itertools.cycle([10, 50, 150])
colors = np.random.rand(frames, points)
colormaps = itertools.cycle(['Purples', 'Blues', 'Greens', 'Oranges', 'Reds'])
markers = itertools.cycle(['o', 'v', '^', 's', 'p'])

# init the figure
fig, ax = plt.subplots(figsize=(5,5))

def update(i):
    # clear the axis each frame
    ax.clear()

    # replot things
    ax.scatter(data[:, 0], data[:, 1],
               s=next(sizes),
               c=colors[i, :],
               cmap=next(colormaps),
               marker=next(markers))

    # reformat things
    ax.set_xlabel('world')
    ax.set_ylabel('hello')

ani = animation.FuncAnimation(fig, update, frames=frames, interval=500)
ani.save('scatter.gif', writer='pillow')

我从 matplotlib 和其他来源看到的教程似乎没有使用这种方法,但我看到其他人(以及我自己)在这个站点上建议它。我看到了一些优点和缺点,但我会感谢其他人的想法:

优点

  • 您可以避免对散点图使用 set_... 方法(即 .set_offsets().set_sizes()、...),这些方法的文档更加晦涩难懂(虽然主要的答案会得到你在这里很远!)。另外,对于不同的绘图类型有不同的方法(例如,您使用set_data 表示线条,但不用于散点)。通过清除轴,您可以像 matplotlib 中的任何其他绘图一样确定每帧绘制的元素。
  • 更重要的是,不清楚某些属性是否为set-able,例如标记类型(as commented) 或颜色图。例如,由于标记和颜色图发生了变化,我不知道如何使用ax.set_... 创建上述图。但这对于 ax.scatter() 来说是非常基本的。

缺点

  • 可能会慢很多;即清除和重绘一切似乎比set... 方法更昂贵。所以对于大型动画,这种方法可能有点痛苦。
  • 清除轴也会清除轴标签、轴限制、其他文本等内容。因此,这些格式的内容需要包含在update 中(否则它们将消失)。如果您希望更改某些内容而其他内容保持不变,这可能会很烦人。

当然,速度是个大问题。这是一个显示差异的示例。鉴于此数据:

import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

np.random.seed(42)
frames = 40

x = np.arange(frames)
y = np.sin(x)
colors = itertools.cycle(['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'])
data = [(np.random.uniform(-1, 1, 10) + x[i],
         np.random.uniform(-1, 1, 10) + y[i])
        for i in range(frames)]

您可以使用set... 方法进行绘图:

fig, ax = plt.subplots()

s = ax.scatter([], [])

ax.set_xlim(-2, frames+2)
ax.set_ylim(min(y) - 1, max(y) + 1)

def update(i):
    s.set_offsets(np.column_stack([data[i][0], data[i][1]]))
    s.set_facecolor(next(colors))

ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)
ani.save('set.gif', writer='pillow')

或“清除”方法:

fig, ax = plt.subplots()

def update(i):
    ax.clear()
    ax.scatter(data[i][0], data[i][1], c=next(colors))
    ax.set_xlim(-2, frames+2)
    ax.set_ylim(min(y) - 1, max(y) + 1)

ani = animation.FuncAnimation(fig, update, frames=frames, interval=100)
ani.save('clear.gif', writer='pillow')

要得到这个数字:

使用%%time,我们可以看到清除和重新绘制需要(超过)两倍的时间:

  • 对于set...Wall time: 1.33 s
  • 明确:Wall time: 2.73 s

使用frames 参数在不同的尺度上进行测试。对于较小的动画(较少的帧/数据),这两种方法之间的时间差异是无关紧要的(对我来说,有时会导致我更喜欢清除方法)。但对于较大的情况,使用set_... 可以节省大量时间。

【讨论】:

    【解决方案2】:

    假设你有一个散点图scat = ax.scatter(...),那么你可以

    • 改变位置

            scat.set_offsets(array)
      

    其中array 是一个N x 2 形状的x 和y 坐标数组。

    • 更改尺寸

            scat.set_sizes(array)
      

    其中array 是以点为单位的一维数组。

    • 改变颜色

            scat.set_array(array)
      

    其中array 是一个一维数组,将进行颜色映射。

    这是一个使用 animation module 的简单示例。
    它比它必须的稍微复杂一些,但这应该会给你一个框架来做更有趣的事情。

    (代码于 2019 年 4 月编辑以与当前版本兼容。有关旧代码,请参阅 revision history

    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    import numpy as np
    
    class AnimatedScatter(object):
        """An animated scatter plot using matplotlib.animations.FuncAnimation."""
        def __init__(self, numpoints=50):
            self.numpoints = numpoints
            self.stream = self.data_stream()
    
            # Setup the figure and axes...
            self.fig, self.ax = plt.subplots()
            # Then setup FuncAnimation.
            self.ani = animation.FuncAnimation(self.fig, self.update, interval=5, 
                                              init_func=self.setup_plot, blit=True)
    
        def setup_plot(self):
            """Initial drawing of the scatter plot."""
            x, y, s, c = next(self.stream).T
            self.scat = self.ax.scatter(x, y, c=c, s=s, vmin=0, vmax=1,
                                        cmap="jet", edgecolor="k")
            self.ax.axis([-10, 10, -10, 10])
            # For FuncAnimation's sake, we need to return the artist we'll be using
            # Note that it expects a sequence of artists, thus the trailing comma.
            return self.scat,
    
        def data_stream(self):
            """Generate a random walk (brownian motion). Data is scaled to produce
            a soft "flickering" effect."""
            xy = (np.random.random((self.numpoints, 2))-0.5)*10
            s, c = np.random.random((self.numpoints, 2)).T
            while True:
                xy += 0.03 * (np.random.random((self.numpoints, 2)) - 0.5)
                s += 0.05 * (np.random.random(self.numpoints) - 0.5)
                c += 0.02 * (np.random.random(self.numpoints) - 0.5)
                yield np.c_[xy[:,0], xy[:,1], s, c]
    
        def update(self, i):
            """Update the scatter plot."""
            data = next(self.stream)
    
            # Set x and y data...
            self.scat.set_offsets(data[:, :2])
            # Set sizes...
            self.scat.set_sizes(300 * abs(data[:, 2])**1.5 + 100)
            # Set colors..
            self.scat.set_array(data[:, 3])
    
            # We need to return the updated artist for FuncAnimation to draw..
            # Note that it expects a sequence of artists, thus the trailing comma.
            return self.scat,
    
    
    if __name__ == '__main__':
        a = AnimatedScatter()
        plt.show()
    

    如果您在 OSX 上并使用 OSX 后端,则需要在下面的 FuncAnimation 初始化中将 blit=True 更改为 blit=False。 OSX 后端不完全支持 blitting。性能会受到影响,但该示例应该在禁用 blitting 的 OSX 上正确运行。


    对于仅更新颜色的更简单示例,请查看以下内容:

    import matplotlib.pyplot as plt
    import numpy as np
    import matplotlib.animation as animation
    
    def main():
        numframes = 100
        numpoints = 10
        color_data = np.random.random((numframes, numpoints))
        x, y, c = np.random.random((3, numpoints))
    
        fig = plt.figure()
        scat = plt.scatter(x, y, c=c, s=100)
    
        ani = animation.FuncAnimation(fig, update_plot, frames=range(numframes),
                                      fargs=(color_data, scat))
        plt.show()
    
    def update_plot(i, data, scat):
        scat.set_array(data[i])
        return scat,
    
    main()
    

    【讨论】:

    • 嗨,乔,我已经尝试了您的第一个示例,但它不起作用,而第二个示例可以。也许我会尝试调试第一个选项,这将帮助我提高我的 python 知识。谢谢
    • 不幸的是,第一个示例在 OS X 上使用 matplotlib 1.3.1 时也没有显示。我得到的框架没有显示任何点。第二个例子有效。
    • 你到底是怎么知道.set_array()会更新点颜色的?!
    • 第一个示例不起作用,您必须将行 self.Scat.set_offsets(data[:2, :]) 更改为 self.scat.set_offsets(data[:2, :].reshape(self.numpoints, 2))
    • 有没有改变散点标记的函数?
    【解决方案3】:

    为什么不试试这个

    import numpy as np
    import matplotlib.pyplot as plt
    
    x=np.random.random()
    y=np.random.random()
    
    fig, ax = plt.subplots()
    ax.scatter(x,y,color='teal')
    ax.scatter(y,x,color='crimson')
    ax.set_xlim([0,1])
    ax.set_ylim([0,1])
    
    for i in np.arange(50):
        x=np.random.random()
        y=np.random.random()
        bha=ax.scatter(x,y)
        plt.draw()
        plt.pause(0.5)
        bha.remove()
    
    plt.show()
    

    【讨论】:

      【解决方案4】:

      我写了celluloid 是为了让这更容易。通过示例可能最容易展示:

      import matplotlib.pyplot as plt
      from matplotlib import cm
      import numpy as np
      from celluloid import Camera
      
      numpoints = 10
      points = np.random.random((2, numpoints))
      colors = cm.rainbow(np.linspace(0, 1, numpoints))
      camera = Camera(plt.figure())
      for _ in range(100):
          points += 0.1 * (np.random.random((2, numpoints)) - .5)
          plt.scatter(*points, c=colors, s=100)
          camera.snap()
      anim = camera.animate(blit=True)
      anim.save('scatter.mp4')
      

      它在后台使用ArtistAnimationcamera.snap 捕获用于在动画中创建帧的图形的当前状态。

      编辑:为了量化它使用了多少内存,我通过memory_profiler 运行它。

      Line #    Mem usage    Increment   Line Contents
      ================================================
          11     65.2 MiB     65.2 MiB   @profile
          12                             def main():
          13     65.2 MiB      0.0 MiB       numpoints = 10
          14     65.2 MiB      0.0 MiB       points = np.random.random((2, numpoints))
          15     65.2 MiB      0.1 MiB       colors = cm.rainbow(np.linspace(0, 1, numpoints))
          16     65.9 MiB      0.6 MiB       fig = plt.figure()
          17     65.9 MiB      0.0 MiB       camera = Camera(fig)
          18     67.8 MiB      0.0 MiB       for _ in range(100):
          19     67.8 MiB      0.0 MiB           points += 0.1 * (np.random.random((2, numpoints)) - .5)
          20     67.8 MiB      1.9 MiB           plt.scatter(*points, c=colors, s=100)
          21     67.8 MiB      0.0 MiB           camera.snap()
          22     70.1 MiB      2.3 MiB       anim = camera.animate(blit=True)
          23     72.1 MiB      1.9 MiB       anim.save('scatter.mp4')
      

      总结一下:

      • 创建 100 个图使用 1.9 MiB。
      • 使用 2.3 MiB 制作动画。
      • 这种制作动画的方法总共使用了 4.2 MiB 的内存。

      【讨论】:

      • 由于使用了ArtistAnimation,所以会在内存中创建100个散点图,效率相当低。仅当性能对您不重要时才使用此选项。
      • 内存分析是个好主意。你对FuncAnimation 做了同样的事情吗?有什么区别?
      • 如何显示动画(相对于将其保存到文件)?
      • 它还在使用 matplotlib 吗?
      【解决方案5】:

      事情就是这样。我曾经是Qt和Matlab的用户,对matplotlib上的动画系统不太熟悉。

      但我确实找到了一种方法,可以制作您想要的任何类型的动画,就像在 matlab 中一样。它真的很强大。无需检查模块引用,您可以绘制任何您想要的东西。所以我希望它可以帮助。

      基本思想是在 PyQt 中使用时间事件(我确信 Python 上的其他 Gui 系统,如 wxPython 和 TraitUi 具有相同的内部机制来做出事件响应。但我只是不知道如何)。每次调用 PyQt 的 Timer 事件时,我都会刷新整个画布并重绘整个画面,我知道速度和性能可能会慢慢受到影响,但影响不大。

      这是一个小例子:

      import sys
      from PyQt4 import QtGui
      
      from matplotlib.figure import Figure
      from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
      
      import numpy as np
      
      
      class Monitor(FigureCanvas):
          def __init__(self):
              self.fig = Figure()
              self.ax = self.fig.add_subplot(111)
      
              FigureCanvas.__init__(self, self.fig)
              self.x = np.linspace(0,5*np.pi,400)
              self.p = 0.0
              self.y = np.sin(self.x+self.p)
      
      
              self.line = self.ax.scatter(self.x,self.y)
      
              self.fig.canvas.draw()
      
              self.timer = self.startTimer(100)
      
      
          def timerEvent(self, evt):
              # update the height of the bars, one liner is easier
              self.p += 0.1
              self.y = np.sin(self.x+self.p)
              self.ax.cla()
              self.line = self.ax.scatter(self.x,self.y)
      
              self.fig.canvas.draw()
      
      
      
      if __name__ == "__main__":
          app = QtGui.QApplication(sys.argv)
          w = Monitor()
          w.setWindowTitle("Convergence")
          w.show()
          sys.exit(app.exec_())
      

      您可以在

      中调整刷新速度
              self.timer = self.startTimer(100)
      

      我和你一样想用动画散点图制作排序动画。但我就是找不到所谓的“设置”功能。所以我刷新了整个画布。

      希望对你有帮助..

      【讨论】:

      • 真的很好!但是我没有通过调整self.startTimervalue 来改变刷新率......关于这个的任何提示? (是的,我知道已经有一段时间了......)
      猜你喜欢
      • 2019-12-28
      • 2021-03-05
      • 2021-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多