Лучший опыт

Как создавать анимированные графы в Python.

Matplotlib и Seaborn — вполне приличные Python-библиотеки для создания превосходных графиков. Но такие графики получаются статичными, и крайне трудно подобрать для них красивое представление данных или отследить динамику изменений. Вам бы понравилось, если бы в своей следующей презентации/видео/посте в соцсетях вы бы смогли показать динамику изменений в виде короткого видеоролика? И даже больше: такие графики можно было бы создать в Matplotlib, S
Как создавать анимированные графы в Python...

Matplotlib и Seaborn — вполне приличные Python-библиотеки для создания превосходных графиков. Но такие графики получаются статичными, и крайне трудно подобрать для них красивое представление данных или отследить динамику изменений. Вам бы понравилось, если бы в своей следующей презентации/видео/посте в соцсетях вы бы смогли показать динамику изменений в виде короткого видеоролика? И даже больше: такие графики можно было бы создать в Matplotlib, Seaborn или любой другой библиотеке!

Не так давно я создал несколько динамических графиков для короткой документалки об опиоидном кризисе в США, так что данные для статьи будут браться оттуда. Эта информация размещена в открытом доступе на сайтах Национального института по изучению злоупотребления наркотиками и Центра по контролю и профилактике заболеваний. Скачать можно по ссылке: https://www.drugabuse.gov/sites/default/files/overdose_data_1999-2015.xls.

Строить графики я буду в Matplotlib и Seaborn, а для обработки данных воспользуюсь Numpy и Pandas. Matplotlib предлагает несколько функций для анимации. Так что давайте начнем и импортируем все зависимости.

import numpy as np  import pandas as pd  import seaborn as sns  import matplotlib  import matplotlib.pyplot as plt  import matplotlib.animation as animation

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

overdoses = pd.read_excel('overdose_data_1999-2015.xls',sheetname='Online',skiprows =6)  def get_data(table,rownum,title):      data = pd.DataFrame(table.loc[rownum][2:]).astype(float)      data.columns = {title}      return data

Давайте приступим к делу и перейдем к созданию анимации!


Во-первых, если вы, как и я, пользуетесь Jupiter Notebook, то начните ячейку с %matplotlib notebook— так вы увидите анимацию сразу, а не только после сохранения.

Лично я извлекал статистику по передозировке героином из таблицы с помощью функции get_data, а затем переносил данные в Pandas DataFrame в две колонки. Первая — год, вторая — количество передозировок.

%matplotlib notebook  title = 'Heroin Overdoses'  d = get_data(overdoses,18,title)  x = np.array(d.index)  y = np.array(d['Heroin Overdoses'])  overdose = pd.DataFrame(y,x)  #XN,YN = augment(x,y,10)  #augmented = pd.DataFrame(YN,XN)  overdose.columns = {title}

Далее инициируемwriter, который использует ffmpeg и пишет 20 кадров в секунду с битрейтом 1800. Конечно же, эти значения вы можете настроить сами.

Writer = animation.writers['ffmpeg']  writer = Writer(fps=20, metadata=dict(artist='Me'), bitrate=1800)

Теперь создадим график с обозначениями. Обязательно задавайте пределы для осей Х и У — так анимация не будет «скакать» по диапазону отображаемых данных.

fig = plt.figure(figsize=(10,6))  plt.xlim(1999, 2016)  plt.ylim(np.min(overdose)[0], np.max(overdose)[0])  plt.xlabel('Year',fontsize=20)  plt.ylabel(title,fontsize=20)  plt.title('Heroin Overdoses per Year',fontsize=20)

Основной компонент графика — функция анимации, которой вы задаете, что должно происходить в каждом кадре видео. Здесь i — индекс кадра анимации. Данным индексом вы выделяете диапазон данных, которые должны показываться в кадре. Затем выстраиваете эти данные в seaborn lineplot. Две последние строчки нужны для красоты.

def animate(i):      data = overdose.iloc[:int(i+1)] #select data range      p = sns.lineplot(x=data.index, y=data[title], data=data, color="r")      p.tick_params(labelsize=17)      plt.setp(p.lines,linewidth=7)

Запуск анимации делается через matplotlib.animation.FuncAnimation — там вы привязываете функцию анимации и задаете общее количество кадров. frames определяет частоту, с которой будет вызыватьсяanimate(i).

ani = matplotlib.animation.FuncAnimation(fig, animate, frames=17, repeat=True)

Для сохранения анимации в mp4 смело вызывайте ani.save() . Если вам нужен предпросмотр перед сохранением, то лучше воспользуйтесь plt.show() .

ani.save('HeroinOverdosesJumpy.mp4', writer=writer)

Теперь анимация выглядит вот так:

 

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

def augment(xold,yold,numsteps):      xnew = []      ynew = []      for i in range(len(xold)-1):          difX = xold[i+1]-xold[i]          stepsX = difX/numsteps          difY = yold[i+1]-yold[i]          stepsY = difY/numsteps          for s in range(numsteps):              xnew = np.append(xnew,xold[i]+s*stepsX)              ynew = np.append(ynew,yold[i]+s*stepsY)      return xnew,ynew

Теперь осталось применить эту функцию к данным и увеличить количество кадров в matplotlib.animation.FuncAnimation. Здесь я задаю augment с numsteps=10. Это означает, что я увеличиваю набор данных до 160 точек и указываю frames=160 . Результат получается более сглаженным, однако на графике до сих пор присутствует несколько острых пиков.

 

Чтобы убрать эту «остроту» можно добавить сглаживающую функцию отсюда: https://www.swharden.com/wp/2008-11-17-linear-data-smoothing-in-python/

def smoothListGaussian(listin,strippedXs=False,degree=5):        window=degree*2-1        weight=np.array([1.0]*window)        weightGauss=[]        for i in range(window):            i=i-degree+1            frac=i/float(window)            gauss=1/(np.exp((4*(frac))**2))            weightGauss.append(gauss)      weight=np.array(weightGauss)*weight        smoothed=[0.0]*(len(listin)-window)        for i in range(len(smoothed)):        smoothed[i]=sum(np.array(listin[i:i+window])*weight)/sum(weight)        return smoothed

Еще можно добавить параметры цвета и стиля, чтобы сделать график более оригинальным.

sns.set(rc={'axes.facecolor':'lightgrey', 'figure.facecolor':'lightgrey','figure.edgecolor':'black','axes.grid':False})

Так мы получаем окончательный вариант, показанный выше.

В данной статье мы разобрали функцию анимации в matplotlib на одном примере. Конечно же, с ее помощью можно анимировать любые типы графиков. Просто настройте параметры и тип графика в функции animate() и вы получите бескрайние возможности!

 

Перевод статьи Viviane: How to Create Animated Graphs in Python