查看全课大纲(第 46 / 51 节)
  1. 1.Python 是什么?为什么值得学?
  2. 2.如何为 Python 编程准备一个“全家桶”环境?
  3. 3.Python 有哪几种执行方法?
  4. 4.行与缩进到底有什么用?
  5. 5.注释有什么用,怎么写?
  6. 6.变量:如何给数据起个好名字?
  7. 7.运算符的优先级和常见坑点是什么?
  8. 8.Python 中,数字和布尔值究竟有哪几种?
  9. 9.输入函数 `input()` 和文件读取 `open()` 怎么用?
  10. 10.如何将程序的结果展示给用户或保存下来?
  11. 11.CSV 文件怎么读怎么写才方便?
  12. 12.列表是什么,如何创建和访问它?
  13. 13.列表的修改操作,有哪些方法?
  14. 14.列表切片为什么能取到部分元素?排序和查找又该怎么用?
  15. 15.判断语句:如何让程序根据条件做不同的事?
  16. 16.for 循环如何遍历序列中的元素?
  17. 17.while 循环什么时候才会停?
  18. 18.while 循环的结束条件,你写对了吗?
  19. 19.循环中途想提前结束,怎么办?
  20. 20.`break` 和 `continue` 到底有什么区别?
  21. 21.列表推导式如何让代码更简洁高效?
  22. 22.字符串怎么定义、拼接和操作?
  23. 23.元组:为什么定义单个元素时要加逗号?
  24. 24.集合(set)怎么用?如何快速去重和做集合运算?
  25. 25.字典是什么,为什么比列表更方便?
  26. 26.字典的增删改查与遍历,如何避免 KeyError?
  27. 27.函数里的变量,为什么有的能用有的不能用?
  28. 28.在函数内部修改全局变量,为什么有时不生效?
  29. 29.函数里能修改全局变量吗?返回值怎么用?
  30. 30.匿名函数:如何用 lambda 快速定义简单函数?
  31. 31.面向对象编程是什么,为什么需要它?
  32. 32.类中的方法和属性到底怎么定义和使用?
  33. 33.如何控制对对象内部属性和方法的访问?
  34. 34.异常发生时,程序会怎样继续执行?
  35. 35.如何组织代码:包和模块
  36. 36.`if __name__ == '__main__'` 到底有什么用?
  37. 37.如何用 IDE 的 Debug 功能排查程序错误?
  38. 38.Matplotlib 绘图的第一步该做什么?
  39. 39.Figure 是什么,如何设置图形尺寸?
  40. 40.如何自定义 Matplotlib 的坐标轴?
  41. 41.如何为图表添加图例,让不同曲线一目了然?
  42. 42.饼图怎么画才能直观展示数据占比?
  43. 43.如何用 Matplotlib 绘制散点图?
  44. 44.柱状图怎么画?
  45. 45.如何使用 Matplotlib 绘制等高线图?
  46. 46.如何在 Matplotlib 中绘制子图?
  47. 47.zip 函数如何把多个列表“打包”在一起?
  48. 48.如何给可迭代对象加上索引?
  49. 49.random 模块如何生成随机数?
  50. 50.math 模块怎么用?
  51. 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 0subplot 的索引 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])。

小结

  1. 子图核心:在 Matplotlib 中,子图指的是一个 Figure(画布)中的多个 Axes(坐标轴区域)。
  2. 三种方法:
    • plt.subplot(nrows, ncols, index):用于创建均匀网格布局的子图,简单直接。
    • plt.subplot2grid(shape, loc, rowspan, colspan):用于创建大小不一的复杂网格布局,灵活性高。
    • fig.add_axes([left, bottom, width, height]):用于按画布比例精确布局,可以精确控制子图的位置和大小,甚至实现嵌套。
  3. 索引起点:subplot 的索引从 1 开始;subplot2grid 的位置索引从 0 开始。
  4. 操作对象:使用 subplot2grid 和 add_axes 时,后续的绘图和设置应在其返回的 Axes 对象(如 ax1)上进行,而不是使用 plt. 全局函数。
  5. 比例坐标: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
    下载