tutoriales.com

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.

Intermedio18 min de lectura9 views
Reportar error

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.
💡 Consejo: La agregación no solo reduce la dimensionalidad de tus datos, sino que también es crucial para la ingeniería de características y la preparación de modelos de aprendizaje automático.

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
📌 Nota: Cuando aplicas una función de agregación directamente después de `groupby()`, Pandas selecciona automáticamente las columnas numéricas para aplicar la función, a menos que especifiques una columna como hicimos con `['Ventas']`.

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
🔥 Importante: Las funciones lambda y personalizadas operan sobre Series de Pandas (o sub-DataFrames si no especificas una columna), lo que significa que puedes usar todos los métodos de Series dentro de tu función.

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
📌 Nota: `pivot_table` es una abstracción de `groupby()` y `agg()` que organiza los datos de forma tabular con columnas y filas específicas. Es especialmente útil para visualizaciones rápidas de agregaciones multidimensionales.

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.

⚠️ Advertencia: `apply()` es más flexible que `agg()` y `transform()` porque puede devolver un objeto de cualquier forma. Sin embargo, puede ser más lento en grandes datasets, ya que no está tan optimizado como las funciones vectorizadas de Pandas o NumPy. Úsalo con cautela en producción para grandes volúmenes.
DataFrame Original Agrupar por División (groupby) Grupo A Grupo B Grupo C Aplicar Aplicación (agg) sum() Aplicación (agg) mean() Aplicación (agg) max() Unir Combinación (Resultado Final)

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 con agg() o transform(), 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 tipo category puede acelerar groupby().
# 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!
Dominando Agregaciones

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!