Considérez le dataframe suivant:
A B C D
0 foo one 0.162003 0.087469
1 bar one -1.156319 -1.526272
2 foo two 0.833892 -1.666304
3 bar three -2.026673 -0.322057
4 foo two 0.411452 -0.954371
5 bar two 0.765878 -0.095968
6 foo one -0.654890 0.678091
7 foo three -1.789842 -1.130922
Les commandes suivantes fonctionnent:
> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())
mais aucun des travaux suivants:
> df.groupby('A').transform(lambda x: (x['C'] - x['D']))
ValueError: could not broadcast input array from shape (5) into shape (5,3)
> df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
TypeError: cannot concatenate a non-NDFrame object
Pourquoi? L'exemple de la documentation semble suggérer que l'appel transform
à un groupe permet d'effectuer un traitement d'opération par ligne:
# Note that the following suggests row-wise operation (x.mean is the column mean)
zscore = lambda x: (x - x.mean()) / x.std()
transformed = ts.groupby(key).transform(zscore)
En d'autres termes, je pensais que la transformation est essentiellement un type d'application spécifique (celui qui ne s'agrège pas). Où ai-je tort?
Pour référence, voici la construction de la base de données originale ci-dessus:
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
'foo', 'bar', 'foo', 'foo'],
'B' : ['one', 'one', 'two', 'three',
'two', 'two', 'one', 'three'],
'C' : randn(8), 'D' : randn(8)})
transform
doit renvoyer un nombre, une ligne ou la même forme que l'argument. s'il s'agit d'un nombre, le nombre sera défini sur tous les éléments du groupe, s'il s'agit d'une ligne, il sera diffusé sur toutes les lignes du groupe. Dans votre code, la fonction lambda renvoie une colonne qui ne peut pas être diffusée vers le groupe.zscore
),transform
reçoit une fonction lambda qui suppose que chacunx
est un élément dans legroup
, et renvoie également une valeur par élément dans le groupe. Qu'est-ce que je rate?apply
passe dans le df entier, maistransform
passe chaque colonne individuellement comme une série. 2)apply
peut renvoyer n'importe quelle sortie de forme (scalaire / Series / DataFrame / array / list ...), alors quetransform
doit renvoyer une séquence (1D Series / array / list) de la même longueur que le groupe. C'est pourquoi le POapply()
n'en a pas besointransform()
. C'est une bonne question car le doc n'a pas expliqué clairement les deux différences. (semblable à la distinction entreapply/map/applymap
, ou d'autres choses ...)Réponses:
Deux différences majeures entre
apply
ettransform
Il existe deux différences majeures entre les méthodes
transform
etapply
groupby.apply
transmet implicitement toutes les colonnes de chaque groupe en tant que DataFrame à la fonction personnalisée.transform
transmet chaque colonne de chaque groupe individuellement en tant que série à la fonction personnalisée.apply
peut renvoyer un scalaire, ou une Series ou DataFrame (ou un tableau numpy ou même une liste) .transform
doit renvoyer une séquence (une série, un tableau ou une liste à une dimension) de la même longueur que le groupe .Ainsi,
transform
fonctionne sur une seule série à la fois etapply
fonctionne sur l'ensemble du DataFrame à la fois.Inspection de la fonction personnalisée
Cela peut aider beaucoup d'inspecter l'entrée de votre fonction personnalisée passée à
apply
outransform
.Exemples
Créons des exemples de données et inspectons les groupes afin que vous puissiez voir de quoi je parle:
Créons une fonction personnalisée simple qui imprime le type de l'objet passé implicitement, puis génère une erreur afin que l'exécution puisse être arrêtée.
Passons maintenant cette fonction à la fois au groupby
apply
et auxtransform
méthodes pour voir quel objet lui est passé:Comme vous pouvez le voir, un DataFrame est passé dans le
inspect
fonction. Vous vous demandez peut-être pourquoi le type, DataFrame, a été imprimé deux fois. Pandas dirige le premier groupe deux fois. Il fait cela pour déterminer s'il existe un moyen rapide de terminer le calcul ou non. C'est un détail mineur dont vous ne devriez pas vous inquiéter.Maintenant, faisons la même chose avec
transform
Il est passé une série - un objet Pandas totalement différent.
Ainsi, il
transform
n'est autorisé à travailler qu'avec une seule série à la fois. Il n'est pas impossible qu'il agisse sur deux colonnes en même temps. Donc, si nous essayons de soustraire la colonnea
de l'b
intérieur de notre fonction personnalisée, nous obtiendrions une erreur avectransform
. Voir ci-dessous:Nous obtenons une KeyError lorsque pandas tente de trouver l'index Series
a
qui n'existe pas. Vous pouvez terminer cette opération avecapply
car il a le DataFrame entier:La sortie est une série et un peu déroutante car l'index d'origine est conservé, mais nous avons accès à toutes les colonnes.
Affichage de l'objet pandas passé
Il peut être encore plus utile d'afficher l'objet pandas entier dans la fonction personnalisée, afin que vous puissiez voir exactement avec quoi vous travaillez. Vous pouvez utiliser
print
instructions de I like pour utiliser ladisplay
fonction duIPython.display
module afin que les DataFrames soient bien générés en HTML dans un notebook jupyter:Capture d'écran:
La transformation doit renvoyer une séquence unidimensionnelle de la même taille que le groupe
L'autre différence est que
transform
doit renvoyer une séquence unidimensionnelle de la même taille que le groupe. Dans ce cas particulier, chaque groupe a deux lignes ettransform
doit donc renvoyer une séquence de deux lignes. Si ce n'est pas le cas, une erreur est générée:Le message d'erreur n'est pas vraiment descriptif du problème. Vous devez renvoyer une séquence de la même longueur que le groupe. Donc, une fonction comme celle-ci fonctionnerait:
Le renvoi d'un seul objet scalaire fonctionne également pour
transform
Si vous ne renvoyez qu'un seul scalaire de votre fonction personnalisée, vous
transform
l'utiliserez pour chacune des lignes du groupe:la source
np
n'est pas défini. Je suppose que les débutants apprécieraient si vous incluezimport numpy as np
dans votre réponse.Comme je me sentais de la même manière confus avec l'
.transform
opération par rapport à,.apply
j'ai trouvé quelques réponses pour éclairer le problème. Cette réponse, par exemple, a été très utile.Mon plat à emporter jusqu'à présent est que
.transform
cela fonctionnera (ou traitera)Series
(colonnes) isolément les unes des autres . Cela signifie que lors de vos deux derniers appels:Vous avez demandé
.transform
de prendre des valeurs de deux colonnes et «il» ne les «voit» pas tous les deux en même temps (pour ainsi dire).transform
examinera les colonnes du dataframe une par une et retournera une série (ou un groupe de séries) `` fait '' de scalaires qui se répètentlen(input_column)
fois.Donc ce scalaire, qui devrait être utilisé par
.transform
pour faire le,Series
est le résultat d'une fonction de réduction appliquée sur une entréeSeries
(et seulement sur UNE série / colonne à la fois).Considérez cet exemple (sur votre dataframe):
donnera:
C'est exactement la même chose que si vous ne l'utilisiez que sur une seule colonne à la fois:
cédant:
Notez que
.apply
dans le dernier exemple (df.groupby('A')['C'].apply(zscore)
) fonctionnerait exactement de la même manière, mais échouerait si vous essayiez de l'utiliser sur un dataframe:donne une erreur:
Alors, où d'autre est
.transform
utile? Le cas le plus simple consiste à essayer d'attribuer les résultats de la fonction de réduction à la trame de données d'origine.cédant:
Essayer la même chose avec
.apply
donneraitNaNs
àsum_C
. Car.apply
retournerait un réduitSeries
, qu'il ne sait pas rediffuser:donnant:
Il existe également des cas où
.transform
est utilisé pour filtrer les données:J'espère que cela ajoute un peu plus de clarté.
la source
.transform()
pourrait également être utilisé pour remplir les valeurs manquantes. Surtout si vous souhaitez diffuser une moyenne de groupe ou une statistique de groupe auxNaN
valeurs de ce groupe. Malheureusement, la documentation des pandas ne m'a pas non plus été utile..groupby().filter()
fait la même chose. Merci pour votre explication.apply()
et.transform()
cela me rend également très confus.df.groupby().transform()
ne peut pas travailler pour un sous-groupe df, j'obtiens toujours l'erreurValueError: transform must return a scalar value for each group
cartransform
voit les colonnes une par uneJe vais utiliser un extrait très simple pour illustrer la différence:
Le DataFrame ressemble à ceci:
Il y a 3 identifiants clients dans ce tableau, chaque client a effectué trois transactions et payé 1,2,3 dollars à chaque fois.
Maintenant, je veux trouver le paiement minimum effectué par chaque client. Il existe deux façons de procéder:
En utilisant
apply
:grouping.min ()
Le retour ressemble à ceci:
En utilisant
transform
:grouping.transform (min)
Le retour ressemble à ceci:
Les deux méthodes renvoient un
Series
objet, mais lalength
de la première est 3 et lalength
de la seconde est 9.Si vous voulez répondre
What is the minimum price paid by each customer
, alors laapply
méthode est la plus appropriée.Si vous souhaitez répondre
What is the difference between the amount paid for each transaction vs the minimum payment
, vous souhaitez utilisertransform
, car:Apply
ne fonctionne pas ici simplement parce qu'il renvoie une série de taille 3, mais la longueur du df d'origine est de 9. Vous ne pouvez pas l'intégrer facilement au df d'origine.la source
est comme
ou
la source