案例:繪制Matplotlib動(dòng)態(tài)圖
學(xué)習(xí) zhenguo 老師的 Python 課已經(jīng)一個(gè)星期了,自己感覺(jué)已經(jīng)學(xué)有小成,剛好昨天老師在接單群里發(fā)了一個(gè) 100元的單子,我毫不猶豫的接了,不僅可以檢驗(yàn)自己能否學(xué)以致用,還能賺頓小龍蝦的錢(50元~)。
開(kāi)發(fā)需求
這個(gè)單子的要求,是使用 Python 中的 matplotlib 庫(kù)繪制動(dòng)態(tài)的折線圖,需求描述雖然很簡(jiǎn)單易懂,但是也要好好分析一下。
Matplotlib庫(kù)
這個(gè)庫(kù)也算是 Python 數(shù)據(jù)開(kāi)發(fā)必學(xué)的庫(kù)之一了,它主要的功能就是繪制圖表,而且實(shí)現(xiàn)也非常簡(jiǎn)單,幾行代碼就可以繪制出直方圖、折線圖、散點(diǎn)圖、餅圖等等常用的圖表,一些復(fù)雜的數(shù)據(jù)分析圖表它也可以勝任。
這里分享一個(gè)網(wǎng)址: www.matplotlib.org.cn/gallery/#lines-bars-and-markers,大家可以看看 Matplotlib 繪圖的一些案例,作為自己開(kāi)發(fā)的參考。
核心問(wèn)題
Matplotlib 庫(kù)繪制一張靜態(tài)的折線圖比較簡(jiǎn)單,給定X軸和Y軸的數(shù)據(jù)集就行,但是想要繪制動(dòng)態(tài)的折線圖,就要想辦法讓繪制出來(lái)的圖片動(dòng)起來(lái)。
其實(shí)這個(gè)問(wèn)題理解起來(lái)也不難,Matplotlib 繪圖是生成一張圖片,讓它動(dòng)起來(lái)的原理就像是動(dòng)畫(huà)片,不斷生成新的圖片,讓它們前后連接,逐幀播放就行了。
開(kāi)發(fā)過(guò)程
理解了核心問(wèn)題,就可以開(kāi)始動(dòng)手來(lái)解決問(wèn)題了。
加載數(shù)據(jù)
在繪圖之前,先要把數(shù)據(jù)集合弄到,需求方給了一張 excel 表格,需要從表格中提取需要的數(shù)據(jù)集。
這個(gè)過(guò)程也是比較簡(jiǎn)單的,需要用到 Python 數(shù)據(jù)分析必學(xué)的另一個(gè)庫(kù) Pandas 。
只需要一行代碼 pandas.read_excel('data.xls'),就可以把 excel 文件加載到內(nèi)存。
然后可以像操作 dict 一樣獲取每一列的數(shù)據(jù)集合,如:cls = pandas.read_excel('data.xls')['列頭名稱'],會(huì)得到一個(gè) <class 'pandas.core.series.Series'> 對(duì)象,可以直接遍歷每行的數(shù)據(jù),也可以格式轉(zhuǎn)化成列表類型。
繪制一條折線
有了數(shù)據(jù)之后,就可以畫(huà)圖了,先畫(huà)一條折線,把現(xiàn)有的數(shù)據(jù)展示出來(lái),看看效果。
import matplotlib.pyplot as plt
import data
fig, ax = plt.subplots(figsize=(16, 9), dpi=80)
ax.set_ylim(0, 35)
ax.set_xticks(range(0, len(xdata), 10))
xdata = data.times
ydata = data.dealnums
ax.plot(xdata, ydata)
fig.autofmt_xdate()
plt.show()
代碼比較簡(jiǎn)單,下面是繪制出來(lái)的折線圖:
讓折線動(dòng)起來(lái)
接下來(lái)要做的,就是要讓折線圖動(dòng)起來(lái),不斷顯示新的數(shù)據(jù)。
要實(shí)現(xiàn)這個(gè)效果,需要做兩個(gè)操作,一是讓數(shù)據(jù)動(dòng)起來(lái),在數(shù)據(jù)集中不斷增加新數(shù)據(jù),二是讓繪制的圖形按指定時(shí)間間隔動(dòng)起來(lái)。
第一個(gè)操作,我是這樣做的,直接上代碼:
for x, y in zip(xdata_set, ydata_set):
xdata.append(x)
ydata.append(y)
也就是將原始數(shù)據(jù)集拆成單個(gè)數(shù)據(jù),逐個(gè)加載到X軸和Y軸的數(shù)據(jù)集中,實(shí)現(xiàn)數(shù)據(jù)動(dòng)態(tài)增加的效果。
第二個(gè)操作,我首先想到的辦法,是每次數(shù)據(jù)更新的同時(shí),將整個(gè)畫(huà)布清空,重新畫(huà)出最新的圖表。
for x, y in zip(xdata_set, ydata_set):
xdata.append(x)
ydata.append(y)
plt.clf() # 清空整個(gè)figure
# 重新建立坐標(biāo)軸并畫(huà)出折線圖
ax = fig.add_subplot(1, 1, 1)
ax.plot(xdata, ydata)
plt.pause(1) # 休眠1秒后繪制新圖
這個(gè)思路還有一點(diǎn)需要注意,就是要在開(kāi)始調(diào)用 plt.ion() 方法,啟動(dòng)互動(dòng)模式。
這個(gè)方式不能直接保存圖像為動(dòng)圖,所以沒(méi)法展示了,效果跟后面的動(dòng)圖一樣。
動(dòng)圖新思路
按照上面的思路完成各個(gè)需求細(xì)節(jié)之后,我就把代碼提交給了 zhenguo 老師,老師不僅給予了肯定和鼓勵(lì),還提供了一個(gè)新的思路。
也就是使用 Matplotlib 中的動(dòng)畫(huà)模塊來(lái)畫(huà)動(dòng)態(tài)圖。
趕緊找到相關(guān)模塊和方法的文檔學(xué)習(xí)了起來(lái),最后發(fā)現(xiàn),只需要使用一個(gè) animation.FuncAnimation 類就可以滿足這個(gè)單子的需求。
而且這個(gè)類用起來(lái)很方便,在構(gòu)建函數(shù)中傳入 figure 對(duì)象、更新圖表的函數(shù)、初始化函數(shù)和間隔參數(shù)就行了。
import matplotlib.animation
import matplotlib.pyplot as plt
import data
fig = plt.figure(figsize=(16, 9), dpi=70)
def draw_line(fig, x_data, y_data):
ax = fig.add_subplot(1, 1, 1)
ax.plot(xdata, ydata)
return ax
def init_figure():
# 繪制初始的圖表
xdata = data.times
ydata = data.dealnums
return draw_line(fig, xdata, ydata)
def update(n):
# 更新數(shù)據(jù)集 xdata 和 ydata
xdata = data.new_times
ydata = data.new_dealnums
# 清除之前的坐標(biāo)系
plt.clf()
# 繪制最新的折線圖
return draw_line(fit, xdata, ydata)
ani = animation.FuncAnimation(fig, update, init_func=init_figure, interval=1000)
# 生成 gif 動(dòng)圖并保存
ani.save('test.gif', writer='pillow')
plt.show()
這樣改造之后的代碼也是非常簡(jiǎn)單清晰,而且還能保存生成的動(dòng)圖。
最后完善一些需求和代碼上的細(xì)節(jié)問(wèn)題,最終的效果是這樣的:
是不是很酷!
學(xué)習(xí)了 Python 之后,發(fā)現(xiàn)了很多有意思的編程方向,繪圖開(kāi)發(fā)只是宏觀藍(lán)圖的一小部分。
通過(guò)這次的單子,我完成了從眼會(huì)到手會(huì)的突破,不僅對(duì) Matplotlib 庫(kù)有了更深的理解,更重要的是!
今晚的小龍蝦有著落了!


