python below Übernehmen vs. transform für ein Gruppenobjekt




python title distance (2)

Betrachten Sie den folgenden Datenrahmen:

     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

Die folgenden Befehle funktionieren:

> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())

aber keine der folgenden Arbeiten:

> 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

Warum? Das Beispiel in der Dokumentation scheint darauf hinzuweisen, dass das Aufrufen einer transform für eine Gruppe die Ausführung von zeilenweisen Operationen ermöglicht:

# 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)

Mit anderen Worten, ich dachte, dass Transformation im Wesentlichen eine spezifische Art von Anwendung (die, die nicht aggregiert) ist. Wo liege ich falsch?

Als Referenz folgt unten die Konstruktion des ursprünglichen Datenrahmens oben:

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)})

https://code.i-harness.com


Zwei Hauptunterschiede zwischen apply und transform

Es gibt zwei Hauptunterschiede zwischen den Methoden transform und apply groupby.

  • apply übergibt implizit alle Spalten für jede Gruppe als Datenrahmen an die benutzerdefinierte Funktion, während die transform jede Spalte für jede Gruppe als eine Serie an die benutzerdefinierte Funktion übergibt
  • Die benutzerdefinierte Funktion, die zum Anwenden übergeben wurde, kann einen Skalar oder einen Series- oder DataFrame (oder ein numpiges Array oder eine Liste) zurückgeben. Die an die transform benutzerdefinierte Funktion muss eine Sequenz (eine eindimensionale Reihe, ein Array oder eine Liste) in derselben Länge wie die Gruppe zurückgeben.

Die transform funktioniert also jeweils nur für eine Serie und apply Arbeiten gleichzeitig auf den gesamten DataFrame an.

Überprüfen der benutzerdefinierten Funktion

Es kann ziemlich hilfreich sein, die Eingabe für Ihre benutzerdefinierte Funktion zu überprüfen, die zum apply oder transform .

Beispiele

Lassen Sie uns einige Beispieldaten erstellen und untersuchen Sie die Gruppen, damit Sie sehen können, worüber ich rede:

df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'], 
                   'a':[4,5,1,3], 'b':[6,10,3,11]})
df

Lassen Sie uns eine einfache benutzerdefinierte Funktion erstellen, die den Typ des implizit übergebenen Objekts ausgibt und anschließend einen Fehler auslöst, sodass die Ausführung gestoppt werden kann.

def inspect(x):
    print(type(x))
    raise

Lassen Sie uns nun diese Funktion an die groupby-Methoden apply und transform , um zu sehen, welches Objekt an sie übergeben wird:

df.groupby('State').apply(inspect)

<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError

Wie Sie sehen, wird ein DataFrame an die inspect-Funktion übergeben. Sie wundern sich vielleicht, warum der Typ DataFrame zweimal ausgedruckt wurde. Pandas führt die erste Gruppe zweimal. Es stellt fest, ob es einen schnellen Weg gibt, die Berechnung zu vervollständigen oder nicht. Dies ist ein kleines Detail, um das Sie sich keine Sorgen machen sollten.

Jetzt machen wir das Gleiche mit transform

df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError

Es ist eine Serie - ein völlig anderes Pandas-Objekt.

transform darf also nur mit jeweils einer einzelnen Serie arbeiten. Es ist unmöglich, auf zwei Spalten gleichzeitig zu agieren. Wenn wir also die Spalte a von b innerhalb unserer benutzerdefinierten Funktion versuchen und abziehen, erhalten wir einen Fehler bei der transform . Siehe unten:

def subtract_two(x):
    return x['a'] - x['b']

df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')

Wir erhalten einen KeyError, während Pandas versucht, den Serienindex a zu finden, der nicht existiert. Sie können diese Operation mit apply abschließen apply da sie den gesamten DataFrame enthält:

df.groupby('State').apply(subtract_two)

State     
Florida  2   -2
         3   -8
Texas    0   -2
         1   -5
dtype: int64

Die Ausgabe ist eine Serie und etwas verwirrend, da der ursprüngliche Index beibehalten wird, aber wir haben Zugriff auf alle Spalten.

Das übergebene Pandas Objekt anzeigen

Es kann noch mehr helfen, das gesamte Pandas-Objekt innerhalb der benutzerdefinierten Funktion anzuzeigen, so dass Sie genau sehen können, mit was Sie arbeiten. Sie können print Anweisungen verwenden, indem ich die display aus dem IPython.display Modul verwende, so dass die DataFrames in einem jupyter Notizbuch schön in HTML ausgegeben werden:

from IPython.display import display
def subtract_two(x):
    display(x)
    return x['a'] - x['b']

Bildschirmfoto:

Die Transformation muss eine eindimensionale Sequenz mit derselben Größe wie die Gruppe zurückgeben

Der andere Unterschied besteht darin, dass die transform eine eindimensionale Sequenz mit derselben Größe wie die Gruppe zurückgeben muss. In dieser bestimmten Instanz hat jede Gruppe zwei Zeilen, daher muss transform eine Sequenz von zwei Zeilen zurückgeben. Wenn dies nicht der Fall ist, wird ein Fehler ausgelöst:

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

Die Fehlermeldung ist nicht wirklich beschreibend für das Problem. Sie müssen eine Sequenz mit der gleichen Länge wie die Gruppe zurückgeben. Also, eine Funktion wie diese würde funktionieren:

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

Die Rückgabe eines einzelnen Skalarobjekts funktioniert auch für die transform

Wenn Sie nur einen einzelnen Skalar von Ihrer benutzerdefinierten Funktion zurückgeben, wird sie von transform für jede der Zeilen in der Gruppe verwendet:

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

Als ich mich ähnlich verwirrt fühlte mit .transform operation vs. .apply fand ich ein paar Antworten, die etwas Licht auf das Thema .apply . Diese Antwort war zum Beispiel sehr hilfreich.

Mein Take-Out ist bisher, dass .transform mit Series (Spalten) isoliert voneinander arbeitet (oder handelt). Was das bedeutet ist, dass in Ihren letzten zwei Anrufe:

df.groupby('A').transform(lambda x: (x['C'] - x['D']))
df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())

Sie haben .transform , um Werte aus zwei Spalten zu nehmen, und "es" sieht tatsächlich beide nicht gleichzeitig "um" (sozusagen). transform wird die Datenframe-Spalten nacheinander betrachten und eine Reihe (oder eine Gruppe von Reihen) zurückgeben, die aus Skalaren besteht, die wiederholt len(input_column) -mal sind.

Dieser Skalar, der von .transform verwendet werden .transform , um die Series .transform , ist also das Ergebnis einer Reduktionsfunktion, die auf eine Eingabe- Series angewendet wurde (und nur auf EINE Reihe / Spalte gleichzeitig).

Betrachten Sie dieses Beispiel (auf Ihrem Datenrahmen):

zscore = lambda x: (x - x.mean()) / x.std() # Note that it does not reference anything outside of 'x' and for transform 'x' is one column.
df.groupby('A').transform(zscore)

wird ergeben:

       C      D
0  0.989  0.128
1 -0.478  0.489
2  0.889 -0.589
3 -0.671 -1.150
4  0.034 -0.285
5  1.149  0.662
6 -1.404 -0.907
7 -0.509  1.653

Das ist genau so, als würden Sie es nur für jeweils eine Spalte verwenden:

df.groupby('A')['C'].transform(zscore)

ergebend:

0    0.989
1   -0.478
2    0.889
3   -0.671
4    0.034
5    1.149
6   -1.404
7   -0.509

Beachten Sie, dass .apply im letzten Beispiel ( df.groupby('A')['C'].apply(zscore) ) genauso funktionieren würde, aber es würde fehlschlagen, wenn Sie es in einem Datenframe verwenden würden:

df.groupby('A').apply(zscore)

gibt Fehler:

ValueError: operands could not be broadcast together with shapes (6,) (2,)

Wo sonst ist .transform nützlich? Der einfachste Fall besteht darin, die Ergebnisse der Reduktionsfunktion dem ursprünglichen Datenrahmen zuzuordnen.

df['sum_C'] = df.groupby('A')['C'].transform(sum)
df.sort('A') # to clearly see the scalar ('sum') applies to the whole column of the group

ergebend:

     A      B      C      D  sum_C
1  bar    one  1.998  0.593  3.973
3  bar  three  1.287 -0.639  3.973
5  bar    two  0.687 -1.027  3.973
4  foo    two  0.205  1.274  4.373
2  foo    two  0.128  0.924  4.373
6  foo    one  2.113 -0.516  4.373
7  foo  three  0.657 -1.179  4.373
0  foo    one  1.270  0.201  4.373

Das gleiche mit .apply zu .apply würde NaNs in sum_C . Weil .apply eine reduzierte Series zurückgibt, von der sie nicht weiß, wie sie .apply :

df.groupby('A')['C'].apply(sum)

geben:

A
bar    3.973
foo    4.373

Es gibt auch Fälle, in denen .transform zum Filtern der Daten verwendet wird:

df[df.groupby(['B'])['D'].transform(sum) < -1]

     A      B      C      D
3  bar  three  1.287 -0.639
7  foo  three  0.657 -1.179

Ich hoffe, das bringt ein bisschen mehr Klarheit.





pandas