Matplotlib 子图:使用 plt.subplots() 创建多面板图形
Updated on
单面板图形很少能满足实际分析需求。你需要并排比较分布、将散点图与其残差一起展示,或在仪表板布局中呈现四个指标。没有子图,你将创建单独的图形,它们在一起展示时会失去视觉关联。
Matplotlib 的 plt.subplots() 函数创建具有共享轴、一致大小和灵活布局的多面板图形。本指南涵盖了从基础网格到高级非对称布局的所有内容。
基础子图
单行
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
axes[0].plot(x, np.sin(x))
axes[0].set_title('正弦')
axes[1].plot(x, np.cos(x), color='orange')
axes[1].set_title('余弦')
axes[2].plot(x, np.tan(x), color='green')
axes[2].set_ylim(-5, 5)
axes[2].set_title('正切')
plt.tight_layout()
plt.show()网格布局
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
x = np.linspace(0, 10, 100)
axes[0, 0].plot(x, x, 'b-')
axes[0, 0].set_title('线性')
axes[0, 1].plot(x, x**2, 'r-')
axes[0, 1].set_title('二次')
axes[1, 0].plot(x, np.sqrt(x), 'g-')
axes[1, 0].set_title('平方根')
axes[1, 1].plot(x, np.log(x + 1), 'm-')
axes[1, 1].set_title('对数')
plt.tight_layout()
plt.show()共享轴
共享轴确保比较时的一致缩放:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
data1 = np.random.normal(0, 1, 1000)
data2 = np.random.normal(2, 1.5, 1000)
# 共享 X 轴
fig, axes = plt.subplots(2, 1, figsize=(8, 6), sharex=True)
axes[0].hist(data1, bins=30, color='steelblue', alpha=0.7)
axes[0].set_ylabel('计数')
axes[0].set_title('分布 A')
axes[1].hist(data2, bins=30, color='coral', alpha=0.7)
axes[1].set_ylabel('计数')
axes[1].set_xlabel('值')
axes[1].set_title('分布 B')
plt.tight_layout()
plt.show()import matplotlib.pyplot as plt
import numpy as np
# 同时共享 X 和 Y 轴
fig, axes = plt.subplots(2, 3, figsize=(12, 8), sharex=True, sharey=True)
for i, ax in enumerate(axes.flat):
data = np.random.randn(100)
ax.hist(data, bins=20, color=f'C{i}', alpha=0.7)
ax.set_title(f'样本 {i+1}')
plt.tight_layout()
plt.show()间距和布局控制
tight_layout()
自动调整间距以防止重叠:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
# ... 添加图表 ...
plt.tight_layout() # 自动修正间距
plt.show()subplots_adjust()
手动控制间距:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
plt.subplots_adjust(
left=0.1, # 左边距
right=0.95, # 右边距
top=0.92, # 上边距
bottom=0.08, # 下边距
wspace=0.3, # 子图间水平间距
hspace=0.4, # 子图间垂直间距
)
plt.show()添加总标题
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
fig.suptitle('三角函数', fontsize=16, fontweight='bold')
x = np.linspace(0, 2 * np.pi, 100)
for ax, func, name in zip(axes, [np.sin, np.cos, np.tan], ['sin', 'cos', 'tan']):
ax.plot(x, func(x))
ax.set_title(name)
plt.tight_layout()
plt.show()GridSpec 用于非对称布局
当你需要不同大小的面板时,使用 GridSpec:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
fig = plt.figure(figsize=(12, 8))
gs = gridspec.GridSpec(2, 3, figure=fig)
# 大的左侧面板(跨越2行)
ax1 = fig.add_subplot(gs[:, 0])
ax1.plot(np.random.randn(100).cumsum())
ax1.set_title('时间序列(2行)')
# 右上方面板
ax2 = fig.add_subplot(gs[0, 1])
ax2.bar(['A', 'B', 'C'], [3, 7, 5])
ax2.set_title('柱状图')
ax3 = fig.add_subplot(gs[0, 2])
ax3.scatter(np.random.randn(50), np.random.randn(50))
ax3.set_title('散点图')
# 下方宽面板(跨越2列)
ax4 = fig.add_subplot(gs[1, 1:])
ax4.hist(np.random.randn(500), bins=30, color='coral')
ax4.set_title('直方图(2列)')
plt.tight_layout()
plt.show()subplot2grid 用于基于位置的布局
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(12, 8))
# (行, 列), (行_起始, 列_起始), rowspan, colspan
ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=2)
ax2 = plt.subplot2grid((3, 3), (0, 2), rowspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0))
ax4 = plt.subplot2grid((3, 3), (1, 1))
ax5 = plt.subplot2grid((3, 3), (2, 0), colspan=3)
ax1.set_title('顶部宽')
ax2.set_title('右侧高')
ax3.set_title('中间左')
ax4.set_title('中间中')
ax5.set_title('底部全宽')
plt.tight_layout()
plt.show()遍历子图
import matplotlib.pyplot as plt
import numpy as np
# 展平 axes 数组以便于遍历
fig, axes = plt.subplots(2, 3, figsize=(12, 8))
datasets = [np.random.randn(100) for _ in range(6)]
colors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12', '#9b59b6', '#1abc9c']
for ax, data, color, i in zip(axes.flat, datasets, colors, range(6)):
ax.hist(data, bins=20, color=color, alpha=0.7)
ax.set_title(f'数据集 {i+1}')
ax.set_xlabel('值')
ax.set_ylabel('计数')
plt.tight_layout()
plt.show()移除空子图
当图表数量少于网格位置时:
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 3, figsize=(12, 8))
data_count = 5 # 6个位置只有5个数据集
for i, ax in enumerate(axes.flat):
if i < data_count:
ax.plot(np.random.randn(50).cumsum())
ax.set_title(f'图表 {i+1}')
else:
ax.set_visible(False) # 隐藏空子图
plt.tight_layout()
plt.show()交互式多面板探索
对于希望交互式比较同一数据集不同视图的快速视觉探索,PyGWalker (opens in a new tab) 让你在 Jupyter 中通过拖放列来创建仪表板——无需子图代码:
import pandas as pd
import pygwalker as pyg
df = pd.read_csv('your_data.csv')
walker = pyg.walk(df)FAQ
如何在 Matplotlib 中创建子图?
使用 fig, axes = plt.subplots(行数, 列数) 创建带有子图网格的图形。通过 axes[行, 列](网格)或 axes[i](单行/列)访问各个轴。最后始终调用 plt.tight_layout() 以防止标签重叠。
如何在子图间共享轴?
向 plt.subplots() 传递 sharex=True 或 sharey=True。例如,fig, axes = plt.subplots(2, 1, sharex=True) 使两个子图共享相同的 x 轴刻度。这在比较分布或时间序列时很有用。
如何创建不同大小的子图?
使用 matplotlib.gridspec.GridSpec 创建非对称布局。创建网格并使用切片表示法跨越多个单元格:ax = fig.add_subplot(gs[0, :2]) 创建一个跨越第一行前两列的子图。
如何调整子图间的间距?
调用 plt.tight_layout() 进行自动间距调整。手动控制使用 plt.subplots_adjust(wspace=0.3, hspace=0.4),其中 wspace 是水平间距,hspace 是垂直间距。
如何在所有子图上方添加标题?
使用 fig.suptitle('我的标题', fontsize=16) 在所有子图上方添加总标题。可能需要用 plt.subplots_adjust(top=0.92) 调整上边距或调用 plt.tight_layout() 防止重叠。
总结
Matplotlib 的 plt.subplots() 是多面板图形的基础。常规网格使用 (行数, 列数),一致缩放使用 sharex/sharey,自动间距使用 tight_layout(),非对称布局使用 GridSpec。快速迭代时,用 axes.flat 展平轴数组并循环。这些模式覆盖了绝大多数多面板可视化需求。