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 numpySi 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 deshow()osavefig().- Cuando
ncols=1ynrows=1(el valor predeterminado),axeses un solo objetoAxes, 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ño | figsize 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 suptitleO usa fig.subplots_adjust(top=0.90) en su lugar.
Referencia Rápida
| Tarea | Código |
|---|---|
| Cuadrícula 1×2 | fig, (ax1, ax2) = plt.subplots(1, 2) |
| Cuadrícula 2×2 | fig, ax = plt.subplots(2, 2) |
| Acceder a celda | ax[row, col] |
| Iterar todas las celdas | for ax in axes.flat: |
| Compartir eje x | plt.subplots(2, 1, sharex=True) |
| Compartir eje y | plt.subplots(1, 2, sharey=True) |
| Siempre array 2-D | plt.subplots(..., squeeze=False) |
| Diseño personalizado | gs = GridSpec(rows, cols); fig.add_subplot(gs[r, c]) |
| Abarcar columnas | fig.add_subplot(gs[0, :]) |
| Proporciones de altura | GridSpec(2, 1, height_ratios=[3, 1]) |
| Título de figura | plt.suptitle('Title') |
| Guardar figura | plt.savefig('out.png', dpi=150, bbox_inches='tight') |
Capítulos Relacionados
- Introducción a Matplotlib — resumen de la biblioteca e instalación
- Primeros pasos con Matplotlib — tu primera gráfica
- Gráficas de líneas en Matplotlib — representar datos continuos
- Etiquetas en Matplotlib — etiquetas de ejes, títulos y anotaciones
- Cuadrícula en Matplotlib — agregar y estilizar líneas de cuadrícula
- Gráficas de barras en Matplotlib — comparar categorías
- Gráficas de dispersión en Matplotlib — relaciones entre variables
- Histogramas en Matplotlib — distribuciones de datos