可视化技能之Matplotlib(下)|可视化系列02

在本系列的上篇文章里,咱们从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源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索