Groupby¶
Groupby es un concepto bastante simple. Podemos crear una agrupación de categorías y aplicar una función a las categorías.
El proceso de groupby se puede resumiren los siguientes pasos:
- División: es un proceso en el que dividimos los datos en grupos aplicando algunas condiciones en los conjuntos de datos.
- Aplicación: es un proceso en el que aplicamos una función a cada grupo de forma independiente
- Combinación: es un proceso en el que combinamos diferentes conjuntos de datos después de aplicar groupby y resultados en una estructura de datos
Después de dividir los datos en un grupo, aplicamos una función a cada grupo para realizar algunas operaciones que son:
- Agregación: es un proceso en el que calculamos una estadística resumida (o estadística) sobre cada grupo. Por ejemplo, Calcular sumas de grupo o medios
- Transformación: es un proceso en el que realizamos algunos cálculos específicos del grupo y devolvemos un índice similar. Por ejemplo, llenar NA dentro de grupos con un valor derivado de cada grupo
- Filtración: es un proceso en el cual descartamos algunos grupos, de acuerdo con un cálculo grupal que evalúa Verdadero o Falso. Por ejemplo, Filtrar datos en función de la suma o media grupal
Aplicación¶
Para comprender mejor el concepto de agrupación de tablas, se realiza un ejercicio simple sobre el conjunto de datos pokemon.csv
In [1]:
Copied!
# libreria
import pandas as pd
import numpy as np
import os
# libreria
import pandas as pd
import numpy as np
import os
In [2]:
Copied!
# cargar datos
url='https://drive.google.com/file/d/1uVJcfTBWemOTfxmjWTqALfUpIMdH3LVV/view?usp=drive_link'
url='https://drive.google.com/uc?id=' + url.split('/')[-2]
pokemon_data = pd.read_csv(url, sep=",")
pokemon_data.head()
# cargar datos
url='https://drive.google.com/file/d/1uVJcfTBWemOTfxmjWTqALfUpIMdH3LVV/view?usp=drive_link'
url='https://drive.google.com/uc?id=' + url.split('/')[-2]
pokemon_data = pd.read_csv(url, sep=",")
pokemon_data.head()
Out[2]:
# | Name | Type 1 | Type 2 | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 45 | 49 | 49 | 65 | 65 | 45 | 1 | False |
1 | 2 | Ivysaur | Grass | Poison | 60 | 62 | 63 | 80 | 80 | 60 | 1 | False |
2 | 3 | Venusaur | Grass | Poison | 80 | 82 | 83 | 100 | 100 | 80 | 1 | False |
3 | 4 | Mega Venusaur | Grass | Poison | 80 | 100 | 123 | 122 | 120 | 80 | 1 | False |
4 | 5 | Charmander | Fire | NaN | 39 | 52 | 43 | 60 | 50 | 65 | 1 | False |
In [3]:
Copied!
# renombrar columnas
pokemon_data.columns = pokemon_data.columns.str.lower().str.replace('.','_').str.replace(' ','_') #change into upper case
pokemon_data.head()
# renombrar columnas
pokemon_data.columns = pokemon_data.columns.str.lower().str.replace('.','_').str.replace(' ','_') #change into upper case
pokemon_data.head()
C:\Users\franc\AppData\Local\Temp\ipykernel_7740\2937267274.py:2: FutureWarning: The default value of regex will change from True to False in a future version. In addition, single character regular expressions will *not* be treated as literal strings when regex=True. pokemon_data.columns = pokemon_data.columns.str.lower().str.replace('.','_').str.replace(' ','_') #change into upper case
Out[3]:
# | name | type_1 | type_2 | hp | attack | defense | sp__atk | sp__def | speed | generation | legendary | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | Bulbasaur | Grass | Poison | 45 | 49 | 49 | 65 | 65 | 45 | 1 | False |
1 | 2 | Ivysaur | Grass | Poison | 60 | 62 | 63 | 80 | 80 | 60 | 1 | False |
2 | 3 | Venusaur | Grass | Poison | 80 | 82 | 83 | 100 | 100 | 80 | 1 | False |
3 | 4 | Mega Venusaur | Grass | Poison | 80 | 100 | 123 | 122 | 120 | 80 | 1 | False |
4 | 5 | Charmander | Fire | NaN | 39 | 52 | 43 | 60 | 50 | 65 | 1 | False |
Número de pokemones por generación¶
In [4]:
Copied!
grupo = pokemon_data.groupby('generation')
grupo[['name']].count().reset_index()
grupo = pokemon_data.groupby('generation')
grupo[['name']].count().reset_index()
Out[4]:
generation | name | |
---|---|---|
0 | 1 | 165 |
1 | 2 | 106 |
2 | 3 | 160 |
3 | 4 | 121 |
4 | 5 | 165 |
5 | 6 | 82 |
Número de pokemones por par Tipo I y Tipo II¶
In [5]:
Copied!
grupo = pokemon_data.groupby(['type_1','type_2'])
grupo['name'].count().reset_index()
grupo = pokemon_data.groupby(['type_1','type_2'])
grupo['name'].count().reset_index()
Out[5]:
type_1 | type_2 | name | |
---|---|---|---|
0 | Bug | Electric | 2 |
1 | Bug | Fighting | 2 |
2 | Bug | Fire | 2 |
3 | Bug | Flying | 14 |
4 | Bug | Ghost | 1 |
... | ... | ... | ... |
131 | Water | Ice | 3 |
132 | Water | Poison | 3 |
133 | Water | Psychic | 5 |
134 | Water | Rock | 4 |
135 | Water | Steel | 1 |
136 rows × 3 columns
Calcular hp promedio y hp total agrupados si el pokemon es legendario o no¶
método 01: ocupando el comando agg¶
In [6]:
Copied!
# metodo 01: ocupando el comando agg
grupo = pokemon_data.groupby(['legendary'])
df_leng = grupo.agg({'hp':[np.mean,sum]}).reset_index()
df_leng
# metodo 01: ocupando el comando agg
grupo = pokemon_data.groupby(['legendary'])
df_leng = grupo.agg({'hp':[np.mean,sum]}).reset_index()
df_leng
Out[6]:
legendary | hp | ||
---|---|---|---|
mean | sum | ||
0 | False | 67.182313 | 49379 |
1 | True | 92.738462 | 6028 |
método 02: ocupando el comando apply¶
In [7]:
Copied!
# metodo 02: ocupando el comando apply
def my_custom_function(x):
"""
Funcion que calcula el hp promedio y total
"""
names = {
'mean': x['hp'].mean(),
'sum':x['hp'].sum()}
return pd.Series(names, index=['mean', 'sum'])
# metodo 02: ocupando el comando apply
def my_custom_function(x):
"""
Funcion que calcula el hp promedio y total
"""
names = {
'mean': x['hp'].mean(),
'sum':x['hp'].sum()}
return pd.Series(names, index=['mean', 'sum'])
In [8]:
Copied!
grupo = pokemon_data.groupby(['legendary'])
df_leng = grupo.apply(my_custom_function).reset_index()
df_leng
grupo = pokemon_data.groupby(['legendary'])
df_leng = grupo.apply(my_custom_function).reset_index()
df_leng
Out[8]:
legendary | mean | sum | |
---|---|---|---|
0 | False | 67.182313 | 49379.0 |
1 | True | 92.738462 | 6028.0 |
Normalizar las estadísticas agrupados por generación¶
In [9]:
Copied!
cols_statistics = [
'generation', 'hp',
'attack', 'defense',
'sp__atk','sp__def',
'speed'
]
grupo = pokemon_data[cols_statistics].groupby('generation')
sc = lambda x: (x - x.mean()) / x.std()
grupo.transform(sc)
cols_statistics = [
'generation', 'hp',
'attack', 'defense',
'sp__atk','sp__def',
'speed'
]
grupo = pokemon_data[cols_statistics].groupby('generation')
sc = lambda x: (x - x.mean()) / x.std()
grupo.transform(sc)
Out[9]:
hp | attack | defense | sp__atk | sp__def | speed | |
---|---|---|---|---|---|---|
0 | -0.739479 | -0.898969 | -0.763283 | -0.198010 | -0.160373 | -0.929521 |
1 | -0.206695 | -0.476132 | -0.274479 | 0.237542 | 0.427740 | -0.424060 |
2 | 0.503685 | 0.174386 | 0.423812 | 0.818277 | 1.211892 | 0.249889 |
3 | 0.503685 | 0.759852 | 1.820395 | 1.457086 | 1.996043 | 0.249889 |
4 | -0.952593 | -0.801391 | -0.972770 | -0.343193 | -0.748487 | -0.255573 |
... | ... | ... | ... | ... | ... | ... |
795 | -0.873754 | 0.829182 | 2.337149 | 0.808641 | 2.496477 | -0.639851 |
796 | -0.873754 | 2.885421 | 1.062058 | 2.695980 | 1.166968 | 1.695510 |
797 | 0.561116 | 1.171889 | -0.531806 | 2.381423 | 1.831723 | 0.138603 |
798 | 0.561116 | 2.885421 | -0.531806 | 3.010536 | 1.831723 | 0.527830 |
799 | 0.561116 | 1.171889 | 1.380831 | 1.752310 | 0.502214 | 0.138603 |
800 rows × 6 columns
Identificar generaciones que tienen menos de 100 pokemones¶
In [10]:
Copied!
grupo = pokemon_data[['name','generation']].groupby('generation')
grupo.filter(lambda x: len(x['name']) < 150)
grupo = pokemon_data[['name','generation']].groupby('generation')
grupo.filter(lambda x: len(x['name']) < 150)
Out[10]:
name | generation | |
---|---|---|
166 | Chikorita | 2 |
167 | Bayleef | 2 |
168 | Meganium | 2 |
169 | Cyndaquil | 2 |
170 | Quilava | 2 |
... | ... | ... |
795 | Diancie | 6 |
796 | Mega Diancie | 6 |
797 | Hoopa Confined | 6 |
798 | Hoopa Unbound | 6 |
799 | Volcanion | 6 |
309 rows × 2 columns