查看全课大纲(第 46 / 51 节)
- 1.Python 是什么?为什么值得学?
- 2.如何为 Python 编程准备一个“全家桶”环境?
- 3.Python 有哪几种执行方法?
- 4.行与缩进到底有什么用?
- 5.注释有什么用,怎么写?
- 6.变量:如何给数据起个好名字?
- 7.运算符的优先级和常见坑点是什么?
- 8.Python 中,数字和布尔值究竟有哪几种?
- 9.输入函数 `input()` 和文件读取 `open()` 怎么用?
- 10.如何将程序的结果展示给用户或保存下来?
- 11.CSV 文件怎么读怎么写才方便?
- 12.列表是什么,如何创建和访问它?
- 13.列表的修改操作,有哪些方法?
- 14.列表切片为什么能取到部分元素?排序和查找又该怎么用?
- 15.判断语句:如何让程序根据条件做不同的事?
- 16.for 循环如何遍历序列中的元素?
- 17.while 循环什么时候才会停?
- 18.while 循环的结束条件,你写对了吗?
- 19.循环中途想提前结束,怎么办?
- 20.`break` 和 `continue` 到底有什么区别?
- 21.列表推导式如何让代码更简洁高效?
- 22.字符串怎么定义、拼接和操作?
- 23.元组:为什么定义单个元素时要加逗号?
- 24.集合(set)怎么用?如何快速去重和做集合运算?
- 25.字典是什么,为什么比列表更方便?
- 26.字典的增删改查与遍历,如何避免 KeyError?
- 27.函数里的变量,为什么有的能用有的不能用?
- 28.在函数内部修改全局变量,为什么有时不生效?
- 29.函数里能修改全局变量吗?返回值怎么用?
- 30.匿名函数:如何用 lambda 快速定义简单函数?
- 31.面向对象编程是什么,为什么需要它?
- 32.类中的方法和属性到底怎么定义和使用?
- 33.如何控制对对象内部属性和方法的访问?
- 34.异常发生时,程序会怎样继续执行?
- 35.如何组织代码:包和模块
- 36.`if __name__ == '__main__'` 到底有什么用?
- 37.如何用 IDE 的 Debug 功能排查程序错误?
- 38.Matplotlib 绘图的第一步该做什么?
- 39.Figure 是什么,如何设置图形尺寸?
- 40.如何自定义 Matplotlib 的坐标轴?
- 41.如何为图表添加图例,让不同曲线一目了然?
- 42.饼图怎么画才能直观展示数据占比?
- 43.如何用 Matplotlib 绘制散点图?
- 44.柱状图怎么画?
- 45.如何使用 Matplotlib 绘制等高线图?
- 46.如何在 Matplotlib 中绘制子图?
- 47.zip 函数如何把多个列表“打包”在一起?
- 48.如何给可迭代对象加上索引?
- 49.random 模块如何生成随机数?
- 50.math 模块怎么用?
- 51.Counter 如何快速统计元素出现次数?
如何在 Matplotlib 中绘制子图?
约 13 分钟
小象实战讲义 · Python 编程从入门到实践
当我们需要在一张画布上同时展示多个相关联的图表时,就需要用到子图功能。本节是《数据可视化》章节的一部分,在掌握了基本的折线图、柱状图等单一图形绘制方法后,学习如何将它们组合起来,形成更丰富的信息展示。掌握子图后,你将能更灵活地组织你的数据可视化报告。
💡 核心导读
- 子图的作用:在一个
Figure(画布)中绘制多个独立的Axes(坐标轴区域)。 - 三种主要方法:
plt.subplot(简单网格布局)、plt.subplot2grid(复杂网格布局)、fig.add_axes(按画布比例布局)。 - 关键参数理解:理解
subplot的三个整数参数,以及add_axes中[left, bottom, width, height]的含义。 - 布局控制:如何通过调整参数来控制子图的大小、位置和排列方式。
1. 使用 plt.subplot 绘制等分网格子图
plt.subplot 是最简单的子图绘制方法,它将整个画布均匀地划分为若干行和列,然后在指定的位置创建子图。
语法形式:plt.subplot(nrows, ncols, index)
nrows:子图网格的行数。ncols:子图网格的列数。index:当前要创建的子图在网格中的位置索引,从1开始计数,按从左到右、从上到下的顺序。
下面的例子将画布划分为两行两列(共4个位置),并分别在第1、2、3个位置绘制子图。
import matplotlib
matplotlib.use("Agg") # 无图形界面后端
import matplotlib.pyplot as plt
# 准备数据
x = list(range(10))
y1 = [2 * item + 1 for item in x] # y = 2x + 1
y2 = [item ** 2 for item in x] # y = x^2
# 设置画布大小
plt.figure(figsize=(5, 5))
# 第一个子图:位于2行2列网格的第1个位置(左上角)
plt.subplot(2, 2, 1)
plt.plot(x, y1, label='linear line')
plt.title('Subplot 1: y = 2x+1')
# 第二个子图:位于2行2列网格的第2个位置(右上角)
plt.subplot(2, 2, 2)
plt.plot(x, y2, color='red', linestyle='--', label='square line')
plt.title('Subplot 2: y = x^2')
# 第三个子图:位于2行2列网格的第3个位置(左下角)
plt.subplot(2, 2, 3)
plt.plot(x, y2, color='green', linestyle=':', label='square line again')
plt.title('Subplot 3: y = x^2')
plt.close()这段代码创建了一个 5x5 英寸的画布,并划分为 2 行 2 列:左上角绘制蓝色实线 y = 2x + 1,右上角绘制红色虚线抛物线 y = x²,左下角绘制绿色点线抛物线 y = x²;右下角位置 (2,2,4) 未使用,为空白。
调整布局:如果你想将三张图排成一行,只需将网格定义改为一行三列(1, 3),并相应地调整索引。
import matplotlib
matplotlib.use("Agg") # 无图形界面后端
import matplotlib.pyplot as plt
x = list(range(10))
y1 = [2 * item + 1 for item in x]
y2 = [item ** 2 for item in x]
plt.figure(figsize=(10, 3)) # 将画布调整为更宽更扁,以适应一行三列
plt.subplot(1, 3, 1)
plt.plot(x, y1)
plt.title('Plot 1')
plt.subplot(1, 3, 2)
plt.plot(x, y2, color='red', linestyle='--')
plt.title('Plot 2')
plt.subplot(1, 3, 3)
plt.plot(x, y2, color='green', linestyle=':')
plt.title('Plot 3')
plt.close()这段代码创建了一个 10x3 英寸的画布,并划分为 1 行 3 列;三个子图并排显示,分别绘制了线性曲线、红色虚线和绿色点线的抛物线。
2. 使用 plt.subplot2grid 绘制复杂网格子图
plt.subplot2grid 提供了更灵活的子图布局能力。你可以定义一个总的网格形状,然后指定每个子图在这个网格中的起始位置(row, column)以及它需要跨越的行数(rowspan)和列数(colspan)。
语法形式:plt.subplot2grid(shape, loc, rowspan=1, colspan=1)
shape:一个元组(nrows, ncols),定义底层网格的总行数和列数。loc:一个元组(row, col),定义子图起始位置的行和列索引,从0开始。rowspan:子图在垂直方向跨越的行数,默认为1。colspan:子图在水平方向跨越的列数,默认为1。
下面的例子演示了如何创建四个大小和位置各不相同的子图。
import matplotlib
matplotlib.use("Agg") # 无图形界面后端
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
# 定义一个3行3列的底层网格
# 子图1:从(0,0)开始,跨越3列,1行(即占据第一整行)
ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=3, rowspan=1)
ax1.plot([0, 1], [0, 1])
ax1.set_title('Ax1: Full Top Row')
# 子图2:从(1,0)开始,跨越2列,1行
ax2 = plt.subplot2grid((3, 3), (1, 0), colspan=2, rowspan=1)
ax2.plot([0, 1], [0, 1])
ax2.set_title('Ax2: Middle Left')
# 子图3:从(1,2)开始,跨越1列,2行(即占据最右侧一列的下两行)
ax3 = plt.subplot2grid((3, 3), (1, 2), colspan=1, rowspan=2)
ax3.plot([0, 1], [0, 1])
ax3.set_title('Ax3: Right Column Span')
# 子图4:从(2,0)开始,跨越2列,1行
ax4 = plt.subplot2grid((3, 3), (2, 0), colspan=2, rowspan=1)
ax4.plot([0, 1], [0, 1])
ax4.set_title('Ax4: Bottom Row')
plt.close()这段代码创建了一个 8x8 英寸的画布,底层是一个 3x3 的虚拟网格:Ax1 占据第 0 行(顶部整行);Ax2 占据第 1 行的前两列;Ax3 占据第 1 行第 2 列和第 2 行第 2 列(右侧一列的下两行);Ax4 占据第 2 行的前两列。最终形成四个大小和位置都不相同的子图布局。
关键点:plt.subplot2grid 返回的是一个 Axes 对象(如 ax1)。之后所有的绘图操作(如 plot, bar, pie)和属性设置(如 set_title, set_xlabel)都应在这个对象上进行,而不是使用 plt. 开头的全局函数。
3. 使用 fig.add_axes 按画布比例布局
fig.add_axes 提供了最精确的控制方式。它允许你以画布左下角为原点 (0, 0),右上角为 (1, 1),通过指定子图矩形区域的 [左, 下, 宽, 高] 来放置子图。这些参数都是相对于整个画布大小的比例值。
语法形式:fig.add_axes([left, bottom, width, height])
left:子图左侧边缘距离画布左侧的距离比例(0~1)。bottom:子图底部边缘距离画布底部的距离比例(0~1)。width:子图的宽度比例(0~1)。height:子图的高度比例(0~1)。
import matplotlib
matplotlib.use("Agg") # 无图形界面后端
import matplotlib.pyplot as plt
x = list(range(10))
y1 = [2 * item + 1 for item in x]
y2 = [item ** 2 for item in x]
# 新建一个画布对象
fig = plt.figure(figsize=(6, 6))
# 第一个子图:从画布10%的位置开始绘制,宽高占画布的80%
# left=0.1, bottom=0.1, width=0.8, height=0.8
ax1 = fig.add_axes([0.1, 0.1, 0.8, 0.8])
ax1.plot(x, y1, 'r') # 红色曲线
ax1.set_title('Main Area (Area 1)')
# 第二个子图:视觉上落在主图内部
# 参数仍按整张画布的比例给出:从画布(20%, 60%)处开始,宽高各占画布的25%
ax2 = fig.add_axes([0.2, 0.6, 0.25, 0.25])
ax2.plot(x, y2, 'b') # 蓝色曲线
ax2.set_title('Inset Area (Area 2)')
plt.close()这段代码创建了一个 6x6 英寸的画布。主图(Area 1)是一张较大的红色曲线图,左下角位于画布的 (10%, 10%) 处,宽高各占画布的 80%;内嵌图(Area 2)是一张较小的蓝色曲线图,左下角位于画布的 (20%, 60%) 处,宽高各占画布的 25%。由于它的位置和尺寸都落在主图范围内,视觉上看起来像嵌在主图内部。
坐标理解:add_axes 的参数是相对于整个画布的比例。[0, 0, 0.8, 0.8] 表示子图从画布左下角开始,宽度和高度都占画布的80%。你可以通过调整这些比例值,实现子图的任意重叠和嵌套。
📝 动手练一练
请尝试使用 plt.subplot 方法,创建一个 2行1列 的子图布局。在上方的子图中绘制 y = 2x + 1,在下方的子图中绘制 y = x²(x 取 0 到 9 的整数)。为两个子图分别设置标题。
👉 点击查看参考答案
思路:先用列表推导式生成 x、y1、y2,然后用 plt.subplot(2, 1, 1) 和 plt.subplot(2, 1, 2) 分别创建上下两个子图,并在其中绘制对应的曲线。
import matplotlib
matplotlib.use("Agg") # 无图形界面后端
import matplotlib.pyplot as plt
# 生成数据
x = list(range(10))
y1 = [2 * item + 1 for item in x] # y = 2x + 1
y2 = [item ** 2 for item in x] # y = x²
plt.figure(figsize=(8, 6))
# 第一个子图 (2行1列中的第1个)
plt.subplot(2, 1, 1)
plt.plot(x, y1)
plt.title('Linear')
# 第二个子图 (2行1列中的第2个)
plt.subplot(2, 1, 2)
plt.plot(x, y2)
plt.title('Square')
plt.close()这段代码创建了一个 8x6 英寸的画布,划分为 2 行 1 列:上方子图绘制 y = 2x + 1,标题为 “Linear”;下方子图绘制 y = x²,标题为 “Square”。
常见错误
| 错误写法 | 现象或报错 | 原因 | 正确写法 |
|---|---|---|---|
plt.subplot(1, 2, 0) | ValueError: num must be an integer with 1 <= num <= 2, not 0 | subplot 的索引 index 从1开始,不是0。 | plt.subplot(1, 2, 1) |
plt.subplot2grid((2, 2), (2, 0)) | IndexError: GridSpec slice would result in no space allocated for subplot | 起始位置 (2, 0) 的行索引超出 shape=(2, 2) 的有效范围(行索引只能是 0、1)。 | 把起始行改为 (1, 0),或扩大 shape。 |
使用 plt.xlabel() 为特定子图设置标签。 | 标签可能被设置到最后一个活动的子图,而非预期子图。 | plt.xlabel() 是全局函数,作用于“当前活动的”子图。在创建多个子图后,“当前子图”容易混淆。 | 使用 Axes 对象的方法:ax1.set_xlabel('X Label')。 |
fig.add_axes([0.1, 0.1, 0.8]) | TypeError: Bbox.from_bounds() missing 1 required positional argument: 'height' | 参数必须按 [left, bottom, width, height] 给出 4 个值,少一个就会报错。 | 补全 4 个比例值,如 fig.add_axes([0.1, 0.1, 0.8, 0.8])。 |
小结
- 子图核心:在 Matplotlib 中,子图指的是一个
Figure(画布)中的多个Axes(坐标轴区域)。 - 三种方法:
plt.subplot(nrows, ncols, index):用于创建均匀网格布局的子图,简单直接。plt.subplot2grid(shape, loc, rowspan, colspan):用于创建大小不一的复杂网格布局,灵活性高。fig.add_axes([left, bottom, width, height]):用于按画布比例精确布局,可以精确控制子图的位置和大小,甚至实现嵌套。
- 索引起点:
subplot的索引从 1 开始;subplot2grid的位置索引从 0 开始。 - 操作对象:使用
subplot2grid和add_axes时,后续的绘图和设置应在其返回的Axes对象(如ax1)上进行,而不是使用plt.全局函数。 - 比例坐标:
add_axes的参数是相对于整个画布大小的比例,取值范围在 0 到 1 之间。
📋 行动清单
- 将
plt.subplot的示例代码中的网格从(2,2)改为(3,2),并尝试绘制满6个子图。 - 修改
plt.subplot2grid的示例,尝试将ax3的起始位置改为(0, 2),并观察布局变化。 - 在
fig.add_axes的示例中,尝试调整内嵌图ax2的[left, bottom, width, height]参数,使其位于主图的右下角。
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。
- 下载第5章代码包:子图(含1 个 Notebook)ZIP · 1KB