Skip to content

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.
📚