Pandas práctico (10): Operaciones de grupo usando groupby

A veces necesitas realizar operaciones en subconjuntos de datos. Tus filas pueden tener atributos en común o de alguna manera formar grupos lógicos basados en otras propiedades. Operaciones comunes como encontrar el promedio, máximo, conteo o desviación estándar de valores de grupos de datos es una tarea realmente común, y Pandas hace que esto sea muy fácil de lograr.

En este artículo, aprenderemos cómo usar la función groupby y estudiaremos algunas de las agregaciones integradas que puedes ejecutar en grupos. Esto te dará otra herramienta valiosa para el análisis de datos, y espero que te ayude a realizar tus tareas de una manera mucho más simple.

¡Genial, empecemos!

Cargando nuestros datos de ejemplo

Usaremos datos de un archivo CSV que creé con información sobre 16 Pokémon. Contiene atributos como el Nombre, Color (Verde, Azul, Rojo, Amarillo), y otras estadísticas como HP, Ataque, Defensa y Velocidad.

Estamos interesados en calcular algunas agregaciones comunes sobre grupos de Pokémon con diferentes colores.

import pandas as pd

pdata = pd.read_csv('./sample_data/poke_colors.csv')
pdata
Name Color Evolves HP Attack Defense SpAtk SpDef Speed
0 Caterpie Green True 45 30 35 20 20 45
1 Metapod Green True 50 20 55 25 25 30
2 Scyther Green False 70 110 80 55 80 105
3 Bulbasaur Green True 45 49 49 65 65 45
4 Dratini Blue True 41 64 45 50 50 50
5 Squirtle Blue True 44 48 65 50 64 43
6 Poliwag Blue True 40 50 40 40 40 90
7 Poliwhirl Blue True 65 65 65 50 50 90
8 Charmander Red True 39 52 43 60 50 65
9 Magmar Red False 65 95 57 100 85 93
10 Paras Red True 35 70 55 45 55 25
11 Parasect Red False 60 95 80 60 80 30
12 Pikachu Yellow True 35 55 40 50 50 90
13 Abra Yellow True 25 20 15 105 55 90
14 Psyduck Yellow True 50 52 48 65 50 55
15 Kadabra Yellow True 40 35 30 120 70 10

GroupBy

La función groupby de Pandas se usa para crear objetos GroupBy. Estos objetos pueden realizar muchas agregaciones integradas útiles con solo una llamada de función. groupby recibe como argumento una lista de claves que deciden cómo se realiza la agrupación. En nuestro primer ejemplo agruparemos los Pokémon por color:

pg = pdata.groupby('Color')
pg
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x7ff848e80f28>

Como puedes haber visto en el mensaje de arriba, obtuvimos un objeto de tipo GroupBy. Si quieres obtener un poco más de información sobre él, usa el método size:

pg.size()
Color
Blue      4
Green     4
Red       4
Yellow    4
dtype: int64

Otra forma de obtener información básica sobre el grupo es usando el atributo groups (es un diccionario):

pg.groups
{'Blue': Int64Index([4, 5, 6, 7], dtype='int64'),
 'Green': Int64Index([0, 1, 2, 3], dtype='int64'),
 'Red': Int64Index([8, 9, 10, 11], dtype='int64'),
 'Yellow': Int64Index([12, 13, 14, 15], dtype='int64')}

¡Excelente!

Ahora que sabes cómo crear un grupo básico, revisemos dos propiedades importantes:

# La primera es que puedes iterar sobre objetos GroupBy.

for color, group in pg:
    print(color)
    print(group)
    print('\n\n')
Blue
        Name Color  Evolves  HP  Attack  Defense  SpAtk  SpDef  Speed
4    Dratini  Blue     True  41      64       45     50     50     50
5   Squirtle  Blue     True  44      48       65     50     64     43
6    Poliwag  Blue     True  40      50       40     40     40     90
7  Poliwhirl  Blue     True  65      65       65     50     50     90



Green
        Name  Color  Evolves  HP  Attack  Defense  SpAtk  SpDef  Speed
0   Caterpie  Green     True  45      30       35     20     20     45
1    Metapod  Green     True  50      20       55     25     25     30
2    Scyther  Green    False  70     110       80     55     80    105
3  Bulbasaur  Green     True  45      49       49     65     65     45



Red
          Name Color  Evolves  HP  Attack  Defense  SpAtk  SpDef  Speed
8   Charmander   Red     True  39      52       43     60     50     65
9       Magmar   Red    False  65      95       57    100     85     93
10       Paras   Red     True  35      70       55     45     55     25
11    Parasect   Red    False  60      95       80     60     80     30



Yellow
       Name   Color  Evolves  HP  Attack  Defense  SpAtk  SpDef  Speed
12  Pikachu  Yellow     True  35      55       40     50     50     90
13     Abra  Yellow     True  25      20       15    105     55     90
14  Psyduck  Yellow     True  50      52       48     65     50     55
15  Kadabra  Yellow     True  40      35       30    120     70     10
# La segunda es que puedes acceder a los subgrupos proporcionando la clave correcta al método get_group
pg.get_group('Blue')
Name Color Evolves HP Attack Defense SpAtk SpDef Speed
4 Dratini Blue True 41 64 45 50 50 50
5 Squirtle Blue True 44 48 65 50 64 43
6 Poliwag Blue True 40 50 40 40 40 90
7 Poliwhirl Blue True 65 65 65 50 50 90

Los grupos son interesantes porque te permiten calcular agregaciones en subsecciones definidas por el usuario de tus datos. Calculemos la media de cada estadística para los grupos de colores:

#Evolves es booleano, por lo que True será tratado como 1 y False como 0 al calcular la media
pg.mean()
Evolves HP Attack Defense SpAtk SpDef Speed
Color
Blue 1.00 47.50 56.75 53.75 47.50 51.00 68.25
Green 0.75 52.50 52.25 54.75 41.25 47.50 56.25
Red 0.50 49.75 78.00 58.75 66.25 67.50 53.25
Yellow 1.00 37.50 40.50 33.25 85.00 56.25 61.25

Agrupando solo un subconjunto de columnas

A veces lo que quieres es crear grupos con solo una o dos columnas de interés. Si este es el caso, puedes seleccionarlas (y la columna por la que quieres agrupar) en el dataframe y luego simplemente llamar a groupby:

# Creemos grupos con solo los valores de HP y luego calculemos la media
pdata[['HP', 'Color']].groupby('Color').mean()
HP
Color
Blue 47.50
Green 52.50
Red 49.75
Yellow 37.50
# Hagamos lo mismo para Attack, Defense y Speed
pdata[['Color', 'Attack', 'Defense', 'Speed']].groupby('Color').mean()
Attack Defense Speed
Color
Blue 56.75 53.75 68.25
Green 52.25 54.75 56.25
Red 78.00 58.75 53.25
Yellow 40.50 33.25 61.25

Grupos con múltiples claves

Puedes crear grupos usando más de una columna como clave. Si pasas una lista de claves al método groupby, crearás una agrupación jerárquica. Agrupemos por los atributos Color y Evolves:

opg = pdata.groupby(['Color', 'Evolves'])
opg.size()
Color   Evolves
Blue    True       4
Green   False      1
        True       3
Red     False      2
        True       2
Yellow  True       4
dtype: int64
opg.groups
{('Blue', True): Int64Index([4, 5, 6, 7], dtype='int64'),
 ('Green', False): Int64Index([2], dtype='int64'),
 ('Green', True): Int64Index([0, 1, 3], dtype='int64'),
 ('Red', False): Int64Index([9, 11], dtype='int64'),
 ('Red', True): Int64Index([8, 10], dtype='int64'),
 ('Yellow', True): Int64Index([12, 13, 14, 15], dtype='int64')}

Agregaciones comunes:

Algunas agregaciones integradas ya vienen implementadas en Pandas, y también puedes definir las tuyas propias si las necesitas. En la siguiente sección, ejecutaremos las siguientes funciones para tener una idea del comportamiento predeterminado de Pandas. Todas estas funciones toman en consideración solo valores no-NA:

  • count: Número de elementos en cada grupo.
  • sum: Suma de valores en cada grupo.
  • mean: Media aritmética de cada grupo.
  • max: Valor máximo de cada grupo
  • min: Valor mínimo de cada grupo
  • std: Desviación estándar de cada grupo.
  • var: Varianza de cada grupo.

También, es importante saber que no necesitas asignar el grupo a una variable. Muy a menudo, simplemente lo usarás como un valor intermedio para llamar a una agregación. Así que, en lugar de escribir esto:

pg = pdata.groupby('Color')
pg.mean()

Puedes escribir esto:

pdata.groupby('Color').mean()
# Eliminemos las propiedades Name y Evolves para crear un nuevo dataframe
spdata = pdata.drop(['Name', 'Evolves'], axis=1)
spdata
Color HP Attack Defense SpAtk SpDef Speed
0 Green 45 30 35 20 20 45
1 Green 50 20 55 25 25 30
2 Green 70 110 80 55 80 105
3 Green 45 49 49 65 65 45
4 Blue 41 64 45 50 50 50
5 Blue 44 48 65 50 64 43
6 Blue 40 50 40 40 40 90
7 Blue 65 65 65 50 50 90
8 Red 39 52 43 60 50 65
9 Red 65 95 57 100 85 93
10 Red 35 70 55 45 55 25
11 Red 60 95 80 60 80 30
12 Yellow 35 55 40 50 50 90
13 Yellow 25 20 15 105 55 90
14 Yellow 50 52 48 65 50 55
15 Yellow 40 35 30 120 70 10
# Contemos los elementos en cada grupo
spdata.groupby('Color').count()
HP Attack Defense SpAtk SpDef Speed
Color
Blue 4 4 4 4 4 4
Green 4 4 4 4 4 4
Red 4 4 4 4 4 4
Yellow 4 4 4 4 4 4
# Calculemos la suma en cada grupo
spdata.groupby('Color').sum()
HP Attack Defense SpAtk SpDef Speed
Color
Blue 190 227 215 190 204 273
Green 210 209 219 165 190 225
Red 199 312 235 265 270 213
Yellow 150 162 133 340 225 245
# Calculemos la media en cada grupo
spdata.groupby('Color').mean()
HP Attack Defense SpAtk SpDef Speed
Color
Blue 47.50 56.75 53.75 47.50 51.00 68.25
Green 52.50 52.25 54.75 41.25 47.50 56.25
Red 49.75 78.00 58.75 66.25 67.50 53.25
Yellow 37.50 40.50 33.25 85.00 56.25 61.25
# Encontremos el máximo de cada estadística para cada grupo
spdata.groupby('Color').max()
HP Attack Defense SpAtk SpDef Speed
Color
Blue 65 65 65 50 64 90
Green 70 110 80 65 80 105
Red 65 95 80 100 85 93
Yellow 50 55 48 120 70 90
# Encontremos el mínimo de cada estadística para cada grupo
spdata.groupby('Color').min()
HP Attack Defense SpAtk SpDef Speed
Color
Blue 40 48 40 40 40 43
Green 45 20 35 20 20 30
Red 35 52 43 45 50 25
Yellow 25 20 15 50 50 10
# Calculemos la desviación estándar de cada estadística para cada grupo
spdata.groupby('Color').std()
HP Attack Defense SpAtk SpDef Speed
Color
Blue 11.789826 8.995369 13.149778 5.000000 9.865766 25.276801
Green 11.902381 40.335055 18.803812 22.126530 29.580399 33.260337
Red 14.952703 20.960280 15.456929 23.584953 17.559423 31.920474
Yellow 10.408330 16.258331 14.221463 32.914029 9.464847 37.941841
# Calculemos la varianza de cada estadística para cada subgrupo
spdata.groupby('Color').var()
HP Attack Defense SpAtk SpDef Speed
Color
Blue 139.000000 80.916667 172.916667 25.000000 97.333333 638.916667
Green 141.666667 1626.916667 353.583333 489.583333 875.000000 1106.250000
Red 223.583333 439.333333 238.916667 556.250000 308.333333 1018.916667
Yellow 108.333333 264.333333 202.250000 1083.333333 89.583333 1439.583333

Agregaciones definidas por el usuario

Pandas tiene un método llamado agg que te permite definir y ejecutar tus propias agregaciones sobre grupos. En el siguiente ejemplo, definiremos una función que calcula la suma de los cuadrados de las estadísticas en cada grupo.

def sum_of_squares(arr):
    sos = 0
    for element in arr:
        sos += element**2
    return sos

spdata.groupby('Color').agg(sum_of_squares)
HP Attack Defense SpAtk SpDef Speed
Color
Blue 9442 13125 12075 9100 10696 20549
Green 11450 15801 13051 8275 11650 15975
Red 10571 25654 14523 19225 19150 14399
Yellow 5950 7354 5029 32150 12925 19325

¡Las agregaciones son poderosas!

Agrupar datos para análisis usando Pandas es eficiente, elegante y poderoso. Las bases de datos relacionales tradicionales deben parte de su gran popularidad a su capacidad para realizar este tipo de funcionalidad, y ahora puedes hacer gran parte del mismo trabajo usando Python.

Esta última función (agg) podría ser una de las capacidades más útiles que proporciona Pandas. Las agregaciones definidas por el usuario son increíblemente poderosas. Te permiten calcular cualquier función loca que se te ocurra, de una manera repetible y consistente.

Esta característica es tan poderosa que dedicaremos el próximo artículo a expandir el tema: Hablaremos sobre el poderoso método apply.

¡Gracias por leer!

Qué hacer a continuación

Juan Luis Orozco Villalobos

¡Hola! Soy Juan, ingeniero de software y consultor en Budapest. Me especializo en computación en la nube e IA, y me encanta ayudar a otros a aprender sobre tecnología e ingeniería