Subplots and Layouts in Matplotlib
Creating multiple plots side by side or in a grid is a common need when visualizing data. Matplotlib provides several ways to arrange multiple subplots within a single figure. This guide covers the main approaches: the subplots function, GridSpec for flexible layouts, and the modern constrained_layout and layout_engine systems.
Installing Matplotlib
If you have not installed Matplotlib yet:
pip install matplotlib
Import it alongside NumPy for sample data:
import matplotlib.pyplot as plt
import numpy as np
The subplots Function
The quickest way to create a grid of subplots is with plt.subplots:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, axes = plt.subplots(2, 2) # 2 rows, 2 columns
# axes is a 2x2 numpy array of Axes objects
axes[0, 0].plot(x, np.sin(x))
axes[0, 0].set_title("Sine")
axes[0, 1].plot(x, np.cos(x))
axes[0, 1].set_title("Cosine")
axes[1, 0].plot(x, np.tan(x))
axes[1, 0].set_ylim(-5, 5)
axes[1, 0].set_title("Tangent")
axes[1, 1].plot(x, x**2)
axes[1, 1].set_title("Square")
plt.tight_layout()
plt.show()
Key Parameters
- nrows and ncols: Number of rows and columns
- sharex and sharey: Share axis labels and limits
- squeeze: If True, returns a single Axes object for 1x1 grids
- width_ratios and height_ratios: Control relative sizes
# Different sized subplots
fig, axes = plt.subplots(2, 2, height_ratios=[1, 3], width_ratios=[1, 2])
axes[0, 0].plot(x, np.sin(x))
axes[1, 0].plot(x, np.sin(x*2))
axes[0, 1].plot(x, np.cos(x))
axes[1, 1].plot(x, np.cos(x*2))
plt.tight_layout()
plt.show()
Sharing Axes
When plots share the same data range, sharing axes reduces redundancy:
fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)
for i, ax in enumerate(axes.flat):
ax.plot(x, np.sin(x * (i + 1)))
ax.set_title(f"Frequency i+1")
plt.tight_layout()
plt.show()
The subplot2grid Approach
For more flexible placement, subplot2grid lets you create irregular grids:
fig = plt.figure(figsize=(10, 6))
# Main plot spanning two columns
ax1 = plt.subplot2grid((2, 3), (0, 0), colspan=2)
ax1.plot(x, np.sin(x))
ax1.set_title("Main Plot")
# Side plot
ax2 = plt.subplot2grid((2, 3), (0, 2))
ax2.plot(x, np.cos(x))
ax2.set_title("Side Plot")
# Bottom plot spanning all columns
ax3 = plt.subplot2grid((2, 3), (1, 0), colspan=3)
ax3.plot(x, np.tan(x))
ax3.set_ylim(-5, 5)
ax3.set_title("Bottom Plot")
plt.tight_layout()
plt.show()
This approach is useful when you need a mosaic-like layout where certain plots span multiple rows or columns.
GridSpec for Fine-Grained Control
GridSpec provides the most control over your layout:
import matplotlib.gridspec as gridspec
fig = plt.figure(figsize=(10, 6))
gs = gridspec.GridSpec(3, 3)
# Different configurations
ax1 = fig.add_subplot(gs[0, :]) # Top row, all columns
ax2 = fig.add_subplot(gs[1, 0]) # Middle left
ax3 = fig.add_subplot(gs[1, 1:]) # Middle right (spans columns 1 and 2)
ax4 = fig.add_subplot(gs[2, :]) # Bottom row, all columns
ax1.plot(x, np.sin(x))
ax1.set_title("Top - Full Width")
ax2.plot(x, np.cos(x))
ax2.set_title("Middle Left")
ax3.plot(x, np.tan(x))
ax3.set_title("Middle Right")
ax3.set_ylim(-5, 5)
ax4.bar(["a", "b", "c"], [3, 7, 5])
ax4.set_title("Bottom - Full Width")
plt.tight_layout()
plt.show()
GridSpec with Subplots
You can also use subplots with Gridspec for a cleaner interface:
fig, axes = plt.subplots(2, 2, gridspec_kw={"width_ratios": [1, 2], "height_ratios": [1, 3]})
axes[0, 0].plot(x, np.sin(x))
axes[0, 1].plot(x, np.cos(x))
axes[1, 0].plot(x, np.tan(x))
axes[1, 0].set_ylim(-5, 5)
axes[1, 1].plot(x, x**2)
plt.tight_layout()
plt.show()
Modern Layout Engines
Matplotlib 3.4+ includes improved layout engines that handle spacing automatically.
Constrained Layout
The constrained_layout option automatically adjusts spacing to prevent overlap:
fig, axes = plt.subplots(2, 2, constrained_layout=True)
for i, ax in enumerate(axes.flat):
ax.plot(x, np.sin(x * (i + 1)))
ax.set_title(f"Plot i+1")
ax.set_xlabel("X axis")
ax.set_ylabel("Y axis")
plt.show()
GridSpec with SubFigure
For complex dashboards, SubFigure lets you create independent figure regions:
fig = plt.figure(figsize=(12, 8))
# Create subfigures
subfigs = fig.subfigures(1, 2, width_ratios=[2, 1])
# Left side - main content
ax1 = subfigs[0].subplots(2, 1)
subfigs[0].suptitle("Main Content")
ax1[0].plot(x, np.sin(x))
ax1[0].set_title("Sine Wave")
ax1[1].plot(x, np.cos(x))
ax1[1].set_title("Cosine Wave")
# Right side - sidebar
ax2 = subfigs[1].subplots(3, 1)
subfigs[1].suptitle("Sidebar")
ax2[0].bar(["a", "b", "c"], [3, 5, 2])
ax2[1].bar(["x", "y", "z"], [2, 7, 4])
ax2[2].bar(["1", "2", "3"], [6, 1, 3])
plt.show()
Working with Different Sizes
Sometimes you need subplots of different sizes in the same figure:
fig = plt.figure(figsize=(12, 8))
gs = fig.add_gridspec(3, 3)
# Large plot on the left
ax_large = fig.add_subplot(gs[:, 0:2])
ax_large.plot(x, np.sin(x))
ax_large.set_title("Large Main Plot")
# Small plots stacked on the right
ax_top = fig.add_subplot(gs[0, 2])
ax_top.plot(x, np.cos(x))
ax_top.set_title("Cosine")
ax_mid = fig.add_subplot(gs[1, 2])
ax_mid.plot(x, np.tan(x))
ax_mid.set_ylim(-5, 5)
ax_mid.set_title("Tangent")
ax_bot = fig.add_subplot(gs[2, 2])
ax_bot.bar([1, 2, 3], [3, 7, 5])
ax_bot.set_title("Bar Chart")
plt.tight_layout()
plt.show()
Adding Colorbars
Colorbars need special handling with subplots:
fig, axes = plt.subplots(1, 2, constrained_layout=True)
# Heatmap-style data
data = np.random.rand(10, 10)
im1 = axes[0].imshow(data, cmap="viridis")
axes[0].set_title("With Colorbar")
im2 = axes[1].imshow(data, cmap="plasma")
axes[1].set_title("Without Colorbar")
# Add colorbar to the right of each subplot
fig.colorbar(im1, ax=axes[0])
fig.colorbar(im2, ax=axes[1])
plt.show()
Common Issues and Solutions
Overlapping Labels
The tight_layout function usually fixes overlapping labels:
plt.tight_layout() # Call before plt.show()
Or use constrained_layout=True when creating the figure.
Different Axis Scales
When subplots have very different scales, consider using sharex and sharey or creating separate figures.
Saving Figures with Subplots
When saving, specify the bounding box to avoid cutting off labels:
fig, axes = plt.subplots(2, 2)
# ... add plots ...
plt.savefig("subplots.png", bbox_inches="tight")
plt.savefig("subplots.pdf", bbox_inches="tight")
Getting Started
Matplotlib offers multiple ways to arrange subplots, each with different trade-offs:
- plt.subplots: Quick and easy for regular grids
- subplot2grid: Flexible placement for irregular layouts
- GridSpec: Fine-grained control over cell sizes
- constrained_layout: Automatic spacing that just works
Start with subplots and constrained_layout=True for most cases. Reach for GridSpec when you need custom ratios or irregular arrangements.
From here, explore Matplotlibs tutorials for basics, or dive into pandas DataFrames for data visualization workflows.
See Also
- Getting Started with Matplotlib — Installation and first steps
- pandas DataFrames Explained — Visualizing data with pandas
- Getting Started with pandas — Data analysis foundation