W3docs

Subgráficos de Matplotlib — Guía Completa

Aprende a crear figuras con múltiples paneles usando subplots de Matplotlib: plt.subplots(), GridSpec, ejes compartidos, tamaños y ejemplos de guardado.

Los subgráficos te permiten colocar múltiples gráficas dentro de una sola figura de Matplotlib. En lugar de crear ventanas separadas para cada gráfica, las organizas en una cuadrícula — una al lado de la otra, apiladas o en un diseño personalizado — para que el lector pueda comparar datos de un vistazo. Este capítulo cubre todo, desde una figura simple de dos paneles hasta diseños de cuadrícula flexibles con ejes compartidos y expansión por celda.

Antes de comenzar, instala Matplotlib si aún no lo has hecho:

pip install matplotlib numpy

Si eres nuevo en la biblioteca, lee primero los capítulos de Introducción a Matplotlib y Primeros pasos.

La Forma Más Rápida: plt.subplots()

plt.subplots(nrows, ncols) es el punto de entrada estándar. Devuelve un Figure y un array de objetos Axes.

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(10, 4))

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

axes[0].plot(x, np.sin(x), color='steelblue')
axes[0].set_title('Sine')
axes[0].set_xlabel('x')
axes[0].set_ylabel('sin(x)')

axes[1].plot(x, np.cos(x), color='darkorange')
axes[1].set_title('Cosine')
axes[1].set_xlabel('x')
axes[1].set_ylabel('cos(x)')

plt.tight_layout()
plt.show()

Puntos clave:

  • figsize=(width, height) establece el tamaño de la figura en pulgadas. El valor predeterminado es (6.4, 4.8), que a menudo es demasiado pequeño para múltiples paneles.
  • plt.tight_layout() ajusta automáticamente el espaciado para que los títulos y las etiquetas no se superpongan. Llámalo justo antes de show() o savefig().
  • Cuando ncols=1 y nrows=1 (el valor predeterminado), axes es un solo objeto Axes, no un array.

Creación de una Cuadrícula 2×2

Pasa tanto nrows como ncols para obtener un array NumPy bidimensional de Axes. Indexa con [row, col], donde ambos índices comienzan en cero.

import matplotlib.pyplot as plt
import numpy as np

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

fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(10, 8))

# Top-left
ax[0, 0].plot(x, np.sin(x), color='steelblue')
ax[0, 0].set_title('sin(x)')

# Top-right
ax[0, 1].plot(x, np.cos(x), color='darkorange')
ax[0, 1].set_title('cos(x)')

# Bottom-left  — clamp tan to avoid ±∞ spikes
y_tan = np.clip(np.tan(x), -10, 10)
ax[1, 0].plot(x, y_tan, color='seagreen')
ax[1, 0].set_title('tan(x) [clipped]')

# Bottom-right
ax[1, 1].plot(x, np.exp(x / np.pi), color='tomato')
ax[1, 1].set_title('exp(x/π)')

plt.suptitle('Trigonometric & Exponential Functions', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

plt.suptitle() agrega un título general a la figura por encima de todos los subgráficos. El ajuste y=1.02 lo desplaza para que quede alejado de los títulos del panel de la fila superior.

Iteración sobre los Ejes

Para una cuadrícula grande es más fácil iterar sobre el array de ejes aplanado que indexar cada celda manualmente:

import matplotlib.pyplot as plt
import numpy as np

functions = [np.sin, np.cos, np.tan, np.arctan]
names     = ['sin', 'cos', 'tan', 'arctan']
colors    = ['steelblue', 'darkorange', 'seagreen', 'tomato']

x = np.linspace(-np.pi, np.pi, 300)

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

for ax, fn, name, color in zip(axes.flat, functions, names, colors):
    y = fn(x)
    y = np.clip(y, -5, 5)          # keep tan from dominating the scale
    ax.plot(x, y, color=color)
    ax.set_title(name)
    ax.axhline(0, color='black', linewidth=0.6, linestyle='--')
    ax.axvline(0, color='black', linewidth=0.6, linestyle='--')

plt.tight_layout()
plt.show()

axes.flat devuelve un iterador unidimensional independientemente de la forma de la cuadrícula, por lo que el mismo bucle funciona para una cuadrícula 2×2, 3×3 o cualquier otro diseño.

Compartir Ejes

Cuando los paneles representan la misma magnitud (mismo rango en x o misma escala en y), vincula sus ejes para que al desplazar o hacer zoom en uno se actualicen todos:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 4 * np.pi, 400)

# sharex=True links horizontal axes; sharey=True links vertical axes
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(8, 6), sharex=True)

ax1.plot(x, np.sin(x), color='steelblue')
ax1.set_ylabel('sin(x)')
ax1.set_title('Shared x-axis example')

ax2.plot(x, np.sin(2 * x), color='darkorange')
ax2.set_ylabel('sin(2x)')
ax2.set_xlabel('x (radians)')

# Hide redundant x-tick labels on the top panel
ax1.tick_params(labelbottom=False)

plt.tight_layout()
plt.show()

sharex=True elimina las etiquetas duplicadas del eje x de los paneles superiores y mantiene todos los paneles alineados. Usa sharey=True de forma similar cuando la escala y debe coincidir entre columnas.

Desempaquetar Ejes Directamente

Cuando el diseño es pequeño y se conoce de antemano, puedes desempaquetar el array devuelto directamente en variables con nombre:

import matplotlib.pyplot as plt
import numpy as np

# 1 row, 3 columns → unpack into three named axes
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(13, 4))

x = np.linspace(0, 10, 300)

ax1.plot(x, x ** 0.5,  color='steelblue',  label='√x')
ax1.set_title('Square Root')
ax1.legend()

ax2.plot(x, np.log1p(x), color='darkorange', label='ln(1+x)')
ax2.set_title('Natural Log')
ax2.legend()

ax3.plot(x, x,           color='seagreen',  label='x')
ax3.set_title('Linear')
ax3.legend()

plt.tight_layout()
plt.show()

Esto es más limpio que axes[0], axes[1], axes[2] para diseños cortos. Para una cuadrícula 2-D con dos filas, anida el desempaquetado: (ax1, ax2), (ax3, ax4) = axes.

Diseños Personalizados con GridSpec

plt.subplots() solo crea cuadrículas donde cada celda tiene el mismo tamaño. Para diseños desiguales — un panel de resumen amplio en la parte superior con paneles de detalle debajo — usa matplotlib.gridspec.GridSpec:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np

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

fig = plt.figure(figsize=(10, 8))
gs  = gridspec.GridSpec(nrows=2, ncols=3, figure=fig)

# Top row: one wide panel spanning all three columns
ax_top = fig.add_subplot(gs[0, :])
ax_top.plot(x, np.sin(x), color='steelblue', linewidth=2)
ax_top.set_title('Overview — sin(x)')

# Bottom row: three equal panels
ax_bl = fig.add_subplot(gs[1, 0])
ax_bl.plot(x, np.sin(x),     color='steelblue')
ax_bl.set_title('sin(x)')

ax_bm = fig.add_subplot(gs[1, 1])
ax_bm.plot(x, np.cos(x),     color='darkorange')
ax_bm.set_title('cos(x)')

ax_br = fig.add_subplot(gs[1, 2])
ax_br.plot(x, np.sin(x) * np.cos(x), color='seagreen')
ax_br.set_title('sin(x)·cos(x)')

plt.tight_layout()
plt.show()

La sintaxis de segmentación gs[row, col] es similar a la segmentación de NumPy. gs[0, :] abarca las tres columnas; gs[1, 0] toma solo la primera celda de la fila 1.

Control de Alturas de Filas y Columnas

GridSpec acepta height_ratios y width_ratios para hacer algunas filas o columnas más grandes que otras:

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np

fig = plt.figure(figsize=(9, 7))
gs  = gridspec.GridSpec(
    2, 2,
    height_ratios=[3, 1],   # top row is 3× taller than bottom row
    width_ratios=[2, 1],    # left column is 2× wider than right column
    hspace=0.4,
    wspace=0.3,
)

x = np.linspace(0, 4 * np.pi, 400)

ax1 = fig.add_subplot(gs[0, 0])
ax1.plot(x, np.sin(x), color='steelblue')
ax1.set_title('Main (tall + wide)')

ax2 = fig.add_subplot(gs[0, 1])
ax2.plot(x, np.cos(x), color='darkorange')
ax2.set_title('Side (tall + narrow)')

ax3 = fig.add_subplot(gs[1, 0])
ax3.plot(x, np.sin(x) ** 2, color='seagreen')
ax3.set_title('Bottom-left (short + wide)')

ax4 = fig.add_subplot(gs[1, 1])
ax4.plot(x, np.cos(x) ** 2, color='tomato')
ax4.set_title('Bottom-right (short + narrow)')

plt.suptitle('Proportional Grid Layout', fontsize=13)
plt.show()

hspace y wspace controlan el espacio vertical y horizontal entre los subgráficos (como fracción de la altura/anchura promedio del subgráfico).

Establecer un Tamaño de Figura Común

El parámetro figsize siempre hace referencia a toda la figura, no a los subgráficos individuales. Una buena regla general:

Diseñofigsize recomendado
1 fila × 2 cols(10, 4)
1 fila × 3 cols(13, 4)
2 filas × 2 cols(10, 8)
3 filas × 3 cols(13, 11)

Siempre puedes ajustar según tus datos; estos son puntos de partida que dejan espacio para títulos y etiquetas.

Agregar Títulos y Etiquetas

Cada objeto Axes tiene su propio título y etiquetas de ejes. La figura en su conjunto puede tener un supertítulo:

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
x = np.linspace(0, 5, 100)

for ax, power, color in zip(axes, [2, 3], ['steelblue', 'darkorange']):
    ax.plot(x, x ** power, color=color)
    ax.set_title(f'$x^{power}$')      # LaTeX in title
    ax.set_xlabel('x')
    ax.set_ylabel(f'$x^{power}$')
    ax.grid(True, linestyle='--', alpha=0.5)

plt.suptitle('Power Functions', fontsize=14)
plt.tight_layout()
plt.show()

Guardar una Figura con Múltiples Paneles

Llama a plt.savefig() antes de plt.show() (llamar a show() primero borra la figura):

import matplotlib.pyplot as plt
import numpy as np

fig, axes = plt.subplots(1, 3, figsize=(13, 4))
x = np.linspace(0, 2 * np.pi, 300)

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

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

axes[2].plot(x, np.sin(x) + np.cos(x), color='seagreen')
axes[2].set_title('sin(x) + cos(x)')

plt.tight_layout()
plt.savefig('trig_panels.png', dpi=150, bbox_inches='tight')
plt.show()

bbox_inches='tight' recorta el espacio en blanco adicional alrededor de la figura — útil al insertar la imagen en un documento o página web.

Errores Comunes

Olvidar tight_layout()

Sin tight_layout(), los títulos de los subgráficos y las etiquetas de los ejes de paneles adyacentes suelen superponerse. Llámalo siempre antes de show() o savefig().

Dimensiones de índice incorrectas

plt.subplots(2, 3) devuelve un array 2-D; plt.subplots(1, 3) devuelve un array 1-D. Intentar usar axes[0, 0] en un array 1-D genera un IndexError. Usa axes[0] en su lugar, o pasa squeeze=False para obtener siempre un array 2-D:

fig, axes = plt.subplots(1, 3, squeeze=False)
# axes is now shape (1, 3) — always index with [row, col]
axes[0, 0].plot(...)
axes[0, 1].plot(...)
axes[0, 2].plot(...)

Recorte del supertítulo

plt.suptitle() puede ser recortado por tight_layout(). Corrígelo pasando un rect a tight_layout:

plt.tight_layout(rect=[0, 0, 1, 0.95])   # leave 5% at top for suptitle

O usa fig.subplots_adjust(top=0.90) en su lugar.

Referencia Rápida

TareaCódigo
Cuadrícula 1×2fig, (ax1, ax2) = plt.subplots(1, 2)
Cuadrícula 2×2fig, ax = plt.subplots(2, 2)
Acceder a celdaax[row, col]
Iterar todas las celdasfor ax in axes.flat:
Compartir eje xplt.subplots(2, 1, sharex=True)
Compartir eje yplt.subplots(1, 2, sharey=True)
Siempre array 2-Dplt.subplots(..., squeeze=False)
Diseño personalizadogs = GridSpec(rows, cols); fig.add_subplot(gs[r, c])
Abarcar columnasfig.add_subplot(gs[0, :])
Proporciones de alturaGridSpec(2, 1, height_ratios=[3, 1])
Título de figuraplt.suptitle('Title')
Guardar figuraplt.savefig('out.png', dpi=150, bbox_inches='tight')

Capítulos Relacionados

Was this page helpful?