Skip to content

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=Truesharey=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 展平轴数组并循环。这些模式覆盖了绝大多数多面板可视化需求。

📚