Saltar al contenido

# graficos-en-matplotlib

Gráficos en Matplotlib

Volver a la portada del curso
Lectura (Markdown)

Manipulación de Ejes y Límites

Sección 3 · Personalización de Gráficos

Manipulación de Ejes y Límites

La línea del ingreso mensual de PyShop con los límites automáticos tiene el eje y entre 0.562 y 1.545 millones. Con ese rango, febrero (0.61 M) queda al 5 % de la altura del panel y diciembre (1.50 M) al 95 %: a la vista, diciembre parece vender veinte veces más, cuando en realidad vende 2.5 veces más. Matplotlib elige los límites para que los datos llenen el panel, no para que la comparación sea justa. Esta lectura muestra cómo tomar el control de los ejes: límites, marcas, formato de las etiquetas, escalas, un segundo eje y anotaciones.

1. Límites y márgenes

Por defecto, cada eje se ajusta al mínimo y al máximo de los datos y agrega un margen del 5 % a cada lado. Por eso el eje x de los meses 1 a 12 va de 0.45 a 12.55. set_xlim y set_ylim fijan los límites; si pasas None en un extremo, ese extremo sigue siendo automático:

import matplotlib.pyplot as plt

ventas = generar_ventas()
mes = ventas["fecha"].dt.month
mensual = ventas.groupby(mes)["total"].sum() / 1e6

fig, ax = plt.subplots()
ax.plot(mensual.index, mensual.values, marker="o")
def limites(ax):
    return tuple(round(float(v), 3) for v in ax.get_ylim())

print(limites(ax))    # (0.562, 1.545)
ax.set_ylim(0, None)
print(limites(ax))    # (0.0, 1.545)
print(ax.margins())   # (0.05, 0.05)

En barras el eje y siempre debe empezar en cero, porque la longitud de la barra es el dato; Matplotlib ya lo hace así con ax.bar. En líneas no es obligatorio, pero si el rango no empieza en cero conviene que el lector lo note. ax.margins(x=0) elimina el margen horizontal cuando la serie debe tocar los bordes.

2. Marcas y sus etiquetas

Las marcas automáticas del eje x son 0, 2, 4… 14: Matplotlib no sabe que son meses. set_xticks recibe las posiciones y, con labels=, el texto de cada una. Para etiquetas largas, ax.tick_params(axis="x", rotation=45) las inclina.

MESES = ["Ene", "Feb", "Mar", "Abr", "May", "Jun",
         "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"]
ax.set_xticks(range(1, 13), labels=MESES)
print(len(ax.get_xticks()))   # 12

3. Formateadores

Cada Axis usa un formatter para convertir el número de la marca en texto. El módulo matplotlib.ticker trae varios listos:

  • StrMethodFormatter("{x:,.0f}"): usa la sintaxis de str.format; con ingresos en pesos produce «1,000,000».
  • FuncFormatter(f): llama a una función propia f(valor, posicion); sirve para escribir «1.2 M».
  • PercentFormatter(): agrega el símbolo de porcentaje; con decimals=0 muestra «10%» en lugar de «10.0%».
from matplotlib.ticker import FuncFormatter

def millones(valor, pos):
    return f"{valor:.1f} M"

ax.yaxis.set_major_formatter(FuncFormatter(millones))
# etiquetas: 0.0 M, 0.2 M, 0.4 M ...

El formateador cambia solo el texto: los datos y los límites siguen iguales. Es mejor que dividir entre un millón a mano cuando la misma figura necesita la cifra exacta en otro lugar.

4. Escala logarítmica

Los precios de PyShop van de 110.39 a 6,677.50 MXN: el más caro vale 60.5 veces el más barato. En un histograma con escala lineal, Libros y Ropa quedan apretados en la orilla izquierda y Electrónica ocupa casi todo el ancho. Con ax.set_xscale("log") cada marca multiplica por 10 en vez de sumar una cantidad fija, y los grupos de precios se separan. La escala log es útil cuando los datos abarcan varios órdenes de magnitud; no admite ceros ni negativos.

5. Dos ejes verticales en el mismo panel: twinx

Pedidos (de 349 a 800 por mes) y ticket medio (de 1,738 a 1,958 MXN) tienen unidades distintas. ax.twinx() crea un segundo Axes que comparte el eje x y pone su eje y a la derecha:

pedidos = ventas.groupby(mes).size()
ticket = ventas.groupby(mes)["total"].sum() / pedidos

fig, ax = plt.subplots()
ax.bar(pedidos.index, pedidos.values, color="C0")
ax.set_ylabel("Pedidos")
ax2 = ax.twinx()
ax2.plot(ticket.index, ticket.values, color="C1", marker="o")
ax2.set_ylabel("Ticket medio (MXN)")
print(len(fig.axes))   # 2

Úsalo con cuidado: el lector puede comparar alturas que no son comparables. Colorea cada eje y su etiqueta igual que su serie.

6. Cuadrícula, bordes y anotaciones

ax.grid(axis="y", alpha=0.3) dibuja líneas guía tenues solo en y. ax.spines[["top", "right"]].set_visible(False) quita los bordes que no aportan. ax.annotate escribe un texto con flecha sobre un punto concreto, por ejemplo el máximo de diciembre:

pico = mensual.idxmax()            # 12
ax.annotate(f"{mensual[pico]:.2f} M", xy=(pico, mensual[pico]),
            xytext=(8, 1.4), arrowprops={"arrowstyle": "->"})

Trampas comunes

  • Eje y recortado sin avisar. Exagera las diferencias; en barras es un error.
  • Fijar límites antes de dibujar. Si después agregas otra serie con autoescala, los límites pueden cambiar; ajusta al final.
  • Poner etiquetas sin posiciones. Pasa siempre las posiciones a set_xticks; si no, las etiquetas no coinciden con los datos.
  • Log con ceros. Los valores en cero o negativos desaparecen de un eje logarítmico.
  • twinx sin colores. Sin distinción visual, no se sabe qué eje lee cada serie.

Cierre

El notebook ejes-colores-estilos aplica todo esto a la línea mensual y a los precios, y sigue con colores, colormaps y hojas de estilo. Antes de entregar un gráfico, revisa tres cosas: dónde empieza el eje y, si las marcas dicen algo legible y si las unidades están en la etiqueta.

Recursos