一、子图概述
在数据可视化中,当需要同时展示多组数据或从不同维度呈现同一数据时,子图(Subplot)是一种高效的解决方案。通过将多个图表合理排布在同一画布上,既能保证数据间的关联性,又能实现对比分析,提升信息传递的效率。
1. 子图介绍
子图是指在同一个画布(Figure)中创建的多个独立图表,每个子图拥有自己的坐标轴(Axes)、标题、标签等元素,可独立设置样式和数据,共享画布的整体布局空间。其核心作用包括:
- 多数据对比:同时展示不同数据系列的趋势(如同一时间段内不同产品的销量对比);
- 多维度分析:从不同角度呈现同一数据(如原始数据、累计数据、增长率数据的联动展示);
- 空间高效利用:在有限画布内整合多个相关图表,避免多次绘图的繁琐。
在 Matplotlib 中,子图的创建和管理是通过 Figure
和 Axes
对象实现的:Figure
代表整个画布,Axes
则代表每个子图的坐标轴区域,一个 Figure
可以包含多个 Axes
(子图)。
2. 子图布局
子图的布局决定了多个子图在画布中的排列方式,Matplotlib 支持两种主要布局模式:网格布局(规则排列)和自由布局(灵活排布),分别适用于不同的展示需求。
2.1 网格布局
网格布局是将子图按照规则的行列网格进行排列(如 2 行 3 列、1 行 2 列等),每个子图占据网格中的一个单元格,适合展示结构规整、数量固定的子图。这种布局的优势是整齐有序,便于批量创建和管理,尤其适合对比性质的多组数据展示。
常用实现方法及特点:
实现方法 | 语法/示例 | 核心功能 | 适用场景 | 优势特点 |
---|---|---|---|---|
plt.subplots() |
fig, axes = plt.subplots(nrows=2, ncols=2) |
一次性创建指定行列数的网格子图,返回画布(fig )和子图数组(axes ) |
行列数固定、需批量创建子图的场景 | 操作简洁,支持通过数组索引快速访问子图;可统一设置 sharex /sharey 共享坐标轴 |
plt.subplot() |
ax1 = plt.subplot(2, 2, 1) (2行2列网格中的第1个子图,index从1开始) |
逐个创建子图,通过 rows, cols, index 指定子图在网格中的位置 |
按需创建单个子图,或动态生成网格布局 | 灵活性高,可根据需求分步创建子图;无需提前确定所有子图数量 |
GridSpec |
python<br>gs = plt.GridSpec(2, 2)<br>ax1 = plt.subplot(gs[0, :]) # 第1行占满2列<br> |
自定义网格行列跨度,支持子图合并单元格(如跨多行/多列) | 复杂网格布局(如部分子图需占据更大空间) | 支持非均匀布局,可精确控制子图尺寸和位置;适合突出重要子图的场景 |
网格布局整体特点 |
---|
1. 子图位置由行数(nrows )和列数(ncols )严格定义,排列整齐有序;2. 支持通过 sharex (共享x轴)、sharey (共享y轴)减少重复坐标轴标签,提升可读性;3. 适合子图数量固定、需要统一风格的场景(如论文中的多组实验结果对比、同一指标的多维度分析)。 |
2.2 自由布局
自由布局允许子图在画布中按照自定义的位置和大小进行排布,不受网格行列的限制,适合展示结构不规则或需要突出某个子图的场景。这种布局的优势是灵活性高,可根据子图的重要性调整大小和位置,突出核心信息。
常用实现方法及特点:
实现方法 | 核心功能 | 语法/示例 | 参数说明([left, bottom, width, height] ) |
适用场景 |
---|---|---|---|---|
plt.axes() |
通过相对坐标和尺寸创建子图,直接关联当前画布 | ax1 = plt.axes([0.1, 0.1, 0.4, 0.4]) |
- left :子图左边缘距画布左边界的相对距离(0-1)- bottom :子图下边缘距画布下边界的相对距离(0-1)- width :子图宽度占画布的比例(0-1)- height :子图高度占画布的比例(0-1) |
快速创建单个自由布局子图,脚本式编程场景 |
Figure.add_axes() |
通过画布对象添加子图,需先创建画布,适合面向对象编程 | python<br>fig = plt.figure()<br>ax2 = fig.add_axes([0.6, 0.1, 0.3, 0.7])<br> |
与 plt.axes() 参数完全一致,参数含义同上 |
多画布管理,需明确关联子图所属画布的场景 |
自由布局的整体特点 |
---|
1. 子图位置和大小完全自定义,支持重叠、嵌套、非对称排布等灵活效果; 2. 无需遵循网格规则,可根据子图重要性调整尺寸(如核心图表放大,辅助图表缩小); 3. 需手动优化参数避免子图重叠或间距失衡,对布局设计经验要求较高。 |
二、绘制等分区域子图
等分区域子图是指将画布按照固定行列数均匀分割,每个子图占据相同大小的网格空间,适合需要平等展示多组相关数据的场景。以下分别介绍两种常用的绘制方法:plt.subplot()
和 plt.subplots()
。
1. 使用 plt.subplot() 绘制子图
plt.subplot()
是 Matplotlib 中创建网格子图的基础方法,通过指定网格的行数、列数和子图索引,逐个创建子图。该方法灵活度高,可按需创建单个或多个子图,适合动态生成子图的场景。
plt.subplot()
方法参数说明:
参数名 | 作用描述 | 取值示例 | 补充说明 |
---|---|---|---|
nrows |
网格的行数 | 2 、3 |
决定子图布局的行数,如 nrows=2 表示将画布分为 2 行 |
ncols |
网格的列数 | 2 、4 |
决定子图布局的列数,如 ncols=3 表示将画布分为 3 列 |
index |
子图在网格中的索引位置 | 1 、5 |
索引从 1 开始,按“行优先”排序(如 2x2 网格的索引顺序为 1→2→3→4) |
projection |
子图的投影类型 | 'polar' |
可选参数,用于创建特殊投影子图(如极坐标图),默认为笛卡尔坐标系 |
polar |
是否使用极坐标系(简化的 projection ) |
True 、False |
等价于 projection='polar' ,优先级低于 projection |
sharex |
与其他子图共享 x 轴 | ax 对象 |
如 sharex=ax1 表示与 ax1 子图共享 x 轴,减少重复标签 |
sharey |
与其他子图共享 y 轴 | ax 对象 |
如 sharey=ax2 表示与 ax2 子图共享 y 轴 |
示例:绘制多个子图
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 numpy 模块,使用其数值计算功能
import numpy as np
# 使用 numpy 的 linspace 函数生成一个从 0 到 10 的等差数列,共 100 个点
x = np.linspace(0, 10, 100)
# 创建一个大小为 (宽度10英寸, 高度8英寸) 的画布(Figure)
plt.figure(figsize=(10, 8))
# 在画布上创建第一个子图(2行2列中的第1个位置)
# subplot(2, 2, 1) 表示将画布分为2行2列共4个子图,当前操作的是第1个子图
plt.subplot(2, 2, 1)
# 在该子图中绘制 sin(x) 曲线,颜色设置为蓝色
plt.plot(x, np.sin(x), color='blue')
# 设置该子图的标题为 'sin(x)'
plt.title('sin(x)')
# 在画布上创建第二个子图(2行2列中的第2个位置)
plt.subplot(2, 2, 2)
# 在该子图中绘制 cos(x) 曲线,颜色设置为红色
plt.plot(x, np.cos(x), color='red')
# 设置该子图的标题为 'cos(x)'
plt.title('cos(x)')
# 在画布上创建第三个子图(2行2列中的第3个位置)
plt.subplot(2, 2, 3)
# 在该子图中绘制 tan(x) 曲线,颜色设置为绿色
plt.plot(x, np.tan(x), color='green')
# 设置该子图的标题为 'tan(x)'
plt.title('tan(x)')
# 在画布上创建第四个子图(2行2列中的第4个位置)
plt.subplot(2, 2, 4)
# 在该子图中绘制 exp(x/10) 曲线,颜色设置为紫色
plt.plot(x, np.exp(x/10), color='purple')
# 设置该子图的标题为 'exp(x/10)'
plt.title('exp(x/10)')
# 调用 tight_layout 函数自动调整子图之间的间距,避免重叠或过于紧凑
plt.tight_layout()
# 显示整个画布上的所有子图
plt.show()
绘制的图形如下图所示:
示例:工业月度同比情况
数据如下图所示,需要根据这个数据绘制多个子图。
实现代码如下所示:
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 pandas 模块,用于读取和处理 CSV 数据
import pandas as pd
# 设置中文字体为 SimHei(黑体),确保图表中的中文标签(如标题、坐标轴)能正常显示
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号 '-' 显示为方块的问题(默认字体可能不支持负号)
# 设置为 False 表示允许正常显示负号
plt.rcParams['axes.unicode_minus'] = False
# 定义 CSV 文件路径,请根据实际情况替换为你的文件路径
csv_file_path = './data/工业月度同比情况.csv'
# 使用 pandas 读取指定路径的 CSV 文件,返回一个 DataFrame 对象
df = pd.read_csv(csv_file_path)
# 从 DataFrame 中提取“月份”列,并转换为 Python 列表,用于后续绘图的 x 轴数据
month = df['月份'].tolist()
# 提取“规模以上工业同比增速(%)”列数据,转换为列表
industry_speed = df['规模以上工业同比增速(%)'].tolist()
# 提取“钢材同比增速(%)”列数据,转换为列表
steel_speed = df['钢材同比增速(%)'].tolist()
# 提取“十种有色金属同比增速(%)”列数据,转换为列表
metals_speed = df['十种有色金属同比增速(%)'].tolist()
# 创建第一个子图:位于 3 行 1 列布局中的第 1 个位置
plt.subplot(3, 1, 1)
# 绘制折线图:x 轴为 month,y 轴为 industry_speed
# 颜色为红色,数据点用圆形标记(marker='o'),标记大小为 3
plt.plot(month, industry_speed, color='red', marker='o', markersize=3)
# 设置该子图的标题
plt.title('规模以上工业同比增速')
# 设置 y 轴标签
plt.ylabel('增长速度(%)')
# 设置 y 轴的数值范围为 0 到 10,便于统一尺度观察趋势
plt.ylim(0, 10)
# 在每个数据点上方添加文本标签,显示具体数值
for i in range(len(df)):
plt.text(
month[i], # x 坐标:对应月份
industry_speed[i], # y 坐标:对应增速值
s=industry_speed[i], # 显示的文本内容:增速数值
ha='center', # 水平对齐方式:居中对齐
va='bottom' # 垂直对齐方式:文本在点的下方(向上显示)
)
# 创建第二个子图:位于 3 行 1 列布局中的第 2 个位置
plt.subplot(3, 1, 2)
# 绘制钢材增速折线图,颜色为绿色
plt.plot(month, steel_speed, color='green', marker='o', markersize=3)
plt.title('钢材同比增长速度')
plt.ylabel('增长速度(%)')
# 设置 y 轴范围:从 -5 到 20,因为钢材增速可能出现负值
plt.ylim(-5, 20)
# 为每个数据点添加数值标签
for i in range(len(df)):
plt.text(
month[i],
steel_speed[i],
s=steel_speed[i],
ha='center',
va='bottom'
)
# 创建第三个子图:位于 3 行 1 列布局中的第 3 个位置
plt.subplot(3, 1, 3)
# 绘制十种有色金属增速折线图,颜色为蓝色
plt.plot(month, metals_speed, color='blue', marker='o', markersize=3)
plt.title('十种有色金属同比增长速度')
plt.ylabel('增长速度(%)')
# 设置 y 轴范围:0 到 15
plt.ylim(0, 15)
# 为每个数据点添加数值标签
for i in range(len(df)):
plt.text(
month[i],
metals_speed[i],
s=metals_speed[i],
ha='center',
va='bottom'
)
# 自动调整子图之间的间距,防止标题、标签等元素重叠
plt.tight_layout()
# 显示绘制好的图形窗口
plt.show()
绘制的图形如下图所示:
2. 使用 plt.subplots() 绘制子图
plt.subplots()
是创建网格子图的高效方法,一次性创建指定行列数的所有子图,并返回画布(Figure
)和子图数组(Axes
)。该方法适合批量创建结构固定的子图,代码更简洁,便于统一管理。
plt.subplots()
方法参数说明:
参数名 | 作用描述 | 取值示例 | 补充说明 |
---|---|---|---|
nrows |
网格的行数(默认 1) | 2 、3 |
如 nrows=2 表示创建 2 行子图 |
ncols |
网格的列数(默认 1) | 2 、4 |
如 ncols=3 表示创建 3 列子图 |
figsize |
画布的尺寸(宽, 高),单位为英寸 | (10, 8) |
控制整体画布大小,影响所有子图的显示比例 |
dpi |
画布的分辨率(每英寸像素数) | 100 、150 |
默认值为 100,数值越大图像越清晰 |
sharex |
子图是否共享 x 轴 | True 、'col' |
True /'all' 表示所有子图共享;'col' 表示每列子图共享;False 不共享 |
sharey |
子图是否共享 y 轴 | True 、'row' |
True /'all' 表示所有子图共享;'row' 表示每行子图共享;False 不共享 |
hspace |
子图之间的垂直间距(相对高度比例) | 0.3 、0.5 |
默认值为 0.2,数值越大间距越宽 |
wspace |
子图之间的水平间距(相对宽度比例) | 0.3 、0.5 |
默认值为 0.2,数值越大间距越宽 |
subplot_kw |
传递给子图的关键字参数(如 projection ) |
{'projection': 'polar'} |
用于为所有子图统一设置属性(如都使用极坐标) |
示例:绘制多个子图
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 numpy 模块,使用其数值计算功能
import numpy as np
# 使用 numpy 的 linspace 函数生成一个从 0 到 10 的等差数列,共 100 个点,作为 x 轴的数据
x = np.linspace(0, 10, 100)
# 创建一个包含 2 行 2 列(共 4 个)子图的网格,并返回图形对象 fig 和子图数组 axes
# 注意:这里的 nrows=2 和 ncols=2 实际创建的是 2 行 2 列的布局,但代码注释中提到的是 2 行 3 列,可能是注释错误
fig, axes = plt.subplots(nrows=2, ncols=2)
# 第 1 行第 1 列位置的子图绘制 sin(x) 曲线,颜色设置为蓝色
axes[0, 0].plot(x, np.sin(x), color='blue')
axes[0, 0].set_title('sin(x)') # 设置该子图标题为 'sin(x)'
# 第 1 行第 2 列位置的子图绘制 cos(x) 曲线,颜色设置为紫色
axes[0, 1].plot(x, np.cos(x), color='purple')
axes[0, 1].set_title('cos(x)') # 设置该子图标题为 'cos(x)'
# 第 2 行第 1 列位置的子图绘制 tan(x) 曲线,颜色设置为橙色
axes[1, 0].plot(x, np.tan(x), color='orange')
axes[1, 0].set_title('tan(x)') # 设置该子图标题为 'tan(x)'
# 第 2 行第 2 列位置的子图绘制 exp(x/10) 曲线,颜色设置为棕色
axes[1, 1].plot(x, np.exp(x / 10), color='brown')
axes[1, 1].set_title('exp(x/10)') # 设置该子图标题为 'exp(x/10)'
# 调用 tight_layout 函数自动调整子图之间的间距,避免重叠或过于紧凑
plt.tight_layout()
# 显示整个画布上的所有子图
plt.show()
绘制的图形如下图所示:
示例:部分国家养猫与养狗人群比例
数据如下图所示,需要根据这个数据绘制多个子图。
实现代码如下所示:
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 numpy 模块,用于数值计算和数组操作
import numpy as np
# 导入 pandas 模块,用于读取和处理 CSV 数据
import pandas as pd
# 设置中文字体为 SimHei(黑体),确保图表中的中文标签(如标题、坐标轴、图例)能正常显示
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号 '-' 显示为方块的问题(某些中文字体不支持负号)
# 设置为 False 表示允许正常显示负号
plt.rcParams['axes.unicode_minus'] = False
# 定义 CSV 文件路径,请根据实际情况替换为你的实际文件路径
csv_file_path = './data/部分国家养猫与养狗人群比例.csv'
# 使用 pandas 读取指定路径的 CSV 文件,返回一个 DataFrame 对象(二维表格结构)
df = pd.read_csv(csv_file_path)
# 从 DataFrame 中提取“国家”列,并转换为 Python 列表
# 用于后续作为条形图的 y 轴标签(国家名称)
country = df['国家'].tolist()
# 提取“养猫人群比例(%)”列数据,转换为列表
# 用于绘制养猫比例的横向条形图
x_cat = df['养猫人群比例(%)'].tolist()
# 提取“养狗人群比例(%)”列数据,转换为列表
# 用于绘制养狗比例的横向条形图
x_dog = df['养狗人群比例(%)'].tolist()
# 创建一个包含 1 行 2 列子图的图形画布
# fig: 整个图形对象,用于控制整体布局
# ax: 包含两个 Axes 对象的数组,分别对应左右两个子图
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(10, 6)) # 可选:设置画布大小
# 绘制左侧子图:养猫人群比例的横向条形图(barh 表示 horizontal bar)
ax[0].barh(
y=np.arange(len(country)), # y 轴位置:用 0,1,2... 表示每个国家的位置
width=x_cat, # 条形的长度(宽度),对应养猫比例数值
height=0.5, # 条形的高度(厚度),可调整美观度
tick_label=country, # y 轴刻度标签,显示国家名称
color='#ffa2a5' # 条形颜色,使用十六进制浅红色调(粉红色)
)
ax[0].set_title('部分国家养猫人群比例') # 设置左侧子图标题
ax[0].set_xlabel('人群比例(%)') # 设置 x 轴标签
# 为左侧子图的每个条形添加数值标签
for i in range(len(country)):
ax[0].text(x=x_cat[i], y=i, s=f'{i}%', ha='left', va='center')
# 绘制右侧子图:养狗人群比例的横向条形图
ax[1].barh(
y=np.arange(len(country)), # y 轴位置与左侧一致,确保国家对齐
width=x_dog, # 条形长度对应养狗比例
height=0.5, # 条形高度
tick_label=country, # 显示国家名称
color='#a2d5f5' # 条形颜色,使用十六进制浅蓝色调
)
ax[1].set_title('部分国家养狗人群比例') # 设置右侧子图标题
ax[1].set_xlabel('人群比例(%)') # 设置 x 轴标签
# 为右侧子图的每个条形添加数值标签
for i in range(len(country)):
ax[1].text(x=x_dog[i], y=i, s=f'{i}%', ha='left', va='center')
# 自动调整子图之间的间距,防止标题、标签等元素重叠或显示不全
plt.tight_layout()
# 显示绘制好的图形窗口
plt.show()
绘制的图形如下图所示:
三、绘制跨越区域子图
1. 使用 plt.subplot2grid() 绘制子图
plt.subplot2grid()
允许将画布划分为一个二维网格,并通过指定起始位置和跨越范围来创建子图,非常适合制作复杂布局的可视化面板(如仪表盘)。
plt.subplot2grid()方法参数说明:
参数名 | 类型 | 描述 | 示例 |
---|---|---|---|
shape |
tuple (rows, cols) |
定义整个画布被划分为多少行和列的网格 | (3, 3) 表示 3 行 3 列 |
loc |
tuple (row, col) |
指定子图左上角所在的网格位置(从 (0,0) 开始) |
(0, 0) 表示第一行第一列 |
rowspan |
int(可选) | 子图纵向跨越的行数 | 2 表示占两行高度 |
colspan |
int(可选) | 子图横向跨越的列数 | 2 表示占两列宽度 |
**kwargs |
其他参数 | 传递给 Axes 的参数,如 figsize 、投影方式等 |
projection='polar' |
2. 示例:抖音和快手用户画像对比
数据如下图所示,需要根据这两个数据绘制跨区域子图。
实现代码如下所示:
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 numpy 模块,用于数值计算和数组操作(例如生成坐标位置)
import numpy as np
# 导入 pandas 模块,用于读取和处理 CSV 数据
import pandas as pd
# 设置中文字体为 SimHei(黑体),确保图表中的中文标签(如标题、坐标轴、图例)能正常显示
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号 '-' 显示为方块的问题(某些中文字体不支持负号)
# 设置为 False 表示允许正常显示负号
plt.rcParams['axes.unicode_minus'] = False
# 定义两个 CSV 文件的路径,请根据实际情况替换为你的实际文件路径
csv_file_path1 = './data/抖音和快手平台用户城市分布.csv' # 用户城市分布数据
csv_file_path2 = './data/抖音和快手平台用户年龄分布.csv' # 用户年龄分布数据
# 使用 pandas 读取两个 CSV 文件,返回两个 DataFrame 对象(二维表格结构)
df1 = pd.read_csv(csv_file_path1) # 城市分布数据
df2 = pd.read_csv(csv_file_path2) # 年龄分布数据
# 为城市分布图生成 x 轴的位置索引(0, 1, 2...),用于控制柱子的水平位置
x1 = np.arange(len(df1))
# 为年龄分布图生成 y 轴的位置索引(0, 1, 2...),用于控制条形的垂直位置
x2 = np.arange(len(df2))
# 提取城市分类标签(如:一线城市、二线城市等),用于 x 轴刻度标签
city_labels = df1['城市分类'].tolist()
# 提取年龄区间标签(如:18-24岁、25-34岁等),用于 y 轴刻度标签
age_labels = df2['年龄区间'].tolist()
# 提取抖音平台在不同城市的用户比例数据
douyin_data1 = df1['抖音用户比例(%)'].tolist()
# 提取快手平台在不同城市的用户比例数据
kuaishou_data1 = df1['快手用户比例(%)'].tolist()
# 提取抖音平台在不同年龄区间的用户比例数据
douyin_data2 = df2['抖音用户比例(%)'].tolist()
# 提取快手平台在不同年龄区间的用户比例数据
kuaishou_data2 = df2['快手用户比例(%)'].tolist()
# -------------------------------
# 第一个子图:城市分布对比(顶部横跨两列)
# 使用 subplot2grid 创建自定义布局:整个画布划分为 3 行 2 列
# 当前子图从 (0,0) 开始(第1行第1列),横向占2列,纵向占1行
ax1 = plt.subplot2grid((3, 2), (0, 0), rowspan=1, colspan=2)
# 绘制抖音用户比例的柱状图
ax1.bar(
x=x1, # 柱子的 x 坐标位置
height=douyin_data1, # 柱子的高度(用户比例)
color='#a2c4ff', # 颜色:浅蓝色(代表抖音)
tick_label=city_labels, # x 轴刻度标签:城市分类名称
width=0.3, # 柱子宽度
label='抖音' # 图例标签
)
# 绘制快手用户比例的柱状图,x 位置向右偏移 0.3,实现并列显示
ax1.bar(
x=x1 + 0.3, # 向右偏移,避免与抖音柱子重叠
height=kuaishou_data1, # 快手用户比例
color='#ffa2c4', # 颜色:浅粉色(代表快手)
tick_label=city_labels, # 标签与抖音一致
width=0.3, # 柱子宽度
label='快手' # 图例标签
)
# 设置标题
ax1.set_title('抖音和快手平台用户城市分布')
# 设置 x 轴标签
ax1.set_xlabel('城市分类')
# 设置 y 轴标签
ax1.set_ylabel('用户比例(%)')
# 添加图例,设置字体大小为 8
ax1.legend(fontsize=8)
# 设置 y 轴范围为 0 到 25%,便于统一比较
ax1.set_ylim(0, 25)
# 在每个柱子顶部添加数值标签(抖音)
for i in range(len(df1)):
ax1.text(
x=x1[i], # x 位置
y=douyin_data1[i], # y 位置(柱子顶部)
s=douyin_data1[i], # 显示的文本内容
ha='center', # 水平居中对齐
va='bottom' # 垂直底部对齐(在柱子上方)
)
# 添加快手数据标签
ax1.text(
x=x1[i] + 0.3,
y=kuaishou_data1[i],
s=kuaishou_data1[i],
ha='center',
va='bottom'
)
# -------------------------------
# 第二个子图:抖音平台用户年龄分布(左下角)
# 位置:从 (1,0) 开始(第2行第1列),纵向占2行,横向占1列
ax2 = plt.subplot2grid((3, 2), (1, 0), rowspan=2, colspan=1)
# 绘制横向条形图(barh),展示抖音用户在各年龄段的比例
ax2.barh(
y=x2, # 条形的 y 位置
width=douyin_data2, # 条形长度(用户比例)
height=0.5, # 条形高度(厚度)
tick_label=age_labels, # y 轴标签:年龄区间
color='#a2c4ff' # 浅蓝色,与上图一致
)
# 设置标题
ax2.set_title('抖音平台用户年龄分布')
# 设置 x 轴标签
ax2.set_xlabel('用户比例(%)')
# 设置 x 轴范围为 0 到 30%,便于比较
ax2.set_xlim(0, 30)
# 在每个条形右侧添加数值标签
for i in range(len(df2)):
ax2.text(
x=douyin_data2[i], # x 位置:比例值
y=x2[i], # y 位置:对应年龄区间
s=douyin_data2[i], # 显示的数值
ha='left', # 水平左对齐(在条形右侧)
va='center' # 垂直居中对齐
)
# -------------------------------
# 第三个子图:快手平台用户年龄分布(右下角)
# 位置:从 (1,1) 开始(第2行第2列),纵向占2行,横向占1列
ax3 = plt.subplot2grid((3, 2), (1, 1), rowspan=2, colspan=1)
# 绘制横向条形图,展示快手用户在各年龄段的比例
ax3.barh(
y=x2,
width=kuaishou_data2,
height=0.5,
tick_label=age_labels,
color='#ffa2c4' # 浅粉色,与上图一致
)
# 设置标题
ax3.set_title('快手平台用户年龄分布')
# 设置 x 轴标签
ax3.set_xlabel('用户比例(%)')
# 设置 x 轴范围一致,便于对比
ax3.set_xlim(0, 30)
# 在每个条形右侧添加数值标签
for i in range(len(df2)):
ax3.text(
x=kuaishou_data2[i],
y=x2[i],
s=kuaishou_data2[i],
ha='left',
va='center'
)
# 自动调整所有子图之间的间距,防止标题、标签、图例等元素重叠或被截断
plt.tight_layout()
# 显示绘制好的图形窗口
plt.show()
绘制的图形如下图所示:
四、绘制自定义区域子图
在 Matplotlib 中,除了使用 subplot()
和 subplot2grid()
创建规则布局外,还可以通过更灵活的机制实现高度自定义的子图区域划分。本节将介绍如何使用 GridSpec
类和 add_gridspec()
方法来创建复杂的、可定制间距与比例的子图布局。
1. 通过 GridSpec 类绘制
GridSpec
是一个用于定义网格布局的类,可以指定行数、列数以及每行/列的尺寸比例和间距。
GridSpec 类构造方法参数说明:
参数名 | 类型 | 描述 | 示例 |
---|---|---|---|
nrows |
int | 网格的行数 | 3 |
ncols |
int | 网格的列数 | 2 |
figure |
Figure(可选) | 关联的画布对象 | fig |
left , right , top , bottom |
float(可选) | 子图区域距离画布边缘的位置(归一化坐标 0~1) | left=0.1, right=0.9 |
wspace |
float(可选) | 列之间的水平间距 | 0.3 |
hspace |
float(可选) | 行之间的垂直间距 | 0.4 |
width_ratios |
list of floats | 每列的相对宽度比例 | [2, 1] 表示第一列是第二列的两倍宽 |
height_ratios |
list of floats | 每行的相对高度比例 | [1, 3] 表示第二行是第一行的三倍高 |
🔹 所有间距值为
None
时表示自动调整(默认);
🔹width_ratios
和height_ratios
使用比例而非绝对值。
示例:使用 GridSpec 创建布局
# 导入 matplotlib.gridspec 模块,用于创建复杂的、可自定义行列比例的子图网格布局
import matplotlib.gridspec as gridspec
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 创建一个画布(Figure),设置图形大小为 8 英寸宽 × 6 英寸高
fig = plt.figure(figsize=(8, 6))
# 使用 GridSpec 定义一个 3 行 3 列的网格布局(共 9 个单元格)
# 该布局将用于在画布上精确控制每个子图的位置和大小
gs = gridspec.GridSpec(
nrows=3, # 网格的行数:3 行
ncols=3, # 网格的列数:3 列
figure=fig, # 指定该 GridSpec 属于哪个画布对象
wspace=0.2, # 子图之间的横向间距(空白宽度比例),0.2 表示中等间距
hspace=0.2, # 子图之间的纵向间距(空白高度比例)
width_ratios=[1, 1, 1], # 每一列的相对宽度比例:三列等宽
height_ratios=[1, 1, 1] # 每一行的相对高度比例:三行等高
)
# 在网格的第 1 行(索引 0),占据所有 3 列(切片 0:3)创建第一个子图
ax1 = fig.add_subplot(gs[0, 0:3])
# 在 ax1 中绘制一条简单的折线(x: 0~4, y: [1,2,3,4,5])
ax1.plot([1, 2, 3, 4, 5])
# 在网格的第 2 行(索引 1),占据前 2 列(0~1列)创建第二个子图
ax2 = fig.add_subplot(gs[1, 0:2])
ax2.plot([1, 2, 3, 4, 5])
# 在网格的第 3 行(索引 2),占据第 1 列(索引 0)创建第三个子图
ax3 = fig.add_subplot(gs[2, :1]) # 等价于 gs[2, 0]
ax3.plot([1, 2, 3, 4, 5])
# 在网格的第 3 行(索引 2),占据第 2 列(索引 1)创建第四个子图
ax4 = fig.add_subplot(gs[2, 1:2]) # 注意:切片 1:2 表示只取第1列(索引1)
ax4.plot([1, 2, 3, 4, 5])
# 在网格的第 2-3 行(索引 1 到 2),占据第 3 列(索引 2)创建第五个子图
# 这是一个跨行的子图(纵向合并两个单元格)
ax5 = fig.add_subplot(gs[1:3, 2])
ax5.plot([1, 2, 3, 4, 5])
# 显示图形
plt.show()
绘制的图形如下图所示:
2. 通过 add_gridspec() 方法绘制
Figure.add_gridspec()
是 Matplotlib 3.1+ 推荐的现代方法,它直接在画布上创建一个 GridSpec
实例,并返回该对象,便于链式调用。
add_gridspec()方法参数说明:
参数名 | 类型 | 描述 | 示例 |
---|---|---|---|
nrows |
int | 行数 | 2 |
ncols |
int | 列数 | 3 |
left , right , top , bottom |
float | 子图区域边界(归一化坐标) | left=0.1 |
wspace , hspace |
float | 水平/垂直间距 | wspace=0.2 |
width_ratios |
list | 各列宽度比例 | [1, 2, 1] |
height_ratios |
list | 各行高度比例 | [3, 1] |
示例:使用 add_gridspec() 创建布局
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 创建一个图形画布(Figure),并设置其大小为 8 英寸宽 × 6 英寸高
# figsize 参数控制整个图形的尺寸,便于后续排版和展示
fig = plt.figure(figsize=(8, 6))
# 在画布 fig 上添加一个 GridSpec(网格规范)对象,用于定义子图的网格布局
# 这种方式比直接使用 gridspec.GridSpec 更简洁,是 matplotlib 推荐的现代写法
gs = fig.add_gridspec(
nrows=3, # 定义网格有 3 行
ncols=3, # 定义网格有 3 列
figure=fig, # 指定该 GridSpec 所属的画布对象(通常可省略,因为是通过 fig 调用的)
wspace=0.2, # 设置子图之间的横向间距(width space)
hspace=0.2, # 设置子图之间的纵向间距(height space)
width_ratios=[1, 1, 1], # 每一列的相对宽度比例
height_ratios=[1, 1, 1] # 每一行的相对高度比例
)
# 在网格的第 1 行(索引 0),占据所有 3 列(切片 0:3)创建第一个子图
ax1 = fig.add_subplot(gs[0, 0:3])
# 在 ax1 中绘制一条简单的折线(x: 0~4, y: [1,2,3,4,5])
ax1.plot([1, 2, 3, 4, 5])
# 在网格的第 2 行(索引 1),占据前 2 列(0~1列)创建第二个子图
ax2 = fig.add_subplot(gs[1, 0:2])
ax2.plot([1, 2, 3, 4, 5])
# 在网格的第 3 行(索引 2),占据第 1 列(索引 0)创建第三个子图
ax3 = fig.add_subplot(gs[2, :1]) # 等价于 gs[2, 0]
ax3.plot([1, 2, 3, 4, 5])
# 在网格的第 3 行(索引 2),占据第 2 列(索引 1)创建第四个子图
ax4 = fig.add_subplot(gs[2, 1:2]) # 注意:切片 1:2 表示只取第1列(索引1)
ax4.plot([1, 2, 3, 4, 5])
# 在网格的第 2-3 行(索引 1 到 2),占据第 3 列(索引 2)创建第五个子图
# 这是一个跨行的子图(纵向合并两个单元格)
ax5 = fig.add_subplot(gs[1:3, 2])
ax5.plot([1, 2, 3, 4, 5])
# 显示图形
plt.show()
绘制的图形如下图所示:
五、子图坐标轴共享
在创建多个子图时,有时候需要共享某些坐标轴(x轴或y轴)以便于比较不同数据集的趋势或分布。Matplotlib 提供了便捷的方法来实现这一点,即通过 plt.subplot()
和 plt.subplots()
中的 sharex
和 sharey
参数来控制坐标轴共享。
1. 共享相邻子图的坐标轴
1.1 共享相邻子图的坐标轴介绍
共享坐标轴能够确保相关子图之间的一致性,这对于比较不同的数据集尤其有用。例如,在时间序列分析中,可能希望所有子图共享相同的x轴以展示不同变量随时间的变化趋势;或者在散点图矩阵中,每个子图都共享相同的x轴和y轴范围,以便于对比不同变量之间的关系。
共享坐标轴不仅可以减少重复工作,还能提高图形的可读性和一致性。此外,当调整一个共享坐标轴的缩放比例或移动视图窗口时,其他共享该坐标轴的子图也会同步更新,增强了交互式探索数据的能力。
1.2 plt.subplot()
和 plt.subplots()
方法中的参数 sharex
或 sharey
的取值
在 Matplotlib 中,plt.subplot()
和 plt.subplots()
方法允许通过设置 sharex
和 sharey
参数来控制坐标轴的共享方式。以下是这些参数可能的取值及其含义:
参数 sharex
或 sharey
的取值说明:
取值 | 描述 |
---|---|
"none" |
不共享任何坐标轴(默认)。 |
"all" |
所有子图共享同一个x轴或y轴。这意味着所有子图都将具有相同的轴刻度和范围。 |
"row" |
同一行中的子图共享x轴或y轴。适用于需要在同一行内进行直接比较的情况。 |
"col" |
同一列中的子图共享x轴或y轴。适用于需要在同一列内进行直接比较的情况。 |
"auto" |
根据布局自动决定是否共享坐标轴。通常不会主动选择此选项,因为它依赖于具体的上下文。 |
1.3 示例:部分国家养猫与养狗人群比例
数据如下图所示,需要根据这个数据绘制多个子图,并共享y轴。
实现代码如下所示:
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 numpy 模块,用于数值计算和数组操作
import numpy as np
# 导入 pandas 模块,用于读取和处理 CSV 数据
import pandas as pd
# 设置中文字体为 SimHei(黑体),确保图表中的中文标签(如标题、坐标轴、图例)能正常显示
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号 '-' 显示为方块的问题(某些中文字体不支持负号)
# 设置为 False 表示允许正常显示负号
plt.rcParams['axes.unicode_minus'] = False
# 定义 CSV 文件路径,请根据实际情况替换为你的实际文件路径
csv_file_path = './data/部分国家养猫与养狗人群比例.csv'
# 使用 pandas 读取指定路径的 CSV 文件,返回一个 DataFrame 对象(二维表格结构)
df = pd.read_csv(csv_file_path)
# 从 DataFrame 中提取“国家”列,并转换为 Python 列表
# 用于后续作为条形图的 y 轴标签(国家名称)
country = df['国家'].tolist()
# 提取“养猫人群比例(%)”列数据,转换为列表
# 用于绘制养猫比例的横向条形图
x_cat = df['养猫人群比例(%)'].tolist()
# 提取“养狗人群比例(%)”列数据,转换为列表
# 用于绘制养狗比例的横向条形图
x_dog = df['养狗人群比例(%)'].tolist()
# 创建一个包含 1 行 2 列子图的图形画布
# fig: 整个图形对象,用于控制整体布局
# ax: 包含两个 Axes 对象的数组,分别对应左右两个子图
fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(8, 6), sharey='row')
# 绘制左侧子图:养猫人群比例的横向条形图(barh 表示 horizontal bar)
ax[0].barh(
y=np.arange(len(country)), # y 轴位置:用 0,1,2... 表示每个国家的位置
width=x_cat, # 条形的长度(宽度),对应养猫比例数值
height=0.5, # 条形的高度(厚度),可调整美观度
tick_label=country, # y 轴刻度标签,显示国家名称
color='#ffa2a5' # 条形颜色,使用十六进制浅红色调(粉红色)
)
ax[0].set_title('部分国家养猫人群比例') # 设置左侧子图标题
ax[0].set_xlabel('人群比例(%)') # 设置 x 轴标签
# 为左侧子图的每个条形添加数值标签
for i in range(len(country)):
ax[0].text(x=x_cat[i], y=i, s=f'{i}%', ha='left', va='center')
# 绘制右侧子图:养狗人群比例的横向条形图
ax[1].barh(
y=np.arange(len(country)), # y 轴位置与左侧一致,确保国家对齐
width=x_dog, # 条形长度对应养狗比例
height=0.5, # 条形高度
tick_label=country, # 显示国家名称
color='#a2d5f5' # 条形颜色,使用十六进制浅蓝色调
)
ax[1].set_title('部分国家养狗人群比例') # 设置右侧子图标题
ax[1].set_xlabel('人群比例(%)') # 设置 x 轴标签
# 为右侧子图的每个条形添加数值标签
for i in range(len(country)):
ax[1].text(x=x_dog[i], y=i, s=f'{i}%', ha='left', va='center')
# 自动调整子图之间的间距,防止标题、标签等元素重叠或显示不全
plt.tight_layout()
# 显示绘制好的图形窗口
plt.show()
绘制的图形如下图所示:
2. 共享非相邻子图的坐标轴
2.1 共享非相邻子图的坐标轴介绍
在 Matplotlib 中,除了共享相邻子图(如同行或同列)的坐标轴外,有时也需要让不相邻的子图(例如分散在画布不同位置的子图)共享同一个坐标轴(x 轴或 y 轴)。这种需求常见于复杂布局中,例如仪表盘、多面板可视化或需要跨区域对比数据的场景。
与 plt.subplots(sharex=..., sharey=...)
仅适用于规则网格布局不同,共享非相邻子图的坐标轴通常需要通过手动指定 sharex
或 sharey
参数,将一个子图(Axes)作为另一个子图的“参考轴”,从而实现坐标轴的联动。
2.2 示例:非相邻子图坐标轴共享
# 导入必要的库
import matplotlib.pyplot as plt
import numpy as np
# 使用 subplot 函数创建一个 2x2 的网格布局中的第一个子图 (即左上角位置)
# 221 表示在 2 行 2 列的网格中的第 1 个位置
ax_one = plt.subplot(221)
# 生成 x 轴数据,范围从 0 到 2π,共 400 个点
x1 = np.linspace(0, 2 * np.pi, 400)
# 根据 x1 计算 y 轴数据,这里使用 cos(x^2) 函数
y1 = np.cos(x1 ** 2)
# 在 ax_one 子图中绘制曲线
ax_one.plot(x1, y1)
# 生成第二个 x 轴的数据集,范围从 0.01 到 10,共 100 个点
x2 = np.linspace(0.01, 10, 100)
# 根据 x2 计算 y 轴数据,这里使用 sin(x) 函数
y2 = np.sin(x2)
# 创建 2x2 网格布局中的第四个子图 (即右下角位置),并指定与 ax_one 共享 x 轴
# 这意味着 ax_two 将具有与 ax_one 相同的 x 轴缩放和范围
ax_two = plt.subplot(224, sharex=ax_one)
# 在 ax_two 子图中绘制曲线
ax_two.plot(x2, y2)
# 显示图形
plt.show()
绘制的图形如下图所示:
3. 同一子图共享坐标轴(双 Y 轴)
3.1 同一子图共享 x 坐标轴介绍
在 Matplotlib 中,“同一子图共享 x 坐标轴” 实际上是指:在同一个图表区域(Axes)中,创建两个独立的 Y 轴(左和右),但共享同一个 X 轴。这种技术通常被称为 “双 Y 轴图”(Dual Y-axis Plot),是数据可视化中非常常见的需求。
可使用ax.twinx()
方法实现同一子图共享 x 坐标轴。
方法 | 描述 | 返回值 | 是否有参数 |
---|---|---|---|
ax.twinx() |
创建一个共享 x 轴、但拥有独立 y 轴 的新坐标系(通常在右侧) | 新的 Axes 对象 |
❌ 无参数 |
3.2 示例:某地区全年气温与降水量、蒸发量的关系
数据如下图所示,需要根据这个数据绘制双y轴的图形。
实现代码如下所示:
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 numpy 模块,用于数值计算和数组操作
import numpy as np
# 导入 pandas 模块,用于读取和处理 CSV 数据
import pandas as pd
# 设置中文字体为 SimHei(黑体),确保图表中的中文标签(如标题、坐标轴、图例)能正常显示
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号 '-' 显示为方块的问题(某些中文字体不支持负号)
# 设置为 False 表示允许正常显示负号
plt.rcParams['axes.unicode_minus'] = False
# 定义 CSV 文件路径,请根据实际情况替换为你的实际文件路径
csv_file_path = './data/某地区全年的平均气温与降水量、蒸发量 .csv'
# 使用 pandas 读取指定路径的 CSV 文件,返回一个 DataFrame 对象(二维表格结构)
df = pd.read_csv(csv_file_path)
# 从 DataFrame 中提取 '月份' 列,并将其转换为 Python 列表
month = df['月份'].tolist()
# 从 DataFrame 中提取 '蒸发量 (ml)' 列,并将其转换为 Python 列表
evaporation = df['蒸发量 (ml)'].tolist()
# 从 DataFrame 中提取 '降水量 (ml)' 列,并将其转换为 Python 列表
rainfall = df['降水量 (ml)'].tolist()
# 从 DataFrame 中提取 '平均气温 (℃)' 列,并将其转换为 Python 列表
temperature = df['平均气温 (℃)'].tolist()
# 创建图形和子图对象,fig 是整个图像窗口,ax 是主图的坐标轴对象
fig, ax = plt.subplots()
# 在主图上绘制蒸发量柱状图,设置宽度和颜色,并添加标签
ax.bar(x=np.arange(len(df)), height=evaporation, width=0.3, label='蒸发量', color='#ffa565', tick_label=month)
# 在主图上偏移0.3的位置绘制降水量柱状图,避免两组柱状图重叠
ax.bar(x=np.arange(len(df)) + 0.3, height=rainfall, width=0.3, label='降水量', color='#65a5ff')
# 设置X轴标签为“月份”
ax.set_xlabel('月份')
# 设置Y轴标签为“水量 (ml)”表示蒸发量和降水量
ax.set_ylabel('水量 (ml)')
# 设置图表标题
ax.set_title('某地区全年的平均气温与降水量、蒸发量')
# 创建一个新的坐标轴 ax_new,与原坐标轴共享X轴,用于展示平均气温数据
ax_new = ax.twinx()
# 在新坐标轴上绘制平均气温折线图,并添加标记点
ax_new.plot(month, temperature, color='#a565ff', marker='o', label='平均气温')
# 设置新坐标轴的Y轴标签为“温度 (℃)”
ax_new.set_ylabel('温度 (℃)')
# 获取两个坐标轴上的图例句柄和标签,合并后一次性添加到图中,以确保所有元素都能正确显示在图例中
handles1, labels1 = ax.get_legend_handles_labels()
handles2, labels2 = ax_new.get_legend_handles_labels()
ax.legend(handles=handles1 + handles2, labels=labels1 + labels2)
# 自动调整子图之间的间距,防止标题、标签等元素重叠或显示不全
plt.tight_layout()
# 显示绘制好的图形窗口
plt.show()
绘制的图形如下图所示:
六、子图布局
1. 约束布局
在 Matplotlib 中,合理安排多个子图的布局对于提高图表的可读性和美观性至关重要。约束布局(Constrained Layout)是 Matplotlib 提供的一种自动调整子图位置以避免重叠和优化空间利用的功能。
1.1 约束布局介绍
约束布局是一种用于自动化管理子图间距和位置的方法,旨在减少手动调整子图布局的需求。它通过计算每个子图的最佳位置来确保:
- 子图之间不会重叠;
- 标题、标签、刻度等元素有足够空间显示,而不会被裁剪或遮挡;
- 整体布局更加紧凑且有序。
约束布局特别适用于包含多个子图、颜色条、注释和其他复杂元素的图表。它可以帮助用户创建既美观又专业的可视化结果,无需反复试验不同的参数设置。
1.2 启用约束布局
启用约束布局非常简单,可以通过两种主要方式实现:
1.2.1 全局启用
可以在调用 plt.subplots()
或其他绘图函数之前,通过 plt.rcParams
设置全局使用约束布局。
import matplotlib.pyplot as plt
plt.rcParams['figure.constrained_layout.use'] = True
这种方式将影响所有后续生成的图表,除非在特定的图表中明确禁用约束布局。
1.2.2 局部启用
也可以针对具体的图表启用约束布局,通过向 plt.subplots()
或其他类似函数传递 constrained_layout=True
参数。
import matplotlib.pyplot as plt
fig, axs = plt.subplots(2, 2, constrained_layout=True)
这种方法允许对单个图表进行更精细的控制,不影响其他图表的默认行为。
1.2.3 自定义约束布局参数
除了简单的启用/禁用之外,还可以通过 constrained_layout_pads
参数进一步自定义约束布局的行为。
# 导入 matplotlib.pyplot 模块两次是不必要的,这里保留一次即可。
import matplotlib.pyplot as plt
# 设置全局参数以控制图形布局和子图间距
plt.rcParams['figure.constrained_layout.use'] = True # 启用 constrained_layout 布局管理器,自动调整子图位置避免重叠
plt.rcParams['figure.constrained_layout.w_pad'] = 0.04167 # 子图之间的最小宽度间距(相对字体大小)
plt.rcParams['figure.constrained_layout.h_pad'] = 0.04167 # 子图之间的最小高度间距(相对字体大小)
plt.rcParams['figure.constrained_layout.wspace'] = 0.02 # 相邻子图在水平方向上的间距(相对宽度的比例)
plt.rcParams['figure.constrained_layout.hspace'] = 0.02 # 相邻子图在垂直方向上的间距(相对高度的比例)
# 创建一个包含 2 行 2 列子图的图形(即 4 个子图)
# 返回 fig(整个图形对象)和 axs(子图数组,形状为 2x2)
fig, axs = plt.subplots(nrows=2, ncols=2)
# axs 是一个 2x2 的 NumPy 数组,包含四个 Axes 对象(即四个子图)
# 使用 axs.flat 可以将二维数组展平为一维迭代器,方便对每个子图进行统一操作
for ax in axs.flat:
# 在每一个子图中绘制相同的折线图
# x 坐标为 [1, 2, 3],y 坐标为 [4, 5, 6]
ax.plot([1, 2, 3], [4, 5, 6])
# 显示绘制好的图形窗口
plt.show()
1.2.4 示例:绘制多个子图(约束布局)
实现代码如下所示:
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 numpy 模块,使用其数值计算功能
import numpy as np
# 使用 numpy 的 linspace 函数生成一个从 0 到 10 的等差数列,共 100 个点,作为 x 轴的数据
x = np.linspace(0, 10, 100)
# 创建一个包含 2 行 2 列(共 4 个)子图的网格,并返回图形对象 fig 和子图数组 axes
# 注意:这里的 nrows=2 和 ncols=2 实际创建的是 2 行 2 列的布局,但代码注释中提到的是 2 行 3 列,可能是注释错误
fig, axes = plt.subplots(nrows=2, ncols=2, constrained_layout=True)
# 第 1 行第 1 列位置的子图绘制 sin(x) 曲线,颜色设置为蓝色
axes[0, 0].plot(x, np.sin(x), color='blue')
axes[0, 0].set_title('sin(x)') # 设置该子图标题为 'sin(x)'
# 第 1 行第 2 列位置的子图绘制 cos(x) 曲线,颜色设置为紫色
axes[0, 1].plot(x, np.cos(x), color='purple')
axes[0, 1].set_title('cos(x)') # 设置该子图标题为 'cos(x)'
# 第 2 行第 1 列位置的子图绘制 tan(x) 曲线,颜色设置为橙色
axes[1, 0].plot(x, np.tan(x), color='orange')
axes[1, 0].set_title('tan(x)') # 设置该子图标题为 'tan(x)'
# 第 2 行第 2 列位置的子图绘制 exp(x/10) 曲线,颜色设置为棕色
axes[1, 1].plot(x, np.exp(x / 10), color='brown')
axes[1, 1].set_title('exp(x/10)') # 设置该子图标题为 'exp(x/10)'
# 显示整个画布上的所有子图
plt.show()
绘制的图形如下图所示:
2. 紧密布局
2.1 紧密布局介绍
紧密布局(Tight Layout) 是 Matplotlib 中一种自动调整子图位置和大小的机制,旨在消除子图之间的多余空白,并确保标题、标签、刻度等元素不会被裁剪或重叠。与“约束布局”(Constrained Layout)类似,tight_layout
也是一种布局优化工具,但它采用的是后处理方式:在所有绘图完成后,自动计算并调整子图的几何位置。
2.2 启用紧密布局
2.2.1 通过 tight_layout() 启用
tight_layout()
是 Matplotlib 中最常用的自动布局优化方法之一。它会自动调整子图(Axes
)的位置和间距,确保标题、坐标轴标签、刻度等元素不会被裁剪或重叠,从而生成紧凑且美观的图表。
该方法在所有绘图操作完成后调用,是一种“后处理”式的布局调整机制。
调用方式:
plt.tight_layout()
# 或
fig.tight_layout()
两者功能相同,
plt.tight_layout()
作用于当前图像,fig.tight_layout()
作用于指定的Figure
对象。
tight_layout()
方法参数说明:
参数名 | 类型 | 默认值 | 描述 |
---|---|---|---|
pad |
float | 1.08 |
图像边缘到子图区域的最小边距(单位:英寸)。用于防止边缘元素被裁剪。 |
h_pad |
float 或 None | pad |
子图之间的垂直间距(英寸)。若为 None ,使用 pad 值。 |
w_pad |
float 或 None | pad |
子图之间的水平间距(英寸)。若为 None ,使用 pad 值。 |
rect |
tuple (left, bottom, right, top) |
(0, 0, 1, 1) |
子图占据的归一化坐标区域(0~1),常用于为 suptitle 、图例等外部元素预留空间。例如:rect=[0, 0, 1, 0.95] 表示子图只占用底部 95% 的高度,顶部留出空间。 |
2.2.2 使用 subplots() 或 figure() 方法中的 layout 参数
从 Matplotlib 3.6.0 开始,引入了新的统一布局管理参数 layout
,可用于 plt.subplots()
和 plt.figure()
中,支持多种布局策略。
语法:
plt.subplots(..., layout='tight')
# 或
plt.figure(..., layout='tight')
layout
参数可选值:
值 | 描述 |
---|---|
None |
不启用自动布局(默认)。 |
'tight' |
启用 tight_layout 自动排版。 |
'constrained' |
启用 constrained_layout (推荐用于复杂图表)。 |
'compressed' |
类似 'tight' ,但在某些情况下更紧凑(实验性)。 |
2.2.3 通过 figure.autolayout 配置项启用
可以通过 Matplotlib 的全局配置参数 figure.autolayout
来默认启用 tight_layout
,这样所有后续创建的图像都会自动应用 tight_layout
,无需每次手动调用。
import matplotlib.pyplot as plt
# 全局启用 tight_layout
plt.rcParams['figure.autolayout'] = True
2.2.4 示例:绘制多个子图(紧密布局)
实现代码如下所示:
# 导入 matplotlib.pyplot 模块,用于绘图
import matplotlib.pyplot as plt
# 导入 numpy 模块,使用其数值计算功能
import numpy as np
# 使用 numpy 的 linspace 函数生成一个从 0 到 10 的等差数列,共 100 个点,作为 x 轴的数据
x = np.linspace(0, 10, 100)
# 创建一个包含 2 行 2 列(共 4 个)子图的网格,并返回图形对象 fig 和子图数组 axes
# 注意:这里的 nrows=2 和 ncols=2 实际创建的是 2 行 2 列的布局,但代码注释中提到的是 2 行 3 列,可能是注释错误
fig, axes = plt.subplots(nrows=2, ncols=2)
# 第 1 行第 1 列位置的子图绘制 sin(x) 曲线,颜色设置为蓝色
axes[0, 0].plot(x, np.sin(x), color='blue')
axes[0, 0].set_title('sin(x)') # 设置该子图标题为 'sin(x)'
# 第 1 行第 2 列位置的子图绘制 cos(x) 曲线,颜色设置为紫色
axes[0, 1].plot(x, np.cos(x), color='purple')
axes[0, 1].set_title('cos(x)') # 设置该子图标题为 'cos(x)'
# 第 2 行第 1 列位置的子图绘制 tan(x) 曲线,颜色设置为橙色
axes[1, 0].plot(x, np.tan(x), color='orange')
axes[1, 0].set_title('tan(x)') # 设置该子图标题为 'tan(x)'
# 第 2 行第 2 列位置的子图绘制 exp(x/10) 曲线,颜色设置为棕色
axes[1, 1].plot(x, np.exp(x / 10), color='brown')
axes[1, 1].set_title('exp(x/10)') # 设置该子图标题为 'exp(x/10)'
# 调用 tight_layout 函数自动调整子图之间的间距,避免重叠或过于紧凑
plt.tight_layout(pad=2, h_pad=2, w_pad=2)
# 显示整个画布上的所有子图
plt.show()
绘制的图形如下图所示: