Dans une vidéo de coursera sur Python Pandas groupby (dans l'introduction à la science des données dans Python), l'exemple suivant est donné:
df.groupby('Category').apply(lambda df,a,b: sum(df[a] * df[b]), 'Weight (oz.)', 'Quantity')
Où df est un DataFrame et le lambda est appliqué pour calculer la somme de deux colonnes. Si je comprends bien, l'objet groupby (renvoyé par groupby) auquel la fonction apply est appelée est une série de tuples comprenant l'index qui a été groupé par et la partie du DataFrame qui est ce regroupement spécifique.
Ce que je ne comprends pas, c'est la façon dont le lambda est utilisé:
Il y a trois arguments spécifiés (lambda df, a, b), mais seulement deux sont explicitement passés ('Weight (oz.)' Et 'Quantity'). Comment l'interprète sait-il que les arguments "a" et "b" sont ceux spécifiés comme arguments et que df est utilisé "tel quel"?
J'ai regardé la documentation mais je n'ai pas pu trouver de réponse définitive pour un exemple aussi précis. Je pense que cela doit faire quelque chose avec df étant dans la portée mais ne peux pas trouver d'informations pour soutenir et détailler cette pensée.
La méthode apply passe elle-même chaque "groupe" de l'objet groupby comme premier argument à la fonction. Il sait donc associer "Poids" et "Quantité" à a
et b
en fonction de la position. (par exemple, ce sont les 2e et 3e arguments si vous comptez le premier argument "groupe".
df = pd.DataFrame(np.random.randint(0,11,(10,3)), columns = ['num1','num2','num3'])
df['category'] = ['a','a','a','b','b','b','b','c','c','c']
df = df[['category','num1','num2','num3']]
df
category num1 num2 num3
0 a 2 5 2
1 a 5 5 2
2 a 7 3 4
3 b 10 9 1
4 b 4 7 6
5 b 0 5 2
6 b 7 7 5
7 c 2 2 1
8 c 4 3 2
9 c 1 4 6
gb = df.groupby('category')
l'argument implicite est chaque "groupe" ou dans ce cas chaque catégorie
gb.apply(lambda grp: grp.sum())
Le "grp" est le premier argument de la fonction lambda. Je n'ai pas besoin de spécifier quoi que ce soit pour cela car il est déjà, pris automatiquement pour être chaque groupe de l'objet groupby
category num1 num2 num3
category
a aaa 14 13 8
b bbbb 21 28 14
c ccc 7 9 9
Donc, appliquer passe par chacun d'eux et effectue une opération de somme
print(gb.groups)
{'a': Int64Index([0, 1, 2], dtype='int64'), 'b': Int64Index([3, 4, 5, 6], dtype='int64'), 'c': Int64Index([7, 8, 9], dtype='int64')}
print('1st GROUP:\n', df.loc[gb.groups['a']])
1st GROUP:
category num1 num2 num3
0 a 2 5 2
1 a 5 5 2
2 a 7 3 4
print('SUM of 1st group:\n', df.loc[gb.groups['a']].sum())
SUM of 1st group:
category aaa
num1 14
num2 13
num3 8
dtype: object
Remarquez comment cela est le même que la première ligne de notre opération précédente
Donc, appliquer implicitement passer chaque groupe à l'argument de fonction comme premier argument.
De la docs
GroupBy.apply (func, * args, ** kwargs)
args, kwargs: Tuple et dict
Arguments de position et de mot-clé facultatifs à passer à func
Les arguments supplémentaires passés dans "* args" sont passés après l'argument de groupe implicite.
donc en utilisant votre code
gb.apply(lambda df,a,b: sum(df[a] * df[b]), 'num1', 'num2')
category
a 56
b 167
c 20
dtype: int64
ici 'num1' et 'num2' sont passés comme arguments supplémentaires à chaque appel de la fonction lambda
Donc, appliquer passe par chacun d'eux et effectue votre opération lambda
# copy and paste your lambda function
fun = lambda df,a,b: sum(df[a] * df[b])
print(gb.groups)
{'a': Int64Index([0, 1, 2], dtype='int64'), 'b': Int64Index([3, 4, 5, 6], dtype='int64'), 'c': Int64Index([7, 8, 9], dtype='int64')}
print('1st GROUP:\n', df.loc[gb.groups['a']])
1st GROUP:
category num1 num2 num3
0 a 2 5 2
1 a 5 5 2
2 a 7 3 4
print('Output of 1st group for function "fun":\n',
fun(df.loc[gb.groups['a']], 'num1','num2'))
Output of 1st group for function "fun":
56