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
- Comparte este artículo con amigos y colegas. Gracias por ayudarme a llegar a personas que podrían encontrar útil esta información.
- Puedes encontrar el código fuente para esta serie en este repositorio.
- Este artículo está basado en el libro: Python for Data Analysis.
- Envíame un email con preguntas, comentarios o sugerencias (está en la página Autor)