Agregación Avanzada de Datos con Pandas: El Poder de `groupby()` y `agg()`
Este tutorial profundiza en las capacidades de agregación de datos con Pandas, centrándose en el uso combinado y avanzado de `groupby()` y `agg()`. Aprenderás a realizar análisis multidimensionales y aplicar múltiples funciones de agregación simultáneamente, revelando patrones ocultos en tus conjuntos de datos.
La agregación de datos es una piedra angular en el análisis de datos. Nos permite resumir grandes volúmenes de información en métricas significativas, revelando tendencias, patrones y valores atípicos. En el ecosistema de Python, Pandas se erige como la biblioteca líder para esta tarea, ofreciendo herramientas extremadamente potentes y flexibles, especialmente a través de sus funciones groupby() y agg().
Este tutorial te guiará desde los conceptos básicos hasta técnicas avanzadas de agregación, permitiéndote transformar datos crudos en insights accionables. Preparáte para desatar el verdadero poder de Pandas en tu flujo de trabajo de ciencia de datos. 🚀
¿Por Qué Agregación? La Esencia del Resumen de Datos 📊
Imagina que tienes un conjunto de datos enorme sobre ventas. Quieres saber el total de ventas por región, el promedio de ventas por producto, o incluso el producto más vendido en cada trimestre. Intentar hacer esto manualmente sería una pesadilla. Aquí es donde entra la agregación. Simplifica la complejidad, condensando la información de manera que sea comprensible y útil para la toma de decisiones.
En esencia, la agregación implica:
- División: Separar los datos en grupos basados en uno o más criterios.
- Aplicación: Aplicar una función (como suma, promedio, conteo, máximo, mínimo) a cada grupo.
- Combinación: Unir los resultados de la función aplicada en un nuevo conjunto de datos.
Fundamentos de groupby() en Pandas ✨
groupby() es la función central para realizar agregaciones en Pandas. Permite agrupar filas de un DataFrame basándose en los valores de una o varias columnas. Una vez que los datos están agrupados, puedes aplicar una función de agregación a cada grupo.
Sintaxis Básica de groupby()
import pandas as pd
import numpy as np
# Crear un DataFrame de ejemplo
data = {
'Region': ['Norte', 'Sur', 'Este', 'Oeste', 'Norte', 'Sur', 'Este', 'Oeste', 'Norte', 'Sur'],
'Producto': ['A', 'B', 'A', 'C', 'B', 'A', 'C', 'B', 'A', 'C'],
'Ventas': [100, 150, 200, 50, 120, 180, 70, 130, 210, 60],
'Cantidad': [10, 15, 20, 5, 12, 18, 7, 13, 21, 6]
}
df = pd.DataFrame(data)
print("DataFrame Original:")
print(df)
Salida esperada:
DataFrame Original:
Region Producto Ventas Cantidad
0 Norte A 100 10
1 Sur B 150 15
2 Este A 200 20
3 Oeste C 50 5
4 Norte B 120 12
5 Sur A 180 18
6 Este C 70 7
7 Oeste B 130 13
8 Norte A 210 21
9 Sur C 60 6
Ahora, agrupemos por Region y calculemos la suma de las Ventas.
# Agrupar por 'Region' y sumar 'Ventas'
ventas_por_region = df.groupby('Region')['Ventas'].sum()
print("\nSuma de Ventas por Región:")
print(ventas_por_region)
Salida esperada:
Suma de Ventas por Región:
Region
Este 270
Norte 430
Oeste 180
Sur 390
Name: Ventas, dtype: int64
Agrupando por Múltiples Columnas
Puedes agrupar por más de una columna para un análisis más granular. Por ejemplo, Ventas por Region y Producto.
# Agrupar por 'Region' y 'Producto' y sumar 'Ventas'
ventas_region_producto = df.groupby(['Region', 'Producto'])['Ventas'].sum()
print("\nSuma de Ventas por Región y Producto:")
print(ventas_region_producto)
Salida esperada:
Suma de Ventas por Región y Producto:
Region Producto
Este A 200
C 70
Norte A 310
B 120
Oeste B 130
C 50
Sur A 180
B 150
C 60
Name: Ventas, dtype: int64
El resultado es una Series con un MultiIndex. Si prefieres un DataFrame más convencional, puedes usar .reset_index():
ventas_region_producto_df = df.groupby(['Region', 'Producto'])['Ventas'].sum().reset_index()
print("\nSuma de Ventas por Región y Producto (DataFrame):")
print(ventas_region_producto_df)
Salida esperada:
Suma de Ventas por Región y Producto (DataFrame):
Region Producto Ventas
0 Este A 200
1 Este C 70
2 Norte A 310
3 Norte B 120
4 Oeste B 130
5 Oeste C 50
6 Sur A 180
7 Sur B 150
8 Sur C 60
Agregación con agg(): Múltiples Funciones, Múltiples Columnas 🛠️
Mientras que groupby().sum(), groupby().mean(), etc., son útiles para una única agregación, agg() desata el verdadero poder al permitirte aplicar múltiples funciones de agregación a múltiples columnas de una sola vez.
Aplicando Múltiples Funciones a una Sola Columna
Imagina que quieres la suma y el promedio de Ventas por Region.
# Agrupar por 'Region' y aplicar múltiples funciones a 'Ventas'
agg_ventas = df.groupby('Region')['Ventas'].agg(['sum', 'mean', 'max'])
print("\nAgregación múltiple de Ventas por Región:")
print(agg_ventas)
Salida esperada:
Agregación múltiple de Ventas por Región:
sum mean max
Region
Este 270 135.000000 200
Norte 430 143.333333 210
Oeste 180 90.000000 130
Sur 390 130.000000 180
Puedes usar cadenas de texto para funciones comunes ('sum', 'mean', 'max', 'min', 'count', 'median', 'std', 'var'), o incluso pasar funciones personalizadas o funciones de NumPy.
# Usando funciones de NumPy
agg_np = df.groupby('Region')['Ventas'].agg([np.sum, np.mean])
print("\nAgregación con funciones NumPy:")
print(agg_np)
Salida esperada:
Agregación con funciones NumPy:
sum mean
Region
Este 270 135.000000
Norte 430 143.333333
Oeste 180 90.000000
Sur 390 130.000000
Aplicando Diferentes Funciones a Diferentes Columnas
Aquí es donde agg() realmente brilla. Puedes especificar un diccionario donde las claves son los nombres de las columnas y los valores son las funciones (o listas de funciones) a aplicar a esas columnas.
# Agrupar por 'Region' y aplicar diferentes funciones a 'Ventas' y 'Cantidad'
agg_multi_column = df.groupby('Region').agg({
'Ventas': ['sum', 'mean'],
'Cantidad': ['sum', 'min', 'max']
})
print("\nAgregación de múltiples columnas con diferentes funciones:")
print(agg_multi_column)
Salida esperada:
Agregación de múltiples columnas con diferentes funciones:
Ventas Cantidad
sum mean sum min max
Region
Este 270 135.000000 27 7 20
Norte 430 143.333333 43 10 21
Oeste 180 90.000000 18 5 13
Sur 390 130.000000 39 6 18
Observa el MultiIndex en las columnas. Esto es potente, pero a veces puede ser incómodo. Veremos cómo aplanarlo.
Renombrando Columnas Agregadas
Para una salida más limpia, puedes renombrar las columnas resultantes directamente en agg() utilizando tuplas o diccionarios anidados.
Usando Tuplas para Renombrar
# Renombrar columnas con tuplas (Nombre_Nuevo, Función_Existente)
agg_renombrado_tuplas = df.groupby('Region').agg(
Ventas_Total=('Ventas', 'sum'),
Ventas_Promedio=('Ventas', 'mean'),
Cantidad_Total=('Cantidad', 'sum')
)
print("\nAgregación con columnas renombradas (tuplas):")
print(agg_renombrado_tuplas)
Salida esperada:
Agregación con columnas renombradas (tuplas):
Ventas_Total Ventas_Promedio Cantidad_Total
Region
Este 270 135.000000 27
Norte 430 143.333333 43
Oeste 180 90.000000 18
Sur 390 130.000000 39
Esta es la forma preferida en versiones modernas de Pandas (0.25+), ya que es más explícita y fácil de leer.
Usando Diccionario Anidado (Sintaxis Antigua/Alternativa)
También puedes usar un diccionario de diccionarios, aunque la sintaxis de tuplas es generalmente más clara para este propósito.
# Renombrar columnas con diccionario anidado (alternativa)
agg_renombrado_dict = df.groupby('Region').agg({
'Ventas': [('Ventas_Total', 'sum'), ('Ventas_Promedio', 'mean')],
'Cantidad': [('Cantidad_Total', 'sum')]
})
print("\nAgregación con columnas renombradas (diccionario anidado):")
print(agg_renombrado_dict)
Salida esperada:
Agregación con columnas renombradas (diccionario anidado):
Ventas Cantidad
Ventas_Total Ventas_Promedio Cantidad_Total
Region
Este 270 135.000000 27
Norte 430 143.333333 43
Oeste 180 90.000000 18
Sur 390 130.000000 39
Aquí sigues teniendo un MultiIndex en las columnas, lo cual puede requerir un paso extra para aplanarlas. La sintaxis de tuplas es superior para evitar esto.
Aplanando MultiIndex de Columnas 🔨
Cuando aplicas múltiples funciones a múltiples columnas, a menudo terminas con un MultiIndex en las columnas, lo cual puede ser complicado para trabajar o exportar.
Opción 1: Renombrar directamente con tuplas (como se vio anteriormente)
Esta es la mejor opción si sabes de antemano qué columnas vas a generar y sus nombres finales.
Opción 2: Unir los nombres de las columnas
Si usaste la sintaxis de diccionario de columnas y funciones, puedes aplanar el MultiIndex combinando los nombres de nivel superior e inferior.
# Ejemplo con MultiIndex de columnas
agg_df = df.groupby('Region').agg({
'Ventas': ['sum', 'mean'],
'Cantidad': ['sum', 'min']
})
print("\nDataFrame con MultiIndex en Columnas:")
print(agg_df)
# Aplanar MultiIndex de columnas
agg_df.columns = ['_'.join(col).strip() for col in agg_df.columns.values]
print("\nDataFrame con columnas aplanadas:")
print(agg_df)
Salida esperada:
DataFrame con MultiIndex en Columnas:
Ventas Cantidad
sum mean sum min
Region
Este 270 135.000000 27 7
Norte 430 143.333333 43 10
Oeste 180 90.000000 18 5
Sur 390 130.000000 39 6
DataFrame con columnas aplanadas:
Ventas_sum Ventas_mean Cantidad_sum Cantidad_min
Region
Este 270 135.000000 27 7
Norte 430 143.333333 43 10
Oeste 180 90.000000 18 5
Sur 390 130.000000 39 6
Este método es muy flexible y permite generar nombres de columnas dinámicamente.
Funciones Personalizadas en agg() ⚙️
Una de las características más potentes de agg() es su capacidad para aceptar funciones personalizadas (lambdas o definidas por el usuario). Esto abre un mundo de posibilidades para agregaciones específicas que las funciones estándar no cubren.
Ejemplo con Función Lambda
Calcular el rango (max - min) de ventas por región:
# Calcular el rango de ventas usando una función lambda
ventas_rango = df.groupby('Region')['Ventas'].agg(Rango_Ventas=lambda x: x.max() - x.min())
print("\nRango de Ventas por Región (con lambda):")
print(ventas_rango)
Salida esperada:
Rango de Ventas por Región (con lambda):
Rango_Ventas
Region
Este 130
Norte 110
Oeste 80
Sur 120
Ejemplo con Función Definida por el Usuario
Calcula el coeficiente de variación (desviación estándar / media) para las ventas.
# Definir una función personalizada para el coeficiente de variación
def coeficiente_variacion(series):
if series.mean() == 0: # Evitar división por cero
return 0
return series.std() / series.mean()
# Aplicar la función personalizada
cv_ventas = df.groupby('Region')['Ventas'].agg(CV_Ventas=coeficiente_variacion)
print("\nCoeficiente de Variación de Ventas por Región:")
print(cv_ventas)
Salida esperada:
Coeficiente de Variación de Ventas por Región:
CV_Ventas
Region
Este 0.367680
Norte 0.339233
Oeste 0.628539
Sur 0.461880
Puedes combinar funciones personalizadas con las predefinidas:
agg_custom_mixed = df.groupby('Region').agg(
Ventas_Sum = ('Ventas', 'sum'),
Ventas_CV = ('Ventas', coeficiente_variacion),
Cantidad_Mean = ('Cantidad', 'mean')
)
print("\nAgregación mixta con funciones personalizadas y estándar:")
print(agg_custom_mixed)
Salida esperada:
Agregación mixta con funciones personalizadas y estándar:
Ventas_Sum Ventas_CV Cantidad_Mean
Region
Este 270 0.367680 13.500000
Norte 430 0.339233 14.333333
Oeste 180 0.628539 9.000000
Sur 390 0.461880 13.000000
Transformación vs. Agregación: El Dilema del Tamaño del Output 🧐
Es importante diferenciar entre agregación y transformación. Mientras que la agregación reduce el número de filas (un resultado por grupo), la transformación devuelve un DataFrame/Series con la misma forma que el original, pero con los valores transformados por grupo.
groupby().transform() es útil para rellenar valores nulos con la media del grupo, normalizar datos por grupo, o escalar características. No es el foco principal de este tutorial, pero es crucial conocer la diferencia.
¿Cuándo usar `transform()` en lugar de `agg()`?
`agg()` es para obtener **resúmenes** a nivel de grupo (e.g., la suma total de ventas por región). El resultado tiene menos filas que el original.transform() es para modificar el DataFrame original basándose en cálculos grupales, manteniendo la misma estructura. Por ejemplo, si quieres reemplazar los valores nulos de 'Edad' con la edad media de su 'Género'. El resultado tiene la misma cantidad de filas que el original.
Ejemplos Prácticos y Casos de Uso Avanzados 🚀
Vamos a explorar algunos escenarios más complejos que demuestran la flexibilidad de groupby() y agg().
1. Cálculo de Porcentajes por Grupo
Supongamos que queremos saber la proporción de ventas de cada producto dentro de su región.
# Agrupar por región y producto, luego sumar ventas
ventas_reg_prod = df.groupby(['Region', 'Producto'])['Ventas'].sum().reset_index()
# Calcular el total de ventas por región
ventas_totales_region = ventas_reg_prod.groupby('Region')['Ventas'].transform('sum')
# Calcular el porcentaje
ventas_reg_prod['Porcentaje_Region'] = (ventas_reg_prod['Ventas'] / ventas_totales_region * 100).round(2)
print("\nPorcentaje de Ventas de Producto por Región:")
print(ventas_reg_prod)
Salida esperada:
Porcentaje de Ventas de Producto por Región:
Region Producto Ventas Porcentaje_Region
0 Este A 200 74.07
1 Este C 70 25.93
2 Norte A 310 72.09
3 Norte B 120 27.91
4 Oeste B 130 72.22
5 Oeste C 50 27.78
6 Sur A 180 46.15
7 Sur B 150 38.46
8 Sur C 60 15.38
Aquí, transform('sum') fue clave para obtener la suma de ventas por región, que luego usamos para el cálculo del porcentaje dentro de cada grupo.
2. Análisis de Múltiples Métricas con Pivot Tables
Aunque agg() es muy potente, a veces una tabla pivote puede presentar los resultados de una manera más legible para ciertos tipos de análisis multidimensionales.
# Crear una tabla pivote para ver ventas y cantidad por región y producto
pivot_table = df.pivot_table(
index='Region',
columns='Producto',
values=['Ventas', 'Cantidad'],
aggfunc={'Ventas': 'sum', 'Cantidad': 'mean'}
)
print("\nTabla Pivote de Ventas y Cantidad por Región y Producto:")
print(pivot_table)
Salida esperada (puede variar ligeramente en la visualización, pero la estructura es similar):
Tabla Pivote de Ventas y Cantidad por Región y Producto:
Cantidad Ventas
Producto A B C A B C
Region
Este 20.0 NaN 7.0 200.0 NaN 70.0
Norte 15.5 12.0 NaN 310.0 120.0 NaN
Oeste NaN 13.0 5.0 NaN 130.0 50.0
Sur 18.0 15.0 6.0 180.0 150.0 60.0
3. Detección de Valores Atípicos por Grupo
Podemos usar groupby() y funciones personalizadas para identificar valores atípicos (outliers) dentro de cada grupo, por ejemplo, usando el rango intercuartílico (IQR).
# Función para detectar outliers usando IQR (valores fuera de 1.5 * IQR del cuartil)
def detect_outliers_iqr(series):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
return series[(series < lower_bound) | (series > upper_bound)]
# Agrupar por producto y aplicar la función para ventas
outliers_por_producto = df.groupby('Producto')['Ventas'].apply(detect_outliers_iqr)
print("\nOutliers de Ventas detectados por Producto (usando IQR por grupo):")
print(outliers_por_producto)
Salida esperada (si hubiera outliers en nuestro pequeño dataset):
Outliers de Ventas detectados por Producto (usando IQR por grupo):
Series([], Name: Ventas, dtype: int64)
En nuestro pequeño conjunto de datos, no hay outliers con este criterio, pero si los hubiera, aparecerían aquí con su índice original.
Consideraciones de Rendimiento y Optimización 🚀
Para conjuntos de datos muy grandes, el rendimiento de las operaciones groupby() y agg() puede ser crítico. Aquí hay algunos consejos:
- Usa funciones predefinidas de Pandas/NumPy: Son generalmente más rápidas que las funciones personalizadas de Python puro, ya que están implementadas en C.
- Evita
.apply()con funciones Python lentas: Si puedes lograr el mismo resultado conagg()otransform(), priorízalos. - Tipos de datos: Asegúrate de que tus columnas numéricas tengan tipos de datos apropiados (por ejemplo,
int32,float32) para reducir el uso de memoria y mejorar la velocidad. categorize(): Para columnas que usas para agrupar y tienen un número limitado de valores únicos (como'Region'o'Producto'), convertirlas a tipocategorypuede acelerargroupby().
# Convertir 'Region' y 'Producto' a tipo category
df_optimized = df.copy()
df_optimized['Region'] = df_optimized['Region'].astype('category')
df_optimized['Producto'] = df_optimized['Producto'].astype('category')
print("\nDataFrame con tipos de datos optimizados:")
print(df_optimized.info())
Salida esperada (la memoria usada debería ser menor):
DataFrame con tipos de datos optimizados:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10 entries, 0 to 9
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Region 10 non-null category
1 Producto 10 non-null category
2 Ventas 10 non-null int64
3 Cantidad 10 non-null int64
dtypes: category(2), int64(2)
memory usage: 986.0 bytes # <-- ¡Menos memoria!
Conclusión ✨
Dominar groupby() y agg() en Pandas es esencial para cualquier científico o analista de datos. Estas herramientas te permiten pasar de datos crudos a insights estructurados y significativos, un paso crucial en cualquier proyecto de análisis.
Hemos cubierto desde los fundamentos de la agregación hasta técnicas avanzadas como el uso de funciones personalizadas y la optimización del rendimiento. Con esta base, estás bien equipado para abordar casi cualquier tarea de resumen de datos que se te presente. ¡Sigue practicando y explorando! 🚀
Tutoriales relacionados
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!