Deux différences majeures entre apply
ettransform
Il existe deux différences majeures entre les méthodes transform
et apply
groupby.
- Contribution:
apply
transmet implicitement toutes les colonnes de chaque groupe en tant que DataFrame à la fonction personnalisée.
- while
transform
transmet chaque colonne de chaque groupe individuellement en tant que série à la fonction personnalisée.
- Production:
- La fonction personnalisée passée à
apply
peut renvoyer un scalaire, ou une Series ou DataFrame (ou un tableau numpy ou même une liste) .
- La fonction personnalisée passée à
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 et apply
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
ou transform
.
Exemples
Créons des exemples de données et inspectons les groupes afin que vous puissiez voir de quoi je parle:
import pandas as pd
import numpy as np
df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'],
'a':[4,5,1,3], 'b':[6,10,3,11]})
State a b
0 Texas 4 6
1 Texas 5 10
2 Florida 1 3
3 Florida 3 11
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.
def inspect(x):
print(type(x))
raise
Passons maintenant cette fonction à la fois au groupby apply
et aux transform
méthodes pour voir quel objet lui est passé:
df.groupby('State').apply(inspect)
<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError
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
df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError
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 colonne a
de l' b
intérieur de notre fonction personnalisée, nous obtiendrions une erreur avec transform
. Voir ci-dessous:
def subtract_two(x):
return x['a'] - x['b']
df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')
Nous obtenons une KeyError lorsque pandas tente de trouver l'index Series a
qui n'existe pas. Vous pouvez terminer cette opération avec apply
car il a le DataFrame entier:
df.groupby('State').apply(subtract_two)
State
Florida 2 -2
3 -8
Texas 0 -2
1 -5
dtype: int64
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 utiliserprint
instructions de I like pour utiliser la display
fonction du IPython.display
module afin que les DataFrames soient bien générés en HTML dans un notebook jupyter:
from IPython.display import display
def subtract_two(x):
display(x)
return x['a'] - x['b']
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 et transform
doit donc renvoyer une séquence de deux lignes. Si ce n'est pas le cas, une erreur est générée:
def return_three(x):
return np.array([1, 2, 3])
df.groupby('State').transform(return_three)
ValueError: transform must return a scalar value for each group
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:
def rand_group_len(x):
return np.random.rand(len(x))
df.groupby('State').transform(rand_group_len)
a b
0 0.962070 0.151440
1 0.440956 0.782176
2 0.642218 0.483257
3 0.056047 0.238208
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:
def group_sum(x):
return x.sum()
df.groupby('State').transform(group_sum)
a b
0 9 16
1 9 16
2 4 14
3 4 14
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.