Plotting: matplotlib + seaborn esenciales

python
pandas
eda
El stack de visualización de Python. matplotlib como motor, seaborn para gráficos estadísticos elegantes, plot() directo desde pandas y los patrones que separan un gráfico de exploración de uno publicable.

El stack: matplotlib, seaborn, plot()

Visualizar en Python tiene tres niveles de abstracción que conviven:

  • matplotlib: la librería base. Todo lo demás se construye encima. Verbosa pero completa.
  • seaborn: encima de matplotlib. Gráficos estadísticos con una línea, defaults bonitos.
  • df.plot(): método de pandas que llama a matplotlib. Conveniente para exploración rápida.

No tienes que elegir uno: se combinan. El patrón típico:

  • Exploración: df.plot() o seaborn.
  • Publicación: matplotlib (o seaborn) con ajustes finos sobre la figura resultante.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

plt y sns son los alias convencionales. Ahorra muchos caracteres.

df.plot(): exploración rápida

El método más rápido para ver datos:

df = pd.DataFrame({
    "mes":    pd.date_range("2024-01-01", periods=12, freq="ME"),
    "ventas": [1500, 1700, 1850, 1900, 2100, 2200, 2400, 2350, 2200, 2050, 1900, 2400],
})

df.plot(x="mes", y="ventas")

Una sola línea, ya tienes una serie temporal. df.plot() admite kind=:

kind= Gráfico
"line" (default) Líneas
"bar" / "barh" Barras vertical / horizontal
"scatter" Dispersión
"hist" Histograma
"box" Caja y bigotes
"area" Área apilada
"density" / "kde" Densidad

Ejemplos:

df.plot(kind="bar", x="mes", y="ventas")
df.plot.scatter(x="ventas", y="clientes")     # forma alternativa
df["ventas"].plot.hist(bins=20)

Cuándo usarlo: para mirar datos durante el análisis. Sin pretensiones de publicación, sin ajustar nada, solo “qué pinta tiene esto”.

seaborn: estadística con una línea

seaborn es matplotlib con defaults estéticos y gráficos estadísticos que matplotlib no trae de fábrica:

sns.set_theme(style="whitegrid")    # tema global, una vez al inicio

penguins = sns.load_dataset("penguins")

sns.scatterplot(data=penguins, x="bill_length_mm", y="bill_depth_mm", hue="species")

Los argumentos clave que verás en todos los gráficos de seaborn:

  • data=: el DataFrame.
  • x=, y=: variables como nombres de columna (no la columna directamente).
  • hue=: color por categoría.
  • style=: forma del marcador por categoría.
  • size=: tamaño por variable.

Es la misma idea que las estéticas de ggplot2 en R. Mapeo de columnas a propiedades visuales.

Los gráficos de seaborn que más se usan

# Relación entre dos numéricas
sns.scatterplot(data=df, x="x", y="y", hue="grupo")
sns.regplot(data=df, x="x", y="y")              # con línea de regresión

# Distribución de una numérica
sns.histplot(data=df, x="ventas", bins=30)
sns.kdeplot(data=df, x="ventas", hue="region")  # densidad

# Numérica por categoría
sns.boxplot(data=df, x="region", y="ventas")
sns.violinplot(data=df, x="region", y="ventas")
sns.stripplot(data=df, x="region", y="ventas")  # puntos crudos

# Categoría vs categoría
sns.countplot(data=df, x="region", hue="categoria")
sns.heatmap(matriz_correlaciones, annot=True)

# Múltiples paneles (como facet_wrap de ggplot2)
sns.relplot(data=df, x="x", y="y", col="grupo", kind="scatter")
sns.catplot(data=df, x="categoria", y="ventas", col="region", kind="bar")

Si vienes de ggplot2, relplot y catplot son lo más cercano al concepto facetado de ggplot. Devuelven una FacetGrid que ya gestiona los paneles.

El modelo Figure/Axes: lo que hay que saber

matplotlib tiene un modelo orientado a objetos que conviene conocer aunque no se use directamente todo el rato:

  • Figure: el contenedor entero (la “página”).
  • Axes: un sub-gráfico dentro de la Figure (lo que coloquialmente llamamos “el gráfico”).

Patrón explícito:

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(df["mes"], df["ventas"])
ax.set_title("Ventas mensuales 2024")
ax.set_xlabel("Mes")
ax.set_ylabel("Ventas (€)")
plt.show()

plt.subplots() devuelve la Figure y un Axes. Tú llamas métodos sobre ax para añadir cosas.

Múltiples paneles:

fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].plot(df["mes"], df["ventas"])
axes[1].hist(df["ventas"], bins=15)

axes es un array. Útil cuando construyes dashboards manualmente.

Por qué importa: tanto df.plot() como seaborn pueden recibir un argumento ax= para pintar sobre un Axes existente. Esto permite combinar varios gráficos en una figura.

fig, ax = plt.subplots(figsize=(8, 5))
sns.scatterplot(data=df, x="x", y="y", ax=ax)
sns.regplot(data=df, x="x", y="y", scatter=False, ax=ax, color="red")

Dos capas (puntos + línea de regresión) sobre el mismo Axes.

De exploración a publicable

Lo que separa un gráfico de exploración de uno publicable son 5-6 detalles. El patrón básico:

fig, ax = plt.subplots(figsize=(8, 5))

sns.lineplot(data=df, x="mes", y="ventas", ax=ax, linewidth=2)

ax.set_title("Ventas mensuales 2024", fontsize=14, weight="bold")
ax.set_xlabel("")                                    # quita label redundante
ax.set_ylabel("Ventas (€)")
ax.grid(axis="y", linestyle="--", alpha=0.5)         # solo gridlines horizontales
ax.spines["top"].set_visible(False)                  # quita borde superior
ax.spines["right"].set_visible(False)                # y derecho

plt.tight_layout()
plt.savefig("ventas_2024.png", dpi=300, bbox_inches="tight")

Los detalles que hacen la diferencia:

  • figsize controlado (no el default 6.4 × 4.8).
  • Título y ejes con tipografía decidida.
  • Spines (los bordes del cuadro) reducidos a los necesarios.
  • Grid sutil, no dominante.
  • tight_layout() para que los labels no se corten.
  • dpi=300 y bbox_inches="tight" al guardar, para impresión y para que no haya márgenes raros.

Color: paletas

seaborn trae paletas razonables por defecto. Para cambiar:

sns.set_palette("crest")            # paleta global
sns.color_palette("flare", n_colors=6)  # paleta específica

# Categóricos
sns.scatterplot(data=df, x="x", y="y", hue="grupo", palette="Set2")

# Numérico (gradiente)
sns.scatterplot(data=df, x="x", y="y", hue="valor", palette="viridis")

Categórico (Set1, Set2, Paired, tab10) para grupos discretos. Secuencial (viridis, flare, crest) para valores numéricos ordenados. Divergente (coolwarm, RdBu_r) cuando hay un centro semánticamente significativo (por encima/por debajo de cero).

Plotting desde un groupby

Combinación útil: agregar y pintar en un chain.

(df
    .groupby("region")["ventas"]
    .sum()
    .sort_values()
    .plot(kind="barh", figsize=(8, 4))
)

.plot() también funciona sobre el resultado de un groupby, el método devuelve un Series/DataFrame y se pinta directamente. Idiomático para EDA exprés.

Alternativas modernas

  • plotly: gráficos interactivos (zoom, hover, exportar a HTML). Excelente para dashboards y reports HTML, pero menos elegante para PDF estático.
  • plotnine: implementación de ggplot2 en Python. Si vienes de R y echas de menos el grammar of graphics, mira esto. Sintaxis casi idéntica a ggplot2.
  • altair: gramática declarativa, basada en Vega-Lite. Bonita pero menos extendida.

Para análisis estándar publicable, matplotlib + seaborn cubren el 95 %. Las alternativas son útiles cuando el contexto lo pide (interactividad, sintaxis de ggplot2).

Trampas habituales

  • Olvidar plt.show() o plt.tight_layout(). En Jupyter el plot suele renderizarse solo, pero tight_layout previene que los labels se corten.
  • Mezclar plt. con métodos de ax. sin saber qué pinta dónde. plt.title() afecta al “último Axes activo”. ax.set_title() afecta a uno concreto. Para código robusto, siempre el patrón Axes (fig, ax = plt.subplots()).
  • Llamar a plt.show() dentro de un bucle y que se acumulen ventanas. Si construyes muchos gráficos en serie, ciérralos: plt.close(fig).
  • Guardar antes de tight_layout y obtener labels cortados. Orden: tight_layout()savefig().
  • DPI por defecto en savefig. Es 100, pixelado al imprimir. Usa dpi=300 para impresión, dpi=150 para uso web de calidad.

En la siguiente entrega

Tienes todas las piezas: leer, manipular, agrupar, combinar, plottear. La última entrega es un caso completo: un EDA de principio a fin sobre un dataset real, mostrando el flujo de trabajo idiomático. La pieza que une todo. Lo siguiente.