Pandas GroupBy: Agregación, Transform y Apply (Guía 2025)
Updated on
El patrón split-apply-combine es donde Pandas brilla. El reto: muchos dudan entre agg, transform o apply, o se topan con ordenaciones inesperadas, categorías perdidas o efectos de SettingWithCopy.
Marco PAS:
- Problema: Los cálculos agrupados parecen opacos y lentos cuando solo se necesitan totales o ratios por fila.
- Agitar: Usar
applyde más o olvidaras_index=Falsegenera formas inesperadas, columnas duplicadas y código lento. - Solución: Unas pocas recetas claras—
aggpara resúmenes,transformpara métricas por fila,applysolo cuando haga falta—más opciones para ordenar y manejar grupos ausentes.
Guía rápida: cuándo usar cada método
| Método | Forma resultante | Mejor para | Ejemplo |
|---|---|---|---|
agg / aggregate | Una fila por grupo | Resúmenes y varias métricas | df.groupby("team").agg(avg_score=("score","mean")) |
transform | Misma longitud | Features por fila basadas en grupo | df["z"] = df.groupby("team")["score"].transform("zscore") |
apply | Flexible | Lógicas complejas si agg/transform no alcanzan | df.groupby("team").apply(custom_fn) |
Datos de ejemplo
import pandas as pd
data = {
"team": ["A", "A", "B", "B", "B", "C"],
"player": ["x1", "x2", "y1", "y2", "y3", "z1"],
"score": [9, 7, 8, 6, 10, 3],
"minutes": [30, 25, 28, 32, 20, 15],
}
df = pd.DataFrame(data)Patrones de agregación (agg)
summary = (
df.groupby("team", as_index=False)
.agg(
avg_score=("score", "mean"),
max_score=("score", "max"),
minutes_played=("minutes", "sum"),
)
)- Usa named aggregation para mantener nombres limpios.
as_index=Falseconservateamcomo columna (útil para merges y gráficos).sort=Falsemantiene el orden original si importa.
Multiagregación en una columna
df.groupby("team", as_index=False).agg(
score_mean=("score", "mean"),
score_std=("score", "std"),
score_count=("score", "size"),
)Features por fila (transform)
transform conserva la forma original y agrega métricas de grupo a cada fila.
df["score_pct_of_team"] = (
df["score"] / df.groupby("team")["score"].transform("sum")
)Z-score por equipo:
df["score_z"] = (
df.groupby("team")["score"]
.transform(lambda s: (s - s.mean()) / s.std(ddof=0))
)Usa transform para:
- Ratios frente a totales de grupo
- Scores normalizados
- Flags de grupo (ej.
rank,cumcount)
Cuándo vale la pena apply
apply es flexible pero más lento; reservalo para lo que no cubran agg o transform.
def top_n(group, n=2):
return group.nlargest(n, "score")
top_players = df.groupby("team").apply(top_n, n=1).reset_index(drop=True)Prefiere agg/transform; usa apply cuando:
- Debes devolver un número variable de filas por grupo.
- La lógica requiere Python sin alternativa vectorizada.
Grupos faltantes y orden
result = (
df.groupby("team", dropna=False, sort=False)
.agg(avg_score=("score", "mean"))
)dropna=Falseconserva etiquetasNaN.sort=Falseevita reordenar—útil si el orden tiene significado.
GroupBy con varias llaves e índices
multi = (
df.groupby(["team", "player"], as_index=False)
.agg(score_mean=("score", "mean"))
)Agrupando por niveles de índice:
df2 = df.set_index(["team", "player"])
df2.groupby(level="team")["score"].mean()Errores comunes
| Problema | Solución |
|---|---|
Formas inesperadas (Series vs DataFrame) | Usa as_index=False o luego reset_index(). |
| Nombres de columna duplicados tras merge | Usa named aggregation para definir los nombres. |
Lentitud por apply | Cambia a agg/transform o métodos vectorizados. |
| Categorías ausentes en la salida | Mantén observed=False (default) o asegura categorías; usa dropna=False para NaN. |
Tips de exportación y gráficos
- Tras agregar, ordena para presentar:
summary.sort_values("avg_score", ascending=False). - Para gráficos, pivot del resumen:
summary.pivot(index="team", values="avg_score").
Guías relacionadas
Puntos clave
- Usa
aggpara resúmenes,transformpara features por fila,applycon moderación. - Controla la forma con
as_indexyreset_index. - Gestiona orden y etiquetas faltantes con
sortydropna. - Prioriza métodos vectorizados para mantener rápida la canalización.