Skillversitas

Subplots and Figure Layouts

Most publication and dashboard figures are several charts in one image - a time series with its residuals, four model comparisons, a small-multiples grid. plt.subplots creates a grid of Axes in one call and returns the handles you configure individually; GridSpec extends the grid to merged cells and irregular layouts. This chapter is the vocabulary for arranging multiple charts in one Figure.

A Basic Grid

import numpy as np
import matplotlib.pyplot as plt

fig, axs = plt.subplots(2, 2, figsize=(8, 6))
x = np.linspace(0, 10, 100)

axs[0, 0].plot(x, np.sin(x))
axs[0, 0].set_title("sin")
axs[0, 1].plot(x, np.cos(x))
axs[0, 1].set_title("cos")
axs[1, 0].hist(np.random.normal(size=500), bins=30)
axs[1, 1].scatter(np.random.normal(size=200), np.random.normal(size=200))

fig.tight_layout()
plt.show()

axs is a 2D array of Axes, indexed [row, column]. With a single row or column, squeeze=True (the default) returns a 1D array or a bare Axes. Configure each subplot by handle - the OO pattern from the pyplot chapter is exactly what makes multi-chart figures tractable.

Shared Axes

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
series = [[1, 2, 1.5, 2.5, 3], [2, 1, 2, 1, 2], [3, 3, 2.5, 3.5, 3]]

fig, axs = plt.subplots(3, 1, figsize=(7, 7), sharex=True)
for ax, y in zip(axs, series):
    ax.plot(x, y, marker="o")
axs[-1].set_xlabel("day")      # only the bottom axis needs the label
fig.tight_layout()
plt.show()

sharex=True (or sharey) links the limits across rows or columns: zooming one updates all, and the hidden duplicate labels keep the figure clean. For small multiples of the same quantity, shared axes are almost always what you want.

Irregular Layouts with GridSpec

import matplotlib.pyplot as plt

fig = plt.figure(figsize=(9, 6))
gs = fig.add_gridspec(2, 2, hspace=0.35, wspace=0.35)

ax_main = fig.add_subplot(gs[0, :])        # top row, both columns
ax_a = fig.add_subplot(gs[1, 0])
ax_b = fig.add_subplot(gs[1, 1])

ax_main.set_title("main chart spans full width")
ax_a.set_title("detail A")
ax_b.set_title("detail B")
plt.show()

add_gridspec(rows, cols) defines the grid; add_subplot(gs[0, :]) places an Axes spanning a slice of cells. This is how you build the classic "big overview on top, two details below" figure. hspace/wspace set the gaps between cells; subplots_adjust can add outer margins for a figure-level title.

Fixing Overlap

  • fig.tight_layout() - the automatic fix for labels colliding with neighbors; call it before showing or saving.
  • fig.subplots_adjust(left=0.15, right=0.95, top=0.9, bottom=0.1) - manual margins when tight_layout fights a suptitle or colorbar.
  • Rotate x-tick labels on dense lower rows (ax.tick_params(axis="x", rotation=45)) instead of widening the figure.
  • Prefer a larger figure over a cramped one: figsize is your first layout tool.

Summary

Create grids with plt.subplots(rows, cols), configure each subplot through its Axes handle, use sharex/sharey for small multiples, and reach for GridSpec when a chart must span cells. End every multi-chart figure with tight_layout - or manual margins where it does not reach.