matplotlib祕技:讓視覺化圖形動起來
作者:Viviane Kakerbeck
編譯:weakish
編者按:其實matplotlib有一個少有人知的功能animation.FuncAnimation,可以接受你編寫的動畫函式建立動圖。Viviane Kakerbeck通過一個例子展示了這一功能的用法,並介紹了通過增強資料和高斯平滑,讓動圖更美觀的技巧。

Python的matplotlib和seaborn是非常好用的繪相簿。但它們建立的都是靜態影象,難以通過動態、美觀的方式描述資料值的變化。如果你的下一次演示或者下一篇部落格文章,能用動態圖形展示資料的發展,該有多好?更妙的是,你可以繼續使用matplotlib、seaborn或者其他你喜歡用的庫。
我最近為一部關於美國的阿片樣物質危機的紀錄片製作了一些動態圖形,所以我會在這篇文章中使用相關的資料。資料來自美國國家藥物濫用研究所和CDC的公開資料,可以從以下網址下載: https://www. drugabuse.gov/sites/def ault/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
準備就緒,下面是本文主要部分,如何繪製動畫。
首先,如果你和我一樣,用的是jupyter notebook,那麼我建議你使用%matplotlib notebook指令,這樣可以直接在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 fps(位元率為1800)。當然,你可以按照需要調整這些引數。
Writer = animation.writers['ffmpeg'] writer = Writer(fps=20, metadata=dict(artist='Me'), bitrate=1800)
接下來建立一個帶標籤的圖形。別忘了限定x軸和y軸的範圍,以免動畫在顯示資料時出現跳躍現象。
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表示動畫幀的索引。你可以選擇在i幀中可見的資料範圍。之後我使用seaborn的線圖繪製選定資料。最後兩行我調整了一些尺寸,使圖形看起來更美觀。
def animate(i): data = overdose.iloc[:int(i+1)]# 選定資料範圍 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)
之後只需呼叫ani.save()就可以將動畫儲存為mp4檔案。如果你想在儲存之前先看下效果,那麼可以使用plt.show()。
ani.save('HeroinOverdosesJumpy.mp4', writer=writer)
好了,讓我們來看下效果。

看起來效果還可以,但是感覺有點抖動。為了緩解抖動的現象,我們可以在已有資料中插入一些中間值,平滑一下。為此我們定義以下的資料增強函式:
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。增強資料之後,結果看起來平滑了很多,但在資料值變動處,曲線仍有一些尖角,看起來不是特別美觀。
為了讓影象看起來更美觀,我們實現了一個高斯平滑函式:
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 smoothe
我在這裡不會討論高斯平滑的細節,感興趣的讀者可以看這篇文章: https://www. swharden.com/wp/2008-11 -17-linear-data-smoothing-in-python/
最後我們略微調整下顏色和風格,就得到了文章開頭的動態圖形:
sns.set(rc={'axes.facecolor':'lightgrey', 'figure.facecolor':'lightgrey','figure.edgecolor':'black','axes.grid':False})

本文通過一個例子展現了matplotlib動畫函式的用法。當然,你可以將它用在任何你想要動畫化的圖形上。只需調整animate()函式中的引數和圖形型別,便有無限可能。
我希望你喜歡matplotlib的整個功能,並能善加利用。另外,如果你對我之前提到的紀錄片感興趣,可以在YouTube上檢視: https:// youtu.be/7xrvuSDLHiY