web-dev-qa-db-fra.com

Pandas groupby appliquer vs transformer avec des fonctions spécifiques

Je ne comprends pas quelles fonctions sont acceptables pour les opérations groupby + transform. Souvent, je finis par deviner, tester, revenir en arrière jusqu'à ce que quelque chose fonctionne, mais je pense qu'il devrait y avoir un moyen systématique de déterminer si une solution fonctionnera.

Voici un exemple minimal. Utilisons d'abord groupby + apply avec set:

df = pd.DataFrame({'a': [1,2,3,1,2,3,3], 'b':[1,2,3,1,2,3,3], 'type':[1,0,1,0,1,0,1]})

g = df.groupby(['a', 'b'])['type'].apply(set)

print(g)

a  b
1  1    {0, 1}
2  2    {0, 1}
3  3    {0, 1}

Cela fonctionne bien, mais je veux que le set résultant soit calculé par groupe dans une nouvelle colonne de la trame de données d'origine. J'essaie donc d'utiliser transform:

df['g'] = df.groupby(['a', 'b'])['type'].transform(set)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
---> 23 df['g'] = df.groupby(['a', 'b'])['type'].transform(set)

TypeError: int() argument must be a string, a bytes-like object or a number, not 'set'

C'est l'erreur que je vois dans Pandas v0.19.0. Dans v0.23.0, je vois TypeError: 'set' type is unordered. Bien sûr, je peux mapper un index spécifiquement défini pour atteindre mon résultat:

g = df.groupby(['a', 'b'])['type'].apply(set)
df['g'] = df.set_index(['a', 'b']).index.map(g.get)

print(df)

   a  b  type       g
0  1  1     1  {0, 1}
1  2  2     0  {0, 1}
2  3  3     1  {0, 1}
3  1  1     0  {0, 1}
4  2  2     1  {0, 1}
5  3  3     0  {0, 1}
6  3  3     1  {0, 1}

Mais je pensais que l'avantage de transform était d'éviter un mappage aussi explicite. Où est-ce que je me suis trompé?

11
jpp

Je crois, en premier lieu, qu'il y a de la place pour l'intuition dans l'utilisation de ces fonctions car elles peuvent être très significatives.

Dans votre premier résultat, vous n'essayez pas réellement de - transformer vos valeurs, mais plutôt de agréger celles-ci (ce qui fonctionnerait comme vous le souhaitiez).

Mais pour entrer dans le code, les documents transform sont assez suggestifs en disant que

Renvoie un résultat de la même taille que le bloc de groupe ou diffusable à la taille du bloc de groupe.

Quand tu fais

df.groupby(['a', 'b'])['type'].transform(some_func)

Vous êtes en fait transformant chaque pd.Series objet de chaque groupe dans un nouvel objet en utilisant votre some_func une fonction. Mais le fait est que ce nouvel objet devrait avoir la même taille que le groupe OU être diffusable à la taille du morceau.

Par conséquent, si vous transformez votre série à l'aide de Tuple ou list, vous transformerez essentiellement l'objet

0    1
1    2
2    3
dtype: int64

dans

[1,2,3]

Mais notez que ces valeurs sont maintenant attribuées en retour à leurs index respectifs et c'est pourquoi vous ne voyez aucune différence dans l'opération transform. La ligne qui avait le .iloc[0] valeur de pd.Series aura désormais le [1,2,3][0] valeur de la liste de transformation (la même chose s'appliquerait à Tuple) etc. Notez que ordre et taille ici est important, car sinon vous pourriez gâcher votre les groupes et la transformation ne fonctionneraient pas (et c'est exactement pourquoi set n'est pas une fonction appropriée à utiliser dans ce cas).


La deuxième partie du texte cité dit "diffusable à la taille du groupe".

Cela signifie que vous pouvez également transformer votre pd.Series à un objet qui peut être utilisé dans toutes les lignes. Par exemple

df.groupby(['a', 'b'])['type'].transform(lambda k: 50)

travaillerait. Pourquoi? même si 50 n'est pas itérable, c'est diffusable en utilisant cette valeur à plusieurs reprises dans toutes les positions de votre _ pd.Series.


Pourquoi pouvez-vous apply utiliser set?

Parce que la méthode apply n'a pas cette contrainte de size dans le résultat. Il a en fait trois différents types de résultats, et il déduit si vous voulez développer, réduire ou diffuser vos résultats. Notez que vous ne pouvez pas réduire en transformant *

Par défaut (result_type=None), le type de retour final est déduit du type de retour de la fonction appliquée. result_type: {'expand', 'Reduce', 'broadcast', None}, default None Ceux-ci n'agissent que lorsque axis=1 (Colonnes):

  1. "Développer": les résultats de type liste seront transformés en colonnes.

  2. "Réduire": renvoie une série si possible plutôt que d'étendre les résultats de type liste. C’est le contraire de "développer".

  3. "Diffusion": les résultats seront diffusés dans la forme d'origine du DataFrame, l'index et les colonnes d'origine seront conservés.

12
rafaelc

Le résultat de la transformation est limité à certains types. [Par exemple, il ne peut pas être list, set, Series etc. - Ceci est incorrect , merci @RafaelC pour le commentaire] Je ne pense pas que cela soit documenté, mais lors de l'examen du code source de groupby.py et series.py vous pouvez trouver ces restrictions de type.

Depuis le groupbydocumentation

La méthode transform renvoie un objet indexé de la même (même taille) que celui qui est groupé. La fonction de transformation doit:

  • Renvoie un résultat de la même taille que le bloc de groupe ou diffusable à la taille du bloc de groupe (par exemple, un scalaire, grouped.transform ( lambda x: x.iloc [-1])).
  • Faites fonctionner colonne par colonne sur le bloc de groupe. La transformation est appliquée au premier bloc de groupe à l'aide de chunk.apply.

  • Ne pas effectuer d'opérations sur place sur le bloc de groupe. Les blocs de groupe doivent être traités comme immuables et les modifications apportées à un bloc de groupe peuvent produire des résultats inattendus. Par exemple, lors de l'utilisation de fillna, inplace doit être False (grouped.transform (lambda x: x.fillna (inplace = False))).

  • (Facultatif) fonctionne sur la totalité du bloc de groupe. Si cela est pris en charge, un chemin rapide est utilisé à partir du deuxième bloc.

Avertissement: j'ai une erreur différente (pandas version 0.23.1):

df['g'] = df.groupby(['a', 'b'])['type'].transform(set)
File "***/lib/python3.6/site-packages/pandas/core/groupby/groupby.py", line 3661, in transform
s = klass(res, indexer)        s = klass(res, indexer)
File "***/lib/python3.6/site-packages/pandas/core/series.py", line 242, in __init__
"".format(data.__class__.__name__))
TypeError: 'set' type is unordered

Mise à jour

Après avoir transformé le groupe en un ensemble, pandas ne peut pas le diffuser vers le Series, car il n'est pas ordonné (et a des dimensions différentes de celles du groupe). Si nous le forçons dans une liste, il aura la même taille que le bloc de groupe et nous n'obtiendrons qu'une seule valeur par ligne. La réponse est de l'enrouler dans un conteneur, de sorte que la taille résultante de l'objet deviendra 1, puis pandas pourra le diffuser:

df['g'] = df.groupby(['a', 'b'])['type'].transform(lambda x: np.array(set(x)))
print(df)

   a  b  type       g
0  1  1     1  {0, 1}
1  2  2     0  {0, 1}
2  3  3     1  {0, 1}
3  1  1     0  {0, 1}
4  2  2     1  {0, 1}
5  3  3     0  {0, 1}
6  3  3     1  {0, 1}

Pourquoi j'ai choisi np.array comme conteneur? Car series.py (ligne 205: 206) passez ce type sans autre vérification. Je pense donc que ce comportement sera conservé dans les futures versions.

3
igrinis