Plusieurs agrégations de la même colonne en utilisant pandas GroupBy.agg ()

128

Existe-t-il un moyen intégré à Pandas d'appliquer deux fonctions d'agrégation différentes f1, f2à la même colonne df["returns"], sans avoir à appeler agg()plusieurs fois?

Exemple de dataframe:

import pandas as pd
import datetime as dt

pd.np.random.seed(0)
df = pd.DataFrame({
         "date"    :  [dt.date(2012, x, 1) for x in range(1, 11)], 
         "returns" :  0.05 * np.random.randn(10), 
         "dummy"   :  np.repeat(1, 10)
}) 

La manière syntaxiquement incorrecte, mais intuitivement correcte, de le faire serait:

# Assume `f1` and `f2` are defined for aggregating.
df.groupby("dummy").agg({"returns": f1, "returns": f2})

De toute évidence, Python n'autorise pas les clés en double. Existe-t-il une autre manière d'exprimer l'apport agg()? Peut-être qu'une liste de tuples [(column, function)]fonctionnerait mieux, pour autoriser plusieurs fonctions appliquées à la même colonne? Mais il agg()semble qu'il n'accepte qu'un dictionnaire.

Existe-t-il une solution de contournement pour cela en plus de définir une fonction auxiliaire qui applique simplement les deux fonctions à l'intérieur? (Comment cela fonctionnerait-il avec l'agrégation de toute façon?)

ely
la source
En relation - Agrégation chez les pandas
jezrael
2
À partir de 0.25, pandas fournit une syntaxe plus intuitive pour plusieurs agrégations, ainsi que pour renommer les colonnes de sortie. Consultez la documentation sur les agrégations nommées .
cs95 le
Pour info, cette question a été posée il y a longtemps sur pandas 0.8.x en 9/2012
smci
1
FYI, la réponse acceptée est également obsolète - ne passez pas agg () un dict of dicts.
cs95
@ cs95: Je sais que c'est obsolète, je dis que SO est en train de devenir jonché d'anciennes solutions périmées d'anciennes versions. SO n'a pas de moyen de marquer cela - autre que des commentaires.
smci le

Réponses:

159

Vous pouvez simplement passer les fonctions sous forme de liste:

In [20]: df.groupby("dummy").agg({"returns": [np.mean, np.sum]})
Out[20]:         
           mean       sum
dummy                    
1      0.036901  0.369012

ou sous forme de dictionnaire:

In [21]: df.groupby('dummy').agg({'returns':
                                  {'Mean': np.mean, 'Sum': np.sum}})
Out[21]: 
        returns          
           Mean       Sum
dummy                    
1      0.036901  0.369012
bmu
la source
5
Existe-t-il un moyen de spécifier les noms de colonne de résultat?
Ben
3
@Ben Je pense que vous devez utiliser un changement de nom par la suite. exemple par Tom Augspurger (voir cellule 25)
Stewbaca
1
@Ben: j'ai ajouté un exemple
bmu
10
@sparc_spread Le passage de plusieurs fonctions sous forme de liste est bien décrit dans la documentation pandas . Renommer et transmettre plusieurs fonctions en tant que dictionnaire sera obsolète dans une future version de pandas. Les détails se trouvent dans le journal des modifications 0.20 , que j'ai également résumé ailleurs sur SO .
joelostblom
3
Cela a déjà été dit, mais l'utilisation de dictionnaires pour renommer les colonnes de sortie à partir de l'âge est obsolète. Vous pouvez à la place spécifier une liste de tuples. Voyez cette réponse.
cs95
101

TLDR; Pandas groupby.agga une nouvelle syntaxe plus simple pour spécifier (1) des agrégations sur plusieurs colonnes et (2) plusieurs agrégations sur une colonne. Donc, pour faire cela pour les pandas> = 0,25 , utilisez

df.groupby('dummy').agg(Mean=('returns', 'mean'), Sum=('returns', 'sum'))

           Mean       Sum
dummy                    
1      0.036901  0.369012

OU

df.groupby('dummy')['returns'].agg(Mean='mean', Sum='sum')

           Mean       Sum
dummy                    
1      0.036901  0.369012

Pandas> = 0,25: Agrégation nommée

Pandas a changé le comportement de GroupBy.aggen faveur d'une syntaxe plus intuitive pour spécifier les agrégations nommées. Consultez la section de documentation 0.25 sur les améliorations ainsi que les problèmes GitHub pertinents GH18366 et GH26512 .

De la documentation,

Pour prendre en charge l'agrégation spécifique aux colonnes avec un contrôle sur les noms des colonnes de sortie, pandas accepte la syntaxe spéciale dans GroupBy.agg(), appelée «agrégation nommée», où

  • Les mots-clés sont les noms des colonnes de sortie
  • Les valeurs sont des tuples dont le premier élément est la colonne à sélectionner et le deuxième élément est l'agrégation à appliquer à cette colonne. Pandas fournit à pandas.NamedAgg namedtuple les champs ['column', 'aggfunc'] pour clarifier les arguments. Comme d'habitude, l'agrégation peut être un alias appelable ou une chaîne.

Vous pouvez maintenant passer un tuple via des arguments de mot-clé. Les tuples suivent le format de (<colName>, <aggFunc>).

import pandas as pd

pd.__version__                                                                                                                            
# '0.25.0.dev0+840.g989f912ee'

# Setup
df = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
                   'height': [9.1, 6.0, 9.5, 34.0],
                   'weight': [7.9, 7.5, 9.9, 198.0]
})

df.groupby('kind').agg(
    max_height=('height', 'max'), min_weight=('weight', 'min'),)

      max_height  min_weight
kind                        
cat          9.5         7.9
dog         34.0         7.5

Alternativement, vous pouvez utiliser pd.NamedAgg(essentiellement un namedtuple) qui rend les choses plus explicites.

df.groupby('kind').agg(
    max_height=pd.NamedAgg(column='height', aggfunc='max'), 
    min_weight=pd.NamedAgg(column='weight', aggfunc='min')
)

      max_height  min_weight
kind                        
cat          9.5         7.9
dog         34.0         7.5

C'est encore plus simple pour Series, il suffit de passer l'aggfunc à un argument mot-clé.

df.groupby('kind')['height'].agg(max_height='max', min_height='min')    

      max_height  min_height
kind                        
cat          9.5         9.1
dog         34.0         6.0       

Enfin, si vos noms de colonnes ne sont pas des identifiants python valides, utilisez un dictionnaire avec décompression:

df.groupby('kind')['height'].agg(**{'max height': 'max', ...})

Pandas <0,25

Dans les versions plus récentes des pandas menant jusqu'à 0,24, si vous utilisez un dictionnaire pour spécifier les noms de colonne pour la sortie d'agrégation, vous obtiendrez un FutureWarning:

df.groupby('dummy').agg({'returns': {'Mean': 'mean', 'Sum': 'sum'}})
# FutureWarning: using a dict with renaming is deprecated and will be removed 
# in a future version

L'utilisation d'un dictionnaire pour renommer les colonnes est obsolète dans la v0.20. Sur les versions plus récentes de pandas, cela peut être spécifié plus simplement en passant une liste de tuples. Si vous spécifiez les fonctions de cette manière, toutes les fonctions de cette colonne doivent être spécifiées sous forme de tuples de paires (nom, fonction).

df.groupby("dummy").agg({'returns': [('op1', 'sum'), ('op2', 'mean')]})

        returns          
            op1       op2
dummy                    
1      0.328953  0.032895

Ou,

df.groupby("dummy")['returns'].agg([('op1', 'sum'), ('op2', 'mean')])

            op1       op2
dummy                    
1      0.328953  0.032895
cs95
la source
4
Cela devrait être la meilleure réponse en raison de l'utilisation d'une solution plus claire et plus propre en utilisant la nouvelle version de l'interface.
NKSHELL
Les exemples utilisés pour l'agrégation nommée ne résolvent pas le problème d'origine de l'utilisation de plusieurs agrégations sur la même colonne. Par exemple, pouvez-vous agréger à la fois par minimum et maximum pour la hauteur sans premier sous-ensemble pour df.groupby('kind')['height']?
victor
1
@victor J'ai ajouté un TLDR en haut de la réponse qui répond directement à la question. Et la réponse à votre deuxième question est oui, veuillez jeter un œil à la modification de ma réponse.
cs95
Un code plus générique pour le dernier exemple de votre réponse> = 0,25 pour gérer l'agrégation de plusieurs colonnes comme cela aurait été génial. df.groupby("kind").agg(**{ 'max height': pd.NamedAgg(column='height', aggfunc=max), 'min weight': pd.NamedAgg(column='weight', aggfunc=min) })
Onur Ece
6

Quelque chose comme ce travail:

In [7]: df.groupby('dummy').returns.agg({'func1' : lambda x: x.sum(), 'func2' : lambda x: x.prod()})
Out[7]: 
              func2     func1
dummy                        
1     -4.263768e-16 -0.188565
Chang elle
la source
2
Non, cela ne fonctionne pas. Si vous regardez la chaîne doc car aggregateelle dit explicitement que quand a dictest passé, les clés doivent être des noms de colonnes. Donc, soit votre exemple est quelque chose que vous avez tapé sans vérifier cette erreur, soit Pandas rompt ses propres documents ici.
ely
N / MI n'a pas vu l'appel supplémentaire à l' returnsintérieur. Alors, c'est la version Series de l'agrégat? Je cherche à faire la version DataFrame de l'agrégat, et je souhaite appliquer plusieurs agrégations différentes à chaque colonne en même temps.
ely
1
Essayez ceci: df.groupby ('dummy'). Agg ({'renvoie': {'func1': lambda x: x.sum (), 'func2': lambda x: x.mean ()}})
Chang Elle du
Il donne une erreur d'assertion sans message. D'après l'apparence du code (pandas.core.internals.py, lignes 406-408, version 0.7.3), il semble qu'il effectue une vérification à la fin pour s'assurer qu'il ne renvoie pas plus de colonnes qu'il n'y a de clés dans la première couche du dictionnaire d'agrégation.
ely
Fonctionne bien sur le maître. Vous souhaitez essayer la mise à jour?
Chang She du