Matplotlib中plt.subplots的全面指南:创建灵活的子图布局
参考:plt.subplots
plt.subplots是Matplotlib库中一个强大而灵活的函数,用于创建包含多个子图的图形布局。它允许用户轻松地在一个图形窗口中组织和排列多个图表,非常适合用于数据可视化和科学绘图。本文将深入探讨plt.subplots的各种用法、参数和技巧,帮助你充分利用这个功能来创建复杂而美观的图表布局。
1. plt.subplots的基本用法
plt.subplots函数的基本语法如下:
fig, ax = plt.subplots(nrows, ncols, figsize=(width, height))
其中:
– fig:返回的Figure对象,代表整个图形
– ax:返回的Axes对象或Axes对象的数组,代表子图
– nrows:子图的行数
– ncols:子图的列数
– figsize:整个图形的大小,以英寸为单位
让我们从一个简单的例子开始:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x), label='how2matplotlib.com')
ax.set_title('Simple Sine Wave')
ax.legend()
plt.show()
Output:
在这个例子中,我们创建了一个包含单个子图的图形。plt.subplots()不带参数时默认创建1×1的子图布局。我们使用返回的ax对象来绘制正弦波并设置标题和图例。
2. 创建多个子图
plt.subplots的真正威力在于它可以轻松创建多个子图。让我们看一个2×2布局的例子:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(10, 8))
x = np.linspace(0, 10, 100)
axs[0, 0].plot(x, np.sin(x), label='Sine')
axs[0, 0].set_title('Sine Wave')
axs[0, 1].plot(x, np.cos(x), label='Cosine')
axs[0, 1].set_title('Cosine Wave')
axs[1, 0].plot(x, np.tan(x), label='Tangent')
axs[1, 0].set_title('Tangent Wave')
axs[1, 1].plot(x, np.exp(x), label='Exponential')
axs[1, 1].set_title('Exponential Function')
for ax in axs.flat:
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend()
fig.suptitle('Trigonometric and Exponential Functions - how2matplotlib.com', fontsize=16)
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们创建了一个2×2的子图布局。axs是一个2D数组,我们可以通过索引访问每个子图。我们在每个子图中绘制不同的函数,并为每个子图设置标题、坐标轴标签和图例。最后,我们使用fig.suptitle()为整个图形添加一个总标题,并使用plt.tight_layout()自动调整子图之间的间距。
3. 调整子图布局
plt.subplots提供了多个参数来调整子图的布局:
3.1 figsize
figsize参数用于设置整个图形的大小,单位为英寸。例如:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 2*np.pi, 100)
for i in range(2):
for j in range(2):
axs[i, j].plot(x, np.sin(x + i*np.pi/2 + j*np.pi/4))
axs[i, j].set_title(f'Sine Wave {i*2+j+1} - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们创建了一个12×10英寸的图形,包含2×2的子图布局。
3.2 sharex 和 sharey
sharex 和 sharey 参数允许子图共享 x 轴或 y 轴。这在比较具有相同范围的多个图表时非常有用:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(10, 8), sharex=True, sharey=True)
x = np.linspace(0, 10, 100)
axs[0, 0].plot(x, x, label='Linear')
axs[0, 1].plot(x, x**2, label='Quadratic')
axs[1, 0].plot(x, x**3, label='Cubic')
axs[1, 1].plot(x, np.exp(x), label='Exponential')
for ax in axs.flat:
ax.legend()
ax.set_title('Function Plot - how2matplotlib.com')
fig.text(0.5, 0.04, 'x-axis', ha='center')
fig.text(0.04, 0.5, 'y-axis', va='center', rotation='vertical')
plt.tight_layout()
plt.show()
Output:
在这个例子中,所有子图共享相同的 x 轴和 y 轴范围。我们使用 fig.text() 来添加公共的 x 轴和 y 轴标签。
3.3 gridspec_kw
gridspec_kw 参数允许你更精细地控制子图的布局,例如调整子图之间的间距:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(10, 8),
gridspec_kw={'hspace': 0.3, 'wspace': 0.3})
x = np.linspace(0, 10, 100)
axs[0, 0].plot(x, np.sin(x), 'r-', label='Sine')
axs[0, 1].plot(x, np.cos(x), 'g-', label='Cosine')
axs[1, 0].plot(x, np.tan(x), 'b-', label='Tangent')
axs[1, 1].plot(x, np.exp(x), 'm-', label='Exponential')
for ax in axs.flat:
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend()
ax.set_title('Function Plot - how2matplotlib.com')
plt.show()
Output:
在这个例子中,我们使用 gridspec_kw 参数来增加子图之间的水平和垂直间距。
4. 创建不规则布局
plt.subplots 不仅可以创建规则的网格布局,还可以通过巧妙的使用来创建不规则的布局:
4.1 合并子图
我们可以使用 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, 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, 10, 100)
ax1.plot(x, np.sin(x), label='Sine')
ax2.plot(x, np.cos(x), label='Cosine')
ax3.plot(x, np.tan(x), label='Tangent')
for ax in [ax1, ax2, ax3]:
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend()
ax.set_title('Function Plot - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们创建了一个2×2的网格,但第三个子图跨越了底部的两个网格单元。
4.2 嵌套子图
我们还可以在一个子图内创建更多的子图:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 100)
axs[0, 0].plot(x, np.sin(x), label='Sine')
axs[0, 1].plot(x, np.cos(x), label='Cosine')
# 在右下角的子图中创建嵌套的子图
gs = axs[1, 1].get_gridspec()
axs[1, 0].remove()
axs[1, 1].remove()
axbig = fig.add_subplot(gs[1, :])
axbig.plot(x, np.tan(x), label='Tangent')
nested_gs = gs[1, :].subgridspec(2, 2)
ax1 = fig.add_subplot(nested_gs[0, 0])
ax2 = fig.add_subplot(nested_gs[0, 1])
ax3 = fig.add_subplot(nested_gs[1, 0])
ax4 = fig.add_subplot(nested_gs[1, 1])
for ax in [ax1, ax2, ax3, ax4]:
ax.plot(x, np.random.rand(100))
ax.set_title('Random Data - how2matplotlib.com')
for ax in [axs[0, 0], axs[0, 1], axbig]:
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend()
ax.set_title('Function Plot - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个复杂的例子中,我们创建了一个2×2的主布局,然后在右下角创建了一个跨越两个网格的大子图,并在其中嵌套了4个小子图。
5. 自定义子图样式
plt.subplots 创建的子图可以像普通的 Matplotlib 图表一样进行自定义:
5.1 设置坐标轴范围
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(10, 8))
x = np.linspace(0, 10, 100)
axs[0, 0].plot(x, np.sin(x))
axs[0, 0].set_xlim(0, 5)
axs[0, 0].set_ylim(-1, 1)
axs[0, 1].plot(x, np.cos(x))
axs[0, 1].set_xlim(5, 10)
axs[0, 1].set_ylim(-1, 1)
axs[1, 0].plot(x, np.tan(x))
axs[1, 0].set_xlim(0, 10)
axs[1, 0].set_ylim(-5, 5)
axs[1, 1].plot(x, np.exp(x))
axs[1, 1].set_yscale('log')
for ax in axs.flat:
ax.set_title('Custom Axis Range - how2matplotlib.com')
plt.tight_layout()
plt.show()
Output:
在这个例子中,我们为每个子图设置了不同的坐标轴范围,并为最后一个子图设置了对数刻度。
5.2 添加网格线和次要刻度
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 100)
for i, ax in enumerate(axs.flat):
ax.plot(x, np.sin(x + i*np.pi/4))
ax.set_title(f'Sine Wave {i+1} - how2matplotlib.com')
ax.grid(True, linestyle='--', alpha=0.7)
ax.minorticks_on()
ax.grid(which='minor', linestyle=':', alpha=0.4)
plt.tight_layout()
plt.show()
Output:
这个例子展示了如何为子图添加主网格线和次要网格线,以及如何启用次要刻度。
5.3 自定义颜色和样式
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 100)
styles = ['-', '--', '-.', ':']
colors = ['r', 'g', 'b', 'm']
for i, (ax, style, color) in enumerate(zip(axs.flat, styles, colors)):
ax.plot(x, np.sin(x + i*np.pi/4), linestyle=style, color=color, linewidth=2)
ax.set_title(f'Sine Wave {i+1} - how2matplotlib.com', color=color)
ax.set_facecolor(f'{color}0.1')
ax.tick_params(axis='both', colors=color)
for spine in ax.spines.values():
spine.set_edgecolor(color)
plt.tight_layout()
plt.show()
这个例子展示了如何为每个子图设置不同的线条样式、颜色,以及如何自定义标题、背景和坐标轴的颜色。
6. 处理大量子图
当需要创建大量子图时,可以使用循环来简化代码:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(4, 4, figsize=(15, 15))
x = np.linspace(0, 10, 100)
for i, ax in enumerate(axs.flat):
ax.plot(x, np.sin(x + i*np.pi/8))
ax.set_title(f'Sine Wave {i+1} - how2matplotlib.com')
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.tight_layout()
plt.show()
Output:
这个例子创建了一个4×4的子图网格,共16个子图,每个子图都绘制了一个稍微不同相位的正弦波。
7. 子图的动态调整
有时我们可能需要根据数据的数量动态调整子图的数量和布局:
import matplotlib.pyplot as plt
import numpy as np
def plot_data(data):
n = len(data)
rows = int(np.ceil(np.sqrt(n)))
cols = int(np.ceil(n / rows))
fig, axs = plt.subplots(rows, cols, figsize=(4*cols, 4*rows))
for i, (ax, d) in enumerate(zip(axs.flat, data)):
ax.plot(d)
ax.set_title(f'Dataset {i+1} - how2matplotlib.com')
for j in range(i+1, rows*cols):
fig.delaxes(axs.flat[j])
plt.tight_layout()
plt.show()
# 示例数据
data = [np.random.rand(100) for _ in range(7)]
plot_data(data)
这个函数可以根据输入数据的数量自动创建适当数量的子图,并删除多余的子图。
8. 子图中的多个图表
每个子图可以包含多个图表,这对于比较不同的数据集或展示相关的信息非常有用:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 100)
axs[0, 0].plot(x, np.sin(x), label='Sine')
axs[0, 0].plot(x, np.cos(x), label='Cosine')
axs[0, 0].set_title('Sine and Cosine - how2matplotlib.com')
axs[0, 0].legend()
axs[0, 1].plot(x, x, label='Linear')
axs[0, 1].plot(x, x**2, label='Quadratic')
axs[0, 1].plot(x, x**3, label='Cubic')
axs[0, 1].set_title('Polynomial Functions - how2matplotlib.com')
axs[0, 1].legend()
axs[1, 0].scatter(np.random.rand(50), np.random.rand(50), label='Dataset 1')
axs[1, 0].scatter(np.random.rand(50), np.random.rand(50), label='Dataset 2')
axs[1, 0].set_title('Scatter Plot - how2matplotlib.com')
axs[1, 0].legend()
axs[1, 1].bar(['A', 'B', 'C', 'D'], np.random.rand(4), label='Group 1')
axs[1, 1].bar(['A', 'B', 'C', 'D'], np.random.rand(4), bottom=np.random.rand(4), label='Group 2')
axs[1, 1].set_title('Stacked Bar Chart - how2matplotlib.com')
axs[1, 1].legend()
plt.tight_layout()
plt.show()
Output:
这个例子在每个子图中绘制了多个图表,包括线图、散点图和柱状图。
9. 子图的标题和总标题
为了使图表更具信息性,我们可以为每个子图添加标题,并为整个图形添加一个总标题:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 100)
axs[0, 0].plot(x, np.sin(x))
axs[0, 0].set_title('Sine Wave')
axs[0, 1].plot(x, np.cos(x))
axs[0, 1].set_title('Cosine Wave')
axs[1, 0].plot(x, np.tan(x))
axs[1, 0].set_title('Tangent Wave')
axs[1, 1].plot(x, np.exp(x))
axs[1, 1].set_title('Exponential Function')
for ax in axs.flat:
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.suptitle('Various Mathematical Functions - how2matplotlib.com', fontsize=16)
plt.tight_layout()
plt.subplots_adjust(top=0.92) # 为总标题留出空间
plt.show()
Output:
在这个例子中,我们为每个子图设置了标题,并使用fig.suptitle()为整个图形添加了一个总标题。注意我们使用plt.subplots_adjust()来为总标题留出空间。
10. 子图的注释和文本
我们可以在子图中添加注释和文本来突出重要信息:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 100)
axs[0, 0].plot(x, np.sin(x))
axs[0, 0].set_title('Sine Wave')
axs[0, 0].annotate('Peak', xy=(np.pi/2, 1), xytext=(np.pi/2, 0.5),
arrowprops=dict(facecolor='black', shrink=0.05))
axs[0, 1].plot(x, np.cos(x))
axs[0, 1].set_title('Cosine Wave')
axs[0, 1].text(5, 0, 'Zero Crossing', fontsize=12, ha='center')
axs[1, 0].plot(x, np.tan(x))
axs[1, 0].set_title('Tangent Wave')
axs[1, 0].set_ylim(-10, 10)
axs[1, 0].annotate('Asymptote', xy=(np.pi/2, 10), xytext=(2, 8),
arrowprops=dict(facecolor='red', shrink=0.05))
axs[1, 1].plot(x, np.exp(x))
axs[1, 1].set_title('Exponential Function')
axs[1, 1].text(5, np.exp(5)/2, 'Rapid Growth', fontsize=12, rotation=45)
for ax in axs.flat:
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.suptitle('Mathematical Functions with Annotations - how2matplotlib.com', fontsize=16)
plt.tight_layout()
plt.subplots_adjust(top=0.92)
plt.show()
Output:
这个例子展示了如何在子图中添加注释和文本,包括带箭头的注释和旋转的文本。
11. 子图的颜色映射
当处理多个相关的数据集时,使用颜色映射可以帮助我们更好地可视化数据:
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 20)
y = np.linspace(0, 10, 20)
X, Y = np.meshgrid(x, y)
Z1 = np.sin(X) * np.cos(Y)
Z2 = np.exp(-(X**2 + Y**2) / 10)
Z3 = np.sin(X*Y)
Z4 = np.cos(X*Y)
im1 = axs[0, 0].imshow(Z1, cmap='viridis', extent=[0, 10, 0, 10])
axs[0, 0].set_title('Sin(X) * Cos(Y)')
fig.colorbar(im1, ax=axs[0, 0])
im2 = axs[0, 1].imshow(Z2, cmap='plasma', extent=[0, 10, 0, 10])
axs[0, 1].set_title('Gaussian')
fig.colorbar(im2, ax=axs[0, 1])
im3 = axs[1, 0].imshow(Z3, cmap='coolwarm', extent=[0, 10, 0, 10])
axs[1, 0].set_title('Sin(X*Y)')
fig.colorbar(im3, ax=axs[1, 0])
im4 = axs[1, 1].imshow(Z4, cmap='seismic', extent=[0, 10, 0, 10])
axs[1, 1].set_title('Cos(X*Y)')
fig.colorbar(im4, ax=axs[1, 1])
for ax in axs.flat:
ax.set_xlabel('x')
ax.set_ylabel('y')
fig.suptitle('2D Functions with Color Maps - how2matplotlib.com', fontsize=16)
plt.tight_layout()
plt.subplots_adjust(top=0.92)
plt.show()
Output:
这个例子展示了如何在子图中使用不同的颜色映射来可视化2D函数。
12. 子图中的3D图形
plt.subplots 也可以用于创建3D子图:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
fig = plt.figure(figsize=(12, 10))
ax1 = fig.add_subplot(221, projection='3d')
ax2 = fig.add_subplot(222, projection='3d')
ax3 = fig.add_subplot(223, projection='3d')
ax4 = fig.add_subplot(224, projection='3d')
x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)
Z1 = np.sin(np.sqrt(X**2 + Y**2))
ax1.plot_surface(X, Y, Z1, cmap='viridis')
ax1.set_title('Ripple - how2matplotlib.com')
Z2 = X**2 - Y**2
ax2.plot_surface(X, Y, Z2, cmap='plasma')
ax2.set_title('Saddle - how2matplotlib.com')
Z3 = np.sin(X) * np.cos(Y)
ax3.plot_surface(X, Y, Z3, cmap='coolwarm')
ax3.set_title('Waves - how2matplotlib.com')
Z4 = np.exp(-(X**2 + Y**2))
ax4.plot_surface(X, Y, Z4, cmap='magma')
ax4.set_title('Gaussian - how2matplotlib.com')
for ax in [ax1, ax2, ax3, ax4]:
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.tight_layout()
plt.show()
Output:
这个例子创建了四个3D子图,每个子图都展示了不同的3D表面。
结论
plt.subplots 是 Matplotlib 中一个非常强大和灵活的函数,它允许我们轻松创建复杂的图形布局。通过本文的详细介绍和丰富的示例,我们探索了 plt.subplots 的各种用法,从基本的多子图创建到高级的布局调整和自定义。无论是创建简单的并排比较图,还是构建复杂的数据仪表板,plt.subplots 都能满足各种数据可视化需求。
掌握 plt.subplots 的使用将大大提高你的数据可视化能力,使你能够更有效地展示和分析数据。随着实践的增加,你将能够创建更加复杂和信息丰富的图表,为你的数据分析和科学研究提供有力的支持。