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

Предыдущая статьяПочему разработчикам нужны “часы практики”
Следующая статьяУ меня появилась идея для приложения, но…я не разработчик