groupby — Patrón de Dividir-Aplicar-Combinar
Al finalizar este tema
Podrás explicar el funcionamiento interno de pandas groupby() mediante el patrón de Dividir-Aplicar-Combinar y adquirirás familiaridad con los patrones de agregación, transformación y filtrado por grupo que se utilizan con frecuencia en la práctica.
¿Por qué son necesarias las operaciones por grupo?
El "promedio general" es df["score"].mean() en una sola línea. Sin embargo, las preguntas prácticas suelen tener esta forma:
- ¿Cuál es el salario promedio por departamento?
- ¿Cuál es la suma de ventas mensuales?
- ¿Cuál es el producto más vendido por categoría?
En el momento en que aparece "~por", se requieren operaciones por grupo. Esta es la función de groupby().
Patrón de Dividir-Aplicar-Combinar
Este patrón, nombrado por Hadley Wickham en 2011, es la estructura de pensamiento más recurrente en el análisis de datos.
Datos originales
┌────────┬──────┬───────┐
│ dept │ name │ score │
├────────┼──────┼───────┤
│ Sales │ Alice│ 85 │
│ Sales │ Bob │ 90 │
│ Dev │ Carol│ 95 │
│ Dev │ Dave │ 88 │
│ HR │ Eve │ 78 │
└────────┴──────┴───────┘
1. SPLIT — dividir por departamento
Sales: [85, 90]
Dev: [95, 88]
HR: [78]
2. APPLY — aplicar una función a cada grupo (por ejemplo, la media)
Sales: 87.5
Dev: 91.5
HR: 78.0
3. COMBINE — reunir los resultados en uno
┌────────┬───────┐
│ dept │ score │
├────────┼───────┤
│ Dev │ 91.5 │
│ HR │ 78.0 │
│ Sales │ 87.5 │
└────────┴───────┘En pandas, estos tres pasos se realizan en una sola línea:
df.groupby("dept")["score"].mean()Uso básico
import pandas as pd
df = pd.DataFrame({ "dept": ["Sales", "Sales", "Dev", "Dev", "HR"], "name": ["Alice", "Bob", "Carol", "Dave", "Eve"], "score": [85, 90, 95, 88, 78], "salary": [50000, 52000, 70000, 68000, 45000]})
# Puntuación media por departamentodf.groupby("dept")["score"].mean()# dept# Dev 91.5# HR 78.0# Sales 87.5
# Suma de salarios por departamentodf.groupby("dept")["salary"].sum()# dept# Dev 138000# HR 45000# Sales 102000¿Qué es un objeto GroupBy?
grouped = df.groupby("dept")print(type(grouped)) # <class 'pandas.core.groupby.DataFrameGroupBy'>groupby() no se calcula de inmediato. La evaluación diferida (lazy evaluation) significa que el cálculo solo se ejecuta cuando se aplica alguna función (como mean, sum, etc.). Esto evita cálculos innecesarios en conjuntos de datos grandes.
Verificación del contenido del grupo
for name, group in df.groupby("dept"): print(f"\n--- {name} ---") print(group)
# --- Dev ---# dept name score salary# 2 Dev Carol 95 70000# 3 Dev Dave 88 68000# --- HR ---# ...Varias funciones de agregación
Una columna, múltiples funciones
df.groupby("dept")["score"].agg(["mean", "min", "max", "count"])# mean min max count# dept# Dev 91.5 88 95 2# HR 78.0 78 78 1# Sales 87.5 85 90 2Funciones diferentes por columna
df.groupby("dept").agg( avg_score=("score", "mean"), total_salary=("salary", "sum"), headcount=("name", "count"))# avg_score total_salary headcount# dept# Dev 91.5 138000 2# HR 78.0 45000 1# Sales 87.5 102000 2La agregación con nombre (nombre_columna=("columna_original", "función")) es la forma más limpia, ya que permite especificar los nombres de las columnas del resultado de una sola vez.
Agrupación por varias columnas
sales = pd.DataFrame({ "region": ["East", "East", "West", "West", "East", "West"], "category": ["A", "B", "A", "B", "A", "A"], "revenue": [100, 200, 150, 250, 120, 180]})
sales.groupby(["region", "category"])["revenue"].sum()# region category# East A 220# B 200# West A 330# B 250Al pasar varias columnas como lista, se genera un índice jerárquico (MultiIndex).
# Convertir MultiIndex en columnas normalesresult = sales.groupby(["region", "category"])["revenue"].sum().reset_index()# region category revenue# 0 East A 220# 1 East B 200# 2 West A 330# 3 West B 250reset_index() convierte la clave de grupo desde el índice de nuevo a una columna normal. Esta forma es conveniente para tareas posteriores (visualización, guardado, etc.).
transform — transformación por grupo
agg() devuelve un único valor por grupo (5 filas → 3 filas). transform() devuelve un resultado del mismo tamaño que el original (5 filas → 5 filas).
# Añadir al conjunto original la media de cada departamentodf["dept_avg"] = df.groupby("dept")["score"].transform("mean")print(df)# dept name score salary dept_avg# 0 Sales Alice 85 50000 87.5# 1 Sales Bob 90 52000 87.5# 2 Dev Carol 95 70000 91.5# 3 Dev Dave 88 68000 91.5# 4 HR Eve 78 45000 78.0A cada fila se le adjunta el promedio de su propio departamento. Con esto, se puede calcular la puntuación en comparación con el promedio del departamento:
df["vs_avg"] = df["score"] - df["dept_avg"]# Alice: 85 - 87.5 = -2.5 (por debajo de la media del departamento)# Carol: 95 - 91.5 = +3.5 (por encima de la media del departamento)filter — Filtrado por grupo
Mantiene o elimina todo el grupo que cumple con la condición.
# Solo departamentos con al menos dos personasdf.groupby("dept").filter(lambda g: len(g) >= 2)# dept name score salary# 0 Sales Alice 85 50000# 1 Sales Bob 90 52000# 2 Dev Carol 95 70000# 3 Dev Dave 88 68000El grupo completo de HR (1 persona) ha sido eliminado. El núcleo de filter() es filtrar por grupos, no por filas individuales.
Patrón práctico: Análisis de ventas mensuales
orders = pd.DataFrame({ "date": pd.to_datetime([ "2026-01-05", "2026-01-15", "2026-02-03", "2026-02-20", "2026-03-10", "2026-03-25" ]), "product": ["Widget", "Gadget", "Widget", "Gadget", "Widget", "Widget"], "amount": [1200, 800, 1500, 900, 1100, 1300]})
# Ventas totales por mesmonthly = orders.groupby(orders["date"].dt.to_period("M"))["amount"].sum()print(monthly)# date# 2026-01 2000# 2026-02 2400# 2026-03 2400
# Ventas mensuales medias por productoorders.groupby("product").agg( total=("amount", "sum"), avg=("amount", "mean"), count=("amount", "count"))# total avg count# Gadget 1700 850.0 2# Widget 5100 1275.0 4En los datos de fecha, crear grupos mensuales con .dt.to_period("M") es un patrón muy común.
Resumen clave
| Método | Acción | Tamaño del resultado |
|---|---|---|
agg() | Agregación por grupo (suma, media, recuento...) | Número de grupos (disminuye) |
transform() | Transformación por grupo (mantiene el tamaño original) | Igual que el original |
filter() | Mantiene todos los grupos que cumplen la condición | Igual o menor que el original |
groupby() es la función más potente y utilizada en pandas. Si puede visualizar el patrón Split-Apply-Combine, puede descomponer de forma natural incluso las agregaciones complejas en tres pasos: "dividir por qué columna (Split), aplicar qué función (Apply) y combinar los resultados en qué formato (Combine)".