Как оживить точечный сюжет?

Я пытаюсь сделать анимацию рассеянного графика, где цвета и размер точек меняются на разных этапах анимации. Для данных у меня есть два numpy ndarray со значением x и значением y:

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

Теперь я хочу построить точечный график типа

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

и создать анимацию по индексу i, Как мне это сделать?

5 ответов

Вот быстрый пример использования нового модуля анимации.

Это немного сложнее, чем должно быть, но это должно дать вам основу для более причудливых вещей.

Если вы используете OSX и используете OSX backend, вам нужно изменить blit=True в blit=False в FuncAnimation инициализация ниже. Серверная часть OSX не полностью поддерживает блиттинг. Производительность пострадает, но пример должен работать правильно на OSX с отключенным блиттингом.

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)
        self.scat = self.ax.scatter(x, y, c=c, s=s, animated=True)
        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."""
        data = np.random.random((4, self.numpoints))
        xy = data[:2, :]
        s, c = data[2:, :]
        xy -= 0.5
        xy *= 10
        while True:
            xy += 0.03 * (np.random.random((2, self.numpoints)) - 0.5)
            s += 0.05 * (np.random.random(self.numpoints) - 0.5)
            c += 0.02 * (np.random.random(self.numpoints) - 0.5)
            yield data

    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._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,

    def show(self):
        plt.show()

if __name__ == '__main__':
    a = AnimatedScatter()
    a.show()

Для более простого примера взгляните на следующее:

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=xrange(numframes),
                                  fargs=(color_data, scat))
    plt.show()

def update_plot(i, data, scat):
    scat.set_array(data[i])
    return scat,

main()

Я написал целлулоид, чтобы сделать это проще. Это, вероятно, проще всего показать на примере:

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

Оно использует ArtistAnimation под капотом. camera.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 МиБ.
  • Для создания анимации использовано 2,3 МиБ.
  • Этот метод создания анимации использовал 4,2 МБ памяти в сумме.

TL / DR: если у вас возникли проблемы с методами анимации графика рассеяния, вы можете попытаться просто очищать график в каждом кадре (т. Е. 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_offsets(), .set_sizes(), ...), у которых есть более неясная и менее подробная документация ( хотя основной ответ уведет вас здесь очень далеко! ). Кроме того, существуют разные методы для разных типов сюжетов (например, вы используете set_dataдля линий, но не для точек разброса). Скорее, вы определяете построенные элементы в каждом кадре, как любой другой график в matplotlib.
  • Более того, неясно, являются ли некоторые свойства set-able, например тип маркера ( указано какв комментариях) или цветовая карта . Я бы не знал, как создать приведенный выше сюжет, используя ax.set_..., например, из-за изменения маркера и цветовой карты. Но это довольно просто с ax.scatter().

Минусы

  • Это может быть намного медленнее ; т.е. очистка и перерисовка всего оказывается дороже, чем методы. Так что для больших анимаций этот подход может быть болезненным.
  • Очистка оси также очищает такие вещи, как метки осей, пределы оси, другой текст и т. Д. Таким образом, такие вещи форматирования должны быть включены в 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)]

Вы можете построить, используя метод:

      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_... может значительно сэкономить время.

Вот эта вещь. Я привык к пользователям Qt и Matlab, и я не совсем знаком с системой анимации на matplotlib.

Но я нашел способ, который может создать любую анимацию, какую вы захотите, как в matlab. Это действительно мощный. Не нужно проверять ссылки на модули, и вы можете построить все, что хотите. Надеюсь, это поможет.

Основная идея состоит в том, чтобы использовать событие времени внутри PyQt(я уверен, что другие системы Gui на Python, такие как wxPython и TraitUi, имеют один и тот же внутренний механизм для ответа на событие. Но я просто не знаю как). Каждый раз, когда вызывается событие таймера PyQt, я обновляю весь холст и перерисовываю всю картинку, я знаю, что на скорость и производительность можно медленно влиять, но это не так уж много.

Вот небольшой пример этого:

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)

Я, как и вы, хотите использовать анимированную диаграмму рассеяния для создания анимации сортировки. Но я просто не могу найти так называемую функцию "set". Поэтому я обновил всю канву.

Надеюсь, поможет..

Почему бы не попробовать это

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()
Другие вопросы по тегам