本节提要:图例 Legend与colorbar 一、图例Legend命令常用参数
作为成熟的科研图表,图例的重要性是不言而喻的。所谓一图敌千言,在气象科研领域,图表是进行数据可视化的利器,而图例是帮助阅读者理解图表信息的关键。绘图库matplotlib中专门辟出一个命令——Legend进行设置。下面首先介绍其常用关键字参数。
loc | 设置图例位置,一般在图表内部 |
---|---|
fontsize | 字体大小 |
markerscale | 图例标记相对于原始标记的相对大小 |
markerfirst | 图例在标签左侧,bool值控制 |
numpoints | 图例的标记数目 |
frameon | 图例边框,bool值控制 |
fancybox | 边框是否圆边 |
shadow | 边框阴影 |
framealpha | 边框透明度 |
edgecolor | 边框边缘颜色 |
facecolor | 边框内部填色 |
---|---|
ncol | 图例列数,int值 |
borderpad | 边框内边距 |
labelspacing | 图例之间的垂直间距 |
handlelength | 图例的句柄长度 |
handleheight | 图例的句柄高度 |
handletextpad | 图例与句柄之间间距 |
columnspacing | 列间距 |
title | 图例标题 |
bbox_to_anchor | 指定图例在轴的位置 |
在之前,我们制作了一个墒情图,本次即以此图展示legend命令。
两图相比较,修改了ncol为一列,修改了edgecolor为红色,修改了facecolor为绿色,修改了framealpha为1,修改了fancybox为圆角。
其他参数命令,读者可以自行实验,在jupyter notebook中实验是非常方便的。
二、Legend的位置调节命令——loc与bbox_to_anchor
Legend有两个可以调节位置的命令,使用方式各不相同。
loc是最常用的位置命令,两种使用方式,一是使用0~10数字,二是使用字符命令如'best','right',center','upper right'等,这种图例位置是在子图内部的,可能会出现遮挡图形的情况,所以就有bbox_to_anchor命令,这个命令类似于前面推荐的add_axes命令,指定的是图例的绝对位置,不妨通过上一个图片展示:
ax2.legend((bar1,bar2,line1,line2),('降水','蒸发','墒情','气温'),frameon=True,framealpha=1,ncol=1,shadow=True,bbox_to_anchor=(0,0))
可以看出,将绝对位置定为bbox_to_anchor=(0,0)后,图例可以被放置在子图外了。同样的,可以放置到我们常见的图例位置:
ax2.legend((bar1,bar2,line1,line2),('降水','蒸发','墒情','气温'),frameon=True,framealpha=1,ncol=1,shadow=True,bbox_to_anchor=(1.1,0.9))
注意,两个命令并不是冲突的,可以放在同一句中调节,不会报错。
还可以进行如下操作,bbox_to_anchor=(x1,y1,x2,y2),给予图例框的起始绝对位置和结束绝对位置:
ax2.legend((bar1,bar2,line1,line2),('降水','蒸发','墒情','气温'),bbox_to_anchor=(0,-0.1,1.,-0.10),frameon=True,framealpha=1,ncol=2,shadow=True,mode="expand", borderaxespad=0.)
在mode=‘expand’命令下,指定了起始和结束位置后,图例框将被拉伸到最大,我目前没有用到,可能有读者需要。
三、图例的分类操作等
在前面,我们将每个图例分别注释了标签,在需要的时候,还可以进行分类操作。
大多数时候,我们通过最简便的方法建立一个实验图(直接在绘制时设置label=,legend会自动生成图例):
line1=plt.plot(x,y1,lw=2,ls="-",color='cyan',label='line1')
line2=plt.plot(x,y2,lw=2,ls='--',color='k',label='line2')
line3=plt.plot(x,y3,lw=2,ls=':',color='lightgreen',label='line3')
scatter1=plt.scatter(x2,y4,c='orange',s=15,marker='*',label='scatter1')
scatter2=plt.scatter(x2,y5,c='darkgray',s=19,marker='1',label='scatter2')
scatter3=plt.scatter(x2,y6,c='darkturquoise',s=19,marker='h',label='scatter3')
plt.title('这是总标题')
plt.legend(title='这是图例标题',bbox_to_anchor=(1,0.9),frameon=False,framealpha=0.75)
这种建立图例的方法不能进行分类操作,所以通过在plt.legend(list1,list2)的方式建立图例,一般来说list1代表绘制命令,list2装载字符串作为名称:
plt.legend([line1,line2,line3,scatter1,scatter2,scatter3],['line1','line2','line3','scatter1','scatter2','scatter3'])
然后通过括号分类:
plt.legend([(line1,line2,line3),scatter1,scatter2,scatter3],['这是合并的线','scatter1','scatter2','scatter3']
可以发现,虽然合并了,但是合并的图例里只有一根绿线了,这时需要引进新的模块:
from matplotlib.legend_handler import HandlerLine2D, HandlerTuple
plt.legend([(line1,line2,line3),scatter1,scatter2,scatter3],['这是合并的线','scatter1','scatter2','scatter3'],
handler_map={tuple: HandlerTuple(ndivide=None)},numpoints=1,
title='这是图例标题',bbox_to_anchor=(1,0.9),frameon=False,framealpha=0.75)
这之后,合并的图例能正常显示了。当然散点图也能进行分类处理:
其他绘图样式也都可以在图例中进行分组:
四、如何绘制多个图例
在matplotlib中,由于legend命令的特性,无论plt.legend还是ax.legend,都只能在图表中添加一个图例,一般来说以最后一个legend命令绘制,前面都会被覆盖,比如:
ax.legend(xxx)
ax.legend(ooo)
ax.legend(***)
三个命令,最终只能出现(***)这个图例。但是科研图表存在需要多个图例的情况,如果确实需要绘制时,可以通过ax.add_artist()命令添加。仍然以上一小节的图为例。首先,我们将所有line通过ax.legend()命令绘制出来:
ax.legend([line1,line2,line3],['直线1','直线2','直线3'],numpoints=1,title='图例一',bbox_to_anchor=(1,0.9),frameon=False,framealpha=0.75)
然后,from matplotlib.legend import Legend模块导入,将其他散点和直方在Legend命令下添加,Legend()内部关键字参数与ax.legend()的关键字参数一致,最后,以ax.add_artist()添加到子图上:
from matplotlib.legend import Legend
legend2=Legend(ax,[scatter1,scatter2,scatter3,bar1],['散点1','散点2','散点3','直方1'],title='图例二',frameon=False,bbox_to_anchor=(1,0.3))
ax.add_artist(legend2)
这样就能添加一个图例了:
当然,你还可以增添三个乃至更多的图例,读者可自行推用。
五、散点图多变量下图例的添加
在前面的推送中,介绍到散点图的两种使用方法:一种为以s为变量,固定颜色,通过散点直径大小展示数据;一种是以颜色映射为变量,固定s,通过填色变化来展示数据。这两种是最简单的使用方式,进一步的,散点图还可以将两个都设置为变量,都展示数据的变化。
这一节是源于一个小伙伴在群里问的问题。
首先读入数据:
filename=r'C:\Users\lenovo\Desktop\累年降水数据.xlsx'
df=pd.read_excel(filename)
lon=df['lon']#读入站点经度
lat=df['lat']#读入站点纬度
rain_days=df['rain_days']#读入各站点某年累计降水日数
rains=df['precipitation']#读入各站点某年累计降水量
rain_size=(rain_days-10)**2#对累计降水日数进行处理,使散点大小均匀
然后绘制散点图(添加地图和站点名通过def create_map():绘制,这里略去):
sca=ax.scatter(lon,lat,s=rain_size,c=rains,cmap='GnBu',alpha=0.75,edgecolor='k',label='none')
将处理过的累计降水日数传入s,将累计降水量传给color,设定cmap为'GnBu'。注意,最好能改变alpha小于1,因为散点存在互相重叠情况,不使散点透明,小散点可能被大散点完全覆盖。edgecolor设为黑色在视觉上是最好的。
当然,目前缺乏重要的辅助图例,除了制图员,没人知道这幅图表达了什么,所以接下来,介绍两种添加辅助阅读工具手段。
A、通过添加图例表示圆圈大小含义、通过添加colorbar表示填色的含义
首先将sca传入colorbar命令,生成色条:
position=fig.add_axes([0.1,0.2,0.8,0.01])
b=plt.colorbar(ax=sca,cax=position,extend='both',orientation='horizontal',shrink=0.3,label='累计降水量 $mm$',pad=0.1)
这样就能展示填色的意义了,阅读者能明白填色代表累计降水量,然后,通过如下命令生成图例:
marker1=ax.scatter([],[],s=rain_size.min(),c='k',alpha=0.3)
marker2=ax.scatter([],[],s=rain_size.max()/2,c='k',alpha=0.3)
marker3=ax.scatter([],[],s=rain_size.max(),c='k',alpha=0.3)
legend_markers=[marker1,marker2,marker3]
labels=['30日','60日','90日']
ax.legend(title='累计降水日',handles=legend_markers,
labels=labels,scatterpoints=1,frameon=False,
labelspacing=0.39,handletextpad=3,
bbox_to_anchor=(0.3,0.93))
前面三句就是生成三个圆圈的命令,因为通过两个空列表生成的,所以不会显示在主图上,只能通过句柄命令(handles)传入ax.legend(),这时你可以理解为等同于(为了方便理解如此解释):
ax.legend([line1,line2,line3],['直线1','直线2','直线3'])前一部分为图例,后一部分为标签。
B、通过两个图例分别展示散点直径和散点颜色
前面的程序与A中完全相同,在第四节中已经讲了如何建立多个子图,这里马上就上手使用了,这次不使用colorbar展示颜色变化,而使用带颜色的散点:
from matplotlib.lines import Line2D
cmap=plt.get_cmap('GnBu')
levels=[40,60,80,100,120,140,160,180]
ls = [Line2D(range(1), range(1), linewidth=0, color=cmap(v), marker='o', ms=(10)) for v in levels]
ax.legend(ls,levels,frameon=False,bbox_to_anchor=(1.02,0.5),title='累计降水量($mm$)')
第一步获取cmap,这里和主图的cmap一致。第二步划分降水level。第三步生成我们的彩色圆点,这些彩色圆点实际上是带着marker的plot线条,但是我们将其linewidth设为了0,线条被截去了。第四步传入legend命令。
接着,使用前面提到的添加第二个图例的方法,添加散点直径图例:
marker1=ax.scatter([],[],s=rain_size.min(),c='k',alpha=0.3)
marker2=ax.scatter([],[],s=rain_size.max()/2,c='k',alpha=0.3)
marker3=ax.scatter([],[],s=rain_size.max(),c='k',alpha=0.3)
legend_markers=[marker1,marker2,marker3]
labels=['30日','60日','90日']
legend2=Legend(ax,title='累计降水日',handles=legend_markers,
labels=labels,scatterpoints=1,frameon=False,
labelspacing=0.39,handletextpad=3,
bbox_to_anchor=(1.1,0.98))
ax.add_artist(legend2)
前面部分与方法A中的一致,但是我们已经在前面用ax.legend()命令绘制了一个图例了,这时就只能用ax.add_artist(legend2)方法添加新的图例。
通过这幅图能看出什么呢?可以看出恩施州降水日数和降水量高值区都集中在利川市,而鹤峰的日数和降水量都偏少。再看宣恩县和恩施市,宣恩的降水日更少,但是降水量比恩施市多。