Matplotlib 绘制多图:从单图到复杂布局的完整指南
在数据可视化领域,Matplotlib 是 Python 中最主流的绘图库之一。它功能强大、灵活多变,尤其适合科研人员、数据分析师和开发者进行图表创作。然而,很多初学者在使用 Matplotlib 时,常常只局限于绘制单个图形,而忽略了“多图并列”这一高效展示对比信息的利器。
想象一下,你要向团队汇报两个城市的月度气温变化趋势。如果把两个城市的数据分别画在两个独立的图上,不仅浪费空间,还难以直接对比。但如果你能将两个图并排展示,一眼就能看出差异。这就是 Matplotlib 绘制多图的魅力所在。
本文将带你从零开始,系统掌握 Matplotlib 绘制多图的核心技巧,包括子图布局、共享坐标轴、自定义样式等实用技能。无论你是刚接触 Python 的新手,还是有一定经验的中级开发者,都能从中获得实用价值。
理解子图(Subplots):多图的基础单元
在 Matplotlib 中,一个完整的绘图窗口可以包含多个子图(Subplots)。每个子图可以看作是独立的“画布区域”,但又共同存在于同一个窗口中。这种设计让你可以灵活地组织信息,实现复杂的数据对比。
我们可以用 plt.subplots() 函数来创建多个子图。它的基本语法是:
fig, axes = plt.subplots(nrows=2, ncols=2)
这句代码会创建一个 2×2 的子图网格,总共 4 个子图。fig 表示整个图形窗口,而 axes 是一个二维数组,包含了这 4 个子图对象。
📌 小贴士:把
fig想象成一张大画布,axes就是画布上的 4 个分格区域,每个区域都可以独立绘图。
下面是一个完整的示例:
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(10, 8))
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.sin(x) * np.exp(-x / 5)
y4 = np.cos(x) * np.exp(-x / 5)
axes[0, 0].plot(x, y1, color='blue', label='sin(x)')
axes[0, 0].set_title('正弦函数')
axes[0, 0].set_xlabel('x')
axes[0, 0].set_ylabel('y')
axes[0, 0].legend()
axes[0, 1].plot(x, y2, color='red', label='cos(x)')
axes[0, 1].set_title('余弦函数')
axes[0, 1].set_xlabel('x')
axes[0, 1].set_ylabel('y')
axes[0, 1].legend()
axes[1, 0].plot(x, y3, color='green', label='sin(x)·e^(-x/5)')
axes[1, 0].set_title('衰减正弦')
axes[1, 0].set_xlabel('x')
axes[1, 0].set_ylabel('y')
axes[1, 0].legend()
axes[1, 1].plot(x, y4, color='purple', label='cos(x)·e^(-x/5)')
axes[1, 1].set_title('衰减余弦')
axes[1, 1].set_xlabel('x')
axes[1, 1].set_ylabel('y')
axes[1, 1].legend()
plt.tight_layout()
plt.show()
这段代码展示了如何在一个图形窗口中绘制 4 个不同函数的图像。axes[0, 0] 表示第一行第一列的子图,以此类推。通过这种方式,你可以精确控制每个子图的位置和内容。
布局方式对比:subplots() vs subplot()
Matplotlib 提供了两种创建多图的方式:subplots() 和 subplot()。虽然功能相似,但使用场景和灵活性有明显差异。
subplots() 是推荐的方式,因为它一次创建所有子图,并返回一个 fig 和 axes 数组,便于批量操作。而 subplot() 是逐个添加子图的方式,适合动态构建。
下面用 subplot() 实现同样的 2×2 布局:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10, 8))
ax1 = fig.add_subplot(2, 2, 1)
ax1.plot(np.linspace(0, 10, 100), np.sin(np.linspace(0, 10, 100)), 'b-')
ax1.set_title('正弦函数')
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax2 = fig.add_subplot(2, 2, 2)
ax2.plot(np.linspace(0, 10, 100), np.cos(np.linspace(0, 10, 100)), 'r-')
ax2.set_title('余弦函数')
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax3 = fig.add_subplot(2, 2, 3)
ax3.plot(np.linspace(0, 10, 100), np.sin(np.linspace(0, 10, 100)) * np.exp(-np.linspace(0, 10, 100)/5), 'g-')
ax3.set_title('衰减正弦')
ax3.set_xlabel('x')
ax3.set_ylabel('y')
ax4 = fig.add_subplot(2, 2, 4)
ax4.plot(np.linspace(0, 10, 100), np.cos(np.linspace(0, 10, 100)) * np.exp(-np.linspace(0, 10, 100)/5), 'm-')
ax4.set_title('衰减余弦')
ax4.set_xlabel('x')
ax4.set_ylabel('y')
plt.tight_layout()
plt.show()
虽然 subplot() 也能实现多图,但代码更冗长,且容易出错。因此,强烈建议在需要多个子图时使用 subplots()。
多图布局的灵活控制:行、列、间距
在实际项目中,你可能需要非对称布局,比如两个图上下排列,第三个图占满一行。Matplotlib 提供了 gridspec 模块,让你可以更精细地控制布局。
例如,下面的代码创建了一个 3 行 1 列的布局,但第二行的子图占据两列宽度:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
fig = plt.figure(figsize=(10, 8))
gs = gridspec.GridSpec(3, 2, hspace=0.4, wspace=0.3)
ax1 = fig.add_subplot(gs[0, :])
ax1.plot(np.linspace(0, 10, 100), np.sin(np.linspace(0, 10, 100)), 'b-')
ax1.set_title('完整宽度图')
ax1.set_ylabel('y')
ax1.grid(True)
ax2 = fig.add_subplot(gs[1, 0])
ax2.plot(np.linspace(0, 5, 50), np.cos(np.linspace(0, 5, 50)), 'r-')
ax2.set_title('左半图')
ax2.set_ylabel('y')
ax3 = fig.add_subplot(gs[1, 1])
ax3.plot(np.linspace(0, 5, 50), np.sin(np.linspace(0, 5, 50)), 'g-')
ax3.set_title('右半图')
ax3.set_ylabel('y')
ax4 = fig.add_subplot(gs[2, :])
ax4.plot(np.linspace(0, 10, 100), np.random.randn(100).cumsum(), 'm-')
ax4.set_title('底部大图')
ax4.set_ylabel('y')
plt.show()
这个例子展示了 gridspec 的强大之处:你可以像搭积木一样自由组合子图,实现复杂布局。
共享坐标轴:提升对比效率
当多个子图展示的是同一类数据(如不同城市的温度变化)时,共享坐标轴可以显著提升可读性。用户无需反复查看刻度,就能直接比较趋势。
Matplotlib 支持共享 x 轴或 y 轴,甚至同时共享两者。
import matplotlib.pyplot as plt
import numpy as np
months = np.arange(1, 13)
temp_city1 = np.random.normal(18, 5, 12) # 城市 A
temp_city2 = np.random.normal(22, 6, 12) # 城市 B
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
ax1.plot(months, temp_city1, marker='o', color='blue', label='城市 A')
ax1.set_ylabel('气温 (°C)')
ax1.set_title('两个城市的月度气温对比')
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2.plot(months, temp_city2, marker='s', color='red', label='城市 B')
ax2.set_xlabel('月份')
ax2.set_ylabel('气温 (°C)')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.xlabel('月份')
plt.tight_layout()
plt.show()
通过 sharex=True,两个子图的 x 轴刻度完全一致,用户可以轻松比较每个月的气温差异。
实战案例:绘制销售数据对比图
让我们通过一个真实场景来巩固所学知识。假设你是一家电商公司的数据分析师,需要对比 A、B 两个产品的月销售额。
import matplotlib.pyplot as plt
import numpy as np
months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
sales_A = [12.5, 14.2, 13.8, 16.1, 18.3, 20.5, 22.0, 21.8, 23.2, 24.1, 26.3, 28.0]
sales_B = [10.8, 11.5, 12.3, 13.0, 14.2, 15.6, 17.1, 18.2, 20.0, 22.1, 23.8, 25.4]
fig, (ax1, ax2, ax3, ax4) = plt.subplots(2, 2, figsize=(12, 8))
ax1.plot(months, sales_A, marker='o', label='产品 A', color='blue')
ax1.plot(months, sales_B, marker='s', label='产品 B', color='red')
ax1.set_title('销售额趋势对比')
ax1.set_ylabel('销售额 (万元)')
ax1.legend()
ax1.grid(True, alpha=0.3)
x_pos = np.arange(len(months))
ax2.bar(x_pos - 0.2, sales_A, width=0.4, label='产品 A', color='skyblue')
ax2.bar(x_pos + 0.2, sales_B, width=0.4, label='产品 B', color='salmon')
ax2.set_title('月度销售柱状图')
ax2.set_ylabel('销售额 (万元)')
ax2.set_xticks(x_pos)
ax2.set_xticklabels(months, rotation=45)
ax2.legend()
ax3.fill_between(months, sales_A, alpha=0.5, color='blue', label='产品 A')
ax3.fill_between(months, sales_B, alpha=0.5, color='red', label='产品 B')
ax3.set_title('累计销售面积图')
ax3.set_ylabel('销售额 (万元)')
ax3.legend()
ax3.grid(True, alpha=0.3)
ax4.scatter(sales_A, sales_B, alpha=0.7, color='purple')
ax4.plot([min(sales_A), max(sales_A)], [min(sales_A), max(sales_A)], 'k--', alpha=0.5)
ax4.set_xlabel('产品 A 销售额')
ax4.set_ylabel('产品 B 销售额')
ax4.set_title('销售额相关性散点图')
plt.tight_layout()
plt.show()
这个案例涵盖了折线图、柱状图、面积图和散点图,展示了 Matplotlib 绘制多图在实际业务场景中的强大能力。
总结与建议
Matplotlib 绘制多图是数据可视化中的核心技能。通过掌握 subplots()、gridspec、共享坐标轴等技术,你可以轻松应对从简单对比到复杂分析的各种需求。
- 初学者应从
subplots(nrows, ncols)入手,建立子图概念; - 中级开发者可尝试
gridspec实现复杂布局; - 业务场景中,合理使用共享坐标轴能极大提升图表可读性;
- 保持代码整洁,每个子图都应有清晰的标题和标签。
当你能熟练运用这些技巧时,你的数据报告将不再只是静态图表,而是一组有逻辑、有对比、有洞察的视觉叙事。这正是 Matplotlib 绘制多图的真正价值所在。