El primer gráfico
En resumen: Un gráfico en matplotlib es una figure con axes: plot dibuja una línea, bar dibuja barras, y el título, las etiquetas de los ejes y la leyenda dicen qué se muestra.
Problema
«Muéstrame el año»
El gerente quiere ver cómo va el año. Ya sabes calcular los ingresos por mes: las líneas de pedido sin las canceladas, sumadas por mes. El mes son los primeros 7 caracteres de ordered_at, por ejemplo «2026-03»:
Los números son correctos, pero en 12 filas es difícil ver qué es un pico y qué es una caída. Diciembre es 15 915,90 € y enero es 7228,20 €. ¿Qué tan grande es la caída comparada con los demás meses? En una tabla el ojo no lo capta. En una línea, enseguida.
La idea
Figure y axes
En la lección «Bienvenida» viste un gráfico hecho con una línea de código. Ahora verás lo que hay detrás.
Un gráfico en matplotlib tiene dos objetos:
- la figure (
Figure) es la hoja entera: el tamaño y el guardado en un archivo; - los axes (
Axes) son el recuadro con el eje horizontal y el vertical, donde dibujas.
plt.subplots() te da los dos: fig, ax = plt.subplots(). Después dibujas en los axes. ax.plot(x, y) une los puntos con una línea:
[gráfico] en el texto de la salida significa que el gráfico no tiene título. La línea ya muestra el pico de diciembre y la bajada posterior. Aún le faltan tres cosas: un título, etiquetas en los ejes y meses que se puedan leer.
Más precisamente, para curiosos
Aquí y en Jupyter el gráfico aparece solo al final de la celda, por eso no hay plt.show(). En un script (python chart.py) lo muestras con plt.show() o lo guardas con fig.savefig('monthly.png'). Si no, el script termina y la figure se va con él.
plt.plot(...) sin fig, ax también dibuja: matplotlib toma entonces los axes «actuales» por sí mismo. Es cómodo para un gráfico rápido, pero con varios axes no sabes cuál es el actual. Por eso aquí siempre dibujas en axes con nombre.
La idea
Título, etiquetas y marcas
Un gráfico sin etiquetas obliga al lector a adivinar. Los axes tienen un método para cada cosa:
ax.set_titledice qué estás mirando;ax.set_xlabelyax.set_ylabeldicen qué hay abajo y qué hay en el lateral;ax.tick_params(axis='x', labelrotation=45)gira las etiquetas de las marcas, los números y nombres debajo del eje, para que no se solapen;fig.tight_layout()organiza la hoja para que las etiquetas giradas no se corten.
marker='o' pone un punto en cada mes:
Ahora se ve que 11 meses están entre 7000 € y 10 000 €, y diciembre por encima de 15 000 €. Enero, febrero y septiembre son los más bajos. Por qué baja septiembre lo descubrirás en el proyecto.
Pruébalo
Pedidos por mes
Dibuja el número de pedidos de cada mes (sin los cancelados) como una línea. Guarda los conteos en counts, y pon al gráfico un título y etiquetas en los dos ejes.
Solución
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
sold = orders[orders['status'] != 'cancelled']
counts = sold.groupby(sold['ordered_at'].str[:7]).size()
fig, ax = plt.subplots()
ax.plot(counts.index, counts.values)
ax.set_title('Pedidos por mes')
ax.set_xlabel('Mes')
ax.set_ylabel('Pedidos')
ax.tick_params(axis='x', labelrotation=45)
fig.tight_layout()sold.groupby(sold['ordered_at'].str[:7]).size() da 12 números: 886 pedidos en noviembre, 1211 en diciembre, 728 en enero y 701 en septiembre. La línea tiene la misma forma que los ingresos: un pico en diciembre y una bajada después. Con orders en lugar de sold contarías también los pedidos cancelados. Sin set_xlabel y set_ylabel el lector no sabe si el eje vertical está en unidades o en euros.
La idea
Barras: ax.bar
Una línea une puntos y dice «de aquí a aquí algo cambió». Eso tiene sentido para meses que vienen uno tras otro. Las categorías no vienen una tras otra: para ellas dibujas barras (un gráfico de barras) con ax.bar(names, values).
Los ingresos por categoría necesitan el nombre de la categoría en cada línea: producto → category_id → nombre. Dos llamadas a map lo dan:
Hay 6 barras, y suman 107 466,20 €. Los ingresos del año son 111 186,20 €. Faltan 3720 €: la tarjeta de regalo no tiene categoría, map le da NaN, y groupby se salta NaN en silencio. El gráfico parece correcto. Por eso cuentas las barras y comparas su suma con los ingresos.
Pruébalo
Todas las categorías
Dibuja los ingresos por categoría de modo que la tarjeta de regalo sea una barra propia. Guarda los ingresos por categoría en by_cat, y añade un título y una etiqueta en el eje vertical.
Solución
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
# Los ingresos no incluyen los pedidos cancelados.
sold = orders[orders['status'] != 'cancelled']
lines = items.merge(sold, left_on='order_id', right_on='id')
lines['revenue'] = lines['quantity'] * lines['unit_price']
lines['month'] = lines['ordered_at'].str[:7]
products = pd.read_csv('products.csv')
categories = pd.read_csv('categories.csv')
cat_of = products.set_index('id')['category_id']
cat_name = categories.set_index('id')['name']
lines['category'] = lines['product_id'].map(cat_of).map(cat_name).fillna('Tarjeta de regalo')
by_cat = lines.groupby('category')['revenue'].sum()
fig, ax = plt.subplots()
ax.bar(by_cat.index, by_cat.values)
ax.set_title('Ingresos por categoría, €')
ax.set_ylabel('Ingresos, €')
ax.tick_params(axis='x', labelrotation=30)
fig.tight_layout().fillna('Tarjeta de regalo') después de las dos llamadas a map da un nombre a la categoría que falta, y groupby la conserva. Ahora hay 7 barras, y suman exactamente 111 186,20 €. Los lácteos son la barra más alta con 31 427,90 €, y la tarjeta de regalo la más baja con 3720 €. Sin fillna el gráfico tiene 6 barras y falta la tarjeta de regalo.
La idea
El atajo: df.plot
Series y DataFrame tienen un método plot. Llama a matplotlib por ti: kind='line' (el valor por defecto), kind='bar' y los otros tipos que vienen en las próximas lecciones. Con ax=ax dibuja en tus axes. Después los terminas con los métodos de los axes, como antes:
pandas ha puesto los nombres de las categorías debajo de las barras y los ha girado. También ha puesto una etiqueta abajo: el nombre del índice, category. El título y la etiqueta vertical vienen de tu código.
Las dos maneras dan el mismo gráfico. ax.bar te da control total. df.plot ahorra líneas cuando los datos ya están en una Serie o una tabla.
La idea
Varias líneas y una leyenda
Diciembre es el pico del año. ¿Crecen igual todas las categorías? Dos líneas en unos mismos axes lo responden. Cada ax.plot añade una línea, label le da un nombre, y ax.legend() añade la leyenda: un recuadro que dice qué línea es cuál:
La cocina salta de 964,80 € en noviembre a 3240,60 € en diciembre, 3,4 veces más. Después cae a 592,80 € en enero. La sartén y los cuchillos probablemente se compran como regalo. Los lácteos crecen menos: de 2792,10 € a 3561,20 €.
Una tabla con una columna para cada categoría, df.plot la dibuja de una vez, una línea por columna, con una leyenda a partir de los nombres de las columnas. Por eso la tabla de la lección sobre pivot_table es ancha:
Siete líneas en unos mismos axes ya se enredan. Qué gráfico ayuda cuándo, y cómo mostrar muchas series sin enredarlas, es la próxima lección.
Ejercicio
¿Quién sostiene diciembre?
Los dos grandes grupos de categorías son Alimentos y Hogar (parent_id en categories.csv). Dibuja los ingresos de cada uno por mes como dos líneas en unos mismos axes, con leyenda, título y etiquetas en los ejes. Guarda la tabla por mes y grupo en pv.
Solución
import pandas as pd
import matplotlib.pyplot as plt
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
# Los ingresos no incluyen los pedidos cancelados.
sold = orders[orders['status'] != 'cancelled']
lines = items.merge(sold, left_on='order_id', right_on='id')
lines['revenue'] = lines['quantity'] * lines['unit_price']
lines['month'] = lines['ordered_at'].str[:7]
products = pd.read_csv('products.csv')
categories = pd.read_csv('categories.csv')
cat_of = products.set_index('id')['category_id']
cat_name = categories.set_index('id')['name']
parent_of = categories.set_index('id')['parent_id']
lines['group'] = lines['product_id'].map(cat_of).map(parent_of).map(cat_name)
pv = lines.pivot_table(index='month', columns='group', values='revenue', aggfunc='sum')
fig, ax = plt.subplots()
ax.plot(pv.index, pv['Alimentos'], label='Alimentos')
ax.plot(pv.index, pv['Hogar'], label='Hogar')
ax.legend()
ax.set_title('Alimentos y Hogar por mes, €')
ax.set_xlabel('Mes')
ax.set_ylabel('Ingresos, €')
ax.tick_params(axis='x', labelrotation=45)
fig.tight_layout()El grupo de una línea necesita tres llamadas a map: producto → categoría → parent_id → nombre. pivot_table por mes y grupo da una columna Alimentos y una columna Hogar. En diciembre los alimentos crecen un 30 % (de 7097,40 € a 9249,30 €), y el hogar más que se duplica (de 2077,10 € a 4506,60 €). La tarjeta de regalo no tiene grupo y no está en ninguna de las líneas. Sin label la leyenda está vacía, y sin ax.legend() no hay leyenda en absoluto.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Tipos y valores faltantes · Grupos: groupby · Unir tablas: merge · Ancho y largo: pivot_table y melt
- Continúa en: Qué gráfico para qué pregunta · Distribuciones · Gráficos honestos · Describir los datos
- Ejercicios sobre el tema: Gráficos