Les docs montrent comment appliquer plusieurs fonctions à la fois sur un objet groupby à l'aide d'un dict avec les noms des colonnes de sortie comme clés:
In [563]: grouped['D'].agg({'result1' : np.sum,
.....: 'result2' : np.mean})
.....:
Out[563]:
result2 result1
A
bar -0.579846 -1.739537
foo -0.280588 -1.402938
Toutefois, cela ne fonctionne que sur un objet Series groupby. Et lorsqu'un dict est également transmis à un groupe par DataFrame, il s'attend à ce que les clés correspondent aux noms de colonne auxquels la fonction sera appliquée.
Ce que je veux faire, c'est appliquer plusieurs fonctions à plusieurs colonnes (mais certaines colonnes seront exploitées plusieurs fois). En outre, certaines fonctions dépendent d'autres colonnes de l'objet groupby (comme les fonctions sumif). Ma solution actuelle consiste à aller colonne par colonne et à faire quelque chose comme le code ci-dessus, en utilisant lambdas pour des fonctions qui dépendent d'autres lignes. Mais cela prend beaucoup de temps (je pense qu’il faut beaucoup de temps pour parcourir un objet groupby). Je devrai le changer pour pouvoir parcourir l'ensemble de l'objet groupby en une seule fois, mais je me demande s'il existe une méthode intégrée dans pandas pour le faire de manière plus propre.
Par exemple, j'ai essayé quelque chose comme
grouped.agg({'C_sum' : lambda x: x['C'].sum(),
'C_std': lambda x: x['C'].std(),
'D_sum' : lambda x: x['D'].sum()},
'D_sumifC3': lambda x: x['D'][x['C'] == 3].sum(), ...)
mais comme prévu, j'obtiens une erreur KeyError (car les clés doivent être une colonne si agg
est appelé à partir d'un DataFrame).
Existe-t-il une méthode intégrée permettant de faire ce que j'aimerais faire, ou une possibilité que cette fonctionnalité soit ajoutée, ou devrais-je simplement parcourir le groupe manuellement?
Merci
La seconde moitié de la réponse actuellement acceptée est obsolète et comporte deux dépréciations. Tout d’abord et surtout, vous ne pouvez plus transmettre un dictionnaire de dictionnaires à la méthode agg
groupby. Deuxièmement, n'utilisez jamais .ix
.
Si vous souhaitez utiliser deux colonnes distinctes en même temps, je vous suggérerais d'utiliser la méthode apply
qui transmet implicitement un DataFrame à la fonction appliquée. Utilisons un dataframe similaire à celui du dessus
df = pd.DataFrame(np.random.Rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df
a b c d group
0 0.418500 0.030955 0.874869 0.145641 0
1 0.446069 0.901153 0.095052 0.487040 0
2 0.843026 0.936169 0.926090 0.041722 1
3 0.635846 0.439175 0.828787 0.714123 1
Un dictionnaire mappé à partir de noms de colonnes sur des fonctions d'agrégation reste un moyen parfaitement efficace pour effectuer une agrégation.
df.groupby('group').agg({'a':['sum', 'max'],
'b':'mean',
'c':'sum',
'd': lambda x: x.max() - x.min()})
a b c d
sum max mean sum <lambda>
group
0 0.864569 0.446069 0.466054 0.969921 0.341399
1 1.478872 0.843026 0.687672 1.754877 0.672401
Si vous n'aimez pas ce nom de colonne lambda, vous pouvez utiliser une fonction normale et attribuer un nom personnalisé à l'attribut spécial __name__
, comme ceci:
def max_min(x):
return x.max() - x.min()
max_min.__= 'Max minus Min'
df.groupby('group').agg({'a':['sum', 'max'],
'b':'mean',
'c':'sum',
'd': max_min})
a b c d
sum max mean sum Max minus Min
group
0 0.864569 0.446069 0.466054 0.969921 0.341399
1 1.478872 0.843026 0.687672 1.754877 0.672401
apply
et renvoyer une sérieMaintenant, si vous avez plusieurs colonnes qui doivent interagir ensemble, vous ne pouvez pas utiliser agg
, qui passe implicitement une série à la fonction d'agrégation. Lorsque vous utilisez apply
, l'ensemble du groupe en tant que DataFrame est transmis à la fonction.
Je recommande de créer une fonction personnalisée unique qui renvoie une série de toutes les agrégations. Utilisez l'index de la série comme étiquettes pour les nouvelles colonnes:
def f(x):
d = {}
d['a_sum'] = x['a'].sum()
d['a_max'] = x['a'].max()
d['b_mean'] = x['b'].mean()
d['c_d_prodsum'] = (x['c'] * x['d']).sum()
return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum'])
df.groupby('group').apply(f)
a_sum a_max b_mean c_d_prodsum
group
0 0.864569 0.446069 0.466054 0.173711
1 1.478872 0.843026 0.687672 0.630494
Si vous êtes amoureux de MultiIndexes, vous pouvez toujours renvoyer une série avec celle-ci:
def f_mi(x):
d = []
d.append(x['a'].sum())
d.append(x['a'].max())
d.append(x['b'].mean())
d.append((x['c'] * x['d']).sum())
return pd.Series(d, index=[['a', 'a', 'b', 'c_d'],
['sum', 'max', 'mean', 'prodsum']])
df.groupby('group').apply(f_mi)
a b c_d
sum max mean prodsum
group
0 0.864569 0.446069 0.466054 0.173711
1 1.478872 0.843026 0.687672 0.630494
Pour la première partie, vous pouvez passer un dict de noms de colonnes pour les clés et une liste de fonctions pour les valeurs:
In [28]: df
Out[28]:
A B C D E GRP
0 0.395670 0.219560 0.600644 0.613445 0.242893 0
1 0.323911 0.464584 0.107215 0.204072 0.927325 0
2 0.321358 0.076037 0.166946 0.439661 0.914612 1
3 0.133466 0.447946 0.014815 0.130781 0.268290 1
In [26]: f = {'A':['sum','mean'], 'B':['prod']}
In [27]: df.groupby('GRP').agg(f)
Out[27]:
A B
sum mean prod
GRP
0 0.719580 0.359790 0.102004
1 0.454824 0.227412 0.034060
MISE À JOUR 1:
Étant donné que la fonction d'agrégation fonctionne sur Series, les références aux autres noms de colonne sont perdues. Pour contourner ce problème, vous pouvez référencer l’ensemble de la structure de données et l’indexer à l’aide des index de groupe de la fonction lambda.
Voici une solution de contournement:
In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.loc[g.index].E.sum()}
In [69]: df.groupby('GRP').agg(f)
Out[69]:
A B D
sum mean prod <lambda>
GRP
0 0.719580 0.359790 0.102004 1.170219
1 0.454824 0.227412 0.034060 1.182901
Ici, la colonne "D" résultante est composée des valeurs "E" additionnées.
MISE À JOUR 2:
Voici une méthode qui, je pense, fera tout ce que vous demandez. Commencez par créer une fonction lambda personnalisée. Ci-dessous, g fait référence au groupe. Lors de l'agrégation, g sera une série. Passer de g.index
à df.ix[]
sélectionne le groupe actuel de df. Je teste ensuite si la colonne C est inférieure à 0,5. La série booléenne renvoyée est transmise à g[]
qui sélectionne uniquement les lignes répondant aux critères.
In [95]: cust = lambda g: g[df.loc[g.index]['C'] < 0.5].sum()
In [96]: f = {'A':['sum','mean'], 'B':['prod'], 'D': {'my name': cust}}
In [97]: df.groupby('GRP').agg(f)
Out[97]:
A B D
sum mean prod my name
GRP
0 0.719580 0.359790 0.102004 0.204072
1 0.454824 0.227412 0.034060 0.570441
Comme alternative (principalement esthétique) à la réponse de Ted Petrou, j’ai préféré une liste légèrement plus compacte. N'envisagez pas de l'accepter, il s'agit simplement d'un commentaire beaucoup plus détaillé sur la réponse de Ted, plus le code/les données. Python/Pandas n’est pas mon premier/meilleur, mais j’ai trouvé que cela lisait bien:
df.groupby('group') \
.apply(lambda x: pd.Series({
'a_sum' : x['a'].sum(),
'a_max' : x['a'].max(),
'b_mean' : x['b'].mean(),
'c_d_prodsum' : (x['c'] * x['d']).sum()
})
)
a_sum a_max b_mean c_d_prodsum
group
0 0.530559 0.374540 0.553354 0.488525
1 1.433558 0.832443 0.460206 0.053313
Je le trouve plus proche de dplyr
pipes et de data.table
commandes chaînées. Pour ne pas dire qu'ils sont meilleurs, mais me sont plus familiers. (Je reconnais certainement le pouvoir et, pour beaucoup, la préférence d'utiliser des fonctions plus formalisées def
pour ces types d'opérations. Ceci est juste une alternative, pas nécessairement meilleure.)
J'ai généré des données de la même manière que Ted, je vais ajouter une graine pour la reproductibilité.
import numpy as np
np.random.seed(42)
df = pd.DataFrame(np.random.Rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df
a b c d group
0 0.374540 0.950714 0.731994 0.598658 0
1 0.156019 0.155995 0.058084 0.866176 0
2 0.601115 0.708073 0.020584 0.969910 1
3 0.832443 0.212339 0.181825 0.183405 1
La réponse de Ted est incroyable. J'ai fini par utiliser une version plus petite de celle-ci au cas où quelqu'un serait intéressé. Utile lorsque vous recherchez une agrégation qui dépend de valeurs provenant de plusieurs colonnes:
df=pd.DataFrame({'a': [1,2,3,4,5,6], 'b': [1,1,0,1,1,0], 'c': ['x','x','y','y','z','z']})
a b c
0 1 1 x
1 2 1 x
2 3 0 y
3 4 1 y
4 5 1 z
5 6 0 z
df.groupby('c').apply(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())
c
x 2.0
y 4.0
z 5.0
J'aime cette approche car je peux toujours utiliser l'agrégat. Peut-être que les gens me diront pourquoi il est nécessaire d'appliquer pour obtenir plusieurs colonnes lors de l'agrégation de groupes.
Cela semble évident maintenant, mais tant que vous ne sélectionnez pas la colonne d'intérêt directement après le groupby, vous aurez accès à toutes les colonnes du bloc de données à partir de votre fonction d'agrégation.
df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())
df.groupby('c').aggregate(lambda x: x[(x['a']>1) & (x['b']==1)].mean())['a']
df.groupby('c').aggregate(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())
J'espère que ça aide.
Pandas >= 0.25.0
, agrégations nomméesDepuis pandas version 0.25.0
ou supérieur, nous nous éloignons de l'agrégation par le dictionnaire et du renommage, pour nous diriger vers agrégations nommées qui accepte une Tuple
. Maintenant, nous pouvons simultanément agréger + renommer en un nom de colonne plus informatif:
Exemple :
df = pd.DataFrame(np.random.Rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
a b c d group
0 0.521279 0.914988 0.054057 0.125668 0
1 0.426058 0.828890 0.784093 0.446211 0
2 0.363136 0.843751 0.184967 0.467351 1
3 0.241012 0.470053 0.358018 0.525032 1
Appliquez GroupBy.agg
avec l'agrégation nommée:
df.groupby('group').agg(
a_sum=('a', 'sum'),
a_mean=('a', 'mean'),
b_mean=('b', 'mean'),
c_sum=('c', 'sum'),
d_range=('d', lambda x: x.max() - x.min())
)
a_sum a_mean b_mean c_sum d_range
group
0 0.947337 0.473668 0.871939 0.838150 0.320543
1 0.604149 0.302074 0.656902 0.542985 0.057681