孔乙己:你可知subplot有几种写法?

2021-01-04 14:48:25 浏览数 (1)

话说,这篇文章我本来想起个名字叫《如何用Python画子图》,然后我看了一眼公众号后台:

太单调了!太贫瘠了!太boring了!你的想象力呢?你的创造力呢?你的。。脑子呢?你就会写个《如何……》吗?讲真,这么多如何,看的我都不认识如何了。 所以如何是谁?如萍 何书桓吗?

然后不知怎的我就想到了《孔乙己》,大约是因为播放《情深深雨濛濛》的那段时间,我正在学习这篇课文罢。

言归正传。我们初学Python的时候,画张图出来还是不难的,导入matplotlib之后,直接选择相应的绘图函数作图就可以了。网上也有很多现成的绘图示例,可以学习借鉴(抄抄代码)。 但是,我们的甲方or领导or老师or审稿人or Others,最喜欢把很多张子图放到一张大图里对比着看了呢!所有没有办法,我们也得掌握绘制子图的技能。这样的绘图过程,在Python上叫作subplot,在NCL上叫作panel。

NCL中进行panel的方法是设置plot的数量,然后针对每张图片进行属性的设置,最终通过gsn_panel函数把它们排布在一起。

我们要用Python中matplotlib绘图库实现panel的话,首先要厘清几个概念。比如plt、subplot、fig、ax等都跟绘图有关,却又很抽象,让人不是很好理解。我一开始经常不知道在设置每张子图的坐标系,或者是添加legend、title的时候,到底应该用谁来点。是plt.xticks(),还是fig.xticks(),还是ax.xticks()呢?

这个时候,就要祭出传说中最厉害的穷举法了!一个个试,试到不报错为止

这里先给出一段最简单的绘制折线的代码:

代码语言:javascript复制
import matplotlib.pyplot as plt
x = [1,2,3,4]
y = [1,0,2,4]
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
plt.plot(x, y)
plt.savefig('example.png')

出图效果如下:

从这张图片能看出来,图片的四周存在着较大的空白,对于出图效果的美观程度存在一定的影响。NCL中直接绘制png图片时也会出现类似的问题,解决方案为先用NCL绘制eps矢量图,然后再convert成png图片,就能去除图片中的白边了。详见下文:

如何去除NCL生成的png图片中的白边?

而Python就更直接啦,只要在plt.savefig()中设置bbox_inches='tight'就可以去除白边了。此外,还可以通过dpi的设置来调整图片的大小。

代码语言:javascript复制
plt.savefig('example.png',dpi=300,bbox_inches='tight')

完美~

突然发现1024画出来是个√,真是个好数字呢

回过头来看代码。其实,最开始的这句import已经告诉了我们plt是啥:

代码语言:javascript复制
import matplotlib.pyplot as plt

它是我们将matplotlib.pyplot导入之后,给它起的简称,毕竟matplotlib.pyplot这个名字也太长了。。它是matplotlib下的绘图函数集合,并且出图风格和MATLAB比较相似。也就是说,plt是一个绘图包。

而下面代码中的fig则是利用plt绘图包里的figure函数创建的一个空白画布,ax则是这张画布中的一个子图。只不过fig.add_subplot(1,1,1)表示的是这个名为fig的空白画布中,总共有1行,1列,ax为其第1个子图。

代码语言:javascript复制
fig = plt.figure()
ax = fig.add_subplot(1,1,1)

搞清楚这些概念,我们就可以愉快地开始panel了!假设我们要画4张均匀分布于画布中的子图:

代码语言:javascript复制
import matplotlib.pyplot as plt
plt.rcParams['font.size'] = 12    #设置默认字号
x = [1,2,3,4]
y = [1,0,2,4]
fig = plt.figure()
ax1 = fig.add_subplot(2,2,1) #或:plt.subplot(2,2,1)
plt.plot(x, y)
plt.title('a) ')  
ax2 = fig.add_subplot(2,2,2) # 或:plt.subplot(2,2,2)
plt.plot(x, y)
plt.title('b) ',fontsize=14,color='blue')
ax3 = fig.add_subplot(2,2,3) # 或:plt.subplot(2,2,3)
plt.plot(x, y)
plt.title('c) ',fontsize=16,color='red',loc='left',bbox=dict(facecolor='y', edgecolor='blue', alpha=0.65 ))
ax4 = fig.add_subplot(2,2,4) # 或:plt.subplot(2,2,4)
plt.plot(x, y)
plt.title('d) ',fontsize=18,color='green',rotation=45)
plt.savefig('example.png',dpi=300,bbox_inches='tight')

这里不管是写成ax1=fig.add_subplot(2,2,1),还是直接写为plt.subplot(2,2,1)的形式,都表示接下来使用的plt绘图包中的函数,都是针对子图subplot(2,2,1)进行的设置,而并非整个画布fig。比如我们上面对各个子图的标题都进行了不同的设置,也都分别作用到了子图,而不是所有图片上。这里出图效果如下:

除了以上这种方法,我们也可以通过对子图的轴进行操作的方法来实现上面这张图同样的效果:

代码语言:javascript复制
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
plt.rcParams['font.size'] = 12    # 设置默认字号
x = [1,2,3,4]
y = [1,0,2,4]
data = pd.Series(np.array(y),index=x)
fig,axes=plt.subplots(2,2) 
data.plot(ax=axes[0,0])
axes[0,0].set_title('a) ')
data.plot(ax=axes[0,1])
axes[0,1].set_title('b) ',fontsize=14,color='blue')
data.plot(ax=axes[1,0])
axes[1,0].set_title('c) ',fontsize=16,color='red',loc='left',bbox=dict(facecolor='y', edgecolor='blue', alpha=0.65 ))
data.plot(ax=axes[1,1])
axes[1,1].set_title('d) ',fontsize=18,color='green',rotation=45)
plt.savefig('example.png',dpi=300,bbox_inches='tight')

注意嗷!!这里的用法是axes[m,n].set_title, 以及set_xticks, set_yticks这种set开头的都是和axes[m,n]配套的,plt.的是titile,xticks,yticks这种没有set开头的。混用是会报错的哦

有些时候,我们也会遇到不规则划分的情况,可以通过指定画布的具体位置来添加子图,比如我在这篇文章(如何用Python画站点雪花图?)中定位子图的方法:

代码语言:javascript复制
fig=plt.figure(figsize=[5.5,5],dpi=300)
plt.subplots_adjust(top=0.95, bottom=0.05, right=0.85, left=0.05, hspace=0, wspace=0)

这个方法有个肉眼可见的缺点:位置实在是太难定了,需要试无数次,眼睛都快调瞎了。。而且讲真,你自己摆的还不一定比默认的好看。所以,如果我们有3张图,甲方非要你以上面两张,下面一张的形式来放。那么subplot()应该怎么设置呢?

一直以来,很多人(当然包括我寄几,我向来都是推己及人的

)都有个误解,以为subplot只能处理1×n,n×n,n×1这几种情况,硬要画3张图,就会出现第4张子图空白,而不是第3张子图占满第3、4张子图所在位置的情况。就像这样:

这是我们长久以来受NCL中gsn_panel的思维定势的影响,以为对于画布的划分是一成不变的,而在subplot()函数中,它是可以随用随划分的啊!

subplot(numRows,numCols,plotNum):表示将画布按照numRows行、numCols列划分后的第plotNum张图片。所以,如果我们想让上图中的图c)占满下面一整行的图片,只要将图c)的subplot设置为(2,1,2)即可,也就是图c)是将画布fig进行2行,1列的划分之后的第2张图片。整个代码可以这样写:

代码语言:javascript复制
import matplotlib.pyplot as plt
plt.rcParams['font.size'] = 12    #设置默认字号
x = [1,2,3,4]
y = [1,0,2,4]
fig = plt.figure()
plt.subplot(2,2,1)
plt.plot(x, y)
plt.title('a) ')
plt.subplot(2,2,2)
plt.plot(x, y)
plt.title('b) ',fontsize=14,color='blue')
plt.subplot(2,1,2)
plt.plot(x, y)
plt.title('c) ',fontsize=16,color='red',loc='left',bbox=dict(facecolor='y', edgecolor='blue', alpha=0.65 ))
plt.savefig('example.png',dpi=300,bbox_inches='tight')

出图效果鹅妹子~~嘤!!!

0 人点赞