web-dev-qa-db-fra.com

Agrégation en pandas

  1. Comment effectuer une agrégation avec des pandas?
  2. Pas de DataFrame après agrégation! Qu'est-il arrivé?
  3. Comment agréger principalement des colonnes de chaînes (à lists, Tuples, strings with separator)?
  4. Comment agréger les comptes?
  5. Comment créer une nouvelle colonne remplie de valeurs agrégées?

J'ai vu ces questions récurrentes poser des questions sur divers aspects de la fonctionnalité d'agrégation pandas. La plupart des informations concernant l'agrégation et ses divers cas d'utilisation aujourd'hui sont fragmentées entre des dizaines de messages mal rédigés et non consultables. le but ici est de rassembler certains des points les plus importants pour la postérité.

Ce Q/A est censé être le prochain épisode d'une série de guides d'utilisation utiles:

Veuillez noter que ce message n'est pas destiné à remplacer documentation sur l'agrégation et à propos de groupby , alors lisez-le également!

13
jezrael

Question 1

Comment effectuer une agrégation avec pandas?

Développé documentation d'agrégation .

Les fonctions d'agrégation sont celles qui réduisent la dimension des objets renvoyés. Cela signifie que les séries/DataFrame de sortie ont moins ou les mêmes lignes que l'original. Certaines fonctions d'agrégation courantes sont présentées ci-dessous:

Une fonctionLa description
 moyenne () Calculer la moyenne des groupes 
 somme () Calculer la somme des valeurs de groupe 
 taille () Calculer la taille des groupes 
 count () Calculer le nombre de groupe 
 std () Écart type des groupes 
 var () Calculer la variance des groupes 
 sem () Erreur standard de la moyenne des groupes 
 describe () Génère des statistiques descriptives 
 first () Calculer la première des valeurs de groupe 
 last () Calculer la dernière des valeurs de groupe 
 nth () Prendre la nième valeur, ou un sous-ensemble si n est une liste 
 min () Calculer le minimum des valeurs de groupe 
 Max () Calculer le maximum des valeurs de groupe 
np.random.seed(123)

df = pd.DataFrame({'A' : ['foo', 'foo', 'bar', 'foo', 'bar', 'foo'],
                   'B' : ['one', 'two', 'three','two', 'two', 'one'],
                   'C' : np.random.randint(5, size=6),
                   'D' : np.random.randint(5, size=6),
                   'E' : np.random.randint(5, size=6)})
print (df)
     A      B  C  D  E
0  foo    one  2  3  0
1  foo    two  4  1  0
2  bar  three  2  1  1
3  foo    two  1  0  3
4  bar    two  3  1  4
5  foo    one  2  1  0

Agrégation par colonnes filtrées et fonctions implémentées par cython :

df1 = df.groupby(['A', 'B'], as_index=False)['C'].sum()
print (df1)
     A      B  C
0  bar  three  2
1  bar    two  3
2  foo    one  4
3  foo    two  5

Une fonction d'agrégation utilise pour toutes les colonnes sans spécification dans la fonction groupby, ici A, B colonnes:

df2 = df.groupby(['A', 'B'], as_index=False).sum()
print (df2)
     A      B  C  D  E
0  bar  three  2  1  1
1  bar    two  3  1  4
2  foo    one  4  4  0
3  foo    two  5  1  3

Vous pouvez également spécifier uniquement certaines colonnes utilisées pour l'agrégation dans la liste après la fonction groupby:

df3 = df.groupby(['A', 'B'], as_index=False)['C','D'].sum()
print (df3)
     A      B  C  D
0  bar  three  2  1
1  bar    two  3  1
2  foo    one  4  4
3  foo    two  5  1

Mêmes résultats en utilisant la fonction DataFrameGroupBy.agg :

df1 = df.groupby(['A', 'B'], as_index=False)['C'].agg('sum')
print (df1)
     A      B  C
0  bar  three  2
1  bar    two  3
2  foo    one  4
3  foo    two  5

df2 = df.groupby(['A', 'B'], as_index=False).agg('sum')
print (df2)
     A      B  C  D  E
0  bar  three  2  1  1
1  bar    two  3  1  4
2  foo    one  4  4  0
3  foo    two  5  1  3

Pour les fonctions multipliées appliquées à une colonne, utilisez la liste de Tuples - noms des nouvelles colonnes et des fonctions agrégées:

df4 = (df.groupby(['A', 'B'])['C']
         .agg([('average','mean'),('total','sum')])
         .reset_index())
print (df4)
     A      B  average  total
0  bar  three      2.0      2
1  bar    two      3.0      3
2  foo    one      2.0      4
3  foo    two      2.5      5

Si vous souhaitez passer plusieurs fonctions est possible passer list de Tuples:

df5 = (df.groupby(['A', 'B'])
         .agg([('average','mean'),('total','sum')]))

print (df5)
                C             D             E      
          average total average total average total
A   B                                              
bar three     2.0     2     1.0     1     1.0     1
    two       3.0     3     1.0     1     4.0     4
foo one       2.0     4     2.0     4     0.0     0
    two       2.5     5     0.5     1     1.5     3

Obtenez ensuite MultiIndex dans les colonnes:

print (df5.columns)
MultiIndex(levels=[['C', 'D', 'E'], ['average', 'total']],
           labels=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]])

Et pour convertir en colonnes, aplatir MultiIndex utilisez map avec join:

df5.columns = df5.columns.map('_'.join)
df5 = df5.reset_index()
print (df5)
     A      B  C_average  C_total  D_average  D_total  E_average  E_total
0  bar  three        2.0        2        1.0        1        1.0        1
1  bar    two        3.0        3        1.0        1        4.0        4
2  foo    one        2.0        4        2.0        4        0.0        0
3  foo    two        2.5        5        0.5        1        1.5        3

Une autre solution est de passer la liste des fonctions d'agrégation, puis d'aplatir MultiIndex et pour une autre colonne, les noms utilisent str.replace :

df5 = df.groupby(['A', 'B']).agg(['mean','sum'])

df5.columns = (df5.columns.map('_'.join)
                  .str.replace('sum','total')
                  .str.replace('mean','average'))
df5 = df5.reset_index()
print (df5)
     A      B  C_average  C_total  D_average  D_total  E_average  E_total
0  bar  three        2.0        2        1.0        1        1.0        1
1  bar    two        3.0        3        1.0        1        4.0        4
2  foo    one        2.0        4        2.0        4        0.0        0
3  foo    two        2.5        5        0.5        1        1.5        3

Si vous voulez spécifier chaque colonne avec une fonction agrégée, passez séparément dictionary:

df6 = (df.groupby(['A', 'B'], as_index=False)
         .agg({'C':'sum','D':'mean'})
         .rename(columns={'C':'C_total', 'D':'D_average'}))
print (df6)
     A      B  C_total  D_average
0  bar  three        2        1.0
1  bar    two        3        1.0
2  foo    one        4        2.0
3  foo    two        5        0.5

Vous pouvez également passer une fonction personnalisée:

def func(x):
    return x.iat[0] + x.iat[-1]

df7 = (df.groupby(['A', 'B'], as_index=False)
         .agg({'C':'sum','D': func})
         .rename(columns={'C':'C_total', 'D':'D_sum_first_and_last'}))
print (df7)
     A      B  C_total  D_sum_first_and_last
0  bar  three        2                     2
1  bar    two        3                     2
2  foo    one        4                     4
3  foo    two        5                     1

Question 2

Pas de DataFrame après agrégation! Qu'est-il arrivé?

Agrégation par 2 colonnes ou plus:

df1 = df.groupby(['A', 'B'])['C'].sum()
print (df1)
A    B    
bar  three    2
     two      3
foo  one      4
     two      5
Name: C, dtype: int32

Vérifiez d'abord Index et type de pandas object:

print (df1.index)
MultiIndex(levels=[['bar', 'foo'], ['one', 'three', 'two']],
           labels=[[0, 0, 1, 1], [1, 2, 0, 2]],
           names=['A', 'B'])

print (type(df1))
<class 'pandas.core.series.Series'>

Il existe 2 solutions pour obtenir MultiIndex Series aux colonnes:

  • ajouter le paramètre as_index=False
df1 = df.groupby(['A', 'B'], as_index=False)['C'].sum()
print (df1)
     A      B  C
0  bar  three  2
1  bar    two  3
2  foo    one  4
3  foo    two  5
df1 = df.groupby(['A', 'B'])['C'].sum().reset_index()
print (df1)
     A      B  C
0  bar  three  2
1  bar    two  3
2  foo    one  4
3  foo    two  5

Si groupé par une colonne:

df2 = df.groupby('A')['C'].sum()
print (df2)
A
bar    5
foo    9
Name: C, dtype: int32

... obtenez Series avec Index:

print (df2.index)
Index(['bar', 'foo'], dtype='object', name='A')

print (type(df2))
<class 'pandas.core.series.Series'>

Et la solution est la même que dans MultiIndex Series:

df2 = df.groupby('A', as_index=False)['C'].sum()
print (df2)
     A  C
0  bar  5
1  foo  9

df2 = df.groupby('A')['C'].sum().reset_index()
print (df2)
     A  C
0  bar  5
1  foo  9

Question 3

Comment agréger principalement des colonnes de chaînes (à lists, Tuples, strings with separator)?

df = pd.DataFrame({'A' : ['a', 'c', 'b', 'b', 'a', 'c', 'b'],
                   'B' : ['one', 'two', 'three','two', 'two', 'one', 'three'],
                   'C' : ['three', 'one', 'two', 'two', 'three','two', 'one'],
                   'D' : [1,2,3,2,3,1,2]})
print (df)
   A      B      C  D
0  a    one  three  1
1  c    two    one  2
2  b  three    two  3
3  b    two    two  2
4  a    two  three  3
5  c    one    two  1
6  b  three    one  2

Au lieu de la fonction aggregeta, il est possible de passer list, Tuple, set pour convertir la colonne:

df1 = df.groupby('A')['B'].agg(list).reset_index()
print (df1)
   A                    B
0  a           [one, two]
1  b  [three, two, three]
2  c           [two, one]

L'alternative est d'utiliser GroupBy.apply :

df1 = df.groupby('A')['B'].apply(list).reset_index()
print (df1)
   A                    B
0  a           [one, two]
1  b  [three, two, three]
2  c           [two, one]

Pour convertir en chaînes avec séparateur, utilisez .join uniquement si la colonne chaîne:

df2 = df.groupby('A')['B'].agg(','.join).reset_index()
print (df2)
   A                B
0  a          one,two
1  b  three,two,three
2  c          two,one

Si la colonne numérique utilise la fonction lambda avec astype pour la conversion en strings:

df3 = (df.groupby('A')['D']
         .agg(lambda x: ','.join(x.astype(str)))
         .reset_index())
print (df3)
   A      D
0  a    1,3
1  b  3,2,2
2  c    2,1

Une autre solution consiste à convertir en chaînes avant groupby:

df3 = (df.assign(D = df['D'].astype(str))
         .groupby('A')['D']
         .agg(','.join).reset_index())
print (df3)
   A      D
0  a    1,3
1  b  3,2,2
2  c    2,1

Pour convertir toutes les colonnes, ne passez aucune liste de colonnes après groupby. Il n'y a pas de colonne D car exclusion automatique des colonnes "nuisances" , cela signifie que toutes les colonnes numériques sont exclues.

df4 = df.groupby('A').agg(','.join).reset_index()
print (df4)
   A                B            C
0  a          one,two  three,three
1  b  three,two,three  two,two,one
2  c          two,one      one,two

Il est donc nécessaire de convertir toutes les colonnes en chaînes, puis d'obtenir toutes les colonnes:

df5 = (df.groupby('A')
         .agg(lambda x: ','.join(x.astype(str)))
         .reset_index())
print (df5)
   A                B            C      D
0  a          one,two  three,three    1,3
1  b  three,two,three  two,two,one  3,2,2
2  c          two,one      one,two    2,1

Question 4

Comment agréger les comptes?

df = pd.DataFrame({'A' : ['a', 'c', 'b', 'b', 'a', 'c', 'b'],
                   'B' : ['one', 'two', 'three','two', 'two', 'one', 'three'],
                   'C' : ['three', np.nan, np.nan, 'two', 'three','two', 'one'],
                   'D' : [np.nan,2,3,2,3,np.nan,2]})
print (df)
   A      B      C    D
0  a    one  three  NaN
1  c    two    NaN  2.0
2  b  three    NaN  3.0
3  b    two    two  2.0
4  a    two  three  3.0
5  c    one    two  NaN
6  b  three    one  2.0

Une fonction GroupBy.size pour size de chaque groupe:

df1 = df.groupby('A').size().reset_index(name='COUNT')
print (df1)
   A  COUNT
0  a      2
1  b      3
2  c      2

Une fonction GroupBy.count exclure les valeurs manquantes:

df2 = df.groupby('A')['C'].count().reset_index(name='COUNT')
print (df2)
   A  COUNT
0  a      2
1  b      2
2  c      1

La fonction doit être utilisée pour plusieurs colonnes pour compter les valeurs non manquantes:

df3 = df.groupby('A').count().add_suffix('_COUNT').reset_index()
print (df3)
   A  B_COUNT  C_COUNT  D_COUNT
0  a        2        2        1
1  b        3        2        3
2  c        2        1        1

Fonction associée Series.value_counts retourne un objet de taille contenant le nombre de valeurs uniques dans l'ordre décroissant afin que le premier élément soit l'élément le plus fréquent. Exclut les valeurs de NaNs par défaut.

df4 = (df['A'].value_counts()
              .rename_axis('A')
              .reset_index(name='COUNT'))
print (df4)
   A  COUNT
0  b      3
1  a      2
2  c      2

Si vous voulez la même sortie que l'utilisation de la fonction groupby + size add Series.sort_index :

df5 = (df['A'].value_counts()
              .sort_index()
              .rename_axis('A')
              .reset_index(name='COUNT'))
print (df5)
   A  COUNT
0  a      2
1  b      3
2  c      2

Question 5

Comment créer une nouvelle colonne remplie de valeurs agrégées?

Méthode GroupBy.transform renvoie un objet qui est indexé de la même (même taille) que celui qui est groupé

Pandas documentation pour plus d'informations.

np.random.seed(123)

df = pd.DataFrame({'A' : ['foo', 'foo', 'bar', 'foo', 'bar', 'foo'],
                    'B' : ['one', 'two', 'three','two', 'two', 'one'],
                    'C' : np.random.randint(5, size=6),
                    'D' : np.random.randint(5, size=6)})
print (df)
     A      B  C  D
0  foo    one  2  3
1  foo    two  4  1
2  bar  three  2  1
3  foo    two  1  0
4  bar    two  3  1
5  foo    one  2  1


df['C1'] = df.groupby('A')['C'].transform('sum')
df['C2'] = df.groupby(['A','B'])['C'].transform('sum')


df[['C3','D3']] = df.groupby('A')['C','D'].transform('sum')
df[['C4','D4']] = df.groupby(['A','B'])['C','D'].transform('sum')

print (df)

     A      B  C  D  C1  C2  C3  D3  C4  D4
0  foo    one  2  3   9   4   9   5   4   4
1  foo    two  4  1   9   5   9   5   5   1
2  bar  three  2  1   5   2   5   2   2   1
3  foo    two  1  0   9   5   9   5   5   1
4  bar    two  3  1   5   3   5   2   3   1
5  foo    one  2  1   9   4   9   5   4   4
19
jezrael