Skip to content

add_subplot vs subplots

Mental Model

fig.add_subplot() builds a grid one cell at a time -- useful when subplots are added conditionally or in a loop. plt.subplots() builds the entire grid in one call and returns all Axes at once -- simpler for fixed layouts.

Default choice: plt.subplots(). Switch to add_subplot only when the layout is built dynamically (e.g., number of panels determined at runtime) or when panels have irregular sizes that require GridSpec.

Overview

Matplotlib provides two main ways to create axes within a figure:

Method Style Returns
fig.add_subplot() OOP, incremental Single Axes
plt.subplots() Convenience function Figure + Axes array

plt.subplots()

Basic Usage

Creates figure and all axes at once:

```python import matplotlib.pyplot as plt

Single axes

fig, ax = plt.subplots() ax.plot([1, 2, 3])

Multiple axes (2 rows, 3 cols)

fig, axes = plt.subplots(2, 3, figsize=(12, 8)) ```

Return Value

```python

1x1: ax is single Axes object

fig, ax = plt.subplots()

1xN or Nx1: axes is 1D array

fig, axes = plt.subplots(1, 3) # shape: (3,) fig, axes = plt.subplots(3, 1) # shape: (3,)

NxM: axes is 2D array

fig, axes = plt.subplots(2, 3) # shape: (2, 3) ```

Accessing Axes

```python fig, axes = plt.subplots(2, 3)

2D indexing

axes[0, 0].plot(...) # Top-left axes[1, 2].plot(...) # Bottom-right

Flatten for iteration

for ax in axes.flat: ax.grid(True)

Unpack if known size

fig, (ax1, ax2) = plt.subplots(1, 2) fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2) ```

Common Parameters

python fig, axes = plt.subplots( nrows=2, ncols=3, figsize=(12, 8), sharex=True, # Share x-axis sharey=True, # Share y-axis squeeze=False, # Always return 2D array gridspec_kw={'hspace': 0.3, 'wspace': 0.2} )


fig.add_subplot()

Basic Usage

Adds one axes at a time to an existing figure:

```python fig = plt.figure(figsize=(12, 8))

Add subplot at position (row, col, index) - 1-indexed

ax1 = fig.add_subplot(2, 3, 1) # Row 1, Col 1 ax2 = fig.add_subplot(2, 3, 2) # Row 1, Col 2 ax3 = fig.add_subplot(2, 3, 4) # Row 2, Col 1 ```

Compact Notation

```python

Three-digit shorthand (only for grids < 10x10)

ax1 = fig.add_subplot(231) # Same as (2, 3, 1) ax2 = fig.add_subplot(232) # Same as (2, 3, 2) ```

Non-Uniform Grids

```python fig = plt.figure(figsize=(10, 8))

Large subplot spanning multiple positions

ax1 = fig.add_subplot(2, 2, 1) # Top-left quarter ax2 = fig.add_subplot(2, 2, 2) # Top-right quarter ax3 = fig.add_subplot(2, 1, 2) # Bottom half (spans both columns) ```

With GridSpec

```python from matplotlib.gridspec import GridSpec

fig = plt.figure(figsize=(10, 8)) gs = GridSpec(3, 3, figure=fig)

ax1 = fig.add_subplot(gs[0, :]) # Top row, all columns ax2 = fig.add_subplot(gs[1:, 0]) # Left column, rows 1-2 ax3 = fig.add_subplot(gs[1:, 1:]) # Bottom-right 2x2 ```


Comparison

When to Use plt.subplots()

Use when:

  • Creating regular grid of subplots
  • All subplots needed at once
  • Want shared axes
  • Simple layouts

```python

Perfect for regular grids

fig, axes = plt.subplots(2, 3) for ax, data in zip(axes.flat, datasets): ax.plot(data) ```

When to Use fig.add_subplot()

Use when:

  • Non-uniform subplot sizes
  • Adding subplots incrementally
  • Complex layouts with GridSpec
  • Mixing different subplot sizes

```python

Perfect for irregular layouts

fig = plt.figure() ax_main = fig.add_subplot(2, 2, (1, 2)) # Top half ax_left = fig.add_subplot(2, 2, 3) # Bottom-left ax_right = fig.add_subplot(2, 2, 4) # Bottom-right ```


Side-by-Side Examples

Regular 2x2 Grid

```python

plt.subplots (preferred)

fig, axes = plt.subplots(2, 2, figsize=(10, 8)) axes[0, 0].plot(x, y1) axes[0, 1].plot(x, y2) axes[1, 0].plot(x, y3) axes[1, 1].plot(x, y4)

fig.add_subplot

fig = plt.figure(figsize=(10, 8)) ax1 = fig.add_subplot(221) ax2 = fig.add_subplot(222) ax3 = fig.add_subplot(223) ax4 = fig.add_subplot(224) ax1.plot(x, y1) ax2.plot(x, y2) ax3.plot(x, y3) ax4.plot(x, y4) ```

Main Plot with Side Plots

```python

Using add_subplot with GridSpec

fig = plt.figure(figsize=(10, 8)) gs = GridSpec(2, 2, width_ratios=[3, 1], height_ratios=[1, 3])

ax_main = fig.add_subplot(gs[1, 0]) # Main scatter plot ax_top = fig.add_subplot(gs[0, 0]) # Top histogram ax_right = fig.add_subplot(gs[1, 1]) # Right histogram

ax_main.scatter(x, y) ax_top.hist(x, bins=30) ax_right.hist(y, bins=30, orientation='horizontal') ```


Common Patterns

Iterate Over Subplots

```python

With subplots

fig, axes = plt.subplots(2, 3) for ax, (title, data) in zip(axes.flat, datasets.items()): ax.plot(data) ax.set_title(title)

With add_subplot

fig = plt.figure() for i, (title, data) in enumerate(datasets.items(), 1): ax = fig.add_subplot(2, 3, i) ax.plot(data) ax.set_title(title) ```

Shared Axes

```python

Easy with subplots

fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)

Manual with add_subplot

fig = plt.figure() ax1 = fig.add_subplot(221) ax2 = fig.add_subplot(222, sharex=ax1, sharey=ax1) ax3 = fig.add_subplot(223, sharex=ax1, sharey=ax1) ax4 = fig.add_subplot(224, sharex=ax1, sharey=ax1) ```


Summary

Feature plt.subplots() fig.add_subplot()
Create multiple at once ✅ Yes ❌ One at a time
Regular grids ✅ Ideal Works
Irregular layouts Limited ✅ Ideal
Shared axes ✅ Easy (sharex, sharey) Manual
Return value Figure + Axes array Single Axes
GridSpec integration Via gridspec_kw ✅ Direct

Recommendation:

  • Start with plt.subplots() for most cases
  • Use fig.add_subplot() + GridSpec for complex layouts

Exercises

Exercise 1. Create a figure using fig.add_subplot() to add three subplots in a single row (1x3 layout). In each subplot, plot a different trigonometric function (sin, cos, tan) over \([0, 2\pi]\). Add titles to each subplot.

Solution to Exercise 1
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)

fig = plt.figure(figsize=(12, 4))

ax1 = fig.add_subplot(1, 3, 1)
ax1.plot(x, np.sin(x))
ax1.set_title('sin(x)')

ax2 = fig.add_subplot(1, 3, 2)
ax2.plot(x, np.cos(x))
ax2.set_title('cos(x)')

ax3 = fig.add_subplot(1, 3, 3)
ax3.plot(x, np.tan(x))
ax3.set_ylim(-5, 5)
ax3.set_title('tan(x)')

plt.tight_layout()
plt.show()

Exercise 2. Recreate the same 1x3 layout from Exercise 1 using plt.subplots(1, 3) instead. Compare how you access the axes objects versus the add_subplot approach. Add a shared y-label using fig.supylabel().

Solution to Exercise 2
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)

fig, axes = plt.subplots(1, 3, figsize=(12, 4))

axes[0].plot(x, np.sin(x))
axes[0].set_title('sin(x)')

axes[1].plot(x, np.cos(x))
axes[1].set_title('cos(x)')

axes[2].plot(x, np.tan(x))
axes[2].set_ylim(-5, 5)
axes[2].set_title('tan(x)')

fig.supylabel('y')
plt.tight_layout()
plt.show()

Exercise 3. Create a figure where you mix both approaches: use plt.subplots(2, 2) for a 2x2 grid, remove the bottom-right subplot with fig.delaxes(), and then use fig.add_subplot() to add a wide subplot spanning the entire bottom row using fig.add_subplot(2, 1, 2). Plot different data in each subplot.

Solution to Exercise 3
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)

fig, axes = plt.subplots(2, 2, figsize=(10, 8))

axes[0, 0].plot(x, np.sin(x), color='blue')
axes[0, 0].set_title('sin(x)')

axes[0, 1].plot(x, np.cos(x), color='red')
axes[0, 1].set_title('cos(x)')

axes[1, 0].plot(x, np.exp(-x), color='green')
axes[1, 0].set_title('exp(-x)')

fig.delaxes(axes[1, 1])

ax_bottom = fig.add_subplot(2, 1, 2)
ax_bottom.plot(x, np.sin(x) * np.cos(x), color='purple', linewidth=2)
ax_bottom.set_title('sin(x) * cos(x)')

plt.tight_layout()
plt.show()