Matplotlib Subplots: Create Multi-Panel Figures with plt.subplots()
Updated on
Single-panel figures are rarely enough for real analysis. You need to compare distributions side by side, show a scatter plot next to its residuals, or present four metrics in a dashboard layout. Without subplots, you'd create separate figures that lose their visual relationship when presented together. Whether you are building a grid of histograms or combining a heatmap with bar charts, subplots are the foundation.
Matplotlib's plt.subplots() function creates multi-panel figures with shared axes, consistent sizing, and flexible layouts. This guide covers everything from basic grids to advanced asymmetric layouts.
Basic Subplots
Single Row
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('Sine')
axes[1].plot(x, np.cos(x), color='orange')
axes[1].set_title('Cosine')
axes[2].plot(x, np.tan(x), color='green')
axes[2].set_ylim(-5, 5)
axes[2].set_title('Tangent')
plt.tight_layout()
plt.show()Grid Layout
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('Linear')
axes[0, 1].plot(x, x**2, 'r-')
axes[0, 1].set_title('Quadratic')
axes[1, 0].plot(x, np.sqrt(x), 'g-')
axes[1, 0].set_title('Square Root')
axes[1, 1].plot(x, np.log(x + 1), 'm-')
axes[1, 1].set_title('Logarithmic')
plt.tight_layout()
plt.show()Shared Axes
Sharing axes ensures consistent scales for comparison. This is especially useful when displaying multiple histograms or seaborn boxplots across categories:
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)
# Share X axis
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('Count')
axes[0].set_title('Distribution A')
axes[1].hist(data2, bins=30, color='coral', alpha=0.7)
axes[1].set_ylabel('Count')
axes[1].set_xlabel('Value')
axes[1].set_title('Distribution B')
plt.tight_layout()
plt.show()import matplotlib.pyplot as plt
import numpy as np
# Share both X and Y axes
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'Sample {i+1}')
plt.tight_layout()
plt.show()Spacing and Layout Control
tight_layout()
Automatically adjusts spacing to prevent overlap:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
# ... add plots ...
plt.tight_layout() # Auto-fix spacing
plt.show()subplots_adjust()
Manual control over spacing:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
plt.subplots_adjust(
left=0.1, # Left margin
right=0.95, # Right margin
top=0.92, # Top margin
bottom=0.08, # Bottom margin
wspace=0.3, # Width space between subplots
hspace=0.4, # Height space between subplots
)
plt.show()Adding a Super Title
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
fig.suptitle('Trigonometric Functions', 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 for Asymmetric Layouts
When you need panels of different sizes, use 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)
# Large left panel (spans 2 rows)
ax1 = fig.add_subplot(gs[:, 0])
ax1.plot(np.random.randn(100).cumsum())
ax1.set_title('Time Series (2 rows)')
# Top right panels
ax2 = fig.add_subplot(gs[0, 1])
ax2.bar(['A', 'B', 'C'], [3, 7, 5])
ax2.set_title('Bar Chart')
ax3 = fig.add_subplot(gs[0, 2])
ax3.scatter(np.random.randn(50), np.random.randn(50))
ax3.set_title('Scatter')
# Bottom wide panel (spans 2 columns)
ax4 = fig.add_subplot(gs[1, 1:])
ax4.hist(np.random.randn(500), bins=30, color='coral')
ax4.set_title('Histogram (2 columns)')
plt.tight_layout()
plt.show()subplot2grid for Position-Based Layout
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(12, 8))
# (rows, cols), (row_start, col_start), 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('Top Wide')
ax2.set_title('Right Tall')
ax3.set_title('Middle Left')
ax4.set_title('Middle Center')
ax5.set_title('Bottom Full Width')
plt.tight_layout()
plt.show()Iterating Over Subplots
import matplotlib.pyplot as plt
import numpy as np
# Flatten axes array for easy iteration
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'Dataset {i+1}')
ax.set_xlabel('Value')
ax.set_ylabel('Count')
plt.tight_layout()
plt.show()Remove Empty Subplots
When you have fewer plots than grid positions:
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 3, figsize=(12, 8))
data_count = 5 # Only 5 datasets for 6 slots
for i, ax in enumerate(axes.flat):
if i < data_count:
ax.plot(np.random.randn(50).cumsum())
ax.set_title(f'Plot {i+1}')
else:
ax.set_visible(False) # Hide empty subplot
plt.tight_layout()
plt.show()Interactive Multi-Panel Exploration
For rapid visual exploration where you want to compare different views of the same dataset interactively, PyGWalker (opens in a new tab) lets you create dashboards by dragging and dropping columns in Jupyter -- no subplot code required:
import pandas as pd
import pygwalker as pyg
df = pd.read_csv('your_data.csv')
walker = pyg.walk(df)FAQ
How do I create subplots in Matplotlib?
Use fig, axes = plt.subplots(rows, cols) to create a figure with a grid of subplots. Access individual axes with axes[row, col] for grids or axes[i] for single rows/columns. Always call plt.tight_layout() at the end to prevent overlapping labels.
How do I share axes between subplots?
Pass sharex=True or sharey=True to plt.subplots(). For example, fig, axes = plt.subplots(2, 1, sharex=True) makes both subplots share the same x-axis scale. This is useful for comparing distributions or time series.
How do I create subplots of different sizes?
Use matplotlib.gridspec.GridSpec for asymmetric layouts. Create a grid and use slice notation to span multiple cells: ax = fig.add_subplot(gs[0, :2]) creates a subplot spanning the first two columns of the first row.
How do I adjust spacing between subplots?
Call plt.tight_layout() for automatic spacing. For manual control, use plt.subplots_adjust(wspace=0.3, hspace=0.4) where wspace is horizontal spacing and hspace is vertical spacing.
How do I add a title above all subplots?
Use fig.suptitle('My Title', fontsize=16) to add a super-title above all subplots. You may need to adjust the top margin with plt.subplots_adjust(top=0.92) or call plt.tight_layout() to prevent overlap.
Conclusion
Matplotlib's plt.subplots() is the foundation for multi-panel figures. Use it with (rows, cols) for regular grids, sharex/sharey for consistent scales, tight_layout() for automatic spacing, and GridSpec for asymmetric layouts. For quick iteration, flatten the axes array with axes.flat and loop. These patterns cover the vast majority of multi-panel visualization needs. When exporting your multi-panel figures, see the savefig guide to prevent labels from being clipped.
Related Guides
- Matplotlib Histogram -- create histogram grids using subplots for distribution comparison.
- Matplotlib Colormap -- choose the right color schemes for multi-panel visualizations.
- Matplotlib savefig -- export multi-panel figures without clipping labels or titles.
- Seaborn Heatmap -- combine heatmaps with subplots for correlation dashboards.
- Seaborn Pairplot -- seaborn's built-in multi-panel scatter plot grid as an alternative to manual subplot layouts.