Grupos: groupby
En resumen: groupby en pandas divide las filas por una clave, calcula en cada grupo (sum, mean, agg) y junta el resultado en una tabla.
Problema
«¿Qué tamaño tiene el pedido medio?»
El gerente pregunta cuánto gasta un cliente en un pedido, en promedio. Tienes los pedidos del año y sus líneas: cada línea es un producto en un pedido, con la cantidad y el precio. Los pedidos cancelados no son ingresos, así que los quitas con una máscara, como en la lección sobre filtros:
Hay 25 069 líneas y 10 459 pedidos. La media de la columna revenue es 4,44 €, pero esa es la línea media, no el pedido medio. Un pedido tiene varias líneas, y para un pedido primero tienes que sumar sus líneas.
Necesitas un total para cada pedido: 10 459 números a partir de 25 069 líneas. Con un for y un diccionario se puede, pero es lento y largo. pandas tiene para eso una sola operación.
La idea
Dividir, aplicar, combinar
groupby('order_id') divide las filas en grupos: un grupo por cada valor distinto de order_id. Después eliges una columna y un cálculo: ['revenue'].sum() suma los ingresos de cada grupo. Al final pandas pone los resultados en una Serie con order_id como índice.
Eso se llama split-apply-combine (dividir, aplicar, combinar). Aquí con las primeras 7 líneas:
El pedido 1 tiene una línea (31,60 €), y los pedidos 2, 3 y 4 tienen dos cada uno. El pedido 3 es 37,5 + 8,4 = 45,90 €. De 7 líneas salen 4 números, uno por grupo. La clave order_id es el índice del resultado, por eso aparece debajo del nombre de la columna.
En lugar de sum puedes usar cualquier resumen de NumPy: mean, min, max, count.
Míralo
Cómo recorre groupby las filas
Las mismas 7 líneas, despacio: cada línea entra en el grupo de su order_id, el grupo se suma en un número, y los números se alinean en el resultado.
import pandas as pd
items = pd.read_csv('order_items.csv').head(7)items['revenue'] = items['quantity'] * items['unit_price']print(items[['order_id', 'revenue']])items.groupby('order_id')['revenue'].sum()Paso a paso, en palabras
- 7 filas de order_items con los ingresos de cada línea: pedidos 1, 2, 3 y 4.
- Dividir (split): groupby('order_id') pone cada fila en el grupo de su order_id.
- 4 order_id distintos, 4 grupos. El pedido 1 tiene una fila, los demás 2 cada uno.
- Aplicar (apply): ['revenue'].sum() en cada grupo. Pedido 3: 37,5 + 8,4 = 45,9.
- Combinar (combine): las 4 sumas pasan a ser una Serie con order_id como índice.
- Entraron 7 filas, salieron 4 números: uno por cada grupo.
Pruébalo
Qué producto se pide más
En los 60 pedidos de order_items.csv: ¿cuántas unidades de cada producto se pidieron? Guárdalas en units, y los tres productos con más unidades en top3.
Solución
import pandas as pd
items = pd.read_csv('order_items.csv')
units = items.groupby('product_id')['quantity'].sum()
top3 = units.nlargest(3)
print(top3)units = items.groupby('product_id')['quantity'].sum() da un número para cada producto, y units.nlargest(3) da los productos 13, 5 y 10: pepinos (31 unidades), leche fresca (27) y manzanas (25). Los pepinos tienen existencias 0: se agotaron porque la gente los quiere. Con count() en lugar de sum() contarías las líneas, y una línea puede ser de 4 unidades.
La idea
Varios cálculos a la vez: agg
A menudo necesitas varios números para cada grupo. agg toma una lista de cálculos y da una columna para cada uno:
Las columnas se llaman como los cálculos. Para un informe es mejor ponerles nombre tú mismo. Son las agregaciones con nombre: nombre_nuevo=('columna', 'cálculo'), y cada una puede usar otra columna:
El pedido 3 tiene 2 líneas, 7 unidades y 45,90 € de ingresos. La tabla tiene una fila por pedido y una columna por cada pregunta que hiciste.
La idea
El conteo: size o count
Los dos contadores se parecen, pero cuentan cosas distintas. size() cuenta las filas de cada grupo. count() cuenta los valores rellenados de cada columna: NaN no se cuenta. Aquí están los clientes por el año en que se registraron:
16 clientes se registraron en 2025, pero la columna city tiene 15 valores: falta la ciudad de un cliente. Si quieres «cuántos clientes», size() es la respuesta correcta. count() sobre una columna responde «de cuántos conocemos la ciudad».
La idea
Varias claves
La clave puede ser una lista de columnas. Entonces un grupo es cada par de valores. Aquí están las unidades del año por producto y por precio:
Hay 25 productos y 26 grupos: un producto se vendió a dos precios. Es el café (producto 17): 245 paquetes a 13,90 € y 444 paquetes a 14,90 €, después de que el precio subió el 1 de marzo. El resultado tiene un índice con dos niveles, y units.loc[17] toma los grupos del café.
Más precisamente, para curiosos
Los grupos con NaN en la clave se caen: la tarjeta de regalo no tiene categoría, y products.groupby('category_id') la deja fuera en silencio. groupby('category_id', dropna=False) la conserva como un grupo NaN. Comprueba con len cuántas filas entran en los grupos y cuántas salen.
La idea
El número del grupo en cada fila: transform
A veces no quieres reducir el grupo, sino poner su número junto a cada una de sus filas. Por ejemplo: qué tan caro es un producto comparado con el precio medio de su categoría. transform('mean') calcula la media del grupo y la repite en cada fila, conservando el orden y el índice de la tabla:
transform da 25 números, uno por producto, y mean() da 6, uno por categoría. Por eso la columna cat_avg encaja directamente en la tabla. El café cuesta 14,90 €, y la media de bebidas es 5,35 €: 2,79 veces más. El detergente en polvo es 2,19 veces la media de limpieza.
Más precisamente, para curiosos
El resultado de mean() tiene category_id como índice, y la tabla products tiene el índice 0, 1, 2… Si lo asignas como columna, pandas lo alinea por el índice, no por la categoría, y casi todo se vuelve NaN. transform conserva el índice de la tabla. La tarjeta de regalo no tiene categoría, así que su cat_avg es NaN.
Pruébalo
La parte de la línea en el pedido
Para cada línea de order_items.csv: ¿qué parte de los ingresos de su pedido aporta? Añade a items una columna order_total con los ingresos de todo el pedido y una columna share con la parte de la línea.
Solución
import pandas as pd
items = pd.read_csv('order_items.csv')
items['revenue'] = items['quantity'] * items['unit_price']
items['order_total'] = items.groupby('order_id')['revenue'].transform('sum')
items['share'] = items['revenue'] / items['order_total']
print(items[items['order_id'] == 3])items.groupby('order_id')['revenue'].transform('sum') pone los ingresos del pedido en cada una de sus líneas, y items['revenue'] / items['order_total'] da la parte. En el pedido 3 el detergente en polvo aporta 37,5 de 45,90 €, cerca del 82 %, y las manzanas cerca del 18 %. Las partes de las líneas de un pedido siempre suman 1. Con .sum() en lugar de transform, la columna sería casi toda NaN: el total es por pedido, y la tabla es por línea.
La idea
La clave como columna: as_index=False
El resultado de groupby tiene la clave en el índice. Para filtrarla, unirla con otra tabla o ponerla en un gráfico necesitas una columna normal. Hay dos maneras, con el mismo resultado:
as_index=False lo dice al agrupar, y reset_index() lo hace después: el índice pasa a ser una columna, y las filas se numeran 0, 1, 2… Las columnas son order_id y revenue.
Ejercicio
El pedido medio
Responde al gerente de la primera tarjeta. Guarda en order_totals los ingresos de cada pedido del año sin los cancelados, y el pedido medio, redondeado a 2 decimales, en avg_order.
Solución
import pandas as pd
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
cancelled = orders.loc[orders['status'] == 'cancelled', 'id']
items = items[~items['order_id'].isin(cancelled)]
items['revenue'] = items['quantity'] * items['unit_price']
order_totals = items.groupby('order_id')['revenue'].sum()
avg_order = round(order_totals.mean(), 2)
print(len(order_totals), avg_order)order_totals = items.groupby('order_id')['revenue'].sum() da 10 459 números, uno por pedido, y round(order_totals.mean(), 2) da 10,63 €. La línea media era 4,44 €: un pedido tiene 2,4 líneas de promedio. Con los cancelados serían 11 116 pedidos y una media de 10,61 €, cerca, pero no correcto: un pedido cancelado no son ingresos.
¿Cuántos clientes hicieron al menos un pedido durante el año, y cuántos de ellos solo uno? Guarda el número de pedidos de cada cliente en per_customer, el número de clientes en n_customers, y cuántos tienen exactamente un pedido en once.
Solución
import pandas as pd
orders = pd.read_csv('year_orders.csv')
per_customer = orders.groupby('customer_id').size()
n_customers = len(per_customer)
once = (per_customer == 1).sum()
print(n_customers, once, per_customer.max())per_customer = orders.groupby('customer_id').size() da un número para cada cliente: 1390 clientes. (per_customer == 1).sum() cuenta 224 con un pedido, y el cliente más fiel tiene 121 pedidos, 2 o 3 por semana. len(orders) daría los pedidos (11 116), y per_customer.min() solo la cantidad más pequeña, 1.
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Seleccionar: [], loc e iloc · Filtrar y ordenar · Columnas nuevas · Tipos y valores faltantes
- Continúa en: Unir tablas: merge · Ancho y largo: pivot_table y melt · Fechas y series temporales · El primer gráfico
- Ejercicios sobre el tema: Preparar los datos