Matplotlib玩轉(zhuǎn)動態(tài)可視化

最近看到很多盆友們用pyecharts、Bokeh和plotly等繪圖庫制作動態(tài)圖,還有用pbi制作的,以及網(wǎng)頁工具flourish等。其實(shí)matplotlib這個經(jīng)典繪圖庫也是可以的,這不就來了嘛~
目錄
1.效果預(yù)覽 2.數(shù)據(jù)獲取 3.數(shù)據(jù)預(yù)處理 4.matplotlib動態(tài)可視化
1.效果預(yù)覽
我們從國家統(tǒng)計(jì)局 下載最近30年全國各地區(qū)生產(chǎn)總值(實(shí)際上是1993年-2019年),使用matplotlib繪制動態(tài)可視化圖,效果如下:

2.數(shù)據(jù)獲取
直接從國家統(tǒng)計(jì)局-「國家數(shù)據(jù)」(http://data.stats.gov.cn/)下載原始數(shù)據(jù)即可,數(shù)據(jù)長這樣:

3.數(shù)據(jù)預(yù)處理
#?需要引入的庫
import?pandas?as?pd
import?matplotlib.pyplot?as?plt
import?matplotlib.ticker?as?ticker
import?matplotlib.animation?as?animation
from?IPython.display?import?HTML
原始數(shù)據(jù)的行索引是地區(qū),列索引是年份,我們后續(xù)作圖需要的數(shù)據(jù)結(jié)構(gòu)偏向于窄表(當(dāng)然寬表其實(shí)也可以做到,這里我個人習(xí)慣用窄表處理)。
為了實(shí)現(xiàn)寬表變窄表,用到pandas里的melt方法。
import?pandas?as?pd
#?讀取下載后的數(shù)據(jù)
df?=?pd.read_excel(r'F:\微信公眾號\matplotlib動態(tài)圖\各地區(qū)生產(chǎn)總值.xlsx')
df.head()

#?使用melt方法進(jìn)行處理
data?=?df.melt(id_vars='地區(qū)',value_vars=range(1993,2020),var_name='年份',value_name='生產(chǎn)總值(億)')
data.head()

4.matplotlib動態(tài)可視化
matplotlib動態(tài)圖用到的是animation.FuncAnimation方法,其實(shí)動態(tài)就是N張圖一張一張按照一定頻率刷新,我們也有其他方法實(shí)現(xiàn),這里不展開。
在我們的效果展示中,可以看到 類型是條形圖,數(shù)值高低排序,每個條形圖顏色不一樣,我們來一步一步看看如何做出最終效果~
4.1.樸實(shí)無華的條形圖
barh是條形圖,就是橫著的柱狀圖,以下我們先取2019年的年度數(shù)據(jù)展示前10地區(qū)
import?matplotlib.pyplot?as?plt
import?matplotlib.ticker?as?ticker
#?以下代碼解決顯示中文問題
plt.rcParams['font.sans-serif']?=?['SimHei']
#?繪制?2019年?各地區(qū)生產(chǎn)總值(前10)
currentYear?=?2019
ddata?=?(data[data['年份']==currentYear]
????????.sort_values(by='生產(chǎn)總值(億)',ascending?=?True)
????????.tail(10))
fix,?ax?=?plt.subplots(figsize=(15,8))
ax.barh(ddata['地區(qū)'],?ddata['生產(chǎn)總值(億)'])

我們看到上面這張圖平平無奇,樸實(shí)無華的配色,沒有多一分的元素(標(biāo)題、數(shù)據(jù)標(biāo)簽等等),接下來我們先把條形圖美化一下
4.2.有點(diǎn)還行的條形圖
通過自定義條形圖配色,再附上一些text說明。
關(guān)于配色,直接從網(wǎng)上(https://www.beejson.com/word/rgb.html)找 16進(jìn)制配色表,然后取31個即可(咱們一共有31個地區(qū)數(shù)據(jù)不含港澳臺)。然后將31個地區(qū)與31個顏色進(jìn)行組合成字典備用!
「構(gòu)造地區(qū)-顏色字典」
#?我直接從網(wǎng)上批量復(fù)制了?30多個顏色值,然后隨機(jī)抽取31個和31個地區(qū)配對
a?=?['#FFFFCC??#FFCC00??#CC9909??#663300??#FF6600??#663333??#CC6666??#FF6666??#FF0000??#FFFF99??#FFCC66??#FF9900??#FF9966??#CC3300??#996666??#FFCCCC??#660000??#FF3300??#FF6666??#FFCC33??#CC6600??#FF6633??#996633??#CC9999??#FF3333??#990000??#CC9966??#FFFF33??#CC9933??#993300??#FF9933??#330000??#993333??#CC3333??#CC0000??#FFCC99??#FFFF00??#996600']
b?='?'.join(a)
c?=?b.split('?\t')
import?random
#?隨機(jī)不放回抽取31個
color?=?random.sample(c,31)
#?獲取原數(shù)據(jù)中地區(qū)列表
province?=?list(data['地區(qū)'].unique())
#?組合成?地區(qū)-顏色值?字典
colors?=?dict(zip(province,color))
「繪制有顏的條形圖」
fig,?ax?=?plt.subplots(figsize=(15,8))
ax.barh(ddata['地區(qū)'],?ddata['生產(chǎn)總值(億)'],color?=?[colors[x]?for?x?in?ddata['地區(qū)']])
#?在每個條形圖末端顯示?歸屬地區(qū)?及?生產(chǎn)總值(億)
for?i,?(num,?pro)?in?enumerate(zip(ddata['生產(chǎn)總值(億)'],?ddata['地區(qū)'])):
????ax.text(num,i,?pro,?ha?=?'right')
????ax.text(num,i,?f':{num}億',?ha?=?'left')
#?在右側(cè)中部偏下顯示當(dāng)前年份
ax.text(1,?0.4,?currentYear,?transform=?ax.transAxes,?size=40,?ha='right')
Text(1,?0.4,?'2019')

有人就要說了,上面這個圖也沒啥好看的,除了增加了單獨(dú)的配色以及數(shù)據(jù)顯示外。。講的太對了,字體還丑、顏色搭配也是難看,當(dāng)然這些都是可以自己配置的 因?yàn)楹罄m(xù) 我們會換個plt.xkcd()**「手繪卡通風(fēng)格」**的形式,但是卡通風(fēng)格的形式需要特別處理中文字體顯示問題,這里先介紹下來自好朋友 「'小明哥'」 的幫助,如下代碼(設(shè)置字體為我系統(tǒng)里的微軟雅黑,字號16)
#?字體管理
from?matplotlib?import?font_manager
my_font?=?font_manager.FontProperties(fname=r"C:\Windows\Fonts\msyh.ttc",size=14)
#?再加入其他一些元素(如標(biāo)題、刻度線、刻度放在最上方等)
fig,?ax?=?plt.subplots(figsize=(15,8))
ax.barh(ddata['地區(qū)'],?ddata['生產(chǎn)總值(億)'],color?=?[colors[x]?for?x?in?ddata['地區(qū)']])
for?i,?(num,?pro)?in?enumerate(zip(ddata['生產(chǎn)總值(億)'],?ddata['地區(qū)'])):
????ax.text(num,i,?pro,?ha?=?'right',fontproperties=my_font)
????ax.text(num,i,?f'?:{num}億',?ha?=?'left',fontproperties=my_font)
ax.text(1,?0.4,?currentYear,?transform=?ax.transAxes,fontproperties=my_font,?size=40,?ha='right')
#?x刻度設(shè)置在頂部
ax.xaxis.set_ticks_position('top')
#?x刻度顏色設(shè)置為灰色,大小為12
ax.tick_params(axis=?'x',colors=?'#777777',labelsize=?12)
#?去掉y刻度
ax.set_yticks([])
#?設(shè)置?xy軸內(nèi)邊距
ax.margins(0,?0.01)
#?顯示網(wǎng)格(x軸向虛線)
ax.grid(which=?'major',?axis=?'x',linestyle=?'--')
#?網(wǎng)格線至于底部
ax.set_axisbelow(True)
#?在左上角顯示?標(biāo)題(不是用的title方法)
ax.text(0,?1.06,'全國各地區(qū)生產(chǎn)總值(1993-2019)',transform=?ax.transAxes,weight?=600,ha?=?'left',fontproperties=?my_font,size=24)
#?去掉邊框
plt.box(False)

4.3.會動的條形圖
既然動圖是一張張圖刷新而來,那我們把每年的數(shù)據(jù)都做一張圖再定時刷新替換不就好了,這樣當(dāng)然是可以的。
所以,我們先看看「animation.FuncAnimation」(https://matplotlib.org/api/animation_api.html)吧
官網(wǎng)有個簡單的例子:sin(x)的動態(tài)演示。這里不做介紹,源碼清晰,我們直接現(xiàn)學(xué)現(xiàn)做~
先把上面作圖代碼封裝成函數(shù)
這個函數(shù)只需要一個參數(shù),year(年份)
fig,?ax?=?plt.subplots(figsize=(12,16))
def?drawBarh(year):
????ddata?=?(data[data['年份']==year]
????????.sort_values(by='生產(chǎn)總值(億)',ascending?=?True)
????????.tail(31))
????ax.clear()
????
????ax.barh(ddata['地區(qū)'],?ddata['生產(chǎn)總值(億)'],color?=?[colors[x]?for?x?in?ddata['地區(qū)']])
????for?i,?(num,?pro)?in?enumerate(zip(ddata['生產(chǎn)總值(億)'],?ddata['地區(qū)'])):
????????ax.text(num,i,?pro,?ha?=?'right',fontproperties=my_font)
????????ax.text(num,i,?f'?:{num}億',?ha?=?'left',fontproperties=my_font)
????ax.text(1,?0.4,?year,?transform=?ax.transAxes,fontproperties=my_font,?size=40,?ha='right')
????#?x刻度設(shè)置在頂部
????ax.xaxis.set_ticks_position('top')
????#?x刻度顏色設(shè)置為灰色,大小為12
????ax.tick_params(axis=?'x',colors=?'#777777',labelsize=?12)
????#?去掉y刻度
????ax.set_yticks([])
????#?設(shè)置?xy軸內(nèi)邊距
????ax.margins(0,?0.01)
????#?顯示網(wǎng)格(x軸向虛線)
????ax.grid(which=?'major',?axis=?'x',linestyle=?'--')
????#?網(wǎng)格線至于底部
????ax.set_axisbelow(True)
????#?在左上角顯示?標(biāo)題(不是用的title方法)
????ax.text(0,?1.06,'全國各地區(qū)生產(chǎn)總值(1993-2019)',transform=?ax.transAxes,weight?=600,ha?=?'left',fontproperties=?my_font,size=24)
????#?去掉邊框
????plt.box(False)
#?演示2019年數(shù)據(jù)
drawBarh(2019)

animation動圖制作
再調(diào)用animation.FuncAnimation方法進(jìn)行動圖制作,我們在輸出的頁面可以進(jìn)行動畫演示(快捷、后退、開始、暫停等等)。
fig?,ax?=plt.subplots(figsize?=?(12,16))
#?手繪卡通風(fēng)格
plt.xkcd()
animator?=?animation.FuncAnimation(fig,drawBarh,frames=range(1993,2020),interval=500)
HTML(animator.to_jshtml())

#?通過以下方式可以保存為動圖(保存為視頻方式我們單獨(dú)介紹吧)
animator.save('生產(chǎn)總值動態(tài)圖.gif',bitrate=1800,writer?='pillow')?「交流與思考」:我們在效果動圖中發(fā)現(xiàn)其實(shí)沒那么順滑,這是因?yàn)槲覀兪前凑彰恳荒甑臄?shù)據(jù)繪制一次導(dǎo)致的,那么如何讓效果更加順滑呢?
(一般來說,可以把每年的數(shù)據(jù)分為多份,比如我們認(rèn)為每兩年之間存在N組值,那么就是有N-2個缺失值,通過pandas的缺失值插值處理可以補(bǔ)充一些值作為繪圖的輔助值,從而讓效果更加順滑,那么如何進(jìn)行插值呢?pandas其實(shí)有現(xiàn)成的方式,這里也不展開說明了)
?
-END-

