Plotting: matplotlib + seaborn esenciales
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 pdplt 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:
figsizecontrolado (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=300ybbox_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()oplt.tight_layout(). En Jupyter el plot suele renderizarse solo, perotight_layoutpreviene que los labels se corten. - Mezclar
plt.con métodos deax.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_layouty obtener labels cortados. Orden:tight_layout()→savefig(). - DPI por defecto en
savefig. Es 100, pixelado al imprimir. Usadpi=300para impresión,dpi=150para 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.