在本系列的上篇文章裏,咱們從Matplotlib的基礎可視化框架開始,逐步畫出折線圖、柱狀圖等基礎圖表,經過對座標軸標籤、標題文本等的精細調節畫出信息更明確豐富的可視圖,也實踐了雙軸圖及子圖,最後看了下極座標系下繪圖的效果。本篇繼續探索Matplotlib的強悍可視化能力。html
Matplotlib動態可視化
計算機及通訊技術的發展極大豐富了多媒體內容的發展,文不如圖、圖不如動圖;BI近些年也逐漸發展,人們已不知足於看靜態的圖表。短視頻的火熱也給了動態圖更多的發展空間。動態圖和交互圖表能更生動地表現數據變化、展示數據關聯,傳達更多的信息。html5
插入排序的動態展示
生動的動畫有助於咱們理解算法。經過Matplotlib其實咱們也能夠繪製動態的算法關鍵過程,下面拿插入排序做爲例子看Matplotlib如何繪製動態圖。python
玩撲克時的抓牌環節很契合插入排序的執行過程。其思路是:保持手中的已有的牌始終有序,當抓到一張新牌時,按照牌面的點數,將其插入合適的位置[1]。怎麼去判斷該插入的位置呢?咱們一般的作法就是從左到右或從右到左掃描以找到當前牌的位置,初始化時咱們能夠新建一個數組做爲始終有序的結果集,也能夠直接用原來的數組空間進行交換操做,總體時間複雜度是O(n^2)。將這一過程翻譯爲Python代碼以下:算法
def isort(lst): n=len(lst) #直接用原數組進行排序 for i in range(1,n): x=lst[i] #當前值 j=i-1 while j>=0 and x<lst[j]: #從右往左找插入的位置 lst[j+1]=lst[j] #將比x大的牌日後移一位 j-=1 lst[j+1]=x #換牌 return lst
每次抓牌時判斷新牌的合適位置canvas
爲了直觀展現插入排序的關鍵步驟,咱們將每作一次插入的結果保存下來而後用Matplotlib畫成一系列柱狀圖。經過matplotlib.animation
繪製成動態圖。api
首先改一下排序函數,增長一個變量保存每次到插入步驟時的數組,由於不是遞歸的排序代碼,在for循環前用一個變量w保存關鍵結果,基於這些中間結果畫一系列的圖,再連成動態GIF圖,代碼以下,關鍵步驟都有註釋。數組
def isort(lst): #插入排序代碼 n=len(lst) w={0:{'v':lst.copy(),'j':-1}} #保存j以肯定到哪裏插入 for i in range(1,n): x=lst[i] #當前值 j=i-1 while j>=0 and x<lst[j]: #x比j處值小時,繼續向左 lst[j+1]=lst[j] #將比x大的牌日後移一位 j-=1 lst[j+1]=x #換牌 w[i]={'v':lst.copy(),'j':j} #print(xs,i,j,x) return w #不須要lst
import matplotlib.animation as anm #引入接口fig,ax=plt.subplots()def draw_bar(i): #每傳入一個i畫一個柱狀圖 w=wk[i] nw=len(w['v']) ax.clear() #清空以前畫的元素 c1=[] for j in range(nw): #調節柱的顏色 if j<i: c1.append('#1EAFAE') elif i==nw-1: c1.append('#1EAFAE') elif j==i: c1.append('#BA5C25') else: c1.append('#69FFFF') if i!=nw-1 and w['j']+1!=i: #所交換的位置 c1[w['j']+1]='#FFA069' rs=ax.bar(range(nw),w['v'],color=c1) ax.set_ylim(0,8) ax.set_title('Insert Sorted Animation') c=0 for r in rs: #給每一個柱加文本標籤 ax.annotate('{0}'.format(w['v'][c]), xy=(r.get_x()+r.get_width()/2,r.get_height()+0.1)) c+=1 #繪製動圖amt=anm.FuncAnimation(fig,draw_bar,frames=range(6),interval=600)amt.save('insert-sorted-animation-1.gif') #保存動圖到本地文件
拿一個未排序數組進行測試,效果以下:微信
Matplotlib繪製動態圖表的思路是將一系列圖按必定時間間隔順序播放,利用眼睛的視覺暫留造成動態感,每張靜態圖就是一幀。上面的代碼看上去有些長,但大部分語句用來調顏色和標籤文本,畫圖部分仍然是熟悉的fig,ax=plt.subplots()
建畫布、ax.bar()
畫柱狀圖、ax.set_title()
設置標題、ax.annotate()
在每一個柱的合適位置加文本標籤。Matplotlib將動圖相關的接口封裝在matplotlib.animation
裏,FuncAnimation(fig,func,frames)
經過重複調用func裏的畫圖函數在fig上造成動圖。FuncAnimation的參數以下:app
•fig
:用來生成動畫的畫布;•func
:經過調用matplotlib繪圖方法來出圖做爲動圖的每一幀;•frames
:一個迭代對象,會將其中每個元素做爲繪製一幀的參數傳入func函數;•interval
:每一幀的展現時間,默認200,單位是毫秒,也就是200毫秒跳到下一張圖;框架
要將動圖保存到文件經過.save(fname)
實現,另外也能夠用.to_html5_video()
把動畫轉爲HTML5下video標籤支持的數據或用.to_jshtml
生成HTML表示的動畫數據,例如在jupyter notebook環境中,能夠用如下語句直接渲染出帶播放控制檯的動圖。
動態排序圖實踐
學動態圖繪製不該該錯過一直挺熱門的動態排序圖(Bar Chart Race)。經過一系列的條形圖營造出你追我趕的熱鬧場面,看盡事件的變遷。特別適合的應用場景是各類排名的變化,如城市排名變化、某些主題搜索指數變化、××沉浮史等。把這類圖拆解一下看到的是一系列條形圖和條柱之間的交換動態效果。有了上面的插入排序作熱身,一樣能夠經過繪製一系列條形圖再調用FuncAnimation(fig,func,frames)
獲得動態排序圖。網上能夠找到各類年度季度排名的公開數據集,一些講動態排序圖的教程也給出了數據集。爲了再下降數據獲取門檻,咱們直接隨機生成簡單的排名數據。
假設咱們有以下的數據表df,表示7位用戶A~G各自在3月到12月的消費金額。如今要畫出從3月到12月用戶消費金額的排名變化。color列用來給各自標識顏色,畫條形圖和畫製做動圖所用接口和參數前面都講過(包括上篇文章),直接用ax.barh(y,width,color)
和FuncAnimation(fig,func,frames)
來繪製,條形圖是從下往上畫的,所以正序排序後正好是最高的柱在最上面,不須要額外調轉,具體代碼以下。
fig,ax=plt.subplots()def race_bar(i): idx=str(i) wdf=df.sort_values(by=idx) width=list(wdf[idx]) yw=list(wdf['tag']) ax.clear() rs=ax.barh(yw,width,color=wdf['color']) ax.set_xlim(0,wdf[idx].max()+200) ax.set_title('A to G Animation') ax.text(wdf[idx].max()+20,1,'{0}'.format(i),fontsize=30) #繪製當前條形圖對應時間週期 ax.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False) c=0 for r in rs: ax.annotate('{0}'.format(yw[c]),xy=(r.get_width()-40,r.get_y()+r.get_height()/2-0.16)) ax.annotate('{0}'.format(width[c]),xy=(r.get_width()+5,r.get_y()+r.get_height()/2-0.16)) c+=1amt=anm.FuncAnimation(fig,race_bar,frames=range(3,13),interval=600)#總體結構和插入排序一脈相承
繪製效果如圖:
注:爲了更好地得到具備你追我趕、一同向前的效果,且防止數據變化太過跳脫,防止出現前一秒仍是第1、忽然掉到最後一名的劇烈變更狀況,生成df時,沒有所有使用隨機函數生成隨機數,此處使用的方法是第一次隨機生成數據,下一幀的數據在當前數據基礎上加[-50,100]的值,本處設定是當前數x[i]是偶數時,x[i+1]=x[i]+randint(20,200),奇數時x[i+1]=x+randint(-30,100)。生成數據集的代碼以下:
df=pd.DataFrame({'tag':list('ABCDEFG'),'color':['#1EAFAE', '#A3FFFF', '#69FFFF', '#BA5C25', '#FFA069', '#9E5B3A', '#D7CE88']})df['3']=df['tag'].apply(lambda x:random.randint(50,600)) #初始列
for i in range(4,13): idx=str(i-1) #偶數增幅,奇數在原來基礎上[-30,50+5*i]變更 df['{0}'.format(i)]=df[idx].apply(lambda x:x+random.randint(20,100+i*6) if x%2==0 else x+random.randint(-30,50+i*5))
動態折線圖
換一種圖表類型也不難。最近動態折線圖也很火。由於df也具備時間屬性,此次只用A、B、C三行的數據繪製動態折線圖,改一下數據處理並將ax.bar()
換成ax.plot
,成果如圖。
繪製代碼以下:
fig,ax=plt.subplots()def race_line(i): k=['A','B','C'] #只取3我的的數據 x=range(3,i+1) ax.clear() for ki in k: wdf=df.loc[df['tag']==ki] #篩選出畫折線的x,y y=[list(wdf.loc[wdf['tag']==ki][str(j)])[0] for j in x] ax.plot(x,y,color=list(wdf['color'])[0]) ax.text(i+0.2,y[-1]+2,'{0}:{1}'.format(ki,y[-1])) ax.set_xlim(3,i+3) ax.set_title('ABC Lines Animation')
ax.tick_params(top=True, bottom=False,left=False,right=True, labelright=True,labelleft=False, labeltop=True, labelbottom=False)
amt=anm.FuncAnimation(fig,race_line,frames=range(6,13),interval=500)amt.save('lines-animation-1.gif') #把動圖保存爲gif文件
繪製三維動態圖也是一樣的套路,建畫布時加上projection="3d"
參數,繪圖時參數從[x,y]變成[x,y,z],其餘按框架來作。
形狀繪製深刻
在上篇的圖表元素調校部分簡單提到了在畫布上加橢圓、矩形的代碼,這裏再細化一下Matplotlib能夠繪製的形狀。整理以下:
#繪製基本形狀的框架,以圓形爲例import matplotlib.patches as mpatchesfrom matplotlib.collections import PatchCollectionfig, ax = plt.subplots(subplot_kw=dict(aspect="equal")) #設置橫縱座標單位長度一致,也可寫 plt.axis('equal')
patches = [] #需渲染的形狀集合circle = mpatches.Circle((50,50),20) #初始化一個圓心在(50,50),半徑爲20的圓形patches.append(circle)
pcc= PatchCollection(patches,color="#69FFFF") #因patches整合到了PatchCollection對象裏,在Circle裏寫顏色參數好像沒用,須要把color傳到這裏ax.add_collection(pcc)ax.set_ylim(0,100) #設置x,y的展現範圍ax.set_xlim(0,100)
•.Circle(xy,radius,**kwargs)
: 繪製一個圓形,第一個參數是圓心座標,能夠傳數組或元組,x、y不是單獨傳的;radius是圓的半徑;後續的參數有圖形標籤(label)、線風格(linestyle)、圓邊框寬度(linewidth)、圖層順序(zorder)等;•.Ellipse(xy,width,height,angle,**kwargs)
: 以xy爲圓心繪製一個橢圓。Circle()的第二個參數是半徑,橢圓須要長軸長度和短軸長度,也就是width和height,angle控制旋轉角度,逆時針,按度計算,例如angle=90時,原來一個扁的橢圓就變成了長的橢圓,轉了90度;其餘參數和Circle()基本一致,下面也再也不重複。•.Wedge(center, r, theta1, theta2, width, **kwargs)
: 楔形,像劈掉一部分的圓,是餅圖的那一塊塊餅,能夠猜想用pie()
繪製餅圖時調用了Wedge;center對應圓的xy,即圓心座標;r是半徑,只繪製從theta1到theta2之間的圓形,交換t1和t2能夠獲得餅的另外一個部分,width默認是None,當設置了width會從r-width的部分開始畫,獲得環狀圖;•.Rectangle(xy,width,height,angle,**kwargs)
: 和橢圓的參數寫法驚人一致,不一樣之處在於矩形的xy是左下角座標而不是中心的座標;•.RegularPolygon(xy,numVertices,radius,orientation,**kwargs)
: 繪製正多邊形xy是圖形的中心點,numVertices是頂點個數,如numVertices=5是正五邊形;radius:從圖形中心xy到頂點的距離;orientation:旋轉的度數,是弧度制;•.Arrow(x,y,dx,dy, width, **kwargs)
: 繪製一個箭頭,x:箭頭尾部的x座標,y:箭頭尾部的y座標;dx:箭頭指向位置距離x的長度,dy同理,width是箭頭的寬度,默認值是1,當形狀用通常設置得大一些。另外還有hatch參數能夠設置箭頭的底紋效果;•.PathPatch(path, **kwargs)
: 繪製一系列座標構成的路徑,是很是強大的接口,繪製各類不規則的形狀、圖標、貝塞爾曲線等通常都直接用Path的接口,和Canvas自己path對象的規則基本一致,東西比較多,很差展開;•.FancyBboxPatch(xy,width,height,boxstyle='round',**kwargs)
: 邊框效果更個性化的圖形,前面3個參數就是矩形的參數,boxstyle控制繪製各類效果,boxstyle支持的有circle(圓邊)、round(邊緣鈍化的矩形)、square(方邊)、sawtooth(鋸齒邊)等。下面的整理更形象。
基於上面的形狀,這裏復現一下繪製經典的數據科學維恩圖。畫維恩圖只須要Circle(xy,r)
就夠了,因patches整合到了PatchCollection對象裏,在Circle裏寫顏色參數彷佛沒用,就把color從PatchCollection傳入。
import matplotlib.patches as mpatchesfrom matplotlib.collections import PatchCollectionfig, ax = plt.subplots(subplot_kw=dict(aspect="equal"))
p1 = mpatches.Circle((15, 85), 78,fc='#1EAFAE',alpha=0.62) #(x,y),r,fcolor,alphap2 = mpatches.Circle((50, 10), 78, fc="#69FFFF",alpha=0.62)p3 = mpatches.Circle((85, 85), 78, fc="#BA5C25",alpha=0.62)patches = [p1,p2,p3]
collection = PatchCollection(patches,color=['#1EAFAE',"#69FFFF","#BA5C25"],alpha=0.62)#collection.set_color(['#1EAFAE',"#69FFFF","#BA5C25"])ax.add_collection(collection)ax.text(50,50,'Data\nScience',ha='center',fontsize=11)ax.text(125,100,'Math',ha='center')ax.text(50,-33,'Domains\nKnowledge',ha='center')ax.text(-30,100,'CS',ha='center')ax.text(0,20,'Danger\nZone',ha='center',fontsize=9)ax.text(100,20,'Traditional\nResearch',ha='center',fontsize=8)ax.text(50,107,'Machine\nLearning',ha='center',fontsize=9)ax.set_ylim(-100,200)ax.set_xlim(-100,200)plt.axis('off') #不顯示座標軸
數據科學經典維恩圖
另一種畫多個圓的方法是用ax.add_artist(ada)
,示例代碼以下:
from mpl_toolkits.axes_grid1.anchored_artists import AnchoredDrawingAreap1 = mpatches.Circle((15, 85), 78,fc='#1EAFAE',alpha=0.62) #(x,y),r,fcolor,alphaada = AnchoredDrawingArea(100,100,0, 0,loc=10, pad=0., frameon=False) #width( in pixels), height, xdescent, ydescent, loc, pad=0.4,ada.drawing_area.add_artist(p1)ax.add_artist(ada)
在matplotlib.image
的接口中有圖像的讀取接口,ax.imshow(mpimg.imread('imagename.png'))
能夠讀取圖片並顯示,所以Matplotlib即能畫餅柱折點等圖形,也能畫更底層的線段、楔形、多邊形,還能讀取圖片進行處理。經常使用需求有給圖片加文本水印、給圖形加圖片(如畫各國動態排序柱圖時給對應柱畫上國旗)、用形狀裁剪圖片等;
極座標
plt.subplot()
其中有一個參數是projection,表示所使用的座標系統,以前畫三維圖的時候用到projection='3d'
,畫心臟線函數的時候用到了projection='polar'
,再來細化一下極座標下的繪圖。
#極座標系下的可視化和直角座標沒多少改變ax=plt.subplot(111,projection='polar')x=[5,4,3,2,1]ax.plot(x)
pyplot.subplot
支持的座標系統有'rectilinear'、'polar'、'lambert'、'hammer'、 'mollweide'、'aitoff'等看有哪些座標系統[2],主要在3d繪圖、極座標繪圖、地圖投影等場景下使用。正如rectilinear直角座標系下肯定一個位置用[x,y],在極座標系下定位一個位置經過[theta,r],theta表示正方向旋轉的弧度,r表示距離原點的直線距離(也稱r軸爲極徑)。
直角座標與極座標對比
極座標系可視化有一些基本的屬性能夠設置。
•ax.set_theta_direction(-1)
: 設置極座標角度的正方向,默認值是1,表示逆時針方向,設置爲-1時是順時針方向;•ax.set_theta_zero_location(loc,offset=0)
: 設置極座標洗0°的位置,默認是loc是'E',表示正東方向,loc有八種選擇:("N", "NW", "W", "SW", "S", "SE", "E", "NE"),offset表示在loc的基礎上按照正方形偏移多少度數;•ax.set_thetagrids(angles,labels,fmt)
:設置極座標角度網格線上標籤的顯示,labels是要顯示的標籤,angles是標籤所在對應的角度(注意不是弧度),angles值的範圍應該取[0,360], 默認顯示0°、45°、90°、135°、180°、225°、270°、315°的網格線。相似於直角座標系下的ax.set_xticklabels(df['x'])
;•ax.set_rgrids(radii,labels)
: 設置極徑網格線和標籤顯示,和上面ax.set_thetagrids
效果對應;•ax.set_rlabel_position(value)
: 設置極徑標籤顯示位置,value爲標籤所要顯示在的角度;•ax.set_rlim(0,30)
: 設置極徑顯示範圍,對應直角座標下的set_ylim(0,30)
;•ax.set_rscale()
: 設置極徑方向所用的比例尺,默認是'linear'表示是線性變化,能夠設置爲'log'獲得對數比例尺;
不少咱們常見的圖將其轉到極座標系下會有驚豔的效果,例如餅圖能夠認爲是極座標系下的柱狀圖,將柱的高度映射爲楔形的弧度;玫瑰圖能夠是極座標系下的堆積柱狀圖,柱的高度映射爲r及弧度theta的佔比;雷達圖能夠是極座標系下的折線圖。
咱們用極座標繪製南丁格爾玫瑰圖的時候,能夠再次複習柱狀圖bar的參數,代碼以下。
y=[42, 142, 61, 119, 68]z=[77, 46, 65, 81, 50]ax=plt.subplot(111, projection='polar')#對y和z進行一些運算以適應弧度制yw=[i*2*3.1416/sum(y) for i in y]xw=[sum(y[0:i])*2*3.1416/sum(y) for i in range(len(y))]yzw=[i*2*3.1416/sum(z) for i in z]zw=[sum(z[0:i])*2*3.1416/sum(z) for i in range(len(z))]ax.bar(xw,y,width=yw,align='edge',linewidth=1,edgecolor='k') #設置x對應柱的邊緣開始畫而不是中心了ax.bar(xw,y,width=yw,bottom=y,align='edge',linewidth=1,edgecolor='k') #設置柱的邊緣顏色以區分各個餅
轉換的過程須要對數據進行換算,這算一個Matplotlib不夠智能的設置,不能直接經過換座標系統的語句實現數據的一個換算,例如將原先的x軸自動換算到[0,2pi]繪製美觀的圖表,針對這種換座標系實現堆積的方法,基於屬性映射的可視化語法,會將換算細節封裝好,能直接使用出圖。
Matplotlib簡單交互
Matplotlib畫靜態圖很是專業,同時它也能經過事件監聽實現基礎的交互功能。Matplotlib經過plt.connect(s, func)
實現對鼠標和鍵盤等事件的監聽,s表示plt會關聯的事件,如s='button_press_event'表示按下鼠標時會出發func函數,在func裏寫入觸發事件後的處理邏輯,相應事件[3]還有:{'key_press_event':'按下按鍵','key_release_event':'鬆開按鍵','resize_event':'改變窗體大小','close_event':'關閉窗體'}等。官網給了兩個例子分別表示按下按鈕時print相應的座標以及按鍵時觸發保存圖片等交互。基於Matplotlib的接口要實現流暢複雜的交互代碼會很複雜。
本身簡單實現了一下當鼠標點擊到柱狀圖的柱子上時會高亮當前柱並顯示當前柱對應的值。效果以下:
具體代碼以下,jupyter notebook環境彷佛不支持Matplotlib的交互操做,需依賴GUI環境,所以運行結果是經過腳本運行獲得的。
from matplotlib.backend_bases import MouseButtonimport matplotlib.pyplot as plt
xw=list('ABCDE')yw=[42, 142, 61, 119, 68]fig, ax = plt.subplots()
rs=ax.bar(xw,yw,color='#1EAFAE') #繪製初始的條形圖ax.set_title('plt.connect demo')
def on_click_bar(event): if event.button is MouseButton.LEFT: x, y = event.xdata, event.ydata print('w',x,y) for r in range(len(rs)): if x>rs[r].get_x() and x<rs[r].get_x()+rs[r].get_width() and y<rs[r].get_height(): print('q',x,y) ax.clear() c=['#BA5C25' if i==r else '#1EAFAE' for i in range(len(rs))] ax.bar(xw,yw,color=c) ax.text(x,y,'{0}'.format(rs[r].get_height())) ax.set_title('plt.connect demo') fig.canvas.draw_idle() #刷新當前畫布
plt.connect('button_press_event', on_click_bar) #監聽# 另外一種寫法是 fig.canvas.mpl_connect('button_press_event', onclick)plt.show()
關於Matplotlib庫,還能夠深刻的有:
•圖形的布爾運算、Path的具體規則等;•漸變的顏色調節;•地圖投影及basemap的使用;•根據三維數據繪製等高線ax.contour(X, Y, Z,levels)
;•等等。
Matplotlib的各模塊內容細化拆解會有很是多的內容,市面上有挺多專門講mat可視化的厚書,若是隻考慮快速使用和了解幾大模塊的話,Matplotlib的精要內容是能夠15分鐘學會的,我的認爲在知道了基本可視化框架後,瞭解折線圖、柱狀圖、餅圖、直方圖等的繪製方法和基本參數,再學會添加文本、調節座標軸,會經過雙座標軸和子圖畫多張圖,最後瞭解下動態圖和事件監聽作基礎交互。其餘內容和細節經過需求驅動深刻學。
相關閱讀
可視化技能之Matplotlib(上)
用可視化地圖講照片的故事
酒缸分酒問題的解決
點擊 閱讀原文 可直達文中繪圖代碼的jupyter notebook文檔。有任何建議歡迎留言交流。
References
[1]
劉新宇.算法新解[M].人民郵電出版社,2017:19.[2]
Matplotlib支持的座標系統:
https://matplotlib.org/api/_as_gen/matplotlib.pyplot.subplot.html#matplotlib.pyplot.subplot
[3]
connect所監聽的事件:
https://matplotlib.org/users/event_handling.html
本文分享自微信公衆號 - 蟄蟲始航(lyns_sailing)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。