Subplots and Layouts in Matplotlib

· 5 min read · Updated March 14, 2026 · intermediate
python matplotlib visualization data

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