python多子图绘制

一张图往往不能容纳所有,多子图绘制是数据展示中非常重要的方法,网上资料零零散散,查阅起来不方便也不过瘾,现将所学整理成文档,力求简单易懂易上手。

Matplotlib 汲取了 MATLAB 和 R 语言中的绘图特点和方法,在多子图绘制方面有强大的功能,本文使用该库作多子图绘制方面的介绍。

绘图思想

首先需要对 matplotlib 中的绘图思想作着重了解,否则会在绘图的过程中逐渐忘了自己在干什么。

我们经常会见到例如 fig, ax = plt.subplots() 的语句,不知道 fig、ax到底是什么,嫌麻烦直接 plt.plot(data) 了事,但是当想要调整坐标轴、调整数据、添加子图时才会发现,这种方法过于草率,学习一下游戏规则还是很有必要。

基本观点

从英文来看:

  • FigureCanvas 画布,Figure 图

  • Axes 坐标轴系?

按照官方的介绍,大概分别代表了图中的下面区域:

fig_map

因此可以理解为:

  • 画图,需要一张画布,这个画布就是 fig
  • 在画布上任你画什么,都在 Axes 下,这个单词究竟该怎么翻译,体现了不同人的观点,有两种观点:
    • 从模糊的角度说,不管 Axes 是什么意思,这里就是画图的地方(可能是名字起得不好)
    • 另一种角度认为,画图时,实际上是有多个维度、多个坐标系的,最后呈现在一张图中,需要一个统一的维度,也就是 Axes 将多维度、多坐标系统一在了一张图中(听起来很有道理)

核心结构

个人觉得 fig、ax 的结构才是考验功底的地方。

目前看到比较浅显易懂的是:matplotlib:先搞明白plt. /ax./ fig再画

文中表明:

  1. 造成困惑的最大问题是,pyplot 可以使用多种方式实现同一种绘图,对小白来说很不友好
  2. plt 画图简单但是草率,ax 画图才是进阶与核心
  3. figure、axes、axis 的三级框架是 pyplot 画图必须掌握的结构,在 axes 层级绘图是比较专业的做法,很多操作,plt 和 ax 各自有实现的方法,优先采用 axes 的方法,主要都是在 plt 方法前加上了 set_ 的前缀

ax 几乎可以填改各种图像中的内容,比如 ax.legend() 可以添加子图的图例等等。

子图绘制方法

有了上面的思想作为铺垫,接触下面的子图绘制方法将不再困难和枯燥。

总体来说就是:

  • 建立一张画布,顺便建立一个或者多个 axes 轴系
  • 每个 axes 轴系上就是一个子图了,随便折腾

细究起来,Matplotlib 提供了 subplot、subplots、add_subplot、add_axes 等五花八门的子图绘制方法,它们是有区别的,尤其是需要面向对象画图的时候,区别很大:直观来说,如果前缀是 plt. 那么就是普通的方法,如果前缀是 fig. 或者 ax. ,那么这个方法是面向对象的方法。

subplot(x, y, i)

定位:基本作图,不用面向对象。

作用:把一个绘图区域(可以认为是画布)分成多个小区域,用来绘制多个子图。

多子图的产生方法:

subplot(222) - 表示把画布分成(2*2=4)个小区域,并将本图绘制在第2个子区域(也就是右上角位置),可以加逗号:plt.subplot(2,2,2)

  • 第一个参数是==行数==
  • 第二个参数是==列数==
  • 第三个参数是当前图片的==位置==,是==先行后列==的!而且这里的索引是从 1 开始的!

比如1,3,那么最终会得到3个图横向排列,如果是3,1,那么将会是3个图纵向排列

fig.add_subplot()

定位:面向对象的方法!前缀是 fig

作用:先向 Figure 实例中添加 Axes实例,然后在Axes实例中绘制图片

其实 plt.subplot()add_subplot() 参数和含义都相同。

plt.subplots()

定位:普通方法,可以不面向对象,也可以面向对象,例如 fig.subplots() 调用

作用:产生子图,会返回 fig 和 ax,如果是多子图场景,那么返回值 ax 将会是一个 list,包含所有轴系!

个人觉得这一套多子图绘制思路科学合理,很好用,很适合用于多子图的构建。

常用流程如下:

1
2
3
fig, ax = plt.subplots(2, 1)
ax[0].plot()
ax[1].plot()

其中, ax 是包含多个子图轴系的列表,使用索引调取子图,直接绘制,非常好用。

值得注意的索引问题

(1)由于现在是多行了,所以索引本来也是要以 list 形式的,例如 [0, 0] 就表示第一行,第一列的位置

(这里的索引从0开始!)。

为了更加方便索引,也可以先展平,然后再绘图,比如:

1
2
3
fig, ax = plt.subplots(2, 5, figsize=(25,10))
ax = ax.flatten()
ax[i].plot(xxx)

此时索引就不需要再以 list 形式了,就可以直接用从0开始的数字索引了。

(2)另外,还要注意,索引代表的图像应该是先行后列的

axes() 与 .add_axes()

定位:前者是普通方法,后者是面向对象的方法

作用:用于手绘子图

举例:

plt.axes([0.65, 0.65, 0.2, 0.2])

其中:

  • 参数1:子图的位置 x(均为百分数,0-1之间)
  • 参数2:子图的位置 y
  • 参数3:子图的宽度 w
  • 参数4:子图的高度 h

以上参数全部使用百分数,0-1之间,一般来说,取0.1-0.9之间比较好

就像下图所示:

img

个人理解,在plt中画图时,应该是按照==左下角为坐标原点==来进行绘制的!

面向对象时,可以如下使用:

1
2
fig = plt.figure()
ax1 = fig.add_axes([0.65, 0.65, 0.2, 0.2])

子图间隔调整

tight_layout()

根据默认的子图排列,可能会导致子图出现标题重叠的现象,比如:

img

为了避免这种情况,可以使用 plt.tight_layout() 自动调整子图,使之填充图像区域,避免重叠:

img

该函数有一个参数有意思,就是 pad,但是目前来看效果不明显,这个pad 似乎是根据字的大小来设置的一个相对数字。

参考:

  1. plt.tight_layout()
  2. 官方:matplotlib.pyplot.tight_layout
  3. 此外,还有多种方法可以实现比较紧密的布局,参考: Matplotlib 中文用户指南 3.4 密致布局指南

subplots_adjust()

还可以使用 plt.subplots_adjust() 可用于调整这些图之间的间隔

1
2
fig = plt.figure()
fig.subplots_adjust(hspace=0.4, wspace=0.4)

要注意的是:hspace 和 wspace 表示的是占子图高度和宽度的百分比!所以介于0和1之间。

GridSpec 弹性绘图

plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)

在该方法下,可以使用 切片的方法指定子图的位置和范围(仍然是先行,后列,左包,右不包)

1
2
3
4
plt.subplot(grid[0, 0])
plt.subplot(grid[0, 1:])
plt.subplot(grid[1, :2])
plt.subplot(grid[1, 2]);

效果如下:

img

参考:Matplotlib 多子图绘制

多子图标题

多子图情况下,如果需要添加总图标题,可以使用:

plt.suptitle("title") 的方式添加总图的标题

还可以调整 suptitle 的位置:

plt.suptitle("title", y=0.98)

参考:How to position suptitle?

其他

有一些本身就高定制化的图片,例如 seaborn 中的 jointplot 等图,就不太容易放置在子图中,如有需要,可以参考 How to plot multiple Seaborn Jointplot in Subplot 进行绘制,不过似乎是需要添加一些自定义的文件。

常用函数

  1. plt.subplot(x,x,x) 表示当前对该子图绘画
  2. plt.plot() 绘制曲线,也可以绘制直线
  3. plt.title("") 起标题
  4. plt.xlabel("") 横轴意义
  5. plt.ylabel("") 纵轴意义
  6. plt.xlim([start, end]) 横轴范围
  7. plt.imshow(img) 将图片放入子图(主要加载图片应该在其他画线操作都结束之后,否则图片加载不出来)
  8. plt.show() 最后把整个图进行展示
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2022 Sun Yue

请我喝杯咖啡吧~

支付宝
微信