Obtenez des statistiques pour chaque groupe (comme le nombre, la moyenne, etc.) à l'aide de pandas GroupBy?

439

J'ai un bloc de données dfet j'utilise plusieurs colonnes pour groupby:

df['col1','col2','col3','col4'].groupby(['col1','col2']).mean()

De la manière ci-dessus, j'obtiens presque la table (trame de données) dont j'ai besoin. Ce qui manque, c'est une colonne supplémentaire qui contient le nombre de lignes dans chaque groupe. En d'autres termes, je veux dire, mais j'aimerais aussi savoir combien de numéros ont été utilisés pour obtenir ces moyens. Par exemple, dans le premier groupe, il y a 8 valeurs et dans le second 10 et ainsi de suite.

En bref: comment obtenir des statistiques par groupe pour une trame de données?

romain
la source

Réponses:

427

Sur l' groupbyobjet, la aggfonction peut prendre une liste pour appliquer plusieurs méthodes d'agrégation à la fois. Cela devrait vous donner le résultat dont vous avez besoin:

df[['col1', 'col2', 'col3', 'col4']].groupby(['col1', 'col2']).agg(['mean', 'count'])
Boud
la source
2
Je pense que vous avez besoin de la référence de colonne pour être une liste. Voulez-vous dire peut-être: df[['col1','col2','col3','col4']].groupby(['col1','col2']).agg(['mean', 'count'])
rysqui
43
Cela crée quatre colonnes de comptage, mais comment en obtenir une seule? (La question demande "une colonne supplémentaire" et c'est ce que j'aimerais aussi.)
Jaan
16
Veuillez consulter ma réponse si vous souhaitez obtenir une seule countcolonne par groupe.
Pedro M Duarte
Que se passe-t-il si j'ai un dénommé Counts séparé et au lieu de compter les lignes du type groupé, je dois ajouter le long de la colonne Counts.
Abhishek Bhatia
@Jaan result = df['col1','col2','col3','col4'].groupby(['col1', 'col2']).mean() ; counts = times.groupby(['col1', 'col2']).size() ; result['count'] = counts
alvitawa
913

Réponse rapide:

Le moyen le plus simple d'obtenir le nombre de lignes par groupe est d'appeler .size(), ce qui renvoie a Series:

df.groupby(['col1','col2']).size()


Habituellement, vous voulez que ce résultat soit un DataFrame(au lieu d'un Series) afin que vous puissiez faire:

df.groupby(['col1', 'col2']).size().reset_index(name='counts')


Si vous souhaitez savoir comment calculer le nombre de lignes et d'autres statistiques pour chaque groupe, continuez à lire ci-dessous.


Exemple détaillé:

Considérez l'exemple de trame de données suivant:

In [2]: df
Out[2]: 
  col1 col2  col3  col4  col5  col6
0    A    B  0.20 -0.61 -0.49  1.49
1    A    B -1.53 -1.01 -0.39  1.82
2    A    B -0.44  0.27  0.72  0.11
3    A    B  0.28 -1.32  0.38  0.18
4    C    D  0.12  0.59  0.81  0.66
5    C    D -0.13 -1.65 -1.64  0.50
6    C    D -1.42 -0.11 -0.18 -0.44
7    E    F -0.00  1.42 -0.26  1.17
8    E    F  0.91 -0.47  1.35 -0.34
9    G    H  1.48 -0.63 -1.14  0.17

Utilisons d'abord .size()pour obtenir le nombre de lignes:

In [3]: df.groupby(['col1', 'col2']).size()
Out[3]: 
col1  col2
A     B       4
C     D       3
E     F       2
G     H       1
dtype: int64

Utilisons ensuite .size().reset_index(name='counts')pour obtenir le nombre de lignes:

In [4]: df.groupby(['col1', 'col2']).size().reset_index(name='counts')
Out[4]: 
  col1 col2  counts
0    A    B       4
1    C    D       3
2    E    F       2
3    G    H       1


Inclure les résultats pour plus de statistiques

Lorsque vous souhaitez calculer des statistiques sur des données groupées, cela ressemble généralement à ceci:

In [5]: (df
   ...: .groupby(['col1', 'col2'])
   ...: .agg({
   ...:     'col3': ['mean', 'count'], 
   ...:     'col4': ['median', 'min', 'count']
   ...: }))
Out[5]: 
            col4                  col3      
          median   min count      mean count
col1 col2                                   
A    B    -0.810 -1.32     4 -0.372500     4
C    D    -0.110 -1.65     3 -0.476667     3
E    F     0.475 -0.47     2  0.455000     2
G    H    -0.630 -0.63     1  1.480000     1

Le résultat ci-dessus est un peu ennuyeux à traiter en raison des étiquettes de colonne imbriquées et également parce que le nombre de lignes est basé sur une colonne.

Pour gagner plus de contrôle sur la sortie, je divise généralement les statistiques en agrégations individuelles que je combine ensuite en utilisant join. Cela ressemble à ceci:

In [6]: gb = df.groupby(['col1', 'col2'])
   ...: counts = gb.size().to_frame(name='counts')
   ...: (counts
   ...:  .join(gb.agg({'col3': 'mean'}).rename(columns={'col3': 'col3_mean'}))
   ...:  .join(gb.agg({'col4': 'median'}).rename(columns={'col4': 'col4_median'}))
   ...:  .join(gb.agg({'col4': 'min'}).rename(columns={'col4': 'col4_min'}))
   ...:  .reset_index()
   ...: )
   ...: 
Out[6]: 
  col1 col2  counts  col3_mean  col4_median  col4_min
0    A    B       4  -0.372500       -0.810     -1.32
1    C    D       3  -0.476667       -0.110     -1.65
2    E    F       2   0.455000        0.475     -0.47
3    G    H       1   1.480000       -0.630     -0.63



Notes de bas de page

Le code utilisé pour générer les données de test est indiqué ci-dessous:

In [1]: import numpy as np
   ...: import pandas as pd 
   ...: 
   ...: keys = np.array([
   ...:         ['A', 'B'],
   ...:         ['A', 'B'],
   ...:         ['A', 'B'],
   ...:         ['A', 'B'],
   ...:         ['C', 'D'],
   ...:         ['C', 'D'],
   ...:         ['C', 'D'],
   ...:         ['E', 'F'],
   ...:         ['E', 'F'],
   ...:         ['G', 'H'] 
   ...:         ])
   ...: 
   ...: df = pd.DataFrame(
   ...:     np.hstack([keys,np.random.randn(10,4).round(2)]), 
   ...:     columns = ['col1', 'col2', 'col3', 'col4', 'col5', 'col6']
   ...: )
   ...: 
   ...: df[['col3', 'col4', 'col5', 'col6']] = \
   ...:     df[['col3', 'col4', 'col5', 'col6']].astype(float)
   ...: 


Avertissement:

Si certaines des colonnes que vous agrégez ont des valeurs nulles, alors vous voulez vraiment regarder le nombre de lignes de groupe comme une agrégation indépendante pour chaque colonne. Sinon, vous pourriez être induit en erreur quant au nombre d'enregistrements réellement utilisés pour calculer des choses comme la moyenne, car les pandas supprimeront les NaNentrées dans le calcul de la moyenne sans vous en informer.

Pedro M Duarte
la source
1
Hé, j'aime vraiment votre solution, en particulier la dernière, où vous utilisez le chaînage de méthodes. Cependant, comme il est souvent nécessaire d'appliquer différentes fonctions d'agrégation à différentes colonnes, on pourrait également concaténer les trames de données résultantes à l'aide de pd.concat. C'est peut-être plus facile à lire que le chaînage de sous
file
4
belle solution, mais pour In [5]: counts_df = pd.DataFrame(df.groupby('col1').size().rename('counts')), il est peut-être préférable de définir la taille () comme une nouvelle colonne si vous souhaitez manipuler le cadre de données pour une analyse plus approfondie, ce qui devrait êtrecounts_df = pd.DataFrame(df.groupby('col1').size().reset_index(name='counts')
LancelotHolmes
2
Merci pour le bit "Inclure les résultats pour plus de statistiques"! Étant donné que ma prochaine recherche consistait à aplatir le multiindex résultant sur les colonnes, je lierai la réponse ici: stackoverflow.com/a/50558529/1026
Nickolay
Génial! Pourriez-vous s'il vous plaît me donner un indice sur la façon d'ajouter isnullà cette requête pour l'avoir également dans une colonne? 'col4': ['median', 'min', 'count', 'isnull']
Peter.k
38

Une fonction pour les gouverner tous: GroupBy.describe

Retours count, mean, stdet d' autres statistiques utiles par groupe.

df.groupby(['col1', 'col2'])['col3', 'col4'].describe()

# Setup
np.random.seed(0)
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                          'foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three',
                          'two', 'two', 'one', 'three'],
                   'C' : np.random.randn(8),
                   'D' : np.random.randn(8)})

from IPython.display import display

with pd.option_context('precision', 2):
    display(df.groupby(['A', 'B'])['C'].describe())

           count  mean   std   min   25%   50%   75%   max
A   B                                                     
bar one      1.0  0.40   NaN  0.40  0.40  0.40  0.40  0.40
    three    1.0  2.24   NaN  2.24  2.24  2.24  2.24  2.24
    two      1.0 -0.98   NaN -0.98 -0.98 -0.98 -0.98 -0.98
foo one      2.0  1.36  0.58  0.95  1.15  1.36  1.56  1.76
    three    1.0 -0.15   NaN -0.15 -0.15 -0.15 -0.15 -0.15
    two      2.0  1.42  0.63  0.98  1.20  1.42  1.65  1.87

Pour obtenir des statistiques spécifiques, il suffit de les sélectionner,

df.groupby(['A', 'B'])['C'].describe()[['count', 'mean']]

           count      mean
A   B                     
bar one      1.0  0.400157
    three    1.0  2.240893
    two      1.0 -0.977278
foo one      2.0  1.357070
    three    1.0 -0.151357
    two      2.0  1.423148

describefonctionne pour plusieurs colonnes ( passez ['C']à ['C', 'D']- ou supprimez-le complètement - et voyez ce qui se passe, le résultat est une trame de données à colonnes MultiIndexed).

Vous obtenez également différentes statistiques pour les données de chaîne. Voici un exemple,

df2 = df.assign(D=list('aaabbccc')).sample(n=100, replace=True)

with pd.option_context('precision', 2):
    display(df2.groupby(['A', 'B'])
               .describe(include='all')
               .dropna(how='all', axis=1))

              C                                                   D                
          count  mean       std   min   25%   50%   75%   max count unique top freq
A   B                                                                              
bar one    14.0  0.40  5.76e-17  0.40  0.40  0.40  0.40  0.40    14      1   a   14
    three  14.0  2.24  4.61e-16  2.24  2.24  2.24  2.24  2.24    14      1   b   14
    two     9.0 -0.98  0.00e+00 -0.98 -0.98 -0.98 -0.98 -0.98     9      1   c    9
foo one    22.0  1.43  4.10e-01  0.95  0.95  1.76  1.76  1.76    22      2   a   13
    three  15.0 -0.15  0.00e+00 -0.15 -0.15 -0.15 -0.15 -0.15    15      1   c   15
    two    26.0  1.49  4.48e-01  0.98  0.98  1.87  1.87  1.87    26      2   b   15

Pour plus d'informations, consultez la documentation .

cs95
la source
Toutes les distributions ne sont pas normales. IQR serait incroyable.
Brad
7

Nous pouvons facilement le faire en utilisant groupby et count. Mais, nous devons nous rappeler d'utiliser reset_index ().

df[['col1','col2','col3','col4']].groupby(['col1','col2']).count().\
reset_index()
Nimesh
la source
3
Cette solution fonctionne tant qu'il n'y a pas de valeur nulle dans les colonnes, sinon elle peut être trompeuse (le nombre sera inférieur au nombre réel d'observation par groupe).
Adrien Pacifico
4

Pour obtenir plusieurs statistiques, réduisez l'index et conservez les noms des colonnes:

df = df.groupby(['col1','col2']).agg(['mean', 'count'])
df.columns = [ ' '.join(str(i) for i in col) for col in df.columns]
df.reset_index(inplace=True)
df

Produit:

** entrez la description de l'image ici **

Jake Drew
la source
1

Créez un objet de groupe et appelez des méthodes comme dans l'exemple ci-dessous:

grp = df.groupby(['col1',  'col2',  'col3']) 

grp.max() 
grp.mean() 
grp.describe() 
Mahendra
la source
1

Veuillez essayer ce code

new_column=df[['col1', 'col2', 'col3', 'col4']].groupby(['col1', 'col2']).count()
df['count_it']=new_column
df

Je pense que le code ajoutera une colonne appelée 'count it' qui compte chaque groupe

Ichsan
la source