Вероятно, самый простой способ анимировать ваши графики Python

Создавайте кадры сюжетов и объединяйте их в GIF

Визуализация всегда является важным использованием Python для науки о данных и анализа данных. Иногда мы хотим, чтобы наши графики двигались для более продвинутого представления и понимания. Однако большинство анимированных библиотек визуализации требуют дополнительных усилий для изучения, например matplotlib.animation. Когда мы просто хотим быстро и легко сгенерировать анимированный график, это может быть не лучший подход.

В этой статье я представлю относительно менее масштабируемый, но гораздо более простой способ анимации наших графиков Python, который использует библиотеку ImageIO. Эта библиотека обычно используется для управления изображениями в Python, а также для объединения нескольких изображений для создания GIF-файлов. Он удивительно прост в использовании.

Прежде чем мы сможем использовать библиотеку ImageIO, нам нужно установить ее с помощью pip следующим образом.

pip install imageio

1. Пример линейного графика

Давайте начнем с базовой линейной диаграммы для этой демонстрации. Чтобы упростить его, я хочу создать массив NumPy с 50 целыми числами. Затем эти целые числа можно изобразить в виде линейной диаграммы.

import numpy as np
import matplotlib.pyplot as plt
import imageio
np.random.seed(0)
SIZE = 50
y = np.random.randint(-5, 5, SIZE)
plt.plot(y)
plt.ylim(-10, 10)
plt.show()

В приведенном выше коде я установил случайное начальное число, чтобы вы могли воспроизвести точно такой же результат, как у меня, если хотите. SIZE определяется как константа, так что вы можете изменить его по своему усмотрению.

Целые числа генерируются в диапазоне [-5,5]. Также, чтобы график было легче читать, я хотел бы добавить ylim(-10, 10), чтобы все точки находились в средней части графика.

Теперь у нас есть эта статическая базовая линейная диаграмма. Допустим, мы хотим анимировать линейный график, нанося точки одну за другой. Также было бы лучше иметь результат в формате GIF, чтобы мы могли встроить его куда угодно.

Конечно, правильный метод — использовать модуль matplotlib.animation. Тем не менее, я бы предложил быстрый и простой способ — создать кадры в виде отдельных изображений PNG. Затем мы можем использовать ImageIO, чтобы объединить их вместе в формате GIF.

1.1 Создание кадров

На самом деле генерировать кадры не так уж и сложно. Идея состоит в том, чтобы построить линейный график с 2 точками, 3 точками, … и 50 точками. Каждый график должен быть сохранен в отдельном изображении.

Код выглядит следующим образом

for i in range(2, SIZE+1):
    plt.plot(y[0:i])
    plt.ylim(-10, 10)
    plt.savefig(f'line-{i}.png')
    plt.close()

Цикл for начинается с 2 точек, потому что одна точка не может быть сгенерирована как допустимая линейная диаграмма. Он остановится на SIZE+1, так что range() будет завершен на точном количестве точек. Другими словами, последние i в нашем случае равны 50.

Важно определить ylim в каждом кадре. В противном случае ось y будет определяться автоматически, поэтому в кадрах она будет разной.

После того, как график настроен, мы можем сохранить фигуру как изображение PNG с предопределенным шаблоном имени файла. Этот шаблон имени файла будет использоваться позже для заполнения фреймов. Наконец, нам нужно закрыть сюжет, вызвав plt.close(). Таким образом, следующий кадр в следующем цикле может быть сгенерирован правильно.

После запуска этого фрагмента кода в текущем рабочем каталоге будут сгенерированы 49 файлов PNG.

1.2 Генерация GIF

Следующим шагом будет объединение их вместе в GIF. Библиотека ImageIO предоставляет «писатель», с помощью которого мы можем легко добавлять изображения в виде фреймов.

with imageio.get_writer('line.gif', mode='i') as writer:
    for i in range(2, SIZE+1):
        image = imageio.imread(f'line-{i}.png')
        writer.append_data(image)

Мы можем использовать оператор with, поэтому нам не нужно беспокоиться о закрытии потока. Имя файла GIF будет называться line.gif. Флаг mode='i' — это подсказка, сообщающая ImageIO, что входными данными будут изображения.

Затем у нас может быть еще один цикл for для получения индекса. Таким образом, мы можем использовать индекс для получения имен файлов кадров. Как только у нас будет имя файла, просто добавьте его к текущему писателю.

После того, как мы запустим приведенный выше код, файл GIF line.gif уже должен быть сгенерирован.

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

import os
for i in range(2, SIZE+1):
    os.remove(f'line-{i}.png')

1.3 Небольшое улучшение

Показанный выше GIF может быть не идеальным. В частности, ось x также перемещается вместе с кадрами. Это может быть нормально в зависимости от того, чего мы хотим достичь, но предположим, что мы хотим зафиксировать ось X в GIF, мы определенно можем это сделать.

Этот пример также показывает, что представляемый здесь метод является достаточно гибким. По сути, когда мы хотим иметь некоторые функции в наших анимированных диаграммах, мы полагаемся не на какие-либо API библиотеки, а на то, как были сгенерированы кадры.

В этом случае нам просто нужно добавить конфигурацию xlim() в исходный код, чтобы генерировать кадры с фиксированной осью x.

for i in range(2, SIZE+1):
    plt.plot(y[0:i])
    plt.ylim(-10, 10)
    plt.xlim(0, 50)
    plt.savefig(f'line-{i}.png')
    plt.close()

После этого код ImageIO для генерации GIF даже не нужно менять, потому что мы обновили содержимое кадров, но количество изображений осталось прежним.

Наконец, новый GIF выглядит следующим образом.

2. Более причудливый пример гистограммы

Метод был хорошо представлен в приведенном выше примере линейного графика. Однако я просто хочу показать, что у метода больше возможностей. На этот раз давайте анимируем гистограмму.

Прежде чем демонстрация может начаться, нам нужно определить ось X и список оси Y. Причина, по которой нам нужен список значений оси Y, заключается в том, что мы хотим, чтобы GIF отображал несколько кадров, и каждый кадр представлял собой гистограмму. Другими словами, они имеют одинаковую ось x, но разные значения y.

x_axis = [1, 2, 3]
y_axis_list = [
    [0, 0, 0],
    [1, 2, 3],
    [3, 2, 1],
    [5, 5, 5],
    [7, 7, 7],
    [9, 2, 9],
    [2, 9, 2],
    [1, 1, 1],
    [9, 9, 9],
    [0, 0, 0]
]

Приведенный выше код является просто примером. Выкройки нет, просто придумал :)

2.1 Генерация гистограммы GIF

Затем давайте создадим гистограмму кадров.

png_files = []
for i, y_axis in enumerate(y_axis_list):
    # Create Plot
    plt.bar(x_axis, y_axis)
    plt.ylim(0,10)
    # Create PNG file
    filename = f'bar-{i}.png'
    png_files.append(filename)
    # Save Figure
    plt.savefig(filename)
    plt.close()
with imageio.get_writer('bar.gif', mode='i') as writer:
    for filename in png_files:
        image = imageio.imread(filename)
        writer.append_data(image)

На этот раз я не хочу возиться с методом range() в цикле for. Мы можем определить список png_files, содержащий все имена файлов кадров. Итак, позже, когда мы захотим создать GIF из изображений PNG, нам просто нужно получить имена файлов из этого списка.

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

Сгенерированный GIF выглядит следующим образом.

Эммм, работает, но не идеально. Вообще читать нельзя.

2.2 Создание GIF с плавным переходом

Мышление с точки зрения анимации. Хорошая анимация — это не что иное, как увеличение FPS и плавный переход. Основная идея состоит в том, чтобы добавить больше переходных кадров между двумя состояниями.

Поэтому мы можем сделать следующее.

smooth_coef = 10
png_files = []
for i in range(0, len(y_axis_list)-1):
    # Get Current & Next Frame
    y_axis_curr = y_axis_list[i]
    y_axis_next = y_axis_list[i+1]
# Generate Middle Frames
    y_diff = np.array(y_axis_next) - np.array(y_axis_curr)
    for j in range(0, smooth_coef+1):
        y_axis = (y_axis_curr + (y_diff / smooth_coef) * j)
        # Create Plot
        plt.bar(x_axis, y_axis)
        plt.ylim(0,10)  
        # Create PNG file
        filename = f'bar-{i}-{j}.png'
        png_files.append(filename)
        # Stretch the last frame
        if j == smooth_coef:
            for _ in range(5):
                png_files.append(filename)
        # Save Figure
        plt.savefig(filename)
        plt.close()

Позвольте мне объяснить это. smooth_coef обозначает «гладкий коэффициент». Это означает, что мы хотим добавить 10 переходных кадров между каждыми двумя основными кадрами. Основные кадры точно соответствуют оси Y в демонстрационном наборе данных.

В цикле for мы получим текущую ось Y и следующую. После этого мы можем использовать следующие значения y для вычитания текущего. Таким образом, результатом будет «разница» между этими двумя основными кадрами. Тогда, если мы позволим «разнице» поделить на гладкий коэффициент, мы получим ступенчатую разницу. Таким образом, мы можем вычислить разницу между каждым переходным кадром. Все добавленные переходные кадры превратят текущий кадр в следующий кадр.

y_axis = (y_axis_curr + (y_diff / smooth_coef) * j)

Другой трюк — позволить GIF «остановиться» на критическом кадре на некоторое время, чтобы мы могли видеть его чуть дольше. Это можно сделать, повторяя имена файлов в определенное время, чтобы кадр повторялся. В сгенерированном GIF мы увидим кадр несколько раз, что также означает большее время.

Давайте посмотрим на результат.

Это гораздо лучше!

3. Метод общий

В отличие от каких-либо конкретных графических библиотек, таких как matplotlib.animation, метод ImageIO-GIF гораздо более общий. То есть мы даже не ограничены в использовании каких библиотек. Пока фигуру можно сохранить в файлы изображений, мы можем использовать этот метод, чтобы позволить ей двигаться.

Быстрый пример выглядит следующим образом. Мы можем использовать Seaborn для создания гистограммы.

import seaborn as sns
sns.set_style('darkgrid')

Модуль Seaborn импортируется и устанавливается стиль. Тогда единственная часть кода, которую нам нужно изменить, выглядит следующим образом.

sns.barplot(x_axis, y_axis)

Полученный GIF выглядит следующим образом.

Краткое содержание

В этой статье я представил метод анимации визуализации Python в GIF без использования каких-либо специально созданных библиотек анимации сюжетов. Идея состоит в том, чтобы генерировать сюжет кадр за кадром в файлы изображений. Затем эти файлы изображений можно объединить вместе в формате GIF с помощью модуля ImageIO.

Метод также довольно общий. Нам не нужно изучать какие-либо API или методы для определенных стилей анимации. Все, что нам нужно сделать, это сгенерировать различные кадры для достижения требований. Кроме того, он даже не ограничивается какими-либо библиотеками визуализации, поскольку опирается на изображения, сгенерированные в виде кадров.

Обратите внимание, что этот метод может не быть обычным для построения графиков. Одной из проблем может быть масштабируемость, потому что нам нужно сгенерировать множество отдельных файлов в виде кадров, прежде чем мы сможем получить GIF.



Если вы считаете, что мои статьи полезны, подумайте о том, чтобы присоединиться к Medium Membership, чтобы поддержать меня и тысячи других писателей! (Нажмите на ссылку выше)