Matplotlib中如何创建跨越多行多列的子图布局
参考:Make subplots span multiple grid rows and columns in Matplotlib
Matplotlib是Python中强大的数据可视化库,它提供了灵活的方式来创建复杂的图表布局。在本文中,我们将深入探讨如何在Matplotlib中创建跨越多行多列的子图布局。这种技术对于创建复杂的数据展示非常有用,可以让你在一个图形窗口中展示多个相关但又独立的图表,同时保持整体布局的美观和逻辑性。
1. 基础知识:理解Matplotlib的子图概念
在开始创建跨越多行多列的子图之前,我们需要先理解Matplotlib中的子图概念。子图是指在一个图形窗口中的独立绘图区域。通过使用子图,我们可以在同一个窗口中绘制多个图表。
以下是一个简单的创建子图的例子:
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot([1, 2, 3, 4], [1, 4, 2, 3])
ax1.set_title('Plot 1 - how2matplotlib.com')
ax2.plot([1, 2, 3, 4], [1, 2, 4, 3])
ax2.set_title('Plot 2 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们创建了一个包含两个并排的子图的图形。plt.subplots(1, 2)
创建了一行两列的子图布局。
2. 使用GridSpec创建复杂布局
要创建跨越多行多列的子图,我们需要使用Matplotlib的GridSpec功能。GridSpec允许我们更灵活地定义子图的位置和大小。
以下是一个使用GridSpec创建跨越多行多列子图的基本例子:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(12, 8))
gs = gridspec.GridSpec(3, 3)
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :-1])
ax3 = fig.add_subplot(gs[1:, -1])
ax4 = fig.add_subplot(gs[-1, 0])
ax5 = fig.add_subplot(gs[-1, -2])
ax1.set_title('Span all columns - how2matplotlib.com')
ax2.set_title('Span 2 columns - how2matplotlib.com')
ax3.set_title('Span 2 rows - how2matplotlib.com')
ax4.set_title('Single cell 1 - how2matplotlib.com')
ax5.set_title('Single cell 2 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们创建了一个3×3的网格,然后定义了5个子图,其中一些跨越了多个网格单元。
3. 使用add_gridspec方法
从Matplotlib 3.1版本开始,我们可以使用figure.add_gridspec()
方法来创建GridSpec对象。这提供了一种更简洁的语法来创建复杂的子图布局。
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(12, 8))
gs = fig.add_gridspec(3, 3)
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :-1])
ax3 = fig.add_subplot(gs[1:, -1])
ax4 = fig.add_subplot(gs[-1, 0])
ax5 = fig.add_subplot(gs[-1, -2])
ax1.set_title('Span all columns - how2matplotlib.com')
ax2.set_title('Span 2 columns - how2matplotlib.com')
ax3.set_title('Span 2 rows - how2matplotlib.com')
ax4.set_title('Single cell 1 - how2matplotlib.com')
ax5.set_title('Single cell 2 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
这个例子的结果与前一个例子相同,但语法更加简洁。
4. 调整子图之间的间距
在创建跨越多行多列的子图时,调整子图之间的间距对于优化布局非常重要。我们可以使用gridspec_kw
参数来调整这些间距。
import matplotlib.pyplot as plt
fig, axs = plt.subplots(3, 3, figsize=(12, 8),
gridspec_kw={'hspace': 0.4, 'wspace': 0.3})
axs[0, 0].set_title('Row 0, Col 0 - how2matplotlib.com')
axs[0, 1].set_title('Row 0, Col 1 - how2matplotlib.com')
axs[0, 2].set_title('Row 0, Col 2 - how2matplotlib.com')
axs[1, 0].set_title('Row 1, Col 0 - how2matplotlib.com')
axs[1, 1].set_title('Row 1, Col 1 - how2matplotlib.com')
axs[1, 2].set_title('Row 1, Col 2 - how2matplotlib.com')
axs[2, 0].set_title('Row 2, Col 0 - how2matplotlib.com')
axs[2, 1].set_title('Row 2, Col 1 - how2matplotlib.com')
axs[2, 2].set_title('Row 2, Col 2 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,hspace
控制子图之间的垂直间距,wspace
控制水平间距。
5. 创建不规则的网格布局
有时,我们可能需要创建更加不规则的网格布局。GridSpec允许我们定义具有不同宽度和高度的行和列。
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(12, 8))
gs = gridspec.GridSpec(3, 3, width_ratios=[1, 2, 1], height_ratios=[1, 2, 1])
ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, :-1])
ax3 = fig.add_subplot(gs[1:, -1])
ax4 = fig.add_subplot(gs[-1, 0])
ax5 = fig.add_subplot(gs[-1, -2])
ax1.set_title('Span all columns - how2matplotlib.com')
ax2.set_title('Span 2 columns - how2matplotlib.com')
ax3.set_title('Span 2 rows - how2matplotlib.com')
ax4.set_title('Single cell 1 - how2matplotlib.com')
ax5.set_title('Single cell 2 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们使用width_ratios
和height_ratios
参数来定义不同宽度和高度的列和行。
6. 嵌套GridSpec
对于更复杂的布局,我们可以使用嵌套的GridSpec。这允许我们在一个子图区域内创建另一个网格布局。
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(12, 8))
gs0 = gridspec.GridSpec(1, 2, figure=fig)
gs00 = gridspec.GridSpecFromSubplotSpec(2, 2, subplot_spec=gs0[0])
gs01 = gridspec.GridSpecFromSubplotSpec(3, 3, subplot_spec=gs0[1])
ax1 = fig.add_subplot(gs00[0, 0])
ax2 = fig.add_subplot(gs00[0, 1])
ax3 = fig.add_subplot(gs00[1, :])
ax4 = fig.add_subplot(gs01[:-1, :])
ax5 = fig.add_subplot(gs01[-1, :-1])
ax6 = fig.add_subplot(gs01[-1, -1])
ax1.set_title('Nested 1 - how2matplotlib.com')
ax2.set_title('Nested 2 - how2matplotlib.com')
ax3.set_title('Nested 3 - how2matplotlib.com')
ax4.set_title('Nested 4 - how2matplotlib.com')
ax5.set_title('Nested 5 - how2matplotlib.com')
ax6.set_title('Nested 6 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
这个例子展示了如何创建一个复杂的嵌套布局,其中左侧是一个2×2的网格,右侧是一个3×3的网格。
7. 使用subplot2grid创建跨越多行多列的子图
除了GridSpec,Matplotlib还提供了subplot2grid
函数,它可以用来创建跨越多行多列的子图。
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(12, 8))
ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=3)
ax2 = plt.subplot2grid((3, 3), (1, 0), colspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 2), rowspan=2)
ax4 = plt.subplot2grid((3, 3), (2, 0))
ax5 = plt.subplot2grid((3, 3), (2, 1))
ax1.set_title('Span all columns - how2matplotlib.com')
ax2.set_title('Span 2 columns - how2matplotlib.com')
ax3.set_title('Span 2 rows - how2matplotlib.com')
ax4.set_title('Single cell 1 - how2matplotlib.com')
ax5.set_title('Single cell 2 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,subplot2grid
函数的第一个参数指定网格的大小,第二个参数指定子图的起始位置,colspan
和rowspan
参数用于指定子图跨越的列数和行数。
8. 使用add_axes方法创建自定义位置的子图
对于需要更精确控制子图位置和大小的情况,我们可以使用fig.add_axes
方法。这个方法允许我们使用相对坐标来定位子图。
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(12, 8))
ax1 = fig.add_axes([0.1, 0.7, 0.8, 0.2])
ax2 = fig.add_axes([0.1, 0.3, 0.5, 0.3])
ax3 = fig.add_axes([0.7, 0.3, 0.2, 0.5])
ax4 = fig.add_axes([0.1, 0.1, 0.2, 0.1])
ax5 = fig.add_axes([0.4, 0.1, 0.2, 0.1])
ax1.set_title('Custom position 1 - how2matplotlib.com')
ax2.set_title('Custom position 2 - how2matplotlib.com')
ax3.set_title('Custom position 3 - how2matplotlib.com')
ax4.set_title('Custom position 4 - how2matplotlib.com')
ax5.set_title('Custom position 5 - how2matplotlib.com')
plt.show()
Output:
在这个例子中,add_axes
方法的参数是一个列表,包含四个值:[left, bottom, width, height],这些值都是相对于整个图形的比例。
9. 创建具有共享轴的子图
在某些情况下,我们可能希望创建共享x轴或y轴的子图。这可以通过sharex
和sharey
参数来实现。
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2*np.pi, 400)
y = np.sin(x**2)
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(8, 10), sharex=True)
ax1.plot(x, y)
ax1.set_title('Shared X-axis - how2matplotlib.com')
ax2.plot(x, y**2)
ax2.set_title('Shared X-axis - how2matplotlib.com')
ax3.plot(x, y**3)
ax3.set_title('Shared X-axis - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,三个子图共享同一个x轴,这意味着当你在一个子图上缩放或平移x轴时,其他子图的x轴也会相应地改变。
10. 创建具有不同比例的子图
有时,我们可能需要在同一个图形中创建具有不同比例的子图。这可以通过为每个子图单独设置aspect
参数来实现。
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(12, 8))
gs = fig.add_gridspec(2, 2)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, :])
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
ax1.plot(x, y)
ax1.set_title('Default aspect - how2matplotlib.com')
ax2.plot(x, y)
ax2.set_aspect('equal')
ax2.set_title('Equal aspect - how2matplotlib.com')
ax3.plot(x, y)
ax3.set_aspect(0.5)
ax3.set_title('Custom aspect - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们创建了三个子图,每个子图都有不同的纵横比。## 11. 使用constrained_layout自动调整布局
constrained_layout
是Matplotlib提供的一个自动布局调整功能,它可以自动处理子图之间的间距和标签重叠问题。
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(3, 3, figsize=(12, 10), constrained_layout=True)
for ax in axs.flat:
ax.plot(np.random.rand(50))
ax.set_title('Subplot - how2matplotlib.com')
ax.set_xlabel('X Label')
ax.set_ylabel('Y Label')
fig.suptitle('Constrained Layout Example - how2matplotlib.com', fontsize=16)
plt.show()
Output:
在这个例子中,我们创建了一个3×3的子图网格,并使用constrained_layout=True
来自动调整布局。这样可以确保所有的标签和标题都不会重叠。
12. 创建不规则形状的子图
有时,我们可能需要创建一些不规则形状的子图布局。这可以通过组合不同大小的子图来实现。
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(12, 8))
gs = gridspec.GridSpec(3, 3)
ax1 = fig.add_subplot(gs[0, :2])
ax2 = fig.add_subplot(gs[0, 2])
ax3 = fig.add_subplot(gs[1:, 0])
ax4 = fig.add_subplot(gs[1:, 1:])
ax1.set_title('Irregular shape 1 - how2matplotlib.com')
ax2.set_title('Irregular shape 2 - how2matplotlib.com')
ax3.set_title('Irregular shape 3 - how2matplotlib.com')
ax4.set_title('Irregular shape 4 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
这个例子创建了四个不同大小和形状的子图,展示了如何使用GridSpec来创建复杂的布局。
13. 在子图中嵌入子图
Matplotlib允许我们在一个子图中嵌入另一个子图,这对于创建图中图(inset)非常有用。
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(10, 8))
# 主图
x = np.linspace(0, 10, 100)
y = np.sin(x)
ax.plot(x, y)
ax.set_title('Main plot with inset - how2matplotlib.com')
# 嵌入的子图
axins = ax.inset_axes([0.6, 0.1, 0.3, 0.3])
axins.plot(x, y)
axins.set_xlim(4, 6)
axins.set_ylim(-1, 1)
axins.set_xticklabels([])
axins.set_yticklabels([])
ax.indicate_inset_zoom(axins)
plt.show()
Output:
这个例子在主图中创建了一个放大的区域,展示了如何使用inset_axes
方法来创建嵌入的子图。
14. 创建具有不同比例的子图组
有时我们可能需要创建一组子图,其中每个子图具有不同的大小。这可以通过调整GridSpec的参数来实现。
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(12, 8))
gs = gridspec.GridSpec(2, 2, width_ratios=[2, 1], height_ratios=[1, 2])
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1])
ax3 = fig.add_subplot(gs[1, 0])
ax4 = fig.add_subplot(gs[1, 1])
ax1.set_title('Larger width - how2matplotlib.com')
ax2.set_title('Smaller width - how2matplotlib.com')
ax3.set_title('Larger height - how2matplotlib.com')
ax4.set_title('Default size - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们使用width_ratios
和height_ratios
参数来创建具有不同大小的子图。
15. 创建圆形子图
虽然大多数子图都是矩形的,但Matplotlib也允许我们创建圆形的子图,这对于某些特殊的可视化需求可能很有用。
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(8, 8))
circle = plt.Circle((0.5, 0.5), 0.4, transform=ax.transAxes, facecolor='lightgrey', edgecolor='black')
ax.add_artist(circle)
theta = np.linspace(0, 2*np.pi, 100)
r = 0.4
x = 0.5 + r * np.cos(theta)
y = 0.5 + r * np.sin(theta)
ax.plot(x, y, transform=ax.transAxes)
ax.set_aspect('equal')
ax.axis('off')
ax.text(0.5, 0.5, 'Circular Subplot\nhow2matplotlib.com', ha='center', va='center', transform=ax.transAxes)
plt.show()
Output:
这个例子展示了如何创建一个圆形的子图区域,并在其中绘制内容。
16. 创建极坐标子图
对于某些类型的数据,使用极坐标系可能比笛卡尔坐标系更合适。Matplotlib允许我们轻松创建极坐标子图。
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(12, 5))
ax1 = fig.add_subplot(121, projection='polar')
ax2 = fig.add_subplot(122, projection='polar')
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
ax1.plot(theta, r)
ax1.set_title('Linear - how2matplotlib.com')
ax2.plot(theta, r**2)
ax2.set_title('Quadratic - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
这个例子创建了两个极坐标子图,展示了如何在极坐标系中绘制不同的函数。
17. 创建3D子图
Matplotlib还支持创建3D子图,这对于可视化三维数据非常有用。
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(figsize=(12, 5))
ax1 = fig.add_subplot(121, projection='3d')
ax2 = fig.add_subplot(122, projection='3d')
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z1 = np.sin(np.sqrt(X**2 + Y**2))
Z2 = np.cos(np.sqrt(X**2 + Y**2))
ax1.plot_surface(X, Y, Z1)
ax1.set_title('3D Surface 1 - how2matplotlib.com')
ax2.plot_surface(X, Y, Z2)
ax2.set_title('3D Surface 2 - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
这个例子创建了两个3D子图,展示了如何绘制三维表面图。
18. 创建具有共享颜色条的子图
当我们有多个子图需要使用相同的颜色映射时,创建一个共享的颜色条可以使图表更加简洁和易读。
import matplotlib.pyplot as plt
import numpy as np
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
data1 = np.random.rand(10, 10)
data2 = np.random.rand(10, 10)
im1 = ax1.imshow(data1, cmap='viridis')
ax1.set_title('Subplot 1 - how2matplotlib.com')
im2 = ax2.imshow(data2, cmap='viridis')
ax2.set_title('Subplot 2 - how2matplotlib.com')
fig.colorbar(im1, ax=[ax1, ax2], label='Shared Colorbar')
plt.tight_layout()
plt.show()
Output:
这个例子创建了两个子图,它们共享同一个颜色条,这对于比较两个数据集非常有用。
19. 创建具有不同坐标系的子图组合
Matplotlib允许我们在同一个图形中组合不同类型的坐标系,这对于展示不同类型的数据非常有用。
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(12, 8))
gs = fig.add_gridspec(2, 2)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1], projection='polar')
ax3 = fig.add_subplot(gs[1, 0], projection='3d')
ax4 = fig.add_subplot(gs[1, 1])
# 笛卡尔坐标系
x = np.linspace(0, 10, 100)
ax1.plot(x, np.sin(x))
ax1.set_title('Cartesian - how2matplotlib.com')
# 极坐标系
theta = np.linspace(0, 2*np.pi, 100)
ax2.plot(theta, np.sin(4*theta))
ax2.set_title('Polar - how2matplotlib.com')
# 3D坐标系
X, Y = np.meshgrid(range(10), range(10))
Z = np.sin(X) + np.cos(Y)
ax3.plot_surface(X, Y, Z)
ax3.set_title('3D - how2matplotlib.com')
# 对数坐标系
ax4.loglog(x, x**2)
ax4.set_title('Log-Log - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
这个例子展示了如何在一个图形中组合笛卡尔坐标系、极坐标系、3D坐标系和对数坐标系。
20. 创建动态子图布局
有时,我们可能需要根据数据的数量动态创建子图。以下是一个根据数据列表长度动态创建子图的例子:
import matplotlib.pyplot as plt
import numpy as np
def create_dynamic_subplots(data_list):
n = len(data_list)
cols = int(np.ceil(np.sqrt(n)))
rows = int(np.ceil(n / cols))
fig, axs = plt.subplots(rows, cols, figsize=(4*cols, 4*rows))
axs = axs.flatten() # 将多维数组转为一维
for i, data in enumerate(data_list):
axs[i].plot(data)
axs[i].set_title(f'Subplot {i+1} - how2matplotlib.com')
# 隐藏多余的子图
for j in range(i+1, len(axs)):
axs[j].axis('off')
plt.tight_layout()
plt.show()
# 示例使用
data_list = [np.random.rand(50) for _ in range(7)]
create_dynamic_subplots(data_list)
这个例子展示了如何根据数据列表的长度动态创建子图布局,并自动调整图形大小和布局。
总结:
在本文中,我们深入探讨了Matplotlib中创建跨越多行多列子图布局的各种方法和技巧。我们从基本的子图概念开始,逐步深入到更复杂的布局技术,包括使用GridSpec、subplot2grid、add_axes等方法。我们还介绍了如何创建共享轴的子图、不规则形状的子图、嵌套子图,以及如何处理不同类型的坐标系。
通过掌握这些技术,你将能够创建复杂而富有表现力的图表布局,更好地展示和比较多个数据集。无论是创建简单的并排图表,还是复杂的仪表板式布局,Matplotlib都提供了强大而灵活的工具来满足你的需求。
记住,创建有效的可视化不仅仅是about技术实现,还需要考虑数据的本质和你想要传达的信息。合理使用这些布局技术可以帮助你更清晰、更有效地展示你的数据和分析结果。